audiostream.c 76.1 KB
Newer Older
aymeric's avatar
aymeric committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
mediastreamer2 library - modular sound and video processing and streaming
Copyright (C) 2006  Simon MORLAT (simon.morlat@linphone.org)

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
17
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
aymeric's avatar
aymeric committed
18 19 20
*/


jehan's avatar
jehan committed
21 22 23
#ifdef HAVE_CONFIG_H
#include "mediastreamer-config.h"
#endif
aymeric's avatar
aymeric committed
24

jehan's avatar
jehan committed
25
#include "mediastreamer2/mediastream.h"
aymeric's avatar
aymeric committed
26 27 28 29 30
#include "mediastreamer2/dtmfgen.h"
#include "mediastreamer2/mssndcard.h"
#include "mediastreamer2/msrtp.h"
#include "mediastreamer2/msfileplayer.h"
#include "mediastreamer2/msfilerec.h"
31
#include "mediastreamer2/msvolume.h"
smorlat's avatar
smorlat committed
32
#include "mediastreamer2/msequalizer.h"
33 34
#include "mediastreamer2/mstee.h"
#include "mediastreamer2/msaudiomixer.h"
35
#include "mediastreamer2/mscodecutils.h"
Simon Morlat's avatar
Simon Morlat committed
36
#include "mediastreamer2/msitc.h"
37 38
#include "mediastreamer2/msvaddtx.h"
#include "mediastreamer2/msgenericplc.h"
39
#include "mediastreamer2/mseventqueue.h"
Ghislain MARY's avatar
Ghislain MARY committed
40
#include "private.h"
aymeric's avatar
aymeric committed
41

42
#ifdef __ANDROID__
43
#include "mediastreamer2/devices.h"
44
#endif
45

46 47 48 49
#if __APPLE__
#include "TargetConditionals.h"
#endif

50 51
#include <sys/types.h>

52
#ifndef _WIN32
aymeric's avatar
aymeric committed
53 54 55 56
	#include <sys/socket.h>
	#include <netdb.h>
#endif

Simon Morlat's avatar
Simon Morlat committed
57
static void configure_av_recorder(AudioStream *stream);
58
static void configure_decoder(AudioStream *stream, PayloadType *pt, int sample_rate, int nchannels);
Simon Morlat's avatar
Simon Morlat committed
59
static void audio_stream_configure_resampler(AudioStream *st, MSFilter *resampler,MSFilter *from, MSFilter *to);
Simon Morlat's avatar
Simon Morlat committed
60
static void audio_stream_set_rtp_output_gain_db(AudioStream *stream, float gain_db);
Simon Morlat's avatar
Simon Morlat committed
61

62
static void audio_stream_free(AudioStream *stream) {
Ghislain MARY's avatar
Ghislain MARY committed
63
	media_stream_free(&stream->ms);
aymeric's avatar
aymeric committed
64 65 66
	if (stream->soundread!=NULL) ms_filter_destroy(stream->soundread);
	if (stream->soundwrite!=NULL) ms_filter_destroy(stream->soundwrite);
	if (stream->dtmfgen!=NULL) ms_filter_destroy(stream->dtmfgen);
Ghislain MARY's avatar
Ghislain MARY committed
67
	if (stream->flowcontrol != NULL) ms_filter_destroy(stream->flowcontrol);
68
	if (stream->plc!=NULL)	ms_filter_destroy(stream->plc);
aymeric's avatar
aymeric committed
69
	if (stream->ec!=NULL)	ms_filter_destroy(stream->ec);
70 71
	if (stream->volrecv!=NULL) ms_filter_destroy(stream->volrecv);
	if (stream->volsend!=NULL) ms_filter_destroy(stream->volsend);
72 73
	if (stream->mic_equalizer) ms_filter_destroy(stream->mic_equalizer);
	if (stream->spk_equalizer) ms_filter_destroy(stream->spk_equalizer);
74 75
	if (stream->read_decoder != NULL) ms_filter_destroy(stream->read_decoder);
	if (stream->write_encoder != NULL) ms_filter_destroy(stream->write_encoder);
jehan's avatar
jehan committed
76 77
	if (stream->read_resampler!=NULL) ms_filter_destroy(stream->read_resampler);
	if (stream->write_resampler!=NULL) ms_filter_destroy(stream->write_resampler);
78
	if (stream->dtmfgen_rtp!=NULL) ms_filter_destroy(stream->dtmfgen_rtp);
Simon Morlat's avatar
Simon Morlat committed
79
	if (stream->dummy) ms_filter_destroy(stream->dummy);
80 81 82
	if (stream->recv_tee) ms_filter_destroy(stream->recv_tee);
	if (stream->recorder) ms_filter_destroy(stream->recorder);
	if (stream->recorder_mixer) ms_filter_destroy(stream->recorder_mixer);
83 84 85
	if (stream->local_mixer) ms_filter_destroy(stream->local_mixer);
	if (stream->local_player) ms_filter_destroy(stream->local_player);
	if (stream->local_player_resampler) ms_filter_destroy(stream->local_player_resampler);
Simon Morlat's avatar
Simon Morlat committed
86 87 88 89
	if (stream->av_recorder.encoder) ms_filter_destroy(stream->av_recorder.encoder);
	if (stream->av_recorder.recorder) ms_filter_destroy(stream->av_recorder.recorder);
	if (stream->av_recorder.resampler) ms_filter_destroy(stream->av_recorder.resampler);
	if (stream->av_recorder.video_input) ms_filter_destroy(stream->av_recorder.video_input);
90
	if (stream->vaddtx) ms_filter_destroy(stream->vaddtx);
91
	if (stream->outbound_mixer) ms_filter_destroy(stream->outbound_mixer);
92
	if (stream->recorder_file) ms_free(stream->recorder_file);
93
	if (stream->rtp_io_session) rtp_session_destroy(stream->rtp_io_session);
94

aymeric's avatar
aymeric committed
95 96 97 98 99
	ms_free(stream);
}

static int dtmf_tab[16]={'0','1','2','3','4','5','6','7','8','9','*','#','A','B','C','D'};

