1 /* remote.c by Richard van Paasen <rvpaasen@dds.nl> */
2 
3 /********************************************************************
4  *                                                                  *
5  * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE.   *
6  * USE, DISTRIBUTION AND REPRODUCTION OF THIS SOURCE IS GOVERNED BY *
7  * THE GNU PUBLIC LICENSE 2, WHICH IS INCLUDED WITH THIS SOURCE.    *
8  * PLEASE READ THESE TERMS BEFORE DISTRIBUTING.                     *
9  *                                                                  *
10  * THE Ogg123 SOURCE CODE IS (C) COPYRIGHT 2000-2001                *
11  * by Kenneth C. Arnold <ogg@arnoldnet.net> AND OTHER CONTRIBUTORS  *
12  * http://www.xiph.org/                                             *
13  *                                                                  *
14  ********************************************************************
15 
16  last mod: $Id$
17 
18  ********************************************************************/
19 
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <pthread.h>
29 #include <semaphore.h>
30 
31 #if HAVE_SELECT
32 #include <sys/select.h>
33 #endif
34 
35 #include "ogg123.h"
36 #include "format.h"
37 
38 /* Maximum size of the input buffer */
39 #define MAXBUF 1024
40 /* Undefine logfile if you don't want it */
41 //#define LOGFILE "/tmp/ogg123.log"
42 
43 /* The play function in ogg123.c */
44 extern void play (char *source_string);
45 extern ogg123_options_t options;
46 extern void set_seek_opt(ogg123_options_t *ogg123_opts, char *buf);
47 
48 /* Status */
49 typedef enum { PLAY, STOP, PAUSE, NEXT, QUIT} Status;
50 static Status status = STOP;
51 
52 /* Threading is introduced to reduce the
53    amount of processor time that ogg123
54    will take if it is in idle state */
55 
56 /* Thread control locks */
57 static pthread_mutex_t main_lock;
58 static sem_t sem_command;
59 static sem_t sem_processed;
60 static pthread_mutex_t output_lock;
61 
62 #ifdef LOGFILE
send_log(const char * fmt,...)63 void send_log(const char* fmt, ...) {
64 
65   FILE* fp;
66   va_list ap;
67   pthread_mutex_lock (&output_lock);
68   fp=fopen(LOGFILE,"a");
69   va_start(ap, fmt);
70   vfprintf(fp, fmt, ap);
71   va_end(ap);
72   fprintf(fp, "\n");
73   fclose(fp);
74   pthread_mutex_unlock (&output_lock);
75   return;
76 }
77 #else
78   #define send_log(...)
79 #endif
80 
send_msg(const char * fmt,...)81 static void send_msg(const char* fmt, ...) {
82 
83   va_list ap;
84   pthread_mutex_lock (&output_lock);
85   fprintf(stdout, "@");
86   va_start(ap, fmt);
87   vfprintf(stdout, fmt, ap);
88   va_end(ap);
89   fprintf(stdout, "\n");
90   pthread_mutex_unlock (&output_lock);
91   return;
92 }
93 
send_err(const char * fmt,...)94 static void send_err(const char* fmt, ...) {
95   va_list ap;
96   pthread_mutex_lock (&output_lock);
97   fprintf(stderr, "@");
98   va_start(ap, fmt);
99   vfprintf(stderr, fmt, ap);
100   va_end(ap);
101   fprintf(stderr, "\n");
102   pthread_mutex_unlock (&output_lock);
103   return;
104 }
105 
getstatus()106 static Status getstatus() {
107 
108   return status;
109 }
110 
setstatus(Status s)111 static void setstatus(Status s) {
112 
113   status = s;
114 }
115 
invertpause()116 static void invertpause() {
117 
118   if (status==PLAY) {
119     status = PAUSE;
120   }
121   else if (status==PAUSE) {
122     status = PLAY;
123   }
124   return;
125 }
126 
remotethread(void * arg)127 static void * remotethread(void * arg) {
128 
129   int done = 0;
130   int error = 0;
131   int ignore = 0;
132   char buf[MAXBUF+1];
133   char *b;
134 
135 #if HAVE_SELECT
136   fd_set fd;
137 #endif
138 
139   buf[MAXBUF]=0;
140 
141   while(!done) {
142     /* Read a line */
143     buf[0] = 0;
144     send_log("Waiting for input: ...");
145 
146 #if HAVE_SELECT
147     FD_ZERO(&fd);
148     FD_SET(0,&fd);
149     select (1, &fd, NULL, NULL, NULL);
150 #endif
151 
152     fgets(buf, MAXBUF, stdin);
153     buf[strlen(buf)-1] = 0;
154 
155     /* Lock on */
156     pthread_mutex_lock (&main_lock);
157 
158     send_log("Input: %s", buf);
159     error = 0;
160 
161     if (!strncasecmp(buf,"l",1)) {
162 	/* prepare to load */
163       if ((b=strchr(buf,' ')) != NULL) {
164         /* Prepare to load a new song */
165         strcpy((char*)arg, b+1);
166         setstatus(NEXT);
167       }
168       else {
169         /* Invalid load command */
170         error = 1;
171       }
172     }
173     else
174     if (!strncasecmp(buf,"p",1)) {
175       /* Prepare to (un)pause */
176       invertpause();
177     }
178 	else
179     if (!strncasecmp(buf,"j",1)) {
180       /* Prepare to seek */
181       if ((b=strchr(buf,' ')) != NULL) {
182         set_seek_opt(&options, b+1);
183 	  }
184       ignore = 1;
185     }
186     else
187     if (!strncasecmp(buf,"s",1)) {
188       /* Prepare to stop */
189       setstatus(STOP);
190     }
191 	else
192     if (!strncasecmp(buf,"r",1)) {
193       /* Prepare to reload */
194       setstatus(NEXT);
195     }
196     else
197     if (!strncasecmp(buf,"h",1)) {
198       /* Send help */
199 	  send_msg("H +----------------------------------------------------+");
200 	  send_msg("H | Ogg123 remote interface                            |");
201 	  send_msg("H |----------------------------------------------------|");
202 	  send_msg("H | Load <file>     -  load a file and starts playing  |");
203 	  send_msg("H | Pause           -  pause or unpause playing        |");
204 	  send_msg("H | Jump [+|-]<f>   -  jump <f> seconds forth or back  |");
205 	  send_msg("H | Stop            -  stop playing                    |");
206 	  send_msg("H | Reload          -  reload last song                |");
207 	  send_msg("H | Quit            -  quit ogg123                     |");
208 	  send_msg("H |----------------------------------------------------|");
209 	  send_msg("H | refer to README.remote for documentation           |");
210 	  send_msg("H +----------------------------------------------------+");
211 	  ignore = 1;
212     }
213     else
214     if (!strncasecmp(buf,"q",1)) {
215       /* Prepare to quit */
216       setstatus(QUIT);
217       done = 1;
218     }
219     else {
220       /* Unknown input received */
221       error = 1;
222     }
223 
224     if (ignore) {
225       /* Unlock */
226       pthread_mutex_unlock (&main_lock);
227       ignore = 0;
228     } else {
229       if (error) {
230     	/* Send the error and unlock */
231     	send_err("E Unknown command '%s'", buf);
232     	send_log("Unknown command '%s'", buf);
233 		/* Unlock */
234     	pthread_mutex_unlock (&main_lock);
235       }
236       else {
237     	/* Signal the main thread */
238     	sem_post(&sem_command);
239     	/* Unlock */
240     	pthread_mutex_unlock (&main_lock);
241     	/* Wait until the change has been noticed */
242     	sem_wait(&sem_processed);
243       }
244     }
245   }
246 
247   return NULL;
248 }
249 
remote_mainloop(void)250 void remote_mainloop(void) {
251 
252   int r;
253   pthread_t th;
254   Status s;
255   char fname[MAXBUF+1];
256 
257   /* Need to output line by line! */
258   setlinebuf(stdout);
259 
260   /* Send a greeting */
261   send_msg("R ogg123 from " PACKAGE " " VERSION);
262 
263   /* Initialize the thread controlling variables */
264   pthread_mutex_init(&main_lock, NULL);
265   sem_init(&sem_command, 0, 0);
266   sem_init(&sem_processed, 0, 0);
267 
268   /* Start the thread */
269   r = pthread_create(&th, NULL, remotethread, (void*)fname);
270   if (r != 0) {
271     send_err("E Could not create a thread (code %d)", r);
272     return;
273   }
274 
275   send_log("Start");
276 
277   /* The thread may already have processed some input,
278      get the current status
279    */
280   pthread_mutex_lock(&main_lock);
281   s = getstatus();
282   pthread_mutex_unlock(&main_lock);
283 
284   while (s != QUIT) {
285 
286     /* wait for a new command */
287     if (s != NEXT) {
288 
289       /* Wait until a new status is available,
290          This puts the main tread asleep and
291          saves resources
292        */
293 
294       sem_wait(&sem_command);
295 
296       pthread_mutex_lock(&main_lock);
297       s = getstatus();
298       pthread_mutex_unlock(&main_lock);
299     }
300 
301     send_log("Status: %d", s);
302 
303     if (s == NEXT) {
304 
305       /* The status is to play a new song. Set
306          the status to PLAY and signal the thread
307          that the status has been processed.
308        */
309 
310       send_msg("I %s", fname);
311       send_msg("S 0.0 0 00000 xxxxxx 0 0 0 0 0 0 0 0");
312       send_msg("P 2");
313       pthread_mutex_lock(&main_lock);
314       setstatus(PLAY);
315       s = getstatus();
316       send_log("mainloop s=%d", s);
317       sem_post(&sem_processed);
318       s = getstatus();
319       send_log("mainloop s=%d", s);
320       pthread_mutex_unlock(&main_lock);
321 
322       /* Start the player. The player calls the playloop
323          frequently to check for a new status (e.g. NEXT,
324          STOP or PAUSE.
325        */
326 
327       pthread_mutex_lock(&main_lock);
328       s = getstatus();
329       pthread_mutex_unlock(&main_lock);
330       send_log("mainloop s=%d", s);
331       play(fname);
332 
333       /* Retrieve the new status */
334       pthread_mutex_lock(&main_lock);
335       s = getstatus();
336       pthread_mutex_unlock(&main_lock);
337 
338 /* don't know why this was here, sending "play stoped" on NEXT wasn't good idea... */
339 //      if (s == NEXT) {
340 
341 	    /* Send "play stopped" */
342 //        send_msg("P 0");
343 //        send_log("P 0");
344 //      } else {
345 
346 	    /* Send "play stopped at eof" */
347 //        send_msg("P 0 EOF");
348 //        send_log("P 0 EOF");
349 //      }
350 
351     }
352     else {
353 
354       /* Irrelevent status, notice the thread that
355          it has been processed.
356        */
357 
358       sem_post(&sem_processed);
359     }
360   }
361 
362   /* Send "Quit" */
363   send_msg("Q");
364   send_log("Quit");
365 
366   /* Cleanup the semaphores */
367   sem_destroy(&sem_command);
368   sem_destroy(&sem_processed);
369 
370   return;
371 }
372 
remote_playloop(void)373 int remote_playloop(void) {
374 
375   Status s;
376 
377   /* Check the status. If the player should pause,
378      then signal that the command has been processed
379      and wait for a new status. A new status will end
380      the player and return control to the main loop.
381      The main loop will signal that the new command
382      has been processed.
383    */
384 
385   pthread_mutex_lock (&main_lock);
386   s = getstatus();
387   pthread_mutex_unlock (&main_lock);
388 
389   send_log("playloop entry s=%d", s);
390 
391   if (s == PAUSE) {
392 
393     /* Send "pause on" */
394     send_msg("P 1");
395 
396     while (s == PAUSE) {
397 
398       sem_post(&sem_processed);
399       sem_wait(&sem_command);
400       pthread_mutex_lock (&main_lock);
401       s = getstatus();
402       pthread_mutex_unlock (&main_lock);
403     }
404 
405     /* Send "pause off" */
406     send_msg("P 2");
407   }
408 
409     /* Send stop msg to the frontend */
410     /* this probably should be done after the audio buffer is flushed and no audio is actually playing, but don't know how */
411   if ((s == STOP) || (s == QUIT)) send_msg("P 0");
412 
413   send_log("playloop exit s=%d", s);
414 
415   return ((s == NEXT) || (s == STOP) || (s == QUIT));
416 }
417 
remote_time(double current,double total)418 void remote_time(double current, double total) {
419 
420   /* Send the frame (not implemented yet) and the time */
421   send_msg("F 0 0 %.2f %.2f", current, (total-current));
422 
423   return;
424 }
425