diff --git a/coreapi/ec-calibrator.c b/coreapi/ec-calibrator.c index d052f838c3e192d644386140257fa02c942bf3a1..7c8d309152fb3b4d5d1f2a9f6106d15d1e24e57c 100644 --- a/coreapi/ec-calibrator.c +++ b/coreapi/ec-calibrator.c @@ -109,6 +109,9 @@ static void ecc_deinit_filters(EcCalibrator *ecc){ ms_filter_destroy(ecc->sndwrite); ms_ticker_destroy(ecc->ticker); + + if (ecc->capt_card) ms_snd_card_unref(ecc->capt_card); + if (ecc->play_card) ms_snd_card_unref(ecc->play_card); } static void on_tone_sent(void *data, MSFilter *f, unsigned int event_id, void *arg){ @@ -273,10 +276,7 @@ static void ecc_play_tones(EcCalibrator *ecc){ static void * ecc_thread(void *p){ EcCalibrator *ecc=(EcCalibrator*)p; - - ecc_init_filters(ecc); ecc_play_tones(ecc); - ecc_deinit_filters(ecc); ms_thread_exit(NULL); return NULL; } @@ -290,14 +290,15 @@ EcCalibrator * ec_calibrator_new(MSFactory *factory, MSSndCard *play_card, MSSnd ecc->cb_data=cb_data; ecc->audio_init_cb=audio_init_cb; ecc->audio_uninit_cb=audio_uninit_cb; - ecc->capt_card=capt_card; - ecc->play_card=play_card; + ecc->capt_card = ms_snd_card_ref(capt_card); + ecc->play_card = ms_snd_card_ref(play_card); ecc->factory=factory; return ecc; } void ec_calibrator_start(EcCalibrator *ecc){ - ms_thread_create(&ecc->thread,NULL,ecc_thread,ecc); + ecc_init_filters(ecc); + ms_thread_create(&ecc->thread, NULL, ecc_thread, ecc); } LinphoneEcCalibratorStatus ec_calibrator_get_status(EcCalibrator *ecc){ @@ -306,6 +307,7 @@ LinphoneEcCalibratorStatus ec_calibrator_get_status(EcCalibrator *ecc){ void ec_calibrator_destroy(EcCalibrator *ecc){ if (ecc->thread != 0) ms_thread_join(ecc->thread,NULL); + ecc_deinit_filters(ecc); ms_free(ecc); } diff --git a/coreapi/echo-tester.c b/coreapi/echo-tester.c index b28dd5cb8f34384fe5ac0fb2caf6fa174016e46b..c244e757cba2baf6891e9314b41b8c22c292d6c3 100644 --- a/coreapi/echo-tester.c +++ b/coreapi/echo-tester.c @@ -27,8 +27,8 @@ EchoTester* ec_tester_new(MSFactory *factory, MSSndCard *capture_card, MSSndCard *playback_card, unsigned int rate) { EchoTester *ect = ms_new0(EchoTester,1); ect->factory = factory; - ect->capture_card = capture_card; - ect->playback_card = playback_card; + ect->capture_card = ms_snd_card_ref(capture_card); + ect->playback_card = ms_snd_card_ref(playback_card); ect->rate = rate; return ect; @@ -76,6 +76,8 @@ static void ect_uninit_filters(EchoTester *ect) { } void ec_tester_destroy(EchoTester *ect) { + if (ect->capture_card) ms_snd_card_unref(ect->capture_card); + if (ect->playback_card) ms_snd_card_unref(ect->playback_card); ms_free(ect); } diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index f14d345fef14929839a0b95cea24aabd9aaa9e91..bfaa68651cc6041a37ee7504705c93e3f044588d 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -532,6 +532,22 @@ void linphone_core_cbs_set_last_call_ended(LinphoneCoreCbs *cbs, LinphoneCoreCbs cbs->vtable->last_call_ended = cb; } +LinphoneCoreCbsAudioDeviceChangedCb linphone_core_cbs_get_audio_device_changed(LinphoneCoreCbs *cbs) { + return cbs->vtable->audio_device_changed; +} + +void linphone_core_cbs_set_audio_device_changed(LinphoneCoreCbs *cbs, LinphoneCoreCbsAudioDeviceChangedCb cb) { + cbs->vtable->audio_device_changed = cb; +} + +LinphoneCoreCbsAudioDevicesListUpdatedCb linphone_core_cbs_get_audio_devices_list_updated(LinphoneCoreCbs *cbs) { + return cbs->vtable->audio_devices_list_updated; +} + +void linphone_core_cbs_set_audio_devices_list_updated(LinphoneCoreCbs *cbs, LinphoneCoreCbsAudioDevicesListUpdatedCb cb) { + cbs->vtable->audio_devices_list_updated = cb; +} + void linphone_core_cbs_set_ec_calibration_result(LinphoneCoreCbs *cbs, LinphoneCoreCbsEcCalibrationResultCb cb) { cbs->vtable->ec_calibration_result = cb; } @@ -1245,6 +1261,9 @@ static void build_sound_devices_table(LinphoneCore *lc){ old=lc->sound_conf.cards; lc->sound_conf.cards=devices; if (old!=NULL) ms_free((void *)old); + + L_GET_PRIVATE_FROM_C_OBJECT(lc)->computeAudioDevicesList(); + linphone_core_notify_audio_devices_list_updated(lc); } static string get_default_local_ring(LinphoneCore * lc) { @@ -4708,7 +4727,8 @@ bool_t linphone_core_sound_device_can_playback(LinphoneCore *lc, const char *dev LinphoneStatus linphone_core_set_ringer_device(LinphoneCore *lc, const char * devid){ MSSndCard *card=get_card_from_string_id(devid,MS_SND_CARD_CAP_PLAYBACK, lc->factory); - lc->sound_conf.ring_sndcard=card; + if (lc->sound_conf.ring_sndcard) ms_snd_card_unref(lc->sound_conf.ring_sndcard); + if (card) lc->sound_conf.ring_sndcard = ms_snd_card_ref(card); if (card && linphone_core_ready(lc)) lp_config_set_string(lc->config,"sound","ringer_dev_id",ms_snd_card_get_string_id(card)); return 0; @@ -4716,7 +4736,8 @@ LinphoneStatus linphone_core_set_ringer_device(LinphoneCore *lc, const char * de LinphoneStatus linphone_core_set_playback_device(LinphoneCore *lc, const char * devid){ MSSndCard *card=get_card_from_string_id(devid,MS_SND_CARD_CAP_PLAYBACK, lc->factory); - lc->sound_conf.play_sndcard=card; + if (lc->sound_conf.play_sndcard) ms_snd_card_unref(lc->sound_conf.play_sndcard); + if (card) lc->sound_conf.play_sndcard = ms_snd_card_ref(card); if (card && linphone_core_ready(lc)) lp_config_set_string(lc->config,"sound","playback_dev_id",ms_snd_card_get_string_id(card)); return 0; @@ -4724,7 +4745,8 @@ LinphoneStatus linphone_core_set_playback_device(LinphoneCore *lc, const char * LinphoneStatus linphone_core_set_capture_device(LinphoneCore *lc, const char * devid){ MSSndCard *card=get_card_from_string_id(devid,MS_SND_CARD_CAP_CAPTURE, lc->factory); - lc->sound_conf.capt_sndcard=card; + if (lc->sound_conf.capt_sndcard) ms_snd_card_unref(lc->sound_conf.capt_sndcard); + if (card) lc->sound_conf.capt_sndcard = ms_snd_card_ref(card); if (card && linphone_core_ready(lc)) lp_config_set_string(lc->config,"sound","capture_dev_id",ms_snd_card_get_string_id(card)); return 0; @@ -4732,7 +4754,8 @@ LinphoneStatus linphone_core_set_capture_device(LinphoneCore *lc, const char * d LinphoneStatus linphone_core_set_media_device(LinphoneCore *lc, const char * devid){ MSSndCard *card=get_card_from_string_id(devid,MS_SND_CARD_CAP_PLAYBACK, lc->factory); - lc->sound_conf.media_sndcard=card; + if (lc->sound_conf.media_sndcard) ms_snd_card_unref(lc->sound_conf.media_sndcard); + if (card) lc->sound_conf.media_sndcard = ms_snd_card_ref(card); if (card && linphone_core_ready(lc)) lp_config_set_string(lc->config,"sound","media_dev_id",ms_snd_card_get_string_id(card)); return 0; @@ -6430,6 +6453,10 @@ static void sound_config_uninit(LinphoneCore *lc) { sound_config_t *config=&lc->sound_conf; ms_free((void *)config->cards); + if (config->ring_sndcard) ms_snd_card_unref(config->ring_sndcard); + if (config->media_sndcard) ms_snd_card_unref(config->media_sndcard); + if (config->capt_sndcard) ms_snd_card_unref(config->capt_sndcard); + if (config->play_sndcard) ms_snd_card_unref(config->play_sndcard); lp_config_set_string(lc->config,"sound","remote_ring",config->remote_ring); lp_config_set_float(lc->config,"sound","playback_gain_db",config->soft_play_lev); diff --git a/coreapi/private_functions.h b/coreapi/private_functions.h index 15daaa0496ff7e6363a4468e42272fa65715e18e..df213647c04e4748c700157b084dd116d577cacd 100644 --- a/coreapi/private_functions.h +++ b/coreapi/private_functions.h @@ -52,6 +52,7 @@ void linphone_call_notify_tmmbr_received(LinphoneCall *call, int stream_index, i void linphone_call_notify_snapshot_taken(LinphoneCall *call, const char *file_path); void linphone_call_notify_next_video_frame_decoded(LinphoneCall *call); void linphone_call_notify_camera_not_working(LinphoneCall *call, const char *camera_name); +void linphone_call_notify_audio_device_changed(LinphoneCall *call, LinphoneAudioDevice *audioDevice); LinphoneCall * linphone_call_new_outgoing(struct _LinphoneCore *lc, const LinphoneAddress *from, const LinphoneAddress *to, const LinphoneCallParams *params, LinphoneProxyConfig *cfg); LinphoneCall * linphone_call_new_incoming(struct _LinphoneCore *lc, const LinphoneAddress *from, const LinphoneAddress *to, LinphonePrivate::SalCallOp *op); @@ -555,6 +556,8 @@ void linphone_core_notify_is_composing_received(LinphoneCore *lc, LinphoneChatRo void linphone_core_notify_dtmf_received(LinphoneCore* lc, LinphoneCall *call, int dtmf); void linphone_core_notify_first_call_started(LinphoneCore *lc); void linphone_core_notify_last_call_ended(LinphoneCore *lc); +void linphone_core_notify_audio_device_changed(LinphoneCore *lc, LinphoneAudioDevice *audioDevice); +void linphone_core_notify_audio_devices_list_updated(LinphoneCore *lc); /* * return true if at least a registered vtable has a cb for dtmf received*/ bool_t linphone_core_dtmf_received_has_listener(const LinphoneCore* lc); diff --git a/coreapi/proxy.c b/coreapi/proxy.c index aaab4d3714ba767e7c9dc4b7cfca8e05a09f6efa..134a44a31469c60aff097b85a4a4e178d595cdf6 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -165,7 +165,12 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *cf cfg->avpf_rr_interval = lc ? !!lp_config_get_default_int(lc->config, "proxy", "avpf_rr_interval", 5) : 5; cfg->publish_expires= lc ? lp_config_get_default_int(lc->config, "proxy", "publish_expires", -1) : -1; cfg->publish = lc ? !!lp_config_get_default_int(lc->config, "proxy", "publish", FALSE) : FALSE; - cfg->push_notification_allowed = lc ? !!lp_config_get_default_int(lc->config, "proxy", "push_notification_allowed", FALSE) : FALSE; + + bool_t push_allowed_default = FALSE; +#if defined(__ANDROID__) || defined(TARGET_OS_IPHONE) + push_allowed_default = TRUE; +#endif + cfg->push_notification_allowed = lc ? !!lp_config_get_default_int(lc->config, "proxy", "push_notification_allowed", push_allowed_default) : push_allowed_default; cfg->refkey = refkey ? ms_strdup(refkey) : NULL; if (nat_policy_ref) { LinphoneNatPolicy *policy = linphone_config_create_nat_policy_from_section(lc->config,nat_policy_ref); diff --git a/coreapi/ringtoneplayer.c b/coreapi/ringtoneplayer.c index 1e17719d06227418b20c779f810851d66ccae01c..f7e79df071ba1959c3565ae2836575bcfa904027 100644 --- a/coreapi/ringtoneplayer.c +++ b/coreapi/ringtoneplayer.c @@ -55,6 +55,10 @@ bool_t linphone_ringtoneplayer_is_started(LinphoneRingtonePlayer* rp) { return linphone_ringtoneplayer_ios_is_started(rp); } +RingStream* linphone_ringtoneplayer_get_stream(LinphoneRingtonePlayer* rp) { + return NULL; +} + int linphone_ringtoneplayer_stop(LinphoneRingtonePlayer* rp) { return linphone_ringtoneplayer_ios_stop(rp); } @@ -106,6 +110,10 @@ bool_t linphone_ringtoneplayer_is_started(LinphoneRingtonePlayer* rp) { return (rp->ringstream!=NULL); } +RingStream* linphone_ringtoneplayer_get_stream(LinphoneRingtonePlayer* rp) { + return rp->ringstream; +} + LinphoneStatus linphone_ringtoneplayer_stop(LinphoneRingtonePlayer* rp) { if (rp->ringstream) { ring_stop(rp->ringstream); diff --git a/coreapi/vtables.c b/coreapi/vtables.c index f084c2b98766387c272a0d6d9363caad76071c1b..ceea4bb3a51da0de89ba512d77e31871c065a779 100644 --- a/coreapi/vtables.c +++ b/coreapi/vtables.c @@ -109,6 +109,16 @@ void linphone_core_notify_last_call_ended(LinphoneCore *lc) { cleanup_dead_vtable_refs(lc); } +void linphone_core_notify_audio_device_changed(LinphoneCore *lc, LinphoneAudioDevice *audioDevice) { + NOTIFY_IF_EXIST(audio_device_changed, lc, audioDevice); + cleanup_dead_vtable_refs(lc); +} + +void linphone_core_notify_audio_devices_list_updated(LinphoneCore *lc) { + NOTIFY_IF_EXIST(audio_devices_list_updated, lc); + cleanup_dead_vtable_refs(lc); +} + void linphone_core_notify_call_encryption_changed(LinphoneCore *lc, LinphoneCall *call, bool_t on, const char *authentication_token) { NOTIFY_IF_EXIST(call_encryption_changed, lc,call,on,authentication_token); cleanup_dead_vtable_refs(lc); diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index a494d1721dc9f0810ebe9666f28e0dd6cb2ccc45..e0688be837d495d95a8cb70642432c498f249b29 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -74,6 +74,7 @@ set(ROOT_HEADER_FILES set(C_API_HEADER_FILES c-address.h + c-audio-device.h c-auth-info.h c-api.h c-call-cbs.h diff --git a/include/linphone/api/c-api.h b/include/linphone/api/c-api.h index 8004f9a4fa799a74b3b9eec3000e11627b9781a0..6b2c8d44d5b43c0cc944e5111031b3eab14c5738 100644 --- a/include/linphone/api/c-api.h +++ b/include/linphone/api/c-api.h @@ -22,6 +22,7 @@ #include "linphone/utils/general.h" +#include "linphone/api/c-audio-device.h" #include "linphone/api/c-auth-info.h" #include "linphone/api/c-address.h" #include "linphone/api/c-call-cbs.h" diff --git a/include/linphone/api/c-audio-device.h b/include/linphone/api/c-audio-device.h new file mode 100644 index 0000000000000000000000000000000000000000..43f3143c86ce2ad39f0c9d4b4278535d46244746 --- /dev/null +++ b/include/linphone/api/c-audio-device.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LINPHONE_AUDIO_DEVICE_H +#define LINPHONE_AUDIO_DEVICE_H + +#include "linphone/api/c-types.h" + +/** + * @addtogroup audio + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Returns the id of the audio device + * @param[in] audioDevice the #LinphoneAudioDevice + * @return the id of the audio device + */ +LINPHONE_PUBLIC const char *linphone_audio_device_get_id(const LinphoneAudioDevice *audioDevice); + +/** + * Returns the name of the audio device + * @param[in] audioDevice the #LinphoneAudioDevice + * @return the name of the audio device + */ +LINPHONE_PUBLIC const char *linphone_audio_device_get_device_name(const LinphoneAudioDevice *audioDevice); + +/** + * Returns the driver name used by the device + * @param[in] audioDevice the #LinphoneAudioDevice + * @returns the name of the driver used by this audio device + */ +LINPHONE_PUBLIC const char *linphone_audio_device_get_driver_name(const LinphoneAudioDevice *audioDevice); + +/** + * Returns the capabilities of the device + * @param[in] audioDevice the #LinphoneAudioDevice + * @returns the capabilities of the audio device (RECORD, PLAY or both) as a bit mask + */ +LINPHONE_PUBLIC LinphoneAudioDeviceCapabilities linphone_audio_device_get_capabilities(const LinphoneAudioDevice *audioDevice); + +/** + * Returns the type of the device + * @param[in] audioDevice the #LinphoneAudioDevice + * @returns the type of the audio device (microphone, speaker, earpiece, bluetooth, etc...) + */ +LINPHONE_PUBLIC LinphoneAudioDeviceType linphone_audio_device_get_type(const LinphoneAudioDevice *audioDevice); + +/** + * Returns whether or not the audio device has the given capability + * @param[in] audioDevice the #LinphoneAudioDevice + * @param[in] capability the capability to check + * @returns TRUE if the audio device has the capability, FALSE otherwise + */ +LINPHONE_PUBLIC bool_t linphone_audio_device_has_capability(const LinphoneAudioDevice *audioDevice, const LinphoneAudioDeviceCapabilities capability); + +/** + * Takes a reference on a #LinphoneAudioDevice. + */ +LINPHONE_PUBLIC LinphoneAudioDevice *linphone_audio_device_ref(LinphoneAudioDevice *audioDevice); + +/** + * Releases a #LinphoneAudioDevice. + */ +LINPHONE_PUBLIC void linphone_audio_device_unref(LinphoneAudioDevice *audioDevice); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/linphone/api/c-call-cbs.h b/include/linphone/api/c-call-cbs.h index 6ebdf54f1e28535eb4029ac10b24f3a5e8d69bca..d750aaa8a955712419f363679a82d48f4df28787 100644 --- a/include/linphone/api/c-call-cbs.h +++ b/include/linphone/api/c-call-cbs.h @@ -215,6 +215,20 @@ LINPHONE_PUBLIC LinphoneCallCbsCameraNotWorkingCb linphone_call_cbs_get_camera_n */ LINPHONE_PUBLIC void linphone_call_cbs_set_camera_not_working(LinphoneCallCbs *cbs, LinphoneCallCbsCameraNotWorkingCb cb); +/** + * Get the audio device changed callback. + * @param[in] cbs LinphoneCallCbs object. + * @return The audio device changed callback. + */ +LINPHONE_PUBLIC LinphoneCallCbsAudioDeviceChangedCb linphone_call_cbs_get_audio_device_changed(LinphoneCallCbs *cbs); + +/** + * Set the audio device changed callback. + * @param[in] cbs LinphoneCallCbs object. + * @param[in] cb The audio device changedcallback to be used. + */ +LINPHONE_PUBLIC void linphone_call_cbs_set_audio_device_changed(LinphoneCallCbs *cbs, LinphoneCallCbsAudioDeviceChangedCb cb); + /** * @} */ diff --git a/include/linphone/api/c-call.h b/include/linphone/api/c-call.h index 421057cb24cc9b31a5d9e1b83682fdbc25879d3c..69ab2536cbdf54d4eecf0c9a0059a9feefad67e4 100644 --- a/include/linphone/api/c-call.h +++ b/include/linphone/api/c-call.h @@ -837,6 +837,36 @@ LINPHONE_PUBLIC void linphone_call_set_params(LinphoneCall *call, const Linphone **/ LINPHONE_PUBLIC const LinphoneCallParams *linphone_call_get_params(LinphoneCall *call); +/** + * Sets the given #LinphoneAudioDevice as input for this call only. + * @param[in] call The #LinphoneCall + * @param[in] audio_device The #LinphoneAudioDevice + */ +LINPHONE_PUBLIC void linphone_call_set_input_audio_device(LinphoneCall *call, LinphoneAudioDevice *audio_device); + +/** + * Sets the given #LinphoneAudioDevice as output for this call only. + * @param[in] call The #LinphoneCall + * @param[in] audio_device The #LinphoneAudioDevice + */ +LINPHONE_PUBLIC void linphone_call_set_output_audio_device(LinphoneCall *call, LinphoneAudioDevice *audio_device); + +/** + * Gets the current input device for this call. + * @param[in] call The #LinphoneCall + * @returns the #LinphoneAudioDevice used by this call as input or NULL if there is currently no soundcard configured (depending on the state of the call) + * @maybenil + */ +LINPHONE_PUBLIC const LinphoneAudioDevice* linphone_call_get_input_audio_device(const LinphoneCall *call); + +/** + * Gets the current output device for this call. + * @param[in] call The #LinphoneCall + * @returns the #LinphoneAudioDevice used by this call as output or NULL if there is currently no soundcard configured (depending on the state of the call) + * @maybenil + */ +LINPHONE_PUBLIC const LinphoneAudioDevice* linphone_call_get_output_audio_device(const LinphoneCall *call); + /** * @} */ diff --git a/include/linphone/api/c-callbacks.h b/include/linphone/api/c-callbacks.h index 7b32a06448ec4c57c23782148476bea808875b09..7c02866cae8e82d93a73cb203882e824ce1d1af4 100644 --- a/include/linphone/api/c-callbacks.h +++ b/include/linphone/api/c-callbacks.h @@ -121,6 +121,14 @@ typedef void (*LinphoneCallCbsNextVideoFrameDecodedCb)(LinphoneCall *call); */ typedef void (*LinphoneCallCbsCameraNotWorkingCb)(LinphoneCall *call, const char *camera_name); +/** + * Callback to notify that the audio device has been changed. + * + * @param call LinphoneCall for which the audio device has changed + * @param audioDevice the new audio device used for this call + */ +typedef void (*LinphoneCallCbsAudioDeviceChangedCb)(LinphoneCall *call, LinphoneAudioDevice *audioDevice); + /** * @} **/ diff --git a/include/linphone/callbacks.h b/include/linphone/callbacks.h index ca0ef8821d902e59d7f014c2b5ca80d302241f8a..ade4ae39b8b774a623ee1fe1fa0a51617c9f9e92 100644 --- a/include/linphone/callbacks.h +++ b/include/linphone/callbacks.h @@ -455,6 +455,21 @@ typedef void (*LinphoneCoreCbsFirstCallStartedCb)(LinphoneCore *lc); */ typedef void (*LinphoneCoreCbsLastCallEndedCb)(LinphoneCore *lc); +/** + * Callback prototype telling that the audio device for at least one call has changed + * @param[in] lc LinphoneCore object + * @param[in] audioDevice the newly used LinphoneAudioDevice object + */ +typedef void (*LinphoneCoreCbsAudioDeviceChangedCb)(LinphoneCore *lc, LinphoneAudioDevice *audioDevice); + +/** + * Callback prototype telling the audio devices list has been updated. + * Either a new device is available or a previously available device isn't anymore. + * You can call linphone_core_get_audio_devices() to get the new list. + * @param[in] lc LinphoneCore object + */ +typedef void (*LinphoneCoreCbsAudioDevicesListUpdatedCb)(LinphoneCore *lc); + /** * @} **/ diff --git a/include/linphone/core.h b/include/linphone/core.h index 5ff575abcc344b0b5baaffd1c2e738c739edfc81..acc299bb5aaa38c8cb53cb2ea9bd43f4ac1c37bc 100644 --- a/include/linphone/core.h +++ b/include/linphone/core.h @@ -235,6 +235,8 @@ typedef struct _LinphoneCoreVTable{ LinphoneCoreCbsChatRoomEphemeralMessageDeleteCb chat_room_ephemeral_message_deleted; LinphoneCoreCbsFirstCallStartedCb first_call_started; LinphoneCoreCbsLastCallEndedCb last_call_ended; + LinphoneCoreCbsAudioDeviceChangedCb audio_device_changed; + LinphoneCoreCbsAudioDevicesListUpdatedCb audio_devices_list_updated; void *user_data; /**<User data associated with the above callbacks */ } LinphoneCoreVTable; @@ -837,6 +839,34 @@ LINPHONE_PUBLIC LinphoneCoreCbsLastCallEndedCb linphone_core_cbs_get_last_call_e **/ LINPHONE_PUBLIC void linphone_core_cbs_set_last_call_ended(LinphoneCoreCbs *cbs, LinphoneCoreCbsLastCallEndedCb cb); +/** + * Gets the audio device changed callback. + * @param[in] cbs LinphoneCoreCbs object + * @return The current callback + */ +LINPHONE_PUBLIC LinphoneCoreCbsAudioDeviceChangedCb linphone_core_cbs_get_audio_device_changed(LinphoneCoreCbs *cbs); + +/** + * Sets the audio device changed callback. + * @param[in] cbs LinphoneCoreCbs object + * @param[in] cb The callback to use + **/ +LINPHONE_PUBLIC void linphone_core_cbs_set_audio_device_changed(LinphoneCoreCbs *cbs, LinphoneCoreCbsAudioDeviceChangedCb cb); + +/** + * Gets the audio devices list updated callback. + * @param[in] cbs LinphoneCoreCbs object + * @return The current callback + */ +LINPHONE_PUBLIC LinphoneCoreCbsAudioDevicesListUpdatedCb linphone_core_cbs_get_audio_devices_list_updated(LinphoneCoreCbs *cbs); + +/** + * Sets the audio devices list updated callback. + * @param[in] cbs LinphoneCoreCbs object + * @param[in] cb The callback to use + **/ +LINPHONE_PUBLIC void linphone_core_cbs_set_audio_devices_list_updated(LinphoneCoreCbs *cbs, LinphoneCoreCbsAudioDevicesListUpdatedCb cb); + /** * @brief Sets a callback to call each time the echo-canceler calibration is completed. */ @@ -6321,6 +6351,90 @@ LINPHONE_PUBLIC void linphone_core_set_auto_iterate_enabled(LinphoneCore *core, */ LINPHONE_PUBLIC bool_t linphone_core_is_auto_iterate_enabled(LinphoneCore *core); +/** + * Returns a list of audio devices, with only the first device for each type + * To have the list of all audio devices, use #linphone_core_get_extended_audio_devices + * @param[in] core The #LinphoneCore + * @returns \bctbx_list{LinphoneAudioDevice} A list with the first #LinphoneAudioDevice of each type + * @ingroup audio + */ +LINPHONE_PUBLIC bctbx_list_t *linphone_core_get_audio_devices(const LinphoneCore *core); + +/** + * Returns the list of all audio devices + * @param[in] core The #LinphoneCore + * @returns \bctbx_list{LinphoneAudioDevice} A list of all #LinphoneAudioDevice + * @ingroup audio + */ +LINPHONE_PUBLIC bctbx_list_t *linphone_core_get_extended_audio_devices(const LinphoneCore *core); + +/** + * Sets the given #LinphoneAudioDevice as input for all active calls. + * @param[in] core The #LinphoneCore + * @param[in] audio_device The #LinphoneAudioDevice + * @ingroup audio + */ +LINPHONE_PUBLIC void linphone_core_set_input_audio_device(LinphoneCore *core, LinphoneAudioDevice *audio_device); + +/** + * Sets the given #LinphoneAudioDevice as output for all active calls. + * @param[in] core The #LinphoneCore + * @param[in] audio_device The #LinphoneAudioDevice + * @ingroup audio + */ +LINPHONE_PUBLIC void linphone_core_set_output_audio_device(LinphoneCore *core, LinphoneAudioDevice *audio_device); + +/** + * Gets the input audio device for the current call + * @param[in] core The #LinphoneCore + * @returns The input audio device for the current or first call, NULL if there is no call + * @maybenil + * @ingroup audio + */ +LINPHONE_PUBLIC const LinphoneAudioDevice* linphone_core_get_input_audio_device(const LinphoneCore *core); + +/** + * Gets the output audio device for the current call + * @param[in] core The #LinphoneCore + * @returns The output audio device for the current or first call, NULL if there is no call + * @maybenil + * @ingroup audio + */ +LINPHONE_PUBLIC const LinphoneAudioDevice* linphone_core_get_output_audio_device(const LinphoneCore *core); + +/** + * Sets the given #LinphoneAudioDevice as default input for next calls. + * @param[in] core The #LinphoneCore + * @param[in] audio_device The #LinphoneAudioDevice + * @ingroup audio + */ +LINPHONE_PUBLIC void linphone_core_set_default_input_audio_device(LinphoneCore *core, LinphoneAudioDevice *audio_device); + +/** + * Sets the given #LinphoneAudioDevice as default output for next calls. + * @param[in] core The #LinphoneCore + * @param[in] audio_device The #LinphoneAudioDevice + * @ingroup audio + */ +LINPHONE_PUBLIC void linphone_core_set_default_output_audio_device(LinphoneCore *core, LinphoneAudioDevice *audio_device); + +/** + * Gets the default input audio device + * @param[in] core The #LinphoneCore + * @returns The default input audio device + * @ingroup audio + */ +LINPHONE_PUBLIC const LinphoneAudioDevice* linphone_core_get_default_input_audio_device(const LinphoneCore *core); + +/** + * Gets the default output audio device + * @param[in] core The #LinphoneCore + * @returns The default output audio device + * @ingroup audio + */ +LINPHONE_PUBLIC const LinphoneAudioDevice* linphone_core_get_default_output_audio_device(const LinphoneCore *core); + + #ifdef __cplusplus } #endif diff --git a/include/linphone/enums/call-enums.h b/include/linphone/enums/call-enums.h index 9521bfb955c580dfe12fbd918169db1bb0ffe194..9dfb503d69cbb356fde30607d0961e1d09c560c1 100644 --- a/include/linphone/enums/call-enums.h +++ b/include/linphone/enums/call-enums.h @@ -50,6 +50,33 @@ typedef enum _LinphoneCallState{ LinphoneCallStateEarlyUpdating, /**< We are updating the call while not yet answered (SIP UPDATE in early dialog sent) */ } LinphoneCallState; +/** + * #LinphoneAudioDeviceType enum represents the different types of an audio device. + * @ingroup audio + */ +typedef enum _LinphoneAudioDeviceType { + LinphoneAudioDeviceTypeUnknown, /** Unknown */ + LinphoneAudioDeviceTypeMicrophone, /** Microphone */ + LinphoneAudioDeviceTypeEarpiece, /** Earpiece */ + LinphoneAudioDeviceTypeSpeaker, /** Speaker */ + LinphoneAudioDeviceTypeBluetooth, /** Bluetooth */ + LinphoneAudioDeviceTypeBluetoothA2DP, /** Bluetooth A2DP */ + LinphoneAudioDeviceTypeTelephony, /** Telephony */ + LinphoneAudioDeviceTypeAuxLine, /** AuxLine */ + LinphoneAudioDeviceTypeGenericUsb, /** GenericUsb */ + LinphoneAudioDeviceTypeHeadset, /** Headset */ + LinphoneAudioDeviceTypeHeadphones, /** Headphones */ +} LinphoneAudioDeviceType; + +/** + * #LinphoneAudioDeviceCapabilities enum represents whether a device can record audio, play audio or both + * @ingroup audio + */ +typedef enum _LinphoneAudioDeviceCapabilities { + LinphoneAudioDeviceCapabilityRecord = 1 << 0, /** Can record audio */ + LinphoneAudioDeviceCapabilityPlay = 1 << 1, /** Can play audio */ +} LinphoneAudioDeviceCapabilities; + // ============================================================================= // DEPRECATED // ============================================================================= diff --git a/include/linphone/ringtoneplayer.h b/include/linphone/ringtoneplayer.h index 0bbf53431a9236f6657e7d51ce8edcbfc0011611..44c257679b84b29c8ccbd2d14ec37e53beef5ea2 100644 --- a/include/linphone/ringtoneplayer.h +++ b/include/linphone/ringtoneplayer.h @@ -46,6 +46,7 @@ LINPHONE_PUBLIC LinphoneStatus linphone_ringtoneplayer_start(MSFactory *factory, LINPHONE_PUBLIC LinphoneStatus linphone_ringtoneplayer_start_with_cb(MSFactory *factory, LinphoneRingtonePlayer* rp, MSSndCard* card, const char* ringtone, int loop_pause_ms, LinphoneRingtonePlayerFunc end_of_ringtone, void * user_data); LINPHONE_PUBLIC bool_t linphone_ringtoneplayer_is_started(LinphoneRingtonePlayer* rp); +LINPHONE_PUBLIC RingStream* linphone_ringtoneplayer_get_stream(LinphoneRingtonePlayer* rp); LINPHONE_PUBLIC LinphoneStatus linphone_ringtoneplayer_stop(LinphoneRingtonePlayer* rp); #ifdef __cplusplus diff --git a/include/linphone/types.h b/include/linphone/types.h index 1de296d00fd33a49d45774b958d6673138c942b1..6bb279b7b071242fd380a3f756e56f9e51698cae 100644 --- a/include/linphone/types.h +++ b/include/linphone/types.h @@ -1243,4 +1243,10 @@ typedef struct _LinphoneHeaders LinphoneHeaders; **/ typedef struct _LinphonePushNotificationMessage LinphonePushNotificationMessage; +/** + * Object holding audio device information. + * @ingroup audio +**/ +typedef struct _LinphoneAudioDevice LinphoneAudioDevice; + #endif /* LINPHONE_TYPES_H_ */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 55e9126b9f7d7b31712440d66c35d411c64f4e47..335a37f892e04832ca2e0dacab82f7f60868183b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -111,6 +111,8 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES c-wrapper/internal/c-tools.h call/call-p.h call/call.h + call/audio-device/audio-device.h + call/audio-device/audio-device.cpp call/local-conference-call-p.h call/local-conference-call.h call/remote-conference-call-p.h @@ -284,6 +286,7 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES address/identity-address.cpp address/identity-address-parser.cpp c-wrapper/api/c-address.cpp + c-wrapper/api/c-audio-device.cpp c-wrapper/api/c-auth-info.cpp c-wrapper/api/c-call-cbs.cpp c-wrapper/api/c-call-params.cpp diff --git a/src/c-wrapper/api/c-audio-device.cpp b/src/c-wrapper/api/c-audio-device.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dad6dfb2da65460d195302ba0b1c8a8d7b9510f1 --- /dev/null +++ b/src/c-wrapper/api/c-audio-device.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include "linphone/api/c-audio-device.h" +#include "call/audio-device/audio-device.h" +#include "c-wrapper/c-wrapper.h" + +using namespace LinphonePrivate; + +const char *linphone_audio_device_get_id(const LinphoneAudioDevice *audioDevice) { + if (audioDevice) { + return L_STRING_TO_C(AudioDevice::toCpp(audioDevice)->getId()); + } + return NULL; +} + +const char *linphone_audio_device_get_device_name(const LinphoneAudioDevice *audioDevice) { + if (audioDevice) { + return L_STRING_TO_C(AudioDevice::toCpp(audioDevice)->getDeviceName()); + } + return NULL; +} + +const char *linphone_audio_device_get_driver_name(const LinphoneAudioDevice *audioDevice) { + if (audioDevice) { + return L_STRING_TO_C(AudioDevice::toCpp(audioDevice)->getDriverName()); + } + return NULL; +} + +LinphoneAudioDeviceCapabilities linphone_audio_device_get_capabilities(const LinphoneAudioDevice *audioDevice) { + return static_cast<LinphoneAudioDeviceCapabilities>(AudioDevice::toCpp(audioDevice)->getCapabilities()); +} + +LinphoneAudioDeviceType linphone_audio_device_get_type(const LinphoneAudioDevice *audioDevice) { + return static_cast<LinphoneAudioDeviceType>(AudioDevice::toCpp(audioDevice)->getType()); +} + +bool_t linphone_audio_device_has_capability(const LinphoneAudioDevice *audioDevice, const LinphoneAudioDeviceCapabilities capability) { + return static_cast<bool_t>(linphone_audio_device_get_capabilities(audioDevice) & capability); +} + +LinphoneAudioDevice *linphone_audio_device_ref(LinphoneAudioDevice *audioDevice) { + if (audioDevice) { + AudioDevice::toCpp(audioDevice)->ref(); + return audioDevice; + } + return NULL; +} + +void linphone_audio_device_unref(LinphoneAudioDevice *audioDevice) { + if (audioDevice) { + AudioDevice::toCpp(audioDevice)->unref(); + } +} \ No newline at end of file diff --git a/src/c-wrapper/api/c-call-cbs.cpp b/src/c-wrapper/api/c-call-cbs.cpp index afb0add68476f1ec8787bc3450593aa5d1ff1025..b528c0b0722936ae4fa57c292b4367abc8a4ffff 100644 --- a/src/c-wrapper/api/c-call-cbs.cpp +++ b/src/c-wrapper/api/c-call-cbs.cpp @@ -37,6 +37,7 @@ struct _LinphoneCallCbs { LinphoneCallCbsSnapshotTakenCb snapshotTakenCb; LinphoneCallCbsNextVideoFrameDecodedCb nextVideoFrameDecodedCb; LinphoneCallCbsCameraNotWorkingCb cameraNotWorkingCb; + LinphoneCallCbsAudioDeviceChangedCb audioDeviceChangedCb; }; BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneCallCbs); @@ -160,3 +161,11 @@ LinphoneCallCbsCameraNotWorkingCb linphone_call_cbs_get_camera_not_working(Linph void linphone_call_cbs_set_camera_not_working(LinphoneCallCbs *cbs, LinphoneCallCbsCameraNotWorkingCb cb) { cbs->cameraNotWorkingCb = cb; } + +LinphoneCallCbsAudioDeviceChangedCb linphone_call_cbs_get_audio_device_changed(LinphoneCallCbs *cbs) { + return cbs->audioDeviceChangedCb; +} + +void linphone_call_cbs_set_audio_device_changed(LinphoneCallCbs *cbs, LinphoneCallCbsAudioDeviceChangedCb cb) { + cbs->audioDeviceChangedCb = cb; +} diff --git a/src/c-wrapper/api/c-call.cpp b/src/c-wrapper/api/c-call.cpp index 6bc794abd4af55bc09ddc38d472ce5c86e3a33da..72a2d6480a19eb0de26752a6e8839276b320e435 100644 --- a/src/c-wrapper/api/c-call.cpp +++ b/src/c-wrapper/api/c-call.cpp @@ -31,6 +31,7 @@ #include "conference/params/media-session-params-p.h" #include "conference/session/ms2-streams.h" #include "core/core-p.h" +#include "call/audio-device/audio-device.h" // ============================================================================= @@ -189,6 +190,10 @@ void linphone_call_notify_camera_not_working(LinphoneCall *call, const char *cam NOTIFY_IF_EXIST(CameraNotWorking, camera_not_working, call, camera_name); } +void linphone_call_notify_audio_device_changed(LinphoneCall *call, LinphoneAudioDevice *audioDevice) { + NOTIFY_IF_EXIST(AudioDeviceChanged, audio_device_changed, call, audioDevice); +} + // ============================================================================= // Public functions. // ============================================================================= @@ -700,3 +705,30 @@ LinphoneCall *linphone_call_new_incoming (LinphoneCore *lc, const LinphoneAddres L_GET_PRIVATE_FROM_C_OBJECT(lcall)->initiateIncoming(); return lcall; } + +void linphone_call_set_input_audio_device(LinphoneCall *call, LinphoneAudioDevice *audio_device) { + if (audio_device) { + L_GET_CPP_PTR_FROM_C_OBJECT(call)->setInputAudioDevice(LinphonePrivate::AudioDevice::toCpp(audio_device)); + } +} + +void linphone_call_set_output_audio_device(LinphoneCall *call, LinphoneAudioDevice *audio_device) { + if (audio_device) { + L_GET_CPP_PTR_FROM_C_OBJECT(call)->setOutputAudioDevice(LinphonePrivate::AudioDevice::toCpp(audio_device)); + } +} + +const LinphoneAudioDevice* linphone_call_get_input_audio_device(const LinphoneCall *call) { + LinphonePrivate::AudioDevice *audioDevice = L_GET_CPP_PTR_FROM_C_OBJECT(call)->getInputAudioDevice(); + if (audioDevice) { + return audioDevice->toC(); + } + return NULL; +} +const LinphoneAudioDevice* linphone_call_get_output_audio_device(const LinphoneCall *call) { + LinphonePrivate::AudioDevice *audioDevice = L_GET_CPP_PTR_FROM_C_OBJECT(call)->getOutputAudioDevice(); + if (audioDevice) { + return audioDevice->toC(); + } + return NULL; +} \ No newline at end of file diff --git a/src/c-wrapper/api/c-core.cpp b/src/c-wrapper/api/c-core.cpp index 3153a22e3919eb00c29ab87a6c77454d11d32f0c..4f0dee39c24a1a325d0c99260a6386cdb7d7f730 100644 --- a/src/c-wrapper/api/c-core.cpp +++ b/src/c-wrapper/api/c-core.cpp @@ -28,6 +28,7 @@ #include "chat/encryption/encryption-engine.h" #include "chat/encryption/legacy-encryption-engine.h" #include "linphone/api/c-types.h" +#include "call/audio-device/audio-device.h" // ============================================================================= @@ -161,3 +162,67 @@ LinphoneChatRoom * linphone_core_get_new_chat_room_from_conf_addr(LinphoneCore * } return chatRoom; } + +bctbx_list_t *linphone_core_get_audio_devices(const LinphoneCore *lc) { + return LinphonePrivate::AudioDevice::getCListFromCppList(L_GET_CPP_PTR_FROM_C_OBJECT(lc)->getAudioDevices()); +} + +bctbx_list_t *linphone_core_get_extended_audio_devices(const LinphoneCore *lc) { + return LinphonePrivate::AudioDevice::getCListFromCppList(L_GET_CPP_PTR_FROM_C_OBJECT(lc)->getExtendedAudioDevices()); +} + +void linphone_core_set_input_audio_device(LinphoneCore *lc, LinphoneAudioDevice *audio_device) { + if (audio_device) { + L_GET_CPP_PTR_FROM_C_OBJECT(lc)->setInputAudioDevice(LinphonePrivate::AudioDevice::toCpp(audio_device)); + } +} + +void linphone_core_set_output_audio_device(LinphoneCore *lc, LinphoneAudioDevice *audio_device) { + if (audio_device) { + L_GET_CPP_PTR_FROM_C_OBJECT(lc)->setOutputAudioDevice(LinphonePrivate::AudioDevice::toCpp(audio_device)); + } +} + +const LinphoneAudioDevice* linphone_core_get_input_audio_device(const LinphoneCore *lc) { + LinphonePrivate::AudioDevice *audioDevice = L_GET_CPP_PTR_FROM_C_OBJECT(lc)->getInputAudioDevice(); + if (audioDevice) { + return audioDevice->toC(); + } + return NULL; +} + +const LinphoneAudioDevice* linphone_core_get_output_audio_device(const LinphoneCore *lc) { + LinphonePrivate::AudioDevice *audioDevice = L_GET_CPP_PTR_FROM_C_OBJECT(lc)->getOutputAudioDevice(); + if (audioDevice) { + return audioDevice->toC(); + } + return NULL; +} + +void linphone_core_set_default_input_audio_device(LinphoneCore *lc, LinphoneAudioDevice *audio_device) { + if (audio_device) { + L_GET_CPP_PTR_FROM_C_OBJECT(lc)->setDefaultInputAudioDevice(LinphonePrivate::AudioDevice::toCpp(audio_device)); + } +} + +void linphone_core_set_default_output_audio_device(LinphoneCore *lc, LinphoneAudioDevice *audio_device) { + if (audio_device) { + L_GET_CPP_PTR_FROM_C_OBJECT(lc)->setDefaultOutputAudioDevice(LinphonePrivate::AudioDevice::toCpp(audio_device)); + } +} + +const LinphoneAudioDevice* linphone_core_get_default_input_audio_device(const LinphoneCore *lc) { + LinphonePrivate::AudioDevice *audioDevice = L_GET_CPP_PTR_FROM_C_OBJECT(lc)->getDefaultInputAudioDevice(); + if (audioDevice) { + return audioDevice->toC(); + } + return NULL; +} + +const LinphoneAudioDevice* linphone_core_get_default_output_audio_device(const LinphoneCore *lc) { + LinphonePrivate::AudioDevice *audioDevice = L_GET_CPP_PTR_FROM_C_OBJECT(lc)->getDefaultOutputAudioDevice(); + if (audioDevice) { + return audioDevice->toC(); + } + return NULL; +} diff --git a/src/c-wrapper/c-wrapper.h b/src/c-wrapper/c-wrapper.h index c0e8be26ee8d36b6693930ed9356e0aad31214ad..3eefcc566776f2ee584dfe66667b004285a01487 100644 --- a/src/c-wrapper/c-wrapper.h +++ b/src/c-wrapper/c-wrapper.h @@ -78,6 +78,7 @@ L_REGISTER_TYPES(L_REGISTER_ID) BELLE_SIP_TYPE_ID(LinphoneAccountCreator), BELLE_SIP_TYPE_ID(LinphoneAccountCreatorCbs), BELLE_SIP_TYPE_ID(LinphoneAccountCreatorService), +BELLE_SIP_TYPE_ID(LinphoneAudioDevice), BELLE_SIP_TYPE_ID(LinphoneAuthInfo), BELLE_SIP_TYPE_ID(LinphoneBuffer), BELLE_SIP_TYPE_ID(LinphoneCallCbs), diff --git a/src/call/audio-device/audio-device.cpp b/src/call/audio-device/audio-device.cpp new file mode 100644 index 0000000000000000000000000000000000000000..da8c449a075368097097f9b2853f42c796b3e2c1 --- /dev/null +++ b/src/call/audio-device/audio-device.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include "audio-device.h" +#include "logger/logger.h" + +using namespace std; + +LINPHONE_BEGIN_NAMESPACE + +AudioDevice::AudioDevice(MSSndCard *soundCard) + :soundCard(ms_snd_card_ref(soundCard)) +{ + const char *id = ms_snd_card_get_string_id(soundCard); + deviceId = id; + + const char *name = ms_snd_card_get_name(soundCard); + deviceName = name; + + unsigned int cap = ms_snd_card_get_capabilities(soundCard); + if (cap & MS_SND_CARD_CAP_CAPTURE && cap & MS_SND_CARD_CAP_PLAYBACK) { + capabilities = static_cast<Capabilities>(static_cast<int>(Capabilities::Record) | static_cast<int>(Capabilities::Play)); + } else if (cap & MS_SND_CARD_CAP_CAPTURE) { + capabilities = Capabilities::Record; + } else if (cap & MS_SND_CARD_CAP_PLAYBACK) { + capabilities = Capabilities::Play; + } + + driverName = ms_snd_card_get_driver_type(soundCard); + + MSSndCardDeviceType type = ms_snd_card_get_device_type(soundCard); + switch (type) { + case MS_SND_CARD_DEVICE_TYPE_MICROPHONE: + deviceType = AudioDevice::Type::Microphone; + break; + case MS_SND_CARD_DEVICE_TYPE_EARPIECE: + deviceType = AudioDevice::Type::Earpiece; + break; + case MS_SND_CARD_DEVICE_TYPE_SPEAKER: + deviceType = AudioDevice::Type::Speaker; + break; + case MS_SND_CARD_DEVICE_TYPE_BLUETOOTH: + deviceType = AudioDevice::Type::Bluetooth; + break; + case MS_SND_CARD_DEVICE_TYPE_BLUETOOTH_A2DP: + deviceType = AudioDevice::Type::BluetoothA2DP; + break; + case MS_SND_CARD_DEVICE_TYPE_TELEPHONY: + deviceType = AudioDevice::Type::Telephony; + break; + case MS_SND_CARD_DEVICE_TYPE_AUX_LINE: + deviceType = AudioDevice::Type::AuxLine; + break; + case MS_SND_CARD_DEVICE_TYPE_GENERIC_USB: + deviceType = AudioDevice::Type::GenericUsb; + break; + case MS_SND_CARD_DEVICE_TYPE_HEADSET: + deviceType = AudioDevice::Type::Headset; + break; + case MS_SND_CARD_DEVICE_TYPE_HEADPHONES: + deviceType = AudioDevice::Type::Headphones; + break; + default: + case MS_SND_CARD_DEVICE_TYPE_UNKNOWN: + deviceType = AudioDevice::Type::Unknown; + lWarning() << "Device [" << deviceName << "] type is unknown"; + break; + } +} + +AudioDevice::~AudioDevice() { + ms_snd_card_unref(soundCard); +} + +MSSndCard *AudioDevice::getSoundCard() const { + return soundCard; +} + +const string& AudioDevice::getId() const { + return deviceId; +} + +const string& AudioDevice::getDeviceName() const { + return deviceName; +} + +const string& AudioDevice::getDriverName() const { + return driverName; +} + +const AudioDevice::Capabilities& AudioDevice::getCapabilities() const { + return capabilities; +} + +const AudioDevice::Type& AudioDevice::getType() const { + return deviceType; +} + +string AudioDevice::toString() const { + std::ostringstream ss; + ss << driverName << ": driver [" << driverName << "], type ["; + switch (deviceType) { + case AudioDevice::Type::Microphone: + ss << "Microphone"; + break; + case AudioDevice::Type::Earpiece: + ss << "Earpiece"; + break; + case AudioDevice::Type::Speaker: + ss << "Speaker"; + break; + case AudioDevice::Type::Bluetooth: + ss << "Bluetooth"; + break; + case AudioDevice::Type::BluetoothA2DP: + ss << "BluetoothA2DP"; + break; + case AudioDevice::Type::Telephony: + ss << "Telephony"; + break; + case AudioDevice::Type::AuxLine: + ss << "AuxLine"; + break; + case AudioDevice::Type::GenericUsb: + ss << "Generic USB"; + break; + case AudioDevice::Type::Headset: + ss << "Headset"; + break; + case AudioDevice::Type::Headphones: + ss << "Headphones"; + break; + case AudioDevice::Type::Unknown: + default: + ss << "Unknown"; + break; + } + ss << "]"; + return ss.str(); +} + +LINPHONE_END_NAMESPACE diff --git a/src/call/audio-device/audio-device.h b/src/call/audio-device/audio-device.h new file mode 100644 index 0000000000000000000000000000000000000000..779c6cc64ed4c1f7b2f8a82d6b9e8008a9703fb4 --- /dev/null +++ b/src/call/audio-device/audio-device.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ +#ifndef AUDIO_DEVICE_H +#define AUDIO_DEVICE_H + +#include <belle-sip/object++.hh> +#include "linphone/api/c-types.h" +#include "linphone/enums/call-enums.h" +#include <mediastreamer2/mssndcard.h> + +LINPHONE_BEGIN_NAMESPACE + +class AudioDevice : public bellesip::HybridObject<LinphoneAudioDevice, AudioDevice> { +public: + enum Type { + Unknown = LinphoneAudioDeviceTypeUnknown, + Microphone = LinphoneAudioDeviceTypeMicrophone, + Earpiece = LinphoneAudioDeviceTypeEarpiece, + Speaker = LinphoneAudioDeviceTypeSpeaker, + Bluetooth = LinphoneAudioDeviceTypeBluetooth, + BluetoothA2DP = LinphoneAudioDeviceTypeBluetoothA2DP, + Telephony = LinphoneAudioDeviceTypeTelephony, + AuxLine = LinphoneAudioDeviceTypeAuxLine, + GenericUsb = LinphoneAudioDeviceTypeGenericUsb, + Headset = LinphoneAudioDeviceTypeHeadset, + Headphones + }; + + enum Capabilities { + Record = LinphoneAudioDeviceCapabilityRecord, + Play = LinphoneAudioDeviceCapabilityPlay + }; + + AudioDevice(MSSndCard *soundCard); + ~AudioDevice(); + + MSSndCard *getSoundCard() const; + const std::string& getId() const; + const std::string& getDeviceName() const; + const std::string& getDriverName() const; + const Capabilities& getCapabilities() const; + const Type& getType() const; + + std::string toString() const override; + + std::ostream& operator << (std::ostream& str) { + str << this->toString(); + return str; + } + +private: + MSSndCard *soundCard; + std::string deviceId; + std::string deviceName; + std::string driverName; + Capabilities capabilities; + Type deviceType; +}; + +LINPHONE_END_NAMESPACE +#endif diff --git a/src/call/call.cpp b/src/call/call.cpp index 8730851b4594e739a96380c7b0d36a61a1eb4089..d3174bd958645db590b7cfecfd045bbd5e558d95 100644 --- a/src/call/call.cpp +++ b/src/call/call.cpp @@ -951,4 +951,82 @@ void Call::setSpeakerVolumeGain (float value) { static_pointer_cast<MediaSession>(d->getActiveSession())->setSpeakerVolumeGain(value); } +void Call::setInputAudioDevice(AudioDevice *audioDevice) { + L_D(); + + if ((audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Record)) == 0) { + lError() << "Audio device [" << audioDevice << "] doesn't have Record capability"; + return; + } + + static_pointer_cast<MediaSession>(d->getActiveSession())->setInputAudioDevice(audioDevice); + linphone_call_notify_audio_device_changed(L_GET_C_BACK_PTR(getSharedFromThis()), audioDevice->toC()); +} + +void Call::setOutputAudioDevice(AudioDevice *audioDevice) { + L_D(); + + if ((audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Play)) == 0) { + lError() << "Audio device [" << audioDevice << "] doesn't have Play capability"; + return; + } + + RingStream *ringStream = nullptr; + switch (getState()) { + case CallSession::State::OutgoingInit: + case CallSession::State::OutgoingRinging: + ringStream = getCore()->getCCore()->ringstream; + if (ringStream) { + ring_stream_set_output_ms_snd_card(ringStream, audioDevice->getSoundCard()); + } + break; + case CallSession::State::IncomingReceived: + ringStream = linphone_ringtoneplayer_get_stream(getCore()->getCCore()->ringtoneplayer); + if (ringStream) { + ring_stream_set_output_ms_snd_card(ringStream, audioDevice->getSoundCard()); + } + break; + default: + static_pointer_cast<MediaSession>(d->getActiveSession())->setOutputAudioDevice(audioDevice); + break; + } + linphone_call_notify_audio_device_changed(L_GET_C_BACK_PTR(getSharedFromThis()), audioDevice->toC()); +} + +AudioDevice* Call::getInputAudioDevice() const { + L_D(); + return static_pointer_cast<MediaSession>(d->getActiveSession())->getInputAudioDevice(); +} + +AudioDevice* Call::getOutputAudioDevice() const { + L_D(); + + RingStream *ringStream = nullptr; + switch (getState()) { + case CallSession::State::OutgoingInit: + case CallSession::State::OutgoingRinging: + ringStream = getCore()->getCCore()->ringstream; + if (ringStream) { + MSSndCard *card = ring_stream_get_output_ms_snd_card(ringStream); + if (card) { + return getCore()->findAudioDeviceMatchingMsSoundCard(card); + } + } + break; + case CallSession::State::IncomingReceived: + ringStream = linphone_ringtoneplayer_get_stream(getCore()->getCCore()->ringtoneplayer); + if (ringStream) { + MSSndCard *card = ring_stream_get_output_ms_snd_card(ringStream); + if (card) { + return getCore()->findAudioDeviceMatchingMsSoundCard(card); + } + } + break; + default: + return static_pointer_cast<MediaSession>(d->getActiveSession())->getOutputAudioDevice(); + } + + return nullptr; +} + LINPHONE_END_NAMESPACE diff --git a/src/call/call.h b/src/call/call.h index 667a1e651ba04d84d0925ae778bba54cbea6d6f3..e355be40024cf4a97fc75801f85d793222545432 100644 --- a/src/call/call.h +++ b/src/call/call.h @@ -23,6 +23,7 @@ #include "conference/params/media-session-params.h" #include "conference/session/call-session.h" #include "core/core-accessor.h" +#include "call/audio-device/audio-device.h" #include "object/object.h" // ============================================================================= @@ -129,6 +130,11 @@ public: void setParams (const MediaSessionParams *msp); void setSpeakerVolumeGain (float value); + void setInputAudioDevice(AudioDevice *audioDevice); + void setOutputAudioDevice(AudioDevice *audioDevice); + AudioDevice *getInputAudioDevice() const; + AudioDevice *getOutputAudioDevice() const; + protected: Call (CallPrivate &p, std::shared_ptr<Core> core); diff --git a/src/conference/session/audio-stream.cpp b/src/conference/session/audio-stream.cpp index 6778cedecf89b888c4a308457cb8197c150377b5..cff0846904d512e1d3a3dc25e3ef9a110c0e5d35 100644 --- a/src/conference/session/audio-stream.cpp +++ b/src/conference/session/audio-stream.cpp @@ -251,7 +251,12 @@ void MS2AudioStream::render(const OfferAnswerContext ¶ms, CallSession::State if (isMain()){ getMediaSessionPrivate().getCurrentParams()->getPrivate()->setUsedAudioCodec(rtp_profile_get_payload(audioProfile, usedPt)); } - MSSndCard *playcard = getCCore()->sound_conf.lsd_card ? getCCore()->sound_conf.lsd_card : getCCore()->sound_conf.play_sndcard; + // try to get playcard from the stream if it was already set + MSSndCard *playcard = audio_stream_get_output_ms_snd_card(mStream); + // If stream doesn't have a playcard associated with it, then use the default values + if (!playcard) + playcard = getCCore()->sound_conf.lsd_card ? getCCore()->sound_conf.lsd_card : getCCore()->sound_conf.play_sndcard; + if (!playcard) lWarning() << "No card defined for playback!"; MSSndCard *captcard = getCCore()->sound_conf.capt_sndcard; @@ -352,8 +357,13 @@ void MS2AudioStream::render(const OfferAnswerContext ¶ms, CallSession::State if (ok) { VideoStream *vs = getPeerVideoStream(); if (vs) audio_stream_link_video(mStream, vs); + + if (mCurrentCaptureCard) ms_snd_card_unref(mCurrentCaptureCard); + if (mCurrentPlaybackCard) ms_snd_card_unref(mCurrentPlaybackCard); mCurrentCaptureCard = ms_media_resource_get_soundcard(&io.input); mCurrentPlaybackCard = ms_media_resource_get_soundcard(&io.output); + if (mCurrentCaptureCard) mCurrentCaptureCard = ms_snd_card_ref(mCurrentCaptureCard); + if (mCurrentPlaybackCard) mCurrentPlaybackCard = ms_snd_card_ref(mCurrentPlaybackCard); int err = audio_stream_start_from_io(mStream, audioProfile, dest.rtpAddr.c_str(), dest.rtpPort, dest.rtcpAddr.c_str(), dest.rtcpPort, usedPt, &io); @@ -436,6 +446,8 @@ void MS2AudioStream::stop(){ getMediaSessionPrivate().getCurrentParams()->getPrivate()->setUsedAudioCodec(nullptr); + if (mCurrentCaptureCard) ms_snd_card_unref(mCurrentCaptureCard); + if (mCurrentPlaybackCard) ms_snd_card_unref(mCurrentPlaybackCard); mCurrentCaptureCard = nullptr; mCurrentPlaybackCard = nullptr; } @@ -700,6 +712,24 @@ bool MS2AudioStream::echoCancellationEnabled()const{ ms_filter_call_method(mStream->ec, MS_ECHO_CANCELLER_GET_BYPASS_MODE, &val); return !val; } + +void MS2AudioStream::setInputDevice(AudioDevice *audioDevice) { + audio_stream_set_input_ms_snd_card(mStream, audioDevice->getSoundCard()); +} + +void MS2AudioStream::setOutputDevice(AudioDevice *audioDevice) { + audio_stream_set_output_ms_snd_card(mStream, audioDevice->getSoundCard()); +} + +AudioDevice* MS2AudioStream::getInputDevice() const { + MSSndCard *card = audio_stream_get_input_ms_snd_card(mStream); + return getCore().findAudioDeviceMatchingMsSoundCard(card); +} + +AudioDevice* MS2AudioStream::getOutputDevice() const { + MSSndCard *card = audio_stream_get_output_ms_snd_card(mStream); + return getCore().findAudioDeviceMatchingMsSoundCard(card); +} void MS2AudioStream::finish(){ if (mStream){ diff --git a/src/conference/session/media-session.cpp b/src/conference/session/media-session.cpp index c386c879809e5899cc13e422f8791c9d9877cb92..54521ca25811e17dd0e0b533818c15a654b5d96e 100644 --- a/src/conference/session/media-session.cpp +++ b/src/conference/session/media-session.cpp @@ -343,8 +343,9 @@ void MediaSessionPrivate::remoteRinging () { return; } - setState(CallSession::State::OutgoingRinging, "Remote ringing"); + // Start ringback tone before moving to next state as we need to retrieve the output device of the state we are currently in q->getCore()->getPrivate()->getToneManager()->startRingbackTone(q->getSharedFromThis()); + setState(CallSession::State::OutgoingRinging, "Remote ringing"); } } @@ -1948,6 +1949,7 @@ void MediaSessionPrivate::accept (const MediaSessionParams *msp, bool wasRinging updateRemoteSessionIdAndVer(); + if (getStreamsGroup().prepare()){ callAcceptanceDefered = true; return; /* Deferred until completion of ICE gathering */ @@ -2863,6 +2865,30 @@ void MediaSession::setParams (const MediaSessionParams *msp) { } } +void MediaSession::setInputAudioDevice(AudioDevice *audioDevice) { + L_D(); + AudioControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (i) i->setInputDevice(audioDevice); +} + +void MediaSession::setOutputAudioDevice(AudioDevice *audioDevice) { + L_D(); + AudioControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (i) i->setOutputDevice(audioDevice); +} +AudioDevice* MediaSession::getInputAudioDevice() const { + L_D(); + AudioControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (i) return i->getInputDevice(); + return nullptr; +} + +AudioDevice* MediaSession::getOutputAudioDevice() const { + L_D(); + AudioControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (i) return i->getOutputDevice(); + return nullptr; +} LINPHONE_END_NAMESPACE diff --git a/src/conference/session/media-session.h b/src/conference/session/media-session.h index 52f465ead44324e2feec076ed10980cdae6e7ee2..4e887412ee5681ed2c60726bcff59a6568e8e800 100644 --- a/src/conference/session/media-session.h +++ b/src/conference/session/media-session.h @@ -22,6 +22,7 @@ #include "call-session.h" #include "conference/params/media-session-params.h" +#include "call/audio-device/audio-device.h" // ============================================================================= @@ -111,6 +112,11 @@ public: void setNativePreviewWindowId (void *id); void setParams (const MediaSessionParams *msp); void setSpeakerVolumeGain (float value); + + void setInputAudioDevice(AudioDevice *audioDevice); + void setOutputAudioDevice(AudioDevice *audioDevice); + AudioDevice* getInputAudioDevice() const; + AudioDevice* getOutputAudioDevice() const; private: L_DECLARE_PRIVATE(MediaSession); L_DISABLE_COPY(MediaSession); diff --git a/src/conference/session/ms2-streams.h b/src/conference/session/ms2-streams.h index d09c625815e1077a9a31510821eafcd7d0588aec..457d5e046529fb44cce95201fa565151a3278859 100644 --- a/src/conference/session/ms2-streams.h +++ b/src/conference/session/ms2-streams.h @@ -146,6 +146,10 @@ public: virtual void sendDtmf(int dtmf) override; virtual void enableEchoCancellation(bool value) override; virtual bool echoCancellationEnabled()const override; + virtual void setInputDevice(AudioDevice *audioDevice) override; + virtual void setOutputDevice(AudioDevice *audioDevice) override; + virtual AudioDevice* getInputDevice() const override; + virtual AudioDevice* getOutputDevice() const override; virtual MediaStream *getMediaStream()const override; virtual ~MS2AudioStream(); diff --git a/src/conference/session/streams.h b/src/conference/session/streams.h index fabb81f72b7e89785cab4ce4b6f185977a5e8e08..dc3e3f69b39c524f8f94601e8fe49605691e533f 100644 --- a/src/conference/session/streams.h +++ b/src/conference/session/streams.h @@ -26,6 +26,7 @@ #include "port-config.h" #include "call-session.h" #include "media-description-renderer.h" +#include "call/audio-device/audio-device.h" LINPHONE_BEGIN_NAMESPACE @@ -181,6 +182,10 @@ public: virtual void sendDtmf(int dtmf) = 0; virtual void enableEchoCancellation(bool value) = 0; virtual bool echoCancellationEnabled()const = 0; + virtual void setInputDevice(AudioDevice *audioDevice) = 0; + virtual void setOutputDevice(AudioDevice *audioDevice) = 0; + virtual AudioDevice* getInputDevice() const = 0; + virtual AudioDevice* getOutputDevice() const = 0; virtual ~AudioControlInterface() = default; }; diff --git a/src/conference/session/tone-manager.cpp b/src/conference/session/tone-manager.cpp index 54d3747cbf19ea830ea24029f67520c5505f2244..bf2a188f0cbf21545291fc40bfd56c2e0ba7b69a 100644 --- a/src/conference/session/tone-manager.cpp +++ b/src/conference/session/tone-manager.cpp @@ -406,12 +406,25 @@ void ToneManager::doStartRingbackTone(const std::shared_ptr<CallSession> &sessio if (!lc->sound_conf.play_sndcard) return; + MSSndCard *ringCard = lc->sound_conf.lsd_card ? lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard; + std::shared_ptr<LinphonePrivate::Call> call = getCore()->getCurrentCall(); + if (call) { + AudioDevice * audioDevice = call->getOutputAudioDevice(); + + // If the user changed the audio device before the ringback started, the new value will be stored in the call playback card + // It is NULL otherwise + if (audioDevice) { + ringCard = audioDevice->getSoundCard(); + } + } + if (lc->sound_conf.remote_ring) { ms_snd_card_set_stream_type(ringCard, MS_SND_CARD_STREAM_VOICE); lc->ringstream = ring_start(lc->factory, lc->sound_conf.remote_ring, 2000, ringCard); } + } void ToneManager::doStartRingtone(const std::shared_ptr<CallSession> &session) { @@ -467,6 +480,14 @@ void ToneManager::doStopRingbackTone() { lInfo() << "[ToneManager] " << __func__; LinphoneCore *lc = getCore()->getCCore(); if (lc->ringstream) { + MSSndCard *card = ring_stream_get_output_ms_snd_card(lc->ringstream); + + if (card) { + AudioDevice * audioDevice = getCore()->findAudioDeviceMatchingMsSoundCard(card); + if (audioDevice) { + getCore()->getPrivate()->setOutputAudioDevice(audioDevice); + } + } ring_stop(lc->ringstream); lc->ringstream = NULL; } @@ -502,6 +523,18 @@ void ToneManager::doStopRingtone(const std::shared_ptr<CallSession> &session) { } else { LinphoneCore *lc = getCore()->getCCore(); if (linphone_ringtoneplayer_is_started(lc->ringtoneplayer)) { + RingStream * ringStream = linphone_ringtoneplayer_get_stream(lc->ringtoneplayer); + if (ringStream) { + MSSndCard *card = ring_stream_get_output_ms_snd_card(ringStream); + + if (card) { + AudioDevice * audioDevice = getCore()->findAudioDeviceMatchingMsSoundCard(card); + if (audioDevice) { + getCore()->getPrivate()->setOutputAudioDevice(audioDevice); + } + } + } + linphone_ringtoneplayer_stop(lc->ringtoneplayer); } } diff --git a/src/core/core-p.h b/src/core/core-p.h index 068a8159ec64fcff4250aae65c59095f9c12e1ab..534ca7303a6c55fd31eb2947ec057010c14884e1 100644 --- a/src/core/core-p.h +++ b/src/core/core-p.h @@ -30,6 +30,7 @@ #include "auth-info/auth-stack.h" #include "conference/session/tone-manager.h" #include "utils/background-task.h" +#include "call/audio-device/audio-device.h" // ============================================================================= @@ -74,6 +75,9 @@ public: void setCurrentCall (const std::shared_ptr<Call> &call) { currentCall = call; } void setVideoWindowId (bool preview, void *id); + bool setOutputAudioDevice(AudioDevice *audioDevice); + bool setInputAudioDevice(AudioDevice *audioDevice); + void loadChatRooms (); void handleEphemeralMessages (time_t currentTime); void initEphemeralMessages (); @@ -137,6 +141,8 @@ public: void startEphemeralMessageTimer (time_t expireTime); void stopEphemeralMessageTimer (); + void computeAudioDevicesList (); + private: bool isInBackground = false; bool isFriendListSubscriptionEnabled = false; @@ -163,6 +169,7 @@ private: std::list<std::shared_ptr<ChatMessage>> ephemeralMessages; belle_sip_source_t *timer = nullptr; + std::list<AudioDevice *> audioDevices; L_DECLARE_PUBLIC(Core); }; diff --git a/src/core/core.cpp b/src/core/core.cpp index 22fc48cb7de61aa030687136c36473d2aede5b6d..e5789b1e942e212923b5c44f42dffb87501b53a6 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -158,6 +158,11 @@ void CorePrivate::shutdown() { LinphoneFriendList *list = (LinphoneFriendList *) elem->data; linphone_friend_list_enable_subscriptions(list,FALSE); } + + for (auto &audioDevice : audioDevices) { + audioDevice->unref(); + } + audioDevices.clear(); if (toneManager) toneManager->deleteTimer(); @@ -322,6 +327,39 @@ void CorePrivate::stopEphemeralMessageTimer () { } } +bool CorePrivate::setInputAudioDevice(AudioDevice *audioDevice) { + if ((audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Record)) == 0) { + lError() << "Audio device [" << audioDevice << "] doesn't have Record capability"; + return false; + } + + bool applied = false; + if (static_cast<unsigned int>(calls.size()) > 0) { + for (const auto &call : calls) { + call->setInputAudioDevice(audioDevice); + applied = true; + } + } + + return applied; +} + +bool CorePrivate::setOutputAudioDevice(AudioDevice *audioDevice) { + if ((audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Play)) == 0) { + lError() << "Audio device [" << audioDevice << "] doesn't have Play capability"; + return false; + } + + bool applied = false; + if (static_cast<unsigned int>(calls.size()) > 0) { + for (const auto &call : calls) { + call->setOutputAudioDevice(audioDevice); + applied = true; + } + } + + return applied; +} // ============================================================================= Core::Core () : Object(*new CorePrivate) { @@ -562,6 +600,156 @@ bool Core::isFriendListSubscriptionEnabled () const { return d->isFriendListSubscriptionEnabled; } +// --------------------------------------------------------------------------- +// Audio devices. +// --------------------------------------------------------------------------- + +void CorePrivate::computeAudioDevicesList() { + for (auto &audioDevice : audioDevices) { + audioDevice->unref(); + } + audioDevices.clear(); + + MSSndCardManager *snd_card_manager = ms_factory_get_snd_card_manager(getCCore()->factory); + const bctbx_list_t *list = ms_snd_card_manager_get_list(snd_card_manager); + + for (const bctbx_list_t *it = list; it != nullptr; it = bctbx_list_next(it)) { + MSSndCard *card = static_cast<MSSndCard *>(bctbx_list_get_data(it)); + AudioDevice *audioDevice = new AudioDevice(card); + audioDevices.push_back(audioDevice); + } +} + +AudioDevice* Core::findAudioDeviceMatchingMsSoundCard(MSSndCard *soundCard) const { + for (const auto &audioDevice : getExtendedAudioDevices()) { + if (audioDevice->getSoundCard() == soundCard) { + return audioDevice; + } + } + return nullptr; +} + +const list<AudioDevice *> Core::getAudioDevices() const { + std::list<AudioDevice *> audioDevices; + bool micFound = false, speakerFound = false, earpieceFound = false, bluetoothMicFound = false, bluetoothSpeakerFound = false; + + for (const auto &audioDevice : getExtendedAudioDevices()) { + switch (audioDevice->getType()) { + case AudioDevice::Type::Microphone: + if (!micFound) { + micFound = true; + audioDevices.push_back(audioDevice); + } + break; + case AudioDevice::Type::Earpiece: + if (!earpieceFound) { + earpieceFound = true; + audioDevices.push_back(audioDevice); + } + break; + case AudioDevice::Type::Speaker: + if (!speakerFound) { + speakerFound = true; + audioDevices.push_back(audioDevice); + } + break; + case AudioDevice::Type::Bluetooth: + if (!bluetoothMicFound && (audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Record))) { + audioDevices.push_back(audioDevice); + } else if (!bluetoothSpeakerFound && (audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Play))) { + audioDevices.push_back(audioDevice); + } + + // Do not allow to be set to false + // Not setting flags inside if statement in order to handle the case of a bluetooth device that can record and play sound + if (!bluetoothMicFound) bluetoothMicFound = (audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Record)); + if (!bluetoothSpeakerFound) bluetoothSpeakerFound = (audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Play)); + break; + default: + break; + } + if (micFound && speakerFound && earpieceFound && bluetoothMicFound && bluetoothSpeakerFound) break; + } + return audioDevices; +} + +const list<AudioDevice *> Core::getExtendedAudioDevices() const { + L_D(); + return d->audioDevices; +} + +void Core::setInputAudioDevice(AudioDevice *audioDevice) { + + L_D(); + bool success = d->setInputAudioDevice(audioDevice); + + if (success) { + linphone_core_notify_audio_device_changed(L_GET_C_BACK_PTR(getSharedFromThis()), audioDevice->toC()); + } +} + +void Core::setOutputAudioDevice(AudioDevice *audioDevice) { + + L_D(); + bool success = d->setOutputAudioDevice(audioDevice); + + if (success) { + linphone_core_notify_audio_device_changed(L_GET_C_BACK_PTR(getSharedFromThis()), audioDevice->toC()); + } +} + +AudioDevice* Core::getInputAudioDevice() const { + shared_ptr<LinphonePrivate::Call> call = getCurrentCall(); + if (call) { + return call->getInputAudioDevice(); + } + + for (const auto &call : getCalls()) { + return call->getInputAudioDevice(); + } + + return nullptr; +} + +AudioDevice* Core::getOutputAudioDevice() const { + shared_ptr<LinphonePrivate::Call> call = getCurrentCall(); + if (call) { + return call->getOutputAudioDevice(); + } + + for (const auto &call : getCalls()) { + return call->getOutputAudioDevice(); + } + + return nullptr; +} + +void Core::setDefaultInputAudioDevice(AudioDevice *audioDevice) { + if ((audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Record)) == 0) { + lError() << "Audio device [" << audioDevice << "] doesn't have Record capability"; + return; + } + linphone_core_set_capture_device(getCCore(), audioDevice->getId().c_str()); +} + +void Core::setDefaultOutputAudioDevice(AudioDevice *audioDevice) { + if ((audioDevice->getCapabilities() & static_cast<int>(AudioDevice::Capabilities::Play)) == 0) { + lError() << "Audio device [" << audioDevice << "] doesn't have Play capability"; + return; + } + linphone_core_set_playback_device(getCCore(), audioDevice->getId().c_str()); +} + +AudioDevice* Core::getDefaultInputAudioDevice() const { + MSSndCard *card = getCCore()->sound_conf.capt_sndcard; + return findAudioDeviceMatchingMsSoundCard(card); +} + +AudioDevice* Core::getDefaultOutputAudioDevice() const { + MSSndCard *card = getCCore()->sound_conf.play_sndcard; + return findAudioDeviceMatchingMsSoundCard(card); +} + // ----------------------------------------------------------------------------- // Misc. // ----------------------------------------------------------------------------- diff --git a/src/core/core.h b/src/core/core.h index 53b3b923db36bb87090093db5bbfb374fba926f6..3558b0faff16ac9df25148f50f2ba5f7514d9150 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -26,6 +26,7 @@ #include "object/object.h" #include "linphone/types.h" +#include "call/audio-device/audio-device.h" // ============================================================================= @@ -182,6 +183,24 @@ public: void enableFriendListSubscription (bool enable); bool isFriendListSubscriptionEnabled () const; + // --------------------------------------------------------------------------- + // Audio devices. + // --------------------------------------------------------------------------- + + AudioDevice *findAudioDeviceMatchingMsSoundCard(MSSndCard *soundCard) const; + const std::list<AudioDevice *> getAudioDevices() const; + const std::list<AudioDevice *> getExtendedAudioDevices() const; + + void setInputAudioDevice(AudioDevice *audioDevice); + void setOutputAudioDevice(AudioDevice *audioDevice); + AudioDevice *getInputAudioDevice() const; + AudioDevice *getOutputAudioDevice() const; + + void setDefaultInputAudioDevice(AudioDevice *audioDevice); + void setDefaultOutputAudioDevice(AudioDevice *audioDevice); + AudioDevice* getDefaultInputAudioDevice() const; + AudioDevice* getDefaultOutputAudioDevice() const; + // --------------------------------------------------------------------------- // Misc. // --------------------------------------------------------------------------- diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c index 681c2adb45b21d08b39d8c97d69f6a9b988dbe11..7a2d7cf43c970a25555faf7170a0bdcee4ba3f6b 100644 --- a/tester/call_single_tester.c +++ b/tester/call_single_tester.c @@ -275,6 +275,598 @@ static void simple_call_with_multipart_invite_body(void) { simple_call_base(FALSE, FALSE, TRUE); } +static LinphoneAudioDevice * unregister_device(bool_t enable, LinphoneCoreManager* mgr, LinphoneAudioDevice *current_dev, MSSndCardDesc *card_desc) { + + // Unref current_dev + linphone_audio_device_unref(current_dev); + + if (enable) { + MSFactory *factory = linphone_core_get_ms_factory(mgr->lc); + MSSndCardManager *sndcard_manager = ms_factory_get_snd_card_manager(factory); + + // Check that description is in the card manager + BC_ASSERT_PTR_NOT_NULL(bctbx_list_find(sndcard_manager->descs, card_desc)); + + // Unregister card + ms_snd_card_manager_unregister_desc(sndcard_manager, card_desc); + linphone_core_reload_sound_devices(mgr->lc); + + // Get next device at the head of the list + // Use linphone_core_get_extended_audio_devices instead of linphone_core_get_audio_devices because we added 2 BT devices, therefore we want the raw list + // In fact, linphone_core_get_audio_devices returns only 1 device per type + bctbx_list_t *audio_devices = linphone_core_get_extended_audio_devices(mgr->lc); + LinphoneAudioDevice *next_dev = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices); + BC_ASSERT_PTR_NOT_NULL(next_dev); + linphone_audio_device_ref(next_dev); + + // Unref cards + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + return next_dev; + } + + return linphone_audio_device_ref(current_dev); + +} + +static void call_with_disconnecting_device_base(bool_t before_ringback, bool_t during_ringback, bool_t during_call) { + bctbx_list_t* lcs; + // Marie is the caller + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + + // load audio devices and get initial number of cards + linphone_core_reload_sound_devices(marie->lc); + bctbx_list_t *audio_devices = linphone_core_get_extended_audio_devices(marie->lc); + int native_audio_devices_count = bctbx_list_size(audio_devices); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + MSFactory *factory = linphone_core_get_ms_factory(marie->lc); + // Adding 1 devices to Marie' sound card manager: + // - dummy_test_snd_card_desc + MSSndCardManager *sndcard_manager = ms_factory_get_snd_card_manager(factory); + + // This devices are prepended to the list of so that they can be easily accessed later + ms_snd_card_manager_register_desc(sndcard_manager, &dummy_test_snd_card_desc); + linphone_core_reload_sound_devices(marie->lc); + + // Choose Marie's audio devices + // Use linphone_core_get_extended_audio_devices instead of linphone_core_get_audio_devices because we added 2 BT devices, therefore we want the raw list + // In fact, linphone_core_get_audio_devices returns only 1 device per type + audio_devices = linphone_core_get_extended_audio_devices(marie->lc); + int audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, (native_audio_devices_count + 1), int, "%d"); + + // As new devices are prepended, they can be easily accessed and we do not run the risk of gettting a device whose type is Unknown + // device at the head of the list + LinphoneAudioDevice *current_dev = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices); + BC_ASSERT_PTR_NOT_NULL(current_dev); + linphone_audio_device_ref(current_dev); + + // Unref cards + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + lcs=bctbx_list_append(NULL,marie->lc); + + // Pauline is offline + LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + linphone_core_set_network_reachable(pauline->lc,FALSE); + + lcs=bctbx_list_append(lcs,pauline->lc); + + // Set audio device to start with a known situation + linphone_core_set_default_input_audio_device(marie->lc, current_dev); + linphone_core_set_default_output_audio_device(marie->lc, current_dev); + + LinphoneCall * marie_call = linphone_core_invite_address(marie->lc,pauline->identity); + BC_ASSERT_PTR_NOT_NULL(marie_call); + + current_dev = unregister_device(before_ringback, marie, current_dev, &dummy_test_snd_card_desc); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + // Pauline is now online - ringback can start + linphone_core_set_network_reachable(pauline->lc,TRUE); + + // Pauline shall receive the call immediately + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallIncomingReceived,1,5000)); + + LinphoneCall * pauline_call = linphone_core_get_current_call(pauline->lc); + BC_ASSERT_PTR_NOT_NULL(pauline_call); + linphone_call_ref(pauline_call); + + // Marie should hear ringback as well + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallOutgoingRinging,1,5000)); + + // After the ringback startx, the default device is expected to be used + linphone_audio_device_unref(current_dev); + audio_devices = linphone_core_get_extended_audio_devices(marie->lc); + current_dev = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices); + linphone_audio_device_ref(current_dev); + + // Unref cards + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + linphone_core_set_output_audio_device(marie->lc, current_dev); + + // Check Marie's output device + BC_ASSERT_PTR_EQUAL(linphone_core_get_output_audio_device(marie->lc), current_dev); + + current_dev = unregister_device(during_ringback, marie, current_dev, &dummy_test_snd_card_desc); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + // Take call - ringing ends + linphone_call_accept(pauline_call); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + + // Check Marie's output device + BC_ASSERT_PTR_EQUAL(linphone_core_get_output_audio_device(marie->lc), current_dev); + + // Unref current device as we are deletig its card + current_dev = unregister_device(during_call, marie, current_dev, &dummy_test_snd_card_desc); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + // End call + linphone_call_terminate(pauline_call); + linphone_call_unref(pauline_call); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallEnd, 1)); + + BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneCoreLastCallEnded, 1, int, "%d"); + BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCoreLastCallEnded, 1, int, "%d"); + + // After call, unref the sound card + linphone_audio_device_unref(current_dev); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(marie); +} + +static void call_with_disconnecting_device_before_ringback(void) { + call_with_disconnecting_device_base(TRUE, FALSE, FALSE); +} + +static void call_with_disconnecting_device_during_ringback(void) { + call_with_disconnecting_device_base(FALSE, TRUE, FALSE); +} + +static void call_with_disconnecting_device_after_ringback(void) { + call_with_disconnecting_device_base(FALSE, FALSE, TRUE); +} + +static LinphoneAudioDevice * change_device(bool_t enable, LinphoneCoreManager* mgr, LinphoneAudioDevice *current_dev, LinphoneAudioDevice *dev0, LinphoneAudioDevice *dev1) { + + // Unref current_dev + linphone_audio_device_unref(current_dev); + + if (enable) { + LinphoneAudioDevice *next_dev = NULL; + + if (current_dev == dev0) { + next_dev = dev1; + } else { + next_dev = dev0; + } + + BC_ASSERT_PTR_NOT_NULL(next_dev); + next_dev = linphone_audio_device_ref(next_dev); + + int noDevChanges = mgr->stat.number_of_LinphoneCoreAudioDeviceChanged; + // Change output audio device + linphone_core_set_output_audio_device(mgr->lc, next_dev); + BC_ASSERT_EQUAL(mgr->stat.number_of_LinphoneCoreAudioDeviceChanged, (noDevChanges + 1), int, "%d"); + BC_ASSERT_PTR_EQUAL(linphone_core_get_output_audio_device(mgr->lc), next_dev); + + return next_dev; + } + + return linphone_audio_device_ref(current_dev); +} + +static void simple_call_with_audio_device_change_base(bool_t before_ringback, bool_t during_ringback, bool_t during_call) { + bctbx_list_t* lcs; + // Marie is the caller + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + + // load audio devices and get initial number of cards + linphone_core_reload_sound_devices(marie->lc); + bctbx_list_t *audio_devices = linphone_core_get_extended_audio_devices(marie->lc); + int native_audio_devices_count = bctbx_list_size(audio_devices); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + MSFactory *factory = linphone_core_get_ms_factory(marie->lc); + // Adding 2 devices to Marie' sound card manager: + // - dummy_test_snd_card_desc + // - dummy2_test_snd_card_desc + MSSndCardManager *sndcard_manager = ms_factory_get_snd_card_manager(factory); + + // This devices are prepended to the list of so that they can be easily accessed later + ms_snd_card_manager_register_desc(sndcard_manager, &dummy_test_snd_card_desc); + ms_snd_card_manager_register_desc(sndcard_manager, &dummy2_test_snd_card_desc); + linphone_core_reload_sound_devices(marie->lc); + + // Choose Marie's audio devices + // Use linphone_core_get_extended_audio_devices instead of linphone_core_get_audio_devices because we added 2 BT devices, therefore we want the raw list + // In fact, linphone_core_get_audio_devices returns only 1 device per type + audio_devices = linphone_core_get_extended_audio_devices(marie->lc); + int audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, (native_audio_devices_count + 2), int, "%d"); + + // As new devices are prepended, they can be easily accessed and we do not run the risk of gettting a device whose type is Unknown + // device at the head of the list + LinphoneAudioDevice *dev0 = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices); + BC_ASSERT_PTR_NOT_NULL(dev0); + linphone_audio_device_ref(dev0); + + // 2nd device in the list + LinphoneAudioDevice *dev1 = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices->next); + BC_ASSERT_PTR_NOT_NULL(dev1); + linphone_audio_device_ref(dev1); + + // At the start, choose default device i.e. dev0 + LinphoneAudioDevice *current_dev = dev0; + BC_ASSERT_PTR_NOT_NULL(current_dev); + linphone_audio_device_ref(current_dev); + + // Unref cards + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + lcs=bctbx_list_append(NULL,marie->lc); + + // Pauline is offline + LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + linphone_core_set_network_reachable(pauline->lc,FALSE); + + lcs=bctbx_list_append(lcs,pauline->lc); + + // Set audio device to start with a known situation + linphone_core_set_default_input_audio_device(marie->lc, current_dev); + linphone_core_set_default_output_audio_device(marie->lc, current_dev); + + LinphoneCall * marie_call = linphone_core_invite_address(marie->lc,pauline->identity); + BC_ASSERT_PTR_NOT_NULL(marie_call); + + current_dev = change_device(before_ringback, marie, current_dev, dev0, dev1); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + // Pauline is now online - ringback can start + linphone_core_set_network_reachable(pauline->lc,TRUE); + + // Pauline shall receive the call immediately + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallIncomingReceived,1,5000)); + + LinphoneCall * pauline_call = linphone_core_get_current_call(pauline->lc); + BC_ASSERT_PTR_NOT_NULL(pauline_call); + linphone_call_ref(pauline_call); + + // Marie should hear ringback as well + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallOutgoingRinging,1,5000)); + // Check Marie's output device + BC_ASSERT_PTR_EQUAL(linphone_core_get_output_audio_device(marie->lc), current_dev); + + current_dev = change_device(during_ringback, marie, current_dev, dev0, dev1); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + // Take call - ringing ends + linphone_call_accept(pauline_call); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + + // Check Marie's output device + BC_ASSERT_PTR_EQUAL(linphone_core_get_output_audio_device(marie->lc), current_dev); + + current_dev = change_device(during_call, marie, current_dev, dev0, dev1); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + // End call + linphone_call_terminate(pauline_call); + linphone_call_unref(pauline_call); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallEnd, 1)); + + BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneCoreLastCallEnded, 1, int, "%d"); + BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCoreLastCallEnded, 1, int, "%d"); + + // After call, unref the sound card + linphone_audio_device_unref(dev0); + linphone_audio_device_unref(dev1); + linphone_audio_device_unref(current_dev); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(marie); +} + +static void simple_call_with_audio_device_change_before_ringback(void) { + simple_call_with_audio_device_change_base(TRUE, FALSE, FALSE); +} + +static void simple_call_with_audio_device_change_during_ringback(void) { + simple_call_with_audio_device_change_base(FALSE, TRUE, FALSE); +} + +static void simple_call_with_audio_device_change_after_ringback(void) { + simple_call_with_audio_device_change_base(FALSE, FALSE, TRUE); +} + +static void simple_call_with_audio_device_change_pingpong(void) { + simple_call_with_audio_device_change_base(TRUE, TRUE, TRUE); +} + +static LinphoneAudioDevice* pause_call_changing_device(bool_t enable, LinphoneCoreManager* mgr_pausing, LinphoneCoreManager* mgr_paused, LinphoneCoreManager* mgr_change_device, LinphoneAudioDevice *current_dev, LinphoneAudioDevice *dev0, LinphoneAudioDevice *dev1) { + + LinphoneCall * call = linphone_core_get_current_call(mgr_pausing->lc); + BC_ASSERT_PTR_NOT_NULL(call); + + linphone_call_pause(call); + BC_ASSERT_TRUE(wait_for(mgr_change_device->lc,mgr_paused->lc,&mgr_pausing->stat.number_of_LinphoneCallPausing,1)); + + BC_ASSERT_TRUE(wait_for(mgr_pausing->lc,mgr_paused->lc,&mgr_paused->stat.number_of_LinphoneCallPausedByRemote,1)); + BC_ASSERT_TRUE(wait_for(mgr_pausing->lc,mgr_paused->lc,&mgr_pausing->stat.number_of_LinphoneCallPaused,1)); + + LinphoneAudioDevice *next_dev = change_device(enable, mgr_change_device, current_dev, dev0, dev1); + + //stay in pause a little while in order to generate traffic + wait_for_until(mgr_pausing->lc, mgr_paused->lc, NULL, 5, 2000); + + linphone_call_resume(call); + + BC_ASSERT_TRUE(wait_for(mgr_pausing->lc,mgr_paused->lc,&mgr_pausing->stat.number_of_LinphoneCallStreamsRunning,2)); + BC_ASSERT_TRUE(wait_for(mgr_pausing->lc,mgr_paused->lc,&mgr_paused->stat.number_of_LinphoneCallStreamsRunning,2)); + + // Check Marie's output device + BC_ASSERT_PTR_EQUAL(linphone_core_get_output_audio_device(mgr_change_device->lc), next_dev); + + return next_dev; + +} + +static void simple_call_with_audio_device_change_during_call_pause_base(bool_t callee, bool_t caller) { + bctbx_list_t* lcs; + // Marie is the caller + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + + // load audio devices and get initial number of cards + linphone_core_reload_sound_devices(marie->lc); + bctbx_list_t *audio_devices = linphone_core_get_extended_audio_devices(marie->lc); + int native_audio_devices_count = bctbx_list_size(audio_devices); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + MSFactory *marie_factory = linphone_core_get_ms_factory(marie->lc); + // Adding 2 devices to Marie' sound card manager: + // - dummy_test_snd_card_desc + // - dummy2_test_snd_card_desc + MSSndCardManager *marie_sndcard_manager = ms_factory_get_snd_card_manager(marie_factory); + + // This devices are prepended to the list of so that they can be easily accessed later + ms_snd_card_manager_register_desc(marie_sndcard_manager, &dummy_test_snd_card_desc); + ms_snd_card_manager_register_desc(marie_sndcard_manager, &dummy2_test_snd_card_desc); + linphone_core_reload_sound_devices(marie->lc); + + // Choose Marie's audio devices + // Use linphone_core_get_extended_audio_devices instead of linphone_core_get_audio_devices because we added 2 BT devices, therefore we want the raw list + // In fact, linphone_core_get_audio_devices returns only 1 device per type + audio_devices = linphone_core_get_extended_audio_devices(marie->lc); + int audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, (native_audio_devices_count + 2), int, "%d"); + + // As new devices are prepended, they can be easily accessed and we do not run the risk of gettting a device whose type is Unknown + // device at the head of the list + LinphoneAudioDevice *marie_dev0 = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices); + BC_ASSERT_PTR_NOT_NULL(marie_dev0); + marie_dev0 = linphone_audio_device_ref(marie_dev0); + + // 2nd device in the list + LinphoneAudioDevice *marie_dev1 = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices->next); + BC_ASSERT_PTR_NOT_NULL(marie_dev1); + marie_dev1 = linphone_audio_device_ref(marie_dev1); + + // At the start, choose default device for marie i.e. marie_dev0 + LinphoneAudioDevice *marie_current_dev = marie_dev0; + BC_ASSERT_PTR_NOT_NULL(marie_current_dev); + marie_current_dev = linphone_audio_device_ref(marie_current_dev); + + // Unref cards + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + lcs=bctbx_list_append(NULL,marie->lc); + + // Set audio device to start with a known situation + linphone_core_set_default_input_audio_device(marie->lc, marie_current_dev); + linphone_core_set_default_output_audio_device(marie->lc, marie_current_dev); + + // Pauline is online - ringback can start immediately + LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + linphone_core_set_network_reachable(pauline->lc,TRUE); + + MSFactory *pauline_factory = linphone_core_get_ms_factory(pauline->lc); + // Adding 2 devices to Marie' sound card manager: + // - dummy_test_snd_card_desc + // - dummy2_test_snd_card_desc + MSSndCardManager *pauline_sndcard_manager = ms_factory_get_snd_card_manager(pauline_factory); + + // This devices are prepended to the list of so that they can be easily accessed later + ms_snd_card_manager_register_desc(pauline_sndcard_manager, &dummy2_test_snd_card_desc); + ms_snd_card_manager_register_desc(pauline_sndcard_manager, &dummy_test_snd_card_desc); + linphone_core_reload_sound_devices(pauline->lc); + + // Choose Marie's audio devices + // Use linphone_core_get_extended_audio_devices instead of linphone_core_get_audio_devices because we added 2 BT devices, therefore we want the raw list + // In fact, linphone_core_get_audio_devices returns only 1 device per type + audio_devices = linphone_core_get_extended_audio_devices(pauline->lc); + audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, (native_audio_devices_count + 2), int, "%d"); + + // As new devices are prepended, they can be easily accessed and we do not run the risk of gettting a device whose type is Unknown + // device at the head of the list + LinphoneAudioDevice *pauline_dev0 = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices); + BC_ASSERT_PTR_NOT_NULL(pauline_dev0); + pauline_dev0 = linphone_audio_device_ref(pauline_dev0); + + // 2nd device in the list + LinphoneAudioDevice *pauline_dev1 = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices->next); + BC_ASSERT_PTR_NOT_NULL(pauline_dev1); + pauline_dev1 = linphone_audio_device_ref(pauline_dev1); + + // At the start, choose default device for pauline i.e. pauline_dev0 + LinphoneAudioDevice *pauline_current_dev = pauline_dev0; + BC_ASSERT_PTR_NOT_NULL(pauline_current_dev); + pauline_current_dev = linphone_audio_device_ref(pauline_current_dev); + + // Unref cards + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + lcs=bctbx_list_append(lcs,pauline->lc); + + // Set audio device to start with a known situation + linphone_core_set_default_input_audio_device(pauline->lc, pauline_current_dev); + linphone_core_set_default_output_audio_device(pauline->lc, pauline_current_dev); + + LinphoneCall * marie_call = linphone_core_invite_address(marie->lc,pauline->identity); + BC_ASSERT_PTR_NOT_NULL(marie_call); + + // Pauline shall receive the call immediately + BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallIncomingReceived,1,5000)); + + LinphoneCall * pauline_call = linphone_core_get_current_call(pauline->lc); + BC_ASSERT_PTR_NOT_NULL(pauline_call); + + // Marie should hear ringback as well + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallOutgoingRinging,1,5000)); + // Check Marie's output device + BC_ASSERT_PTR_EQUAL(linphone_core_get_output_audio_device(marie->lc), marie_current_dev); + + // Take call - ringing ends + linphone_call_accept(pauline_call); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + pauline_current_dev = pause_call_changing_device(callee, pauline, marie, pauline, pauline_current_dev, pauline_dev0, pauline_dev1); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + marie_current_dev = pause_call_changing_device(callee, pauline, marie, marie, marie_current_dev, marie_dev0, marie_dev1); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + +//Line commented because it makes the test to SEGFAULT + pauline_current_dev = pause_call_changing_device(caller, marie, pauline, pauline, pauline_current_dev, pauline_dev0, pauline_dev1); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + +//Line commented because it makes the test to SEGFAULT + marie_current_dev = pause_call_changing_device(caller, marie, pauline, marie, marie_current_dev, marie_dev0, marie_dev1); + + //stay in pause a little while in order to generate traffic + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + // End call + linphone_call_terminate(pauline_call); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallEnd, 1)); + + BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneCoreLastCallEnded, 1, int, "%d"); + BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCoreLastCallEnded, 1, int, "%d"); + + // After call, unref the sound card + linphone_audio_device_unref(marie_dev0); + linphone_audio_device_unref(marie_dev1); + linphone_audio_device_unref(marie_current_dev); + linphone_core_manager_destroy(marie); + linphone_audio_device_unref(pauline_dev0); + linphone_audio_device_unref(pauline_dev1); + linphone_audio_device_unref(pauline_current_dev); + linphone_core_manager_destroy(pauline); +} + +static void simple_call_with_audio_device_change_during_call_pause_callee(void) { + simple_call_with_audio_device_change_during_call_pause_base(TRUE, FALSE); +} + +static void simple_call_with_audio_device_change_during_call_pause_caller(void) { + simple_call_with_audio_device_change_during_call_pause_base(FALSE, TRUE); +} + +static void simple_call_with_audio_device_change_during_call_pause_caller_callee(void) { + simple_call_with_audio_device_change_during_call_pause_base(TRUE, TRUE); +} + +static void simple_call_with_audio_devices_reload(void) { + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + + BC_ASSERT_PTR_NULL(linphone_core_get_output_audio_device(marie->lc)); + BC_ASSERT_PTR_NULL(linphone_core_get_input_audio_device(marie->lc)); + BC_ASSERT_PTR_NULL(linphone_core_get_output_audio_device(pauline->lc)); + BC_ASSERT_PTR_NULL(linphone_core_get_input_audio_device(pauline->lc)); + + BC_ASSERT_TRUE(call(marie, pauline)); + + BC_ASSERT_PTR_NOT_NULL(linphone_core_get_output_audio_device(marie->lc)); + BC_ASSERT_PTR_NOT_NULL(linphone_core_get_input_audio_device(marie->lc)); + BC_ASSERT_PTR_NOT_NULL(linphone_core_get_output_audio_device(pauline->lc)); + // Pauline is using a file player as input so no sound card + BC_ASSERT_PTR_NULL(linphone_core_get_input_audio_device(pauline->lc)); + + LinphoneCall *marie_call = linphone_core_get_current_call(marie->lc); + BC_ASSERT_PTR_NOT_NULL(linphone_call_get_output_audio_device(marie_call)); + BC_ASSERT_PTR_NOT_NULL(linphone_call_get_input_audio_device(marie_call)); + + LinphoneCall *pauline_call = linphone_core_get_current_call(pauline->lc); + BC_ASSERT_PTR_NOT_NULL(linphone_call_get_output_audio_device(pauline_call)); + // Pauline is using a file player as input so no sound card + BC_ASSERT_PTR_NULL(linphone_call_get_input_audio_device(pauline_call)); + + MSFactory *factory = linphone_core_get_ms_factory(marie->lc); + MSSndCardManager *sndcard_manager = ms_factory_get_snd_card_manager(factory); + ms_snd_card_manager_register_desc(sndcard_manager, &dummy_test_snd_card_desc); + linphone_core_reload_sound_devices(marie->lc); + BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneCoreAudioDevicesListUpdated, 1, int, "%d"); + + bctbx_list_t *audio_devices = linphone_core_get_audio_devices(marie->lc); + BC_ASSERT_EQUAL(bctbx_list_size(audio_devices), 1, int, "%d"); + LinphoneAudioDevice *audio_device = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices); + BC_ASSERT_PTR_NOT_NULL(audio_device); + linphone_audio_device_ref(audio_device); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + linphone_core_set_output_audio_device(marie->lc, audio_device); + BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneCoreAudioDeviceChanged, 1, int, "%d"); + BC_ASSERT_PTR_EQUAL(linphone_core_get_output_audio_device(marie->lc), audio_device); + linphone_core_set_input_audio_device(marie->lc, audio_device); + BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneCoreAudioDeviceChanged, 2, int, "%d"); + BC_ASSERT_PTR_EQUAL(linphone_core_get_input_audio_device(marie->lc), audio_device); + + end_call(marie, pauline); + + BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneCoreLastCallEnded, 1, int, "%d"); + BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCoreLastCallEnded, 1, int, "%d"); + + linphone_audio_device_unref(audio_device); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(marie); +} + /*This test is added to reproduce a crash when a call is failed synchronously*/ static void simple_call_with_no_sip_transport(void){ LinphoneCoreManager* marie; @@ -461,6 +1053,8 @@ static void _direct_call_well_known_port(int iptype){ linphone_core_enable_ipv6(marie->lc,FALSE); linphone_core_enable_ipv6(pauline->lc,FALSE); } + BC_ASSERT_PTR_NOT_NULL(pauline_dest); + if (pauline_dest == NULL) goto end; linphone_core_set_default_proxy_config(marie->lc,NULL); linphone_core_set_default_proxy_config(pauline->lc, NULL); @@ -478,11 +1072,12 @@ static void _direct_call_well_known_port(int iptype){ BC_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,1)); BC_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,1)); + linphone_address_unref(pauline_dest); liblinphone_tester_check_rtcp(marie,pauline); end_call(marie,pauline); +end: linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); - linphone_address_unref(pauline_dest); } @@ -5186,6 +5781,17 @@ test_t call_tests[] = { TEST_NO_TAG("Simple call with UDP", simple_call_with_udp), TEST_NO_TAG("Simple call without soundcard", simple_call_without_soundcard), TEST_NO_TAG("Simple call with multipart INVITE body", simple_call_with_multipart_invite_body), + TEST_NO_TAG("Simple call with audio devices reload", simple_call_with_audio_devices_reload), + TEST_NO_TAG("Call with disconnecting device before ringback", call_with_disconnecting_device_before_ringback), + TEST_NO_TAG("Call with disconnecting device during ringback", call_with_disconnecting_device_during_ringback), + TEST_NO_TAG("Call with disconnecting device after ringback", call_with_disconnecting_device_after_ringback), + TEST_NO_TAG("Simple call with audio device change before ringback", simple_call_with_audio_device_change_before_ringback), + TEST_NO_TAG("Simple call with audio device change during ringback", simple_call_with_audio_device_change_during_ringback), + TEST_NO_TAG("Simple call with audio device change after ringback", simple_call_with_audio_device_change_after_ringback), + TEST_NO_TAG("Simple call with audio device change ping-pong", simple_call_with_audio_device_change_pingpong), + TEST_NO_TAG("Simple call with audio device change during call pause callee", simple_call_with_audio_device_change_during_call_pause_callee), + TEST_NO_TAG("Simple call with audio device change during call pause caller", simple_call_with_audio_device_change_during_call_pause_caller), + TEST_NO_TAG("Simple call with audio device change during call pause both parties", simple_call_with_audio_device_change_during_call_pause_caller_callee), TEST_ONE_TAG("Call terminated automatically by linphone_core_destroy", automatic_call_termination, "LeaksMemory"), TEST_NO_TAG("Call with http proxy", call_with_http_proxy), TEST_NO_TAG("Call with timed-out bye", call_with_timed_out_bye), diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index cb507d5c3d276c65ab1cb45d0d3f0cadec74686c..d1f3cd85a9df805656b78998d64ee09f52e41b34 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -343,7 +343,9 @@ typedef struct _stats { int number_of_LinphoneGlobalConfiguring; int number_of_LinphoneCoreFirstCallStarted; - int number_of_LinphoneCoreLastCallEnded; + int number_of_LinphoneCoreLastCallEnded; + int number_of_LinphoneCoreAudioDeviceChanged; + int number_of_LinphoneCoreAudioDevicesListUpdated; }stats; @@ -439,6 +441,8 @@ void call_stats_updated(LinphoneCore *lc, LinphoneCall *call, const LinphoneCall void global_state_changed(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message); void first_call_started(LinphoneCore *lc); void last_call_ended(LinphoneCore *lc); +void audio_device_changed(LinphoneCore *lc, LinphoneAudioDevice *device); +void audio_devices_list_updated(LinphoneCore *lc); LinphoneAddress * create_linphone_address(const char * domain); LinphoneAddress * create_linphone_address_for_algo(const char * domain, const char * username); @@ -550,6 +554,12 @@ const char *liblinphone_tester_get_empty_rc(void); int liblinphone_tester_copy_file(const char *from, const char *to); char * generate_random_e164_phone_from_dial_plan(const LinphoneDialPlan *dialPlan); +extern MSSndCardDesc dummy_test_snd_card_desc; +#define DUMMY_TEST_SOUNDCARD "dummy test sound card" + +extern MSSndCardDesc dummy2_test_snd_card_desc; +#define DUMMY2_TEST_SOUNDCARD "dummy2 test sound card" + #ifdef __cplusplus }; #endif diff --git a/tester/setup_tester.c b/tester/setup_tester.c index 1404bc2042e63bdd6c029345bc2820b9f8fb05c1..d4beb3040d8d8c4575cb67ffe78d15f2f5024174 100644 --- a/tester/setup_tester.c +++ b/tester/setup_tester.c @@ -1708,6 +1708,163 @@ static void dial_plan(void) { } bctbx_list_free_with_data(dial_plans, (bctbx_list_free_func)linphone_dial_plan_unref); } + +static void audio_devices(void) { + LinphoneCoreManager* manager = linphone_core_manager_new("marie_rc"); + LinphoneCore *core = manager->lc; + + bctbx_list_t *sound_devices = linphone_core_get_sound_devices_list(core); + int sound_devices_count = bctbx_list_size(sound_devices); + BC_ASSERT_GREATER_STRICT(sound_devices_count, 0, int, "%d"); + bctbx_list_free(sound_devices); + + // Check extended audio devices list matches legacy sound devices list + bctbx_list_t *audio_devices = linphone_core_get_extended_audio_devices(core); + int audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, sound_devices_count, int, "%d"); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + // Check legacy sound card selection matches new audio devices API + const char *capture_device = linphone_core_get_capture_device(core); + BC_ASSERT_PTR_NOT_NULL(capture_device); + if (capture_device) { + const LinphoneAudioDevice *input_device = linphone_core_get_default_input_audio_device(core); + BC_ASSERT_PTR_NOT_NULL(input_device); + if (input_device) { + BC_ASSERT_STRING_EQUAL(linphone_audio_device_get_id(input_device), capture_device); + } + } + + // Check legacy sound card selection matches new audio devices API + const char *playback_device = linphone_core_get_playback_device(core); + BC_ASSERT_PTR_NOT_NULL(playback_device); + if (playback_device) { + const LinphoneAudioDevice *output_device = linphone_core_get_default_output_audio_device(core); + BC_ASSERT_PTR_NOT_NULL(output_device); + if (output_device) { + BC_ASSERT_STRING_EQUAL(linphone_audio_device_get_id(output_device), playback_device); + } + } + + // We are not in call so there is no current input audio device + BC_ASSERT_PTR_NULL(linphone_core_get_input_audio_device(core)); + BC_ASSERT_PTR_NULL(linphone_core_get_output_audio_device(core)); + + // Check that devices list is empty as the current one type is UNKNOWN + audio_devices = linphone_core_get_audio_devices(core); + audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, 0, int, "%d"); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + // Let's add a new sound card and check it appears correctly in audio devices list + MSFactory *factory = linphone_core_get_ms_factory(core); + MSSndCardManager *sndcard_manager = ms_factory_get_snd_card_manager(factory); + ms_snd_card_manager_register_desc(sndcard_manager, &dummy_test_snd_card_desc); + linphone_core_reload_sound_devices(core); + BC_ASSERT_EQUAL(manager->stat.number_of_LinphoneCoreAudioDevicesListUpdated, 1, int, "%d"); + + audio_devices = linphone_core_get_extended_audio_devices(core); + audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, sound_devices_count + 1, int, "%d"); + LinphoneAudioDevice *audio_device = (LinphoneAudioDevice *)bctbx_list_get_data(audio_devices); + BC_ASSERT_PTR_NOT_NULL(audio_device); + if (!audio_device) { + goto end; + } + + // Check the Audio Device object has correct values + linphone_audio_device_ref(audio_device); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + BC_ASSERT_EQUAL(linphone_audio_device_get_type(audio_device), LinphoneAudioDeviceTypeBluetooth, int, "%d"); + BC_ASSERT_TRUE(linphone_audio_device_has_capability(audio_device, LinphoneAudioDeviceCapabilityPlay)); + BC_ASSERT_TRUE(linphone_audio_device_has_capability(audio_device, LinphoneAudioDeviceCapabilityRecord)); + BC_ASSERT_STRING_EQUAL(linphone_audio_device_get_device_name(audio_device), DUMMY_TEST_SOUNDCARD); + + // Check that device is in the audio devices list + audio_devices = linphone_core_get_audio_devices(core); + audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, 1, int, "%d"); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + // Check that we can change the default audio device + const LinphoneAudioDevice *input_device = linphone_core_get_default_input_audio_device(core); + BC_ASSERT_PTR_NOT_NULL(input_device); + if (input_device) { + BC_ASSERT_PTR_NOT_EQUAL(audio_device, input_device); + } + linphone_core_set_default_input_audio_device(core, audio_device); + input_device = linphone_core_get_default_input_audio_device(core); + BC_ASSERT_PTR_NOT_NULL(input_device); + if (input_device) { + BC_ASSERT_PTR_EQUAL(audio_device, input_device); + } + + const LinphoneAudioDevice *output_device = linphone_core_get_default_output_audio_device(core); + BC_ASSERT_PTR_NOT_NULL(output_device); + if (output_device) { + BC_ASSERT_PTR_NOT_EQUAL(audio_device, output_device); + } + linphone_core_set_default_output_audio_device(core, audio_device); + output_device = linphone_core_get_default_output_audio_device(core); + BC_ASSERT_PTR_NOT_NULL(output_device); + if (output_device) { + BC_ASSERT_PTR_EQUAL(audio_device, output_device); + } + + // We are not in call so this should do nothing + linphone_core_set_input_audio_device(core, audio_device); + BC_ASSERT_EQUAL(manager->stat.number_of_LinphoneCoreAudioDeviceChanged, 0, int, "%d"); + linphone_core_set_output_audio_device(core, audio_device); + BC_ASSERT_EQUAL(manager->stat.number_of_LinphoneCoreAudioDeviceChanged, 0, int, "%d"); + + // Let's add another bluetooth sound card + ms_snd_card_manager_register_desc(sndcard_manager, &dummy2_test_snd_card_desc); + linphone_core_reload_sound_devices(core); + BC_ASSERT_EQUAL(manager->stat.number_of_LinphoneCoreAudioDevicesListUpdated, 2, int, "%d"); + + // Check that device is in the extended audio devices list + audio_devices = linphone_core_get_extended_audio_devices(core); + audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, sound_devices_count + 2, int, "%d"); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + // Check that device is not in the simple audio devices list as we already have a bluetooth audio device + audio_devices = linphone_core_get_audio_devices(core); + audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, 1, int, "%d"); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + ms_snd_card_manager_unregister_desc(sndcard_manager, &dummy_test_snd_card_desc); + linphone_core_reload_sound_devices(core); + BC_ASSERT_EQUAL(manager->stat.number_of_LinphoneCoreAudioDevicesListUpdated, 3, int, "%d"); + + // Check that device is no longer in the extended audio devices list + audio_devices = linphone_core_get_extended_audio_devices(core); + audio_devices_count = bctbx_list_size(audio_devices); + BC_ASSERT_EQUAL(audio_devices_count, sound_devices_count + 1, int, "%d"); + bctbx_list_free_with_data(audio_devices, (void (*)(void *))linphone_audio_device_unref); + + // Check that the device we removed is no longer the default + input_device = linphone_core_get_default_input_audio_device(core); + BC_ASSERT_PTR_NOT_NULL(input_device); + if (input_device) { + BC_ASSERT_STRING_NOT_EQUAL(linphone_audio_device_get_device_name(input_device), DUMMY_TEST_SOUNDCARD); + MSSndCard *sndcard = ms_snd_card_manager_get_default_capture_card(sndcard_manager); + BC_ASSERT_STRING_EQUAL(linphone_audio_device_get_device_name(input_device), ms_snd_card_get_name(sndcard)); + } + output_device = linphone_core_get_default_output_audio_device(core); + BC_ASSERT_PTR_NOT_NULL(output_device); + if (output_device) { + BC_ASSERT_STRING_NOT_EQUAL(linphone_audio_device_get_device_name(output_device), DUMMY_TEST_SOUNDCARD); + MSSndCard *sndcard = ms_snd_card_manager_get_default_playback_card(sndcard_manager); + BC_ASSERT_STRING_EQUAL(linphone_audio_device_get_device_name(input_device), ms_snd_card_get_name(sndcard)); + } + + linphone_audio_device_unref(audio_device); +end: + linphone_core_manager_destroy(manager); +} + test_t setup_tests[] = { TEST_NO_TAG("Version check", linphone_version_test), TEST_NO_TAG("Linphone Address", linphone_address_test), @@ -1753,7 +1910,8 @@ test_t setup_tests[] = { TEST_ONE_TAG("Search friend result has capabilities", search_friend_get_capabilities, "MagicSearch"), TEST_ONE_TAG("Search friend result chat room remote", search_friend_chat_room_remote, "MagicSearch"), TEST_NO_TAG("Delete friend in linphone rc", delete_friend_from_rc), - TEST_NO_TAG("Dialplan", dial_plan) + TEST_NO_TAG("Dialplan", dial_plan), + TEST_NO_TAG("Audio devices", audio_devices) }; test_suite_t setup_test_suite = {"Setup", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each, diff --git a/tester/tester.c b/tester/tester.c index 906bce2af57d6fe553fc53dab810538797b6568f..f356759a3e03ac8960b35a24499bd0ddef10a1b8 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -493,6 +493,8 @@ void linphone_core_manager_init2(LinphoneCoreManager *mgr, const char* rc_file, linphone_core_cbs_set_message_sent(mgr->cbs, liblinphone_tester_chat_room_msg_sent); linphone_core_cbs_set_first_call_started(mgr->cbs, first_call_started); linphone_core_cbs_set_last_call_ended(mgr->cbs, last_call_ended); + linphone_core_cbs_set_audio_device_changed(mgr->cbs, audio_device_changed); + linphone_core_cbs_set_audio_devices_list_updated(mgr->cbs, audio_devices_list_updated); mgr->phone_alias = phone_alias ? ms_strdup(phone_alias) : NULL; @@ -1765,6 +1767,16 @@ void last_call_ended(LinphoneCore *lc) { counters->number_of_LinphoneCoreLastCallEnded++; } +void audio_device_changed(LinphoneCore *lc, LinphoneAudioDevice *device) { + stats *counters = get_stats(lc); + counters->number_of_LinphoneCoreAudioDeviceChanged++; +} + +void audio_devices_list_updated(LinphoneCore *lc) { + stats *counters = get_stats(lc); + counters->number_of_LinphoneCoreAudioDevicesListUpdated++; +} + void setup_sdp_handling(const LinphoneCallTestParams* params, LinphoneCoreManager* mgr ){ if( params->sdp_removal ){ sal_default_set_sdp_handling(linphone_core_get_sal(mgr->lc), SalOpSDPSimulateRemove); @@ -2153,3 +2165,234 @@ int liblinphone_tester_copy_file(const char *from, const char *to) return 0; } + + +static const int flowControlIntervalMs = 5000; +static const int flowControlThresholdMs = 40; + +static int dummy_set_sample_rate(MSFilter *obj, void *data) { + return 0; +} + +static int dummy_get_sample_rate(MSFilter *obj, void *data) { + int *n = (int*)data; + *n = 44100; + return 0; +} + +static int dummy_set_nchannels(MSFilter *obj, void *data) { + return 0; +} + +static int dummy_get_nchannels(MSFilter *obj, void *data) { + int *n = (int*)data; + *n = 1; + return 0; +} + +static MSFilterMethod dummy_snd_card_methods[] = { + {MS_FILTER_SET_SAMPLE_RATE, dummy_set_sample_rate}, + {MS_FILTER_GET_SAMPLE_RATE, dummy_get_sample_rate}, + {MS_FILTER_SET_NCHANNELS, dummy_set_nchannels}, + {MS_FILTER_GET_NCHANNELS, dummy_get_nchannels}, + {0,NULL} +}; + +struct _DummyOutputContext { + MSFlowControlledBufferizer buffer; + int samplerate; + int nchannels; + ms_mutex_t mutex; +}; + +typedef struct _DummyOutputContext DummyOutputContext; + +static void dummy_snd_write_init(MSFilter *obj){ + DummyOutputContext *octx = (DummyOutputContext *)ms_new0(DummyOutputContext, 1); + octx->samplerate = 44100; + ms_flow_controlled_bufferizer_init(&octx->buffer, obj, octx->samplerate, 1); + ms_mutex_init(&octx->mutex,NULL); + + octx->nchannels = 1; + + obj->data = octx; +} + +static void dummy_snd_write_uninit(MSFilter *obj){ + DummyOutputContext *octx = (DummyOutputContext*)obj->data; + ms_flow_controlled_bufferizer_uninit(&octx->buffer); + ms_mutex_destroy(&octx->mutex); + free(octx); +} + +static void dummy_snd_write_process(MSFilter *obj) { + + DummyOutputContext *octx = (DummyOutputContext*) obj->data; + + ms_mutex_lock(&octx->mutex); + // Retrieve data and put them in the filter bugffer ready to be played + ms_flow_controlled_bufferizer_put_from_queue(&octx->buffer, obj->inputs[0]); + ms_mutex_unlock(&octx->mutex); +} + +MSFilterDesc dummy_filter_write_desc = { + MS_FILTER_PLUGIN_ID, + "DummyPlayer", + "dummy player", + MS_FILTER_OTHER, + NULL, + 1, + 0, + dummy_snd_write_init, + NULL, + dummy_snd_write_process, + NULL, + dummy_snd_write_uninit, + dummy_snd_card_methods +}; + +static MSFilter *dummy_snd_card_create_writer(MSSndCard *card) { + MSFilter *f = ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &dummy_filter_write_desc); + DummyOutputContext *octx = (DummyOutputContext*)(f->data); + ms_flow_controlled_bufferizer_set_samplerate(&octx->buffer, octx->samplerate); + ms_flow_controlled_bufferizer_set_nchannels(&octx->buffer, octx->nchannels); + ms_flow_controlled_bufferizer_set_max_size_ms(&octx->buffer, flowControlThresholdMs); + ms_flow_controlled_bufferizer_set_flow_control_interval_ms(&octx->buffer, flowControlIntervalMs); + return f; +} + +struct _DummyInputContext { + queue_t q; + MSFlowControlledBufferizer buffer; + int samplerate; + int nchannels; + ms_mutex_t mutex; +}; + +typedef struct _DummyInputContext DummyInputContext; + +static void dummy_snd_read_init(MSFilter *obj){ + DummyInputContext *ictx = (DummyInputContext *)ms_new0(DummyInputContext, 1); + ictx->samplerate = 44100; + ms_flow_controlled_bufferizer_init(&ictx->buffer, obj, ictx->samplerate, 1); + ms_mutex_init(&ictx->mutex,NULL); + qinit(&ictx->q); + + ictx->nchannels = 1; + + obj->data = ictx; +} + +static void dummy_snd_read_uninit(MSFilter *obj){ + DummyInputContext *ictx = (DummyInputContext*)obj->data; + + flushq(&ictx->q,0); + ms_flow_controlled_bufferizer_uninit(&ictx->buffer); + ms_mutex_destroy(&ictx->mutex); + + free(ictx); +} + +static void dummy_snd_read_process(MSFilter *obj) { + + DummyInputContext *ictx = (DummyInputContext*) obj->data; + + mblk_t *m; + + ms_mutex_lock(&ictx->mutex); + // Retrieve data and put them in the filter output queue + while ((m = getq(&ictx->q)) != NULL) { + ms_queue_put(obj->outputs[0], m); + } + ms_mutex_unlock(&ictx->mutex); +} + +MSFilterDesc dummy_filter_read_desc = { + MS_FILTER_PLUGIN_ID, + "DummyRecorder", + "dummy recorder", + MS_FILTER_OTHER, + NULL, + 0, + 1, + dummy_snd_read_init, + NULL, + dummy_snd_read_process, + NULL, + dummy_snd_read_uninit, + dummy_snd_card_methods +}; + +static MSFilter *dummy_snd_card_create_reader(MSSndCard *card) { + MSFilter *f = ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &dummy_filter_read_desc); + + DummyInputContext *ictx = (DummyInputContext*)(f->data); + ms_flow_controlled_bufferizer_set_samplerate(&ictx->buffer, ictx->samplerate); + ms_flow_controlled_bufferizer_set_nchannels(&ictx->buffer, ictx->nchannels); + ms_flow_controlled_bufferizer_set_max_size_ms(&ictx->buffer, flowControlThresholdMs); + ms_flow_controlled_bufferizer_set_flow_control_interval_ms(&ictx->buffer, flowControlIntervalMs); + + return f; +} + +static void dummy_test_snd_card_detect(MSSndCardManager *m); + +MSSndCardDesc dummy_test_snd_card_desc = { + "dummyTest", + dummy_test_snd_card_detect, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + dummy_snd_card_create_reader, + dummy_snd_card_create_writer, + NULL +}; + +static MSSndCard* create_dummy_test_snd_card(void) { + MSSndCard* sndcard; + sndcard = ms_snd_card_new(&dummy_test_snd_card_desc); + sndcard->data = NULL; + sndcard->name = ms_strdup(DUMMY_TEST_SOUNDCARD); + sndcard->capabilities = MS_SND_CARD_CAP_PLAYBACK | MS_SND_CARD_CAP_CAPTURE; + sndcard->latency = 0; + sndcard->device_type = MS_SND_CARD_DEVICE_TYPE_BLUETOOTH; + return sndcard; +} + +static void dummy_test_snd_card_detect(MSSndCardManager *m) { + ms_snd_card_manager_prepend_card(m, create_dummy_test_snd_card()); +} + +static void dummy2_test_snd_card_detect(MSSndCardManager *m); + +MSSndCardDesc dummy2_test_snd_card_desc = { + "dummyTest2", + dummy2_test_snd_card_detect, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + dummy_snd_card_create_reader, + dummy_snd_card_create_writer, + NULL +}; + +static MSSndCard* create_dummy2_test_snd_card(void) { + MSSndCard* sndcard; + sndcard = ms_snd_card_new(&dummy2_test_snd_card_desc); + sndcard->data = NULL; + sndcard->name = ms_strdup(DUMMY2_TEST_SOUNDCARD); + sndcard->capabilities = MS_SND_CARD_CAP_PLAYBACK | MS_SND_CARD_CAP_CAPTURE; + sndcard->latency = 0; + sndcard->device_type = MS_SND_CARD_DEVICE_TYPE_BLUETOOTH; + return sndcard; +} + +static void dummy2_test_snd_card_detect(MSSndCardManager *m) { + ms_snd_card_manager_prepend_card(m, create_dummy2_test_snd_card()); +} diff --git a/tools/abstractapi.py b/tools/abstractapi.py index 4d7a249633a8d0778209791159101b332c1d9d2e..1de0e0c3a9582da4ff20f404c99262cd5af4cb51 100644 --- a/tools/abstractapi.py +++ b/tools/abstractapi.py @@ -531,7 +531,9 @@ class CParser(object): 'LinphoneFriendListSyncStatus' : 'LinphoneFriendList', 'LinphonePlayerState' : 'LinphonePlayer', 'LinphonePresenceActivityType' : 'LinphonePresenceActivity', - 'LinphoneTunnelMode' : 'LinphoneTunnel' + 'LinphoneTunnelMode' : 'LinphoneTunnel', + 'LinphoneAudioDeviceType' : 'LinphoneAudioDevice', + 'LinphoneAudioDeviceCapabilities' : 'LinphoneAudioDevice' } self.cProject = cProject diff --git a/wrappers/java/classes/org/linphone/core/tools/audio/AudioHelper.java b/wrappers/java/classes/org/linphone/core/tools/audio/AudioHelper.java index d68b6182c4555e205ee838dd76fc39494f30f9ec..cd68bb852b19a20c4cacde7897d8b6f618839c3e 100644 --- a/wrappers/java/classes/org/linphone/core/tools/audio/AudioHelper.java +++ b/wrappers/java/classes/org/linphone/core/tools/audio/AudioHelper.java @@ -36,6 +36,8 @@ import java.io.IOException; import java.io.FileInputStream; import java.lang.SecurityException; +import org.linphone.core.Core; +import org.linphone.core.AudioDevice; import org.linphone.core.tools.Log; import org.linphone.core.tools.service.CoreManager; @@ -45,6 +47,7 @@ public class AudioHelper implements OnAudioFocusChangeListener { private AudioFocusRequestCompat mCallRequest; private MediaPlayer mPlayer; private int mVolumeBeforeEchoTest; + private AudioDevice mPreviousDefaultOutputAudioDevice; public AudioHelper(Context context) { mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); @@ -55,8 +58,7 @@ public class AudioHelper implements OnAudioFocusChangeListener { public void startAudioForEchoTestOrCalibration() { requestCallAudioFocus(); - // TODO: remove when audio routing will be handled by the Core - mAudioManager.setSpeakerphoneOn(true); + routeAudioToSpeaker(); mVolumeBeforeEchoTest = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL); int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL); @@ -74,8 +76,7 @@ public class AudioHelper implements OnAudioFocusChangeListener { Log.e("[Audio Helper] Couldn't restore volume: ", se); } - // TODO: remove when audio routing will be handled by the Core - mAudioManager.setSpeakerphoneOn(false); + routeAudioToEarpiece(); releaseCallAudioFocus(); } @@ -225,4 +226,32 @@ public class AudioHelper implements OnAudioFocusChangeListener { break; } } + + private void routeAudioToEarpiece() { + // Let's restore the default output device before the echo calibration or test + Core core = CoreManager.instance().getCore(); + core.setDefaultOutputAudioDevice(mPreviousDefaultOutputAudioDevice); + Log.i("[Audio Helper] Restored previous default output audio device: " + mPreviousDefaultOutputAudioDevice); + mPreviousDefaultOutputAudioDevice = null; + } + + private void routeAudioToSpeaker() { + // For echo canceller calibration & echo tester, we can't change the soundcard dynamically as the stream isn't created yet... + Core core = CoreManager.instance().getCore(); + mPreviousDefaultOutputAudioDevice = core.getDefaultOutputAudioDevice(); + if (mPreviousDefaultOutputAudioDevice.getType() == AudioDevice.Type.Speaker) { + Log.i("[Audio Helper] Current default output audio device is already the speaker: " + mPreviousDefaultOutputAudioDevice); + return; + } + Log.i("[Audio Helper] Saved current default output audio device: " + mPreviousDefaultOutputAudioDevice); + + for (AudioDevice audioDevice : core.getAudioDevices()) { + if (audioDevice.getType() == AudioDevice.Type.Speaker) { + Log.i("[Audio Helper] Found speaker device, replacing default output audio device with: " + audioDevice); + core.setDefaultOutputAudioDevice(audioDevice); + return; + } + } + Log.e("[Audio Helper] Couldn't find speaker audio device"); + } } \ No newline at end of file diff --git a/wrappers/java/classes/org/linphone/core/tools/audio/BluetoothHelper.java b/wrappers/java/classes/org/linphone/core/tools/audio/BluetoothHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..d34baaee9119691bbc3e3add6df6a01c5d7a9361 --- /dev/null +++ b/wrappers/java/classes/org/linphone/core/tools/audio/BluetoothHelper.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +package org.linphone.core.tools.audio; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; + +import org.linphone.core.tools.Log; +import org.linphone.core.tools.receiver.BluetoothReceiver; +import org.linphone.core.tools.service.CoreManager; + +public class BluetoothHelper { + private AudioManager mAudioManager; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothReceiver mBluetoothReceiver; + + public BluetoothHelper(Context context) { + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mBluetoothAdapter != null) { + Log.i("[Bluetooth] Adapter found"); + if (mAudioManager.isBluetoothScoAvailableOffCall()) { + Log.i("[Bluetooth] SCO available off call, continue"); + } else { + Log.w("[Bluetooth] SCO not available off call !"); + } + + mBluetoothReceiver = new BluetoothReceiver(); + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + context.registerReceiver(mBluetoothReceiver, filter); + } + + Log.i("[Bluetooth] Bluetooth helper created"); + } +} \ No newline at end of file diff --git a/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove21.java b/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove21.java index 6042d16ff34d0952614ce5b8c011412b17ecf184..9e95461212b1659e573f3d027ba9fa858d67830c 100644 --- a/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove21.java +++ b/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove21.java @@ -43,6 +43,7 @@ public class NetworkManagerAbove21 implements NetworkManagerInterface { private ConnectivityManager mConnectivityManager; private ConnectivityManager.NetworkCallback mNetworkCallback; private Network mNetworkAvailable; + private Network mLastNetworkAvailable; private boolean mWifiOnly; public NetworkManagerAbove21(final AndroidPlatformHelper helper, ConnectivityManager cm, boolean wifiOnly) { @@ -50,6 +51,7 @@ public class NetworkManagerAbove21 implements NetworkManagerInterface { mConnectivityManager = cm; mWifiOnly = wifiOnly; mNetworkAvailable = null; + mLastNetworkAvailable = null; mNetworkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(final Network network) { @@ -63,11 +65,14 @@ public class NetworkManagerAbove21 implements NetworkManagerInterface { } Log.i("[Platform Helper] [Network Manager 21] A network is available: " + info.getTypeName() + ", wifi only is " + (mWifiOnly ? "enabled" : "disabled")); - if (!mWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI) { + if (!mWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI || info.getType() == ConnectivityManager.TYPE_ETHERNET) { mNetworkAvailable = network; mHelper.updateNetworkReachability(); } else { Log.i("[Platform Helper] [Network Manager 21] Network isn't wifi and wifi only mode is enabled"); + if (mWifiOnly) { + mLastNetworkAvailable = network; + } } } }); @@ -82,6 +87,9 @@ public class NetworkManagerAbove21 implements NetworkManagerInterface { if (mNetworkAvailable != null && mNetworkAvailable.equals(network)) { mNetworkAvailable = null; } + if (mLastNetworkAvailable != null && mLastNetworkAvailable.equals(network)) { + mLastNetworkAvailable = null; + } mHelper.updateNetworkReachability(); } }); @@ -148,10 +156,15 @@ public class NetworkManagerAbove21 implements NetworkManagerInterface { mWifiOnly = isWifiOnlyEnabled; if (mWifiOnly && mNetworkAvailable != null) { NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(mNetworkAvailable); - if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) { - Log.i("[Platform Helper] [Network Manager 21] Wifi only mode enabled and current network isn't wifi"); + if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI && networkInfo.getType() != ConnectivityManager.TYPE_ETHERNET) { + Log.i("[Platform Helper] [Network Manager 21] Wifi only mode enabled and current network isn't wifi or ethernet"); + mLastNetworkAvailable = mNetworkAvailable; mNetworkAvailable = null; } + } else if (!mWifiOnly && mNetworkAvailable == null) { + Log.i("[Platform Helper] [Network Manager 21] Wifi only mode disabled, restoring previous network"); + mNetworkAvailable = mLastNetworkAvailable; + mLastNetworkAvailable = null; } } diff --git a/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove23.java b/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove23.java index d848695d380275cf549f0134aee72542d70b6571..e0d6fe3e495da90f367d0475f475f27bac80d187 100644 --- a/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove23.java +++ b/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove23.java @@ -43,6 +43,7 @@ public class NetworkManagerAbove23 implements NetworkManagerInterface { private ConnectivityManager mConnectivityManager; private ConnectivityManager.NetworkCallback mNetworkCallback; private Network mNetworkAvailable; + private Network mLastNetworkAvailable; private boolean mWifiOnly; public NetworkManagerAbove23(final AndroidPlatformHelper helper, ConnectivityManager cm, boolean wifiOnly) { @@ -50,6 +51,7 @@ public class NetworkManagerAbove23 implements NetworkManagerInterface { mConnectivityManager = cm; mWifiOnly = wifiOnly; mNetworkAvailable = null; + mLastNetworkAvailable = null; mNetworkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(final Network network) { @@ -63,11 +65,14 @@ public class NetworkManagerAbove23 implements NetworkManagerInterface { } Log.i("[Platform Helper] [Network Manager 23] A network is available: " + info.getTypeName() + ", wifi only is " + (mWifiOnly ? "enabled" : "disabled")); - if (!mWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI) { + if (!mWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI || info.getType() == ConnectivityManager.TYPE_ETHERNET) { mNetworkAvailable = network; mHelper.updateNetworkReachability(); } else { Log.i("[Platform Helper] [Network Manager 23] Network isn't wifi and wifi only mode is enabled"); + if (mWifiOnly) { + mLastNetworkAvailable = network; + } } } }); @@ -82,6 +87,9 @@ public class NetworkManagerAbove23 implements NetworkManagerInterface { if (mNetworkAvailable != null && mNetworkAvailable.equals(network)) { mNetworkAvailable = null; } + if (mLastNetworkAvailable != null && mLastNetworkAvailable.equals(network)) { + mLastNetworkAvailable = null; + } mHelper.updateNetworkReachability(); } }); @@ -148,10 +156,15 @@ public class NetworkManagerAbove23 implements NetworkManagerInterface { mWifiOnly = isWifiOnlyEnabled; if (mWifiOnly && mNetworkAvailable != null) { NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(mNetworkAvailable); - if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) { - Log.i("[Platform Helper] [Network Manager 23] Wifi only mode enabled and current network isn't wifi"); + if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI && networkInfo.getType() != ConnectivityManager.TYPE_ETHERNET) { + Log.i("[Platform Helper] [Network Manager 23] Wifi only mode enabled and current network isn't wifi or ethernet"); + mLastNetworkAvailable = mNetworkAvailable; mNetworkAvailable = null; } + } else if (!mWifiOnly && mNetworkAvailable == null) { + Log.i("[Platform Helper] [Network Manager 23] Wifi only mode disabled, restoring previous network"); + mNetworkAvailable = mLastNetworkAvailable; + mLastNetworkAvailable = null; } } diff --git a/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove24.java b/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove24.java index 8dc472f3c96045d3af2db502d499cb392e08f511..ffee15d04488429f172a13d0be694af05dbc0387 100644 --- a/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove24.java +++ b/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove24.java @@ -45,6 +45,7 @@ public class NetworkManagerAbove24 implements NetworkManagerInterface { private ConnectivityManager mConnectivityManager; private ConnectivityManager.NetworkCallback mNetworkCallback; private Network mNetworkAvailable; + private Network mLastNetworkAvailable; private boolean mWifiOnly; public NetworkManagerAbove24(final AndroidPlatformHelper helper, ConnectivityManager cm, boolean wifiOnly) { @@ -52,6 +53,7 @@ public class NetworkManagerAbove24 implements NetworkManagerInterface { mConnectivityManager = cm; mWifiOnly = wifiOnly; mNetworkAvailable = null; + mLastNetworkAvailable = null; mNetworkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(final Network network) { @@ -65,11 +67,14 @@ public class NetworkManagerAbove24 implements NetworkManagerInterface { } Log.i("[Platform Helper] [Network Manager 24] A network is available: " + info.getTypeName() + ", wifi only is " + (mWifiOnly ? "enabled" : "disabled")); - if (!mWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI) { + if (!mWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI || info.getType() == ConnectivityManager.TYPE_ETHERNET) { mNetworkAvailable = network; mHelper.updateNetworkReachability(); } else { Log.i("[Platform Helper] [Network Manager 24] Network isn't wifi and wifi only mode is enabled"); + if (mWifiOnly) { + mLastNetworkAvailable = network; + } } } }); @@ -84,6 +89,9 @@ public class NetworkManagerAbove24 implements NetworkManagerInterface { if (mNetworkAvailable != null && mNetworkAvailable.equals(network)) { mNetworkAvailable = null; } + if (mLastNetworkAvailable != null && mLastNetworkAvailable.equals(network)) { + mLastNetworkAvailable = null; + } mHelper.updateNetworkReachability(); } }); @@ -151,10 +159,15 @@ public class NetworkManagerAbove24 implements NetworkManagerInterface { mWifiOnly = isWifiOnlyEnabled; if (mWifiOnly && mNetworkAvailable != null) { NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(mNetworkAvailable); - if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) { - Log.i("[Platform Helper] [Network Manager 24] Wifi only mode enabled and current network isn't wifi"); + if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI && networkInfo.getType() != ConnectivityManager.TYPE_ETHERNET) { + Log.i("[Platform Helper] [Network Manager 24] Wifi only mode enabled and current network isn't wifi or ethernet"); + mLastNetworkAvailable = mNetworkAvailable; mNetworkAvailable = null; } + } else if (!mWifiOnly && mNetworkAvailable == null) { + Log.i("[Platform Helper] [Network Manager 24] Wifi only mode disabled, restoring previous network"); + mNetworkAvailable = mLastNetworkAvailable; + mLastNetworkAvailable = null; } } diff --git a/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove26.java b/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove26.java index 13e2aeff96a042501b89480c11b3e91438dfa05b..ab4f23978c2ae43905b4cf5e30f4cd0cecefa4a6 100644 --- a/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove26.java +++ b/wrappers/java/classes/org/linphone/core/tools/network/NetworkManagerAbove26.java @@ -45,6 +45,7 @@ public class NetworkManagerAbove26 implements NetworkManagerInterface { private ConnectivityManager mConnectivityManager; private ConnectivityManager.NetworkCallback mNetworkCallback; private Network mNetworkAvailable; + private Network mLastNetworkAvailable; private boolean mWifiOnly; public NetworkManagerAbove26(final AndroidPlatformHelper helper, ConnectivityManager cm, boolean wifiOnly) { @@ -52,6 +53,7 @@ public class NetworkManagerAbove26 implements NetworkManagerInterface { mConnectivityManager = cm; mWifiOnly = wifiOnly; mNetworkAvailable = null; + mLastNetworkAvailable = null; mNetworkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { @@ -62,11 +64,14 @@ public class NetworkManagerAbove26 implements NetworkManagerInterface { } Log.i("[Platform Helper] [Network Manager 26] A network is available: " + info.getTypeName() + ", wifi only is " + (mWifiOnly ? "enabled" : "disabled")); - if (!mWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI) { + if (!mWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI || info.getType() == ConnectivityManager.TYPE_ETHERNET) { mNetworkAvailable = network; mHelper.updateNetworkReachability(); } else { Log.i("[Platform Helper] [Network Manager 26] Network isn't wifi and wifi only mode is enabled"); + if (mWifiOnly) { + mLastNetworkAvailable = network; + } } } @@ -76,6 +81,9 @@ public class NetworkManagerAbove26 implements NetworkManagerInterface { if (mNetworkAvailable != null && mNetworkAvailable.equals(network)) { mNetworkAvailable = null; } + if (mLastNetworkAvailable != null && mLastNetworkAvailable.equals(network)) { + mLastNetworkAvailable = null; + } mHelper.updateNetworkReachability(); } @@ -120,10 +128,15 @@ public class NetworkManagerAbove26 implements NetworkManagerInterface { mWifiOnly = isWifiOnlyEnabled; if (mWifiOnly && mNetworkAvailable != null) { NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(mNetworkAvailable); - if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) { - Log.i("[Platform Helper] [Network Manager 26] Wifi only mode enabled and current network isn't wifi"); + if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI && networkInfo.getType() != ConnectivityManager.TYPE_ETHERNET) { + Log.i("[Platform Helper] [Network Manager 26] Wifi only mode enabled and current network isn't wifi or ethernet"); + mLastNetworkAvailable = mNetworkAvailable; mNetworkAvailable = null; } + } else if (!mWifiOnly && mNetworkAvailable == null) { + Log.i("[Platform Helper] [Network Manager 26] Wifi only mode disabled, restoring previous network"); + mNetworkAvailable = mLastNetworkAvailable; + mLastNetworkAvailable = null; } } diff --git a/wrappers/java/classes/org/linphone/core/tools/receiver/BluetoothReceiver.java b/wrappers/java/classes/org/linphone/core/tools/receiver/BluetoothReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..6868be7cd01064d78c33e51b225ca7feffa3b659 --- /dev/null +++ b/wrappers/java/classes/org/linphone/core/tools/receiver/BluetoothReceiver.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.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 3 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, see <http://www.gnu.org/licenses/>. + */ +package org.linphone.core.tools.receiver; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothHeadset; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; + +import org.linphone.core.tools.Log; +import org.linphone.core.tools.service.CoreManager; + +public class BluetoothReceiver extends BroadcastReceiver { + public BluetoothReceiver() { + super(); + Log.i("[Bluetooth] Bluetooth receiver created"); + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.i("[Bluetooth] Bluetooth broadcast received"); + + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + switch (state) { + case BluetoothAdapter.STATE_OFF: + Log.w("[Bluetooth] Adapter has been turned off"); + break; + case BluetoothAdapter.STATE_TURNING_OFF: + Log.w("[Bluetooth] Adapter is being turned off"); + break; + case BluetoothAdapter.STATE_ON: + Log.i("[Bluetooth] Adapter has been turned on"); + break; + case BluetoothAdapter.STATE_TURNING_ON: + Log.i("[Bluetooth] Adapter is being turned on"); + break; + case BluetoothAdapter.ERROR: + Log.e("[Bluetooth] Adapter is in error state !"); + break; + default: + Log.w("[Bluetooth] Unknown adapter state: ", state); + break; + } + } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED); + if (state == BluetoothHeadset.STATE_CONNECTED) { + Log.i("[Bluetooth] Bluetooth headset connected"); + if (CoreManager.isReady()) CoreManager.instance().onBluetoothHeadsetStateChanged(); + } else if (state == BluetoothHeadset.STATE_DISCONNECTED) { + Log.i("[Bluetooth] Bluetooth headset disconnected"); + if (CoreManager.isReady()) CoreManager.instance().onBluetoothHeadsetStateChanged(); + } else if (state == BluetoothHeadset.STATE_CONNECTING) { + Log.i("[Bluetooth] Bluetooth headset connecting"); + } else { + Log.w("[Bluetooth] Bluetooth headset unknown state changed: " + state); + } + } else { + Log.w("[Bluetooth] Bluetooth unknown action: " + action); + } + } +} diff --git a/wrappers/java/classes/org/linphone/core/tools/receivers/DozeReceiver.java b/wrappers/java/classes/org/linphone/core/tools/receiver/DozeReceiver.java similarity index 100% rename from wrappers/java/classes/org/linphone/core/tools/receivers/DozeReceiver.java rename to wrappers/java/classes/org/linphone/core/tools/receiver/DozeReceiver.java diff --git a/wrappers/java/classes/org/linphone/core/tools/receivers/InteractivityReceiver.java b/wrappers/java/classes/org/linphone/core/tools/receiver/InteractivityReceiver.java similarity index 69% rename from wrappers/java/classes/org/linphone/core/tools/receivers/InteractivityReceiver.java rename to wrappers/java/classes/org/linphone/core/tools/receiver/InteractivityReceiver.java index 4c30625af4883ef2102b8d993c21fde5b9affb4b..05911b30627656e86653e62bbda906f6db24a761 100644 --- a/wrappers/java/classes/org/linphone/core/tools/receivers/InteractivityReceiver.java +++ b/wrappers/java/classes/org/linphone/core/tools/receiver/InteractivityReceiver.java @@ -18,25 +18,6 @@ */ package org.linphone.core.tools.receiver; -/* -InteractivityReceiver.java -Copyright (C) 2019 Belledonne Communications, Grenoble, France - -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 -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; diff --git a/wrappers/java/classes/org/linphone/core/tools/service/CoreManager.java b/wrappers/java/classes/org/linphone/core/tools/service/CoreManager.java index 0c2670e0245c0fab143fc221d4dca99c899aadca..a852b6d38458b5d89744958def22603b2263f5a9 100644 --- a/wrappers/java/classes/org/linphone/core/tools/service/CoreManager.java +++ b/wrappers/java/classes/org/linphone/core/tools/service/CoreManager.java @@ -35,6 +35,7 @@ import org.linphone.core.CoreListenerStub; import org.linphone.core.tools.Log; import org.linphone.core.tools.PushNotificationUtils; import org.linphone.core.tools.audio.AudioHelper; +import org.linphone.core.tools.audio.BluetoothHelper; import org.linphone.mediastream.Version; import java.util.Timer; @@ -60,6 +61,7 @@ public class CoreManager { protected Context mContext; protected Core mCore; + private Class mServiceClass; private Timer mTimer; private Runnable mIterateRunnable; @@ -67,6 +69,7 @@ public class CoreManager { private CoreListenerStub mListener; private AudioHelper mAudioHelper; + private BluetoothHelper mBluetoothHelper; private native void updatePushNotificationInformation(long ptr, String appId, String token); @@ -97,6 +100,7 @@ public class CoreManager { } mAudioHelper = new AudioHelper(mContext); + mBluetoothHelper = new BluetoothHelper(mContext); Log.i("[Core Manager] Ready"); } @@ -166,10 +170,10 @@ public class CoreManager { mCore.addListener(mListener); try { - Class serviceClass = getServiceClass(); - if (serviceClass == null) serviceClass = CoreService.class; - mContext.startService(new Intent().setClass(mContext, serviceClass)); - Log.i("[Core Manager] Starting service ", serviceClass.getName()); + mServiceClass = getServiceClass(); + if (mServiceClass == null) mServiceClass = CoreService.class; + mContext.startService(new Intent().setClass(mContext, mServiceClass)); + Log.i("[Core Manager] Starting service ", mServiceClass.getName()); } catch (IllegalStateException ise) { Log.w("[Core Manager] Failed to start service: ", ise); // On Android > 8, if app in background, startService will trigger an IllegalStateException when called from background @@ -181,6 +185,9 @@ public class CoreManager { public void onLinphoneCoreStop() { Log.i("[Core Manager] Destroying"); + mContext.stopService(new Intent().setClass(mContext, mServiceClass)); + Log.i("[Core Manager] Stopping service ", mServiceClass.getName()); + if (mTimer != null) { mTimer.cancel(); mTimer.purge(); @@ -209,6 +216,11 @@ public class CoreManager { } } + public void onBluetoothHeadsetStateChanged() { + Log.i("[Core Manager] Bluetooth headset state changed, reload sound devices"); + mCore.reloadSoundDevices(); + } + public void onBackgroundMode() { Log.i("[Core Manager] App has entered background mode"); if (mCore != null) { diff --git a/wrappers/java/classes/org/linphone/core/tools/service/CoreService.java b/wrappers/java/classes/org/linphone/core/tools/service/CoreService.java index 2e56ed5d6c41f51ee9f035802f19d3ad633d2bf1..0ebf8c791d4b4d062b8df410c3328c0041cc4f89 100644 --- a/wrappers/java/classes/org/linphone/core/tools/service/CoreService.java +++ b/wrappers/java/classes/org/linphone/core/tools/service/CoreService.java @@ -108,8 +108,6 @@ public class CoreService extends Service { @Override public void onTaskRemoved(Intent rootIntent) { Log.i("[Core Service] Task removed"); - stopSelf(); - super.onTaskRemoved(rootIntent); } diff --git a/wrappers/java/java_class.mustache b/wrappers/java/java_class.mustache index 5066f26bb7f15ee383e12c96d0ca8d1b38b47e33..ee69d15aef0b1ad5bd181d4d0c294ad683c1d402 100644 --- a/wrappers/java/java_class.mustache +++ b/wrappers/java/java_class.mustache @@ -68,7 +68,7 @@ public {{#isLinphoneFactory}}abstract class{{/isLinphoneFactory}}{{#isNotLinphon {{/lines}} {{/detailedDoc}} */ - {{name}}({{value}}){{commarorsemicolon}} + {{name}}({{{value}}}){{commarorsemicolon}} {{/values}} protected final int mValue; @@ -80,7 +80,7 @@ public {{#isLinphoneFactory}}abstract class{{/isLinphoneFactory}}{{#isNotLinphon static public {{{className}}} fromInt(int value) throws RuntimeException { switch(value) { {{#values}} - case {{value}}: return {{name}}; + case {{{value}}}: return {{name}}; {{/values}} default: throw new RuntimeException("Unhandled enum value " + value + " for {{{className}}}");