Android Framework Audio Subsystem (11) Commutation du canal audio du branchement du casque

Cette série d'articles lien Master: sous-répertoire thématique cadre Android classe Sous - système audio


Résumé et description des points clés de ce chapitre:

Ce chapitre se concentre principalement sur la partie de commutation du canal sonore dans la partie de branchement et de débranchement du casque en haut à gauche de la carte mentale ci-dessus. Il explique principalement le principe de commutation de canal et l'analyse de processus de commutation de canal.


1 La description principale de la commutation du canal sonore du branchement et du débranchement du casque

1.1 Analyse de scénario de commutation de canaux sonores

Il y a deux cas d'analyse ici, l'un est l'insertion d'une carte son USB et l'autre est l'insertion d'un casque sur le périphérique principal.

Insérez la carte son USB:

  1. Vous pouvez trouver le module correspondant dans usb à partir du fichier de configuration audio_policy. Il doit y avoir des sorties contenant usb_accessory et usb_device. Après avoir branché la carte son USB, la sortie et le fil de lecture correspondant seront créés. (Les deux sorties usb_accessory et usb_device correspondent à une chacune).
  2. Le thread playBackThread qui a précédemment établi un contact avec la carte son intégrée doit être basculé vers le playBackThread créé par USB, et l'AudioTrack de l'APP passera du mode de lecture / lecture / sortie d'origine au nouveau lecture / lecture / sortie. Dans le nouveau playBackThread, chaque APP crée une piste correspondante.
  3. Sélectionnez Périphérique dans la sortie, effectuez à nouveau certains réglages et décidez de lire ou non le son du casque ou des haut-parleurs.

Branchez le casque dans le primaire:

  1. Il n'est pas nécessaire de créer une sortie et un fil de lecture, car dans ce cas, le thread dans lequel ils sont impliqués n'a pas changé, il n'est donc pas nécessaire de recréer la sortie et playBackThread.
  2. Pas besoin de changer de sortie.
  3. Sélectionnez Device (Headset) dans la sortie d'origine.

Il n'y a pas beaucoup de différence dans le processus. Tout d'abord, vous devez déterminer si vous devez créer une sortie, si le son de lecture a besoin d'un nouveau thread pour être traité, et enfin sélectionner le périphérique dans la sortie.

1.2 Trois étapes pour changer le cœur du canal sonore

Une interruption se produit lorsque le matériel est branché sur le casque. Définissez la carte son dans le gestionnaire d'interruption pour laisser le son sortir du casque. Le pilote signale un événement de plug-in audio. Cet événement est destiné à un périphérique à brancher ou débrancher. Système Android, le canal audio est commuté par le système Android. Le système Android change de canal sonore

Les trois étapes principales sont les suivantes:

@ 1 checkOutputsForDevice

Pour cet appareil, ouvrez une nouvelle sortie et créez un nouveau fil de lecture. Déterminer à partir du fichier audio_policy.conf "combien de sorties auraient dû être" peut le supporter, mOutputs a dit "la sortie a été ouverte", les deux peuvent être comparés pour déterminer "la sortie n'a pas été ouverte"

@ 2 checkOutputForAllStrategies / checkOutputForStrategy

Pour tous les sons de regroupement de stratégie, déterminez s'il est nécessaire de migrer vers une nouvelle sortie, et si nécessaire, migrez la piste correspondante vers une nouvelle sortie, ici implique 2 jugements

@@ 2.1 Déterminez si la migration est requise:

  1. Pour la stratégie, récupérez son ancien périphérique, puis récupérez ses sorties (srcOutputs);
  2. Pour la stratégie, récupérez son newDevice, puis récupérez ses sorties (dstOutputs);
  3. Si les deux srcOutputs et dstOutputs ne sont pas identiques, cela signifie que vous devez migrer

@@ 2.2 En cas de migration:

Définissez la piste correspondante à l'état d'invalidation. Lorsque l'application écrit AudioTrack et constate qu'elle est à l'état d'invalidation, elle recrée une nouvelle piste

  1. audio_devices_t oldDevice = getDeviceForStrategy (stratégie, true / * fromCache * /);
  2. audio_devices_t newDevice = getDeviceForStrategy (stratégie, false / * fromCache * /);
  3. SortedVector <audio_io_handle_t> srcOutputs = getOutputsForDevice (oldDevice, mPreviousOutputs);
  4. SortedVector <audio_io_handle_t> dstOutputs = getOutputsForDevice (newDevice, mOutputs);

