Android AudioRecord wymusza kolejny strumień do źródła dźwięku MIC

82

Aktualizacja 3: Współpracowałem z innym deweloperem i wydaje się, że znaleźliśmy kogoś, kto może to zrobić za dużą sumę pieniędzy. Wysłali nam testową aplikację i wydaje się, że działa. Pójdziemy dalej i kupimy źródło. Mam nadzieję, że nie zostaniemy oszukani. Zaktualizuję, gdy się dowiem

Aktualizacja 2: Nadal nad tym pracuję. Po bardziej bolesnych dniach myślę, że nie ma nic szczególnego, ale po stronie natywnej używają AudioFlinger ( zobacz link ), aby wywołać AudioFlinger :: setParameters

Teraz szukam sposobu, w jaki mogę napisać prosty JNI do wywołania AudioFlinger :: setParameters z audio_io_handle_t ioHandle, const String8 i keyValuePairs

Wiem, co może być keyValuePairs, ale nie mam pojęcia o audio_io_handle_t

Aktualizacja: teraz uważam, że inne aplikacje mogą używać dźwięku QCOM z CAF. Zobacz audio_extn_utils_send_audio_calibration pod linkiem dla tego samego

i voice_get_incall_rec_snd_device pod linkiem dla tego samego

Nie mam wiedzy na temat C / ++. Jak mogę się dowiedzieć, czy mogę wywołać te metody ze strony natywnej? Ponieważ inne aplikacje mogą, musi istnieć sposób.


Zmagam się z tym przez ponad 40 dni przez co najmniej 5-6 godzin dziennie. Nie jestem pewien, czy jest to dozwolone przez SO, ale jestem szczęśliwy, że mogę również przekazać darowiznę za poprawną odpowiedź.

Mam aplikację do nagrywania rozmów, która korzysta ze źródła dźwięku VOICE_CALL. Chociaż ASOP tego nie wdraża / nie nakazuje, większość producentów zaimplementowała VOICE_CALL, a aplikacje korzystające ze źródła dźwięku VOICE_CALL działały dobrze na wielu urządzeniach. Tak jest do Androida 6.

Google zmieniło to zachowanie w systemie Android 6. Otwarcie źródła dźwięku VOICE_CALL wymaga teraz android.permission.CAPTURE_AUDIO_OUTPUT, który jest przyznawany tylko aplikacjom systemowym.

To zasadniczo zatrzymuje nagrywanie rozmów lub powinno. Cóż, działa to w przypadku mojej i ponad 200 innych aplikacji do nagrywania rozmów, z wyjątkiem 3, które znalazły sposób na obejście tego ograniczenia.

Wypróbowałem te aplikacje na wielu różnych telefonach z systemem Android 6 i odkryłem pewne cechy w sposobie, w jaki udaje im się nagrywać.

Wszystkie używają klasy Android AudioRecord i otwartego źródła dźwięku MIC. Ja też; ale w mojej aplikacji dźwięk jest odbierany tylko z MIC, a nie z drugiej strony. Dowiedziałem się, że wysyłają jakieś wywołanie systemowe tuż po lub przed rozpoczęciem nagrywania.

Spójrz na następujący dziennik jednej z aplikacji, które pomyślnie nagrywały VOICE_CALL, mimo że do nagrywania używa MIC. Wygląda na to, że aplikacja pozwala na miksowanie / trasowanie / przesyłanie strumieniowe / łączenie źródła dźwięku VOICE_CALL z MIC.

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

Jak widać w pierwszym wierszu, zaczyna się od MIC audio source input_source = 1; routing = -2147483644.

Następnie w drugiej linii robi coś i otrzymuje android.permission.MODIFY_AUDIO_SETTINGS, co jest normalnym zezwoleniem i moja aplikacja też je ma. Wydaje się, że jest to najważniejsza część i wygląda na to, że wszyscy 3 używają JNI do robienia wszystkiego, co robią, aby wyzwolić przesyłanie strumieniowe / łączenie źródła dźwięku VOICE_CALL do MIC i nagrywać za pomocą standardowego API AudioRecorder

