Во-первых, небольшая справочная информация, чтобы объяснить мою мотивацию: у меня есть приложение Qt/C++/Objective-C++, которое использует CoreAudio/AVFoundation для получения входящего звука с указанных аудиовходов на Mac, изменения звука, а затем воспроизведения измененного звук обратно через некоторые указанные аудиовыходы. Все это работало нормально до Мохаве и Каталины, после чего новые ограничения конфиденциальности микрофона Apple привели к тому, что он больше не мог получать входящий звук (вместо этого он получал только нули/молчание из-за отсутствия явного разрешения пользователя на использование микрофон).
Чтобы исправить это, я добавил код для перехода через новые обручи (т. е. добавил тег NSMicrophoneUsageDescription
в Info.plist, добавил вызовы authorizationStatusForMediaType
и requestAccessForMediaType
, как было предложено, и т. д.), и теперь мое приложение снова работает, как ожидалось, при запуске с его значка (т. е. оно выводит «MyAudioProcessingApp хотел бы использовать запросчик микрофона», и как только пользователь ответит, на панели управления «Безопасность и конфиденциальность / Конфиденциальность / Микрофон» появится флажок для моего приложения, который определяет, может ли мое приложение прослушивать входящий звук). Это все работает нормально, насколько это возможно.
Моя проблема заключается в следующем: мое приложение также имеет функцию «фонового режима», когда пользователь может попросить приложение установить себя как системную службу без графического интерфейса (которая запускается при загрузке через launchd/launchctl), чтобы оно выполнять обработку звука в фоновом режиме, как только загрузится Mac (т. е. не требуя от кого-либо входа в систему или запуска приложения вручную). Это очень полезно для людей, которые хотят запустить это приложение на «безголовом / встроенном» Mac как часть фиксированной аудиоустановки, где все, что нужно сделать, это включить Mac, чтобы он начал обрабатывать звук.
Однако я обнаружил, что когда мое приложение работает в качестве фонового процесса таким образом, [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]
всегда возвращает AVAuthorizationStatusDenied
, даже если пользователь ранее дал разрешение моему приложению на доступ к микрофону. Это происходит даже несмотря на то, что эффективный идентификатор пользователя процесса совпадает с идентификатором пользователя, предоставившего разрешение на использование микрофона, а исполняемый файл — это тот же файл, который ранее сгенерировал запрос на разрешение, на который согласился пользователь.
У меня вопрос: нужен ли какой-то особый трюк, чтобы получить доступ к микрофону во время работы в фоновом режиме? Или Apple решила, что launchctl-launched-daemons просто не могут получить доступ к микрофону ни при каких обстоятельствах, и поэтому мне не повезло?
ps файл MyAudioProcessingApp.app/Contents/Info.plist
и файл /Library/LaunchDaemons/com.mycompany.myprogram.plist
моего приложения (оба слегка анонимизированы) приведены ниже, если они актуальны:
----- begin MyProcessingApp.app/Contents/Info.plist ------- snip ------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>MyAudioProcessingApp</string>
<key>CFBundleGetInfoString</key>
<string>Created by Qt/QMake</string>
<key>CFBundleIconFile</key>
<string>vcore.icns</string>
<key>CFBundleIdentifier</key>
<string>com.mycompany.MyAudioProcessingApp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NOTE</key>
<string>This file was generated by Qt/QMake.</string>
<key>NSMicrophoneUsageDescription</key>
<string>To allow MyAudioProcessingApp to process incoming audio data.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>
----- end MyProcessingApp.app/Contents/Info.plist ------- snip ------
---- begin /Library/LaunchDaemons/com.mycompany.myprogram.plist ------ snip ------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "
http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:</string>
</dict>
<key>Label</key>
<string>com.mycompany.MyAudioProcessingApp</string>
<key>Program</key>
<string>/Library/MyCompany/MyAudioProcessingApp/run_my_program_in_background.sh</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/myprogram.stdout</string>
<key>StandardErrorPath</key>
<string>/tmp/myprogram.stderr</string>
<key>UserName</key>
<string>jaf</string> // NOTE: this is set dynamically to the correct user as part of the install-as-service step
<key>ProcessType</key>
<string>Interactive</string>
<key>GroupName</key>
<string>admin</string>
<key>InitGroups</key>
<true/>
</dict>
</plist>
---- end /Library/LaunchDaemons/com.mycompany.myprogram.plist ------ snip ------
---- begin /Library/MyCompany/MyAudioProcessingApp/run_my_program_in_background.sh ------ snip ------
#!/bin/bash
PATH_TO_MYPROGRAM_EXE="/Library/MyCompany/MyAudioProcessingApp/MyAudioProcessingApp.app/Contents/MacOS/MyAudioProcessingApp"
"$PATH_TO_MYPROGRAM_EXE" run_without_gui
exit 0
---- end /Library/MyCompany/MyAudioProcessingApp/run_my_program_in_background.sh ------ snip ------