100
static void on_dtmf_received(RtpSession *s, uint32_t dtmf, void *user_data)
aymeric's avatar
aymeric committed
101
{
smorlat's avatar
smorlat committed
102
	AudioStream *stream=(AudioStream*)user_data;
aymeric's avatar
aymeric committed
103 104 105 106 107
	if (dtmf>15){
		ms_warning("Unsupported telephone-event type.");
		return;
	}
	ms_message("Receiving dtmf %c.",dtmf_tab[dtmf]);
smorlat's avatar
smorlat committed
108 109
	if (stream->dtmfgen!=NULL && stream->play_dtmfs){
		ms_filter_call_method(stream->dtmfgen,MS_DTMF_GEN_PUT,&dtmf_tab[dtmf]);
aymeric's avatar
aymeric committed
110 111 112
	}
}

113 114 115 116 117 118 119 120
/**
 * This function must be called from the MSTicker thread:
 * it replaces one filter by another one.
 * This is a dirty hack that works anyway.
 * It would be interesting to have something that does the job
 * more easily within the MSTicker API.
 * return TRUE if the decoder was changed, FALSE otherwise.
 */
121
static bool_t audio_stream_payload_type_changed(RtpSession *session, void *data) {
122
	AudioStream *stream = (AudioStream *)data;
123
	RtpProfile *prof = rtp_session_get_profile(session);
124
	int payload = rtp_session_get_recv_payload_type(stream->ms.sessions.rtp_session);
125 126 127 128 129 130 131 132
	PayloadType *pt = rtp_profile_get_payload(prof, payload);

	if (stream->ms.decoder == NULL){
		ms_message("audio_stream_payload_type_changed(): no decoder!");
		return FALSE;
	}

	if (pt != NULL){
133 134 135 136 137 138
		MSFilter *dec;
		/* if new payload type is Comfort Noise (CN), just do nothing */
		if (strcasecmp(pt->mime_type, "CN")==0) {
			ms_message("Ignore payload type change to CN");
			return FALSE;
		}
139

140 141 142 143
		if (stream->ms.current_pt && strcasecmp(pt->mime_type, stream->ms.current_pt->mime_type)==0 && pt->clock_rate==stream->ms.current_pt->clock_rate){
			ms_message("Ignoring payload type number change because it points to the same payload type as the current one");
			return FALSE;
		}
144

145 146
		//dec = ms_filter_create_decoder(pt->mime_type);
		dec = ms_factory_create_decoder(stream->ms.factory, pt->mime_type);
147 148
		if (dec != NULL) {
			MSFilter *nextFilter = stream->ms.decoder->outputs[0]->next.filter;
149

150
			ms_message("Replacing decoder on the fly");
151 152 153 154 155
			ms_filter_unlink(stream->ms.rtprecv, 0, stream->ms.decoder, 0);
			ms_filter_unlink(stream->ms.decoder, 0, nextFilter, 0);
			ms_filter_postprocess(stream->ms.decoder);
			ms_filter_destroy(stream->ms.decoder);
			stream->ms.decoder = dec;
156 157
			configure_decoder(stream, pt, stream->sample_rate, stream->nchannels);
			if (stream->write_resampler){
Simon Morlat's avatar
Simon Morlat committed
158
				audio_stream_configure_resampler(stream, stream->write_resampler,stream->ms.decoder,stream->soundwrite);
159
			}
160 161 162
			ms_filter_link(stream->ms.rtprecv, 0, stream->ms.decoder, 0);
			ms_filter_link(stream->ms.decoder, 0, nextFilter, 0);
			ms_filter_preprocess(stream->ms.decoder, stream->ms.sessions.ticker);
163
			stream->ms.current_pt=pt;
164 165
			return TRUE;
		} else {
166
			ms_error("No decoder found for %s", pt->mime_type);
167 168
		}
	} else {
169
		ms_warning("No payload type defined with number %i", payload);
170 171 172 173
	}
	return FALSE;
}

174
/*
175
 * note: since not all filters implement MS_FILTER_GET_SAMPLE_RATE and MS_FILTER_GET_NCHANNELS, the PayloadType passed here is used to guess this information.
176
 */
Simon Morlat's avatar
Simon Morlat committed
177
static void audio_stream_configure_resampler(AudioStream *st, MSFilter *resampler,MSFilter *from, MSFilter *to) {
178
	int from_rate=0, to_rate=0;
179
	int from_channels = 0, to_channels = 0;
180 181
	ms_filter_call_method(from,MS_FILTER_GET_SAMPLE_RATE,&from_rate);
	ms_filter_call_method(to,MS_FILTER_GET_SAMPLE_RATE,&to_rate);
182 183
	ms_filter_call_method(from, MS_FILTER_GET_NCHANNELS, &from_channels);
	ms_filter_call_method(to, MS_FILTER_GET_NCHANNELS, &to_channels);
184
	if (from_channels == 0) {
Simon Morlat's avatar
Simon Morlat committed
185
		from_channels = st->nchannels;
186 187 188
		ms_error("Filter %s does not implement the MS_FILTER_GET_NCHANNELS method", from->desc->name);
	}
	if (to_channels == 0) {
Simon Morlat's avatar
Simon Morlat committed
189
		to_channels = st->nchannels;
190 191
		ms_error("Filter %s does not implement the MS_FILTER_GET_NCHANNELS method", to->desc->name);
	}
192
	if (from_rate == 0){
193
		ms_error("Filter %s does not implement the MS_FILTER_GET_SAMPLE_RATE method", from->desc->name);
Simon Morlat's avatar
Simon Morlat committed
194
		from_rate = st->sample_rate;
195 196
	}
	if (to_rate == 0){
197
		ms_error("Filter %s does not implement the MS_FILTER_GET_SAMPLE_RATE method", to->desc->name);
Simon Morlat's avatar
Simon Morlat committed
198
		to_rate = st->sample_rate;
199
	}
200 201
	ms_filter_call_method(resampler,MS_FILTER_SET_SAMPLE_RATE,&from_rate);
	ms_filter_call_method(resampler,MS_FILTER_SET_OUTPUT_SAMPLE_RATE,&to_rate);
202 203
	ms_filter_call_method(resampler, MS_FILTER_SET_NCHANNELS, &from_channels);
	ms_filter_call_method(resampler, MS_FILTER_SET_OUTPUT_NCHANNELS, &to_channels);
Ronan's avatar
Ronan committed
204 205 206 207
	ms_message(
		"configuring %s:%p-->%s:%p from rate [%i] to rate [%i] and from channel [%i] to channel [%i]",
		from->desc->name, from, to->desc->name, to, from_rate, to_rate, from_channels, to_channels
	);
208 209
}

