首先,一些背景信息来解释我的动机:我有一个 Qt/C++/Objective-C++ 应用程序,它使用 CoreAudio/AVFoundation 从 Mac 上的指定音频输入接收传入音频,修改音频,然后播放修改后的音频通过一些指定的音频输出输出音频。这一切都很好,直到 Mojave 和 Catalina,此时 Apple 的新麦克风隐私限制导致它不再能够接收传入的音频(它只收到零/静音,因为缺乏明确的用户使用权限麦克风)。
为了解决这个问题,我添加了代码以跳过新的 get-the-user's-permission箍(即向 Info.plist 添加标签,添加对和的NSMicrophoneUsageDescription
调用等),现在我的应用程序再次按预期工作从其图标启动(即它提出“MyAudioProcessingApp 想使用麦克风”请求程序,一旦用户响应,我的应用程序的复选框就会出现在“安全和隐私/隐私/麦克风”控制面板中,并控制是否不是我的应用程序可以收听传入的音频)。就目前而言,这一切都很好。authorizationStatusForMediaType
requestAccessForMediaType
我的问题是——我的应用程序还有一个“后台模式”功能,用户可以要求应用程序将自身安装为非 GUI 系统服务(通过 launchd/launchctl 在启动时运行),这样它就会Mac 启动后立即在后台进行音频处理(即无需任何人登录或手动启动应用程序)。这对于想要在“无头/嵌入式”mac 上作为固定音频安装的一部分运行此应用程序的人来说非常有用,在这种情况下,任何人都需要做的就是打开 Mac 的电源,让它开始处理音频。
但是,我发现当我的应用程序以这种方式作为后台进程运行时,[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]
总是返回AVAuthorizationStatusDenied
,即使用户之前已授予我的应用程序访问麦克风的权限。即使进程的有效用户 ID 与授予麦克风权限的用户相同,并且运行的可执行文件与先前生成用户同意的权限提示的文件相同,也会发生这种情况。
我的问题是,我需要一些特殊技巧才能在后台运行时访问麦克风吗?或者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 ------