Android 语音遥控器的整体分析-底层实现机制分析
前面我们知道Writer类中开了一个线程在进行实际的录音。
Writer.addSource()中,有这样一段代码:
const char *kHeader = isWide ? "#!AMR-WB\n" : "#!AMR\n";
ssize_t n = strlen(kHeader);
if (write(mFd, kHeader, n) != n) {
return ERROR_IO;
}
其实就是在录音之前先将音频的一些参数信息写进去。
在StagefrightRecorder::createAudioSource()中,有一段代码:
sp<MediaSource> audioEncoder =
OMXCodec::Create(client.interface(), encMeta,
true /* createEncoder */, audioSource);
mAudioSourceNode = audioSource;
return audioEncoder;
这里是创建一个编码器
在OMXCodec::Create()中是这样的,先调用findMatchingCodecs查找匹配的编码器,如果没有匹配的就返回,,如果需要添加编码器,那么就添加一个:
if (createEncoder) {
sp<MediaSource> softwareCodec =
InstantiateSoftwareEncoder(componentName, source, meta);
if (softwareCodec != NULL) {
ALOGV("Successfully allocated software codec '%s'", componentName);
return softwareCodec;
}
}
因此,添加音频编码器就在这里添加咯!!!
static sp<MediaSource> InstantiateSoftwareEncoder(
const char *name, const sp<MediaSource> &source,
const sp<MetaData> &meta) {
struct FactoryInfo {
const char *name;
sp<MediaSource> (*CreateFunc)(const sp<MediaSource> &, const sp<MetaData> &);
};
static const FactoryInfo kFactoryInfo[] = {
FACTORY_REF(AACEncoder)
};
for (size_t i = 0;
i < sizeof(kFactoryInfo) / sizeof(kFactoryInfo[0]); ++i) {
if (!strcmp(name, kFactoryInfo[i].name)) {
return (*kFactoryInfo[i].CreateFunc)(source, meta);
}
}
return NULL;
}
由这个create中的匹配过程,可以知道,编解码器存放的地方是:struct MediaCodecList中,以一个向量保存
\\frameworks\av\media\libstagefright\MediaCodecList.h
Vector<CodecInfo> mCodecInfos;
ssize_t MediaCodecList::findCodecByType(
const char *type, bool encoder, size_t startIndex) const {
ssize_t typeIndex = mTypes.indexOfKey(type);
if (typeIndex < 0) {
return -ENOENT;
}
while (startIndex < mCodecInfos.size()) {
const CodecInfo &info = mCodecInfos.itemAt(startIndex);
if (info.mIsEncoder == encoder && (info.mTypes & typeMask)) {
return startIndex;
}
++startIndex;
}
return -ENOENT;
}
对编码器的使用,也就是线程里的 err = mSource->read(&buffer); 由read()函数可以知道音频数据来源是 List<MediaBuffer * > mBuffersReceived这个Buffer:
status_t AudioSource::read(
MediaBuffer **out, const ReadOptions *options) {
Mutex::Autolock autoLock(mLock);
*out = NULL;
if (mInitCheck != OK) {
return NO_INIT;
}
while (mStarted && mBuffersReceived.empty()) {
mFrameAvailableCondition.wait(mLock);
}
if (!mStarted) {
return OK;
}
MediaBuffer *buffer = *mBuffersReceived.begin();
mBuffersReceived.erase(mBuffersReceived.begin());
++mNumClientOwnedBuffers;
buffer->setObserver(this);
buffer->add_ref();
// Mute/suppress the recording sound
int64_t timeUs;
CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
int64_t elapsedTimeUs = timeUs - mStartTimeUs;
if (elapsedTimeUs < kAutoRampStartUs) {
memset((uint8_t *) buffer->data(), 0, buffer->range_length());
} else if (elapsedTimeUs < kAutoRampStartUs + kAutoRampDurationUs) {
int32_t autoRampDurationFrames =
(kAutoRampDurationUs * mSampleRate + 500000LL) / 1000000LL;
int32_t autoRampStartFrames =
(kAutoRampStartUs * mSampleRate + 500000LL) / 1000000LL;
int32_t nFrames = mNumFramesReceived - autoRampStartFrames;
rampVolume(nFrames, autoRampDurationFrames,
(uint8_t *) buffer->data(), buffer->range_length());
}
// Track the max recording signal amplitude.
if (mTrackMaxAmplitude) {
trackMaxAmplitude(
(int16_t *) buffer->data(), buffer->range_length() >> 1);
}
*out = buffer;
return OK;
}
而跟踪这个mBuffersReceived,可以知道Android在构造AudioSource实例的时候会指定一个数据源inputSource和回调函数AudioRecordCallbackFunction,在AudioSource中会构造AudioRecord对象,实际录音过程中就是在AudioRecord中通过这个回调函数中将数据一帧一帧填充进去的。
AudioSource::AudioSource(
audio_source_t inputSource, uint32_t sampleRate, uint32_t channelCount)
: mRecord(NULL),
mStarted(false),
mSampleRate(sampleRate),
mPrevSampleTimeUs(0),
mNumFramesReceived(0),
mNumClientOwnedBuffers(0) {
ALOGV("sampleRate: %d, channelCount: %d", sampleRate, channelCount);
CHECK(channelCount == 1 || channelCount == 2);
size_t minFrameCount;
status_t status = AudioRecord::getMinFrameCount(&minFrameCount,
sampleRate,
AUDIO_FORMAT_PCM_16_BIT,
audio_channel_in_mask_from_count(channelCount));
if (status == OK) {
// make sure that the AudioRecord callback never returns more than the maximum
// buffer size
int frameCount = kMaxBufferSize / sizeof(int16_t) / channelCount;
// make sure that the AudioRecord total buffer size is large enough
int bufCount = 2;
while ((bufCount * frameCount) < minFrameCount) {
bufCount++;
}
mRecord = new AudioRecord(
inputSource, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
audio_channel_in_mask_from_count(channelCount),
bufCount * frameCount,
<span style="color:#ff0000;">AudioRecordCallbackFunction</span>,
this,
frameCount);
mInitCheck = mRecord->initCheck();
} else {
mInitCheck = status;
}
}
如果想知道录音设备,比如MIC中的数据是怎么通过回调函数写到Buffer中,那就要继续跟踪传到AudioRecord中的回调函数AudioRecordCallbackFunction了。
分析AudioRecord可以知道,在创建AudioRecord的时候会创建一个线程
if (cbf != NULL) {
mAudioRecordThread = new AudioRecordThread(*this, threadCanCallJava);
mAudioRecordThread->run("AudioRecord", ANDROID_PRIORITY_AUDIO);
}
调用回调的这个过程就是在这个AudioRecordThread线程中操作的。
bool AudioRecord::AudioRecordThread::threadLoop()
{
{
AutoMutex _l(mMyLock);
if (mPaused) {
mMyCond.wait(mMyLock);
// caller will check for exitPending()
return true;
}
}
if (!mReceiver.processAudioBuffer(this)) {
pause();
}
return true;
}
一次次在processAudioBuffer(this)中调用回调函数(也就是AudioRecord.mCbf)。
mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);
前面都是初始化构建过程,现在来看下AudioRecord::start,我们知道其中会调用IAudioRecord类型对象mAudioRecord的start函数。
status_t AudioRecord::start(AudioSystem::sync_event_t event, int triggerSession)
{
status_t ret = NO_ERROR;
sp<AudioRecordThread> t = mAudioRecordThread;
ALOGV("start, sync event %d trigger session %d", event, triggerSession);
AutoMutex lock(mLock);
// acquire a strong reference on the IAudioRecord and IMemory so that they cannot be destroyed
// while we are accessing the cblk
sp<IAudioRecord> audioRecord = mAudioRecord;
sp<IMemory> iMem = mCblkMemory;
audio_track_cblk_t* cblk = mCblk;
if (!mActive) {
mActive = true;
cblk->lock.lock();
if (!(cblk->flags & CBLK_INVALID)) {
cblk->lock.unlock();
ALOGV("mAudioRecord->start()");
ret = mAudioRecord->start(event, triggerSession);
cblk->lock.lock();
if (ret == DEAD_OBJECT) {
android_atomic_or(CBLK_INVALID, &cblk->flags);
}
}
if (cblk->flags & CBLK_INVALID) {
audio_track_cblk_t* temp = cblk;
ret = restoreRecord_l(temp);
cblk = temp;
}
cblk->lock.unlock();
if (ret == NO_ERROR) {
mNewPosition = cblk->user + mUpdatePeriod;
cblk->bufferTimeoutMs = (event == AudioSystem::SYNC_EVENT_NONE) ? MAX_RUN_TIMEOUT_MS :
AudioSystem::kSyncRecordStartTimeOutMs;
cblk->waitTimeMs = 0;
if (t != 0) {
t->resume();
} else {
mPreviousPriority = getpriority(PRIO_PROCESS, 0);
get_sched_policy(0, &mPreviousSchedulingGroup);
androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
}
} else {
mActive = false;
}
}
return ret;
}
分析围绕IAudioRecord接口的类图
可以知道,最终调用的是AudioFinger中RecordHandler这个类的start()函数。
status_t AudioFlinger::RecordHandle::start(int /*AudioSystem::sync_event_t*/ event,
int triggerSession) {
ALOGV("RecordHandle::start()");
return mRecordTrack->start((AudioSystem::sync_event_t)event, triggerSession);
}
后面再分析AudioFlinger怎么操作硬件。
这篇博文提到了怎么在AudioFlinger中处理音频多路输出的情况:
http://blog.csdn.net/hgl868/article/details/6888502
Android 语音遥控器的整体分析-底层实现机制分析相关推荐
- Android 语音遥控器的整体分析
今天蓝牙遥控器的导入终于完成了,分别梳理和记录一下部分语音和蓝牙相关的知识,首先是主机端上层语音部分: 一.应用层使用MediaRecorder的过程(应用层) 1.创建一个MediaRecorder ...
- Android 语音遥控器的整体分析-HAL层的AudioFlinger
上篇说到语音部分最后会通过AudioFlinger来操作HAL层. 一.首先我们看下硬件接口层的接口(奇怪为什么只有Audio的hardwareinterface): (1)hardware\libh ...
- Android 语音遥控器的整体分析-主机端语音解码的添加
前面几篇大致介绍了HAL层的实现方式.这里要介绍下如何在Android主机端的HAL层语音解码的添加. 一.首先需要了解libhardware.so(\libhardware\hardware.c) ...
- 2022-2028全球与中国语音遥控器市场现状及未来发展趋势
2021年全球语音遥控器市场销售额达到了 亿美元,预计2028年将达到 亿美元,年复合增长率(CAGR)为 %(2022-2028).地区层面来看,中国市场在过去几年变化较快,2021年市场规模为 百 ...
- 智能会议系统(34)---Android语音通话实现方案及相关技术介绍
Android语音通话实现方案及相关技术介绍 Android语音通话实现方案及相关技术介绍 语音通话 Step1语音采集和输出 Step2编解码方式 Step3网络传输 Step4去噪声消回音 语音通 ...
- Android系统(62)---Alarm的机制
Android中Alarm的机制 本次给大家分析的是Android中Alarm的机制所用源码为最新的Android4.4.4.首先简单介绍如何使用Alarm并给出其工作原理,接着分析Alarm和Tim ...
- Android语音通话实现方案及相关技术介绍
Android语音通话实现方案及相关技术介绍 Android语音通话实现方案及相关技术介绍 语音通话 Step1语音采集和输出 Step2编解码方式 Step3网络传输 Step4去噪声消回音 语音通 ...
- Android 语音播放Media Player
原文地址: https://developer.android.com/guide/topics/media/mediaplayer.html#viacontentresolver 语音播放 因为实习 ...
- android 语音播放
android 语音播放 MediaPlayer可以播放本地或者网络的音频,流程如下: Uri myUri = ....; // initialize Uri here MediaPlayer med ...
最新文章
- bp神经网络应用实例_人工智能BP神经网络学习神器——AISPACE
- 如何删除Cookie?
- HTTP1.0,HTTP1.1,HTTPS和HTTP2.0的区别
- springboot @ConfigurationProperties注入属性流程
- JavaScript分支结构(判断结构)使用教程
- Angular @NgModule providers里multi等于true在源代码里如何体现的
- JAVA中在某游戏系统有猫狗猪_算法面试题之猫狗队列(java)
- equation在c语言中是什么意思,MathType出现此对象创建于Equation中的问题怎么办
- JMeter循环控制器循环次数使用变量控制注意事项
- 鸿蒙历程和路标图,鸿蒙2.0来了?华为开发者大会时间确认:Mate40会不会首发?...
- 百度文库文章提取器(下)
- Mac_苹果电脑设置眼睛保护色
- 英文名字的昵称(亲切的叫法)
- iPhoneSE3定价或跌穿3K,苹果不给安卓手机活路了?
- HDUOJ 2059 龟兔赛跑——
- windows使用模拟器
- “咱们吃鸡吧”的背后
- CentOS 7 网络配置
- 博途PLC和CODESYS平台下FB编程应用(如何实例化多个FB)
- Leetcode刷题笔记之445. 两数相加Ⅱ
热门文章
- ArrayList 排序
- 15个可以做签名的高权重论坛
- 【屏幕录制软件集锦】自己收藏的一些认为比较好的软件
- 色阶的中间调调节原理——兼论色阶和曲线的联系
- java80/20法则_那些很熟悉但又不知怎么用的设计法则(1):80/20法则
- linebreak_operator-linebreak
- html5 等比压缩图片,HTML5实现input:file上传压缩,等比压缩图片、base64和文件互相转换...
- glc四驱软件测试,【图】独家官方答复 奔驰GLC四驱情况新进展_汽车之家
- 特斯拉是l3还是l2_自动驾驶L2、L3级,到底是什么?竟引得BBA联手开发
- 虹科方案 | 虹科Vdoo安全平台:CVE-2020-25860 - 在 RAUC 嵌入式固件更新框架中发现的重大漏洞