jehan's avatar
jehan committed
210
static void audio_stream_process_rtcp(MediaStream *media_stream, mblk_t *m){
211 212
}

213
void audio_stream_iterate(AudioStream *stream){
214
	media_stream_iterate(&stream->ms);
215 216 217
}

bool_t audio_stream_alive(AudioStream * stream, int timeout){
218
	return media_stream_alive((MediaStream*)stream,timeout);
aymeric's avatar
aymeric committed
219 220
}

jehan's avatar
jehan committed
221 222
/*invoked from FEC capable filters*/
static  mblk_t* audio_stream_payload_picker(MSRtpPayloadPickerContext* context,unsigned int sequence_number) {
223
	return rtp_session_pick_with_cseq(((AudioStream*)(context->filter_graph_manager))->ms.sessions.rtp_session, sequence_number);
Simon Morlat's avatar
Simon Morlat committed
224 225 226
}

static void stop_preload_graph(AudioStream *stream){
227
	ms_ticker_detach(stream->ms.sessions.ticker,stream->dummy);
228

Ghislain MARY's avatar
Ghislain MARY committed
229 230 231 232
	if (stream->ms.voidsink) {
		ms_filter_unlink(stream->dummy,0,stream->ms.voidsink,0);
		ms_filter_destroy(stream->ms.voidsink);
		stream->ms.voidsink=NULL;
233
	}else if (stream->soundwrite) {
234
		int muted = 0;
235
		ms_filter_unlink(stream->dummy,0,stream->soundwrite,0);
236
		ms_filter_call_method(stream->soundwrite, MS_AUDIO_PLAYBACK_MUTE, &muted);
237
	}
Simon Morlat's avatar
Simon Morlat committed
238 239 240 241 242
	ms_filter_destroy(stream->dummy);
	stream->dummy=NULL;
}

bool_t audio_stream_started(AudioStream *stream){
243
	return media_stream_started(&stream->ms);
Simon Morlat's avatar
Simon Morlat committed
244 245
}

246
/* This function is used either on IOS to workaround the long time to initialize the Audio Unit or for ICE candidates gathering. */
Simon Morlat's avatar
Simon Morlat committed
247
void audio_stream_prepare_sound(AudioStream *stream, MSSndCard *playcard, MSSndCard *captcard){
248
	audio_stream_unprepare_sound(stream);
249
	stream->dummy=ms_factory_create_filter(stream->ms.factory, MS_RTP_RECV_ID);
250
	rtp_session_set_payload_type(stream->ms.sessions.rtp_session,0);
251
	rtp_session_enable_rtcp(stream->ms.sessions.rtp_session, FALSE);
252
	ms_filter_call_method(stream->dummy,MS_RTP_RECV_SET_SESSION,stream->ms.sessions.rtp_session);
253

Simon Morlat's avatar
Simon Morlat committed
254
	if (captcard && playcard){
255
#if TARGET_OS_IPHONE
256
		int muted = 1;
Simon Morlat's avatar
Simon Morlat committed
257 258 259
		stream->soundread=ms_snd_card_create_reader(captcard);
		stream->soundwrite=ms_snd_card_create_writer(playcard);
		ms_filter_link(stream->dummy,0,stream->soundwrite,0);
260
		ms_filter_call_method(stream->soundwrite, MS_AUDIO_PLAYBACK_MUTE, &muted);
261
#else
262
		stream->ms.voidsink=ms_factory_create_filter(stream->ms.factory,  MS_VOID_SINK_ID);
Ghislain MARY's avatar
Ghislain MARY committed
263
		ms_filter_link(stream->dummy,0,stream->ms.voidsink,0);
Simon Morlat's avatar
Simon Morlat committed
264
#endif
265
	} else {
266
		stream->ms.voidsink=ms_factory_create_filter(stream->ms.factory,  MS_VOID_SINK_ID);
Ghislain MARY's avatar
Ghislain MARY committed
267
		ms_filter_link(stream->dummy,0,stream->ms.voidsink,0);
268

269
	}
270 271 272
	if (stream->ms.sessions.ticker == NULL) media_stream_start_ticker(&stream->ms);
	ms_ticker_attach(stream->ms.sessions.ticker,stream->dummy);
	stream->ms.state=MSStreamPreparing;
Simon Morlat's avatar
Simon Morlat committed
273 274
}

Simon Morlat's avatar
Simon Morlat committed
275
static void _audio_stream_unprepare_sound(AudioStream *stream, bool_t keep_sound_resources){
276
	if (stream->ms.state==MSStreamPreparing){
Simon Morlat's avatar
Simon Morlat committed
277
		stop_preload_graph(stream);
278
#if TARGET_OS_IPHONE
Simon Morlat's avatar
Simon Morlat committed
279 280 281 282 283 284
		if (!keep_sound_resources){
			if (stream->soundread) ms_filter_destroy(stream->soundread);
			stream->soundread=NULL;
			if (stream->soundwrite) ms_filter_destroy(stream->soundwrite);
			stream->soundwrite=NULL;
		}
Simon Morlat's avatar
Simon Morlat committed
285
#endif
286
	}
287
	stream->ms.state=MSStreamInitialized;
Simon Morlat's avatar
Simon Morlat committed
288 289
}

Simon Morlat's avatar
Simon Morlat committed
290 291 292 293
void audio_stream_unprepare_sound(AudioStream *stream){
	_audio_stream_unprepare_sound(stream,FALSE);
}

294 295 296 297 298
static void player_callback(void *ud, MSFilter *f, unsigned int id, void *arg){
	AudioStream *stream=(AudioStream *)ud;
	int sr=0;
	int channels=0;
	switch(id){
299
		case MS_FILTER_OUTPUT_FMT_CHANGED:
300 301 302 303 304 305 306 307 308 309 310 311 312 313
			ms_filter_call_method(f,MS_FILTER_GET_SAMPLE_RATE,&sr);
			ms_filter_call_method(f,MS_FILTER_GET_NCHANNELS,&channels);
			if (f==stream->local_player){
				ms_filter_call_method(stream->local_player_resampler,MS_FILTER_SET_SAMPLE_RATE,&sr);
				ms_filter_call_method(stream->local_player_resampler,MS_FILTER_SET_NCHANNELS,&channels);
			}
		break;
		default:
		break;
	}
}

