web-dev-qa-db-fra.com

Android AudioRecord forçant un autre flux à la source audio MIC

Mise à jour 3: J'ai établi un partenariat avec un autre développeur et nous semblons trouver quelqu'un qui peut le faire pour une grosse somme d'argent. Ils nous ont envoyé un apk de test et cela semble fonctionner. Nous irons de l'avant et achèterons la source. J'espère que nous n'allons pas être arnaqués. Je mettrai à jour une fois que je le découvrirai

Mise à jour 2: Je travaille toujours dessus. Après des jours plus pénibles, je pense maintenant qu'il n'y a rien d'extraordinaire mais ils utilisent simplement AudioFlinger ( Voir le lien ) du côté natif pour appeler AudioFlinger :: setParameters

Je cherche maintenant à trouver comment écrire un simple JNI pour appeler AudioFlinger :: setParameters avec audio_io_handle_t ioHandle, const String8 & keyValuePairs

Je sais ce qui peut keyValuePairs être mais pas un indice sur audio_io_handle_t

Mise à jour: Je crois maintenant que d'autres applications pourraient utiliser l'audio QCOM avec CAF. Voir audio_extn_utils_send_audio_calibration sur lien pour le même

et voice_get_incall_rec_snd_device à lien pour les mêmes

Je n'ai aucune connaissance en C/++. Comment savoir si je peux appeler ces méthodes du côté natif? Étant donné que d'autres applications le peuvent, il doit y avoir un moyen.


Je me bats avec ça depuis plus de 40 jours pendant au moins 5-6 heures par jour. Je ne sais pas si cela est autorisé par SO mais je suis heureux de faire un don pour la bonne réponse aussi.

J'ai une application d'enregistrement d'appels qui utilise la source audio VOICE_CALL. Bien que ASOP ne l'implémente pas/ne l'exige pas, la plupart des fabricants ont implémenté VOICE_CALL et les applications qui utilisent la source audio VOICE_CALL ont bien fonctionné sur de nombreux appareils. C'est jusqu'à Android 6.

Google a modifié ce comportement avec Android 6. L'ouverture de la source audio VOICE_CALL nécessite désormais Android.permission.CAPTURE_AUDIO_OUTPUT qui n'est accordé qu'aux applications système.

Cela arrête essentiellement l'enregistrement des appels, ou cela aurait dû. Eh bien, c'est le cas pour la mienne et plus de 200 autres applications d'enregistrement d'appels, à l'exception de 3 qui ont trouvé un moyen de contourner cette limitation.

J'ai essayé ces applications sur de nombreux téléphones différents avec Android 6 et j'ai découvert certaines caractéristiques dans la façon dont ils parviennent à enregistrer.

Ils utilisent tous Android classe AudioRecord et source audio MIC ouverte. Moi aussi; mais sur mon application, je ne reçois que l'audio de MIC, pas l'autre partie. Ce que j'ai découvert est révélateur moi qu'ils émettent une sorte d'appel système juste après ou avant de commencer l'enregistrement.

Jetez un œil au journal suivant, l'une des applications qui ont réussi à enregistrer VOICE_CALL, même s'il utilise MIC pour enregistrer. Il semble que l'application réussisse à mélanger/router/diffuser/fusionner la source audio VOICE_CALL dans 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)

Comme vous pouvez le voir sur la première ligne, il commence avec la source audio MIC input_source = 1; routing = -2147483644.

Ensuite, sur la deuxième ligne, il fait quelque chose et obtient Android.permission.MODIFY_AUDIO_SETTINGS qui est une autorisation normale et mon application l'a également. Cela semble être la partie la plus importante et il semble que les 3 utilisent JNI pour faire ce qu'ils font pour déclencher la diffusion/fusion de la source audio VOICE_CALL sur MIC et enregistrer avec l'API AudioRecorder standard

Sur la ligne suivante, vous voyez que le matériel audio commence à mélanger VOICE_CALL (input_source = 4) même s'il a ouvert la source audio MIC (1).

J'ai supposé qu'ils utilisaient

AudioManager.setParameters("key=value")

et essayé de nombreuses variantes telles que

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

sans aucune chance.

Ensuite, j'ai trouvé Android, NDK, routage audio, forçant l'audio à travers le casque et j'ai pensé qu'ils pourraient être un peu comment mélanger/router/diffuser/fusionner VOICE_CALL dans la session AudioRecord en cours et (car il n'y a pas de Connaissance C) a essayé d'utiliser la reflation pour obtenir la même chose avec le code ci-dessous (encore une fois) sans chance.

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

}

Évidemment, il me manque quelque chose qui rend l'enregistrement possible.

J'ai même proposé de payer pour obtenir cette information, tout a refusé. Assez juste, je l'ai dit. Je le publierai une fois/si je le trouve!

Avez-vous une idée de ce qu'ils pourraient faire?

81
nLL

Mon partenaire et moi avons pu acheter ce que nous recherchions. Nous étions sur la bonne voie, vous définissez keyValuePairs du côté natif.

Malheureusement, je ne peux pas publier la source en raison des restrictions de licence de la société que nous avons rédigée pour nous

10
nLL

Vos résultats sont intéressants. J'ai travaillé sur quelques petits projets impliquant l'API AudioRecord. J'espère que les éléments suivants vous aideront:

Supposons que vous souhaitiez configurer une instance AudioRecord afin de pouvoir enregistrer en mono 16 bits à 16 kHz. Vous pouvez y parvenir en créant une classe avec quelques méthodes d'assistance, comme suit:

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

Je suppose que vous devrez garder les mêmes autorisations en place, car les utilisateurs de guimauve sont les seuls à nous accorder l'accès à certains autorisations sinon presque toutes les plus cool.

Essayez d'implémenter la classe et faites-nous savoir comment cela s'est passé, je laisse également quelques références supplémentaires au cas où vous auriez besoin de plus de recherches.

Bonne chance.

API AudioRecord

API AudioCaputre

API MediaRecorder

0
Joel