Android源码分析:录音AudioRecording

Auido Record

录音功能的使用,在Java层可以调用AndroidSDK中的API—-android.media.AudioRecord来实现;在native层,可以调用C++中的AudioRecord类。

调用关系结构图如下:

 

 

AudioRecord
AudioRecord.cpp

 

调用API

android.media.AudioRecord

Apps

JNI
android_media_AudioRecord.cpp

 

IAudioRecord
IAudioRecord.cpp

Binder IPC

 

AudioFlinger::RecordHandle

BnAudioRecord

BnInterface<IAudioRecord>

Java

C/C++

 

从图中可以看出,Java层通过JNI调用到native层的AudioRecord,后者通过IAudioRecord接口跨进程调用到server侧(AudioFlinger)的RecordHandle

server侧负责启动录音线程,将从录音数据源里采集的音频数据填充到共享内存缓冲区,然后应用程序侧从其里面拷贝数据到自己的缓冲区,见后面详述。

在应用程序APP中,使用android.media.AudioRecord的构造函数创建一个AudioRecord,构造函数如下:

public AudioRecord(int audioSource, //指定声音源
int sampleRateInHz,//
指定采样率
int channelConfig,//
指定声道数,单声道还是双声道
int audioFormat, //
指定8PCM还是16PCM
int bufferSizeInBytes)//
缓冲区大小

在创建Java对象AudioRecord时,会调用native_setup函数,在其JNI层的实现中,会创建一个对应的C++AudioRecord对象,并调用后者的set函数。set函数先做些参数检查和调整,接着会获取输入句柄,背后的操作则是使用设备管理器得到适当的输入设备;

audio_io_handle_t input = AudioSystem::getInput(inputSource, sampleRate, format, channels, udioSystem::audio_in_acoustics)flags);

它会调用到AudioFlinger侧,先是在AudioPolicyService中确定输入设备(见AudioPolicyManagerBase::getInput),然后到AudioFlinger中打开输入,见AudioFlinger::openInput中的代码片段,通过音频硬件得到一个音频输入流:

AudioStreamIn *input = mAudioHardware->openInputStream(*pDevices, (int *)&format, &channels, &samplingRate, &status, (AudioSystem::audio_in_acoustics)acoustics);

openInput中,再接着就是创建录音线程,并将其添加到录音线程列表中:

thread = new RecordThread(this, input, reqSamplingRate, reqChannels, id);
mRecordThreads.add(id, thread);

set函数中,第二个重要的操作是得到IAudioRecord接口代理对象,见AudioRecord::openRecord函数,它跨进程调用到audioflingeropenRecord,得到一个指向IAudioRecord接口的指针(根据Binder章节叙述,它实际指向的是子类BpAudioRecord这个代理对象),利用它获取共享内存控制块(见管理双方共享缓冲区的数据结构audio_track_cblk_t,同AudioTrack),还可以利用它进行录音的(start/stop)控制。set函数中的代码片段如下:

// create the IaudioRecord
status = openRecord(sampleRate, format, channelCount,
frameCount, flags, input);

它调用到AudioFlinger侧则是创建RecordTrackRecordHandle等对象,以供使用。

set函数中第三个重要的操作是创建一个ClientRecordThread线程对象,用于处理录音过程中的各种事件,调用回调函数将事件发送给Java层,比如录音到达某个位置时,就会调用Java中指定的listener(见OnRecordPositionUpdateListener)。

if (cbf != 0) {//若指定了回调函数
mClientRecordThread = new ClientRecordThread(*this, threadCanCallJava);//
创建线程
if (mClientRecordThread == 0) {
return NO_INIT;//
未创建成功
}
}

那么C++AudioRecord如何实现对Java层中listener的回调呢?原来在在JNI层中,调用AudioRecord::set时,为其指定了回调函数:recorderCallback(参见文件android_media_AudioRecord.cpp):

