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