static void setup_local_player(AudioStream *stream, int samplerate, int channels){
	MSConnectionHelper cnx;
Simon Morlat's avatar
Simon Morlat committed
314
	int master=0;
315

Sandrine Avakian's avatar
Sandrine Avakian committed
316

317 318
	stream->local_player=ms_factory_create_filter(stream->ms.factory, MS_FILE_PLAYER_ID);
	stream->local_player_resampler=ms_factory_create_filter(stream->ms.factory, MS_RESAMPLE_ID);
319

320 321
	ms_connection_helper_start(&cnx);
	ms_connection_helper_link(&cnx,stream->local_player,-1,0);
322 323 324
	if (stream->local_player_resampler){
		ms_connection_helper_link(&cnx,stream->local_player_resampler,0,0);
	}
325
	ms_connection_helper_link(&cnx,stream->local_mixer,1,-1);
326

327 328 329 330
	if (stream->local_player_resampler){
		ms_filter_call_method(stream->local_player_resampler,MS_FILTER_SET_OUTPUT_SAMPLE_RATE,&samplerate);
		ms_filter_call_method(stream->local_player_resampler,MS_FILTER_SET_OUTPUT_NCHANNELS,&channels);
	}
331 332
	ms_filter_call_method(stream->local_mixer,MS_FILTER_SET_SAMPLE_RATE,&samplerate);
	ms_filter_call_method(stream->local_mixer,MS_FILTER_SET_NCHANNELS,&channels);
Simon Morlat's avatar
Simon Morlat committed
333
	ms_filter_call_method(stream->local_mixer,MS_AUDIO_MIXER_SET_MASTER_CHANNEL,&master);
334 335 336
	ms_filter_add_notify_callback(stream->local_player,player_callback,stream,TRUE);
}

337
static OrtpRtcpXrPlcStatus audio_stream_get_rtcp_xr_plc_status(void *userdata) {
338 339 340
	AudioStream *stream = (AudioStream *)userdata;
	if ((stream->features & AUDIO_STREAM_FEATURE_PLC) != 0) {
		int decoder_have_plc = 0;
341
		if (stream->ms.decoder && ms_filter_has_method(stream->ms.decoder, MS_AUDIO_DECODER_HAVE_PLC)) {
342 343 344 345 346 347 348 349 350 351 352
			ms_filter_call_method(stream->ms.decoder, MS_AUDIO_DECODER_HAVE_PLC, &decoder_have_plc);
		}
		if (decoder_have_plc == 0) {
			return OrtpRtcpXrSilencePlc;
		} else {
			return OrtpRtcpXrEnhancedPlc;
		}
	}
	return OrtpRtcpXrNoPlc;
}

353
static int audio_stream_get_rtcp_xr_signal_level(void *userdata) {
354 355
	AudioStream *stream = (AudioStream *)userdata;
	if ((stream->features & AUDIO_STREAM_FEATURE_VOL_RCV) != 0) {
356 357 358
		float volume = 0.f;
		if (stream->volrecv)
			ms_filter_call_method(stream->volrecv, MS_VOLUME_GET_MAX, &volume);
359
		return (int)volume;
360 361 362 363
	}
	return ORTP_RTCP_XR_UNAVAILABLE_PARAMETER;
}

364
static int audio_stream_get_rtcp_xr_noise_level(void *userdata) {
365 366
	AudioStream *stream = (AudioStream *)userdata;
	if ((stream->features & AUDIO_STREAM_FEATURE_VOL_RCV) != 0) {
367 368 369
		float volume = 0.f;
		if (stream->volrecv)
			ms_filter_call_method(stream->volrecv, MS_VOLUME_GET_MIN, &volume);
370
		return (int)volume;
371 372 373 374
	}
	return ORTP_RTCP_XR_UNAVAILABLE_PARAMETER;
}

375
static float audio_stream_get_rtcp_xr_average_quality_rating(void *userdata) {
376 377 378 379
	AudioStream *stream = (AudioStream *)userdata;
	return audio_stream_get_average_quality_rating(stream);
}

380
static float audio_stream_get_rtcp_xr_average_lq_quality_rating(void *userdata) {
381 382 383 384
	AudioStream *stream = (AudioStream *)userdata;
	return audio_stream_get_average_lq_quality_rating(stream);
}

385
static bool_t ci_ends_with(const char *filename, const char*suffix){
386 387
	size_t filename_len=strlen(filename);
	size_t suffix_len=strlen(suffix);
388 389 390 391
	if (filename_len<suffix_len) return FALSE;
	return strcasecmp(filename+filename_len-suffix_len,suffix)==0;
}

392
MSFilter *_ms_create_av_player(const char *filename, MSFactory* factory){
393
	if (ci_ends_with(filename,".mkv"))
394
		return ms_factory_create_filter(factory, MS_MKV_PLAYER_ID);
395
	else if (ci_ends_with(filename,".wav"))
396
		return ms_factory_create_filter(factory, MS_FILE_PLAYER_ID);
397 398
	else
		ms_error("Cannot open %s, unsupported file extension", filename);
399 400 401 402 403 404 405
	return NULL;
}

static void unplumb_av_player(AudioStream *stream){
	struct _AVPlayer *player=&stream->av_player;
	MSConnectionHelper ch;
	bool_t reattach=stream->ms.state==MSStreamStarted;
406

407
	if (!player->plumbed) return;
408

François Grisez's avatar
François Grisez committed
409 410
	/*detach the outbound graph before modifying the graph*/
	ms_ticker_detach(stream->ms.sessions.ticker,stream->soundread);
411 412 413 414 415 416 417
	if (player->videopin!=-1){
		ms_connection_helper_start(&ch);
		ms_connection_helper_unlink(&ch,player->player,-1,player->videopin);
		ms_connection_helper_unlink(&ch,player->video_output,0,0);
	}
	ms_connection_helper_start(&ch);
	ms_connection_helper_unlink(&ch,player->player,-1,player->audiopin);
Simon Morlat's avatar
Simon Morlat committed
418 419
	if (player->decoder)
		ms_connection_helper_unlink(&ch,player->decoder,0,0);
420 421 422 423
	ms_connection_helper_unlink(&ch,player->resampler,0,0);
	ms_connection_helper_unlink(&ch,stream->outbound_mixer,1,-1);
	/*and attach back*/
	if (reattach) ms_ticker_attach(stream->ms.sessions.ticker,stream->soundread);
424
	player->plumbed = FALSE;
425 426 427 428
}

