Tuesday, March 20, 2012

A Method to Capture Android-x86 System Audio

(From BY: XZPETER - 20TH, 2011 )

I have just posted a note about a month ago about video capturing in android system (that post is written in Chinese). This time I will talk something about audio capture.

Actually, I have looking into the issue for some days, and I didn’t find a good way to do that. There are indeed some articles talked about different ways to do audio recording in android by all kinds of APIs, like OpenSLES (this should be supported after gingerbread) orMediaRecorder which belongs to android SDK API. However nearly all of them were recording the mic input, rather than the system output. Meanwhile, I have seen many posts on android-dev asking for the same question, and it seems that there is currently no valid answer on this. So I hope my method will at least make some sense for those whoreally want to capture the system output and don’t know how, like me.

My method is not good, since you have to recompile the android tree, however this is currently the only way for me to achieve my goal. Please tell me if you know other ways to do that. So download the android source tree will be the first step here. And the main idea of the work is: since there is no API for system audio capture, we have to capture it by ourselves, in a lower level, e.g., the android framework.

The source.android.com is the best place for you if you don’t have a android source tree, andUbuntu is highly suggested as the compiling environment. After you have read that, you should have a workable android source tree. here workable means you should be able to compile the tree and launch emulator successfully with the android kernel you just compiled. To achieve that, you may have to establish the environment first (e.g. to installsun-java-6, not the one called openjdk, which may cause strange compilation errors), run some scripts to initialize macros for compilation (like source build/envsetup.sh andlunch), and finally run make -j8 under your android source root. (if you don’t use -j8, which means enable parallel compilation with eight threads, you will possibly have to wait for a really long time before the compilation is done)

1. Play some audio

Before we try to capture the system sound, we do have to find some audio to play, so that we can know we have captured something. This is really a easy task. Just find any mp3 file (e.g. music.mp3), put it under the android /sdcard/ dir with adb push (CAUTION here: you have to use mksdcard command to create a sdcard image, and then use -sdcard option when launching the emulator. By default, the emulator will have no sdcard mounted).

Then, use the Media Scanner of dev tools in the android system to activate the scan process of audio files. After that, in the application called Music there should be a mp3 file ready for you to play. By playing the song, You should be able to hear the sound on your host, although it is actually played on the emulator.

2. Do the system audio capture

Comes to the main part of this article. first of all let’s see the android audio system in the graph.

Android Audio System

I am not a expert on android system, but we can see here that the lowest level of audio system goes to /dev/eac device file, and there is an AudioHardwareInterface, which is possibly hardware related, to access this file directly. And here AudioFlinger seems to be the best part for us to hack, since it is not depending on hardware, and it should has the final mixer output, which is what we want (we don’t want any single sound track, we need the overall system output, isn’t it?). Actually, here AudioFlinger did not only the mixer work, and also resampling stuff.

AudioFlinger is implemented under dir frameworks/base/services/audioflinger/ (Here I assume that we are currently under the root of android source tree). What we are going to do is to find the mixer output. In the file AudioFlinger.cpp, we can seeAudioFlinger::MixerThread::threadLoop(), which is the working thread of the mixer, and this MixerThread is inherited from AudioFlinger::BaseThread. Then, just search the keyword mOutput->write with your best editor (vim, emacs, gedit, whatever), and we will find something like this under the threadLoop() function:

...
mLastWriteTime = systemTime();
mInWrite = true;
mBytesWritten += mixBufferSize;
int bytesWritten = (int)mOutput->write(mMixBuffer, mixBufferSize);
if (bytesWritten < 0) mBytesWritten -= mixBufferSize;
mNumWrites++;
mInWrite = false;
...

That is the very point that mixer output buffer is transferred to hardware related codes I think, and the audio clip is in mMixbuffer, with size mixBufferSize. In this buffer, there are PCM raw audio data with 44100Hz sampling rate, 2 channels and 16 bits little endian as its param.

If you write this buffer out to a file, like /data/wav.raw, you can just use adb pull to retrieve the data file to your host machine and play it with aplay:

$ aplay -t raw -c 2 -f S16_LE -r 44100 wav.raw

Didn’t you heard the sound? That’s it.

No comments: