1 /*
2  * playaudio.c:
3  * Play audio data.
4  *
5  * The game here is that we use threads and fork. Two things you never want to
6  * see together in the same sentence. Presently we only do MPEG data. Because
7  * we can't assume that mpg123 or whatever isn't going to roll over and play
8  * dead if we present it with random stuff we picked up off the network, we
9  * arrange to restart it if it dies.
10  *
11  * Copyright (c) 2002 Chris Lightfoot.
12  * Email: chris@ex-parrot.com; WWW: http://www.ex-parrot.com/~chris/
13  *
14  */
15 
16 #ifdef HAVE_CONFIG_H
17     #include <config.h>
18 #endif
19 
20 #include "compat/compat.h"
21 
22 #include <errno.h>
23 #include <pthread.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h> /* On many systems (Darwin...), stdio.h is a prerequisite. */
27 #include <string.h>
28 #include <time.h>
29 #include <unistd.h>
30 #include <sys/wait.h>
31 
32 #include "common/util.h"
33 #include "common/log.h"
34 
35 #include "playaudio.h"
36 
37 /* The program we use to play MPEG data. Can be changed with -M */
38 char *audio_mpeg_player = "mpg123 -";
39 
40 static pthread_mutex_t mpeg_mtx = PTHREAD_MUTEX_INITIALIZER;
41 
42 sig_atomic_t run_player;
43 
44 #define m_lock      pthread_mutex_lock(&mpeg_mtx)
45 #define m_unlock    pthread_mutex_unlock(&mpeg_mtx)
46 
47 /* audiochunk:
48  * A bit of audio which we are going to throw at the decoder. list represents
49  * the list of all chunks which are available, wr the place that we insert new
50  * data that we've obtained and rd the place that we're reading data to send
51  * into the decoder. */
52 typedef struct _audiochunk {
53     unsigned char *data;
54     size_t len;
55     struct _audiochunk *next;
56 } *audiochunk;
57 
58 static audiochunk list, wr, rd;
59 
60 /* audiochunk_new:
61  * Allocate a buffer and copy some data into it. */
audiochunk_new(const unsigned char * data,const size_t len)62 static audiochunk audiochunk_new(const unsigned char *data, const size_t len) {
63     audiochunk A;
64     alloc_struct(_audiochunk, A);
65     A->len = len;
66     if (data) {
67         A->data = xmalloc(len);
68         memcpy(A->data, data, len);
69     }
70     return A;
71 }
72 
73 /* audiochunk_delete:
74  * Free memory from an audiochunk. */
audiochunk_delete(audiochunk A)75 static void audiochunk_delete(audiochunk A) {
76     xfree(A->data);
77     xfree(A);
78 }
79 
80 /* audiochunk_write:
81  * Write the contents of an audiochunk down a file descriptor. Returns 0 on
82  * success or -1 on failure. */
83 #define WRCHUNK     1024
audiochunk_write(const audiochunk A,int fd)84 static int audiochunk_write(const audiochunk A, int fd) {
85     const unsigned char *p;
86     ssize_t n;
87     if (A->len == 0)
88         return 0;
89     p = A->data;
90     do {
91         size_t d = WRCHUNK;
92         if (p + d > A->data + A->len)
93             d = A->data + A->len - p;
94 
95         n = write(fd, p, d);
96         if (n == -1 && errno != EINTR)
97             return -1;
98         else
99             p += d;
100     } while (p < A->data + A->len);
101     return 0;
102 }
103 
104 /* How much data we have buffered; if this rises too high, we start silently
105  * dropping data. */
106 static size_t buffered;
107 #define MAX_BUFFERED    (8 * 1024 * 1024)   /* 8Mb */
108 
109 /* mpeg_submit_chunk:
110  * Put some MPEG data into the queue to be played. */
mpeg_submit_chunk(const unsigned char * data,const size_t len)111 void mpeg_submit_chunk(const unsigned char *data, const size_t len) {
112     audiochunk A;
113 
114     m_lock;
115 
116     if (buffered > MAX_BUFFERED) {
117         log_msg(LOG_INFO, "MPEG buffer full with %d bytes", buffered);
118         goto finish;
119     }
120     A = audiochunk_new(data, len);
121     wr->next = A;
122     wr = wr->next;
123 
124     buffered += len;
125 
126 finish:
127     m_unlock;
128 }
129 
130 /* mpeg_play:
131  * Play MPEG data. This runs in a separate thread. The parameter is the
132  * audiochunk from which we start reading data. */
133 int mpeg_fd;     /* the file descriptor into which we write data. */
134 
mpeg_play(void * a)135 static void *mpeg_play(void *a) {
136     /*audiochunk A;
137     A = (audiochunk)a;*/
138 
139     while (run_player) {
140         audiochunk A;
141 
142         m_lock;
143         A = rd->next;
144         m_unlock;
145 
146         if (A) {
147             /* Got some data, submit it to the encoder. */
148             if (audiochunk_write(A, mpeg_fd) == -1)
149                 log_msg(LOG_ERROR, "write to MPEG player: %s", strerror(errno));
150 
151             m_lock;
152             buffered -= A->len;
153             audiochunk_delete(rd);
154             rd = A;
155             m_unlock;
156 
157         } else {
158             /* No data, sleep for a little bit. */
159             xnanosleep(100000000);  /* 0.1s */
160         }
161     }
162 
163     return NULL;
164 }
165 
166 /* mpeg_player_manager:
167  * Main loop of child process which keeps an MPEG player running. */
mpeg_player_manager(void)168 static void mpeg_player_manager(void) {
169 
170     struct sigaction sa = {{0}};
171     pid_t mpeg_pid;
172 
173     sa.sa_handler = SIG_DFL;
174     sigaction(SIGCHLD, &sa, NULL);
175 
176     while (run_player) {
177         time_t whenstarted;
178         int st;
179 
180         log_msg(LOG_INFO, "starting MPEG player `%s'", audio_mpeg_player);
181 
182         whenstarted = time(NULL);
183         switch ((mpeg_pid = fork())) {
184             case 0:
185                 execl("/bin/sh", "/bin/sh", "-c", audio_mpeg_player, NULL);
186                 log_msg(LOG_ERROR, "exec: %s", strerror(errno));
187                 abort(); /* TODO: exit ¿? */
188                 break;
189 
190             case -1:
191                 log_msg(LOG_ERROR, "fork: %s", strerror(errno));
192                 abort(); /* gah, not much we can do now. */ /* TODO: exit ¿? */
193                 break;
194 
195             default:
196                 /* parent. */
197                 log_msg(LOG_INFO, " MPEG player has PID %d", (int)mpeg_pid);
198                 break;
199         }
200 
201         /* wait for it to exit. */
202         waitpid(mpeg_pid, &st, 0);
203         mpeg_pid = 0;
204 
205         if (WIFEXITED(st))
206             log_msg(LOG_INFO, "MPEG player exited with status %d", WEXITSTATUS(st));
207         else if (WIFSIGNALED(st))
208             log_msg(LOG_INFO, "MPEG player killed by signal %d", WTERMSIG(st));
209         /* else ?? */
210 
211         if (run_player && time(NULL) - whenstarted < 5) {
212             /* The player expired very quickly. Probably something's wrong;
213              * sleep for a bit and hope the problem goes away. */
214             log_msg(LOG_WARNING, "MPEG player expired after %d seconds, sleeping for a bit", (int)(time(NULL) - whenstarted));
215             sleep(5);
216         }
217     }
218     if (mpeg_pid)
219         kill(mpeg_pid, SIGTERM);
220 }
221 
222 /* do_mpeg_player:
223  * Fork and start a process which keeps an MPEG player running. Then start a
224  * thread which passes data into the player. */
225 pid_t mpeg_mgr_pid;
226 
do_mpeg_player(void)227 void do_mpeg_player(void) {
228     int pp[2];
229     pthread_t thr;
230 
231     rd = wr = list = audiochunk_new(NULL, 0);
232 
233     pipe(pp);
234 
235     run_player = TRUE;
236 
237     mpeg_mgr_pid = fork();
238 
239     if (mpeg_mgr_pid == 0) {
240         close(pp[1]);
241         dup2(pp[0], 0); /* make pipe our standard input */
242         mpeg_player_manager();
243         abort(); /* TODO: exit ¿? */
244     } else {
245         close(pp[0]);
246         mpeg_fd = pp[1];
247         pthread_create(&thr, NULL, mpeg_play, rd);
248     }
249     /* TODO: handle error */
250 
251     /* away we go... */
252 }
253 
stop_mpeg_player(void)254 void stop_mpeg_player(void)
255 {
256     run_player = FALSE;
257 
258     /*
259      * Pass on the signal to the MPEG player manager so that it can abort,
260      * since it won't die when the pipe into it dies.
261      */
262     kill(mpeg_mgr_pid, SIGTERM);
263 }