Android AudioRecord forcing another stream to MIC audio source
Asked Answered
B

2

84

Update 3: I have partnered with another developer and we seem to found somebody who can do this for a large sum of money. They have sent us a test apk and it seems to work. We will go ahead and purchase the source. I hope we are not going to be scammed. I'll update once I find out

Update 2: Still working on it. After more painful days I now think there is nothing fancy going on but they are simply using AudioFlinger (See the link) on the native side to call AudioFlinger::setParameters

I am now looking to find how I can write a simple JNI to call AudioFlinger::setParameters with audio_io_handle_t ioHandle, const String8& keyValuePairs

I know what can keyValuePairs be but not a clue about audio_io_handle_t

Update: I now believe other apps might be using QCOM audio with CAF. See audio_extn_utils_send_audio_calibration at link for same

and voice_get_incall_rec_snd_device at link for same

I have no C/++ knowledge. How can I find out if I can call these methods from native side? Since other apps can, there must be a way.


I've been struggling with this over 40 days for at least 5-6 hours a day. I am not sure if it is permitted by SO but I am happy to make donation for the correct answer too.

I have a call recording app that uses VOICE_CALL audio source. Although ASOP does not implement/mandate it, most manufacturers have implemented VOICE_CALL and apps that use VOICE_CALL audio source worked fine on many devices. That is until Android 6.

Google changed this behavior with Android 6. Opening VOICE_CALL audio source now require android.permission.CAPTURE_AUDIO_OUTPUT which is only granted to system applications.

This essentially stops call recording, or it should have. Well, it does for mine and 200+ other call recording apps apart from 3 who have found a way to get around this limitation.

I have been trying those apps on many different phones with Android 6 and found out certain characteristics in the way they manage to record.

They all use Android AudioRecord class and open MIC audio source. I do too; but on my app, I only get audio from MIC, not the other party. What I've found out is telling me that they are issuing some sort of system call just after or before starting recording.

Have a look at the following log form one of the apps that successfully record VOICE_CALL, even though it uses MIC to record. It looks like app is some how managing to mix/route/stream/merge VOICE_CALL audio source in to 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)

As you can see in the first line it is starting with MIC audio source input_source=1;routing=-2147483644.

Then, on the second line it does something and get granted android.permission.MODIFY_AUDIO_SETTINGS which is normal permission and my app has it too. This seems to be the most important part and it looks like all 3 are using JNI to do what ever they do to trigger streaming/merging of VOICE_CALL audio source to MIC and record with standart AudioRecorder API

On the next line you see that audio hardware starting to mixing VOICE_CALL (input_source=4) even though they have opened MIC(1) audio source.

I have assumed they used

AudioManager.setParameters("key=value")

and tried many variations such as

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

without any luck.

Then, I've found Android, NDK, Audio routing, forcing audio through the headset and thought they might be some how mix/route/stream/merge VOICE_CALL in to current AudioRecord session and (since have no C knowledge) tried to use reflation to achieve same thing with below code (again) without luck.

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

}

Obviously there is something I am missing that makes recording possible.

I even offered to pay to get this information, all refused. Fair enough I've said. I will be publishing it once/if I find it!

Do you have any idea as to what they might be doing?

Broomfield answered 6/5, 2016 at 19:52 Comment(11)
could you link the apps that do it successfully?Anuradhapura
Sure play.google.com/store/apps/details?id=com.skvalex.callrecorder play.google.com/store/apps/details?id=com.boldbeast.recorder play.google.com/store/apps/details?id=com.boldbeast.recorder They do not use root methods for this issueBroomfield
You posted the last one twice.Pyrogenous
play.google.com/store/apps/…Broomfield
What makes you think that using JNI would be necessary? All AudioManager.setParameters does is call AudioSystem.setParameters, which in turn calls the corresponding native function, which then calls the AudioFlinger. Have you tried specifying the routing as a positive value (i.e. 0x80000040)? Btw, the DTS repo you linked just seems to be a fork of this tag from the CodeAurora Forum (which is where the source deliveries for Qualcomm platforms usually comes from, or at least used to).Adhere
When I use AudioManager.setParameters I get log like "adev_..setparamaters.." and what I enter as key-value pairs output to the log. When I watch other apps' log i see "in_set_parameters:audio_hw_primary: in_set_parameters:.." there ise no log output from audio device, it never called. Also, all 3 apps execute a jni call just after creating AudioRecord objectBroomfield
I only see "in_set_parameters:audio_hw_primary:" on my logs when I create audioRecord object whic I asusme it calls in_set_parameters internally. If I use MIC audio source I see "audio_hw_primary: in_set_parameters: enter: kvpairs=format=1;input_source=1;routing=-2147483644". On the other apps I see the sma e but, just after that I see "D/PermissionCache: checking android.permission.MODIFY_AUDIO_SETTINGS for uid" and "in_set_parameters: enter: kvpairs=input_source=4;routing=-2147483584;format=1". This leads me bellive they use JNI as according to logs AudioRecord is not created second timeBroomfield
have you tried to decompile it so you can take a peek? (for research purposes only ;) )Fontainebleau
#45881454Elisabeth
I also stuck in Recording issue If you have any idea then please share...Elisabeth
#59339828 can you please see thisMayne
B
9

