如何在 Mojave/Catalina 下从后台(基于 launchctl 的)进程录制音频?

How to record audio from a background (launchctl-based) process under Mojave/Catalina?

首先,一些背景信息来解释我的动机:我有一个 Qt/C++/Objective-C++ 应用程序,它使用 CoreAudio/AVFoundation 从指定的接收音频Mac 上的音频输入,修改音频,然后通过某些指定的音频输出播放修改后的音频。在 Mojave 和 Catalina 之前,这一切都很好,此时 Apple 的新麦克风隐私限制导致它不再能够接收传入的音频(由于缺乏明确的用户许可,它只接收到 zeroes/silence使用麦克风)。

为了解决这个问题,我添加了代码以跳过新的获取用户权限 hoops(即向 Info.plist 添加了一个 NSMicrophoneUsageDescription 标记,添加了对authorizationStatusForMediaTyperequestAccessForMediaType 按照建议等),现在我的应用程序在从其图标启动时再次按预期工作(即它提出 "MyAudioProcessingApp would like to use the microphone" 请求者,一旦用户响应,一个我的应用程序的复选框出现在 "Security and Privacy / Privacy / Microphone" 控制面板中并控制我的应用程序是否可以收听传入的音频)。就目前而言,一切正常。

我的问题是——我的应用程序还有一个 "background mode" 功能,用户可以要求应用程序将自身安装为非 GUI 系统服务(运行s通过 launchd/launchctl 启动),这样一旦 Mac 启动,它就会在后台进行音频处理(即不需要任何人登录或手动启动应用程序)。这对于想要在 "headless/embedded" mac 上 运行 此应用程序作为固定音频安装的一部分的人非常有用,任何人需要做的就是打开 Mac 的电源] 让它开始处理音频。

但是,我发现当我的应用程序 运行 以这种方式作为后台进程时,[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] 总是 returns AVAuthorizationStatusDenied,即使用户之前已授予我的应用访问麦克风的权限。即使进程的有效用户 ID 与授予麦克风权限的用户相同,并且可执行文件 运行ning 与先前生成权限提示的文件相同,也会发生这种情况用户同意。

我的问题是,在后台 运行ning 时,是否需要一些特殊技巧才能访问麦克风?还是 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 ------

您可以尝试将作业定义 .plist 移动到 /System/Library/LaunchDaemons/ 并从那里尝试删除 UserName 键。

它可能允许系统守护进程的访问限制较少,毕竟,使用语音控制 mac 的人如何登录。可能会询问 Apple 技术支持,或者询问包容性副总裁。

您可能需要暂时禁用 SIP 才能使用它。还要记得先使用launchctl删除现有作业。

不太可能,但仍然值得一试,如果你目前没有选择,也可以从 /System/Library/LaunchAgents/ 再次尝试,无论是否使用 UserName 键

今天早上我收到了 Apple 技术支持人员的回复;他们说不可能从非 GUI 系统服务访问麦克风。

他们建议使用他们的反馈助手提交功能请求(我已经这样做了),或者作为变通方法,将我的程序设置为登录项,并将 Mac 设置为自动登录该用户。

由于我不太相信 Apple 会根据我的要求改变他们的行为,我想后者是我接下来要研究的。

我 运行 在从 High Sierra 升级到 Catalina 时遇到了同样的问题,并在启动时通过 auto-logging 在新创建的用户中解决了这个问题。我还需要 运行 在特定时间录制脚本,所以我在 ~/Library/LaunchAgents 中创建了一个 plist 文件,它调用一个保存为应用程序的 Automator bash 脚本,其 plist 添加了 NSMicrophoneUsageDescription如上所述。我第一次 运行 script/app,我必须在 GUI window 中接受麦克风权限,而且,烦人的是,每当我对 bash script/Automator 应用程序。我不知道保存为应用程序的 AppleScript 是否也是如此。 我更喜欢 cron 而不是 LaunchAgents,但是通过 bash 命令自动程序在 cron 中安排相同的应用程序不会提示 GUI 权限请求,所以我想我被 LaunchAgents 和不同的应用程序困住了不同的参数集(我没有'找不到任何方法将参数从 LaunchAgents plist 文件传递​​到 app/bash 脚本)。