static void close_av_player(AudioStream *stream){
	struct _AVPlayer *player=&stream->av_player;
429

430 431
	if (player->player){
		MSPlayerState st=MSPlayerClosed;
Ghislain MARY's avatar
Ghislain MARY committed
432
		unplumb_av_player(stream);
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
		if (ms_filter_call_method(player->player,MS_PLAYER_GET_STATE,&st)==0){
			if (st!=MSPlayerClosed)
				ms_filter_call_method_noarg(player->player,MS_PLAYER_CLOSE);
		}
		ms_filter_destroy(player->player);
		player->player=NULL;
	}
	if (player->resampler){
		ms_filter_destroy(player->resampler);
		player->resampler=NULL;
	}
	if (player->decoder){
		ms_filter_destroy(player->decoder);
		player->decoder=NULL;
	}
}

static void configure_av_player(AudioStream *stream, const MSFmtDescriptor *audiofmt, const MSFmtDescriptor *videofmt){
	struct _AVPlayer *player=&stream->av_player;
	int stream_rate=0;
	int stream_channels=0;
454

455
	ms_message("AudioStream [%p] Configure av_player, audiofmt=%s videofmt=%s",stream,ms_fmt_descriptor_to_string(audiofmt),ms_fmt_descriptor_to_string(videofmt));
456

457 458 459 460 461 462 463 464
	if (audiofmt){
		if (player->decoder){
			if (audiofmt->nchannels>0){
				ms_filter_call_method(player->decoder,MS_FILTER_SET_NCHANNELS,(void*)&audiofmt->nchannels);
			}
			if (audiofmt->rate>0){
				ms_filter_call_method(player->decoder,MS_FILTER_SET_SAMPLE_RATE,(void*)&audiofmt->rate);
			}
Simon Morlat's avatar
Simon Morlat committed
465
		}
466 467
		ms_filter_call_method(player->resampler,MS_FILTER_SET_NCHANNELS,(void*)&audiofmt->nchannels);
		ms_filter_call_method(player->resampler,MS_FILTER_SET_SAMPLE_RATE,(void*)&audiofmt->rate);
468
	}
469

470 471 472 473 474 475 476 477
	ms_filter_call_method(stream->outbound_mixer,MS_FILTER_GET_SAMPLE_RATE,&stream_rate);
	ms_filter_call_method(stream->outbound_mixer,MS_FILTER_GET_NCHANNELS,&stream_channels);
	ms_filter_call_method(player->resampler,MS_FILTER_SET_OUTPUT_NCHANNELS,&stream_channels);
	ms_filter_call_method(player->resampler,MS_FILTER_SET_OUTPUT_SAMPLE_RATE,&stream_rate);
	if (videofmt){
		MSPinFormat pf;
		pf.pin=0;
		pf.fmt=videofmt;
478
		ms_filter_call_method(player->video_output,MS_FILTER_SET_INPUT_FMT,&pf);
479 480 481 482 483 484 485
	}
}

static void plumb_av_player(AudioStream *stream){
	struct _AVPlayer *player=&stream->av_player;
	MSConnectionHelper ch;
	bool_t reattach=stream->ms.state==MSStreamStarted;
486

487 488 489 490 491 492 493
	if (player->videopin!=-1){
		ms_connection_helper_start(&ch);
		ms_connection_helper_link(&ch,player->player,-1,player->videopin);
		ms_connection_helper_link(&ch,player->video_output,0,0);
	}
	ms_connection_helper_start(&ch);
	ms_connection_helper_link(&ch,player->player,-1,player->audiopin);
Simon Morlat's avatar
Simon Morlat committed
494 495
	if (player->decoder)
		ms_connection_helper_link(&ch,player->decoder,0,0);
496 497 498 499 500 501 502 503 504 505 506 507 508 509
	ms_connection_helper_link(&ch,player->resampler,0,0);
	/*detach the outbound graph before attaching to the outbound mixer*/
	if (reattach) ms_ticker_detach(stream->ms.sessions.ticker,stream->soundread);
	ms_connection_helper_link(&ch,stream->outbound_mixer,1,-1);
	/*and attach back*/
	if (reattach) ms_ticker_attach(stream->ms.sessions.ticker,stream->soundread);
	player->plumbed=TRUE;
}

static int open_av_player(AudioStream *stream, const char *filename){
	struct _AVPlayer *player=&stream->av_player;
	MSPinFormat fmt1={0},fmt2={0};
	MSPinFormat *audiofmt=NULL;
	MSPinFormat *videofmt=NULL;
510

511
	if (player->player) close_av_player(stream);
512
	//player->player=_ms_create_av_player(filename);
513
	player->player=_ms_create_av_player(filename, stream->ms.factory);
514 515 516 517 518 519 520 521 522 523 524 525
	if (player->player==NULL){
		ms_warning("AudioStream[%p]: no way to open [%s].",stream,filename);
		return -1;
	}
	if (ms_filter_call_method(player->player,MS_PLAYER_OPEN,(void*)filename)==-1){
		close_av_player(stream);
		return -1;
	}
	fmt1.pin=0;
	ms_filter_call_method(player->player,MS_FILTER_GET_OUTPUT_FMT,&fmt1);
	fmt2.pin=1;
	ms_filter_call_method(player->player,MS_FILTER_GET_OUTPUT_FMT,&fmt2);
526
	if (fmt1.fmt==NULL && fmt2.fmt==NULL){
527 528 529 530 531
		/*assume PCM*/
		int sr=8000;
		int channels=1;
		ms_filter_call_method(player->player,MS_FILTER_GET_SAMPLE_RATE,&sr);
		ms_filter_call_method(player->player,MS_FILTER_GET_NCHANNELS,&channels);
532
		fmt1.fmt=ms_factory_get_audio_format(stream->ms.factory, "pcm", sr, channels, NULL);
533 534
		audiofmt=&fmt1;
	}else{
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
		if (fmt1.fmt) {
			if (fmt1.fmt->type==MSAudio){
				audiofmt=&fmt1;
				player->audiopin=0;
			}else{
				videofmt=&fmt1;
				player->videopin=0;
			}
		}
		if (fmt2.fmt){
			if (fmt2.fmt->type == MSAudio){
				audiofmt=&fmt2;
				player->audiopin=1;
			}else{
				videofmt=&fmt2;
				player->videopin=1;
			}
552

553
		}
Simon Morlat's avatar
Simon Morlat committed
554
	}
555
	if (audiofmt && audiofmt->fmt && strcasecmp(audiofmt->fmt->encoding,"pcm")!=0){
556 557
		player->decoder=ms_factory_create_decoder(stream->ms.factory, audiofmt->fmt->encoding);

558 559 560 561 562 563
		if (player->decoder==NULL){
			ms_warning("AudioStream[%p]: no way to decode [%s]",stream,filename);
			close_av_player(stream);
			return -1;
		}
	}
564 565 566
	player->resampler=ms_factory_create_filter(stream->ms.factory, MS_RESAMPLE_ID);
	if (videofmt && videofmt->fmt) player->video_output=ms_factory_create_filter(stream->videostream->ms.factory,MS_ITC_SINK_ID);

567
	else player->videopin=-1;
568
	configure_av_player(stream,audiofmt ? audiofmt->fmt : NULL ,videofmt ? videofmt->fmt : NULL);
569
	if (stream->videostream) video_stream_open_player(stream->videostream,player->video_output);
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
	plumb_av_player(stream);
	return 0;
}

