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ć?
Odpowiedzi:
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
źródło
Twoje odkrycia są interesujące. Pracowałem nad kilkoma małymi projektami wykorzystującymi
AudioRecord
API. Mamy nadzieję, że poniższe rozwiązania pomogą:Załóżmy, że chcesz skonfigurować
AudioRecord
instancję, 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
źródło