1 /*
2 * GPAC - Multimedia Framework C SDK
3 *
4 * Authors: Jean Le Feuvre
5 * Copyright (c) Telecom ParisTech 2018
6 * All rights reserved
7 *
8 * This file is part of GPAC / audio output filter
9 *
10 * GPAC is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * GPAC is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; see the file COPYING. If not, write to
22 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25
26
27 #include <gpac/filters.h>
28 #include <gpac/constants.h>
29 #include <gpac/modules/audio_out.h>
30 #include <gpac/thread.h>
31
32 typedef struct
33 {
34 //options
35 char *drv;
36 u32 bnum, bdur, threaded, priority;
37 Bool clock;
38 GF_Fraction64 dur;
39 Double speed, start;
40 u32 vol, pan, buffer;
41 GF_Fraction adelay;
42
43 GF_FilterPid *pid;
44 u32 sr, afmt, nb_ch, timescale;
45 u64 ch_cfg;
46 GF_AudioOutput *audio_out;
47 GF_Thread *th;
48 u32 audio_th_state;
49 Bool needs_recfg, wait_recfg;
50 u32 bytes_per_sample;
51
52 u32 pck_offset;
53 u64 first_cts;
54 Bool aborted;
55 Bool speed_set;
56 GF_Filter *filter;
57 Bool is_eos;
58 Bool first_write_done;
59
60 s32 pid_delay;
61
62 Bool buffer_done, no_buffering;
63 u64 hwdelay_us, totaldelay_us;
64 } GF_AudioOutCtx;
65
66
aout_reconfig(GF_AudioOutCtx * ctx)67 void aout_reconfig(GF_AudioOutCtx *ctx)
68 {
69 u32 sr, afmt, old_afmt, nb_ch;
70 u64 ch_cfg;
71 GF_Err e = GF_OK;
72 sr = ctx->sr;
73
74 nb_ch = ctx->nb_ch;
75 afmt = old_afmt = ctx->afmt;
76 ch_cfg = ctx->ch_cfg;
77
78 //config not ready, wait
79 if (!nb_ch || !sr || !afmt) {
80 //force a get_packet to trigger reconfigure
81 gf_filter_pid_get_packet(ctx->pid);
82 return;
83 }
84
85 e = ctx->audio_out->Configure(ctx->audio_out, &sr, &nb_ch, &afmt, ch_cfg);
86 if (e) {
87 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[AudioOut] Failed to configure audio output: %s\n", gf_error_to_string(e) ));
88 afmt = GF_AUDIO_FMT_S16;
89 sr = 44100;
90 nb_ch = 2;
91 }
92 if (ctx->speed == FIX_ONE) ctx->speed_set = GF_TRUE;
93
94 if (ctx->vol<=100) {
95 if (ctx->audio_out->SetVolume)
96 ctx->audio_out->SetVolume(ctx->audio_out, ctx->vol);
97 ctx->vol = 101;
98 }
99 if (ctx->pan<=100) {
100 if (ctx->audio_out->SetPan)
101 ctx->audio_out->SetPan(ctx->audio_out, ctx->pan);
102 ctx->pan = 101;
103 }
104
105
106 if (ctx->sr * ctx->nb_ch * old_afmt == 0) {
107 ctx->needs_recfg = GF_FALSE;
108 ctx->wait_recfg = GF_FALSE;
109 return;
110 }
111
112 if ((sr != ctx->sr) || (nb_ch!=ctx->nb_ch) || (afmt!=old_afmt) || !ctx->speed_set) {
113 gf_filter_pid_negociate_property(ctx->pid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(sr));
114 gf_filter_pid_negociate_property(ctx->pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(afmt));
115 gf_filter_pid_negociate_property(ctx->pid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(nb_ch));
116 gf_filter_pid_negociate_property(ctx->pid, GF_PROP_PID_AUDIO_SPEED, &PROP_DOUBLE(ctx->speed));
117 ctx->speed_set = GF_TRUE;
118 ctx->needs_recfg = GF_FALSE;
119 //drop all packets until next reconfig
120 ctx->wait_recfg = GF_TRUE;
121 ctx->sr = sr;
122 ctx->nb_ch = nb_ch;
123 ctx->afmt = afmt;
124 ctx->ch_cfg = ch_cfg;
125 } else if (e==GF_OK) {
126 ctx->needs_recfg = GF_FALSE;
127 ctx->wait_recfg = GF_FALSE;
128 }
129 ctx->bytes_per_sample = gf_audio_fmt_bit_depth(afmt) * nb_ch / 8;
130 ctx->hwdelay_us = 0;
131 if (ctx->audio_out->GetAudioDelay) {
132 ctx->hwdelay_us = ctx->audio_out->GetAudioDelay(ctx->audio_out);
133 ctx->hwdelay_us *= 1000;
134 GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("[AudioOut] Hardware delay is "LLU" us\n", ctx->hwdelay_us ));
135 }
136 ctx->totaldelay_us = 0;
137 if (ctx->audio_out->GetTotalBufferTime) {
138 ctx->totaldelay_us = ctx->audio_out->GetTotalBufferTime(ctx->audio_out);
139 ctx->totaldelay_us *= 1000;
140 GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("[AudioOut] Total audio delay is "LLU" ms\n", ctx->totaldelay_us ));
141 }
142 }
143
aout_th_proc(void * p)144 u32 aout_th_proc(void *p)
145 {
146 GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) p;
147
148 ctx->audio_th_state = 1;
149
150 GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[AudioOut] Entering audio thread ID %d\n", gf_th_id() ));
151
152 while (ctx->audio_th_state == 1) {
153 if (ctx->needs_recfg) {
154 aout_reconfig(ctx);
155 } else if (ctx->pid) {
156 ctx->audio_out->WriteAudio(ctx->audio_out);
157 } else {
158 gf_sleep(10);
159 }
160 }
161 GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[AudioOut] Exiting audio thread\n"));
162 ctx->audio_out->Shutdown(ctx->audio_out);
163 ctx->audio_th_state = 3;
164 return 0;
165 }
166
167
aout_fill_output(void * ptr,u8 * buffer,u32 buffer_size)168 static u32 aout_fill_output(void *ptr, u8 *buffer, u32 buffer_size)
169 {
170 u32 done = 0;
171 GF_AudioOutCtx *ctx = ptr;
172 Bool is_first_pck = GF_TRUE;
173
174 memset(buffer, 0, buffer_size);
175 if (!ctx->pid || ctx->aborted) return 0;
176
177 if (!ctx->buffer_done) {
178 u32 size;
179 GF_FilterPacket *pck;
180
181 //query full buffer duration in us
182 u64 dur = gf_filter_pid_query_buffer_duration(ctx->pid, GF_FALSE);
183
184 GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AudioOut] buffer %d / %d ms\r", dur/1000, ctx->buffer));
185
186 /*the compositor sends empty packets after its reconfiguration to check when the config is active
187 we therefore probe the first packet before probing the buffer fullness*/
188 pck = gf_filter_pid_get_packet(ctx->pid);
189 if (!pck) return 0;
190
191 if (! gf_filter_pck_is_blocking_ref(pck)) {
192 if ((dur < ctx->buffer * 1000) && !gf_filter_pid_is_eos(ctx->pid))
193 return 0;
194 gf_filter_pck_get_data(pck, &size);
195 if (!size) {
196 gf_filter_pid_drop_packet(ctx->pid);
197 return 0;
198 }
199 //check the decoder output is full (avoids initial underrun)
200 if (gf_filter_pid_query_buffer_duration(ctx->pid, GF_TRUE)==0)
201 return 0;
202 }
203 ctx->buffer_done = GF_TRUE;
204 }
205 //do not throw underflow log util first packet is fetched
206 if (ctx->first_write_done)
207 is_first_pck = GF_FALSE;
208
209 while (done < buffer_size) {
210 const char *data;
211 u32 size;
212 u64 cts;
213 s64 delay;
214 GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->pid);
215 if (!pck) {
216 if (gf_filter_pid_is_eos(ctx->pid)) {
217 ctx->is_eos = GF_TRUE;
218 } else if (!is_first_pck) {
219 GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AudioOut] buffer underflow\n"));
220 }
221 return done;
222 }
223 ctx->is_eos = GF_FALSE;
224 if (ctx->needs_recfg) {
225 return done;
226 }
227
228 delay = ctx->pid_delay;
229 if (ctx->adelay.den)
230 delay += ctx->adelay.num * (s32)ctx->timescale / (s32)ctx->adelay.den;
231
232 cts = gf_filter_pck_get_cts(pck);
233 if (delay >= 0) {
234 cts += delay;
235 } else if (cts < (u64) -delay) {
236 gf_filter_pid_drop_packet(ctx->pid);
237 continue;
238 } else {
239 cts -= (u64) -delay;
240 }
241
242 if (ctx->dur.num>0) {
243 if (!ctx->first_cts) ctx->first_cts = cts+1;
244
245 if ((cts - ctx->first_cts + 1) * ctx->dur.den > (u64) ctx->dur.num*ctx->timescale) {
246 gf_filter_pid_drop_packet(ctx->pid);
247 if (!ctx->aborted) {
248 GF_FilterEvent evt;
249 GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->pid);
250 gf_filter_pid_send_event(ctx->pid, &evt);
251
252 ctx->aborted = GF_TRUE;
253 }
254 return done;
255 }
256 }
257
258 data = gf_filter_pck_get_data(pck, &size);
259
260 if (!done && ctx->clock && data && size) {
261 GF_Fraction64 timestamp;
262 timestamp.num = cts;
263 if (ctx->pck_offset)
264 timestamp.num += ctx->pck_offset/ctx->bytes_per_sample;
265
266 timestamp.num -= (ctx->hwdelay_us*ctx->timescale)/1000000;
267 if (timestamp.num<0) timestamp.num = 0;
268 timestamp.den = ctx->timescale;
269 gf_filter_hint_single_clock(ctx->filter, gf_sys_clock_high_res(), timestamp);
270 GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[AudioOut] At %d ms audio frame CTS "LLU" (compensated time %g s)\n", gf_sys_clock(), cts, ((Double)timestamp.num)/timestamp.den ));
271 }
272
273 if (data && !ctx->wait_recfg) {
274 u32 nb_copy;
275 assert(size >= ctx->pck_offset);
276
277 nb_copy = (size - ctx->pck_offset);
278 if (nb_copy + done > buffer_size) nb_copy = buffer_size - done;
279 memcpy(buffer+done, data+ctx->pck_offset, nb_copy);
280
281 if (!done && gf_filter_reporting_enabled(ctx->filter)) {
282 char szStatus[1024];
283 u64 bdur = gf_filter_pid_query_buffer_duration(ctx->pid, GF_FALSE);
284 sprintf(szStatus, "%d Hz %d ch %s buffer %d / %d ms", ctx->sr, ctx->nb_ch, gf_audio_fmt_name(ctx->afmt), (u32) (bdur/1000), ctx->buffer);
285 gf_filter_update_status(ctx->filter, -1, szStatus);
286 }
287
288
289 done += nb_copy;
290 ctx->first_write_done = GF_TRUE;
291 is_first_pck = GF_FALSE;
292 if (nb_copy + ctx->pck_offset < size) {
293 ctx->pck_offset += nb_copy;
294 return done;
295 }
296 ctx->pck_offset = 0;
297 }
298 gf_filter_pid_drop_packet(ctx->pid);
299 }
300 return done;
301 }
302
aout_set_priority(GF_AudioOutCtx * ctx,u32 prio)303 void aout_set_priority(GF_AudioOutCtx *ctx, u32 prio)
304 {
305 if (prio==ctx->priority) return;
306 ctx->priority = prio;
307 if (ctx->th) gf_th_set_priority(ctx->th, (s32) ctx->priority);
308 else if (ctx->audio_out->SelfThreaded && ctx->audio_out->SetPriority)
309 ctx->audio_out->SetPriority(ctx->audio_out, ctx->priority);
310 }
311
aout_configure_pid(GF_Filter * filter,GF_FilterPid * pid,Bool is_remove)312 static GF_Err aout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
313 {
314 const GF_PropertyValue *p;
315 GF_PropertyEntry *pe=NULL;
316 u32 sr, nb_ch, afmt, timescale;
317 u64 ch_cfg;
318 GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter);
319
320 if (is_remove) {
321 assert(ctx->pid==pid);
322 ctx->pid=NULL;
323 return GF_OK;
324 }
325 assert(!ctx->pid || (ctx->pid==pid));
326 gf_filter_pid_check_caps(pid);
327
328 sr = afmt = nb_ch = timescale = 0;
329 ch_cfg = 0;
330 p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE);
331 if (p) timescale = p->value.uint;
332
333 p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE);
334 if (p) sr = p->value.uint;
335 p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT);
336 if (p) afmt = p->value.uint;
337 p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS);
338 if (p) nb_ch = p->value.uint;
339 p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT);
340 if (p) ch_cfg = p->value.longuint;
341
342 if (ctx->audio_out->SetVolume) {
343 p = gf_filter_pid_get_info(pid, GF_PROP_PID_AUDIO_VOLUME, &pe);
344 if (p) ctx->audio_out->SetVolume(ctx->audio_out, p->value.uint);
345 }
346 if (ctx->audio_out->SetPan) {
347 p = gf_filter_pid_get_info(pid, GF_PROP_PID_AUDIO_PAN, &pe);
348 if (p) ctx->audio_out->SetPan(ctx->audio_out, p->value.uint);
349 }
350 gf_filter_release_property(pe);
351
352 p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_PRIORITY);
353 if (p) aout_set_priority(ctx, p->value.uint);
354
355 if (ctx->first_cts && (ctx->timescale != timescale)) {
356 ctx->first_cts-=1;
357 ctx->first_cts *= timescale;
358 ctx->first_cts /= ctx->timescale;
359 ctx->first_cts+=1;
360 }
361 ctx->timescale = timescale;
362
363 p = gf_filter_pid_get_property_str(pid, "BufferLength");
364 ctx->no_buffering = (p && !p->value.sint) ? GF_TRUE : GF_FALSE;
365 if (ctx->no_buffering) ctx->buffer_done = GF_TRUE;
366
367 if ((ctx->sr==sr) && (ctx->afmt == afmt) && (ctx->nb_ch == nb_ch) && (ctx->ch_cfg == ch_cfg)) {
368 ctx->needs_recfg = GF_FALSE;
369 ctx->wait_recfg = GF_FALSE;
370 return GF_OK;
371 }
372
373 //whenever change of sample rate / format / channel, force buffer requirements and speed setup
374 if ((ctx->sr!=sr) || (ctx->afmt != afmt) || (ctx->nb_ch != nb_ch)) {
375 GF_FilterEvent evt;
376 ctx->speed_set = GF_FALSE;
377
378 //set buffer reqs to bdur or 100 ms - we don't "buffer" in the filter, but this will allow dispatching
379 //several input frames in the buffer (default being 1 pck / 1000 us max in buffers). Not doing so could cause
380 //the session to end because input is blocked (no tasks posted) and output still holds a packet
381 GF_FEVT_INIT(evt, GF_FEVT_BUFFER_REQ, pid);
382 evt.buffer_req.max_buffer_us = ctx->buffer * 1000;
383 if (ctx->bdur) {
384 u64 b = ctx->bdur;
385 b *= 1000;
386 if (evt.buffer_req.max_buffer_us < b)
387 evt.buffer_req.max_buffer_us = (u32) b;
388 }
389 evt.buffer_req.pid_only = GF_TRUE;
390
391 gf_filter_pid_send_event(pid, &evt);
392
393 gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "AudioOut");
394 gf_filter_pid_send_event(pid, &evt);
395 ctx->speed = evt.play.speed;
396 ctx->start = evt.play.start_range;
397 }
398
399 ctx->pid = pid;
400 ctx->sr = sr;
401 ctx->afmt = afmt;
402 ctx->nb_ch = nb_ch;
403 ctx->ch_cfg = ch_cfg;
404
405 p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_PRIORITY);
406 if (p) aout_set_priority(ctx, p->value.uint);
407
408 p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY);
409 ctx->pid_delay = p ? p->value.sint : 0;
410
411 ctx->needs_recfg = GF_TRUE;
412
413 //not threaded, request a task to restart audio (cannot do it during the audio callback)
414 if (!ctx->th) gf_filter_post_process_task(filter);
415 return GF_OK;
416 }
417
aout_initialize(GF_Filter * filter)418 static GF_Err aout_initialize(GF_Filter *filter)
419 {
420 const char *sOpt;
421 void *os_wnd_handler;
422 GF_Err e;
423 GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter);
424
425 ctx->filter = filter;
426
427
428 ctx->audio_out = (GF_AudioOutput *) gf_module_load(GF_AUDIO_OUTPUT_INTERFACE, ctx->drv);
429 /*if not init we run with a NULL audio compositor*/
430 if (!ctx->audio_out) {
431 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[AudioOut] No audio output modules found, cannot load audio output\n"));
432 return GF_IO_ERR;
433 }
434 if (!gf_opts_get_key("core", "audio-output")) {
435 gf_opts_set_key("core", "audio-output", ctx->audio_out->module_name);
436 }
437
438 ctx->audio_out->FillBuffer = aout_fill_output;
439 ctx->audio_out->audio_renderer = ctx;
440
441 GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[AudioOut] Setting up audio module %s\n", ctx->audio_out->module_name));
442
443 if (!ctx->bnum || !ctx->bdur) ctx->bnum = ctx->bdur = 0;
444
445 os_wnd_handler = NULL;
446 sOpt = gf_opts_get_key("Temp", "OSWnd");
447 if (sOpt) sscanf(sOpt, "%p", &os_wnd_handler);
448 e = ctx->audio_out->Setup(ctx->audio_out, os_wnd_handler, ctx->bnum, ctx->bdur);
449
450 if (e != GF_OK) {
451 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[AudioOut] Could not setup module %s\n", ctx->audio_out->module_name));
452 gf_modules_close_interface((GF_BaseInterface *)ctx->audio_out);
453 ctx->audio_out = NULL;
454 return e;
455 }
456 /*only used for coverage for now*/
457 if (ctx->audio_out->QueryOutputSampleRate) {
458 u32 sr = 48000;
459 u32 ch = 2;
460 u32 bps = 16;
461 ctx->audio_out->QueryOutputSampleRate(ctx->audio_out, &sr, &ch, &bps);
462 }
463 if (ctx->audio_out->SelfThreaded) {
464 } else if (ctx->threaded) {
465 ctx->th = gf_th_new("AudioOutput");
466 gf_th_run(ctx->th, aout_th_proc, ctx);
467 }
468
469 aout_set_priority(ctx, GF_THREAD_PRIORITY_REALTIME);
470
471 return GF_OK;
472 }
473
aout_finalize(GF_Filter * filter)474 static void aout_finalize(GF_Filter *filter)
475 {
476 GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter);
477
478 /*stop and shutdown*/
479 if (ctx->audio_out) {
480 /*kill audio thread*/
481 if (ctx->th) {
482 GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[AudioOut] stopping audio thread\n"));
483 ctx->audio_th_state = 2;
484 while (ctx->audio_th_state != 3) {
485 gf_sleep(33);
486 }
487 GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[AudioOut] audio thread stopped\n"));
488 gf_th_del(ctx->th);
489 } else {
490 ctx->audio_out->Shutdown(ctx->audio_out);
491 }
492 gf_modules_close_interface((GF_BaseInterface *)ctx->audio_out);
493 ctx->audio_out = NULL;
494 }
495 }
496
aout_process(GF_Filter * filter)497 static GF_Err aout_process(GF_Filter *filter)
498 {
499 GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter);
500
501 if (!ctx->th && ctx->needs_recfg) {
502 aout_reconfig(ctx);
503 }
504
505 if (ctx->th || ctx->audio_out->SelfThreaded) {
506 if (ctx->is_eos) return GF_EOS;
507 gf_filter_ask_rt_reschedule(filter, 100000);
508 return GF_OK;
509 }
510
511 ctx->audio_out->WriteAudio(ctx->audio_out);
512 return GF_OK;
513 }
514
aout_process_event(GF_Filter * filter,const GF_FilterEvent * evt)515 static Bool aout_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
516 {
517 GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter);
518 if (!ctx->audio_out) return GF_TRUE;
519
520 switch (evt->base.type) {
521 case GF_FEVT_PLAY:
522 if (ctx->audio_out->Play) ctx->audio_out->Play(ctx->audio_out, evt->play.hw_buffer_reset ? 2 : 1);
523 break;
524 case GF_FEVT_STOP:
525 if (ctx->audio_out->Play) ctx->audio_out->Play(ctx->audio_out, 0);
526 break;
527 default:
528 break;
529 }
530 //cancel
531 return GF_TRUE;
532 }
533
534 #define OFFS(_n) #_n, offsetof(GF_AudioOutCtx, _n)
535
536 static const GF_FilterArgs AudioOutArgs[] =
537 {
538 { OFFS(drv), "audio driver name", GF_PROP_NAME, NULL, NULL, 0},
539 { OFFS(bnum), "number of audio buffers - 0 for auto", GF_PROP_UINT, "2", NULL, 0},
540 { OFFS(bdur), "total duration of all buffers in ms - 0 for auto. The longer the audio buffer is, the longer the audio latency will be (pause/resume). The quality of fast forward audio playback will also be degradated when using large audio buffers", GF_PROP_UINT, "100", NULL, 0},
541 { OFFS(threaded), "force dedicated thread creation if sound card driver is not threaded", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED},
542 { OFFS(dur), "only play the specified duration", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_HINT_ADVANCED},
543 { OFFS(clock), "hint audio clock for this stream (reports system time and CTS), for other filters to use", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED},
544 { OFFS(speed), "set playback speed. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0},
545 { OFFS(start), "set playback start offset. Negative value means percent of media dur with -1 <=> dur", GF_PROP_DOUBLE, "0.0", NULL, 0},
546 { OFFS(vol), "set default audio volume, as a percentage between 0 and 100", GF_PROP_UINT, "100", "0-100", GF_FS_ARG_UPDATE},
547 { OFFS(pan), "set stereo pan, as a percentage between 0 and 100, 50 being centered", GF_PROP_UINT, "50", "0-100", GF_FS_ARG_UPDATE},
548 { OFFS(buffer), "set buffer in ms", GF_PROP_UINT, "200", NULL, 0},
549 { OFFS(adelay), "set audio delay in sec", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
550 {0}
551 };
552
553 static const GF_FilterCapability AudioOutCaps[] =
554 {
555 CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO),
556 CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW),
557 //we accept all audio formats, but will ask for input reconfiguration if sound card does not support
558 };
559
560
561 GF_FilterRegister AudioOutRegister = {
562 .name = "aout",
563 GF_FS_SET_DESCRIPTION("Audio output")
564 GF_FS_SET_HELP("This filter outputs a single uncompressed audio PID to a soundcard.")
565 .private_size = sizeof(GF_AudioOutCtx),
566 .args = AudioOutArgs,
567 SETCAPS(AudioOutCaps),
568 .initialize = aout_initialize,
569 .finalize = aout_finalize,
570 .configure_pid = aout_configure_pid,
571 .process = aout_process,
572 .process_event = aout_process_event
573 };
574
aout_register(GF_FilterSession * session)575 const GF_FilterRegister *aout_register(GF_FilterSession *session)
576 {
577 return &AudioOutRegister;
578 }
579
580