Android音频路由策略

Android音频路由策略

1. 分析音频的默认路由

在之前的文章音频输出设备是如何决定的中,我们知道AudioTrack的创建过程会调用到getOutputForAttr

status_t AudioPolicyManager::getOutputForAttr(const audio_attributes_t *attr,
                                              audio_io_handle_t *output,
                                              audio_session_t session,
                                              audio_stream_type_t *stream,
                                              uid_t uid,
                                              uint32_t samplingRate,
                                              audio_format_t format,
                                              audio_channel_mask_t channelMask,
                                              audio_output_flags_t flags,
                                              audio_port_handle_t selectedDeviceId,
                                              const audio_offload_info_t *offloadInfo)
{
	...
    sp deviceDesc;
    for (size_t i = 0; i < mAvailableOutputDevices.size(); i++) {
        if (mAvailableOutputDevices[i]->getId() == selectedDeviceId) {
			ALOGE("Jon,change route\n");
            deviceDesc = mAvailableOutputDevices[i];
            break;
        }
    }
    mOutputRoutes.addRoute(session, *stream, SessionRoute::SOURCE_TYPE_NA, deviceDesc, uid);
    ...
}

如上就是音频默认路由创建的地方
参数说明:
session:会话ID,默认情况下是在每个AudioTrack创建的时候,会在AudioTrack::set方法中创建一个全局唯一的会话id。
stream:music/ring/voice 等音频流类型
SessionRoute::SOURCE_TYPE_NA:音频数据源类型(MIC/CAMCORDER/VOICE)
deviceDesc:这个参数是特别要重点关注的,它会对音频路由造成非常大的影响:

1.如果是在AudioTrack第一次构造的时候CALL进来的,那么deviceDesc为null;
2.如果之后AudioTrack对象有调用setOutputDevice重新设置输出设备,那么会由于之前创建的AudioTrack变为invalid从而导致AudioTrack被重新分配进而CALL到getOutputForAttr重新设置路由,这时deviceDesc就有可能不为null了。

uid:当前调用进程的uid
我们先分析AudioTrack的第一次构造的时候

void SessionRouteMap::addRoute(audio_session_t session,
                               audio_stream_type_t streamType,
                               audio_source_t source,
                               sp descriptor,
                               uid_t uid)
{
    if (mMapType == MAPTYPE_INPUT && streamType != SessionRoute::STREAM_TYPE_NA) {
        ALOGE("Adding Output Route to InputRouteMap");
        return;
    } else if (mMapType == MAPTYPE_OUTPUT && source != SessionRoute::SOURCE_TYPE_NA) {
        ALOGE("Adding Input Route to OutputRouteMap");
        return;
    }
	
	//先确认当前会话是否在之前已经绑定到了一个路由上,如果是则直接取出路由,否则路由为null
    sp route = indexOfKey(session) >= 0 ? valueFor(session) : 0;

    if (route != 0) {
    	//如果当前会话已经存在路由了,那么先判断路由指向的descriptor是否发生了改变,如果是则标记路由改变,增加路由引用计数同时更新路由的设备描述符
        if (((route->mDeviceDescriptor == 0) && (descriptor != 0)) ||
                ((route->mDeviceDescriptor != 0) &&
                 ((descriptor == 0) || (!route->mDeviceDescriptor->equals(descriptor))))) {
            route->mChanged = true;
        }
        route->mRefCount++;
        route->mDeviceDescriptor = descriptor;
    } else {
		//如果当前会话未曾绑定过路由,则为当前会话新建一个路由
        route = new SessionRoute(session, streamType, source, descriptor, uid);
        //增加当前路由的引用计数为1
        route->mRefCount++;
        //将路由和会话绑定
        add(session, route);
        //如果当前的设备描述符不为null,则标记路由发生改变(默认创建AudioTrack由于descriptor为null,因此route->mChanged是不会被标记为true的)
        if (descriptor != 0) {
            route->mChanged = true;
        }
    }
}

