Android AudioRecord принудительно передает другой поток на источник звука MIC

82

Обновление 3: у меня есть партнерские отношения с другим разработчиком, и мы, кажется, нашли кого-то, кто может сделать это за большую сумму денег. Они прислали нам тестовый apk и вроде работает. Мы продолжим и купим исходный код. Надеюсь, нас не обманут. Я обновлюсь, как только узнаю

Обновление 2: все еще работаю над этим. После более болезненных дней я теперь думаю, что ничего особенного не происходит, но они просто используют AudioFlinger ( см. Ссылку ) на собственной стороне для вызова AudioFlinger :: setParameters

Теперь я ищу, как я могу написать простой JNI для вызова AudioFlinger :: setParameters с audio_io_handle_t ioHandle, const String8 и keyValuePairs

Я знаю, что может быть keyValuePairs , но понятия не имею об audio_io_handle_t

Обновление: теперь я считаю, что другие приложения могут использовать звук QCOM с CAF. См. Audio_extn_utils_send_audio_calibration по ссылке для того же

и voice_get_incall_rec_snd_device по ссылке для того же

Я не знаю C / ++. Как я могу узнать, могу ли я вызвать эти методы с нативной стороны? Поскольку другие приложения могут, должен быть способ.


Я боролся с этим более 40 дней, по крайней мере, по 5-6 часов в день. Я не уверен, разрешено ли это SO, но я также рад сделать пожертвование за правильный ответ.

У меня есть приложение для записи разговоров, которое использует источник звука VOICE_CALL. Хотя ASOP не реализует / не требует этого, большинство производителей реализовали VOICE_CALL, и приложения, использующие источник звука VOICE_CALL, отлично работали на многих устройствах. Так было до Android 6.

Google изменил это поведение в Android 6. Для открытия источника звука VOICE_CALL теперь требуется android.permission.CAPTURE_AUDIO_OUTPUT, который предоставляется только системным приложениям.

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

Я пробовал эти приложения на разных телефонах с Android 6 и обнаружил определенные характеристики в том, как им удается записывать.

Все они используют класс Android AudioRecord и открытый источник звука MIC. Я тоже; но в своем приложении я получаю звук только от микрофона, а не от другого абонента. Я узнал, что они выдают какой-то системный вызов сразу после или перед началом записи.

Взгляните на следующий журнал из одного из приложений, которые успешно записывают VOICE_CALL, даже если для записи используется MIC. Похоже, приложение каким-то образом управляет микшированием / маршрутизацией / потоковой передачей / объединением аудиоисточника VOICE_CALL в микрофон.

- D/audio_hw_primary: in_set_parameters: enter: kvpairs=input_source=1;routing=-2147483644
- D/PermissionCache: checking android.permission.MODIFY_AUDIO_SETTINGS for uid=10286 => granted (432 us)
- D/audio_hw_primary: in_set_parameters: enter: kvpairs=input_source=4;routing=-2147483584;format=1
- D/audio_hw_primary: select_devices: out_snd_device(0: ) in_snd_device(283: voice-dmic-ef)
- D/hardware_info: hw_info_append_hw_type : device_name = voice-dmic-ef
- D/voice: voice_get_incall_rec_snd_device: in_snd_device(283: voice-dmic-ef) incall_record_device(283: voice-dmic-ef)

Как вы можете видеть в первой строке, он начинается с источника звука MIC input_source = 1; routing = -2147483644.

Затем во второй строке он что-то делает и получает android.permission.MODIFY_AUDIO_SETTINGS, что является нормальным разрешением, и у моего приложения оно тоже есть. Это кажется наиболее важной частью, и похоже, что все 3 используют JNI, чтобы делать то, что они когда-либо делали, чтобы инициировать потоковую передачу / слияние источника звука VOICE_CALL с микрофоном и записывать с помощью стандартного API AudioRecorder.

В следующей строке вы видите, что аудиооборудование начинает микшировать VOICE_CALL (input_source = 4), даже если они открыли источник звука MIC (1).

Я предположил, что они использовали

AudioManager.setParameters("key=value")

и пробовал много вариантов, таких как

AudioManager.setParameters("input_source=4;routing=-2147483584;format=1")

без везения.

Затем я нашел Android, NDK, маршрутизацию звука, принудительную передачу звука через гарнитуру, и подумал, что они могут каким-то образом смешивать / маршрутизировать / передавать / объединять VOICE_CALL в текущем сеансе AudioRecord и (поскольку не знаю C) попытался использовать рефляцию чтобы добиться того же с кодом ниже (снова) без везения.

