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