ok,关于AudioTrack默认的音频路由我们姑且到此为止

我们再看看,track->start也就是音频播放流程
AudioTrack->start会调用到AudioFlinger内部的PlaybackThread中Track的start函数如下:

frameworks\av\services\audioflinger\Tracks.cpp

status_t AudioFlinger::PlaybackThread::Track::start(AudioSystem::sync_event_t event __unused,
                                                    audio_session_t triggerSession __unused)
{ 
	...
	status = playbackThread->addTrack_l(this);
	...
}
status_t AudioFlinger::PlaybackThread::addTrack_l(const sp& track)
{
	...
	status = AudioSystem::startOutput(mId, track->streamType(),
                                              track->sessionId());
	...
}

最终会通过AudioPolicyService->AudioPolicyManagerBase的startOutput函数

status_t AudioPolicyManager::startOutput(audio_io_handle_t output,
                                             audio_stream_type_t stream,
                                             audio_session_t session)
{
	//通过当前的音频路径获取索引
	ssize_t index = mOutputs.indexOfKey(output);
	//通过索引获取音频路径的设备描述符
	sp outputDesc = mOutputs.valueAt(index);
	audio_devices_t newDevice;
	...
	//判断音频路由是否发生改变,如果发生了改变,那么就用新的路由,新的device开启音频播放
	} else if (mOutputRoutes.hasRouteChanged(session)) {
	newDevice = getNewOutputDevice(outputDesc, false /*fromCache*/);
	checkStrategyRoute(getStrategy(stream), output);
	}
	...
	//开启新的路由,这里会重新设置output的device
	status_t status = startSource(outputDesc, stream, newDevice, address, &delayMs);
}

我们之前其实已经讲过了,判断音频路由切换的原则,就是音频路由指向的设备描述符发生改变。但是需要记住一点的是AudioTrack对象通过setOutputDevice方法改变selectedDeviceId,这并不一定会改变路由,切记。原因如下:

    sp deviceDesc;
    for (size_t i = 0; i < mAvailableOutputDevices.size(); i++) {
        if (mAvailableOutputDevices[i]->getId() == selectedDeviceId) {
            deviceDesc = mAvailableOutputDevices[i];
            break;
        }
    }

如上,setOutputDevice设置的device必须是available的,这个可以通过dumpsys media.audio_policy 去确认当前可用的设备集合。还有一点需要留意的是这儿匹配device使用的是id匹配而不是type,因为有的设备的id和type并不一致,比如“Telephony Tx”

 - Available output devices:
  Device 1:
  - id:  1                             //匹配的是这个字段
  - tag name: Earpiece
  - type: AUDIO_DEVICE_OUT_EARPIECE        //并不匹配这个字段          
  - Profiles:
      Profile 0:
          - format: AUDIO_FORMAT_PCM_16_BIT
          - sampling rates:48000
          - channel masks:0x0010
  Device 2:
  - id:  2
  - tag name: Speaker
  - type: AUDIO_DEVICE_OUT_SPEAKER                        
  - Profiles:
      Profile 0:
          - format: AUDIO_FORMAT_PCM_16_BIT
          - sampling rates:48000
          - channel masks:0x0003
  Device 3:
  - id:  3
  - tag name: Telephony Tx
  - type: AUDIO_DEVICE_OUT_TELEPHONY_TX                   
  - Profiles:
      Profile 0:
          - format: AUDIO_FORMAT_PCM_16_BIT
          - sampling rates:8000, 16000
          - channel masks:0x0001, 0x0003

2. 切换音频路由

第一小节分析了默认的音频路由以及开启音频路由的方式,这一小节我们分析实际的音频路由切换。
先给出一个音频路由切换的例子:

#define BUF_SZ 44100
#define FRAME_COUNT 1024
#define pcm_file "/data/xusong-shzj2.wav"

