Android音频

AudioRecord详解

2020-05-21  本文已影响0人  _好好学习

写在前面: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 响应的服务请求主要有:

上一篇 下一篇

猜你喜欢

热点阅读