MSFilter * audio_stream_open_remote_play(AudioStream *stream, const char *filename){
	if (stream->ms.state!=MSStreamStarted){
		ms_warning("AudioStream[%p]: audio_stream_play_to_remote() works only when the stream is started.",stream);
		return NULL;
	}
	if (stream->outbound_mixer==NULL){
		ms_warning("AudioStream[%p]: audio_stream_play_to_remote() works only when the stream has AUDIO_STREAM_FEATURE_REMOTE_PLAYING capability.",stream);
		return NULL;
	}
	if (open_av_player(stream,filename)==-1){
		return NULL;
	}
	return stream->av_player.player;
}

589 590 591 592 593 594 595 596 597
void audio_stream_close_remote_play(AudioStream *stream){
	MSPlayerState state;
	if (stream->av_player.player){
		ms_filter_call_method(stream->av_player.player,MS_PLAYER_GET_STATE,&state);
		if (state!=MSPlayerClosed)
			ms_filter_call_method_noarg(stream->av_player.player,MS_PLAYER_CLOSE);
	}
	if (stream->videostream) video_stream_close_player(stream->videostream);
}
598

Simon Morlat's avatar
Simon Morlat committed
599
static void video_input_updated(void *stream, MSFilter *f, unsigned int event_id, void *arg){
600
	if (event_id==MS_FILTER_OUTPUT_FMT_CHANGED){
Simon Morlat's avatar
Simon Morlat committed
601 602 603 604 605
		ms_message("Video ITC source updated.");
		configure_av_recorder((AudioStream*)stream);
	}
}

606
static void av_recorder_handle_event(void *userdata, MSFilter *recorder, unsigned int event, void *event_arg){
Simon Morlat's avatar
Simon Morlat committed
607
#ifdef VIDEO_ENABLED
608 609 610 611
	AudioStream *audiostream = (AudioStream *)userdata;
	if (audiostream->videostream != NULL) {
		video_recorder_handle_event(audiostream->videostream, recorder, event, event_arg);
	}
Simon Morlat's avatar
Simon Morlat committed
612
#endif
613 614
}

Simon Morlat's avatar
Simon Morlat committed
615
static void setup_av_recorder(AudioStream *stream, int sample_rate, int nchannels){
Sandrine Avakian's avatar
Sandrine Avakian committed
616

617
	stream->av_recorder.recorder=ms_factory_create_filter(stream->ms.factory, MS_MKV_RECORDER_ID);
Simon Morlat's avatar
Simon Morlat committed
618
	if (stream->av_recorder.recorder){
Simon Morlat's avatar
Simon Morlat committed
619
		MSPinFormat pinfmt={0};
620 621 622
		stream->av_recorder.video_input=ms_factory_create_filter(stream->ms.factory, MS_ITC_SOURCE_ID);
		stream->av_recorder.resampler=ms_factory_create_filter(stream->ms.factory,MS_RESAMPLE_ID);
		stream->av_recorder.encoder=ms_factory_create_filter(stream->ms.factory,MS_OPUS_ENC_ID);
623

Simon Morlat's avatar
Simon Morlat committed
624 625 626
		if (stream->av_recorder.encoder==NULL){
			int g711_rate=8000;
			int g711_nchannels=1;
627
			stream->av_recorder.encoder=ms_factory_create_filter(stream->ms.factory, MS_ULAW_ENC_ID);
Simon Morlat's avatar
Simon Morlat committed
628 629 630 631
			ms_filter_call_method(stream->av_recorder.resampler,MS_FILTER_SET_SAMPLE_RATE,&sample_rate);
			ms_filter_call_method(stream->av_recorder.resampler,MS_FILTER_SET_OUTPUT_SAMPLE_RATE,&g711_rate);
			ms_filter_call_method(stream->av_recorder.resampler,MS_FILTER_SET_NCHANNELS,&nchannels);
			ms_filter_call_method(stream->av_recorder.resampler,MS_FILTER_SET_OUTPUT_NCHANNELS,&g711_nchannels);
632
			pinfmt.fmt=ms_factory_get_audio_format(stream->ms.factory, "pcmu",g711_rate,g711_nchannels,NULL);
633

Simon Morlat's avatar
Simon Morlat committed
634 635 636 637 638 639 640 641 642
		}else{
			int got_sr=0;
			ms_filter_call_method(stream->av_recorder.encoder,MS_FILTER_SET_SAMPLE_RATE,&sample_rate);
			ms_filter_call_method(stream->av_recorder.encoder,MS_FILTER_GET_SAMPLE_RATE,&got_sr);
			ms_filter_call_method(stream->av_recorder.encoder,MS_FILTER_SET_NCHANNELS,&nchannels);
			ms_filter_call_method(stream->av_recorder.resampler,MS_FILTER_SET_SAMPLE_RATE,&sample_rate);
			ms_filter_call_method(stream->av_recorder.resampler,MS_FILTER_SET_OUTPUT_SAMPLE_RATE,&got_sr);
			ms_filter_call_method(stream->av_recorder.resampler,MS_FILTER_SET_NCHANNELS,&nchannels);
			ms_filter_call_method(stream->av_recorder.resampler,MS_FILTER_SET_OUTPUT_NCHANNELS,&nchannels);
643
			pinfmt.fmt=ms_factory_get_audio_format(stream->ms.factory,"opus",48000,nchannels,NULL);
Simon Morlat's avatar
Simon Morlat committed
644
		}
Simon Morlat's avatar
Simon Morlat committed
645 646 647
		pinfmt.pin=1;
		ms_message("Configuring av recorder with audio format %s",ms_fmt_descriptor_to_string(pinfmt.fmt));
		ms_filter_call_method(stream->av_recorder.recorder,MS_FILTER_SET_INPUT_FMT,&pinfmt);
Simon Morlat's avatar
Simon Morlat committed
648
		ms_filter_add_notify_callback(stream->av_recorder.video_input,video_input_updated,stream,TRUE);
649
		ms_filter_add_notify_callback(stream->av_recorder.recorder, av_recorder_handle_event, stream, TRUE);
Simon Morlat's avatar
Simon Morlat committed
650 651 652 653 654 655 656 657 658
	}
}