int AudioTrackTest::Test01() {
    long rate = 44100;
	int fd = 0;
	int length = 0;
	unsigned long int MinFrameCount = 0;
	unsigned char mBuffer[FRAME_COUNT];
	
	fd = open(pcm_file,O_RDWR);
	if(fd < 0)
	{
		printf("%s open failed\n",pcm_file);
	}

	/*
	 * MinFrameCount : 
	 *		要达到正常的播放,那么最小的帧数是这么多
	 */
	AudioTrack::getMinFrameCount(&MinFrameCount,AUDIO_STREAM_MUSIC,rate);
	printf("MinFrameCount = %lu\n",MinFrameCount);
	ALOGE("Jon,AudioTrack Construct E");
	sp track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
    							rate,
    							AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
    							AUDIO_CHANNEL_OUT_STEREO,
    							MinFrameCount);

    status_t status = track->initCheck();
    if(status != NO_ERROR) {
		track.clear();
        ALOGD("Failed for initCheck()");
		return -1;
	}

	ALOGE("Jon,AudioTrack Construct X");

    // start play
    ALOGD("start");
	
	track->setOutputDevice(AUDIO_DEVICE_OUT_SPEAKER);
	track->setVolume(0,0);
	ALOGE("Jon,AudioTrack Start");
    track->start();
	int us;
	do{
		length = read(fd,mBuffer,FRAME_COUNT);
		if(length > 0)
		{
			track->write(mBuffer,length,false);
			us = length*1000*1000/(rate*2*16);
			usleep(us);
		}else{
			lseek(fd,0,SEEK_SET);
			length = 0;
		}

	}while(length >= 0);	
	
    usleep(20000);

    ALOGD("stop");
    track->stop();

    usleep(20000);

    return 0;

}

我的目的很简单,在耳机和Speaker都存在的情况下music stream是从耳机播放的,我现在要在start之前切换到Speaker输出,实现音频路由的切换。很明显我在AudioTrack和start之间利用setOutputDevice重新指定了输出设备。我们看看会发生什么。

frameworks\av\media\libmedia\AudioTrack.cpp

status_t AudioTrack::setOutputDevice(audio_port_handle_t deviceId) {
    AutoMutex lock(mLock);
    if (mSelectedDeviceId != deviceId) {
    	//设置device id
        mSelectedDeviceId = deviceId;
        //将track的控制块的flags标记为invalid,以使得track重新分配
        android_atomic_or(CBLK_INVALID, &mCblk->mFlags);
    }
    return NO_ERROR;
}

setOutputDevice就是这么简单,关键在于后面Track的start函数,let‘s go,

status_t AudioTrack::start()
{
	...
	status_t status = NO_ERROR;
	//当前不运行
    if (!(flags & CBLK_INVALID)) {
        status = mAudioTrack->start();
        if (status == DEAD_OBJECT) {
            flags |= CBLK_INVALID;
        }
    }
	//由于之前setOutputDevice已经将flags设置为CBLK_INVALID,因此会导致Track被重新creat并重新start
    if (flags & CBLK_INVALID) {
        status = restoreTrack_l("start");
    }
    ...
}

status_t AudioTrack::restoreTrack_l(const char *from)
{
	status_t result = createTrack_l();
	...
	result = mAudioTrack->start();
	...
}

createTrack_l会调用getOutputForAttr重新修改一次路由(参考第一小节),这个时候mSelectedDeviceId也就派上用场了,从而更新路由后,在AudioTrack::start中通过startOutput更改音频路径的device。

3. 分析路由切换中的Id匹配

1. 分析音频的默认路由的分析中我们清楚了路由切换是需要将待切换设备的Id和当前系统可用输出设备的Id进行匹配的,只有Id匹配的设备才会允许路由的切换。那么问题来了,系统中可用输出设备的Id是从何而来的呢 ?

这个Id和音频路径的Id是非常类似的,它也是一个全局唯一的变量,然后逐步递增的

frameworks\av\services\audiopolicy\common\managerdefinitions\src\AudioPort.cpp

audio_port_handle_t AudioPort::getNextUniqueId()
{
    return static_cast(android_atomic_inc(&mNextUniqueId));
}

