From 59273a273729ac569f963654914cef7855b6b49b Mon Sep 17 00:00:00 2001 From: Julien Wadel <julien.wadel@belledonne-communications.com> Date: Tue, 4 Mar 2025 10:53:53 +0100 Subject: [PATCH] Fix call volume typo in documentation. Add a manual test for checking device changed by doing manual OS updates. It can be run with options `--all --suite "Audio Routes --test "Simple call with real audio devices reload"` Add 2 tools function to detect volumes on record/playback. Move usable functions into global headers to avoid future code duplications: check device name and id. For tester, fix missing entitlements in plist to access microphones. --- build/osx/Info.plist.in | 11 +++ include/linphone/api/c-call.h | 4 +- tester/audio_routes_tester.c | 157 ++++++++++++++++++++++++++++------ tester/liblinphone_tester.c | 74 ++++++++++++++++ tester/liblinphone_tester.h | 44 ++++++++++ 5 files changed, 264 insertions(+), 26 deletions(-) diff --git a/build/osx/Info.plist.in b/build/osx/Info.plist.in index f8d5ac7da2..ca6a1b0c96 100644 --- a/build/osx/Info.plist.in +++ b/build/osx/Info.plist.in @@ -41,5 +41,16 @@ <string>True</string> <key>NSCameraUsageDescription</key> <string>Allow camera for video sessions</string> + <key>NSMicrophoneUsageDescription</key> + <string>Allow microphone for audio sessions</string> + <key>NSBluetoothPeripheralUsageDescription</key> + <string>Use for wireless headsets</string> + <key>com.apple.security.device.camera</key> + <true/> + <key>com.apple.security.device.microphone</key> + <true/> + <key>com.apple.security.device.audio-input</key> + <true/> + </dict> </plist> diff --git a/include/linphone/api/c-call.h b/include/linphone/api/c-call.h index b7819607c3..ea67de1e2e 100644 --- a/include/linphone/api/c-call.h +++ b/include/linphone/api/c-call.h @@ -763,14 +763,14 @@ LINPHONE_PUBLIC LinphoneChatRoom *linphone_call_get_chat_room(LinphoneCall *call /** * Gets the mesured playback volume level (received from remote) in dbm0. * @param call The call. @notnil - * @return float Volume level in percentage. + * @return float Volume level in dbm0. */ LINPHONE_PUBLIC float linphone_call_get_play_volume(const LinphoneCall *call); /** * Gets the mesured record volume level (sent to remote) in dbm0. * @param call The call. @notnil - * @return float Volume level in percentage. + * @return float Volume level in dbm0. */ LINPHONE_PUBLIC float linphone_call_get_record_volume(const LinphoneCall *call); diff --git a/tester/audio_routes_tester.c b/tester/audio_routes_tester.c index bd9c0faad2..5ef077a3df 100644 --- a/tester/audio_routes_tester.c +++ b/tester/audio_routes_tester.c @@ -34,10 +34,6 @@ #include "linphone/lpconfig.h" #include "tester_utils.h" -static int audio_device_name_match(LinphoneAudioDevice *audio_device, const char *name) { - return strcmp(linphone_audio_device_get_device_name(audio_device), name); -} - static void register_device(LinphoneCoreManager *mgr, MSSndCardDesc *card_desc) { // Get number of devices before loading @@ -346,15 +342,15 @@ static void call_with_audio_device_change_using_public_api(void) { bctbx_list_t *dev_found = NULL; // 1st device - dev_found = - bctbx_list_find_custom(audio_devices, (bctbx_compare_func)audio_device_name_match, DUMMY_TEST_SOUNDCARD); + dev_found = bctbx_list_find_custom(audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, + DUMMY_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *current_dev = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(current_dev); linphone_audio_device_ref(current_dev); // device dummy_playback in the list - dev_found = bctbx_list_find_custom(audio_devices, (bctbx_compare_func)audio_device_name_match, + dev_found = bctbx_list_find_custom(audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, DUMMY_PLAYBACK_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *playback_dev = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; @@ -362,7 +358,7 @@ static void call_with_audio_device_change_using_public_api(void) { linphone_audio_device_ref(playback_dev); // device dummy_capture in the list - dev_found = bctbx_list_find_custom(audio_devices, (bctbx_compare_func)audio_device_name_match, + dev_found = bctbx_list_find_custom(audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, DUMMY_CAPTURE_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *capture_dev = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; @@ -1549,6 +1545,114 @@ static void simple_call_with_audio_devices_reload(void) { linphone_core_manager_destroy(marie); } +/** + * @brief simple_call_with_real_audio_devices_reload + * Tests: + * - Default device is selected on startup. + * - Current default device is capturing and send audio while in call. + * - Device list is updated on manual change: Activation/Deactivation/System default change. + * - After changing a device, the new device is capturing and send audio while still in call. + * + * + * Steps: + * 1) Use "Default" device that should follow the system device. + * 2) Make a call. + * 3) Change the capture device (1min to do it) + * 4) Check if sound is received. + * 5) Change again the capture device (1min to do it) + * 6) Check if sound is received. + * + * TODO: + * - implement an automatic change by deactivating/reactivating devices using system API. + * - check on playback devices. + * + */ + +static void simple_call_with_real_audio_devices_reload(void) { + // Default device names (currently tested: Mac/Windows) + const char *defaultCaptureName = "Default Capture"; + const char *defaultPlaybackName = "Default Playback"; + + ms_warning("[SCWRADR] Running manual test: Please check the console for steps."); + + bctbx_list_t *lcs = NULL; + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + lcs = bctbx_list_append(lcs, marie->lc); + LinphoneCoreManager *pauline = + linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + lcs = bctbx_list_append(lcs, pauline->lc); + + bctbx_list_t *marie_devices_start = linphone_core_get_extended_audio_devices(marie->lc); + bctbx_list_t *devices_it = marie_devices_start; + + // Default device should be present event if no real devices. + BC_ASSERT_TRUE(!!devices_it); + const LinphoneAudioDevice *device = linphone_core_get_default_input_audio_device(marie->lc); + bool_t capture_set = device ? liblinphone_tester_audio_device_name_match(device, defaultCaptureName) == 0 : FALSE; + device = linphone_core_get_default_output_audio_device(marie->lc); + bool_t playback_set = device ? liblinphone_tester_audio_device_name_match(device, defaultPlaybackName) == 0 : FALSE; + + BC_ASSERT_TRUE(capture_set); + BC_ASSERT_TRUE(playback_set); + + // Setting default device to run test + if (!devices_it) ms_message("[SCWRADR] No devices"); + else if (!capture_set || !playback_set) { + ms_message("[SCWRADR] Current devices list:"); + while (devices_it) { + ms_message("[SCWRADR] %s", + linphone_audio_device_get_device_name((LinphoneAudioDevice *)bctbx_list_get_data(devices_it))); + LinphoneAudioDevice *device = (LinphoneAudioDevice *)bctbx_list_get_data(devices_it); + if (!capture_set && + (linphone_audio_device_get_capabilities(device) & LinphoneAudioDeviceCapabilityRecord) && + liblinphone_tester_audio_device_name_match(device, defaultCaptureName) == 0) { + capture_set = TRUE; + linphone_core_set_default_input_audio_device(marie->lc, device); + } + if (!playback_set && (linphone_audio_device_get_capabilities(device) & LinphoneAudioDeviceCapabilityPlay) && + liblinphone_tester_audio_device_name_match(device, defaultPlaybackName) == 0) { + playback_set = TRUE; + linphone_core_set_default_output_audio_device(marie->lc, device); + } + + devices_it = bctbx_list_next(devices_it); + } + } + + ms_message("[SCWRADR] Making call"); + BC_ASSERT_TRUE(call(marie, pauline)); + + ms_message("[SCWRADR] Checking if volume is received on 200ms"); + BC_ASSERT_TRUE(liblinphone_tester_sound_detection(marie, pauline, 20000, "[SCWRADR]") == 0); + + ms_message("[SCWRADR] Waiting for changing CAPTURE device (1min)"); + BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCoreAudioDevicesListUpdated, + marie->stat.number_of_LinphoneCoreAudioDevicesListUpdated + 1, 60000)); + bctbx_list_t *new_list = linphone_core_get_extended_audio_devices(marie->lc); + bool_t is_new = FALSE; + + bctbx_list_t *devices_changed = liblinphone_tester_find_changing_devices(marie_devices_start, new_list, &is_new); + devices_it = devices_changed; + while (devices_it) { + LinphoneAudioDevice *d = (LinphoneAudioDevice *)bctbx_list_get_data(devices_it); + ms_message("[SCWRADR] Changing Devices detected : %s", linphone_audio_device_get_device_name(d)); + devices_it = bctbx_list_next(devices_it); + } + + BC_ASSERT_TRUE(liblinphone_tester_sound_detection(marie, pauline, 20000, "[SCWRADR]") == 0); + + 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"); + + bctbx_list_free_with_data(devices_changed, (void (*)(void *))linphone_audio_device_unref); + bctbx_list_free_with_data(new_list, (void (*)(void *))linphone_audio_device_unref); + bctbx_list_free_with_data(marie_devices_start, (void (*)(void *))linphone_audio_device_unref); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(marie); +} + static void simple_conference_with_audio_device_change_base(bool_t during_setup, bool_t before_all_join, bool_t after_all_join) { bctbx_list_t *lcs = NULL; @@ -2008,32 +2112,34 @@ static void conference_with_simple_audio_device_change(void) { // 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 1st device - dev_found = - bctbx_list_find_custom(marie_audio_devices, (bctbx_compare_func)audio_device_name_match, DUMMY_TEST_SOUNDCARD); + dev_found = bctbx_list_find_custom( + marie_audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, DUMMY_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *marie_dev0 = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(marie_dev0); linphone_audio_device_ref(marie_dev0); // 2nd device - dev_found = - bctbx_list_find_custom(marie_audio_devices, (bctbx_compare_func)audio_device_name_match, DUMMY2_TEST_SOUNDCARD); + dev_found = bctbx_list_find_custom( + marie_audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, DUMMY2_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *marie_dev1 = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(marie_dev1); linphone_audio_device_ref(marie_dev1); // device dummy_playback in the list - dev_found = bctbx_list_find_custom(marie_audio_devices, (bctbx_compare_func)audio_device_name_match, - DUMMY_PLAYBACK_TEST_SOUNDCARD); + dev_found = + bctbx_list_find_custom(marie_audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, + DUMMY_PLAYBACK_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *marie_playback = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(marie_playback); linphone_audio_device_ref(marie_playback); // device dummy_capture in the list - dev_found = bctbx_list_find_custom(marie_audio_devices, (bctbx_compare_func)audio_device_name_match, - DUMMY_CAPTURE_TEST_SOUNDCARD); + dev_found = + bctbx_list_find_custom(marie_audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, + DUMMY_CAPTURE_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *marie_capture = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(marie_capture); @@ -2092,32 +2198,34 @@ static void conference_with_simple_audio_device_change(void) { // 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 1st device - dev_found = - bctbx_list_find_custom(laure_audio_devices, (bctbx_compare_func)audio_device_name_match, DUMMY2_TEST_SOUNDCARD); + dev_found = bctbx_list_find_custom( + laure_audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, DUMMY2_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *laure_dev0 = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(laure_dev0); linphone_audio_device_ref(laure_dev0); // 2nd device - dev_found = - bctbx_list_find_custom(laure_audio_devices, (bctbx_compare_func)audio_device_name_match, DUMMY_TEST_SOUNDCARD); + dev_found = bctbx_list_find_custom( + laure_audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, DUMMY_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *laure_dev1 = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(laure_dev1); linphone_audio_device_ref(laure_dev1); // device dummy_playback in the list - dev_found = bctbx_list_find_custom(laure_audio_devices, (bctbx_compare_func)audio_device_name_match, - DUMMY_PLAYBACK_TEST_SOUNDCARD); + dev_found = + bctbx_list_find_custom(laure_audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, + DUMMY_PLAYBACK_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *laure_playback = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(laure_playback); linphone_audio_device_ref(laure_playback); // device dummy_capture in the list - dev_found = bctbx_list_find_custom(laure_audio_devices, (bctbx_compare_func)audio_device_name_match, - DUMMY_CAPTURE_TEST_SOUNDCARD); + dev_found = + bctbx_list_find_custom(laure_audio_devices, (bctbx_compare_func)liblinphone_tester_audio_device_name_match, + DUMMY_CAPTURE_TEST_SOUNDCARD); BC_ASSERT_PTR_NOT_NULL(dev_found); LinphoneAudioDevice *laure_capture = (dev_found) ? (LinphoneAudioDevice *)bctbx_list_get_data(dev_found) : NULL; BC_ASSERT_PTR_NOT_NULL(laure_capture); @@ -2501,6 +2609,7 @@ static void simple_conference_with_audio_device_change_using_public_api(void) { test_t audio_routes_tests[] = { TEST_NO_TAG("Simple call with audio devices reload", simple_call_with_audio_devices_reload), + TEST_ONE_TAG("Simple call with real audio devices reload", simple_call_with_real_audio_devices_reload, "skip"), 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), diff --git a/tester/liblinphone_tester.c b/tester/liblinphone_tester.c index 912c8c05bb..fa2f7ced33 100644 --- a/tester/liblinphone_tester.c +++ b/tester/liblinphone_tester.c @@ -719,3 +719,77 @@ float liblinphone_tester_get_cpu_bogomips(void) { #endif return ret; } + +int liblinphone_tester_audio_device_name_match(const LinphoneAudioDevice *audio_device, const char *name) { + return strcmp(linphone_audio_device_get_device_name(audio_device), name); +} + +int liblinphone_tester_audio_device_match(const LinphoneAudioDevice *a, LinphoneAudioDevice *b) { + return strcmp(linphone_audio_device_get_id(a), linphone_audio_device_get_id(b)); +} + +bctbx_list_t *liblinphone_tester_find_changing_devices(bctbx_list_t *a, bctbx_list_t *b, bool_t *is_new) { + bctbx_list_t *devices_changed = NULL; + bctbx_list_t *device_it = a; + bctbx_list_t *dev_found = NULL; + if (!a && !b) return NULL; + // Check for disconnected device + while (device_it) { + LinphoneAudioDevice *device = (LinphoneAudioDevice *)bctbx_list_get_data(device_it); + dev_found = bctbx_list_find_custom(b, (bctbx_compare_func)liblinphone_tester_audio_device_match, device); + device_it = bctbx_list_next(device_it); + if (!dev_found) { + devices_changed = bctbx_list_append(devices_changed, device); + linphone_audio_device_ref(device); + *is_new = FALSE; + } + } + // Check for connected device + if (!devices_changed) { + device_it = b; + while (device_it) { + LinphoneAudioDevice *device = (LinphoneAudioDevice *)bctbx_list_get_data(device_it); + dev_found = bctbx_list_find_custom(a, (bctbx_compare_func)liblinphone_tester_audio_device_match, device); + device_it = bctbx_list_next(device_it); + if (!dev_found) { + devices_changed = bctbx_list_append(devices_changed, device); + linphone_audio_device_ref(device); + *is_new = TRUE; + } + } + } + + return devices_changed; +} + +int liblinphone_tester_sound_detection(LinphoneCoreManager *a, + LinphoneCoreManager *b, + int timeout_ms, + const char *log_tag) { + int have_sound_count = 0; + const float silence_threshold = -20.f; + + LinphoneCall *calls[2] = {linphone_core_get_current_call(a->lc), linphone_core_get_current_call(b->lc)}; + MSTimeSpec start; + + liblinphone_tester_clock_start(&start); + while (have_sound_count < 2 && + !liblinphone_tester_clock_elapsed( + &start, timeout_ms)) { // We want to avoid potential sound spikes while disconnection. + float record_levels[2] = {linphone_call_get_record_volume(calls[0]), linphone_call_get_record_volume(calls[1])}; + float playback_levels[2] = {linphone_call_get_play_volume(calls[0]), linphone_call_get_play_volume(calls[1])}; + bool_t have_sounds[2] = {record_levels[1] > silence_threshold && playback_levels[0] > silence_threshold, + record_levels[0] > silence_threshold && playback_levels[1] > silence_threshold}; + + // Note: Sounds are send from Pauline to Marie without passing by the capture device (send from file) + // The test must check for Marie => Pauline, because the sound comes directly from the capture device. + // At this point, playback device cannot be tested without complex code (device monitoring). + if (have_sounds[0] && have_sounds[1]) ++have_sound_count; + else have_sound_count = 0; + if (log_tag) + ms_message("%s Record => Playback levels: %f => %f, %f => %f", log_tag, record_levels[0], + playback_levels[1], record_levels[1], playback_levels[0]); + wait_for_until(a->lc, b->lc, NULL, 0, 100); + } + return have_sound_count >= 2 ? 0 : -1; +} diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 6af62496af..fb8f5c84ea 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -1251,6 +1251,50 @@ int liblinphone_tester_check_recorded_audio(const char *hellopath, const char *r /* returns a CPU bogomips indication. Only supported for linux.*/ float liblinphone_tester_get_cpu_bogomips(void); + +/** + * @brief liblinphone_tester_audio_device_name_match compare device name with string + * + * @param audio_device the audio device to compare @notnil + * @param name the name to find in device name + * @return the result of strcmp + */ +int liblinphone_tester_audio_device_name_match(const LinphoneAudioDevice *audio_device, const char *name); + +/** + * @brief liblinphone_tester_audio_device_match compare device ids between 2 devices + * + * @param a the first device + * @param b the second device + * @return the result of strcmp + */ +int liblinphone_tester_audio_device_match(const LinphoneAudioDevice *a, LinphoneAudioDevice *b); + +/** + * @brief liblinphone_tester_find_changing_devices Find the device that is missed/new into 2 lists. + * + * @param a first list to check. @maybenil + * @param b second list to check. @maybenil + * @param is_new TRUE if the changed device is new. + * @return the device that changed or NULL if no change. Set is_new if the device has been + * connected. @tobefreed @maybenil + */ +bctbx_list_t *liblinphone_tester_find_changing_devices(bctbx_list_t *a, bctbx_list_t *b, bool_t *is_new); + +/** + * @brief liblinphone_tester_sound_detection Check sounds between 2 Core on 200ms and exit the function on detection. + * + * @param a the first core manager to retrieve current call and make wait loop @notnil + * @param b the second core manager to retrieve current call and make wait loop @notnil + * @param timeout_ms timeout in ms + * @param log_tag Use this tag to monitor level in logs. If NULL, log is switched off @maybenil + * @return -1 if timed out else 0. + */ +int liblinphone_tester_sound_detection(LinphoneCoreManager *a, + LinphoneCoreManager *b, + int timeout_ms, + const char *log_tag); + #ifdef __cplusplus }; #endif -- GitLab