AudioRecord详解

写在前面:AudioRecord 类的主要功能是让各种应用层应用能够管理音频资源,以便它们通过此类能够录制平台的声音输入硬件所收集的声音。此功能的实现就是通过 "pulling 同步"(reading读取)AudioRecord 对象的声音数据来完成的。在录音过程中,应用所需要做的就是通过read方法去及时地获取 AudioRecord 对象的录音数据。
AudioRecord可以获取到一帧帧PCM数据,之后可以对这些数据进行处理。AudioRecord这种方式采集最为灵活,使开发者最大限度的处理采集的音频,同时它捕获到的音频是原始音频PCM格式的!
开始录音的时候,一个 AudioRecord 需要初始化一个相关联的声音buffer,这个 buffer 主要是用来保存新的声音数据。这个 buffer 的大小,我们可以在对象构造期间去指定。它表明一个 AudioRecord 对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化 buffer 容量的数据。

[toc]

AudioRecord使用流程

  1. 配置参数(各个参数都有默认值)
    • audioResource音频采集的来源:可以是麦克风声音、通话声音、系统内置声音。
    • audioSampleRate音频采样率
    • channelConfig声道:单声道、双声道等
    • audioFormat:音频采样精度,指定采样的数据的格式和每次采样的大小,只支持8位和16位。
    • buffer缓冲区大小:音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区。获取最小的缓冲区大小,用于存放AudioRecord采集到的音频数据。
  2. 初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小byte data[] = new byte[recordBufSize];
  3. AudioRecord对象
    • public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
  4. 录制相关操作
    • 开始录制startRecording()
    • 停止录制stop()
    • 释放资源release()
    • read()的使用
  5. 建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中的数据导入数据流
  6. 关闭数据流
  7. 停止录制

使用示例

private AudioRecord audioRecord = null;  // 声明 AudioRecord 对象private int recordBufSize = 0; // 声明recoordBufffer的大小字段

recordBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, EncodingBitRate);  //audioRecord能接受的最小的buffer大小
  audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, EncodingBitRate, recordBufSize); //创建audiorecord对象
  
byte data[] = new byte[recordBufSize];      //初始化一个buffer

audioRecord.startRecording(); //开始录音

//创建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中数据导入数据流。
FileOutputStream os = null;

try {
   os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
   e.printStackTrace();
}
if (null != os) {    
while (isRecording) {        
read = audioRecord.read(data, 0, recordBufSize);      // 如果读取音频数据没有出现错误,就将数据写入到文件        if (AudioRecord.ERROR_INVALID_OPERATION != read) {            try {                
os.write(data);            
} catch (IOException e) {               
e.printStackTrace();          
}        }    }  
try {        
os.close();    
} catch (IOException e) {      
e.printStackTrace();    
}}


isRecording = false; //关闭数据流

if (null != audioRecord) { //停止录制
  audioRecord.stop();
  audioRecord.release();
  audioRecord = null;
  recordingThread = null;
}

AudioRecord源码分析

构造AudioRecord对象

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,  int bufferSizeInBytes)throws IllegalArgumentException {    this((new AudioAttributes.Builder())                .setInternalCapturePreset(audioSource)                .build(),            (new AudioFormat.Builder())                .setChannelMask(getChannelMaskFromLegacyConfig(channelConfig,                                    true/*allow legacy configurations*/))                .setEncoding(audioFormat)                .setSampleRate(sampleRateInHz)                .build(),            bufferSizeInBytes,            AudioManager.AUDIO_SESSION_ID_GENERATE);}

该构造方法实则调用public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int sessionId)这个构造方法:

