记录Android Audio学习中的疑问
简介
本篇博文,主要是记录博主在学习Audio时遇到的问题,不仅仅限于Android,许多问题有自己的理解,有些则挂着等待日后的理解!
Android领域
AudioPolicyManager中的mAvailableOutputDevices成员变量是什么?
答案:它的类型是DeviceVector,也就是一个设备集合,这个集合里面装的是DeviceDescriptor对象,这个对象是通过接卸audio_policy_configuration.xml里面的devicePort标签解析而来c++对象的,如下:
<devicePort tagName="Speaker" type="AUDIO_DEVICE_IN_AUX_DIGITAL" role="source"
address="Aux0_in">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_IN_MONO"/>
</devicePort>
但是,不是每个devicePort转化的DeviceDescriptor都能加入到mAvailableOutputDevices成员上去,必须已经绑定的设备才行,也就是说Android硬件已经存在的audio设备才行,如下:
<attachedDevices>
<item>Speaker</item>
</attachedDevices>
attachedDevices标签就是已经存在的设备,这个item标签和devicePort标签同时都有才能加入到mAvailableOutputDevices成员
IOProfile、AudioPort、AudioProfile中关于Dynamic动态的理解?
首先,虽然他们三个都有关于Dynamic关键字的方法,但是最终Dynamic相关函数都指向AudioProfile的Dynamic方法;
<mixPort name="usb_accessory output" role="source">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="44100" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
<profile name="" format="AUDIO_FORMAT_PCM_8_BIT"
samplingRates="44100" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
如上述摘取usb_audio_policy_configuration.xml文件的部分代码,IOProfile就是一个mixPort,AudioPort就是内部所有的profile标签,就是一个集合,AudioProfile就对应一个profile标签;这样理解Dynamic最终都执行AudioProfile都好理解;看看最终AudioProfile的isDynamic方法:
bool isDynamic() {
return mIsDynamicFormat || mIsDynamicChannels || mIsDynamicRate;
}
只要format、channel和SampleRate任意一个的状态是dynamic就表明这个AudioProfile是Dynamic;而上面三个状态都是set设置进去的,那哪些地方又set这些变量呢?如下:
a. 在解析configuration文件时又设置
Return<AudioProfileTraits::Element> AudioProfileTraits::deserialize(const xmlNode *cur,
PtrSerializingCtx /*serializingContext*/)
{
std::string samplingRates = getXmlAttribute(cur, Attributes::samplingRates);
std::string format = getXmlAttribute(cur, Attributes::format);
std::string channels = getXmlAttribute(cur, Attributes::channelMasks);
Element profile = new AudioProfile(formatFromString(format, gDynamicFormat),
channelMasksFromString(channels, ","),
samplingRatesFromString(samplingRates, ","));
//gDynamicFormat=AUDIO_FORMAT_DEFAULT=0就是一个无效值
profile->setDynamicFormat(profile->getFormat() == gDynamicFormat);
profile->setDynamicChannels(profile->getChannels().isEmpty());
profile->setDynamicRate(profile->getSampleRates().isEmpty());
return profile;
}
当解析xml文件得到的sampleRate、channel为空,format等于0,他们的dynamic状态变量就是true;
b. 从hal层得到的参数
ssize_t AudioProfileVector::addProfileFromHal(const sp<AudioProfile> &profileToAdd)
{
// Check valid profile to add:这个fomat是否有效,不能等于默认值
if (!profileToAdd->hasValidFormat()) {
return -1;
}
if (!profileToAdd->hasValidChannels() && !profileToAdd->hasValidRates()) {
FormatVector formats;
formats.add(profileToAdd->getFormat());
setFormats(FormatVector(formats));
return 0;
}
if (!profileToAdd->hasValidChannels() && profileToAdd->hasValidRates()) {
setSampleRatesFor(profileToAdd->getSampleRates(), profileToAdd->getFormat());
return 0;
}
if (profileToAdd->hasValidChannels() && !profileToAdd->hasValidRates()) {
setChannelsFor(profileToAdd->getChannels(), profileToAdd->getFormat());
return 0;
}
// Go through the list of profile to avoid duplicates 前明三个if说明format channel和sample都有效
for (size_t profileIndex = 0; profileIndex < size(); profileIndex++) {
const sp<AudioProfile> &profile = itemAt(profileIndex);
if (profile->isValid() && profile == profileToAdd) {
// Nothing to do
return profileIndex;
}
}
//dynamic动态是否可以理解为,这个profileToAdd可以删除
profileToAdd->setDynamicFormat(true); // set the format as dynamic to allow removal
return add(profileToAdd);
}
代码执行到这里 profileToAdd->setDynamicFormat(true),标签这个profileToAdd的三个参数:format、channel和sampleRate都是有效的,叶会把他的formatDynamic设置为true;
总结
- 从xml解析出来的sampleRate、format和channel参数为空时dynamic为true
- 从hal层得到的完整有值的上面三个参数,也会把format设置为dynamic
- 只要一个参数的dynamic状态为true,那AudioProfile的Dynamic就为true
最后,这个Dynamic属性表明这个AudioProfile是可以动态从AudioPort中remove掉的
open打开的ouput是什么?
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)
参数解释:
devices是集合,不过一般里面只有一个device,往下层调用时都是取的device的type类型,如AUDIO_DEVICE_OUT_EARPIECE、AUDIO_DEVICE_OUT_SPEAKER等等
flags:是各种状态量,一般是mixport标签里面的flag,如AUDIO_OUTPUT_FLAG_DIRECT等
函数解释:
- open函数会从AudioPolicyManager、AudioPolicyService、AudioFlinger依次调用,通过HIDL方式调用到hal层,hal层则会根据device所属的module,根据module的name去获取module对应的so;
- open在hal层实质就是调用的open_output_stream,相当于每个open就是打开一条stream;如下:
static int adev_open_output_stream(struct audio_hw_device *dev,
audio_io_handle_t handle,
audio_devices_t devices,
audio_output_flags_t flags,
struct audio_config *config,
struct audio_stream_out **stream_out,
const char *address)
{
struct stream_out *out;
out = (struct stream_out *)calloc(1, sizeof(struct stream_out));
out->flags = flags;
//把上层传递过来的device保存在out流结构体中
out->devices = devices;
out->handle = handle;
out->dev = adev;
....
//省略中的代码也没有对device做处理
//但flags参数很重要,省略中很多代码都需要flag做判断进而不同的处理
.....
}
综上,open调用了hal的open_output_stream函数,devices保存到了stream_out结构体中,除此之外并没有做其它的逻辑,所以这里可以回答一个问题;
AudioPolicyManager中在initialize初始化中会把所有的device设备打开吗?
因为initialize初始化函数,会遍历所有的module、IOProfile,并且与mAvailableOutputDevices设备取交集,也就是说只要配置文件中的attachedDevices标签的设备都会被打开,也就是硬件上已经绑定到系统上的音频设备都会去调用open函数打开;
但是这个打开是有局性的,open只会调用到hal的open_output_stream为止,并没有继续往下kernel打开真正的设备业务逻辑,也是合理的,不可能系统上电后就打开所有的音频设备,都还没真正用设备,你现在打开岂不是耗费电量等资源;只有在往里面写音频数据时才会打开与kenel的连接,如下流程:
out_write() --> start_output_stream()
在start_output_stream中就会选择内核设备声卡号和设备号,如:pcmC0D1P 0号声卡的1号播放设备,那这个声卡设备选择的标准是什么呢?
选择声卡设备的标准
这里就要用到open_output_stream的device了,或者address等,不同产商可能不一样;因为不同的音频设备挂载的声卡不一样,假如speaker挂载在0号声卡的1号设备,那就会选择0号声卡1号设备
总结
open函数打开,是以stream为概念的流,程序结构中用stream标识,在open_output_stream并没有打开实际的设备,只是保存了相应的传入参数;在真正写入音频数据时才会调用alsa打开内核音频设备,选择声卡设备,写入数据
再次总结openOutput链路
- 第一处红框,遍历profile实质就是遍历audio_conf里面的mixport标签,也就是数据流
- 第二处红框,经历4个步骤下来后,可能两个不同的profile得到的device是同一个,如下:
<defaultOutputDevice>Speaker</defaultOutputDevice>
<mixPort name="primary output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
<mixPort name="deep_buffer" role="source"
flags="AUDIO_OUTPUT_FLAG_DEEP_BUFFER">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
<route type="mix" sink="Speaker" sources="primary output,deep_buffer,compressed_offload,BT SCO Headset Mic,Telephony Rx"/>
如上配置,流为primary out和deep_buffer的支持设备都包含Speaker,并且Speaker又是默认的输出设备,所以经历了第二个红框后输出的device是一样的,都会Speaker
- 第三处红框,按照第二个红框的案例,不同的profile,虽然选择了相同的device–Speaker,但是都会向Hal打开属于自己的专属stream,并且创建两个PlaybackThread并保存
AudioTrack创建时底层构建的对象链路
如下图:
在PlaybackThread中,每一个Track对应应用端一个AudioTrack,所以PlaybackThread作为服务端,用集合来保存了众多Track,创建时用mTrack集合;
同理一个TrackClientDescriptor也对应一个应用端AudioTrack,但是它保存在AudioOutputDescriptor的mClients中;
以上两个集合中如何对应,就是靠唯一标识portId;
当业务开始时,最上层AudioTrack调用play开始播放音频时,会层层调用start()方法,在PlaybackThread::track的start方法时,会调用其addTrack_l()函数,将track加入到PlaybackThread的另一个集合mActiveTrack中,同时在TrackClientDescriptor中激活client的状态setClientActive;
alsa领域
tinyplay、tinypcminfo等工具编译及使用
别人写的很好了,直接跳转如何查看声卡、pcm设备以及tinyplay、tinymix、tinycap的使用查看吧!