@ 3 getNewOutputDevice / setOutputDevice Cela doit faire fonctionner la couche HAL


2 Prise casque et débranchement interprétation du code source de la commutation du canal audio

Ici commence à partir de l'analyse de onSetWiredDeviceConnectionState dans AudioService, l'implémentation du code est la suivante:

private void onSetWiredDeviceConnectionState(int device, int state, String name)
{
    synchronized (mConnectedDevices) {
        //...
        //关键点1:声道切换入口
        handleDeviceConnection((state == 1), device, (isUsb ? name : ""));
        if (state != 0) {
            //...
            if ((device & mSafeMediaVolumeDevices) != 0) {
                sendMsg(mAudioHandler,MSG_CHECK_MUSIC_ACTIVE,SENDMSG_REPLACE,0,0,null,MUSIC_ACTIVE_POLL_PERIOD_MS);
            }
            //...
        } else {
            //...
        }
        if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) {
            //关键点2:通过AMS上报intent
            sendDeviceConnectionIntent(device, state, name);
        }
    }
}

Nous nous sommes concentrés sur deux points clés ici: handleDeviceConnection, l'entrée de commutation de canal et sendDeviceConnectionIntent, qui signale l'intention à AMS. Dans ce chapitre, nous commençons avec handleDeviceConnection. La méthode handleDeviceConnection de AudioService dans la couche Java peut être directement appelée dans setDeviceConnectionStateInt de AudioPolicyManager dans la couche Native. Le code de setDeviceConnectionStateInt est le suivant:

status_t AudioPolicyManager::setDeviceConnectionStateInt(audio_devices_t device,
                                                         audio_policy_dev_state_t state,
                                                         const char *device_address)
{
    if (!audio_is_output_device(device) && !audio_is_input_device(device)) return BAD_VALUE;
    sp<DeviceDescriptor> devDesc = getDeviceDescriptor(device, device_address);

    // handle output devices
    /*判断上报的是否为output_device*/
    if (audio_is_output_device(device)) {
        SortedVector <audio_io_handle_t> outputs;
        ssize_t index = mAvailableOutputDevices.indexOf(devDesc);
        mPreviousOutputs = mOutputs;
        switch (state)
        {
        // handle output device connection
        case AUDIO_POLICY_DEVICE_STATE_AVAILABLE: {
            //代表存在直接返回,否则代表为新添加的
            if (index >= 0) {
                return INVALID_OPERATION;
            }
            //添加到可用设备
            index = mAvailableOutputDevices.add(devDesc);
            if (index >= 0) {
                //根据device在可用的设备列表中查找
                sp<HwModule> module = getModuleForDevice(device);
                if (module == 0) {
                    mAvailableOutputDevices.remove(devDesc);
                    return INVALID_OPERATION;
                }
                mAvailableOutputDevices[index]->mId = nextUniqueId();
                mAvailableOutputDevices[index]->mModule = module;
            } else {
                return NO_MEMORY;
            }
            //关键点1:针对该device, 打开新的output, 创建新的playbackthread.
            if (checkOutputsForDevice(devDesc, state, outputs, devDesc->mAddress) != NO_ERROR) {
                mAvailableOutputDevices.remove(devDesc);
                return INVALID_OPERATION;
            }

            // Set connect to HALs
            AudioParameter param = AudioParameter(devDesc->mAddress);
            param.addInt(String8(AUDIO_PARAMETER_DEVICE_CONNECT), device);
            mpClientInterface->setParameters(AUDIO_IO_HANDLE_NONE, param.toString());

            } break;
        // handle output device disconnection
        case AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE: {
            // Set Disconnect to HALs
            AudioParameter param = AudioParameter(devDesc->mAddress);
            param.addInt(String8(AUDIO_PARAMETER_DEVICE_DISCONNECT), device);
            mpClientInterface->setParameters(AUDIO_IO_HANDLE_NONE, param.toString());

            // remove device from available output devices
            mAvailableOutputDevices.remove(devDesc);

            checkOutputsForDevice(devDesc, state, outputs, devDesc->mAddress);
            } break;

        default:
            ALOGE("setDeviceConnectionState() invalid state: %x", state);
            return BAD_VALUE;
        }
        //...
        /*关键点2:对所有的strategy分组声音,判断是否需要迁移
         *到新的output, 如果需要则迁移对应Track到新的output
         */
        checkOutputForAllStrategies();
        //...
        for (size_t i = 0; i < mOutputs.size(); i++) {
            audio_io_handle_t output = mOutputs.keyAt(i);
            if ((mPhoneState != AUDIO_MODE_IN_CALL) || (output != mPrimaryOutput)) {
                audio_devices_t newDevice = getNewOutputDevice(mOutputs.keyAt(i),true /*fromCache*/);
                bool force = !mOutputs.valueAt(i)->isDuplicated()
                        && (!deviceDistinguishesOnAddress(device)
                                // always force when disconnecting (a non-duplicated device)
                                || (state == AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE));
                setOutputDevice(output, newDevice, force, 0);
            }
        }

        mpClientInterface->onAudioPortListUpdate();
        return NO_ERROR;
    }  // end if is output device
    //... Audio input 处理
    return BAD_VALUE;
}

