1 /* player.c - simple audio file player implementation
2 *
3 * This version uses PortAudio (http://www.portaudio.com) for audio
4 * output.
5 *
6 * Copyright 2010 Petteri Hintsanen <petterih@iki.fi>
7 *
8 * This file is part of abx.
9 *
10 * abx is free software: you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * abx is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
18 * License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with abx. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "player.h"
25 #include "soundfile.h"
26 #include <assert.h>
27 #include <glib.h>
28 #include <pthread.h>
29 #include <semaphore.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 /* Ring buffer size, in kiB. */
35 static const int BUFFER_SIZE = 512;
36 /* Fill this portion of buffer before playback. This value must be
37 * between 0 and 1. */
38 static const float PREFILL = 0.2;
39 /* Begin to fill buffer after this portion of buffer has been
40 * consumed. This is intended to keep PortAudio stream callback from
41 * signalling spaces semaphore all the time. SLACK value must be
42 * between 0 and 1, though not too close to zero to prevent buffer
43 * underruns. */
44 static const float SLACK = 0.5;
45
46 pthread_mutex_t audio_init_count_mutex = PTHREAD_MUTEX_INITIALIZER;
47 static unsigned int audio_init_count;
48
49 /*
50 * Message structure for passing requests to the controller thread.
51 * These should not be built in user code; API functions (see
52 * player.h) should be used instead.
53 */
54 typedef struct {
55 enum {
56 CMD_PAUSE,
57 CMD_PLAY,
58 CMD_SEEK,
59 CMD_STOP,
60 CMD_TERM
61 } command;
62 double offset;
63 int whence;
64 sem_t *sem;
65 } Message;
66
67 /*
68 * Player handle type. This struct should not be accessed directly
69 * from user code. See init_player.
70 */
71 struct Player {
72 /* Ring buffer. We always keep one vacant slot, so that it is
73 * relatively easy to distinguish between empty (in == out) and
74 * full (in + 1 == out) buffer. */
75 struct {
76 float *begin;
77 float *end;
78 float *in;
79 float *out;
80 int bufsize;
81 int nframes;
82 sem_t prefill;
83 sem_t space;
84 int prefill_sem_ok;
85 int space_sem_ok;
86 } buffer;
87
88 /* reader thread */
89 struct {
90 /* thread status/control values */
91 enum {
92 READER_RESUME,
93 READER_RUNNING,
94 READER_STOP,
95 READER_STOPPED,
96 READER_TERM
97 } control;
98 /* condition variable and mutex for thread control */
99 pthread_cond_t control_cond;
100 pthread_mutex_t control_cond_mutex;
101
102 pthread_t thread_id;
103 int thread_ok;
104 sem_t sem;
105 sem_t notify;
106 int sem_ok;
107 int notify_ok;
108
109 Sound_file *sndfile;
110 Metadata metadata;
111 char is_eof;
112 } reader;
113
114 /* controller thread */
115 struct {
116 pthread_t thread_id;
117 pthread_t thread_ok;
118 GAsyncQueue *messages;
119 } controller;
120
121 /* current playback status */
122 Player_state state;
123 /* location at the most recent seek, in secods */
124 double origin;
125 /* frames played after the most recent seek */
126 int nplayed;
127 /* PortAudio output stream */
128 PaStream *stream;
129 };
130
131 /*
132 * Reader thread.
133 *
134 * This thread implements the producer part of the producer-consumer
135 * model by reading blocks of audio data from the sound file into the
136 * ring buffer. Some notes:
137 *
138 * - Thread blocks on space semaphore, and expects that the semaphore
139 * will be signaled at some point.
140 *
141 * - Thread reads the sound file until EOF is encountered, in which
142 * case it sets is_eof to nonzero and waits on control_cond.
143 *
144 * - Thread signals sem before waiting.
145 *
146 * - Thread signals sem and resets is_eof to zero when awakened from
147 * wait on control_cond.
148 *
149 * - After waking up from wait on control_cond, and filling PREFILL *
150 * BUFFER_SIZE portion of the buffer (or hitting EOF), the thread
151 * signals notify.
152 */
153 static void *
reader_main(void * arg)154 reader_main(void *arg)
155 {
156 Player *player;
157 int nchannels;
158 int notify;
159
160 player = (Player *) arg;
161 nchannels = player->reader.metadata.channels; /* for prettier
162 * offsetting */
163
164 /* g_debug("starting reader thread, reader.control: %d", */
165 /* player->reader.control); */
166
167 for (;;) {
168 pthread_mutex_lock(&player->reader.control_cond_mutex);
169 while (player->reader.control == READER_STOP
170 || player->reader.control == READER_TERM
171 || player->reader.is_eof) {
172 if (player->reader.control == READER_TERM) {
173 /* g_debug("terminating reader thread"); */
174 pthread_exit(NULL);
175 }
176
177 if (player->reader.control == READER_STOP) {
178 sem_post(&player->reader.sem);
179 }
180
181 player->reader.control = READER_STOPPED;
182 /* g_debug("reader stopped"); */
183 pthread_cond_wait(&player->reader.control_cond,
184 &player->reader.control_cond_mutex);
185 /* g_debug("reader resumed"); */
186 assert(player->reader.control == READER_RESUME
187 || player->reader.control == READER_TERM);
188 sem_post(&player->reader.sem);
189 player->reader.is_eof = 0;
190 }
191
192 if (player->reader.control == READER_RESUME) notify = 1;
193 else notify = 0;
194
195 player->reader.control = READER_RUNNING;
196 pthread_mutex_unlock(&player->reader.control_cond_mutex);
197 /* g_debug("reader waiting for space"); */
198 sem_wait(&player->buffer.space);
199
200 /* Reader stop/termination request could occur while waiting
201 * for buffer space. */
202 if (player->reader.control == READER_STOP
203 || player->reader.control == READER_TERM) continue;
204
205 /* read in data */
206 for (;;) {
207 int nitems;
208 int nread;
209 if ((player->buffer.in == player->buffer.end
210 && player->buffer.out == player->buffer.begin)
211 || player->buffer.in + nchannels == player->buffer.out) {
212 /* full buffer */
213 /* g_debug("full buffer"); */
214 if (notify) {
215 /* g_debug("notifying prefill"); */
216 sem_post(&player->reader.notify);
217 }
218 break;
219 }
220
221 if (player->buffer.in == player->buffer.end) {
222 /* rewind the ring buffer */
223 /* g_debug("rewound the ring buffer"); */
224 player->buffer.in = player->buffer.begin;
225 }
226
227 if (player->buffer.in >= player->buffer.out) {
228 nitems = ((player->buffer.end
229 - player->buffer.in) / nchannels);
230 } else {
231 nitems = ((player->buffer.out -
232 player->buffer.in) / nchannels) - 1;
233 }
234
235 assert(nitems > 0);
236
237 /* g_debug("reading %d frames, %d frames in buffer, " */
238 /* "buffer begin = %p, in = %p, out = %p, end = %p", */
239 /* nitems, player->buffer.nframes, */
240 /* (void *) player->buffer.begin, */
241 /* (void *) player->buffer.in, */
242 /* (void *) player->buffer.out, */
243 /* (void *) player->buffer.end); */
244
245 nread = read_pcm_data(player->reader.sndfile,
246 player->buffer.in, nitems);
247
248 /* g_debug("read %d frames", nread); */
249
250 player->buffer.nframes += nread;
251 player->buffer.in += (nread * nchannels);
252 assert(player->buffer.nframes <= player->buffer.bufsize);
253
254 if (nread != nitems) {
255 /* EOF */
256 /* g_debug("eof"); */
257 player->reader.is_eof = 1;
258 if (notify) {
259 sem_post(&player->reader.notify);
260 notify = 0;
261 }
262 break;
263 }
264
265 if (notify && player->buffer.nframes
266 > PREFILL * player->buffer.bufsize) {
267 /* g_debug("read %d frames, notifying prefill", nframes); */
268 sem_post(&player->reader.notify);
269 notify = 0;
270 }
271 }
272 }
273 }
274
275 /*
276 * Stop reader thread. Playback must be stopped, since this function
277 * can garble the input buffer.
278 *
279 * Return 0 on success, or 1 if reader was already stopped.
280 */
281 static int
stop_reader_thread(Player * player)282 stop_reader_thread(Player *player)
283 {
284 /* g_debug("stopping reader"); */
285 /* g_debug("locking reader mutex"); */
286 pthread_mutex_lock(&player->reader.control_cond_mutex);
287 assert(player->reader.control != READER_STOP);
288 if (player->reader.control == READER_STOPPED) {
289 /* g_debug("reader is already waiting"); */
290 pthread_mutex_unlock(&player->reader.control_cond_mutex);
291 /* g_debug("released reader mutex"); */
292 return 1;
293 } else {
294 int space;
295 /* g_debug("telling reader to stop"); */
296 player->reader.control = READER_STOP;
297 sem_getvalue(&player->buffer.space, &space);
298 assert(space == 0 || space == 1);
299 if (space == 0) {
300 /* g_debug("signaling spaces in case reader " */
301 /* "is waiting"); */
302 sem_post(&player->buffer.space);
303 }
304 pthread_mutex_unlock(&player->reader.control_cond_mutex);
305 /* g_debug("released reader mutex"); */
306 /* g_debug("waiting for reader to stop"); */
307 sem_wait(&player->reader.sem);
308 /* g_debug("reader is now sleeping"); */
309 return 0;
310 }
311 }
312
313 /*
314 * Resume reader thread. Block until the input buffer has been filled
315 * with at least PREFILL * BUFFER_SIZE new frames, or EOF has been
316 * encountered.
317 *
318 * Return 0 on success, or 1 if reader was already running.
319 */
320 static int
resume_reader_thread(Player * player)321 resume_reader_thread(Player *player)
322 {
323 /* g_debug("resuming reader"); */
324 /* g_debug("locking reader mutex"); */
325 pthread_mutex_lock(&player->reader.control_cond_mutex);
326 assert(player->reader.control != READER_RESUME);
327 if (player->reader.control == READER_RUNNING) {
328 /* g_debug("reader is already running"); */
329 pthread_mutex_unlock(&player->reader.control_cond_mutex);
330 /* g_debug("released reader mutex"); */
331 return 1;
332 } else {
333 /* g_debug("telling reader to resume"); */
334 player->reader.control = READER_RESUME;
335 pthread_cond_signal(&player->reader.control_cond);
336 pthread_mutex_unlock(&player->reader.control_cond_mutex);
337 /* g_debug("released reader mutex"); */
338 /* g_debug("waiting for reader to wake up"); */
339 sem_wait(&player->reader.sem);
340 /* g_debug("reader is now running"); */
341 /* g_debug("waiting for prefill"); */
342 sem_wait(&player->reader.notify);
343 return 0;
344 }
345 }
346
347 /*
348 * Terminate reader thread. Playback must be stopped, since this
349 * function can garble the input buffer.
350 */
351 static void
terminate_reader_thread(Player * player)352 terminate_reader_thread(Player *player)
353 {
354 int space;
355 assert(player->reader.control != READER_TERM);
356 /* g_debug("terminating reader"); */
357 /* g_debug("locking reader mutex"); */
358 pthread_mutex_lock(&player->reader.control_cond_mutex);
359 /* g_debug("telling reader to terminate"); */
360 player->reader.control = READER_TERM;
361 sem_getvalue(&player->buffer.space, &space);
362 assert(space == 0 || space == 1);
363 if (space == 0) {
364 /* g_debug("signaling spaces in case reader is waiting"); */
365 sem_post(&player->buffer.space);
366 }
367 pthread_cond_signal(&player->reader.control_cond);
368 pthread_mutex_unlock(&player->reader.control_cond_mutex);
369 /* g_debug("waiting for reader to terminate"); */
370 pthread_join(player->reader.thread_id, NULL);
371 /* g_debug("reader has been terminated"); */
372 }
373
374 /*
375 * Seek player. Semantics are as in fseek, offset is in seconds. If
376 * playback is in progress, it is continued from the new location.
377 * Return the new location, or -1 on error.
378 */
379 static double
seek(Player * player,double offset,int whence)380 seek(Player *player, double offset, int whence)
381 {
382 int space;
383 int state;
384 double loc;
385
386 /* g_debug("seeking to %f, whence %d", offset, whence); */
387
388 /* stop playback */
389 state = player->state.playback;
390 Pa_StopStream(player->stream);
391 stop_reader_thread(player);
392
393 /* reset state */
394 switch (whence) {
395 case SEEK_SET:
396 loc = offset;
397 break;
398
399 case SEEK_CUR:
400 loc = player->state.location + offset;
401 break;
402
403 case SEEK_END:
404 loc = player->reader.metadata.duration + offset;
405 break;
406 }
407
408 if (loc < 0 || loc > player->reader.metadata.duration) {
409 return -1;
410 }
411
412 player->state.location = loc;
413 player->origin = loc;
414 player->nplayed = 0;
415 player->buffer.in = player->buffer.out = player->buffer.begin;
416 player->buffer.nframes = 0;
417 if (seek_sound_file(player->reader.sndfile, loc, SEEK_SET) == -1) {
418 return -1;
419 }
420
421 sem_getvalue(&player->buffer.space, &space);
422 assert(space == 0 || space == 1);
423 if (space == 0) sem_post(&player->buffer.space);
424
425 resume_reader_thread(player);
426 /* resume playback if it was in progress */
427 if (state == PLAYING) {
428 Pa_StartStream(player->stream);
429 player->state.playback = PLAYING;
430 }
431
432 return loc;
433 }
434
435 /*
436 * Controller thread.
437 *
438 * This thread polls an asynchronous message queue for command
439 * requests (stop playback, start playback, seek, etc.) and services
440 * them in the order or arrival. This way multiple threads can access
441 * the same player in a serialized manner by calling the "API"
442 * functions. A message can contain a semaphore reference, which is
443 * signalled after the corresponding command has been executed by the
444 * controller (in case the calling code wants to synchronize with the
445 * player).
446 */
447 static void *
controller_main(void * arg)448 controller_main(void *arg)
449 {
450 Player *player = (Player *) arg;
451 Message *msg;
452 int term = 0;
453
454 for (;;) {
455 msg = (Message *) g_async_queue_pop(player->controller.messages);
456 /* g_debug("servicing new request %d", msg->command); */
457 switch (msg->command) {
458 case CMD_PAUSE:
459 switch (player->state.playback) {
460 case PLAYING:
461 Pa_StopStream(player->stream);
462 player->state.playback = PAUSED;
463 break;
464 case PAUSED:
465 Pa_StartStream(player->stream);
466 player->state.playback = PLAYING;
467 break;
468 default:
469 break;
470 }
471 break;
472 case CMD_PLAY:
473 Pa_StartStream(player->stream);
474 player->state.playback = PLAYING;
475 break;
476 case CMD_SEEK:
477 seek(player, msg->offset, msg->whence);
478 break;
479 case CMD_STOP:
480 Pa_StopStream(player->stream);
481 player->state.playback = STOPPED;
482 break;
483 case CMD_TERM:
484 if (player->stream) {
485 Pa_StopStream(player->stream);
486 if (Pa_CloseStream(player->stream) != paNoError) {
487 g_warning("failed to close playback stream");
488 }
489 player->stream = NULL;
490 }
491 if (player->reader.thread_ok) {
492 terminate_reader_thread(player);
493 }
494 term = 1;
495 break;
496 }
497 /* g_debug("request handled"); */
498 if (msg->sem) sem_post(msg->sem);
499 g_free(msg);
500 if (term) {
501 /* g_debug("terminating controller thread"); */
502 pthread_exit(NULL);
503 }
504 }
505
506 return NULL;
507 }
508
509 /*
510 * Stream callback for PortAudio. Fill the audio output buffer from
511 * the ring buffer, and signal space so that the reader thread can
512 * fill the ring buffer.
513 *
514 * Return PaContinue so that PortAudio will keep on playback, or
515 * PaComplete if EOF has been reached.
516 */
517 static int
stream_callback(const void * input,void * output,unsigned long nframes,const PaStreamCallbackTimeInfo * timeinfo,PaStreamCallbackFlags statusflags,void * userdata)518 stream_callback(const void *input, void *output,
519 unsigned long nframes,
520 const PaStreamCallbackTimeInfo *timeinfo,
521 PaStreamCallbackFlags statusflags,
522 void *userdata)
523 {
524 Player *player = (Player *) userdata;
525 char eof = 0;
526 int nchannels = player->reader.metadata.channels;
527 float *outbuf = (float *) output;
528
529 player->nplayed += nframes;
530 while (nframes > 0) {
531 size_t i;
532 if (player->buffer.out == player->buffer.end) {
533 /* rewind the ring buffer */
534 /* g_debug("rewound the ring buffer"); */
535 player->buffer.out = player->buffer.begin;
536 }
537
538 if (player->buffer.out == player->buffer.in) {
539 /* buffer underflow or eof, output silence */
540 if (!player->reader.is_eof) {
541 g_warning("buffer underflow");
542 } else {
543 /* g_debug("eof"); */
544 eof = 1;
545 }
546 while (nframes > 0) {
547 for (i = 0; i < nchannels; i++) {
548 *outbuf++ = -1.0f;
549 }
550 nframes--;
551 }
552 } else {
553 for (i = 0; i < nchannels; i++) {
554 *outbuf++ = *player->buffer.out++;
555 }
556 player->buffer.nframes--;
557 nframes--;
558 }
559 }
560
561 /* update current playback location */
562 player->state.location = (player->origin
563 + (1.0 * player->nplayed
564 / player->reader.metadata.rate));
565 /* g_debug("location %f", player->state.location); */
566
567 if (player->buffer.nframes < SLACK * player->buffer.bufsize) {
568 int space;
569 sem_getvalue(&player->buffer.space, &space);
570 assert(space == 0 || space == 1);
571 if (space == 0) {
572 sem_post(&player->buffer.space);
573 }
574 }
575
576 if (!eof) return paContinue;
577 else return paComplete;
578 }
579
580 /*
581 * Stream finished callback for PortAudio.
582 */
583 static void
stream_finished_callback(void * userdata)584 stream_finished_callback(void *userdata)
585 {
586 Player *player = (Player *) userdata;
587 player->state.playback = STOPPED;
588 }
589
590 /*
591 * Scan for default PortAudio host API output device. Return the
592 * device index, or -1 if no device was found.
593 */
594 static PaDeviceIndex
scan_audio_output(void)595 scan_audio_output(void)
596 {
597 const PaDeviceInfo *devinfo;
598 PaDeviceIndex i;
599 PaDeviceIndex outdev = -1;
600 PaDeviceIndex ndevices;
601
602 ndevices = Pa_GetDeviceCount();
603 if (ndevices < 0) {
604 g_warning("can't find available audio devices: %s",
605 Pa_GetErrorText(ndevices));
606 }
607 for (i = 0; i < ndevices; i++) {
608 devinfo = Pa_GetDeviceInfo(i);
609 if (!devinfo) continue;
610 if (i == Pa_GetHostApiInfo
611 (devinfo->hostApi)->defaultOutputDevice) {
612 outdev = i;
613 /* g_debug("found default host api output device index %d", */
614 /* outdev); */
615 break;
616 }
617 }
618 return outdev;
619 }
620
621 /*
622 * Initialize a new player for given sound file. Use the given
623 * PortAudio device for audio output, or scan for default host API
624 * output device if outdev = -1.
625 *
626 * Return handle to the new player, or NULL on error.
627 */
628 Player *
init_player(const char * filename,PaDeviceIndex outdev)629 init_player(const char *filename, PaDeviceIndex outdev)
630 {
631 Player *player;
632 PaError pa_rval;
633 size_t nbytes;
634 PaStreamParameters strparams;
635
636 /* init player structure */
637 player = g_malloc(sizeof(Player));
638 player->stream = NULL;
639 player->reader.sndfile = NULL;
640 player->reader.sem_ok = player->reader.thread_ok = 0;
641 player->reader.is_eof = 0;
642 pthread_mutex_init(&player->reader.control_cond_mutex, NULL);
643 pthread_cond_init(&player->reader.control_cond, NULL);
644 player->controller.messages = NULL;
645 player->controller.thread_ok = 0;
646 player->buffer.begin = player->buffer.end = NULL;
647 player->buffer.in = player->buffer.out = NULL;
648 player->buffer.space_sem_ok = 0;
649 player->state.location = 0;
650 player->origin = 0;
651
652 /* initialize audio */
653 pthread_mutex_lock(&audio_init_count_mutex);
654 if (audio_init_count == 0) {
655 pa_rval = Pa_Initialize();
656 /* scan for default device */
657
658 if (pa_rval != paNoError) {
659 g_warning("can't initialize audio subsystem: %s",
660 Pa_GetErrorText(pa_rval));
661 close_player(player);
662 pthread_mutex_unlock(&audio_init_count_mutex);
663 return NULL;
664 }
665
666 }
667 audio_init_count++;
668 pthread_mutex_unlock(&audio_init_count_mutex);
669
670 /* scan for default output device, if needed */
671 if (outdev == -1) outdev = scan_audio_output();
672 if (outdev == -1) {
673 g_warning("can't find default output audio device");
674 close_player(player);
675 return NULL;
676 }
677
678 /* open sound file */
679 if ((player->reader.sndfile = open_sound_file(filename))) {
680 player->reader.metadata = get_metadata(player->reader.sndfile);
681 } else {
682 g_warning("can't open file '%s'", filename);
683 close_player(player);
684 return NULL;
685 }
686
687 /* open playback stream */
688 memset(&strparams, 0, sizeof(PaStreamParameters));
689 strparams.channelCount = player->reader.metadata.channels;
690 strparams.device = outdev;
691 strparams.sampleFormat = paFloat32;
692 strparams.suggestedLatency =
693 Pa_GetDeviceInfo(outdev)->defaultHighOutputLatency;
694 strparams.hostApiSpecificStreamInfo = NULL;
695 pa_rval = Pa_OpenStream(&player->stream, NULL, &strparams,
696 player->reader.metadata.rate, 0,
697 paNoFlag, stream_callback,
698 player);
699 if (pa_rval != paNoError) {
700 g_warning("can't open audio stream for playback: %s",
701 Pa_GetErrorText(pa_rval));
702 player->stream = NULL;
703 close_player(player);
704 return NULL;
705 }
706 Pa_SetStreamFinishedCallback(player->stream, stream_finished_callback);
707
708 /* calculate buffer size */
709 nbytes = 1024 * BUFFER_SIZE;
710 nbytes -= nbytes % (player->reader.metadata.channels
711 * sizeof(float));
712 player->buffer.bufsize = nbytes / sizeof(float);
713 player->buffer.begin = (float *) g_malloc(nbytes);
714 player->buffer.end = player->buffer.begin + player->buffer.bufsize;
715 player->buffer.in = player->buffer.out = player->buffer.begin;
716 player->buffer.nframes = 0;
717 /* g_debug("allocated %d bytes of memory for sample buffer", nbytes); */
718
719 /* initialize semaphores */
720 player->buffer.space_sem_ok =
721 (sem_init(&player->buffer.space, 0, 1) == 0);
722 player->reader.sem_ok =
723 (sem_init(&player->reader.sem, 0, 0) == 0);
724 player->reader.notify_ok =
725 (sem_init(&player->reader.notify, 0, 0) == 0);
726 if (!player->buffer.space_sem_ok
727 || !player->reader.sem_ok
728 || !player->reader.notify_ok) {
729 close_player(player);
730 return NULL;
731 }
732
733 /* create threads */
734 if (!g_thread_supported()) g_thread_init(NULL);
735
736 player->controller.messages = g_async_queue_new();
737 player->controller.thread_ok =
738 (pthread_create(&player->controller.thread_id, NULL,
739 controller_main, player) == 0);
740 if (!player->controller.thread_ok) {
741 g_warning("can't create controller thread");
742 close_player(player);
743 return NULL;
744 }
745
746 player->reader.control = READER_STOP;
747 player->reader.thread_ok =
748 (pthread_create(&player->reader.thread_id, NULL,
749 reader_main, player) == 0);
750 if (!player->reader.thread_ok) {
751 g_warning("can't create reader thread");
752 close_player(player);
753 return NULL;
754 }
755 /* g_debug("waiting for reader"); */
756 sem_wait(&player->reader.sem);
757
758 /* g_debug("player %p ok", (void *) player); */
759 return player;
760 }
761
762 /*
763 * Close player and free allocated resources.
764 */
765 void
close_player(Player * player)766 close_player(Player *player)
767 {
768 Message *msg;
769 assert(player);
770
771 /* g_debug("closing player %p", (void *) player); */
772
773 if (player->controller.thread_ok) {
774 /* terminate threads */
775 msg = g_malloc(sizeof(Message));
776 msg->command = CMD_TERM;
777 msg->sem = NULL;
778 g_async_queue_push(player->controller.messages, msg);
779 pthread_join(player->controller.thread_id, NULL);
780 }
781
782 if (player->reader.sndfile &&
783 close_sound_file(player->reader.sndfile) != 0) {
784 g_warning("failed to close sound file");
785 }
786
787 pthread_mutex_lock(&audio_init_count_mutex);
788 audio_init_count--;
789 if (audio_init_count == 0) {
790 if (Pa_Terminate() != paNoError) {
791 g_warning("failed to shut down audio subsystem");
792 }
793 }
794 pthread_mutex_unlock(&audio_init_count_mutex);
795
796 if (player->buffer.begin) g_free(player->buffer.begin);
797 pthread_mutex_destroy(&player->reader.control_cond_mutex);
798 pthread_cond_destroy(&player->reader.control_cond);
799 if (player->buffer.space_sem_ok) sem_destroy(&player->buffer.space);
800 if (player->reader.sem_ok) sem_destroy(&player->reader.sem);
801
802 g_free(player);
803 /* g_debug("closed player %p", (void *) player); */
804 }
805
806 /*
807 * Return sound file metadata from player.
808 */
809 Metadata
get_player_metadata(Player * player)810 get_player_metadata(Player *player)
811 {
812 assert(player);
813 return player->reader.metadata;
814 }
815
816 /*
817 * Return the current playback state.
818 */
819 Player_state
get_player_state(Player * player)820 get_player_state(Player *player)
821 {
822 assert(player);
823 return player->state;
824 }
825
826 /*
827 * Start playback. If sem is non-NULL, it will be signalled after
828 * playback has been started.
829 */
830 void
start_player(Player * player,sem_t * sem)831 start_player(Player *player, sem_t *sem)
832 {
833 Message *msg;
834 assert(player);
835 msg = g_malloc(sizeof(Message));
836 msg->command = CMD_PLAY;
837 msg->sem = sem;
838 g_async_queue_push(player->controller.messages, msg);
839 }
840
841 /*
842 * Stop playback. If sem is non-NULL, it will be signalled after
843 * playback has been stopped.
844 */
845 void
stop_player(Player * player,sem_t * sem)846 stop_player(Player *player, sem_t *sem)
847 {
848 Message *msg;
849 assert(player);
850 msg = g_malloc(sizeof(Message));
851 msg->command = CMD_STOP;
852 msg->sem = sem;
853 g_async_queue_push(player->controller.messages, msg);
854 }
855
856 /*
857 * Pause or resume playback. If sem is not NULL,
858 * it will be signalled after seeking.
859 */
860 void
pause_or_resume_player(Player * player,sem_t * sem)861 pause_or_resume_player(Player *player, sem_t *sem)
862 {
863 Message *msg;
864 assert(player);
865 msg = g_malloc(sizeof(Message));
866 msg->command = CMD_PAUSE;
867 msg->sem = sem;
868 g_async_queue_push(player->controller.messages, msg);
869 }
870
871 /*
872 * Seek player using seek function (which see). If sem is not NULL,
873 * it will be signalled after seeking.
874 */
875 void
seek_player(Player * player,double offset,int whence,sem_t * sem)876 seek_player(Player *player, double offset, int whence, sem_t *sem)
877 {
878 Message *msg;
879 assert(player);
880 msg = g_malloc(sizeof(Message));
881 msg->command = CMD_SEEK;
882 msg->offset = offset;
883 msg->whence = whence;
884 msg->sem = sem;
885 g_async_queue_push(player->controller.messages, msg);
886 }
887