Me and my partner were able to purchase what we were looking for. We were on right path, you set keyValuePairs on the native side.

Unfortunately I cannot publish the source because of the licensing restrictions from the company we had it written for us

Broomfield answered 28/5, 2016 at 19:2 Comment(22)
One person's bug is another person's feature.Metapsychology
Please. Do you tell me company's name that you purchase?Deane
nLL could you direct me to the company details if possible. We are in requirement of something similar on a smaller scale although for an internal college application. Thanks and regards.Shirr
I would also be interested in knowing who you purchased this from for an internal project which has just gone wrong since phones updated to Android M. Or a few more hints would be nice too ;-)Ila
I would be interested in knowing this as well. I would very much appreciate an inbox.Goalie
Hey, Can you please share with me the company?Culinary
Bro please tell me from which company you got code?Surpassing
I am also interested in contacting that company.Athiste
Hey nLL i have been tracking all the updates from you here and ACR blog, can you please guide me to the right path ? I am not asking for source code that i will do it myself all i want is just let me know how you are merging voice_call to mic.As far as your above post is considered here you have mentioned use of NDK(I can work on that) but can you let me know which cpp files i have to work on.I am also stuck on exactly the same problem which you have finally resolved.Luigi
Hello nLL i am also stuck in this issue please tell to how i can buy the licensing of the C\C++ code or anyone who is interested to sell the licensing.Thackeray
i think nLL its from ACR .Callas
ya you are right nLL is ACR , If he cant share the solution why is he using this stackoverflow community ? I think this is totally against the communities main motive.I promise if i will get the solution i will post the solution right away as i believe in open source.Luigi
@japanjotsingh lets find solution togetherCallas
@japanjot-singh we did not find the solution here and the reason is in my replyBroomfield
@Broomfield I might be sounding rude but bro it is very frustrating for all of us :-(Luigi
@Broomfield May i know how much you paid for this solution? atleast this you can shareLuigi
Not at liberty to disclose that either. However it took us total of 8 monthsBroomfield
@Callas yeah lets find solution, do you know ndk?Luigi
@japanjotsingh no , but im trying.Callas
@Broomfield can you let us know from where you purchased this solution?Luigi
CallRecLib is a library providing a hack for recording phone conversations on Android 6 github.com/ViktorDegtyarev/CallRecLibKrum
Hello guys,Actually I also cannot able to find the solution for recording calls. I cannot able to record the voice on the other side in all cases.Actually I have tried the code in Lollipop(Below Marshmallow), Pie(Above Marshmallow).It failed in all the cases.Can you help me on this please ?Turnstone
I
0

Your findings are interesting. I have worked on a few small projects involving the AudioRecord API. Hopefully, the following will help:

Say you want to setup an AudioRecord instance so that you can record in 16-bit mono at 16kHz. You can achieve this by creating a class with a few helper methods, like the following:

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

I am guessing you will need to keep the same permissions in place, since Marshmallow users are the only ones that grant us access to certain permissions if not almost all of the cool ones.

Try implementing the class and let us know how it went, also i'm leaving some extra references in case you need more research.

Good luck.

AudioRecord API

AudioCaputre API

MediaRecorder API

Ivanivana answered 18/5, 2016 at 1:56 Comment(2)
No, unfortunately this has nothing to do with my question. I am already using AudioReocord class and worked with it extensively.Broomfield
have you solved this problem? I really need a working solution :-(Lucillalucille

© 2022 - 2024 — McMap. All rights reserved.