Ensuite, nous analysons principalement la méthode checkOutputsForDevice et la méthode checkOutputForAllStrategies (conversion d'indicateur), puis analysons la partie d'écriture de données (fonction d'écriture d'AudioTrack).

2.1 Analyse checkOutputsForDevice

L'implémentation du code de checkOutputsForDevice est la suivante:

status_t AudioPolicyManager::checkOutputsForDevice(const sp<DeviceDescriptor> devDesc,
                                                       audio_policy_dev_state_t state,
                                                       SortedVector<audio_io_handle_t>& outputs,
                                                       const String8 address)
{
    audio_devices_t device = devDesc->mDeviceType;
    //...
    if (state == AUDIO_POLICY_DEVICE_STATE_AVAILABLE) {
        //...
        for (ssize_t profile_index = 0; profile_index < (ssize_t)profiles.size(); profile_index++) {
            sp<IOProfile> profile = profiles[profile_index];
            // nothing to do if one output is already opened for this profile
            //...
            if (j != outputs.size()) {
                continue;
            }
            //...
            status_t status = mpClientInterface->openOutput(profile->mModule->mHandle,
                                                            &output,
                                                            &config,
                                                            &desc->mDevice,
                                                            address,
                                                            &desc->mLatency,
                                                            desc->mFlags);
            if (status == NO_ERROR) {
                //...
                if (output != AUDIO_IO_HANDLE_NONE) {
                    addOutput(output, desc);
                    if (deviceDistinguishesOnAddress(device) && address != "0") {
                        //...
                    } else if ((desc->mFlags & AUDIO_OUTPUT_FLAG_DIRECT) == 0) {
                        //...
                        // open a duplicating output thread for the new output and the primary output
                        duplicatedOutput = mpClientInterface->openDuplicateOutput(output,mPrimaryOutput);
                        //...
                    }
                }
            } else {
                output = AUDIO_IO_HANDLE_NONE;
            }
            //...
        }
        //...
    } else { // Disconnect
        //...
    }
    return NO_ERROR;
}

Chaque sortie du fichier de configuration audio_policy.conf sera représentée comme un profil, c'est-à-dire que checkOutputsForDevice détectera tous les profils (sortie), recherchera s'il existe un thread correspondant pour chaque profil et le créera dans le cas contraire.

2.2 Analyse checkOutputForAllStrategies

L'implémentation du code de checkOutputForAllStrategies est la suivante:

void AudioPolicyManager::checkOutputForAllStrategies()
{
    if (mForceUse[AUDIO_POLICY_FORCE_FOR_SYSTEM] == AUDIO_POLICY_FORCE_SYSTEM_ENFORCED)
        checkOutputForStrategy(STRATEGY_ENFORCED_AUDIBLE);
    checkOutputForStrategy(STRATEGY_PHONE);
    if (mForceUse[AUDIO_POLICY_FORCE_FOR_SYSTEM] != AUDIO_POLICY_FORCE_SYSTEM_ENFORCED)
        checkOutputForStrategy(STRATEGY_ENFORCED_AUDIBLE);
    checkOutputForStrategy(STRATEGY_SONIFICATION);
    checkOutputForStrategy(STRATEGY_SONIFICATION_RESPECTFUL);
    checkOutputForStrategy(STRATEGY_ACCESSIBILITY);
    checkOutputForStrategy(STRATEGY_MEDIA);
    checkOutputForStrategy(STRATEGY_DTMF);
    checkOutputForStrategy(STRATEGY_REROUTING);
}

Voici une analyse détaillée de checkOutputForStrategy. L'implémentation du code est la suivante:

void AudioPolicyManager::checkOutputForStrategy(routing_strategy strategy)
{
    /*
     *对于该strategy, 得到它的oldDevice, 进而得到它的outputs (srcOutputs);
     *对于该strategy, 得到它的newDevice, 进而得到它的outputs (dstOutputs);
     */
    audio_devices_t oldDevice = getDeviceForStrategy(strategy, true /*fromCache*/);
    audio_devices_t newDevice = getDeviceForStrategy(strategy, false /*fromCache*/);
    SortedVector<audio_io_handle_t> srcOutputs = getOutputsForDevice(oldDevice, mPreviousOutputs);
    SortedVector<audio_io_handle_t> dstOutputs = getOutputsForDevice(newDevice, mOutputs);

    // also take into account external policy-related changes: add all outputs which are
    // associated with policies in the "before" and "after" output vectors
    for (size_t i = 0 ; i < mPreviousOutputs.size() ; i++) {
        const sp<AudioOutputDescriptor> desc = mPreviousOutputs.valueAt(i);
        if (desc != 0 && desc->mPolicyMix != NULL) {
            srcOutputs.add(desc->mIoHandle);
        }
    }
    for (size_t i = 0 ; i < mOutputs.size() ; i++) {
        const sp<AudioOutputDescriptor> desc = mOutputs.valueAt(i);
        if (desc != 0 && desc->mPolicyMix != NULL) {
            dstOutputs.add(desc->mIoHandle);
        }
    }
    //如果这2个srcOutputs、dstOutputs不相同, 表示需要迁移
    if (!vectorsEqual(srcOutputs,dstOutputs)) {
        // mute strategy while moving tracks from one output to another
        for (size_t i = 0; i < srcOutputs.size(); i++) {
            sp<AudioOutputDescriptor> desc = mOutputs.valueFor(srcOutputs[i]);
            if (desc->isStrategyActive(strategy)) {
                setStrategyMute(strategy, true, srcOutputs[i]);
                setStrategyMute(strategy, false, srcOutputs[i], MUTE_TIME_MS, newDevice);
            }
        }

        // Move effects associated to this strategy from previous output to new output
        if (strategy == STRATEGY_MEDIA) {
            audio_io_handle_t fxOutput = selectOutputForEffects(dstOutputs);
            SortedVector<audio_io_handle_t> moved;
            for (size_t i = 0; i < mEffects.size(); i++) {
                sp<EffectDescriptor> effectDesc = mEffects.valueAt(i);
                if (effectDesc->mSession == AUDIO_SESSION_OUTPUT_MIX &&
                        effectDesc->mIo != fxOutput) {
                    if (moved.indexOf(effectDesc->mIo) < 0) {
                        mpClientInterface->moveEffects(AUDIO_SESSION_OUTPUT_MIX, effectDesc->mIo,
                                                       fxOutput);
                        moved.add(effectDesc->mIo);
                    }
                    effectDesc->mIo = fxOutput;
                }
            }
        }
        // Move tracks associated to this strategy from previous output to new output
        for (int i = 0; i < AUDIO_STREAM_CNT; i++) {
            if (i == AUDIO_STREAM_PATCH) {
                continue;
            }
            if (getStrategy((audio_stream_type_t)i) == strategy) {
                /*
                 * 把对应的Track设置为invalidate状态即可,
                 * App写AudioTrack时发现它是invalidate状态,
                 * 就会重新创建新的Track
                 */
                mpClientInterface->invalidateStream((audio_stream_type_t)i);
            }
        }
    }
}

L'opération finale invalidateStream est l'opération invalidateStream d'AudioFlinger, l'implémentation du code est la suivante:

status_t AudioFlinger::invalidateStream(audio_stream_type_t stream)
{
    Mutex::Autolock _l(mLock);
    for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
        PlaybackThread *thread = mPlaybackThreads.valueAt(i).get();
        thread->invalidateTracks(stream);
    }
    return NO_ERROR;
}