static void recorderCallback(int event, void* user, void *info) {
if (event == AudioRecord::EVENT_MORE_DATA) {
// set size to 0 to signal we’re not using the callback to read more data
AudioRecord::Buffer* pBuff = (AudioRecord::Buffer*)info;
pBuff->size = 0;
} else if (event == AudioRecord::EVENT_MARKER) {
audiorecord_callback_cookie *callbackInfo = (audiorecord_callback_cookie *)user;
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (user && env) {
env->CallStaticVoidMethod(
callbackInfo->audioRecord_class,
javaAudioRecordFields.postNativeEventInJava,//
通过虚拟机调用Java层的事件处理函数
callbackInfo->audioRecord_ref, event, 0,0, NULL);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
}
} else if (event == AudioRecord::EVENT_NEW_POS) {
audiorecord_callback_cookie *callbackInfo = (audiorecord_callback_cookie *)user;
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (user && env) {
env->CallStaticVoidMethod(
callbackInfo->audioRecord_class,
javaAudioRecordFields.postNativeEventInJava,//
通过虚拟机调用Java层的事件处理函数
callbackInfo->audioRecord_ref, event, 0,0, NULL);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
}
}
}

可见,这个回调函数调用了Java中的函数,也就是JavaAudioRecord的函数postEventFromNative,由其将event事件发送(post)到java消息队列中。

线程ClientRecordThreadthreadLoop函数调用函数AudioRecord::processAudioBuffer去处理发生的事件,也就是说调用上面指定的回调函数去处理,也就是将消息事件post送到Java层的消息队列里,然后由Java线程里handler去处理,也就是调用指定的listener

可见,在创建实例的过程中,首先是获取输入设备,打开音频输入流,创建;录音线程;然后获取跨进程调用接口IAudioRecord代理对象指针,获取双方通讯的共享内存控制块;最后指定回调函数,创建用于发送事件的线程。这些,都是为开始录音做准备的。

 

录音的开始过程

Java层中调用 startRecording()和 stop()进行开始/停止控制,这将调用到C++层的AudioRecord::startAudioRecord::stop函数。

开始录音时,需要指定输入设备,而输入设备的选择是由音频策略管理器来完成,选择完之后要通过指定参数的方式告诉HAL音频硬件进行切换。完整的调用顺序如下:

android.media.AudioRecord.startRecording -> JNI层 ->AudioRecord::start -> RecordHandle::start -> RecordTrack::start -> RecordThread::start -> AudioSystem::startInput -> AudioPolicyService::startInput -> AudioPolicyManagerBase::startInput(或平台厂商自己的音频策略管理器的startInput) -> AudioPolicyService::setParameters(此处通过发送command,由另一个线程去处理) -> AudioFlinger::setParameters(此处指定的参数是音频策略管理器为AudioParameter::keyRoutingAudioParameter::keyInputSource选定的输入设备参数) -> mAudioHardware->setParameters(调用平台厂家实现的HAL层的音频硬件去指定输入设备)。可以用下图表示该调用关系:

 

android.media.AudioRecord.startRecording

 

Binder IPC

 

JNI

 

AudioRecord::start

RecordHandle::start

 

RecordTrack::start

RecordThread::start

 

AudioSystem::startInput

AudioPolicyService::startInput

AudioPolicyManagerBase(或厂家自己的管理器)::startInput

AudioFlinger::setParameters

AudioPolicyService::setParameters

异步(通过向队列发送命令,然后由另一个线程去处理)

 

硬件适配层(HAL)的音频硬件(接口 AudioHardwareInterface的子类,由硬件平台厂家实现)

 

 

左侧运行在应用程序进程之中,右侧的两栏(AudioFlingerAudioPolicyService)运行在mediaserver进程中。应用程序调用到AudioFlinger,然后由AudioPolicyService确定输入设备,并将其作为设置到音频硬件中,进行相应的切换。

当上面的切换完成后,在应用程序侧的AudioRecordstart函数中会让其线程ClientRecordThread开始工作;在AudioFlinger中,录音线程的start函数(RecordThread::start)需要发送信号唤醒正在mWaitWorkCV上等待的线程循环,开始录音工作。录音线程循环进入睡眠等待的代码片段如下:

// go to sleep
mWaitWorkCV.wait(mLock);//
等待
LOGV(“RecordThread: loop starting”);
continue;

start函数中唤醒线程循环的代码如下:

// signal thread to start
LOGV(“Signal record thread”);
mWaitWorkCV.signal();//
在切换完输入设备后,唤醒线程

