LINUX.ORG.RU

AVFoundation проблема записи после переподключения микрофона

 , , , ,


0

1

Есть разные микрофоны, которые могут быть подключены через разъем 3.5 дюйма или через USB или через bluetooth, поведение при этом одинаковое.

Есть код, который получает доступ к микрофону (подключенному к аудио разъему 3.5 дюйма) и начинает сессию аудиозахвата. При этом начинает отображаться иконка использования микрофона. Захват аудиоустройства (микрофона) продолжается несколько секунд, затем происходит остановка сессии, иконка использования микрофона пропадает, потом идет пауза в несколько секунд, и затем осуществляется вторая попытка доступа к тому же самому микрофону и начала сессии аудиозахвата. При этом снова отображается иконка использования микрофона. Через несколько секунд доступ к микрофону прекращается и сессия аудиозахвата останавливается после чего иконка доступа к микрофону исчезает.

Далее попробуем выполнить те же действия, однако после первой остановки доступа к микрофону попробуем после первой остановки доступа к микрофону выдернуть штекер микрофона из разьема и вставить обратно до попытки начала второй сессии. При этом вторая попытка доступа начинается, ошибок выполняемая часть программы не возвращает но иконки доступа к микрофону не отображается и в этом и есть проблема. После завершения программы и повторного запуска данная иконка снова отображается.

Данная проблема - это только вершина айсберга, т.к. проявляется это в том, что нет возможности записать звук с аудио микрофона после переподключения микрофона до перезапуска программы.

Нормальное ли это поведение фреймворка AVFoundation? Можно ли как то сделать так, чтобы после переподключения микрофона доступ к нему происходил корректно и индикатор использования отображался? Какие дополнительные действия со стороны программиста нужно выполнить в данном случае? Есть описание данного поведения где-то в документации?

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

project(MacAudioInputTestSimple LANGUAGES CXX OBJC)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(MacAudioInputTestSimple
    main.mm
)

set_target_properties(MacAudioInputTestSimple PROPERTIES
    WIN32_EXECUTABLE TRUE
    MACOSX_BUNDLE TRUE
    MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
)

find_library(IOKIT_LIB_PATH IOKit REQUIRED)
find_library(FOUNDATION_LIB_PATH Foundation REQUIRED)
find_library(AV_FOUNDATION_LIB_PATH AVFoundation REQUIRED)
find_library(CORE_FOUNDATION_LIB_PATH CoreFoundation REQUIRED)

message(${IOKIT_LIB_PATH})
message(${FOUNDATION_LIB_PATH})
message(${AV_FOUNDATION_LIB_PATH})
message(${CORE_FOUNDATION_LIB_PATH})

target_link_libraries(MacAudioInputTestSimple PRIVATE
    ${IOKIT_LIB_PATH}
    ${FOUNDATION_LIB_PATH}
    ${AV_FOUNDATION_LIB_PATH}
    ${CORE_FOUNDATION_LIB_PATH})

Info.plist.in

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>

	<key>CFBundleName</key>
	<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
	<key>CFBundleIdentifier</key>
	<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
	<key>CFBundleExecutable</key>
	<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>

	<key>CFBundleVersion</key>
	<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
	<key>CFBundleShortVersionString</key>
	<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
	<key>CFBundleLongVersionString</key>
	<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>

	<key>LSMinimumSystemVersion</key>
	<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>

	<key>CFBundleGetInfoString</key>
	<string>${MACOSX_BUNDLE_INFO_STRING}</string>
	<key>NSHumanReadableCopyright</key>
	<string>${MACOSX_BUNDLE_COPYRIGHT}</string>

	<key>CFBundleIconFile</key>
	<string>${MACOSX_BUNDLE_ICON_FILE}</string>

	<key>CFBundleDevelopmentRegion</key>
	<string>English</string>

	<key>NSMicrophoneUsageDescription</key>
        <string>MacOS audio input test</string>

	<key>NSSupportsAutomaticGraphicsSwitching</key>
	<true/>
</dict>
</plist>

main.mm

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

#include <AVFoundation/AVFoundation.h>
#include <Foundation/NSString.h>
#include <Foundation/NSURL.h>

AVCaptureSession* m_captureSession = nullptr;
AVCaptureDeviceInput* m_audioInput = nullptr;
AVCaptureAudioDataOutput* m_audioOutput = nullptr;

std::condition_variable conditionVariable;
std::mutex mutex;
bool responseToAccessRequestReceived = false;

void receiveResponse()
{
    std::lock_guard<std::mutex> lock(mutex);
    responseToAccessRequestReceived = true;
    conditionVariable.notify_one();
}

void waitForResponse()
{
    std::unique_lock<std::mutex> lock(mutex);
    conditionVariable.wait(lock, [] { return responseToAccessRequestReceived; });
}

void requestPermissions()
{
    responseToAccessRequestReceived = false;
    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted)
    {
        const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
        std::cout << "Request completion handler granted: " << (int)granted << ", status: " << status << std::endl;
        receiveResponse();
    }];

    waitForResponse();
}

void timer(int timeSec)
{
    for (auto timeRemaining = timeSec; timeRemaining > 0; --timeRemaining)
    {
        std::cout << "Timer, remaining time: " << timeRemaining << "s" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

bool updateAudioInput()
{
    [m_captureSession beginConfiguration];

    if (m_audioOutput)
    {
        AVCaptureConnection *lastConnection = [m_audioOutput connectionWithMediaType:AVMediaTypeAudio];
        [m_captureSession removeConnection:lastConnection];
    }

    if (m_audioInput)
    {
        [m_captureSession removeInput:m_audioInput];
        [m_audioInput release];
        m_audioInput = nullptr;
    }

    AVCaptureDevice* audioInputDevice = [AVCaptureDevice deviceWithUniqueID: [NSString stringWithUTF8String: "BuiltInHeadphoneInputDevice"]];

    if (!audioInputDevice)
    {
        std::cout << "Error input audio device creating" << std::endl;
        return false;
    }

    // m_audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioInputDevice error:nil];
   // NSError *error = nil;
    NSError *error = [[NSError alloc] init];
    m_audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioInputDevice error:&error];
    if (error)
    {
        const auto code = [error code];
        const auto domain = [error domain];
        const char* domainC = domain ? [domain UTF8String] : nullptr;
        std::cout << code << " " << domainC << std::endl;
    }


    if (m_audioInput && [m_captureSession canAddInput:m_audioInput]) {
        [m_audioInput retain];
        [m_captureSession addInput:m_audioInput];
    }
    else
    {
        std::cout << "Failed to create audio device input" << std::endl;
        return false;
    }

    if (!m_audioOutput)
    {
        m_audioOutput = [[AVCaptureAudioDataOutput alloc] init];
        if (m_audioOutput && [m_captureSession canAddOutput:m_audioOutput])
        {
            [m_captureSession addOutput:m_audioOutput];
        }
        else
        {
            std::cout << "Failed to add audio output" << std::endl;
            return false;
        }
    }

    [m_captureSession commitConfiguration];

    return true;
}

void start()
{
    std::cout << "Starting..." << std::endl;

    const bool updatingResult = updateAudioInput();
    if (!updatingResult)
    {
        std::cout << "Error, while updating audio input" << std::endl;
        return;
    }

    [m_captureSession startRunning];
}

void stop()
{
    std::cout << "Stopping..." << std::endl;
    [m_captureSession stopRunning];
}

int main()
{
    requestPermissions();

    m_captureSession = [[AVCaptureSession alloc] init];

    start();
    timer(5);
    stop();

    timer(10);

    start();
    timer(5);
    stop();
}
★★★★★