如上,就是这样递增的。其递增的路径主要有2条,都是在AudioPolicyManager对象的构造函数中实现的

for (size_t k = 0; k  < supportedDevices.size(); k++) {
	ssize_t index = mAvailableOutputDevices.indexOf(supportedDevices[k]);
	// give a valid ID to an attached device once confirmed it is reachable
	if (index >= 0 && !mAvailableOutputDevices[index]->isAttached()) {
		mAvailableOutputDevices[index]->attach(mHwModules[i]);
	}
}

如上,是AudioPolicyManager逐一解析module的音频路径,并将音频路径中Available的attach上去。看下attach过程

frameworks\av\services\audiopolicy\common\managerdefinitions\include\DeviceDescriptor.h

void DeviceDescriptor::attach(const sp& module)
{
    AudioPort::attach(module);
    mId = getNextUniqueId();
}

这里需要留意的是当前可用的三个输出device并不是一次attach上去的,因为他们其实是属于2个不同的音频路径,我们看下dumpsys media.audio_policy就知道了

HW Modules dump:
- HW Module 1:
  - name: primary
  - handle: 10
  - version: 2.0
  - outputs:
    output 0:
    - name: primary output
    - Profiles:
        Profile 0:
            - format: AUDIO_FORMAT_PCM_16_BIT
            - sampling rates:48000
            - channel masks:0x0003
    - flags: 0x0006
    - Supported devices:
      Device 1:
      - id:  1
      - tag name: Earpiece
      - type: AUDIO_DEVICE_OUT_EARPIECE                       
      Device 2:
      - id:  2
      - tag name: Speaker
      - type: AUDIO_DEVICE_OUT_SPEAKER                        
      Device 3:
      - tag name: Wired Headset
      - type: AUDIO_DEVICE_OUT_WIRED_HEADSET                  
      Device 4:
      - tag name: Wired Headphones
      - type: AUDIO_DEVICE_OUT_WIRED_HEADPHONE                
      Device 5:
      - tag name: Line
      - type: AUDIO_DEVICE_OUT_LINE                           
      Device 6:
      - tag name: BT SCO All
      - type: AUDIO_DEVICE_OUT_ALL_SCO                        
      Device 7:
      - tag name: HDMI
      - type: AUDIO_DEVICE_OUT_AUX_DIGITAL                    
      Device 8:
      - tag name: Proxy
      - type: AUDIO_DEVICE_OUT_PROXY                          
      Device 9:
      - tag name: FM
      - type: AUDIO_DEVICE_OUT_FM     

如上音频路径primary output上只含有Speaker和Earpiece,因此Telephony Tx的Id并不是在这条音频路径上赋值的,那么在哪儿呢。

output 6:
- name: voice_tx
- Profiles:
    Profile 0:
        - format: AUDIO_FORMAT_PCM_16_BIT
        - sampling rates:8000, 16000, 48000
        - channel masks:0x0001, 0x0003
- flags: 0x0000
- Supported devices:
  Device 1:
  - id:  6
  - tag name: Telephony Tx
  - type: AUDIO_DEVICE_OUT_TELEPHONY_TX    

如上Telephony Tx的Id实际上是在primary的module音频路径voice_tx上完成赋值的。

到此,我们总结下为什么Telephony Tx的Id是6,过程如下:

在primary的module下,系统存在3个可用输出设备,分别为Speaker,Earpiece,Telephony Tx
首先扫描音频路径0:name: primary output,发现其下支持了2个输出设备Speaker,Earpiece,之后调用mAvailableOutputDevices[index]->attach(mHwModules[i]);那么在这个过程中全局的mNextUniqueId会自增2次,且分别将Speaker的Id设置为1,将Earpiece的Id设置为2,这个很好理解,随后我们发现这条音频路径下的其余7个输出设备中并不包含Telephony Tx,因此本次音频路径的扫描就此结束,随后将这条音频路径加入到系统中,与此同时由于addOutput(output, outputDesc);这个方法也会导致mNextUniqueId这个全局变量自增,需要特别留意

AudioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface)
{
	...
	for (size_t k = 0; k  < supportedDevices.size(); k++) {
		ssize_t index = mAvailableOutputDevices.indexOf(supportedDevices[k]);
		// give a valid ID to an attached device once confirmed it is reachable
		if (index >= 0 && !mAvailableOutputDevices[index]->isAttached()) {
			mAvailableOutputDevices[index]->attach(mHwModules[i]);
		}
	}

	addOutput(output, outputDesc);
	setOutputDevice(outputDesc,
		outputDesc->mDevice,
		true,
		0,
		NULL,
		address.string());
	...
}

void AudioPolicyManager::addOutput(audio_io_handle_t output, sp outputDesc)
{
    outputDesc->setIoHandle(output);
    mOutputs.add(output, outputDesc);
    updateMono(output); // update mono status when adding to output list
    nextAudioPortGeneration();
}

frameworks\av\services\audiopolicy\common\managerdefinitions\src\AudioOutputDescriptor.cpp

void SwAudioOutputDescriptor::setIoHandle(audio_io_handle_t ioHandle)
{
    mId = AudioPort::getNextUniqueId();
    mIoHandle = ioHandle;
}

如上这就是mNextUniqueId被增加的第二条路径

ok,继续之前的话题,在音频路径0:name: primary outputmNextUniqueId被加到了3了,之后在音频路径1:name: raw上由于Speaker,Earpiece这2个设备已经在之前attach过了这里并不会再次的attach,因此只会在addOutput的过程中会被自增一次,从而mNextUniqueId被加到了4,再然后音频路径2:name: deep_buffer上的过程和音频路径1:name: raw上是一致也只会在addOutput中被自增一次从而到5,最后扫描到音频路径6:name: voice_tx(其余的音频路径因不满足条件被系统放弃掉了),在这条路径上终于扫描到了Available输出设备Telephony Tx,之后我们调用mAvailableOutputDevices[index]->attach(mHwModules[i]),在这个过程中会将mNextUniqueId自增为6,同时将其赋值给Telephony Tx的Id。

如上就是当前系统Telephony Tx的Id为6的原因

4. 再来个例子我们巩固一下

我们将Android源码例子中AudioTrack改一下,让它播放设备从Speaker切换到TELEPHONY_TX输出。首先,我们系统中可用输出设备中,这2个设备都是存在的,理论上这没什么不妥。

int AudioTrackTest::Test01() {

    sp heap;
    sp iMem;
    uint8_t* p;

    short smpBuf[BUF_SZ];
    long rate = 44100;
    unsigned long phi;
    unsigned long dPhi;
    long amplitude;
    long freq = 1237;
    float f0;

    f0 = pow(2., 32.) * freq / (float)rate;
    dPhi = (unsigned long)f0;
    amplitude = 1000;
    phi = 0;
    Generate(smpBuf, BUF_SZ, amplitude, phi, dPhi);  // fill buffer

    for (int i = 0; i < 1024; i++) {
        heap = new MemoryDealer(1024*1024, "AudioTrack Heap Base");

        iMem = heap->allocate(BUF_SZ*sizeof(short));

        p = static_cast(iMem->pointer());
        memcpy(p, smpBuf, BUF_SZ*sizeof(short));

        sp track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
               rate,
               AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
               AUDIO_CHANNEL_OUT_MONO,
               iMem);

        status_t status = track->initCheck();
        if(status != NO_ERROR) {
            track.clear();
            ALOGD("Failed for initCheck()");
            return -1;
        }
		track->setVolume(0,0);
		track->setOutputDevice(6);

        // start play
        ALOGD("start");
        track->start();


        ALOGD("stop");
        track->stop();
        iMem.clear();
        heap.clear();
        usleep(20000);
    }

    return 0;

}