static void plumb_av_recorder(AudioStream *stream){
	MSConnectionHelper ch;
	ms_connection_helper_start(&ch);
	ms_connection_helper_link(&ch, stream->recorder_mixer,-1, 1);
	ms_connection_helper_link(&ch, stream->av_recorder.resampler,0,0);
	ms_connection_helper_link(&ch, stream->av_recorder.encoder,0,0);
Simon Morlat's avatar
Simon Morlat committed
659
	ms_connection_helper_link(&ch, stream->av_recorder.recorder,1,-1);
660

Simon Morlat's avatar
Simon Morlat committed
661
	ms_filter_link(stream->av_recorder.video_input,0,stream->av_recorder.recorder,0);
Simon Morlat's avatar
Simon Morlat committed
662 663 664 665
}

static void unplumb_av_recorder(AudioStream *stream){
	MSConnectionHelper ch;
666
	MSRecorderState rstate;
Simon Morlat's avatar
Simon Morlat committed
667 668 669 670
	ms_connection_helper_start(&ch);
	ms_connection_helper_unlink(&ch, stream->recorder_mixer,-1, 1);
	ms_connection_helper_unlink(&ch, stream->av_recorder.resampler,0,0);
	ms_connection_helper_unlink(&ch, stream->av_recorder.encoder,0,0);
Simon Morlat's avatar
Simon Morlat committed
671
	ms_connection_helper_unlink(&ch, stream->av_recorder.recorder,1,-1);
672

Simon Morlat's avatar
Simon Morlat committed
673
	ms_filter_unlink(stream->av_recorder.video_input,0,stream->av_recorder.recorder,0);
674

675 676 677 678 679
	if (ms_filter_call_method(stream->av_recorder.recorder,MS_RECORDER_GET_STATE,&rstate)==0){
		if (rstate!=MSRecorderClosed){
			ms_filter_call_method_noarg(stream->av_recorder.recorder, MS_RECORDER_CLOSE);
		}
	}
Simon Morlat's avatar
Simon Morlat committed
680 681 682 683 684
}

static void setup_recorder(AudioStream *stream, int sample_rate, int nchannels){
	int val=0;
	int pin=1;
685
	MSAudioMixerCtl mctl={0};
686

687 688 689 690
	stream->recorder=ms_factory_create_filter(stream->ms.factory, MS_FILE_REC_ID);
	stream->recorder_mixer=ms_factory_create_filter(stream->ms.factory, MS_AUDIO_MIXER_ID);
	stream->recv_tee=ms_factory_create_filter(stream->ms.factory, MS_TEE_ID);

Simon Morlat's avatar
Simon Morlat committed
691 692 693 694
	ms_filter_call_method(stream->recorder_mixer,MS_AUDIO_MIXER_ENABLE_CONFERENCE_MODE,&val);
	ms_filter_call_method(stream->recorder_mixer,MS_FILTER_SET_SAMPLE_RATE,&sample_rate);
	ms_filter_call_method(stream->recorder_mixer,MS_FILTER_SET_NCHANNELS,&nchannels);
	ms_filter_call_method(stream->recv_tee,MS_TEE_MUTE,&pin);
695 696 697
	mctl.pin=pin;
	mctl.param.enabled=FALSE;
	ms_filter_call_method(stream->outbound_mixer,MS_AUDIO_MIXER_ENABLE_OUTPUT,&mctl);
Simon Morlat's avatar
Simon Morlat committed
698 699
	ms_filter_call_method(stream->recorder,MS_FILTER_SET_SAMPLE_RATE,&sample_rate);
	ms_filter_call_method(stream->recorder,MS_FILTER_SET_NCHANNELS,&nchannels);
700

Simon Morlat's avatar
Simon Morlat committed
701 702 703
	setup_av_recorder(stream,sample_rate,nchannels);
}

704
static void on_silence_detected(void *data, MSFilter *f, unsigned int event_id, void *event_arg){
705 706 707 708
	AudioStream *as=(AudioStream*)data;
	if (as->ms.rtpsend){
		switch(event_id){
			case MS_VAD_DTX_NO_VOICE:
Simon Morlat's avatar
Simon Morlat committed
709
				/*ms_message("on_silence_detected(): CN packet to be sent !");*/
710 711 712 713
				ms_filter_call_method(as->ms.rtpsend, MS_RTP_SEND_SEND_GENERIC_CN, event_arg);
				ms_filter_call_method(as->ms.rtpsend, MS_RTP_SEND_MUTE, event_arg);
			break;
			case MS_VAD_DTX_VOICE:
Simon Morlat's avatar
Simon Morlat committed
714
				/*ms_message("on_silence_detected(): resuming audio");*/
715 716 717 718 719 720 721 722 723 724 725 726
				ms_filter_call_method(as->ms.rtpsend, MS_RTP_SEND_UNMUTE, event_arg);
			break;
		}
	}
}

static void on_cn_received(void *data, MSFilter *f, unsigned int event_id, void *event_arg){
	AudioStream *as=(AudioStream*)data;
	if (as->plc){
		ms_message("CN packet received, given to MSGenericPlc filter.");
		ms_filter_call_method(as->plc, MS_GENERIC_PLC_SET_CN, event_arg);
	}
727 728 729 730 731
}

