記事のマスターリンクのこのシリーズ:テーマ別のサブディレクトリのAndroid Frameworkクラスオーディオ・サブシステム
この章の要点の概要と説明:
この章では主に、上記のマインドマップの左上の録音部分での➕AudioRecordプロセス分析とマルチアプリ同時録音方式に焦点を当てています。主に、AudioRecordプロセス分析とフレームワーク、およびマルチAPP同時録音ソリューションについて説明します。
1 AudioRecordテストプログラムのコアソースコード分析
前の章のAudioRecordテストプログラムのコアロジックは、次のように構成されています。
//step1 创建AudioRecord
pAudioRecord = new android::AudioRecord();
//step2 set操作
pAudioRecord->set( inputSource, sampleRateInHz,audioFormat, \
channelConfig,0,NULL, NULL,0,true,0);
//step3 启动对应的线程
pAudioRecord->start()
//step4 循环读取音频数据
while (!g_bQuitAudioRecordThread)
{
//读取数据
pAudioRecord->read(inBuffer, bufferSizeInBytes);
//存储操作
//...
}
//step5 停止对应线程,相对较为简单,忽略分析
pAudioRecord->stop()
前の4つのステップ(作成、設定操作、開始操作、読み取り操作)の詳細な分析を次に示します。
1.1 AudioRecordの作成
AudioRecordコンストラクタコードは次のように実装されます。
AudioRecord::AudioRecord()
: mStatus(NO_INIT), mSessionId(AUDIO_SESSION_ALLOCATE),
mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT)
{
}
AudioRecord::AudioRecord(
audio_source_t inputSource,
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
size_t frameCount,
callback_t cbf,
void* user,
uint32_t notificationFrames,
int sessionId,
transfer_type transferType,
audio_input_flags_t flags,
const audio_attributes_t* pAttributes)
: mStatus(NO_INIT), mSessionId(AUDIO_SESSION_ALLOCATE),
mPreviousPriority(ANDROID_PRIORITY_NORMAL),
mPreviousSchedulingGroup(SP_DEFAULT),
mProxy(NULL)
{
mStatus = set(inputSource, sampleRate, format, channelMask, frameCount, cbf, user,
notificationFrames, false /*threadCanCallJava*/, sessionId, transferType, flags,
pAttributes);
}
ここには2つのタイプのコンストラクターがあることに注意してください。1つはいくつかのパラメーターを初期化し、次にsetメソッドを呼び出して他のパラメーターを設定することです。もう1つのタイプは、すべてのパラメーターを直接設定することです。つまり、AudioTrackと非常によく似た、内部でset操作を直接呼び出します。
1.2 pAudioRecord->設定操作
セットのコードは次のように実装されます。
status_t AudioRecord::set(
audio_source_t inputSource,
//...
const audio_attributes_t* pAttributes)
{
//...
if (sessionId == AUDIO_SESSION_ALLOCATE) {
mSessionId = AudioSystem::newAudioUniqueId();
} else {
mSessionId = sessionId;
}
ALOGV("set(): mSessionId %d", mSessionId);
mFlags = flags;
mCbf = cbf;
if (cbf != NULL) {
mAudioRecordThread = new AudioRecordThread(*this, threadCanCallJava);
mAudioRecordThread->run("AudioRecord", ANDROID_PRIORITY_AUDIO);
}
// create the IAudioRecord
status_t status = openRecord_l(0 /*epoch*/);
//...
return NO_ERROR;
}
ここでは、openRecord_l実装の分析に焦点を当てます。コードは次のとおりです。
status_t AudioRecord::openRecord_l(size_t epoch)
{
status_t status;
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
if (audioFlinger == 0) {
ALOGE("Could not get audioflinger");
return NO_INIT;
}
//...
audio_io_handle_t input;
//关键点1 getInput操作
status = AudioSystem::getInputForAttr(&mAttributes, &input, (audio_session_t)mSessionId,
mSampleRate, mFormat, mChannelMask, mFlags);
//...
sp<IMemory> iMem; // for cblk
sp<IMemory> bufferMem;
//关键点2:AudioFlinger的openRecord操作
sp<IAudioRecord> record = audioFlinger->openRecord(input,
mSampleRate, mFormat,
mChannelMask,
&temp,
&trackFlags,
tid,
&mSessionId,
¬ificationFrames,
iMem,
bufferMem,
&status);
//...
// update proxy 共享内存相关
mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mFrameSize);
mProxy->setEpoch(epoch);
mProxy->setMinimum(mNotificationFramesAct);
//...
return NO_ERROR;
}
release:
AudioSystem::releaseInput(input, (audio_session_t)mSessionId);
if (status == NO_ERROR) {
status = NO_INIT;
}
return status;
}
@ 1 AudioSystemのgetInputForAttrコードは次のように実装されます。
status_t AudioSystem::getInputForAttr(const audio_attributes_t *attr,
audio_io_handle_t *input,
audio_session_t session,
uint32_t samplingRate,
audio_format_t format,
audio_channel_mask_t channelMask,
audio_input_flags_t flags)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return NO_INIT;
return aps->getInputForAttr(attr, input, session, samplingRate, format, channelMask, flags);
}
AudioPolicyManagerのgetInputForAttrメソッドの分析を続けます。コードの実装は次のとおりです。
status_t AudioPolicyManager::getInputForAttr(const audio_attributes_t *attr,
//...
input_type_t *inputType)
{
*input = AUDIO_IO_HANDLE_NONE;
*inputType = API_INPUT_INVALID;
audio_devices_t device;
// handle legacy remote submix case where the address was not always specified
String8 address = String8("");
bool isSoundTrigger = false;
audio_source_t inputSource = attr->source;
audio_source_t halInputSource;
AudioMix *policyMix = NULL;
if (inputSource == AUDIO_SOURCE_DEFAULT) {
inputSource = AUDIO_SOURCE_MIC;
}
halInputSource = inputSource;
if (inputSource == AUDIO_SOURCE_REMOTE_SUBMIX &&
strncmp(attr->tags, "addr=", strlen("addr=")) == 0) {
//...
} else {
//根据APP端传入的音源找到对应的device。
device = getDeviceAndMixForInputSource(inputSource, &policyMix);
//...
if (inputSource == AUDIO_SOURCE_HOTWORD) {
ssize_t index = mSoundTriggerSessions.indexOfKey(session);
if (index >= 0) {
*input = mSoundTriggerSessions.valueFor(session);
isSoundTrigger = true;
flags = (audio_input_flags_t)(flags | AUDIO_INPUT_FLAG_HW_HOTWORD);
ALOGV("SoundTrigger capture on session %d input %d", session, *input);
} else {
halInputSource = AUDIO_SOURCE_VOICE_RECOGNITION;
}
}
}
//根据device找到对应的IOProfile
sp<IOProfile> profile = getInputProfile(device, address,
samplingRate, format, channelMask,
flags);
//...
//根据profile找到对应的Module(so文件),对应一个声卡
//这里调用AudioFlinger的openInput方法
status_t status = mpClientInterface->openInput(profile->mModule->mHandle,
input,
&config,
&device,
address,
halInputSource,
flags);
//...
//根据profile创建一个inputDesc
sp<AudioInputDescriptor> inputDesc = new AudioInputDescriptor(profile);
//inputDesc初始化...
//将input索引和inputDesc绑定
addInput(*input, inputDesc);
mpClientInterface->onAudioPortListUpdate();
return NO_ERROR;
}
ここでは引き続きAudioFlingerのOpenInputメソッドを分析します。コードの実装は次のとおりです。
status_t AudioFlinger::openInput(audio_module_handle_t module,
//...
audio_input_flags_t flags)
{
Mutex::Autolock _l(mLock);
//...
sp<RecordThread> thread = openInput_l(module, input, config, *device, address, source, flags);
//...
return NO_INIT;
}
openInput_lの実装の分析を続けます。コードは次のとおりです。
sp<AudioFlinger::RecordThread> AudioFlinger::openInput_l(audio_module_handle_t module,
audio_io_handle_t *input,
audio_config_t *config,
audio_devices_t device,
const String8& address,
audio_source_t source,
audio_input_flags_t flags)
{
AudioHwDevice *inHwDev = findSuitableHwDev_l(module, device);
//...
if (*input == AUDIO_IO_HANDLE_NONE) {
*input = nextUniqueId();
}
audio_config_t halconfig = *config;
audio_hw_device_t *inHwHal = inHwDev->hwDevice();
audio_stream_in_t *inStream = NULL;
status_t status = inHwHal->open_input_stream(inHwHal, *input, device, &halconfig,
&inStream, flags, address.string(), source);
// If the input could not be opened with the requested parameters and we can handle the
// conversion internally, try to open again with the proposed parameters. The AudioFlinger can
// resample the input and do mono to stereo or stereo to mono conversions on 16 bit PCM inputs.
if (status == BAD_VALUE &&
config->format == halconfig.format && halconfig.format == AUDIO_FORMAT_PCM_16_BIT &&
(halconfig.sample_rate <= 2 * config->sample_rate) &&
(audio_channel_count_from_in_mask(halconfig.channel_mask) <= FCC_2) &&
(audio_channel_count_from_in_mask(config->channel_mask) <= FCC_2)) {
inStream = NULL;
status = inHwHal->open_input_stream(inHwHal, *input, device, &halconfig,
&inStream, flags, address.string(), source);
// FIXME log this new status; HAL should not propose any further changes
}
if (status == NO_ERROR && inStream != NULL) {
AudioStreamIn *inputStream = new AudioStreamIn(inHwDev, inStream);
//创建RecordThread录音线程
sp<RecordThread> thread = new RecordThread(this,
inputStream,
*input,
primaryOutputDevice_l(),
device
);
mRecordThreads.add(*input, thread);
return thread;
}
*input = AUDIO_IO_HANDLE_NONE;
return 0;
}
ここで、最終的にRecordThreadスレッドが作成され、対応する入力インデックスとデバイスがバインドされます。@ 2 audioFlinger-> openRecordコードは次のように実装されます。
sp<IAudioRecord> AudioFlinger::openRecord(
audio_io_handle_t input,
//...
status_t *status)
{
sp<RecordThread::RecordTrack> recordTrack;
sp<RecordHandle> recordHandle;
sp<Client> client;
status_t lStatus;
int lSessionId;
//...
{
Mutex::Autolock _l(mLock);
RecordThread *thread = checkRecordThread_l(input);
//...
// TODO: the uid should be passed in as a parameter to openRecord
recordTrack = thread->createRecordTrack_l(client, sampleRate, format, channelMask,
frameCount, lSessionId, notificationFrames,
IPCThreadState::self()->getCallingUid(),
flags, tid, &lStatus);
//...
}
//...
cblk = recordTrack->getCblk();//获取共享内存头
buffers = recordTrack->getBuffers();//获取共享内存Buffer
// return handle to client
recordHandle = new RecordHandle(recordTrack);
Exit:
*status = lStatus;
return recordHandle;
}
createRecordTrack_lの実装の分析を続けます。コードは次のとおりです。
// RecordThread::createRecordTrack_l() must be called with AudioFlinger::mLock held
sp<AudioFlinger::RecordThread::RecordTrack> AudioFlinger::RecordThread::createRecordTrack_l(
const sp<AudioFlinger::Client>& client,
//...
status_t *status)
{
size_t frameCount = *pFrameCount;
sp<RecordTrack> track;
status_t lStatus;
//...
lStatus = initCheck();
//...
{ // scope for mLock
Mutex::Autolock _l(mLock);
track = new RecordTrack(this, client, sampleRate,
format, channelMask, frameCount, NULL, sessionId, uid,
*flags, TrackBase::TYPE_DEFAULT);
lStatus = track->initCheck();
//...
mTracks.add(track);
//...
}
lStatus = NO_ERROR;
Exit:
*status = lStatus;
return track;
}
ここで最も懸念されるリンクの1つは、独自のトラックを作成するためのplayingThreadと同様に、recordTrackがRecordThreadを通じて作成されることです。
1.3 pAudioRecord-> start()オペレーション
AudioRecord :: startのコード実装は次のとおりです。
status_t AudioRecord::start(AudioSystem::sync_event_t event, int triggerSession)
{
//...
// reset current position as seen by client to 0
mProxy->setEpoch(mProxy->getEpoch() - mProxy->getPosition());
mRefreshRemaining = true;
mNewPosition = mProxy->getPosition() + mUpdatePeriod;
int32_t flags = android_atomic_acquire_load(&mCblk->mFlags);
status_t status = NO_ERROR;
if (!(flags & CBLK_INVALID)) {
ALOGV("mAudioRecord->start()");
status = mAudioRecord->start(event, triggerSession);
if (status == DEAD_OBJECT) {
flags |= CBLK_INVALID;
}
}
//...
return status;
}
ここでは、mAudioRecord-> startの実装に注目します(mAudioRecordは、audioFlinger-> openRecordによって返されるRecordHandleのタイプであり、RecordHandleは、recordTrackを使用して初期化されます)実際、AudioFlinger :: RecordThread :: RecordTrack :: startのメソッドが呼び出されます。
status_t AudioFlinger::RecordThread::RecordTrack::start(AudioSystem::sync_event_t event,
int triggerSession)
{
sp<ThreadBase> thread = mThread.promote();
if (thread != 0) {
RecordThread *recordThread = (RecordThread *)thread.get();
return recordThread->start(this, event, triggerSession);
} else {
return BAD_VALUE;
}
}
ここでは、recordThreadのstartメソッドが呼び出されます。コードは次のとおりです。
status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrack,
AudioSystem::sync_event_t event,
int triggerSession)
{
ALOGV("RecordThread::start event %d, triggerSession %d", event, triggerSession);
sp<ThreadBase> strongMe = this;
status_t status = NO_ERROR;
{
//...
recordTrack->mState = TrackBase::STARTING_1;
mActiveTracks.add(recordTrack);
mActiveTracksGen++;
status_t status = NO_ERROR;
if (recordTrack->isExternalTrack()) {
mLock.unlock();
status = AudioSystem::startInput(mId, (audio_session_t)recordTrack->sessionId());
mLock.lock();
// FIXME should verify that recordTrack is still in mActiveTracks
if (status != NO_ERROR) {
mActiveTracks.remove(recordTrack);
mActiveTracksGen++;
recordTrack->clearSyncStartEvent();
ALOGV("RecordThread::start error %d", status);
return status;
}
}
//...
recordTrack->mState = TrackBase::STARTING_2;
// signal thread to start
mWaitWorkCV.broadcast();//唤醒线程执行
//...
return status;
}
//...
}
ここで、スレッドは後で起動します。スレッドの主な内容は、HALからデータを取得し、内部のRecordTrackを介してデータをAPPのAudioRecordに渡すことです。そのため、ここではAudioSystem :: startInputの実装について懸念しています。AudioSystem :: startInputのコード実装は次のとおりです。
status_t AudioSystem::startInput(audio_io_handle_t input,
audio_session_t session)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return PERMISSION_DENIED;
return aps->startInput(input, session);
}
AudioPolicyManagerのstartInput実装の分析を続けます。コードは次のとおりです。
status_t AudioPolicyManager::startInput(audio_io_handle_t input,
audio_session_t session)
{
ssize_t index = mInputs.indexOfKey(input);
sp<AudioInputDescriptor> inputDesc = mInputs.valueAt(index);
index = inputDesc->mSessions.indexOf(session);
//...
// virtual input devices are compatible with other input devices
if (!isVirtualInputDevice(inputDesc->mDevice)) {
// for a non-virtual input device, check if there is another (non-virtual) active input
audio_io_handle_t activeInput = getActiveInput();//获取活跃的input,有APP在录音
/*关键逻辑:
*当前正在录音 / 想启动录音的input,如果不是同一个,则返回error
*也正是因为如此,Android的APP不可以同时录音
*/
if (activeInput != 0 && activeInput != input) {
sp<AudioInputDescriptor> activeDesc = mInputs.valueFor(activeInput);
if (activeDesc->mInputSource == AUDIO_SOURCE_HOTWORD) {
stopInput(activeInput, activeDesc->mSessions.itemAt(0));
releaseInput(activeInput, activeDesc->mSessions.itemAt(0));
} else {
return INVALID_OPERATION;
}
}
}
//...
inputDesc->mRefCount++;
return NO_ERROR;
}
スレッドRecordThreadは、開始プロセス中に開始されました。同時に、Android APPは同時に記録できないこともわかりました。
1.4 pAudioRecord->読み取り操作
AudioRecord読み取りメソッド。コードは次のように実装されます。
ssize_t AudioRecord::read(void* buffer, size_t userSize)
{
if (mTransfer != TRANSFER_SYNC) {
return INVALID_OPERATION;
}
if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) {
// sanity-check. user is most-likely passing an error code, and it would
// make the return value ambiguous (actualSize vs error).
ALOGE("AudioRecord::read(buffer=%p, size=%zu (%zu)", buffer, userSize, userSize);
return BAD_VALUE;
}
ssize_t read = 0;
Buffer audioBuffer;
while (userSize >= mFrameSize) {
audioBuffer.frameCount = userSize / mFrameSize;
status_t err = obtainBuffer(&audioBuffer, &ClientProxy::kForever);
if (err < 0) {
if (read > 0) {
break;
}
return ssize_t(err);
}
size_t bytesRead = audioBuffer.size;
memcpy(buffer, audioBuffer.i8, bytesRead);
buffer = ((char *) buffer) + bytesRead;
userSize -= bytesRead;
read += bytesRead;
releaseBuffer(&audioBuffer);
}
return read;
}
ここでは、obtainBufferとreleaseBufferを使用して共有メモリを操作し、audioBufferからBufferにデータを読み取ります。
2 AudioRecordフレームワークの簡単な説明
@ 1 AudioRecordフレームワークプロセス
- APPはAudioRecordを作成および設定し、音源inputSource(例:AUDIO_SOURCE_MIC)を指定し、さらにサンプリングレート、チャネル数、フォーマットおよびその他のパラメーターを指定します。
- AudioPolicyManagerは、inputSource:デバイスなどのパラメーターに従って録音デバイスを決定し、デバイス(audio_policy.confによって生成される)
に従ってプロファイルを見つけ、サウンドカードに対応するプロファイルに従ってモジュールを見つけ、サウンドカードに対応するHALファイルをロードします。 - AudioFlingerはRecordThreadを作成し、その後スレッドは上記のデバイスからサウンドデータを読み取ります
- RecordThread内のAPPのAudioRecordに対応するRecordTrackを作成します。APPのAudioRecordとRecordThread内のRecordTrackは、共有メモリを介してデータを転送します
- RecordThreadはHALからデータを取得し(HALファイルのopenInput()を呼び出して入力チャネルを開く)、内部のRecordTrackを介してAPPのAudioRecordにデータを渡します。
@ 2 Androidが複数のアプリを同時に記録できないのはなぜですか?
ネイティブコードでは、APPのAudioRecordによってRecordThreadが作成されます。デバイス上には複数のRecordThreadが存在する可能性があります。実行できるRecordThreadは一度に1つだけなので、複数のAPPではなく1つのAPPしか記録できません録音
3マルチAPP同時録音ソリューション
3.1ソリューションの原則
APPによって作成されたAudioRecordにより、RecordThreadが作成されます。デバイス上に複数のRecordThreadが存在する可能性があります。実行できるRecordThreadは常に1つだけなので、複数のAPPではなく、1つのAPP記録のみが可能です。
実際、AudioFlingerレベルでは、同時録音用に複数のAPPがサポートされており、1つのRecordThreadに複数のRecordTrackを含めることができます。複数のアプリがある場合、デバイスにRecordThreadが1つしかないことが保証されている限り(ここではPlaybackThreadメソッドも参照)、AudioPolicyManager(ポリシーレイヤー)のみでこのレイヤーに制限があります(スレッドの数を制限し、1つしか存在できません)。アクセスするときは、同じスレッドが引き続きアクセスされ、異なるRecordTrackを使用するだけです(このメカニズムは完璧で、コードを変更せずに直接使用できます)。その後、複数のAPPを同時に記録できます。
3.2ソリューションの実装
AudioPolicyManager.cppを次のように変更します(+部分は新しく追加されたコードです)。
status_t AudioPolicyManager::getInputForAttr(const audio_attributes_t *attr,
audio_io_handle_t *input,
audio_session_t session,
uint32_t samplingRate,
audio_format_t format,
audio_channel_mask_t channelMask,
audio_input_flags_t flags,
input_type_t *inputType)
{
//...
config.channel_mask = channelMask;
config.format = format;
+ /* check wether have an AudioInputDescriptor use the same profile */
+ for (size_t input_index = 0; input_index < mInputs.size(); input_index++) {
+ sp<AudioInputDescriptor> desc;
+ desc = mInputs.valueAt(input_index);
+ if (desc->mProfile == profile) {
+ desc->mOpenRefCount++; // 引用计数+1
+ desc->mSessions.add(session); // session
+ return desc->mIoHandle;
+ }
+ }
+
status_t status = mpClientInterface->openInput(profile->mModule->mHandle,
input,
&config,
&device,
address,
halInputSource,
flags);
//...
}