Android AudioRecord erzwingt einen weiteren Stream zur MIC-Audioquelle

81

Update 3: Ich habe mich mit einem anderen Entwickler zusammengetan und wir scheinen jemanden gefunden zu haben, der dies für eine große Geldsumme tun kann. Sie haben uns eine Test-Apk geschickt und es scheint zu funktionieren. Wir werden fortfahren und die Quelle kaufen. Ich hoffe, wir werden nicht betrogen. Ich werde aktualisieren, sobald ich es herausgefunden habe

Update 2: Ich arbeite noch daran. Nach schmerzhafteren Tagen denke ich jetzt, dass nichts Besonderes los ist, aber sie verwenden einfach AudioFlinger ( siehe Link ) auf der nativen Seite, um AudioFlinger :: setParameters aufzurufen

Ich suche jetzt nach Möglichkeiten, wie ich eine einfache JNI schreiben kann, um AudioFlinger :: setParameters mit audio_io_handle_t ioHandle, const String8 & keyValuePairs aufzurufen

Ich weiß, was keyValuePairs sein kann , aber kein Hinweis auf audio_io_handle_t

Update: Ich glaube jetzt, dass andere Apps möglicherweise QCOM-Audio mit CAF verwenden. Siehe audio_extn_utils_send_audio_calibration unter dem Link dazu

und voice_get_incall_rec_snd_device unter link for same

Ich habe keine C / ++ Kenntnisse. Wie kann ich herausfinden, ob ich diese Methoden von der nativen Seite aus aufrufen kann? Da andere Apps das können, muss es einen Weg geben.


Ich habe über 40 Tage lang mindestens 5-6 Stunden am Tag damit zu kämpfen. Ich bin mir nicht sicher, ob dies von SO erlaubt ist, aber ich spende auch gerne für die richtige Antwort.

Ich habe eine Anrufaufzeichnungs-App, die die Audioquelle VOICE_CALL verwendet. Obwohl ASOP dies nicht implementiert / vorschreibt, haben die meisten Hersteller VOICE_CALL implementiert, und Apps, die die Audioquelle VOICE_CALL verwenden, funktionierten auf vielen Geräten einwandfrei. Das ist bis Android 6.

Google hat dieses Verhalten mit Android 6 geändert. Zum Öffnen der Audioquelle VOICE_CALL ist jetzt android.permission.CAPTURE_AUDIO_OUTPUT erforderlich, das nur Systemanwendungen gewährt wird.

Dies stoppt im Wesentlichen die Anrufaufzeichnung oder sollte es haben. Nun, es gilt für meine und mehr als 200 andere Anrufaufzeichnungs-Apps, abgesehen von 3, die einen Weg gefunden haben, diese Einschränkung zu umgehen.

Ich habe diese Apps auf vielen verschiedenen Handys mit Android 6 ausprobiert und bestimmte Eigenschaften bei der Aufzeichnung herausgefunden.

Sie alle verwenden die Android AudioRecord-Klasse und öffnen die MIC-Audioquelle. Ich auch; In meiner App erhalte ich jedoch nur Audio von MIC, nicht von der anderen Partei. Was ich herausgefunden habe, ist mir zu sagen, dass sie kurz nach oder vor Beginn der Aufnahme einen Systemaufruf ausgeben.

Sehen Sie sich das folgende Protokoll einer der Apps an, die VOICE_CALL erfolgreich aufzeichnen, obwohl MIC zum Aufzeichnen verwendet wird. Es sieht so aus, als ob die App das Mischen / Weiterleiten / Streamen / Zusammenführen von VOICE_CALL-Audioquellen in MIC verwaltet.

- 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)

Wie Sie in der ersten Zeile sehen können, beginnt es mit der MIC-Audioquelle input_source = 1; routing = -2147483644.

Dann macht es in der zweiten Zeile etwas und erhält android.permission.MODIFY_AUDIO_SETTINGS, was eine normale Erlaubnis ist und meine App hat es auch. Dies scheint der wichtigste Teil zu sein, und es sieht so aus, als würden alle drei JNI verwenden, um das Streaming / Zusammenführen der VOICE_CALL-Audioquelle zum MIC auszulösen und mit der Standard-AudioRecorder-API aufzunehmen

