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 }