W następnym wierszu widać, że sprzęt audio zaczyna miksować VOICE_CALL (input_source = 4), mimo że otworzył źródło dźwięku MIC (1).

Zakładałem, że używali

AudioManager.setParameters("key=value")

i wypróbowałem wiele odmian, takich jak

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

bez szczęścia.

Następnie znalazłem Android, NDK, routing audio, wymuszając dźwięk przez zestaw słuchawkowy i pomyślałem, że mogą to być sposoby mieszania / trasowania / przesyłania strumieniowego / łączenia VOICE_CALL z bieżącą sesją AudioRecord i (ponieważ nie mam wiedzy na temat C) próbowałem użyć reflacji aby osiągnąć to samo z poniższym kodem (ponownie) bez powodzenia.

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

}

Oczywiście brakuje mi czegoś, co umożliwia nagrywanie.

Zaproponowałem nawet, że zapłacę za te informacje, ale wszyscy odmówili. W porządku, powiedziałem. Opublikuję to raz / jeśli znajdę!

Czy masz pojęcie, co mogą robić?

nLL
źródło
czy mógłbyś połączyć aplikacje, które robią to pomyślnie?
roarster
Ostatnie opublikowałeś dwa razy.
shmosel
1
czy próbowałeś go zdekompilować, aby móc zerknąć? (tylko do celów badawczych;))
Buda Gavril

Odpowiedzi:

9

Ja i mój partner mogliśmy kupić to, czego szukaliśmy. Byliśmy na dobrej drodze, ustawiłeś keyValuePairs po stronie natywnej.

Niestety nie mogę opublikować źródła ze względu na ograniczenia licencyjne firmy, którą dla nas napisaliśmy

nLL
źródło
3
Błąd jednej osoby to funkcja innej osoby.
Elwin Arens
7
tak, masz rację nLL to ACR, jeśli nie może udostępnić rozwiązania, dlaczego korzysta z tej społeczności stackoverflow? Myślę, że jest to całkowicie sprzeczne z głównym motywem społeczności. Obiecuję, że jeśli otrzymam rozwiązanie, od razu opublikuję je, ponieważ wierzę w open source.
japanjot singh
2
@japanjotsingh, razem znajdziemy rozwiązanie
Peter
3
@nLL Może brzmię niegrzecznie, ale brachu, to jest bardzo frustrujące dla nas wszystkich :-(
japanjot singh
2
CallRecLib to biblioteka udostępniająca hack do nagrywania rozmów telefonicznych na Androidzie 6 github.com/ViktorDegtyarev/CallRecLib
Viktor
0

Twoje odkrycia są interesujące. Pracowałem nad kilkoma małymi projektami wykorzystującymi AudioRecordAPI. Mamy nadzieję, że poniższe rozwiązania pomogą:

Załóżmy, że chcesz skonfigurować AudioRecordinstancję, aby można było nagrywać w 16-bitowym trybie mono z częstotliwością 16 kHz. Możesz to osiągnąć, tworząc klasę z kilkoma metodami pomocniczymi, takimi jak:

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

Domyślam się, że będziesz musiał zachować te same uprawnienia, ponieważ użytkownicy Marshmallow są jedynymi, którzy dają nam dostęp do niektórych uprawnień, jeśli nie prawie wszystkich fajnych.

Spróbuj zaimplementować klasę i daj nam znać, jak poszło. Zostawiam też dodatkowe referencje na wypadek, gdybyś potrzebował więcej badań.

Powodzenia.

AudioRecord API

AudioCaputre API

MediaRecorder API

Joel
źródło
1
Nie, niestety nie ma to nic wspólnego z moim pytaniem. Używam już klasy AudioReocord i intensywnie z nią pracowałem.
nLL
rozwiązałeś ten problem? Naprawdę potrzebuję działającego rozwiązania :-(
Nikt