你看我们只加入了一行代码,track->setOutputDevice(6);,这里的设备号为6就不赘述了,查看上面的分析。我们看看代码层面做了哪些动作。
在第一次调用audioFlinger->createTrack,系统会为当前的音频播放选择输出设备为Speak,音频策略是STRATEGY_MEDIA,Stream类型为Music。音频路径为:name: primary output
当我们切换设备的时候,会第二次调用audioFlinger->createTrack,系统会将输出设备改为TELEPHONY_TX,音频策略不变,Stream类型不变。音频路径切换为:name: voice_tx(切换逻辑是:从系统保存的所有音频路径中找出第一个包含TELEPHONY_TX设备的音频路径)然后调用startOutput

frameworks\av\services\audiopolicy\managerdefault\AudioPolicyManager.cpp

    } else if (mOutputRoutes.hasRouteChanged(session)) {
        newDevice = getNewOutputDevice(outputDesc, false /*fromCache*/);
        checkStrategyRoute(getStrategy(stream), output);
    } else {
    ...
    status_t status = startSource(outputDesc, stream, newDevice, address, &delayMs);

调用getNewOutputDevice

hardware\qcom\audio\policy_hal\AudioPolicyManager.cpp

audio_devices_t AudioPolicyManagerCustom::getNewOutputDevice(const sp& outputDesc,
                                                       bool fromCache)
{
	...
    } else if (isStrategyActive(outputDesc, STRATEGY_MEDIA)) {
        device = getDeviceForStrategy(STRATEGY_MEDIA, fromCache);
    } else if (isStrategyActive(outputDesc, STRATEGY_DTMF)) {
    ...
}

第一次切换设备的时候,由于设备AUDIO_DEVICE_OUT_TELEPHONY_TX对于Music的引用计数是为0的,因此直接返回device为0,之后调用startSource,注意这个时候device是为0的

status_t AudioPolicyManagerCustom::startSource(sp outputDesc,
                                             audio_stream_type_t stream,
                                             audio_devices_t device,
                                             const char *address,
                                             uint32_t *delayMs)
{
	//为当前设备对应的stream增加引用计数
	outputDesc->changeRefCount(stream, 1);
	if (outputDesc->mRefCount[stream] == 1 || device != AUDIO_DEVICE_NONE) {
		if (device == AUDIO_DEVICE_NONE) {
				//这个时候isStrategyActive(outputDesc, STRATEGY_MEDIA)为true
	            device = getNewOutputDevice(outputDesc, false /*fromCache*/);
	        }
	    ...
	}
	//设置输出
	muteWaitMs = setOutputDevice(outputDesc, device, force, 0, NULL, address);
	...
}

最后代码在startSource将当前设备STRATEGY_MEDIA的策略active。

device = getDeviceForStrategy(STRATEGY_MEDIA, fromCache);

这时上面代码将返回AUDIO_DEVICE_OUT_TELEPHONY_TX

frameworks\av\services\audiopolicy\managerdefault\AudioPolicyManager.cpp

audio_devices_t AudioPolicyManager::getDeviceForStrategy(routing_strategy strategy,
                                                         bool fromCache)
{
	/*
	 * 1. 判断当前路由是否是active,且stream的路由策略匹配,则直接返回路由指向的设备类型
	 * 2. 至于路由指向的设备切换查看本文之前的分析
	*/
    for (size_t routeIndex = 0; routeIndex < mOutputRoutes.size(); routeIndex++) {
        sp route = mOutputRoutes.valueAt(routeIndex);
        routing_strategy routeStrategy = getStrategy(route->mStreamType);
        if ((routeStrategy == strategy) && route->isActive()) {
            return route->mDeviceDescriptor->type();
        }
    }

    if (fromCache) {
        ALOGVV("getDeviceForStrategy() from cache strategy %d, device %x",
              strategy, mDeviceForStrategy[strategy]);
        return mDeviceForStrategy[strategy];
    }
    return mEngine->getDeviceForStrategy(strategy);
}

你可能感兴趣的:(Android-Audio)