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