/* mpgcontrol.cpp mpg123 controller class This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details, www.gnu.org. Copyright (C) 2000 Simon Harrison (email smh@N_O_S_P_A_M@dr.com) */ #include "mpgcontrol.h" #include "synvis.h" const int mpgcontroller::QUIT=1; const int mpgcontroller::LOAD=2; const int mpgcontroller::STOP=3; const int mpgcontroller::PAUSE=4; const int mpgcontroller::JUMP=5; int mpgcontroller::inpipe[2]={0,0}; int mpgcontroller::outpipe[2]={0,0}; struct sigaction mpgcontroller::handler; int mpgcontroller::errno=0; pid_t mpgcontroller::pid =23; int mpgcontroller::buffersize =0; char mpgcontroller::mpgpath[1024] = ""; fd_set mpgcontroller::fds; mpgreturn mpgcontroller::mpg_data; pthread_mutex_t mpgcontroller::mpg_mutex; // control access to variables pthread_t mpgcontroller::threadid; bool mpgcontroller::now_playing=false; char mpgcontroller::current_track[1024]=""; int mpgcontroller::start_mpg_child( int bufsize, char* mp=NULL) { int i; buffersize = bufsize; if (!mp) { strcpy( mpgpath, "/usr/local/bin/mpg123" ); } else { if (strlen(mp) strcpy(mpgpath,mp); } else { strcpy(mpgpath,""); } } if (pipe(inpipe) || pipe(outpipe)) return 0; fcntl(outpipe[1], F_SETFD, O_NONBLOCK); handler.sa_handler = SIG_DFL; handler.sa_flags = 0; sigaction(SIGCHLD, &handler, NULL); errno = 0; switch (pid = fork()) { case -1: // TODO: handle some kind of error here printf( "error forking()\n " ); break; case 0: // child case. printf( "mpg123 child started.\n"); dup2(inpipe[0], 0); dup2(outpipe[1], 1); inpipe[1] = outpipe[0] = -1; for (i = 2; i < 256; i++) close(i); errno = 0; if (buffersize > 0) { char buf[128]; memset(buf, 0, sizeof(buf)); snprintf(buf, 127, "%d", buffersize? buffersize : 0); execlp(mpgpath,"mpg123","-R","-b",buffersize,"-w",FIFO_NAME,"-",(char *)NULL); printf( "mpg child started with buffer..\n" ); } else { execlp(mpgpath, "mpg123", "-R","-w", FIFO_NAME, "-", (char *)NULL); //execlp(mpgpath, "mpg123", "-R", "-", (char *)NULL); printf( "mpg child started without buffer.\n" ); } if (errno) exit(0); default: // parent. handler.sa_handler = restart_mpg_child; handler.sa_flags = SA_NOCLDSTOP | SA_RESTART; sigaction(SIGCHLD, &handler, NULL); break; } return pid; } void mpgcontroller::add_player_descriptor(fd_set *fds) { FD_SET(outpipe[0], fds); } void mpgcontroller::restart_mpg_child(int i) { // we will use this to clean up on a SIGCHLD if (pid) waitpid(pid, NULL, 0); pid = 0; start_mpg_child(buffersize, mpgpath); } int mpgcontroller::send_cmd(int type, ...) { char buf[512], *filename; int fd = inpipe[1]; long int skip; va_list args; memset(buf, 0, 512); if (fd < 0) return 0; switch (type) { case QUIT: write(fd, "QUIT\n", 5); break; case LOAD: va_start(args, type); filename = va_arg(args, char *); snprintf(buf, 511, "LOAD %s\n", filename); write(fd, buf, strlen(buf)); va_end(args); break; case STOP: write(fd, "STOP\n", 5); break; case PAUSE: write(fd, "PAUSE\n", 6); break; case JUMP: va_start(args, type); skip = va_arg(args, long int); snprintf(buf, 511, "JUMP %+ld\n", skip); write(fd, buf, strlen(buf)); va_end(args); break; default: return 0; } return 1; } int mpgcontroller::quit() { send_cmd(QUIT); } int mpgcontroller::load( char* name) { pthread_mutex_lock( &mpg_mutex ); strcpy( current_track, name ); now_playing = true; pthread_mutex_unlock( &mpg_mutex ); send_cmd(LOAD,name); } int mpgcontroller::stop() { pthread_mutex_lock( &mpg_mutex ); now_playing = false; pthread_mutex_unlock( &mpg_mutex ); send_cmd(STOP); } int mpgcontroller::pause() { send_cmd(PAUSE); } int mpgcontroller::jump(long skip) { send_cmd(JUMP,skip); } void mpgcontroller::check_player_output( fd_set* fds, mpgreturn *mpr) { if (FD_ISSET(outpipe[0],fds)) { read_cmd( outpipe[0], mpr ); } } bool mpgcontroller::wait_for_player( mpgreturn* mpgr ) { bool bRet=false; FD_ZERO(&fds); FD_SET(0,&fds); add_player_descriptor(&fds); if (select(FD_SETSIZE, &fds, NULL, NULL, NULL) >0) { if (FD_ISSET(0,&fds)) { bRet = false; } else { mpgcontroller::check_player_output( &fds, mpgr ); bRet = true; } } return bRet; } mpgreturn* mpgcontroller::read_cmd(int fd, mpgreturn *status) { char buf[512], c = '\0', *p = NULL, *s = NULL; int n = 0, tmpstatus; memset(buf, 0, 512); memset(status, 0, sizeof(mpgreturn)); status->active = true; // this seems inefficient but 1) i dont want to use FILE * and 2) we only // one to get one line at a time // while ((n < 511) && read(fd, &c, 1)) { if (c != '\n') buf[n++] = c; else break; } if (*buf != '@') return NULL; switch (*(p = buf+1)) { case 'F': status->played = strtoul(++p, &s, 10); status->left = strtoul(s, &p, 10); status->elapsed = strtod(p, &s); status->remaining = strtod(s, &p); return status; case 'P': tmpstatus = strtoul(++p, &s, 10); if (tmpstatus == 0) { status->active = false; } break; default: break; } return NULL; } char* mpgcontroller::format_playinfo(mpgreturn *message) { static char buffer[1024]; int minleft = (int)message->remaining / 60; double secleft = message->remaining - minleft*60; int minused = (int)message->elapsed / 60; double secused = message->elapsed - minused*60; sprintf( buffer, "Time: %02d:%05.2f/%02d:%05.2f Frames: (%d/%d) ", minused, secused, minleft, secleft, message->played, message->left); return buffer; } // // Below the threaded interface... // void mpgcontroller::startthread() { pthread_mutex_init( &mpg_mutex, NULL ); pthread_create( &threadid, NULL, mainthread, NULL ); } void* mpgcontroller::mainthread( void* ) { // mpg123 thread-related variables. char next_track[1024]; // next track to play. mpgreturn mpgr; // the data coming back from the player. int mpg_command=0; // zero = no data bool ok = true; // start up the mpg123 player, no buffer start_mpg_child( 0 ); wait_for_player( &mpgr ); // Loop indefinitely waiting for commands, // at frequency of player response. try { while(ok) { if (wait_for_player( &mpgr )) { // copy data to critical section. pthread_mutex_lock( &mpg_mutex ); memcpy( (void*)&mpg_data, (void*)&mpgr, sizeof(mpgreturn) ); if (!mpgr.active) { now_playing = false; } else { now_playing = true; } pthread_mutex_unlock( &mpg_mutex ); } } } catch (...) { pthread_mutex_destroy( &mpg_mutex ); } } bool mpgcontroller::get_safe_data( mpgreturn* mpgr, char* trackname ) { bool b; pthread_mutex_lock( &mpg_mutex ); memcpy( (void*)mpgr, (void*)&mpg_data, sizeof(mpgreturn) ); if ((trackname) && now_playing) strcpy( trackname, current_track ); b = now_playing; pthread_mutex_unlock( &mpg_mutex ); return b; } // Example main program comment in and re-compile to see how the // class works. /* int main() { mpgreturn mpgr; int notloaded=1; mpgcontroller::start_mpg_child( 0 ); for (int i=0;;i++) { if (mpgcontroller::wait_for_player( &mpgr )) { printf( "played - %d\n", mpgr.played ); if (notloaded) { printf( "loading track.\n" ); mpgcontroller::load( "./tr.mp3" ); notloaded = 0; } } } return 0; } */