Android Frameworkオーディオサブシステム(17)AudioRecordフレームワーク

記事のマスターリンクのこのシリーズ:テーマ別のサブディレクトリの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,
                                                       &notificationFrames,
                                                       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フレームワークプロセス

  1. APPはAudioRecordを作成および設定し、音源inputSource(例:AUDIO_SOURCE_MIC)を指定し、さらにサンプリングレート、チャネル数、フォーマットおよびその他のパラメーターを指定します。
  2. AudioPolicyManagerは、inputSource:デバイスなどのパラメーターに従って録音デバイスを決定し、デバイス(audio_policy.confによって生成される)
    に従ってプロファイルを見つけ、サウンドカードに対応するプロファイルに従ってモジュールを見つけ、サウンドカードに対応するHALファイルをロードします。
  3. AudioFlingerはRecordThreadを作成し、その後スレッドは上記のデバイスからサウンドデータを読み取ります
  4. RecordThread内のAPPのAudioRecordに対応するRecordTrackを作成します。APPのAudioRecordとRecordThread内のRecordTrackは、共有メモリを介してデータを転送します
  5. 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);
     //...
}

 

元の記事289件を公開 賞賛された47件 30,000回以上の閲覧

おすすめ

転載: blog.csdn.net/vviccc/article/details/105468626