Le code invalidateTracks du thread est implémenté comme suit:

void AudioFlinger::PlaybackThread::invalidateTracks(audio_stream_type_t streamType)
{
    Mutex::Autolock _l(mLock);
    size_t size = mTracks.size();
    for (size_t i = 0; i < size; i++) {
        sp<Track> t = mTracks[i];
        if (t->streamType() == streamType) {
            t->invalidate();
        }
    }
}

Le code d'invalidation de Track est implémenté comme suit:

void AudioFlinger::PlaybackThread::Track::invalidate()
{
    // FIXME should use proxy, and needs work
    audio_track_cblk_t* cblk = mCblk;
    //设置标志位
    android_atomic_or(CBLK_INVALID, &cblk->mFlags);
    android_atomic_release_store(0x40000000, &cblk->mFutex);
    // client is not in server, so FUTEX_WAKE is needed instead of FUTEX_WAKE_PRIVATE
    (void) syscall(__NR_futex, &cblk->mFutex, FUTEX_WAKE, INT_MAX);
    mIsInvalid = true;
}

En fait, cette opération android_atomic_or (CBLK_INVALID, & cblk-> mFlags); consiste à définir un bit de drapeau, puis après avoir défini le bit de drapeau, vous pouvez détecter le changement du bit de drapeau après avoir réécrit les données dans l'APP, et l'opération correspondante sera effectuée. Ensuite, nous utiliserons AudioTrack d'APP pour écrire des données comme une opportunité d'analyse.