In der nächsten Zeile sehen Sie, dass die Audio-Hardware beginnt, VOICE_CALL (input_source = 4) zu mischen, obwohl sie die MIC (1) -Audioquelle geöffnet hat.

Ich habe angenommen, dass sie verwendet

AudioManager.setParameters("key=value")

und versuchte viele Variationen wie

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

ohne Glück.

Dann habe ich Android, NDK, Audio-Routing gefunden, Audio durch das Headset erzwungen und dachte, es könnte sein, wie VOICE_CALL in die aktuelle AudioRecord-Sitzung gemischt / weitergeleitet / gestreamt / zusammengeführt wird, und habe (da ich keine C-Kenntnisse habe) versucht, die Reflation zu verwenden das gleiche mit dem folgenden Code (wieder) ohne Glück zu erreichen.

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();
    }

}

Offensichtlich fehlt mir etwas, das die Aufnahme ermöglicht.

Ich habe sogar angeboten zu zahlen, um diese Informationen zu erhalten, alles abgelehnt. Fair genug habe ich gesagt. Ich werde es einmal veröffentlichen / wenn ich es finde!

Haben Sie eine Idee, was sie tun könnten?

nLL
quelle
Könnten Sie die Apps verknüpfen, die dies erfolgreich tun?
Roarster
Sie haben den letzten zweimal gepostet.
Shmosel
1
Haben Sie versucht, es zu dekompilieren, damit Sie einen Blick darauf werfen können? (nur zu Forschungszwecken;))
Buda Gavril

Antworten:

9

Ich und mein Partner konnten kaufen, was wir suchten. Wir waren auf dem richtigen Weg, Sie haben keyValuePairs auf der nativen Seite festgelegt.

Leider kann ich die Quelle aufgrund der Lizenzbeschränkungen des Unternehmens, für das wir sie geschrieben haben, nicht veröffentlichen

nLL
quelle
3
Der Fehler einer Person ist das Merkmal einer anderen Person.
Elwin Arens
7
ya Sie haben Recht nLL ist ACR. Wenn er die Lösung nicht teilen kann, warum verwendet er diese Stackoverflow-Community? Ich denke, das ist völlig gegen das Hauptmotiv der Community. Ich verspreche, wenn ich die Lösung bekomme, werde ich die Lösung sofort veröffentlichen, da ich an Open Source glaube.
Japanjot Singh
2
@ Japanjotsingh lassen Sie gemeinsam Lösung finden
Peter
3
@nLL Ich mag unhöflich klingen, aber Bruder, es ist sehr frustrierend für uns alle :-(
Japanjot Singh
2
CallRecLib bietet eine Bibliothek einen Hack für die Aufzeichnung von Telefongesprächen auf Android 6 github.com/ViktorDegtyarev/CallRecLib
Viktor Degtyarev
0

Ihre Ergebnisse sind interessant. Ich habe an einigen kleinen Projekten mit der AudioRecordAPI gearbeitet. Hoffentlich hilft Folgendes:

Angenommen, Sie möchten eine AudioRecordInstanz einrichten, damit Sie mit 16 kHz in 16-Bit-Mono aufnehmen können. Sie können dies erreichen, indem Sie eine Klasse mit einigen Hilfsmethoden wie den folgenden erstellen:

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;
        }
    }

Ich vermute, Sie müssen die gleichen Berechtigungen beibehalten, da Marshmallow- Benutzer die einzigen sind, die uns Zugriff auf bestimmte Berechtigungen gewähren, wenn nicht fast alle coolen.

Versuchen Sie, die Klasse zu implementieren, und lassen Sie uns wissen, wie es gelaufen ist. Außerdem hinterlasse ich einige zusätzliche Referenzen, falls Sie weitere Nachforschungen anstellen müssen.

Viel Glück.

AudioRecord API

AudioCaputre API

MediaRecorder API

Joel
quelle
1
Nein, das hat leider nichts mit meiner Frage zu tun. Ich benutze bereits die AudioReocord-Klasse und habe intensiv damit gearbeitet.
nLL
Hast du dieses Problem gelöst? Ich brauche wirklich eine funktionierende Lösung :-(
Nobody8