音频Open Output业务逻辑
open output顾名思义,你可以理解为打开一条输出通道,这条通道上跑着一定格式、采样率等特定的数据,通道的终点是一个音频设备,用于播放;所以这个open output业务就是初始化这条通道,为这个通道建立各种所需功能等。
openOutput总流程图
纠正上图的错误,上图中其实没有DeviceHalHidl和IDevice的,在android 10中,经过AudioStreamOut后,就会通过HIDL提供的服务端,通过服务端跨进程调用到hal的PrimaryDevice去打开设备
openOutput流程中的关键点
流程启动点
这里以AudioPolicyManager类的initialize函数为起点分析,当AudioPolicyManager加载解析完音频配置文件audio_policy_configuration后,然后会加载配置文件中的每个模块loadHwModule,遍历模块中所有的数据流,最后打开每条流及其支持的设备;如下代码:
status_t AudioPolicyManager::initialize() {
for (const auto& hwModule : mHwModulesAll) {
//配置文件加载进来为module,现在要加载so源码了,mpClientInterface是AudioPolicyService那边,负责加载源码
hwModule->setHandle(mpClientInterface->loadHwModule(hwModule->getName()));
if (hwModule->getHandle() == AUDIO_MODULE_HANDLE_NONE) {
ALOGW("could not open HW module %s", hwModule->getName());
continue;
}
mHwModules.push_back(hwModule);
for (const auto& outProfile : hwModule->getOutputProfiles()) {
//选择当前output流支持的优先级最高的一个,mDefaultOutputDevice > supportDevice[0]
const DeviceVector &supportedDevices = outProfile->getSupportedDevices();
//mAvailableOutputDevices为attached标签设备
DeviceVector availProfileDevices = supportedDevices.filter(mAvailableOutputDevices);
sp<DeviceDescriptor> supportedDevice = 0;
if (supportedDevices.contains(mDefaultOutputDevice)) {
//优先选择默认绑定的设备
supportedDevice = mDefaultOutputDevice;
} else {
// choose first device present in profile's SupportedDevices also part of
// mAvailableOutputDevices.
if (availProfileDevices.isEmpty()) {
continue;
}
//优先取第一个
supportedDevice = availProfileDevices.itemAt(0);
}
if (!mAvailableOutputDevices.contains(supportedDevice)) {
continue;
}
//最终这个supportedDevice是一个DeviceDescriptor,是attached或defauleAttach标签内存在的
sp<SwAudioOutputDescriptor> outputDesc = new SwAudioOutputDescriptor(outProfile,
mpClientInterface);
audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
status_t status = outputDesc->open(nullptr, DeviceVector(supportedDevice),
AUDIO_STREAM_DEFAULT,
AUDIO_OUTPUT_FLAG_NONE, &output);
......
//打开成功的流加入进来
addOutput(output, outputDesc);
setOutputDevices(outputDesc,
DeviceVector(supportedDevice),
true,
0,
NULL);
}
updateDevicesAndOutputs();
return status;
}
上述代码执行逻辑:
- 遍历每个HwModule,在遍历HwModule里面所有的IOProfile。
- IOProfile理解为一个数据流,它有可能支持多种format、sampleRate、channel等,在后面打开时需要传递这些参数,如果一个IOProfile支持多种格式那选择哪个呢?
- 同时,如果IOProfile是一个输出流,数据流最终会流到音频设备,支持播放这个流可能有多个设备,那么取哪一个设备?
上面代码已经告诉我们一个IOProfile如何选择哪个音频Device,规则如下:
a. 先获取IOProfile所有支持的设备device
b. 用mAvailableOutputDevices来过滤上个步骤中所有的设备,也就是取个交集。这个mAvailableOutputDevices其实就是audio_policy_configuration配置文件中attachedDevices标签内的设备
c. 如果过滤后的设备集devices中包含配置文件defaultOutputDevice标签指定的设备就选取它,否则就去集合的第一个设备作为选取设备;
这样就确定要打开哪个设备了,并且打卡成功后,使用mOutputs成员变量存储起来;
如果你对上面说的标签、配置文件不知道是什么东西,那么你可能还需要学习这篇文章
IOProfile选择哪个格式?
格式确定逻辑在SwAudioOutputDescriptor的open函数中,如下:
status_t SwAudioOutputDescriptor::open(const audio_config_t *config,
const DeviceVector &devices,
audio_stream_type_t stream,
audio_output_flags_t flags,
audio_io_handle_t *output)
{
mDevices = devices;
//address一般都是空串,这里大多也是个空串
const String8& address = devices.getFirstValidAddress();
/* devices是一个集合,这里的types返回所有设备的type类型取或,
* 而且type类型能反应这个设备类型、和名称,如AUDIO_DEVICE_OUT_EARPIECE
* 就是一个输出类型设备、名字叫听筒earPiece
**/
audio_devices_t device = devices.types();
audio_config_t lConfig;
if (config == nullptr) {
lConfig = AUDIO_CONFIG_INITIALIZER;
/* SwAudioOutputDescriptor的IOProfile内有多个profile,支持多种格式,取哪个呢?
* 取有效,也就是samplerate、channel、format都不为空且format最大的一个,
* mSampleRate在构造函数时就确定了*/
lConfig.sample_rate = mSamplingRate;
lConfig.channel_mask = mChannelMask;
lConfig.format = mFormat;
} else {
lConfig = *config;
}
......
ALOGV("opening output for device %s profile %p name %s",
mDevices.toString().c_str(), mProfile.get(), mProfile->getName().string());
//会到驱动层kernel,打开一个output在audioFlinger会为其创建一个线程,用键值对存储,key为返回的output,本地保存
status_t status = mClientInterface->openOutput(mProfile->getModuleHandle(),
output,
&lConfig,
&device,
address,
&mLatency,
mFlags);
.....
return status;
}
上述代码已经确定了选择某个具体的设备了,你看出来了吗?
就在代码audio_devices_t device = devices.types()这里,devices是一个集合,这里的types返回所有设备的type类型取或值,而且type类型能反应这个设备类型、和名称,如AUDIO_DEVICE_OUT_EARPIECE,就是一个输出类型设备、名字叫听筒earPiece
除此之外,还直接将成员变量mSamplingRate、mChannelMask和mFormat赋值给此次open的参数,那这些成员什么时候赋初值的呢?
答案就是在构造函数里面撒:
AudioOutputDescriptor::AudioOutputDescriptor(const sp<AudioPort>& port,
AudioPolicyClientInterface *clientInterface)
: mPort(port), mClientInterface(clientInterface)
{
if (mPort.get() != nullptr) {
//因为mPort里面可能有多重profile,支持不同的format、sample等,取有效并且format最大的一个
mPort->pickAudioProfile(mSamplingRate, mChannelMask, mFormat);
....
}
}
void AudioPort::pickAudioProfile(uint32_t &samplingRate,
audio_channel_mask_t &channelMask,
audio_format_t &format) const
{
format = AUDIO_FORMAT_DEFAULT;
samplingRate = 0;
channelMask = AUDIO_CHANNEL_NONE;
// 检查集合profile至少有一个有效,也就是samplerate、channel和format同时不能为空
if (!mProfiles.hasValidProfile()) {
return;
}
//sPcmFormatCompareTable是一个已经初始化的数组,内枚举了支持的format格式,这里取最大一个采样率
audio_format_t bestFormat = sPcmFormatCompareTable[ARRAY_SIZE(sPcmFormatCompareTable) - 1];
......
//遍历每个Profile的采样率、格式等信息,取有效并且format最大的一个
for (size_t i = 0; i < mProfiles.size(); i ++) {
......
audio_format_t formatToCompare = mProfiles[i]->getFormat();
//比较format取大值,并且当前format不能比sPcmFormatCompareTable数组最后一个值大,
//也就是必须是sPcmFormatCompareTable支持的format才行
if ((compareFormats(formatToCompare, format) > 0) &&
(compareFormats(formatToCompare, bestFormat) <= 0)) {
uint32_t pickedSamplingRate = 0;
audio_channel_mask_t pickedChannelMask = AUDIO_CHANNEL_NONE;
pickChannelMask(pickedChannelMask, mProfiles[i]->getChannels());
pickSamplingRate(pickedSamplingRate, mProfiles[i]->getSampleRates());
//上面如果选取结果有效,就把值写入到参数的指针;从而也确定了AudioOutputDescriptor的成员变量了
if (formatToCompare != AUDIO_FORMAT_DEFAULT && pickedChannelMask != AUDIO_CHANNEL_NONE
&& pickedSamplingRate != 0) {
format = formatToCompare;
channelMask = pickedChannelMask;
samplingRate = pickedSamplingRate;
// TODO: shall we return on the first one or still trying to pick a better Profile?
}
}
}
}
总结一句话就是:选取format格式最大,且sampleRate、format和channel都有值的一个profile。
这里我们可以分析,一个openOutput实质就是要打开一条输出通道并可用,音频数据流从这条通道上流向某个设备,这个通道支持特定的sampleRate、format、channel,以及它的音频设备也要打开,最后设备可以接收音频数据后直接播放
AudioFlinger创建线程Thread
在AudioFlinger的openOutput_l方法中,openOutputStream打开成功后,会为其创建一个播放线程,但是为什么打开一个通道就要为它创建一个线程呢?
我认为因为这个通道创建成功后,后续会持续把音频数据灌进去,有时对播放音频还有实时性要求,这样的需求就为它创建一个线程来专门跑,即不影响AudioFlinger的其他任务,也保证了自己的数据独享一个线程来跑。
我是这么想的,不知对不对?
sp<AudioFlinger::ThreadBase> AudioFlinger::openOutput_l(audio_module_handle_t module,
audio_io_handle_t *output,
audio_config_t *config,
audio_devices_t devices,
const String8& address,
audio_output_flags_t flags)
{
AudioHwDevice *outHwDev = findSuitableHwDev_l(module, devices);
.......
AudioStreamOut *outputStream = NULL;
status_t status = outHwDev->openOutputStream(
&outputStream,
*output,
devices,
flags,
config,
address.string());
mHardwareStatus = AUDIO_HW_IDLE;
/**
* 每打开一个outputs,成功后都会为它创建一个线程,会根据其flag创建不同的线程,MmapPlaybackThread、
* OffloadThread、DirectOutputThread、MixerThread等线程,最后保存到mMmapThreads和mPlaybackThreads,
* key就是output句柄,值就是我们创建的线程
* */
if (status == NO_ERROR) {
if (flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) {
sp<MmapPlaybackThread> thread =
new MmapPlaybackThread(this, *output, outHwDev, outputStream,
devices, AUDIO_DEVICE_NONE, mSystemReady);
mMmapThreads.add(*output, thread);
.......
} else {
sp<PlaybackThread> thread;
if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
thread = new OffloadThread(this, outputStream, *output, devices, mSystemReady);
......
} else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
|| !isValidPcmSinkFormat(config->format)
|| !isValidPcmSinkChannelMask(config->channel_mask)) {
thread = new DirectOutputThread(this, outputStream, *output, devices, mSystemReady);
........
} else {
thread = new MixerThread(this, outputStream, *output, devices, mSystemReady);
........
}
mPlaybackThreads.add(*output, thread);
mPatchPanel.notifyStreamOpened(outHwDev, *output);
return thread;
}
}
return 0;
}
创建成功后,AudioFlinger根据去打开时参数flags创建不同的线程,最后也是用键值对类型(output, thread)来对应存储,后续拿到这个output句柄就可以把数据传入对应的线程了。主要有四类线程:
线程名称 | 线程意义 |
---|---|
MmapPlaybackThread | 不详,后续了解到了再说 |
OffloadThread | 继承DirectOutputThread,用于播放一些非PCM格式的Track,该线程上的数据需要传送到硬件解码器上解码后播放 |
DirectOutputThread | 直接输出,不能混音,只有一个Track的线程 |
MixerThread | 混音线程,可以有多个Track |
以上的线程和FLAG对应,所以我们在播放时需要把音频数据送到对应的线程,则传入其FLAG即可。 |
除以上关键点外,openOutput其他部分流程就是逻辑的依次调用、参数转换了,没有做其他额外重要的事情了;下面用一幅图总结,openOutput流程后各个对象之间的引用图,如下:
值得注意的有几点:
- 通道打开成功后,SwAudioOutputDescriptor会保存此次打开的使用参数,如SampleRate、channel、formate以及设备DeviceDescriptor等等;同时也会同步缓存策略那块,策略那块还不是很懂,需要后期再补充!
- 打开通道的参数FLAG很重要,不同的FLAG在AudioFlinger中会创建不同的线程,后期播放时也会根据FLAG来决定走哪个线程