Android AudioRecord 强制另一个流到 MIC 音频源

84

更新3: 我已经和另一位开发者合作,我们似乎找到了一个可以用大笔资金完成此项工作的人。他们已经向我们发送了一个测试apk,看起来可以工作。我们将继续购买源代码。希望我们不会被骗。我一旦发现就会更新

更新2: 还在努力中。经过更多痛苦的日子,我现在认为没有什么花哨的东西,但是他们只是在本地使用AudioFlinger (请参见链接) 调用 AudioFlinger::setParameters

现在我正在寻找如何编写一个简单的JNI来调用 AudioFlinger::setParameters,其中包括 audio_io_handle_t ioHandle, const String8& keyValuePairs

我知道keyValuePairs是什么,但是对audio_io_handle_t毫无头绪

更新: 我现在相信其他应用程序可能正在使用具有CAF的QCOM音频。在同一链接中,请查看audio_extn_utils_send_audio_calibration

同一链接中的voice_get_incall_rec_snd_device

我没有C / ++知识。如何判断我是否可以从本地端调用这些方法? 既然其他应用程序可以,肯定有办法。


我已经挣扎了40多天,每天至少5-6个小时。我不确定SO是否允许,但我很乐意为正确答案捐款。

我有一个通话录音应用程序,使用VOICE_CALL音频源。尽管ASOP没有实现/强制执行它,但大多数制造商已经实现了VOICE_CALL,并且使用VOICE_CALL音频源的应用程序在许多设备上工作正常。直到Android 6。

Google通过Android 6改变了这种行为。打开VOICE_CALL音频源现在需要android.permission.CAPTURE_AUDIO_OUTPUT权限,该权限仅授予系统应用程序。

这基本上阻止了通话录音,或者应该这样做。对于我的应用程序和另外200多个通话录音应用程序来说都是这样,除了3个已找到绕过此限制的方法。

我已经在许多不同的Android 6手机上尝试了这些应用程序,并发现了它们成功录制的某些特征。

它们都使用Android AudioRecord类并打开MIC音频源。我也是; 但是在我的应用程序中,我只从MIC获取音频,而不是对方的声音。我所发现的告诉我,他们在开始录制之后或之前发出了某种系统调用。

请查看以下来自成功记录VOICE_CALL的其中一个应用程序的日志表格,即使它使用MIC进行录制。看起来应用程序可以将VOICE_CALL音频源混合/路由/流式传输/合并到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)

在第一行中,您可以看到它以MIC音频源 input_source=1;routing=-2147483644开头。

然后,在第二行中,它执行了某些操作并获得了android.permission.MODIFY_AUDIO_SETTINGS权限,这是正常的权限,我的应用程序也有。这似乎是最重要的部分,看起来所有三个应用程序都使用JNI来触发流媒体/合并VOICE_CALL音频源到MIC并记录标准AudioRecorder API。

在下一行中,您可以看到音频硬件开始混合VOICE_CALL (input_source=4),即使他们已经打开了MIC(1)音频源。

我假设他们使用了

AudioManager.setParameters("key=value")

并尝试了许多变化,例如

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

然后,我发现了Android、NDK、音频路由、将音频强制输出到耳机,认为它们可能以某种方式将VOICE_CALL混合/路由/流式传输/合并到当前的AudioRecord会话中(因为没有C语言知识),并尝试使用下面的代码通过反射实现相同的功能(再次)没有成功。

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

}

显然,有些东西我没有掌握,才使得录音成为可能。

我甚至愿意付费得到这些信息,但都被拒绝了。没关系,我说过,如果找到了,我会公开发布的!

你是否知道他们可能在做什么?


你能否提供成功实现此功能的应用程序链接? - roarster
你重复发布了最后一个。 - shmosel
当我使用AudioManager.setParameters时,我会得到类似“adev_..setparamaters..”的日志,并且我输入的键值对会输出到日志中。当我观察其他应用程序的日志时,我看到“in_set_parameters:audio_hw_primary: in_set_parameters:..”,但是从音频设备中没有日志输出,它从未被调用。此外,所有3个应用程序在创建AudioRecord对象后都执行jni调用。 - nLL
当我创建audioRecord对象时,日志中只显示“in_set_parameters:audio_hw_primary:”,我认为它在内部调用了in_set_parameters。如果我使用MIC音频源,则会看到“audio_hw_primary: in_set_parameters: enter: kvpairs = format = 1; input_source = 1; routing = -2147483644”。在其他应用程序中,我看到相同的内容,但是在此之后,我看到“D / PermissionCache:检查android.permission.MODIFY_AUDIO_SETTINGS是否适用于uid”和“in_set_parameters:enter:kvpairs = input_source = 4; routing = -2147483584; format = 1”。这使我相信它们使用JNI,因为根据日志,AudioRecord没有第二次创建。 - nLL
1
你尝试过反编译它,这样你就可以偷看一下了吗?(仅限于研究目的 ;) ) - Buda Gavril
显示剩余6条评论
2个回答

9

我和我的合作伙伴成功购买了我们需要的东西。我们走的是正确的道路,您在本地端设置了keyValuePairs。

不幸的是,由于我们委托公司的许可限制,我无法公开源代码。


3
一个人的缺陷可能是另一个人的特色。 - Elwin Arens
8
你说得对,nLL是ACP(可能指某个用户)。如果他不能分享解决方案,那为什么还要使用Stack Overflow社区呢?我认为这完全违背了社区的主要目的。我保证如果我得到解决方案,我会立即发布解决方案,因为我相信开源。 - japanjot singh
2
@japanjotsingh 让我们一起找到解决方案。 - Peter
3
我可能听起来有些无礼,但兄弟,这对我们所有人来说都非常令人沮丧 :-( - japanjot singh
2
CallRecLib是一个库,为Android 6提供了一种记录电话对话的方法。https://github.com/ViktorDegtyarev/CallRecLib - Viktor Degtyarev
显示剩余17条评论

0

你的发现很有趣。我曾经参与过几个涉及AudioRecord API的小项目。希望以下内容能够帮到你:

假设你想要设置一个AudioRecord实例,以便你可以在16kHz的16位单声道录制音频。你可以创建一个带有一些辅助方法的类来实现这个目标,如下所示:

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

我猜你需要保持相同的权限,因为只有Marshmallow用户才能授予我们访问某些权限,如果不是几乎所有很酷的权限。

尝试实现这个类并告诉我们它的效果如何,如果需要更多的研究,我也会留下一些额外的参考资料。

祝你好运。

AudioRecord API

AudioCaputre API

MediaRecorder API


1
不好意思,这与我的问题无关。我已经在使用AudioRecord类并且对它进行了广泛的操作。 - nLL
你解决了这个问题吗?我真的需要一个可行的解决方案 :-( - Homayoon Ahmadi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接