1 | /*
|
---|
2 | * BeOS audio play interface
|
---|
3 | * Copyright (c) 2000, 2001 Fabrice Bellard.
|
---|
4 | *
|
---|
5 | * This library is free software; you can redistribute it and/or
|
---|
6 | * modify it under the terms of the GNU Lesser General Public
|
---|
7 | * License as published by the Free Software Foundation; either
|
---|
8 | * version 2 of the License, or (at your option) any later version.
|
---|
9 | *
|
---|
10 | * This library is distributed in the hope that it will be useful,
|
---|
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
13 | * Lesser General Public License for more details.
|
---|
14 | *
|
---|
15 | * You should have received a copy of the GNU Lesser General Public
|
---|
16 | * License along with this library; if not, write to the Free Software
|
---|
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
---|
18 | */
|
---|
19 |
|
---|
20 | #include <signal.h>
|
---|
21 | #include <stdlib.h>
|
---|
22 | #include <stdio.h>
|
---|
23 | #include <string.h>
|
---|
24 | #include <unistd.h>
|
---|
25 | #include <sys/time.h>
|
---|
26 |
|
---|
27 | #include <Application.h>
|
---|
28 | #include <SoundPlayer.h>
|
---|
29 |
|
---|
30 | extern "C" {
|
---|
31 | #include "avformat.h"
|
---|
32 | }
|
---|
33 |
|
---|
34 | #ifdef HAVE_BSOUNDRECORDER
|
---|
35 | #include <SoundRecorder.h>
|
---|
36 | using namespace BPrivate::Media::Experimental;
|
---|
37 | #endif
|
---|
38 |
|
---|
39 | /* enable performance checks */
|
---|
40 | //#define PERF_CHECK
|
---|
41 |
|
---|
42 | /* enable Media Kit latency checks */
|
---|
43 | //#define LATENCY_CHECK
|
---|
44 |
|
---|
45 | #define AUDIO_BLOCK_SIZE 4096
|
---|
46 | #define AUDIO_BLOCK_COUNT 8
|
---|
47 |
|
---|
48 | #define AUDIO_BUFFER_SIZE (AUDIO_BLOCK_SIZE*AUDIO_BLOCK_COUNT)
|
---|
49 |
|
---|
50 | typedef struct {
|
---|
51 | int fd; // UNUSED
|
---|
52 | int sample_rate;
|
---|
53 | int channels;
|
---|
54 | int frame_size; /* in bytes ! */
|
---|
55 | CodecID codec_id;
|
---|
56 | uint8_t buffer[AUDIO_BUFFER_SIZE];
|
---|
57 | int buffer_ptr;
|
---|
58 | /* ring buffer */
|
---|
59 | sem_id input_sem;
|
---|
60 | int input_index;
|
---|
61 | sem_id output_sem;
|
---|
62 | int output_index;
|
---|
63 | BSoundPlayer *player;
|
---|
64 | #ifdef HAVE_BSOUNDRECORDER
|
---|
65 | BSoundRecorder *recorder;
|
---|
66 | #endif
|
---|
67 | int has_quit; /* signal callbacks not to wait */
|
---|
68 | volatile bigtime_t starve_time;
|
---|
69 | } AudioData;
|
---|
70 |
|
---|
71 | static thread_id main_thid;
|
---|
72 | static thread_id bapp_thid;
|
---|
73 | static int own_BApp_created = 0;
|
---|
74 | static int refcount = 0;
|
---|
75 |
|
---|
76 | /* create the BApplication and Run() it */
|
---|
77 | static int32 bapp_thread(void *arg)
|
---|
78 | {
|
---|
79 | new BApplication("application/x-vnd.ffmpeg");
|
---|
80 | own_BApp_created = 1;
|
---|
81 | be_app->Run();
|
---|
82 | /* kill the process group */
|
---|
83 | // kill(0, SIGINT);
|
---|
84 | // kill(main_thid, SIGHUP);
|
---|
85 | return B_OK;
|
---|
86 | }
|
---|
87 |
|
---|
88 | /* create the BApplication only if needed */
|
---|
89 | static void create_bapp_if_needed(void)
|
---|
90 | {
|
---|
91 | if (refcount++ == 0) {
|
---|
92 | /* needed by libmedia */
|
---|
93 | if (be_app == NULL) {
|
---|
94 | bapp_thid = spawn_thread(bapp_thread, "ffmpeg BApplication", B_NORMAL_PRIORITY, NULL);
|
---|
95 | resume_thread(bapp_thid);
|
---|
96 | while (!own_BApp_created)
|
---|
97 | snooze(50000);
|
---|
98 | }
|
---|
99 | }
|
---|
100 | }
|
---|
101 |
|
---|
102 | static void destroy_bapp_if_needed(void)
|
---|
103 | {
|
---|
104 | if (--refcount == 0 && own_BApp_created) {
|
---|
105 | be_app->Lock();
|
---|
106 | be_app->Quit();
|
---|
107 | be_app = NULL;
|
---|
108 | }
|
---|
109 | }
|
---|
110 |
|
---|
111 | /* called back by BSoundPlayer */
|
---|
112 | static void audioplay_callback(void *cookie, void *buffer, size_t bufferSize, const media_raw_audio_format &format)
|
---|
113 | {
|
---|
114 | AudioData *s;
|
---|
115 | size_t len, amount;
|
---|
116 | unsigned char *buf = (unsigned char *)buffer;
|
---|
117 |
|
---|
118 | s = (AudioData *)cookie;
|
---|
119 | if (s->has_quit)
|
---|
120 | return;
|
---|
121 | while (bufferSize > 0) {
|
---|
122 | #ifdef PERF_CHECK
|
---|
123 | bigtime_t t;
|
---|
124 | t = system_time();
|
---|
125 | #endif
|
---|
126 | len = MIN(AUDIO_BLOCK_SIZE, bufferSize);
|
---|
127 | if (acquire_sem_etc(s->output_sem, len, B_CAN_INTERRUPT, 0LL) < B_OK) {
|
---|
128 | s->has_quit = 1;
|
---|
129 | s->player->SetHasData(false);
|
---|
130 | return;
|
---|
131 | }
|
---|
132 | amount = MIN(len, (AUDIO_BUFFER_SIZE - s->output_index));
|
---|
133 | memcpy(buf, &s->buffer[s->output_index], amount);
|
---|
134 | s->output_index += amount;
|
---|
135 | if (s->output_index >= AUDIO_BUFFER_SIZE) {
|
---|
136 | s->output_index %= AUDIO_BUFFER_SIZE;
|
---|
137 | memcpy(buf + amount, &s->buffer[s->output_index], len - amount);
|
---|
138 | s->output_index += len-amount;
|
---|
139 | s->output_index %= AUDIO_BUFFER_SIZE;
|
---|
140 | }
|
---|
141 | release_sem_etc(s->input_sem, len, 0);
|
---|
142 | #ifdef PERF_CHECK
|
---|
143 | t = system_time() - t;
|
---|
144 | s->starve_time = MAX(s->starve_time, t);
|
---|
145 | #endif
|
---|
146 | buf += len;
|
---|
147 | bufferSize -= len;
|
---|
148 | }
|
---|
149 | }
|
---|
150 |
|
---|
151 | #ifdef HAVE_BSOUNDRECORDER
|
---|
152 | /* called back by BSoundRecorder */
|
---|
153 | static void audiorecord_callback(void *cookie, bigtime_t timestamp, void *buffer, size_t bufferSize, const media_multi_audio_format &format)
|
---|
154 | {
|
---|
155 | AudioData *s;
|
---|
156 | size_t len, amount;
|
---|
157 | unsigned char *buf = (unsigned char *)buffer;
|
---|
158 |
|
---|
159 | s = (AudioData *)cookie;
|
---|
160 | if (s->has_quit)
|
---|
161 | return;
|
---|
162 |
|
---|
163 | while (bufferSize > 0) {
|
---|
164 | len = MIN(bufferSize, AUDIO_BLOCK_SIZE);
|
---|
165 | //printf("acquire_sem(input, %d)\n", len);
|
---|
166 | if (acquire_sem_etc(s->input_sem, len, B_CAN_INTERRUPT, 0LL) < B_OK) {
|
---|
167 | s->has_quit = 1;
|
---|
168 | return;
|
---|
169 | }
|
---|
170 | amount = MIN(len, (AUDIO_BUFFER_SIZE - s->input_index));
|
---|
171 | memcpy(&s->buffer[s->input_index], buf, amount);
|
---|
172 | s->input_index += amount;
|
---|
173 | if (s->input_index >= AUDIO_BUFFER_SIZE) {
|
---|
174 | s->input_index %= AUDIO_BUFFER_SIZE;
|
---|
175 | memcpy(&s->buffer[s->input_index], buf + amount, len - amount);
|
---|
176 | s->input_index += len - amount;
|
---|
177 | }
|
---|
178 | release_sem_etc(s->output_sem, len, 0);
|
---|
179 | //printf("release_sem(output, %d)\n", len);
|
---|
180 | buf += len;
|
---|
181 | bufferSize -= len;
|
---|
182 | }
|
---|
183 | }
|
---|
184 | #endif
|
---|
185 |
|
---|
186 | static int audio_open(AudioData *s, int is_output, const char *audio_device)
|
---|
187 | {
|
---|
188 | int p[2];
|
---|
189 | int ret;
|
---|
190 | media_raw_audio_format format;
|
---|
191 | media_multi_audio_format iformat;
|
---|
192 |
|
---|
193 | #ifndef HAVE_BSOUNDRECORDER
|
---|
194 | if (!is_output)
|
---|
195 | return -EIO; /* not for now */
|
---|
196 | #endif
|
---|
197 | s->input_sem = create_sem(AUDIO_BUFFER_SIZE, "ffmpeg_ringbuffer_input");
|
---|
198 | if (s->input_sem < B_OK)
|
---|
199 | return -EIO;
|
---|
200 | s->output_sem = create_sem(0, "ffmpeg_ringbuffer_output");
|
---|
201 | if (s->output_sem < B_OK) {
|
---|
202 | delete_sem(s->input_sem);
|
---|
203 | return -EIO;
|
---|
204 | }
|
---|
205 | s->input_index = 0;
|
---|
206 | s->output_index = 0;
|
---|
207 | create_bapp_if_needed();
|
---|
208 | s->frame_size = AUDIO_BLOCK_SIZE;
|
---|
209 | /* bump up the priority (avoid realtime though) */
|
---|
210 | set_thread_priority(find_thread(NULL), B_DISPLAY_PRIORITY+1);
|
---|
211 | #ifdef HAVE_BSOUNDRECORDER
|
---|
212 | if (!is_output) {
|
---|
213 | bool wait_for_input = false;
|
---|
214 | if (audio_device && !strcmp(audio_device, "wait:"))
|
---|
215 | wait_for_input = true;
|
---|
216 | s->recorder = new BSoundRecorder(&iformat, wait_for_input, "ffmpeg input", audiorecord_callback);
|
---|
217 | if (wait_for_input && (s->recorder->InitCheck() == B_OK)) {
|
---|
218 | s->recorder->WaitForIncomingConnection(&iformat);
|
---|
219 | }
|
---|
220 | if (s->recorder->InitCheck() != B_OK || iformat.format != media_raw_audio_format::B_AUDIO_SHORT) {
|
---|
221 | delete s->recorder;
|
---|
222 | s->recorder = NULL;
|
---|
223 | if (s->input_sem)
|
---|
224 | delete_sem(s->input_sem);
|
---|
225 | if (s->output_sem)
|
---|
226 | delete_sem(s->output_sem);
|
---|
227 | return -EIO;
|
---|
228 | }
|
---|
229 | s->codec_id = (iformat.byte_order == B_MEDIA_LITTLE_ENDIAN)?CODEC_ID_PCM_S16LE:CODEC_ID_PCM_S16BE;
|
---|
230 | s->channels = iformat.channel_count;
|
---|
231 | s->sample_rate = (int)iformat.frame_rate;
|
---|
232 | s->frame_size = iformat.buffer_size;
|
---|
233 | s->recorder->SetCookie(s);
|
---|
234 | s->recorder->SetVolume(1.0);
|
---|
235 | s->recorder->Start();
|
---|
236 | return 0;
|
---|
237 | }
|
---|
238 | #endif
|
---|
239 | format = media_raw_audio_format::wildcard;
|
---|
240 | format.format = media_raw_audio_format::B_AUDIO_SHORT;
|
---|
241 | format.byte_order = B_HOST_IS_LENDIAN ? B_MEDIA_LITTLE_ENDIAN : B_MEDIA_BIG_ENDIAN;
|
---|
242 | format.channel_count = s->channels;
|
---|
243 | format.buffer_size = s->frame_size;
|
---|
244 | format.frame_rate = s->sample_rate;
|
---|
245 | s->player = new BSoundPlayer(&format, "ffmpeg output", audioplay_callback);
|
---|
246 | if (s->player->InitCheck() != B_OK) {
|
---|
247 | delete s->player;
|
---|
248 | s->player = NULL;
|
---|
249 | if (s->input_sem)
|
---|
250 | delete_sem(s->input_sem);
|
---|
251 | if (s->output_sem)
|
---|
252 | delete_sem(s->output_sem);
|
---|
253 | return -EIO;
|
---|
254 | }
|
---|
255 | s->player->SetCookie(s);
|
---|
256 | s->player->SetVolume(1.0);
|
---|
257 | s->player->Start();
|
---|
258 | s->player->SetHasData(true);
|
---|
259 | return 0;
|
---|
260 | }
|
---|
261 |
|
---|
262 | static int audio_close(AudioData *s)
|
---|
263 | {
|
---|
264 | if (s->input_sem)
|
---|
265 | delete_sem(s->input_sem);
|
---|
266 | if (s->output_sem)
|
---|
267 | delete_sem(s->output_sem);
|
---|
268 | s->has_quit = 1;
|
---|
269 | if (s->player) {
|
---|
270 | s->player->Stop();
|
---|
271 | }
|
---|
272 | if (s->player)
|
---|
273 | delete s->player;
|
---|
274 | #ifdef HAVE_BSOUNDRECORDER
|
---|
275 | if (s->recorder)
|
---|
276 | delete s->recorder;
|
---|
277 | #endif
|
---|
278 | destroy_bapp_if_needed();
|
---|
279 | return 0;
|
---|
280 | }
|
---|
281 |
|
---|
282 | /* sound output support */
|
---|
283 | static int audio_write_header(AVFormatContext *s1)
|
---|
284 | {
|
---|
285 | AudioData *s = (AudioData *)s1->priv_data;
|
---|
286 | AVStream *st;
|
---|
287 | int ret;
|
---|
288 |
|
---|
289 | st = s1->streams[0];
|
---|
290 | s->sample_rate = st->codec->sample_rate;
|
---|
291 | s->channels = st->codec->channels;
|
---|
292 | ret = audio_open(s, 1, NULL);
|
---|
293 | if (ret < 0)
|
---|
294 | return -EIO;
|
---|
295 | return 0;
|
---|
296 | }
|
---|
297 |
|
---|
298 | static int audio_write_packet(AVFormatContext *s1, int stream_index,
|
---|
299 | const uint8_t *buf, int size, int64_t force_pts)
|
---|
300 | {
|
---|
301 | AudioData *s = (AudioData *)s1->priv_data;
|
---|
302 | int len, ret;
|
---|
303 | #ifdef LATENCY_CHECK
|
---|
304 | bigtime_t lat1, lat2;
|
---|
305 | lat1 = s->player->Latency();
|
---|
306 | #endif
|
---|
307 | #ifdef PERF_CHECK
|
---|
308 | bigtime_t t = s->starve_time;
|
---|
309 | s->starve_time = 0;
|
---|
310 | printf("starve_time: %lld \n", t);
|
---|
311 | #endif
|
---|
312 | while (size > 0) {
|
---|
313 | int amount;
|
---|
314 | len = MIN(size, AUDIO_BLOCK_SIZE);
|
---|
315 | if (acquire_sem_etc(s->input_sem, len, B_CAN_INTERRUPT, 0LL) < B_OK)
|
---|
316 | return -EIO;
|
---|
317 | amount = MIN(len, (AUDIO_BUFFER_SIZE - s->input_index));
|
---|
318 | memcpy(&s->buffer[s->input_index], buf, amount);
|
---|
319 | s->input_index += amount;
|
---|
320 | if (s->input_index >= AUDIO_BUFFER_SIZE) {
|
---|
321 | s->input_index %= AUDIO_BUFFER_SIZE;
|
---|
322 | memcpy(&s->buffer[s->input_index], buf + amount, len - amount);
|
---|
323 | s->input_index += len - amount;
|
---|
324 | }
|
---|
325 | release_sem_etc(s->output_sem, len, 0);
|
---|
326 | buf += len;
|
---|
327 | size -= len;
|
---|
328 | }
|
---|
329 | #ifdef LATENCY_CHECK
|
---|
330 | lat2 = s->player->Latency();
|
---|
331 | printf("#### BSoundPlayer::Latency(): before= %lld, after= %lld\n", lat1, lat2);
|
---|
332 | #endif
|
---|
333 | return 0;
|
---|
334 | }
|
---|
335 |
|
---|
336 | static int audio_write_trailer(AVFormatContext *s1)
|
---|
337 | {
|
---|
338 | AudioData *s = (AudioData *)s1->priv_data;
|
---|
339 |
|
---|
340 | audio_close(s);
|
---|
341 | return 0;
|
---|
342 | }
|
---|
343 |
|
---|
344 | /* grab support */
|
---|
345 |
|
---|
346 | static int audio_read_header(AVFormatContext *s1, AVFormatParameters *ap)
|
---|
347 | {
|
---|
348 | AudioData *s = (AudioData *)s1->priv_data;
|
---|
349 | AVStream *st;
|
---|
350 | int ret;
|
---|
351 |
|
---|
352 | if (!ap || ap->sample_rate <= 0 || ap->channels <= 0)
|
---|
353 | return -1;
|
---|
354 |
|
---|
355 | st = av_new_stream(s1, 0);
|
---|
356 | if (!st) {
|
---|
357 | return -ENOMEM;
|
---|
358 | }
|
---|
359 | s->sample_rate = ap->sample_rate;
|
---|
360 | s->channels = ap->channels;
|
---|
361 |
|
---|
362 | ret = audio_open(s, 0, ap->device);
|
---|
363 | if (ret < 0) {
|
---|
364 | av_free(st);
|
---|
365 | return -EIO;
|
---|
366 | }
|
---|
367 | /* take real parameters */
|
---|
368 | st->codec->codec_type = CODEC_TYPE_AUDIO;
|
---|
369 | st->codec->codec_id = s->codec_id;
|
---|
370 | st->codec->sample_rate = s->sample_rate;
|
---|
371 | st->codec->channels = s->channels;
|
---|
372 | return 0;
|
---|
373 | av_set_pts_info(s1, 48, 1, 1000000); /* 48 bits pts in us */
|
---|
374 | }
|
---|
375 |
|
---|
376 | static int audio_read_packet(AVFormatContext *s1, AVPacket *pkt)
|
---|
377 | {
|
---|
378 | AudioData *s = (AudioData *)s1->priv_data;
|
---|
379 | int size;
|
---|
380 | size_t len, amount;
|
---|
381 | unsigned char *buf;
|
---|
382 | status_t err;
|
---|
383 |
|
---|
384 | if (av_new_packet(pkt, s->frame_size) < 0)
|
---|
385 | return -EIO;
|
---|
386 | buf = (unsigned char *)pkt->data;
|
---|
387 | size = pkt->size;
|
---|
388 | while (size > 0) {
|
---|
389 | len = MIN(AUDIO_BLOCK_SIZE, size);
|
---|
390 | //printf("acquire_sem(output, %d)\n", len);
|
---|
391 | while ((err=acquire_sem_etc(s->output_sem, len, B_CAN_INTERRUPT, 0LL)) == B_INTERRUPTED);
|
---|
392 | if (err < B_OK) {
|
---|
393 | av_free_packet(pkt);
|
---|
394 | return -EIO;
|
---|
395 | }
|
---|
396 | amount = MIN(len, (AUDIO_BUFFER_SIZE - s->output_index));
|
---|
397 | memcpy(buf, &s->buffer[s->output_index], amount);
|
---|
398 | s->output_index += amount;
|
---|
399 | if (s->output_index >= AUDIO_BUFFER_SIZE) {
|
---|
400 | s->output_index %= AUDIO_BUFFER_SIZE;
|
---|
401 | memcpy(buf + amount, &s->buffer[s->output_index], len - amount);
|
---|
402 | s->output_index += len-amount;
|
---|
403 | s->output_index %= AUDIO_BUFFER_SIZE;
|
---|
404 | }
|
---|
405 | release_sem_etc(s->input_sem, len, 0);
|
---|
406 | //printf("release_sem(input, %d)\n", len);
|
---|
407 | buf += len;
|
---|
408 | size -= len;
|
---|
409 | }
|
---|
410 | //XXX: add pts info
|
---|
411 | return 0;
|
---|
412 | }
|
---|
413 |
|
---|
414 | static int audio_read_close(AVFormatContext *s1)
|
---|
415 | {
|
---|
416 | AudioData *s = (AudioData *)s1->priv_data;
|
---|
417 |
|
---|
418 | audio_close(s);
|
---|
419 | return 0;
|
---|
420 | }
|
---|
421 |
|
---|
422 | static AVInputFormat audio_demuxer = {
|
---|
423 | "audio_device",
|
---|
424 | "audio grab and output",
|
---|
425 | sizeof(AudioData),
|
---|
426 | NULL,
|
---|
427 | audio_read_header,
|
---|
428 | audio_read_packet,
|
---|
429 | audio_read_close,
|
---|
430 | NULL,
|
---|
431 | AVFMT_NOFILE,
|
---|
432 | };
|
---|
433 |
|
---|
434 | AVOutputFormat audio_muxer = {
|
---|
435 | "audio_device",
|
---|
436 | "audio grab and output",
|
---|
437 | "",
|
---|
438 | "",
|
---|
439 | sizeof(AudioData),
|
---|
440 | #ifdef WORDS_BIGENDIAN
|
---|
441 | CODEC_ID_PCM_S16BE,
|
---|
442 | #else
|
---|
443 | CODEC_ID_PCM_S16LE,
|
---|
444 | #endif
|
---|
445 | CODEC_ID_NONE,
|
---|
446 | audio_write_header,
|
---|
447 | audio_write_packet,
|
---|
448 | audio_write_trailer,
|
---|
449 | AVFMT_NOFILE,
|
---|
450 | };
|
---|
451 |
|
---|
452 | extern "C" {
|
---|
453 |
|
---|
454 | int audio_init(void)
|
---|
455 | {
|
---|
456 | main_thid = find_thread(NULL);
|
---|
457 | av_register_input_format(&audio_demuxer);
|
---|
458 | av_register_output_format(&audio_muxer);
|
---|
459 | return 0;
|
---|
460 | }
|
---|
461 |
|
---|
462 | } // "C"
|
---|
463 |
|
---|