private static void setForceUseOn() {

/*
setForceUse(int usage, int config);

----usage for setForceUse, must match AudioSystem::force_use
public static final int FOR_COMMUNICATION = 0;
public static final int FOR_MEDIA = 1;
public static final int FOR_RECORD = 2;
public static final int FOR_DOCK = 3;
public static final int FOR_SYSTEM = 4;
public static final int FOR_HDMI_SYSTEM_AUDIO = 5;

----device categories config for setForceUse, must match AudioSystem::forced_config
public static final int FORCE_NONE = 0;
public static final int FORCE_SPEAKER = 1;
public static final int FORCE_HEADPHONES = 2;
public static final int FORCE_BT_SCO = 3;
public static final int FORCE_BT_A2DP = 4;
public static final int FORCE_WIRED_ACCESSORY = 5;
public static final int FORCE_BT_CAR_DOCK = 6;
public static final int FORCE_BT_DESK_DOCK = 7;
public static final int FORCE_ANALOG_DOCK = 8;
public static final int FORCE_DIGITAL_DOCK = 9;
public static final int FORCE_NO_BT_A2DP = 10;
public static final int FORCE_SYSTEM_ENFORCED = 11;
public static final int FORCE_HDMI_SYSTEM_AUDIO_ENFORCED = 12;
public static final int FORCE_DEFAULT = FORCE_NONE;


 */

    try {
        Class audioSystemClass = Class.forName("android.media.AudioSystem");
        Method setForceUse = audioSystemClass.getMethod("setForceUse", int.class, int.class);
        setForceUse.invoke(null, 0, 0);      // setForceUse(FOR_RECORD, FORCE_NONE)


    } catch (Exception e) {
        e.printStackTrace();
    }

}

Очевидно, мне не хватает чего-то, что делает запись возможной.

Я даже предлагал заплатить за получение этой информации, все отказались. Я сказал справедливо. Я опубликую его один раз / если найду!

Вы хоть представляете, что они могут делать?

nLL
источник
не могли бы вы связать приложения, которые делают это успешно?
roarster
Конечно play.google.com/store/apps/details?id=com.skvalex.callrecorder play.google.com/store/apps/details?id=com.boldbeast.recorder play.google.com/store/apps/details ? id = com.boldbeast.recorder Они не используют корневые методы для решения этой проблемы
nLL
Вы публиковали последний дважды.
shmosel
1
вы пробовали декомпилировать его, чтобы можно было взглянуть? (только для исследовательских целей;))
Буда Гаврил

Ответы:

9

Я и мой партнер смогли купить то, что искали. Мы были на правильном пути, вы установили keyValuePairs на собственной стороне.

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

nLL
источник
3
Ошибка одного человека - это особенность другого человека.
Элвин Аренс
7
да, вы правы, nLL - это ACR. Если он не может поделиться решением, почему он использует это сообщество stackoverflow? Я думаю, что это полностью противоречит основному мотиву сообщества. Я обещаю, что если я получу решение, я сразу опубликую его, так как я верю в открытый исходный код.
japanjot Singh
2
@japanjotsingh давай найдем решение вместе
Питер,
3
@nLL Я могу показаться грубым, но, братан, это очень расстраивает всех нас :-(
japanjot singh
2
CallRecLib - это библиотека, предоставляющая взлом для записи телефонных разговоров на Android 6 github.com/ViktorDegtyarev/CallRecLib
Виктор Дегтярев
0

Ваши выводы интересны. Я работал над несколькими небольшими проектами, связанными с AudioRecordAPI. Надеюсь, поможет следующее:

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

public class AudioRecordTool {
        private final int minBufferSize;
        private boolean doRecord = false;
        private final AudioRecord audioRecord;

        public AudioRecordTool() {
            minBufferSize = AudioTrack.getMinBufferSize(16000,
                    AudioFormat.CHANNEL_OUT_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);
            audioRecord = new AudioRecord(
                    MediaRecorder.AudioSource.VOICE_COMMUNICATION,
                    16000,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minBufferSize * 2);
        }

        public void writeAudioToStream(OutputStream audioStream) {
            doRecord = true; //Will dictate if we are recording.
            audioRecord.startRecording();
            byte[] buffer = new byte[minBufferSize * 2];
            while (doRecord) {
                int bytesWritten = audioRecord.read(buffer, 0, buffer.length);
                try {
                    audioStream.write(buffer, 0, bytesWritten);
                } catch (IOException e) {
                    //You can log if you like, or simply ignore it
                   stopRecording();
                }
            }
            //cleanup
            audioRecord.stop();
            audioRecord.release();
        }

        public void stopRecording() {
            doRecord = false;
        }
    }

Я предполагаю, что вам нужно будет сохранить те же разрешения, поскольку пользователи Marshmallow - единственные, кто предоставляет нам доступ к определенным разрешениям, если не почти ко всем классным.

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

Удачи.

AudioRecord API

AudioCaputre API

MediaRecorder API

Джоэл
источник
1
Нет, к сожалению, это не имеет отношения к моему вопросу. Я уже использую класс AudioReocord и много работал с ним.
nLL
ты решил эту проблему? Мне действительно нужно рабочее решение :-(
Nobody8