Android Framework subsistema de audio (17) AudioRecord framework

Esta serie de artículos enlace Maestro: sub-directorio temático Android Framework Subsistema de audio


Resumen y descripción de puntos clave en este capítulo:

Este capítulo se centra principalmente en el análisis de proceso de AudioRecord y el esquema de grabación simultánea de múltiples aplicaciones en la parte superior izquierda de grabación del mapa mental anterior. Explica principalmente el análisis y el marco del proceso AudioRecord, así como la solución de grabación simultánea de múltiples aplicaciones.


1 análisis de código fuente del programa de prueba AudioRecord

La lógica central del programa de prueba AudioRecord en el capítulo anterior está organizada de la siguiente manera:

//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()

Aquí hay un análisis detallado de los cuatro pasos anteriores: crear, establecer operación, iniciar operación, leer operación

1.1 Creación de AudioRecord

El código del constructor AudioRecord se implementa de la siguiente manera:

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);
}

Tenga en cuenta que aquí hay dos tipos de constructores, uno es inicializar algunos parámetros y luego llamar al método set para establecer otros parámetros. El otro tipo es configurar directamente todos los parámetros, es decir, llamar directamente a la operación de configuración internamente, que es muy similar a AudioTrack.

1.2 pAudioRecord-> establecer operación

El código de conjunto se implementa de la siguiente manera:

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;
}

Aquí se centran en el análisis de la implementación de openRecord_l, el código es el siguiente:

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;
}

El código getInputForAttr de @ 1 AudioSystem se implementa de la siguiente manera:

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);
}

Continúe analizando el método getInputForAttr de AudioPolicyManager, la implementación del código es la siguiente:

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;
}

Aquí continúe analizando el método OpenInput de AudioFlinger, la implementación del código es la siguiente:

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;
}

Continúe analizando la implementación de openInput_l, el código es el siguiente:

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;
}

Aquí eventualmente creará un hilo RecordThread, vinculando el índice de entrada y el dispositivo correspondientes. @ 2 audioFlinger-> el código openRecord se implementa de la siguiente manera:

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;
}

Continúe analizando la implementación de createRecordTrack_l, el código es el siguiente:

// 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;
}

Uno de los enlaces más preocupados aquí es que recordTrack se creará a través de RecordThread, similar a reproduceThread para crear su propia pista.

1.3 operación pAudioRecord-> start ()

La implementación del código de AudioRecord :: start es la siguiente:

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;
}

Aquí prestamos atención a la implementación de mAudioRecord-> start (mAudioRecord es el tipo de RecordHandle devuelto por audioFlinger-> openRecord, y RecordHandle se inicializa usando recordTrack). De hecho, se llama al método 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;
    }
}

El método de inicio de recordThread se llama aquí, el código es el siguiente:

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;
    }
    //...
}

Aquí el hilo se despertará más tarde. El contenido principal del hilo es obtener datos del HAL y luego pasar los datos al AudioRecord de la aplicación a través del RecordTrack interno. Así que aquí le preocupa la implementación de AudioSystem :: startInput. La implementación del código de AudioSystem :: startInput es la siguiente:

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);
}

Continúe analizando la implementación startInput de AudioPolicyManager, el código es el siguiente:

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;
}

El hilo RecordThread se inició durante el proceso de inicio. Al mismo tiempo, también encontramos que la aplicación de Android no puede grabar al mismo tiempo.

1.4 pAudioRecord-> operación de lectura

Método de lectura AudioRecord, el código se implementa de la siguiente manera:

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;
}

Aquí también se usa el método getBuffer y releaseBuffer para operar la memoria compartida, leer datos de audioBuffer a Buffer.


2 Breve descripción del marco AudioRecord

@ 1 proceso de marco AudioRecord

  1. La aplicación crea y establece AudioRecord, especifica la fuente de sonido inputSource (por ejemplo: AUDIO_SOURCE_MIC) y también especifica la frecuencia de muestreo, el número de canales, el formato y otros parámetros.
  2. AudioPolicyManager determina el dispositivo de grabación de acuerdo con parámetros como inputSource: device, encuentra el perfil de acuerdo con el dispositivo (producido por audio_policy.conf),
    encuentra el módulo de acuerdo con el perfil, que corresponde a una tarjeta de sonido, y luego carga el archivo HAL correspondiente a la tarjeta de sonido.
  3. AudioFlinger crea un RecordThread, y luego el hilo leerá los datos de sonido del dispositivo anterior
  4. Cree un RecordTrack correspondiente para el AudioRecord de la aplicación dentro de RecordThread. El AudioRecord de la aplicación y el RecordTrack dentro del RecordThread transfieren datos a través de la memoria compartida
  5. RecordThread obtiene datos de HAL (llame a openInput () en el archivo HAL para abrir un canal de entrada) y luego pasa los datos a AudioRecord de la aplicación a través de RecordTrack interno

@ 2 ¿Por qué Android no puede grabar múltiples aplicaciones al mismo tiempo?

En el código nativo, un AudioRecord de una aplicación dará como resultado la creación de un RecordThread. Puede haber múltiples RecordThreads en un dispositivo. Solo se puede ejecutar un RecordThread en cualquier momento, por lo que solo puede haber una aplicación de grabación, no múltiples APP Grabación


Solución de grabación simultánea de 3 aplicaciones múltiples

3.1 Principio de solución

El AudioRecord creado por la APLICACIÓN dará como resultado la creación de un RecordThread. Puede haber múltiples RecordThreads en un dispositivo. Solo se puede ejecutar un RecordThread en cualquier momento, por lo que solo puede haber una grabación de la APLICACIÓN, no múltiples APLICACIONES.

De hecho, en el nivel AudioFlinger, se admiten múltiples aplicaciones para la grabación simultánea, y puede haber múltiples RecordTracks en un RecordThread. Solo en AudioPolicyManager (capa de política) esta capa tiene restricciones (limite el número de subprocesos, solo puede haber uno), siempre y cuando garanticemos que solo hay un RecordThread en un dispositivo (aquí también se refiere al método PlaybackThread), cuando hay varias aplicaciones Al acceder, se sigue accediendo al mismo hilo, solo use RecordTrack diferente (este mecanismo es perfecto, se puede usar directamente, sin modificar el código), luego se pueden grabar múltiples aplicaciones simultáneamente.

3.2 Implementación de la solución

Modifique AudioPolicyManager.cpp de la siguiente manera (+ parte es el código recién agregado):

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);
     //...
}

 

Publicado 289 artículos originales · elogiados 47 · 30,000+ vistas

Supongo que te gusta

Origin blog.csdn.net/vviccc/article/details/105468626
Recomendado
Clasificación