录音线程RecordThread的线程循环函数threadLoop比较复杂,要考虑单声道和双声道,要考虑16位还是8位的PCM数据,还要考虑是否需要重采样等因素。但总体原理是,使用HAL中的音频输入流AudioStreamInread函数读取音频数据数据到缓冲区mRsmpInBuffer

mBytesRead = mInput->read(mRsmpInBuffer, mInputBytes);

接着会处理这些数据(如重采样等),在处理完后,将这些数据送到共享缓冲区中(调用了RecordTrack::getNextBuffer用来获取下一个可用来填写的缓冲区),这些都是在线程循环函数中完成。共享缓冲区有数据了,应用程序侧的AudioRecord就可以从中读取数据了。

下图是音频数据的示意图,左侧的AudioTrack位于应用程序进程中,在调用start设置完输入设备并启动线程ClientRecordThread后,就可以使用AudioTrackread函数去从共享缓冲区中读取数据,也就是将数据共享缓冲区中的数据拷贝到read函数提供的缓冲区(第一个参数)中;而在AudioFlinger中,录音线程RecordThreadAudioStreamIn来读取HAL音频硬件的采样数据,然后送到共享缓冲区(由audio_track_cblk_t管理)。如前所述,应用程序侧的线程提供了回调函数ClientRecordThread则提供了各种事件发送功能,如采样过快而未来的及读的OverRun、到带某位置等。

AudioTrackread函数

callback

audio_track_cblk_t管理的共享缓冲区

AudioStreamIn

HAL音频硬件

 

另,在AudioTrack中,使用obtainBuffer函数获取的缓冲区实际指向的是audio_track_cblk_t指向的共享缓冲区中已经ready的数据,读的时候,将该数据拷贝过来,之后要释放读取过的数据。同样,在RecordThread线程中,使用getNextBuffer来获取共享缓冲区中可写的空间,然后将AudioStreamIn读出的数据拷贝到其里面去。

实际应用过程中,一般不使用Java层的AudioRecord去读取PCM数据,更多的是使用C++AudioRecord来获取PCM数据,然后进行录音编码。也就是说,PCM数据一般不需要到“流”到Java层,而是下层的管道中“流淌”,Java层可以控制就行了。

 

录音的停止过程

停止的过程较为简单,应用程序侧跨进程调用AudioFlinger侧的stop,从而调用AudioSystem::stopInput,再到AudioPolicyService::stopInput,再到音频策略管理器(如AudioPolicyManagerBase)的stopInput,接着再使用AudioFlinger再到HAL音频硬件的setParametersAudioParameter::keyRouting设置为0。在停止完输入设备后,应用程序侧需要停止其回调函数线程。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您想了解如何使用AndroidAudioRecorder录制蓝牙麦克风的音频吗?如果是的话,以下是一些基本的步骤: 1. 获取蓝牙设备的音频源 使用`MediaRecorder.AudioSource`中的`VOICE_RECOGNITION`参数获取蓝牙设备的音频源。 2. 配置和准备录音器 创建`AudioRecorder`对象,设置音频源、采样率、声道数、编码格式等参数,并调用`prepare()`方法准备录音器。 3. 启动录音 调用`start()`方法开始录音录音期间,将蓝牙设备连接到Android设备,并将其设置为音频输入源。录音完成后,调用`stop()`方法停止录音,并释放资源。 下面是一些示例代码,可以帮助您开始录制蓝牙麦克风的音频: ``` private AudioRecorder audioRecorder; private void startRecording() { audioRecorder = new AudioRecorder(); audioRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION); audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); audioRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); audioRecorder.setAudioSamplingRate(44100); audioRecorder.setAudioChannels(2); audioRecorder.setOutputFile(getFilePath()); // 设置音频文件保存路径 try { audioRecorder.prepare(); audioRecorder.start(); } catch (Exception e) { e.printStackTrace(); } } private void stopRecording() { if (audioRecorder != null) { audioRecorder.stop(); audioRecorder.release(); audioRecorder = null; } } ``` 请注意,这只是一个基本示例。您可能需要根据自己的需求进行更改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值