简介
打算开始写一些Android代码的流程分析,加深一些记忆,理清一些细节,如果刚好能帮助到你,那就更加好了。手头有的最新的Android代码,就是9.0,所以以此为基准。
为什么从AudioRecord开始?
一是这个接口的修改新旧Android版本的差别不大。
二是它要相对简单一些。
所以作为一个开始来分析代码,其中也有一些细节,比如IBinder/Sharememory等,后面再单独开一篇文章分析代码细节。
Java 接口及用法
构造函数:
/**
* Class constructor.
* Though some invalid parameters will result in an {@link IllegalArgumentException} exception,
* other errors do not. Thus you should call {@link #getState()} immediately after construction
* to confirm that the object is usable.
* @param audioSource the recording source.
* See {@link MediaRecorder.AudioSource} for the recording source definitions.
* @param sampleRateInHz the sample rate expressed in Hertz. 44100Hz is currently the only
* rate that is guaranteed to work on all devices, but other rates such as 22050,
* 16000, and 11025 may work on some devices.
* {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED} means to use a route-dependent value
* which is usually the sample rate of the source.
* {@link #getSampleRate()} can be used to retrieve the actual sample rate chosen.
* @param channelConfig describes the configuration of the audio channels.
* See {@link AudioFormat#CHANNEL_IN_MONO} and
* {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is guaranteed
* to work on all devices.
* @param audioFormat the format in which the audio data is to be returned.
* See {@link AudioFormat#ENCODING_PCM_8BIT}, {@link AudioFormat#ENCODING_PCM_16BIT},
* and {@link AudioFormat#ENCODING_PCM_FLOAT}.
* @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written
* to during the recording. New audio data can be read from this buffer in smaller chunks
* than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
* required buffer size for the successful creation of an AudioRecord instance. Using values
* smaller than getMinBufferSize() will result in an initialization failure.
* @throws java.lang.IllegalArgumentException
*/
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes)
其中
- audioSource,定义在MediaRecorder.AudioSource,有很多种不同的source,底层的AudioFlinger会根据不同的source做一些不同的处理,比如VOICE_COMMUNICATION,需要进行回声消除的处理,一般只是想录音的话,选择AudioSource.MIC即可,或者选择AudioSource.DEFAULT,底层一般也是MIC。
- sampleRateInHz,音频采样率,设置一个需要的值,看抓到的音频数据要做什么使用,44100一般用作高品质录音,常规设置16000就可以满足使用,当然后续读取PCM数据量的大小与这里设置的采样率大小是正相关的
- channelConfig: AudioFormat.CHANNEL_IN_STEREO 和 AudioFormat.CHANNEL_IN_MONO,就是双声道和单声道,看需要设置
- audioFormat:表示一个PCM采样点的位数,8位/16位/32位,ENCODING_PCM_8BIT/ENCODING_PCM_16BIT/ENCODING_PCM_FLOAT,一般使用16位
- bufferSizeInBytes:设置底层obtainBuffer/releaseBuffer的缓冲区大小,作为从应用的进程和AudioServer的进程之间共享内存的大小,分配的函数在: AudioFlinger::ThreadBase::TrackBase::TrackBase,一般读取AudioRecord.getMinBufferSize,设置为最小buffersize即可,这个minbuffersize已经包含了两个buffer的pingpong机制,可以满足要求。
跟着Android的代码进去看了一下这个bufferSizeInBytes的用法,需要收回之前AudioRecord比较简单是说法了,代码也是极其复杂的。
read函数
这里我们重点分析一下read函数,看下数据是如何从驱动,一路上到APK的,流程会很长。
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
return read(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING);
}
READ_BLOCKING/READ_NON_BLOCKING 这里可以选择block或者nonbblock的方式,大部分我想应该是BLOCK的,应用方便处理,而且音频是流式的,理论上不会阻塞。
private native final int native_read_in_byte_array(byte[] audioData,
int offsetInBytes, int sizeInBytes, boolean isBlocking);
上面还是在java里面,下面开始进到native代码里面,这里欠一篇java和jni的调用的分析
frameworks/base/core/jni/android_media_AudioRecord.cpp
template <typename T>
static jint android_media_AudioRecord_readInArray(JNIEnv *env, jobject thiz,
T javaAudioData,
jint offsetInSamples, jint sizeInSamples,
jboolean isReadBlocking) {
frameworks/av/media/libaudioclient/AudioRecord.cpp
ssize_t AudioRecord::read(void* buffer, size_t userSize, bool blocking)
...... 省略部分不重要的代码
status_t err = obtainBuffer(&audioBuffer,
blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
......
size_t bytesRead = audioBuffer.size;
memcpy(buffer, audioBuffer.i8, bytesRead);
......
这里就是从audioBuffer里面将数据复制到用户的buffer里面,等下返回给APK,那么audioBuffer又是什么呢?
status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,
struct timespec *elapsed, size_t *nonContig)
status = proxy->obtainBuffer(&buffer, requested, elapsed);
audioBuffer->raw = buffer.mRaw;
注意这里Buffer的定义比较乱,很多结构体里都有这个定义,Google也发现了这个问题,要统一起来,而且还有一个union在里面,为了支持8bit/16bit录音的使用,所以这个audioBuffer->raw与前面的audioBuffer->i8实际上是一个地址。
所以上面的问题pcm地址audioBuffer->raw = buffer.mRaw;
其中buffer是Proxy::Buffer buffer;
sp<AudioRecordClientProxy> proxy = mProxy;
status = proxy->obtainBuffer(&buffer, requested, elapsed);
变成了这个buffer是怎么来的,这里要返回去看这个mProxy是什么?
mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mFrameSize);
其中比较重要的参数是cblk和buffers,,再继续往前追一下,这两个变量的出处。
iMemPointer = output.cblk ->pointer();
cblk = static_cast<audio_track_cblk_t*>(iMemPointer);
buffers = output.buffers->pointer();
mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mFrameSize);
可以看出这个AudioRecordClientProxy,只是方便调用cblk和buffers的Proxy,提供了obtainBuffer的接口,来从真正的共享buffer里面将获得数据,这里面的细节不再追进去了,实际上是通过audio_track_cblk_t的一个共享内存,采用read/write指针的方法(mFront/mRear)来看有多少avail的数据,然后获取指针出来。
重点是output的出处
status_t AudioRecord::createRecord_l(const Modulo<uint32_t> &epoch, const String16& opPackageName)
record = audioFlinger->createRecord(input,
output,
&status);
input里面包括的是我们想设置的参数,output里面是AudioServer实际设置的参数,已经分配的共享内存。
问题的重点是前面的cblk和buffers是什么???
上面还是在APK的进程里面,下面开始进到AudioServer的进程里面了,尴尬不,这里又欠一篇IBinder跨进程调用的分析
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
class IAudioFlinger : public IInterface
virtual sp<media::IAudioRecord> createRecord(const CreateRecordInput& input,
CreateRecordOutput& output,
status_t *status) = 0;
sp<media::IAudioRecord> AudioFlinger::createRecord(const CreateRecordInput& input,
CreateRecordOutput& output,
status_t *status)
//上来先把我们最重要的变量clear一下
output.cblk.clear();
output.buffers.clear();
//从recordTrack当中得到这个共享内存
output.cblk = recordTrack->getCblk();
output.buffers = recordTrack->getBuffers();
再看这个recordTrack,是一个RecordThread创建出来的
recordTrack = thread->createRecordTrack_l(client, input.attr, &output.sampleRate,
input.config.format, input.config.channel_mask,
&output.frameCount, sessionId,
&output.notificationFrameCount,
clientUid, &output.flags,
input.clientInfo.clientTid,
&lStatus, portId);
传了一大堆参数进去,细节不深究,那么getCblk得到了什么?
class RecordTrack : public TrackBase {
class TrackBase : public ExtendedAudioBufferProvider, public RefBase {
sp<IMemory> getCblk() const { return mCblkMemory; }
mCblkMemory = client->heap()->allocate(size);
sp<MemoryDealer> AudioFlinger::Client::heap() const
{
return mMemoryDealer;
}
mMemoryDealer = new MemoryDealer(
audioFlinger->getClientSharedHeapSize(),
(std::string("AudioFlinger::Client(") + std::to_string(pid) + ")").c_str());
getCblk实际得到了一个heap的内存,这个内存分配在AudioServer里面,write在AudioServer里面,read在AudioRecord里面,也就是在应用APK的进程空间。
下面要看下,数据如何从HAL层到mMemoryDealer。
RecordThread -> RecordTrack
bool AudioFlinger::RecordThread::threadLoop()
status_t result = mInput->stream->read(
(uint8_t*)mRsmpInBuffer + rear * mFrameSize, mBufferSize, &bytesRead);
(void)posix_memalign(&mRsmpInBuffer, 32, mRsmpInFramesOA * mFrameSize);
从HAL层里面将数据读到这个mRsmpInBuffer,名义上看是resample用的,因为HAL层出来的采样率与上层需要的不一定一样,所以需要一个resample的过程,这个buffer里面有可能是44100hz的pcm数据。
// loop over each active track
for (size_t i = 0; i < size; i++) {
activeTrack = activeTracks[i];
status_t status = activeTrack->getNextBuffer(&activeTrack->mSink);
一个RecordThread里面可以有几个active的RecordTrack,将mRsmpInBuffer的数据针对不同track的需要resample成不同的PCM数据。
status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
{
ServerProxy::Buffer buf;
buf.mFrameCount = buffer->frameCount;
status_t status = mServerProxy->obtainBuffer(&buf); //还记得前面说的ClientProxy了吗,这里对应到了ServerProxy,是同一个buffer没错了。
buffer->frameCount = buf.mFrameCount;
buffer->raw = buf.mRaw;
if (buf.mFrameCount == 0) {
// FIXME also wake futex so that overrun is noticed more quickly
(void) android_atomic_or(CBLK_OVERRUN, &mCblk->mFlags);
}
return status;
}
好了,这里彻底对上了,将mResamplerBufferProvider里面的原始pcm数据,通过mRecordBufferConverter::convert转换成需要的samplerate。
framesOut = activeTrack->mRecordBufferConverter->convert(
activeTrack->mSink.raw, activeTrack->mResamplerBufferProvider, framesOut);
再看一下这个converter
mRecordBufferConverter = new RecordBufferConverter(
thread->mChannelMask, thread->mFormat, thread->mSampleRate,
channelMask, format, sampleRate);
很清楚,就是从RecordThread的格式转换为RecordTrack的格式,细节不再进去追了,只是一些实现细节。
到这里以后,从AudioServer到APK的数据流已经清楚了,就是从HAL层到这个mMemoryDealer的共享内存,然后通过mServerProxy和AudioRecordClientProxy对这个共享内存的跨进程同步,复制到AudioRecord的audioBuffer当中,然后复制到Java层。
其他接口
AudioRecord.startRecording();
AudioRecord.stop();
控制开始和结束AudioRecord的API,必须使用
AudioRecord.setNotificationMarkerPosition
AudioRecord.setPositionNotificationPeriod
AudioRecord.setRecordPositionUpdateListener
看起来是可以周期性的notify一下app,不确定什么场景,难道APK自己计数一下read到的数据长度不行?非得回调?
AudioRecord.getTimestamp
获取当前录制音频的时间戳
AudioRecord用法samplecode
从Android的cts里面复制出来的代码,cts是一个理解API很好的代码库
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, mHz,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
AudioRecord.getMinBufferSize(mHz,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT) * 10);
mAudioRecord.startRecording();
while (System.currentTimeMillis() - time < RECORD_TIME) {
Thread.sleep(SLEEP_TIME);
mAudioRecord.read(byteData, 0, BUFFER_SIZE); //只是samplecode, 可以读到数据,一般应用不会这样写
}
mAudioRecord.stop();
后记
大致分析了一下AudioRecord的数据流程,不涉及到HAL层的代码,因为各家的实现会有不同,里面的细节很多,包括IBinder调用/cblk的共享内存/JNI的调用都还没有详细分析,留作后面的功课。