AVFoundation проблема записи после переподключения микрофона
Есть разные микрофоны, которые могут быть подключены через разъем 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();
}