{
…
int initResult = native_setup( new WeakReference<AudioRecord>(this),        mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask,        mAudioFormat, mNativeBufferSizeInBytes,        session, ActivityThread.currentOpPackageName(), 0 
/*nativeRecordInJavaObj*/);
if (initResult != SUCCESS) {    loge("Error code "+initResult+" when initializing native AudioRecord 
object.");    return; 
// with mState == STATE_UNINITIALIZED}mSampleRate = sampleRate[0];mSessionId = session[0];mState = STATE_INITIALIZED;
}

该构造方法实现通过调用native_setup函数进入了frameworks/base/core/jni/android_media_AudioRecord.cpp的(void *)android_media_AudioRecord_setup方法。

static jint
android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jobject jaa, jintArray jSampleRate, jint channelMask, jint channelIndexMask,
jint audioFormat, jint buffSizeInBytes, jintArray jSession, jstring opPackageName,
jlong nativeRecordInJavaObj)
{
    …
    sp<AudioRecord> lpRecorder = 0;
    // create an uninitialized AudioRecord object
    lpRecorder = new AudioRecord(String16(opPackageNameStr.c_str()));
    …
}

这里在JNI创建了一个AudioRecord对象

//frameworks/av/media/libaudioclient/AudioRecord.cpp
// must be called with mLock held
status_t AudioRecord::createRecord_l(const Modulo<uint32_t> &epoch, const String16& opPackageName)
{
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger(); //获得AudioFlinger对象
IAudioFlinger::CreateRecordInput input;
IAudioFlinger::CreateRecordOutput output;
audio_session_t originalSessionId;
sp<media::IAudioRecord> record;

record = audioFlinger->createRecord(input,
output,
&status);
}

//frameworks/av/media/libaudioclient/IAudioFlinger.cpp

virtual sp<media::IAudioRecord> createRecord(const CreateRecordInput& input,
CreateRecordOutput& output,
status_t *status)
{
Parcel data, reply;
sp<media::IAudioRecord> record;
data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());

if (status == nullptr) {
return record;
}

input.writeToParcel(&data);

status_t lStatus = remote()->transact(CREATE_RECORD, data, &reply);
if (lStatus != NO_ERROR) {
ALOGE("createRecord transaction error %d", lStatus);
*status = DEAD_OBJECT;
return record;
}
*status = reply.readInt32();
if (*status != NO_ERROR) {
ALOGE("createRecord returned error %d", *status);
return record;
}

record = interface_cast<media::IAudioRecord>(reply.readStrongBinder());
if (record == 0) {
ALOGE("createRecord returned a NULL IAudioRecord with status OK");
*status = DEAD_OBJECT;
return record;
}
output.readFromParcel(&reply);
return record;
}

//创建IAudioFlinger接口的实例
//frameworks/av/media/libaudioclient/AudioSystem.cpp
const sp<IAudioFlinger> AudioSystem::get_audio_flinger()
{

sp<IAudioFlinger> af;
sp<AudioFlingerClient> afc;
{
Mutex::Autolock _l(gLock);
if (gAudioFlinger == 0) {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder;
do {
binder = sm->getService(String16("media.audio_flinger"));
if (binder != 0)
break;
ALOGW("AudioFlinger not published, waiting...");
usleep(500000); // 0.5 s
} while (true);
if (gAudioFlingerClient == NULL) {
gAudioFlingerClient = new AudioFlingerClient(); //创建AudioFlingerClient对象
} else {
if (gAudioErrorCallback) {
gAudioErrorCallback(NO_ERROR);
}
}
binder->linkToDeath(gAudioFlingerClient);
gAudioFlinger = interface_cast<IAudioFlinger>(binder);
LOG_ALWAYS_FATAL_IF(gAudioFlinger == 0);
afc = gAudioFlingerClient;
// Make sure callbacks can be received by gAudioFlingerClient
ProcessState::self()->startThreadPool();
}
af = gAudioFlinger;
}
if (afc != 0) {
int64_t token = IPCThreadState::self()->clearCallingIdentity();
af->registerClient(afc);
IPCThreadState::self()->restoreCallingIdentity(token);
}
return af;

}
//通过binder进行客户端与服务端的通信

通过IAudioRecord可以调用Server服务器对象(AudioFlinger及AudioFlinger::RecordThread等)的方法并获取执行结果。

开始录制

首先要判断一下AudioRecord的状态是否已经初始化完毕了,然后再去调用AudioRecord.startRecording()。一边从 AudioRecord 中读取音频数据到缓冲区,一边将缓冲区 中数据写入到数据流。

startRecording(Java Framework) → native_start → android_media_AudioRecord_start(JNI) → lpRecorder-.start(Native)->
mAudioRecord.start(event, triggerSession).transactionError();->
virtual ::android::binder::Status start(int32_t event, int32_t triggerSession) = 0;(IAudioRecord)
//sp<media::IAudioRecord> mAudioRecord;
通过IAudioRecord调用Server服务器对象AudioFlinger的方法。

停止录制

stop → native_stop → android_media_AudioRecord_stop → lpRecorder->stop() :

释放资源

release() ->native_release()->android_media_AudioRecord_release()->lpRecord->release()

JNI中nativeToJavaStatus返回操作执行的结果

AudioFlinger

我们知道在创建应用层一个AudioRecord对象的时候,会通过JNI再到native层的AudioRecord.cpp创建一个实现了IAudioRecord的接口实例返回,录制的一系列操作最终都会是通过IAudioRecord调用服务端的AudioFligner的方法实现具体的操作。

AudioFlinger 服务启动后,其他进程可以通过 ServiceManager 来获取其代理对象 IAudioFlinger,通过 IAudioFlinger 可以向 AudioFlinger 发出各种服务请求,从而完成自己的音频业务。

AudioFlinger 响应的服务请求主要有:

  • 获取硬件设备的配置信息
  • 音量调节
  • 静音操作
  • 音频模式切换
  • 音频参数设置
  • 输入输出流设备管理
  • 音频流管理
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355