2.3 Écriture des données après la commutation

Selon l'analyse précédente, la méthode d'écriture de l'AudioTrack dans la couche Java sera appelée et la méthode d'acquisitionBuffer de l'AudioTrack dans la couche Native sera appelée.

status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,
        struct timespec *elapsed, size_t *nonContig)
{
        //...
        do {
            //...
            newSequence = mSequence;
            // did previous obtainBuffer() fail due to media server death or voluntary invalidation?
            if (status == DEAD_OBJECT) {
                // re-create track, unless someone else has already done so
                if (newSequence == oldSequence) {
                    status = restoreTrack_l("obtainBuffer");
                    //...
                }
            }
        //...
        status = proxy->obtainBuffer(&buffer, requested, elapsed);

    } while ((status == DEAD_OBJECT) && (tryCounter-- > 0));
    //...
    return status;
}

@ 1 analyse restoreTrack_l ()

Le code de la fonction restoreTrack_l () ici est implémenté comme suit:

status_t AudioTrack::restoreTrack_l(const char *from)
{
    //...
    result = createTrack_l();
    //...
    return result;
}

Selon l'analyse du chapitre précédent, createTrack_l recrée la piste ici.

Analyse getBuffer du proxy @ 2

Ici, nous nous concentrons sur l'analyse de la réalisation de l'objectBuffer du proxy, le code est le suivant:

status_t ClientProxy::obtainBuffer(Buffer* buffer, const struct timespec *requested,
        struct timespec *elapsed)
{
    //...
    for (;;) {
        int32_t flags = android_atomic_and(~CBLK_INTERRUPT, &cblk->mFlags);
        // check for track invalidation by server, or server death detection
        if (flags & CBLK_INVALID) {
            ALOGV("Track invalidated");
            status = DEAD_OBJECT;//被设置成CBLK_INVALID
            goto end;
        }
        //...
    }
end:
    //...
    return status;
}

Ici, le statut retourné sera défini sur DEAD_OBJECT en fonction du bit d'identification CBLK_INVALID.

Un bref résumé: pendant le processus d'écriture des données, l'ancienne piste a été supprimée et une nouvelle piste a été créée.

 

Publié 289 articles originaux · loué 47 · 30 000+ vues

Je suppose que tu aimes

Origine blog.csdn.net/vviccc/article/details/105409403
conseillé
Classement