static void setup_generic_confort_noise(AudioStream *stream){
	RtpProfile *prof=rtp_session_get_profile(stream->ms.sessions.rtp_session);
	PayloadType *pt=rtp_profile_get_payload(prof, rtp_session_get_send_payload_type(stream->ms.sessions.rtp_session));
732
	int cn = rtp_profile_get_payload_number_from_mime_and_flag(prof, "CN", PAYLOAD_TYPE_FLAG_CAN_SEND);
733

734
	if (cn >= 0 && pt && pt->channels==1){
735 736 737 738
		int samplerate = pt->clock_rate;
		ms_filter_call_method(stream->ms.decoder, MS_FILTER_GET_SAMPLE_RATE, &samplerate);
		if (samplerate == 8000){
			/* RFC3389 CN can be used only for 8khz codecs*/
739
			stream->vaddtx=ms_factory_create_filter(stream->ms.factory, MS_VAD_DTX_ID);
740 741 742 743 744 745
			if (stream->vaddtx) {
				ms_filter_add_notify_callback(stream->vaddtx, on_silence_detected, stream, TRUE);
				ms_filter_add_notify_callback(stream->ms.rtprecv, on_cn_received, stream, TRUE);
			} else {
				ms_warning("Cannot instantiate vaddtx filter!");
			}
746

747
		}
748 749 750
	}
}

751 752 753 754 755 756 757 758 759 760 761 762 763
static void configure_decoder(AudioStream *stream, PayloadType *pt, int sample_rate, int nchannels){
	ms_filter_call_method(stream->ms.decoder,MS_FILTER_SET_SAMPLE_RATE,&sample_rate);
	ms_filter_call_method(stream->ms.decoder,MS_FILTER_SET_NCHANNELS,&nchannels);
	if (pt->recv_fmtp!=NULL) ms_filter_call_method(stream->ms.decoder,MS_FILTER_ADD_FMTP,(void*)pt->recv_fmtp);
	if (ms_filter_has_method(stream->ms.decoder, MS_AUDIO_DECODER_SET_RTP_PAYLOAD_PICKER) || ms_filter_has_method(stream->ms.decoder, MS_FILTER_SET_RTP_PAYLOAD_PICKER)) {
		MSRtpPayloadPickerContext picker_context;
		ms_message("Decoder has FEC capabilities");
		picker_context.filter_graph_manager=stream;
		picker_context.picker=&audio_stream_payload_picker;
		ms_filter_call_method(stream->ms.decoder,MS_AUDIO_DECODER_SET_RTP_PAYLOAD_PICKER, &picker_context);
	}
}

764 765
static int get_usable_telephone_event(RtpProfile *profile, int clock_rate){
	int i;
766
	int fallback_pt=-1;
767 768
	for(i=0; i<128; ++i){
		PayloadType *pt = profile->payload[i];
769 770 771 772 773
		if (pt && strcasecmp(pt->mime_type, "telephone-event")==0 && (pt->flags & PAYLOAD_TYPE_FLAG_CAN_SEND)){
			if (pt->clock_rate == clock_rate)
				return i;
			if (pt->clock_rate == 8000)
				fallback_pt = i;
774 775
		}
	}
776 777 778 779 780 781
	/*
	 * the fallback payload type is used if the remote equipment doesn't conform to RFC4733 2.1,
	 * that requires to use a clock rate which is the same as the audio codec.
	 */
	if (fallback_pt !=-1) ms_warning("The remote equipment doesn't conform to RFC4733 2.1 - it wants to use telephone-event/8000 despite the clock rate of the audio codec is %i", clock_rate);
	return fallback_pt;
782 783
}

784
int audio_stream_start_from_io(AudioStream *stream, RtpProfile *profile, const char *rem_rtp_ip, int rem_rtp_port,
785
	const char *rem_rtcp_ip, int rem_rtcp_port, int payload, const MSMediaStreamIO *io) {
786
	RtpSession *rtps=stream->ms.sessions.rtp_session;
787 788
	PayloadType *pt;
	int tmp, tev_pt;
789
	MSConnectionHelper h;
790
	int sample_rate;
791
	int nchannels;
792
	int err1,err2;
793
	bool_t has_builtin_ec=FALSE;
794
	bool_t resampler_missing = FALSE;
Sylvain Berfini's avatar
Sylvain Berfini committed
795
	bool_t skip_encoder_and_decoder = FALSE;
796
	bool_t do_ts_adjustments = TRUE;
797

798
	if (!ms_media_stream_io_is_consistent(io)) return -1;
aymeric's avatar
aymeric committed
799 800

	rtp_session_set_profile(rtps,profile);
801
	if (rem_rtp_port>0) rtp_session_set_remote_addr_full(rtps,rem_rtp_ip,rem_rtp_port,rem_rtcp_ip,rem_rtcp_port);
802 803 804 805
	if (rem_rtcp_port > 0) {
		rtp_session_enable_rtcp(rtps, TRUE);
	} else {
		rtp_session_enable_rtcp(rtps, FALSE);
Simon Morlat's avatar
Simon Morlat committed
806
	}
aymeric's avatar
aymeric committed
807
	rtp_session_set_payload_type(rtps,payload);
Jehan Monnier's avatar
Jehan Monnier committed
808

809
	ms_filter_call_method(stream->ms.rtpsend,MS_RTP_SEND_SET_SESSION,rtps);
810
	stream->ms.rtprecv=ms_factory_create_filter(stream->ms.factory,MS_RTP_RECV_ID);
Ghislain MARY's avatar
Ghislain MARY committed
811
	ms_filter_call_method(stream->ms.rtprecv,MS_RTP_RECV_SET_SESSION,rtps);
812
	stream->ms.sessions.rtp_session=rtps;
Jehan Monnier's avatar
Jehan Monnier committed
813

814
	if((stream->features & AUDIO_STREAM_FEATURE_DTMF_ECHO) != 0)
815
		stream->dtmfgen=ms_factory_create_filter(stream->ms.factory, MS_DTMF_GEN_ID);
Yann Diorcet's avatar
Yann Diorcet committed
816 817
	else
		stream->dtmfgen=NULL;
818 819 820 821 822 823 824

	/* FIXME: Temporary workaround for -Wcast-function-type. */
	#if __GNUC__ >= 8
		_Pragma("GCC diagnostic push")
		_Pragma("GCC diagnostic ignored \"-Wcast-function-type\"")
	#endif // if __GNUC__ >= 8

Ghislain MARY's avatar
Ghislain MARY committed
825
	rtp_session_signal_connect(rtps,"telephone-event",(RtpCallback)on_dtmf_received,stream);
826
	rtp_session_signal_connect(rtps,"payload_type_changed",(RtpCallback)audio_stream_payload_type_changed,stream);