Как записать звук из фонового (на основе запуска) процесса в Mojave/Catalina?

Во-первых, небольшая справочная информация, чтобы объяснить мою мотивацию: у меня есть приложение 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 ------

person Jeremy Friesner    schedule 25.09.2019    source источник
comment
Мне жаль слышать, что мое предложение не сработало. Завтра проверю, может чего накопаю.   -  person tukan    schedule 01.10.2019
comment
Спасибо @tukan -- если это поможет, я придумал небольшую автономную игрушечную программу/MCVE, которую можно использовать для воспроизведения проблемы с минимальным кодом -- ее можно загрузить по этому URL-адресу: public.msli.com/lcs/jaf/request_microphone_access.zip   -  person Jeremy Friesner    schedule 01.10.2019
comment
Хороший мвц. Я не нашел способа сделать это. Похоже, что это заблокировано, и Apple не упоминает об этом ни в одной документации, которую я смог найти. Теперь вопрос, баг это или фича.   -  person tukan    schedule 02.10.2019
comment
Спасибо, что попробовали @tukan — более или менее к такому выводу я пришел. Я подал заявку в службу технической поддержки на сайте developer.apple.com, и если/когда они ответят, я опишу их ответ здесь.   -  person Jeremy Friesner    schedule 02.10.2019
comment
Я думаю, вы выбрали правильный подход, используя инцидент с ТС, чтобы отследить его. Я бы также рекомендовал подать отзыв и назвать это некорректным поведением. И представители Feedback, и TSI, вероятно, захотят, чтобы вы составили наименьший из возможных тестовый проект для демонстрации проблемы. FWIW Ранее я искал эту информацию и не смог найти никакой документации по этой теме.   -  person drewster    schedule 03.10.2019
comment
@drewster спасибо за идею; Я подал заявку на сайте feedbackassistant.apple.com.   -  person Jeremy Friesner    schedule 03.10.2019


Ответы (2)


Этим утром я получил ответ от сотрудников службы технической поддержки Apple; они говорят, что невозможно получить доступ к микрофону из системной службы без графического интерфейса.

Они рекомендуют подать запрос функции с помощью своего помощника по обратной связи (что я уже сделал) или в качестве обходного пути вместо этого настроить мою программу как элемент входа и настроить Mac для автоматического входа в систему этого пользователя.

Поскольку у меня нет полной уверенности в том, что Apple собирается изменить свое поведение в зависимости от моих запросов, я полагаю, что последнее — это то, что я буду изучать дальше.

person Jeremy Friesner    schedule 07.10.2019
comment
Эта технология Apple неверна. См. этот ответ на странице Спросить у разных - person Allan; 12.05.2020
comment
@Allan Аллан, похоже, этот ответ касается записи из окна терминала, тогда как этот вопрос касается записи из фонового процесса на основе launchctl - это не то же самое, AFAICT. - person Jeremy Friesner; 13.05.2020
comment
Нет, то же самое. Если я подключаюсь по SSH к Mac, я могу записывать с микрофона, графический интерфейс не требуется. - person Allan; 13.05.2020
comment
Круто, но мне нужно включить Mac, и он автоматически начнет обрабатывать входящий звук, даже до того, как кто-либо войдет в систему (как часть интерактивной аудиоустановки, где Mac работает как безголовый сервер), что вот почему я хочу, чтобы программа запускалась как процесс демона через launchd. - person Jeremy Friesner; 13.05.2020

Вы можете попробовать переместить свое определение задания .plist в /System/Library/LaunchDaemons/, а также попробовать его оттуда, удалив ключ UserName.

В конце концов, это может позволить системным демонам менее ограничивать доступ, как люди, которые управляют Mac с помощью голоса, могут войти в систему. Можно спросить об этом в техподдержке Apple или у вице-президента по включению.

Возможно, вам придется временно отключить SIP, чтобы получить его там. Также не забудьте использовать launchctl, чтобы сначала удалить существующее задание.

Менее вероятно, но все же стоит попробовать, если у вас сейчас нет вариантов, также попробуйте снова из /System/Library/LaunchAgents/, как с ключом UserName, так и без него.

person Najinsky    schedule 04.10.2019