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 &params, 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 &params, 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}}}");