music_cmd.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 11 Oct 2009 08:40:35 +0000
changeset 447 50248d0ec654
parent 386 695494546b3c
child 467 4b52401dda92
permissions -rw-r--r--
Fixed bug #357

This was a sneaky one.
When music loops, the looping music play call is done from the audio thread, and SDL threads block signals to avoid signal side effects. So we just need to unblock those signals before executing the new process.
     1 /*
     2     SDL_mixer:  An audio mixer library based on the SDL library
     3     Copyright (C) 1997-2009 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Library General Public
     7     License as published by the Free Software Foundation; either
     8     version 2 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Library General Public License for more details.
    14 
    15     You should have received a copy of the GNU Library General Public
    16     License along with this library; if not, write to the Free
    17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 #include "SDL_config.h"
    23 
    24 /* This file supports an external command for playing music */
    25 
    26 #if defined(unix) || defined(__MACOSX__) /* This is a UNIX-specific hack */
    27 
    28 #include <sys/types.h>
    29 #include <sys/wait.h>
    30 #include <stdio.h>
    31 #include <stdlib.h>
    32 #include <unistd.h>
    33 #include <string.h>
    34 #include <signal.h>
    35 #include <ctype.h>
    36 
    37 #include "SDL_mixer.h"
    38 #include "music_cmd.h"
    39 
    40 /* Unimplemented */
    41 void MusicCMD_SetVolume(int volume)
    42 {
    43 	Mix_SetError("No way to modify external player volume");
    44 }
    45 
    46 /* Load a music stream from the given file */
    47 MusicCMD *MusicCMD_LoadSong(const char *cmd, const char *file)
    48 {
    49 	MusicCMD *music;
    50 
    51 	/* Allocate and fill the music structure */
    52 	music = (MusicCMD *)malloc(sizeof *music);
    53 	if ( music == NULL ) {
    54 		Mix_SetError("Out of memory");
    55 		return(NULL);
    56 	}
    57 	strncpy(music->file, file, (sizeof music->file)-1);
    58 	music->file[(sizeof music->file)-1] = '\0';
    59 	strncpy(music->cmd, cmd, (sizeof music->cmd)-1);
    60 	music->cmd[(sizeof music->cmd)-1] = '\0';
    61 	music->pid = 0;
    62 
    63 	/* We're done */
    64 	return(music);
    65 }
    66 
    67 /* Parse a command line buffer into arguments */
    68 static int ParseCommandLine(char *cmdline, char **argv)
    69 {
    70 	char *bufp;
    71 	int argc;
    72 
    73 	argc = 0;
    74 	for ( bufp = cmdline; *bufp; ) {
    75 		/* Skip leading whitespace */
    76 		while ( isspace(*bufp) ) {
    77 			++bufp;
    78 		}
    79 		/* Skip over argument */
    80 		if ( *bufp == '"' ) {
    81 			++bufp;
    82 			if ( *bufp ) {
    83 				if ( argv ) {
    84 					argv[argc] = bufp;
    85 				}
    86 				++argc;
    87 			}
    88 			/* Skip over word */
    89 			while ( *bufp && (*bufp != '"') ) {
    90 				++bufp;
    91 			}
    92 		} else {
    93 			if ( *bufp ) {
    94 				if ( argv ) {
    95 					argv[argc] = bufp;
    96 				}
    97 				++argc;
    98 			}
    99 			/* Skip over word */
   100 			while ( *bufp && ! isspace(*bufp) ) {
   101 				++bufp;
   102 			}
   103 		}
   104 		if ( *bufp ) {
   105 			if ( argv ) {
   106 				*bufp = '\0';
   107 			}
   108 			++bufp;
   109 		}
   110 	}
   111 	if ( argv ) {
   112 		argv[argc] = NULL;
   113 	}
   114 	return(argc);
   115 }
   116 
   117 static char **parse_args(char *command, char *last_arg)
   118 {
   119 	int argc;
   120 	char **argv;
   121 
   122 	/* Parse the command line */
   123 	argc = ParseCommandLine(command, NULL);
   124 	if ( last_arg ) {
   125 		++argc;
   126 	}
   127 	argv = (char **)malloc((argc+1)*(sizeof *argv));
   128 	if ( argv == NULL ) {
   129 		return(NULL);
   130 	}
   131 	argc = ParseCommandLine(command, argv);
   132 
   133 	/* Add last command line argument */
   134 	if ( last_arg ) {
   135 		argv[argc++] = last_arg;
   136 	}
   137 	argv[argc] = NULL;
   138 
   139 	/* We're ready! */
   140 	return(argv);
   141 }
   142 
   143 /* Start playback of a given music stream */
   144 void MusicCMD_Start(MusicCMD *music)
   145 {
   146 	music->pid = fork();
   147 	switch(music->pid) {
   148 	    /* Failed fork() system call */
   149 	    case -1:
   150 		Mix_SetError("fork() failed");
   151 		return;
   152 
   153 	    /* Child process - executes here */
   154 	    case 0: {
   155 		    char command[PATH_MAX];
   156 		    char **argv;
   157 
   158 		    /* Unblock signals in case we're called from a thread */
   159 		    {
   160 			sigset_t mask;
   161 			sigemptyset(&mask);
   162 			sigprocmask(SIG_SETMASK, &mask, NULL);
   163 		    }
   164 
   165 		    /* Execute the command */
   166 		    strcpy(command, music->cmd);
   167 		    argv = parse_args(command, music->file);
   168 		    if ( argv != NULL ) {
   169 			execvp(argv[0], argv);
   170 		    }
   171 
   172 		    /* exec() failed */
   173 		    perror(argv[0]);
   174 		    _exit(-1);
   175 		}
   176 		break;
   177 
   178 	    /* Parent process - executes here */
   179 	    default:
   180 		break;
   181 	}
   182 	return;
   183 }
   184 
   185 /* Stop playback of a stream previously started with MusicCMD_Start() */
   186 void MusicCMD_Stop(MusicCMD *music)
   187 {
   188 	int status;
   189 
   190 	if ( music->pid > 0 ) {
   191 		while ( kill(music->pid, 0) == 0 ) {
   192 			kill(music->pid, SIGTERM);
   193 			sleep(1);
   194 			waitpid(music->pid, &status, WNOHANG);
   195 		}
   196 		music->pid = 0;
   197 	}
   198 }
   199 
   200 /* Pause playback of a given music stream */
   201 void MusicCMD_Pause(MusicCMD *music)
   202 {
   203 	if ( music->pid > 0 ) {
   204 		kill(music->pid, SIGSTOP);
   205 	}
   206 }
   207 
   208 /* Resume playback of a given music stream */
   209 void MusicCMD_Resume(MusicCMD *music)
   210 {
   211 	if ( music->pid > 0 ) {
   212 		kill(music->pid, SIGCONT);
   213 	}
   214 }
   215 
   216 /* Close the given music stream */
   217 void MusicCMD_FreeSong(MusicCMD *music)
   218 {
   219 	free(music);
   220 }
   221 
   222 /* Return non-zero if a stream is currently playing */
   223 int MusicCMD_Active(MusicCMD *music)
   224 {
   225 	int status;
   226 	int active;
   227 
   228 	active = 0;
   229 	if ( music->pid > 0 ) {
   230 		waitpid(music->pid, &status, WNOHANG);
   231 		if ( kill(music->pid, 0) == 0 ) {
   232 			active = 1;
   233 		}
   234 	}
   235 	return(active);
   236 }
   237 
   238 #endif /* unix */