Android AudioRecord forzando otra transmisión a la fuente de audio MIC

81

Actualización 3: Me he asociado con otro desarrollador y parece que hemos encontrado a alguien que puede hacer esto por una gran suma de dinero. Nos han enviado una apk de prueba y parece funcionar. Seguiremos adelante y compraremos la fuente. Espero que no seamos estafados. Actualizaré una vez que me entere

Actualización 2: Todavía estoy trabajando en ello. Después de días más dolorosos, ahora creo que no hay nada elegante, pero simplemente están usando AudioFlinger ( ver el enlace ) en el lado nativo para llamar a AudioFlinger :: setParameters

Ahora estoy buscando cómo puedo escribir un JNI simple para llamar a AudioFlinger :: setParameters con audio_io_handle_t ioHandle, const String8 & keyValuePairs

Sé lo que puede ser keyValuePairs pero no tengo ni idea de audio_io_handle_t

Actualización: ahora creo que otras aplicaciones podrían estar usando audio QCOM con CAF. Ver audio_extn_utils_send_audio_calibration en el enlace para el mismo

y voice_get_incall_rec_snd_device en el enlace para el mismo

No tengo conocimientos de C / ++. ¿Cómo puedo saber si puedo llamar a estos métodos desde el lado nativo? Dado que otras aplicaciones pueden hacerlo, debe haber una forma.


He estado luchando con esto durante más de 40 días durante al menos 5-6 horas al día. No estoy seguro de si está permitido por SO, pero también estoy feliz de hacer una donación para la respuesta correcta.

Tengo una aplicación de grabación de llamadas que usa la fuente de audio VOICE_CALL. Aunque ASOP no lo implementa / exige, la mayoría de los fabricantes han implementado VOICE_CALL y las aplicaciones que usan la fuente de audio VOICE_CALL funcionaron bien en muchos dispositivos. Eso es hasta Android 6.

Google cambió este comportamiento con Android 6. La apertura de la fuente de audio VOICE_CALL ahora requiere android.permission.CAPTURE_AUDIO_OUTPUT, que solo se otorga a las aplicaciones del sistema.

Esto esencialmente detiene la grabación de llamadas, o debería haberlo hecho. Bueno, lo hace para la mía y para más de 200 aplicaciones de grabación de llamadas además de 3 que han encontrado una manera de sortear esta limitación.

He estado probando esas aplicaciones en muchos teléfonos diferentes con Android 6 y descubrí ciertas características en la forma en que logran grabar.

Todos usan la clase Android AudioRecord y la fuente de audio MIC abierta. Yo también; pero en mi aplicación, solo recibo audio de MIC, no de la otra parte. Lo que descubrí es que me dicen que están emitiendo algún tipo de llamada al sistema justo después o antes de comenzar a grabar.

Eche un vistazo al siguiente registro de una de las aplicaciones que graban con éxito VOICE_CALL, a pesar de que usa MIC para grabar. Parece que la aplicación es de alguna manera para mezclar / enrutar / transmitir / fusionar la fuente de audio VOICE_CALL en el 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)

Como puede ver en la primera línea, comienza con la fuente de audio MIC input_source = 1; routing = -2147483644.

Luego, en la segunda línea, hace algo y se le otorga android.permission.MODIFY_AUDIO_SETTINGS, que es un permiso normal y mi aplicación también lo tiene. Esta parece ser la parte más importante y parece que los 3 están usando JNI para hacer lo que hagan para activar la transmisión / fusión de la fuente de audio VOICE_CALL a MIC y grabar con la API estándar de AudioRecorder.

En la siguiente línea, verá que el hardware de audio comienza a mezclar VOICE_CALL (input_source = 4) a pesar de que han abierto la fuente de audio MIC (1).

He asumido que usaron

AudioManager.setParameters("key=value")

y probé muchas variaciones como

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

sin suerte.

Luego, encontré Android, NDK, enrutamiento de audio, forzando el audio a través de los auriculares y pensé que podrían ser de alguna manera mezclar / enrutar / transmitir / fusionar VOICE_CALL en la sesión actual de AudioRecord y (dado que no tengo conocimiento de C) intenté usar reflation para lograr lo mismo con el siguiente código (nuevamente) sin suerte.

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

}

Obviamente, hay algo que me falta que hace posible la grabación.

Incluso me ofrecí a pagar para obtener esta información, pero todos se negaron. Bastante justo lo he dicho. ¡Lo publicaré una vez / si lo encuentro!

¿Tiene alguna idea de lo que podrían estar haciendo?

nLL
fuente
¿Podrías vincular las aplicaciones que lo hacen correctamente?
rugiente
Publicaste el último dos veces.
shmosel
1
¿Has intentado descompilarlo para poder echarle un vistazo? (solo con fines de investigación;))
Buda Gavril

Respuestas:

9

Mi socio y yo pudimos comprar lo que estábamos buscando. Estábamos en el camino correcto, estableciste keyValuePairs en el lado nativo.

Desafortunadamente, no puedo publicar la fuente debido a las restricciones de licencia de la compañía que lo escribimos para nosotros.

nLL
fuente
3
El error de una persona es la característica de otra.
Elwin Arens
7
ya tienes razón, nLL es ACR, si no puede compartir la solución, ¿por qué está usando esta comunidad de stackoverflow? Creo que esto va totalmente en contra del motivo principal de la comunidad. Prometo que si obtengo la solución, la publicaré de inmediato, ya que creo en el código abierto.
japanjot singh
2
@japanjotsingh busquemos una solución juntos
Peter
3
@nLL Puede que parezca grosero, pero hermano, es muy frustrante para todos nosotros :-(
japanjot singh
2
CallRecLib es una biblioteca que proporciona un truco para grabar conversaciones telefónicas en Android 6 github.com/ViktorDegtyarev/CallRecLib
Viktor Degtyarev
0

Tus hallazgos son interesantes. He trabajado en algunos pequeños proyectos relacionados con la AudioRecordAPI. Con suerte, lo siguiente ayudará:

Supongamos que desea configurar una AudioRecordinstancia para poder grabar en mono de 16 bits a 16 kHz. Puede lograr esto creando una clase con algunos métodos auxiliares, como los siguientes:

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

Supongo que necesitará mantener los mismos permisos, ya que los usuarios de Marshmallow son los únicos que nos otorgan acceso a ciertos permisos, si no a casi todos los interesantes.

Intenta implementar la clase y cuéntanos cómo te fue, también dejo algunas referencias adicionales en caso de que necesites más investigación.

Buena suerte.

API AudioRecord

API AudioCaputre

API MediaRecorder

Joel
fuente
1
No, lamentablemente esto no tiene nada que ver con mi pregunta. Ya estoy usando la clase AudioReocord y trabajé con ella mucho.
nLL
has resuelto este problema? Realmente necesito una solución que funcione :-(
Nadie8