music_cmd.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 04 Jan 2019 22:02:19 -0800
changeset 926 d6c9518fb5ee
parent 848 3907db698eb5
permissions -rw-r--r--
Updated copyright for 2019
     1 /*
     2   SDL_mixer:  An audio mixer library based on the SDL library
     3   Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "SDL_config.h"
    22 
    23 /* This file supports an external command for playing music */
    24 
    25 #ifdef MUSIC_CMD
    26 
    27 #include <sys/types.h>
    28 #include <sys/wait.h>
    29 #include <stdio.h>
    30 #include <stdlib.h>
    31 #include <unistd.h>
    32 #include <string.h>
    33 #include <signal.h>
    34 #include <ctype.h>
    35 #include <limits.h>
    36 #if defined(__linux__) && defined(__arm__)
    37 # include <linux/limits.h>
    38 #endif
    39 
    40 #include "music_cmd.h"
    41 
    42 
    43 typedef struct {
    44     char *file;
    45     char *cmd;
    46     pid_t pid;
    47     int play_count;
    48 } MusicCMD;
    49 
    50 
    51 /* Load a music stream from the given file */
    52 static void *MusicCMD_CreateFromFile(const char *file)
    53 {
    54     MusicCMD *music;
    55 
    56     if (!music_cmd) {
    57         Mix_SetError("You must call Mix_SetMusicCMD() first");
    58         return NULL;
    59     }
    60 
    61     /* Allocate and fill the music structure */
    62     music = (MusicCMD *)SDL_calloc(1, sizeof *music);
    63     if (music == NULL) {
    64         Mix_SetError("Out of memory");
    65         return NULL;
    66     }
    67     music->file = SDL_strdup(file);
    68     music->cmd = SDL_strdup(music_cmd);
    69     music->pid = 0;
    70 
    71     /* We're done */
    72     return music;
    73 }
    74 
    75 /* Parse a command line buffer into arguments */
    76 static int ParseCommandLine(char *cmdline, char **argv)
    77 {
    78     char *bufp;
    79     int argc;
    80 
    81     argc = 0;
    82     for (bufp = cmdline; *bufp;) {
    83         /* Skip leading whitespace */
    84         while (isspace(*bufp)) {
    85             ++bufp;
    86         }
    87         /* Skip over argument */
    88         if (*bufp == '"') {
    89             ++bufp;
    90             if (*bufp) {
    91                 if (argv) {
    92                     argv[argc] = bufp;
    93                 }
    94                 ++argc;
    95             }
    96             /* Skip over word */
    97             while (*bufp && (*bufp != '"')) {
    98                 ++bufp;
    99             }
   100         } else {
   101             if (*bufp) {
   102                 if (argv) {
   103                     argv[argc] = bufp;
   104                 }
   105                 ++argc;
   106             }
   107             /* Skip over word */
   108             while (*bufp && ! isspace(*bufp)) {
   109                 ++bufp;
   110             }
   111         }
   112         if (*bufp) {
   113             if (argv) {
   114                 *bufp = '\0';
   115             }
   116             ++bufp;
   117         }
   118     }
   119     if (argv) {
   120         argv[argc] = NULL;
   121     }
   122     return(argc);
   123 }
   124 
   125 static char **parse_args(char *command, char *last_arg)
   126 {
   127     int argc;
   128     char **argv;
   129 
   130     /* Parse the command line */
   131     argc = ParseCommandLine(command, NULL);
   132     if (last_arg) {
   133         ++argc;
   134     }
   135     argv = (char **)SDL_malloc((argc+1)*(sizeof *argv));
   136     if (argv == NULL) {
   137         return(NULL);
   138     }
   139     argc = ParseCommandLine(command, argv);
   140 
   141     /* Add last command line argument */
   142     if (last_arg) {
   143         argv[argc++] = last_arg;
   144     }
   145     argv[argc] = NULL;
   146 
   147     /* We're ready! */
   148     return(argv);
   149 }
   150 
   151 /* Start playback of a given music stream */
   152 static int MusicCMD_Play(void *context, int play_count)
   153 {
   154     MusicCMD *music = (MusicCMD *)context;
   155 
   156     music->play_count = play_count;
   157 #ifdef HAVE_FORK
   158     music->pid = fork();
   159 #else
   160     music->pid = vfork();
   161 #endif
   162     switch(music->pid) {
   163     /* Failed fork() system call */
   164     case -1:
   165         Mix_SetError("fork() failed");
   166         return -1;
   167 
   168     /* Child process - executes here */
   169     case 0: {
   170         char **argv;
   171 
   172         /* Unblock signals in case we're called from a thread */
   173         {
   174             sigset_t mask;
   175             sigemptyset(&mask);
   176             sigprocmask(SIG_SETMASK, &mask, NULL);
   177         }
   178 
   179         /* Execute the command */
   180         argv = parse_args(music->cmd, music->file);
   181         if (argv != NULL) {
   182             execvp(argv[0], argv);
   183 
   184             /* exec() failed */
   185             perror(argv[0]);
   186         }
   187         _exit(-1);
   188     }
   189     break;
   190 
   191     /* Parent process - executes here */
   192     default:
   193         break;
   194     }
   195     return 0;
   196 }
   197 
   198 /* Return non-zero if a stream is currently playing */
   199 static SDL_bool MusicCMD_IsPlaying(void *context)
   200 {
   201     MusicCMD *music = (MusicCMD *)context;
   202     int status;
   203 
   204     if (music->pid > 0) {
   205         waitpid(music->pid, &status, WNOHANG);
   206         if (kill(music->pid, 0) == 0) {
   207             return SDL_TRUE;
   208         }
   209 
   210         /* We might want to loop */
   211         if (music->play_count != 1) {
   212             int play_count = -1;
   213             if (music->play_count > 0) {
   214                 play_count = (music->play_count - 1);
   215             }
   216             MusicCMD_Play(music, play_count);
   217             return SDL_TRUE;
   218         }
   219     }
   220     return SDL_FALSE;
   221 }
   222 
   223 /* Pause playback of a given music stream */
   224 static void MusicCMD_Pause(void *context)
   225 {
   226     MusicCMD *music = (MusicCMD *)context;
   227     if (music->pid > 0) {
   228         kill(music->pid, SIGSTOP);
   229     }
   230 }
   231 
   232 /* Resume playback of a given music stream */
   233 static void MusicCMD_Resume(void *context)
   234 {
   235     MusicCMD *music = (MusicCMD *)context;
   236     if (music->pid > 0) {
   237         kill(music->pid, SIGCONT);
   238     }
   239 }
   240 
   241 /* Stop playback of a stream previously started with MusicCMD_Start() */
   242 static void MusicCMD_Stop(void *context)
   243 {
   244     MusicCMD *music = (MusicCMD *)context;
   245     int status;
   246 
   247     if (music->pid > 0) {
   248         while (kill(music->pid, 0) == 0) {
   249             kill(music->pid, SIGTERM);
   250             sleep(1);
   251             waitpid(music->pid, &status, WNOHANG);
   252         }
   253         music->pid = 0;
   254     }
   255 }
   256 
   257 /* Close the given music stream */
   258 void MusicCMD_Delete(void *context)
   259 {
   260     MusicCMD *music = (MusicCMD *)context;
   261     SDL_free(music->file);
   262     SDL_free(music);
   263 }
   264 
   265 Mix_MusicInterface Mix_MusicInterface_CMD =
   266 {
   267     "CMD",
   268     MIX_MUSIC_CMD,
   269     MUS_CMD,
   270     SDL_FALSE,
   271     SDL_FALSE,
   272 
   273     NULL,   /* Load */
   274     NULL,   /* Open */
   275     NULL,   /* CreateFromRW */
   276     MusicCMD_CreateFromFile,
   277     NULL,   /* SetVolume */
   278     MusicCMD_Play,
   279     MusicCMD_IsPlaying,
   280     NULL,   /* GetAudio */
   281     NULL,   /* Seek */
   282     MusicCMD_Pause,
   283     MusicCMD_Resume,
   284     MusicCMD_Stop,
   285     MusicCMD_Delete,
   286     NULL,   /* Close */
   287     NULL,   /* Unload */
   288 };
   289 
   290 #endif /* MUSIC_CMD */
   291 
   292 /* vi: set ts=4 sw=4 expandtab: */