QEMU 中音频模拟如何工作
2017-12-27 09:45
921 查看
事情有点棘手,但这里有一个粗略的描述:
QEMUSoundCard:建模一个给定的模拟的声卡
SWVoiceOut:建模一个来自 QEMUSoundCard 的音频输出
SWVoiceIn:建模一个来自 QEMUSoundCard 的音频输入
HWVoiceOut:建模一个主机端的音频输出(后端)
HWVoiceIn:建模一个主机端的音频输入(后端)
每个声音在采样大小,字节序,速率等方面都可以有自己的设置。
对于一个给定声卡的模拟典型的做法如下:
1/ 创建一个 QEMUSoundCard 对象,然后用
2/ 对于每个模拟的输出,调用
3/ 对于每个模拟的输入,调用
注意你必须给
每个 SWVoiceOut 与一个 HWVoiceOut 关联,每个
然而你可以让多个
一个固定大小的立体声采样的循环缓冲区(用于立体声)。其采样的格式为 floats 或 int64_t(依赖于构建配置)。
一个
一个目标转换函数,称为
一个
一个
一个
根据 malc,音频子系统的最初的作者,如果可以的话,这用于允许一个后端使用平台特有的函数来做相同的事情。
(简单地说,所有的输入后端具有一个
每个
一个
一个
一个
因此,如果 HWVoiceOut 的频率为 44kHz,而 SWVoiceOut 的频率为 11kHz,则 ratio 将是 (44/11*(1 << 32)) = 0x4_0000_0000。
一个当 SWVoiceOut 创建时模拟的硬件提供的回调。这个函数用于混合 SWVoiceOut 的采样到目的 HWVoiceOut 立体声缓冲区(它也必须执行频率插值,音量调整,等等。。。)。
这个回调通常调用音频子系统中的另一个辅助函数 (
这是一个小的图形,更好地解释了它:
audio/audio.c 中的
对于每个 HWVoiceOut,找到活跃的 SWVoiceOut 的个数,以及已经被写入缓冲区的
如果
否则,调用
重要的是要注意SWVoiceOut回调:
接收一个
必须调用
因此,最后我们有伪代码:
原文 $QEMU/android/docs/AUDIO.TXT
QEMUSoundCard:建模一个给定的模拟的声卡
SWVoiceOut:建模一个来自 QEMUSoundCard 的音频输出
SWVoiceIn:建模一个来自 QEMUSoundCard 的音频输入
HWVoiceOut:建模一个主机端的音频输出(后端)
HWVoiceIn:建模一个主机端的音频输入(后端)
每个声音在采样大小,字节序,速率等方面都可以有自己的设置。
对于一个给定声卡的模拟典型的做法如下:
1/ 创建一个 QEMUSoundCard 对象,然后用
AUD_register_card()注册它
2/ 对于每个模拟的输出,调用
AUD_open_out()创建一个
SWVoiceOut对象
3/ 对于每个模拟的输入,调用
AUD_open_in()创建一个
SWVoiceIn对象
注意你必须给
AUD_open_out()和
AUD_open_in()传递一个回调函数;后面有更多相关内容。
每个 SWVoiceOut 与一个 HWVoiceOut 关联,每个
SWVoiceIn与一个
HWVoiceIn关联。
然而你可以让多个
SWVoiceOut与相同的
HWVoiceOut关联(相同的事情也发生在 SWVoiceIn/HWVoiceIn 中)。
声音播放细节
每个 HWVoiceOut 也有以下这些:一个固定大小的立体声采样的循环缓冲区(用于立体声)。其采样的格式为 floats 或 int64_t(依赖于构建配置)。
一个
samples字段给出了(固定的)立体声缓冲区中采样对的个数。
一个目标转换函数,称为
clip(),用于从立体声缓冲区中读取并写入一个平台特有的声音缓冲区(比如,Windows 上 WinWave 管理的缓冲区)。
一个
rpos,它循环缓冲区的偏移,指明了从立体声缓冲区中的什么位置读取下一个采样数据,以通过
clip用于下一次转换。
|<----------------- samples ----------------------->| | | | rpos | | |_______v___________________________________________| | | | | | | |_______|___________________________________________|
一个
run_out方法,每次调用以告知输出后端从立体声缓冲区发送采样数据到主机的声卡/声音服务。这个方法也应该修改
rpos并返回
played采样的数据的个数。下面有关于这个过程的一个更详细的描述。
一个
write方法回调用于从一个
SWVoiceOut写入一个模拟的声音采样数据的缓冲区到立体声缓冲区。当前所有后端简单地调用通用函数
audio_pcm_sw_write()来实现它。
根据 malc,音频子系统的最初的作者,如果可以的话,这用于允许一个后端使用平台特有的函数来做相同的事情。
(简单地说,所有的输入后端具有一个
read方法,它简单地调用
audio_pcm_sw_read。)
每个
SWVoiceOut有以下这些:
一个
conv()函数用于从模拟的声卡读取声音采样数据,并复制/混合它们到对应的 HWVoiceOut 的立体声缓冲区。
一个
total_hw_samples_mixed对应于已经被混合到目标
HWVoiceOut立体声缓冲区的采样的个数(从 HWVoiceOut 的
rpos偏移开始)。注意:这是 HWVoiceOut 立体声缓冲区中的采样的计数,而不是模拟的硬件声音采样,它们可能具有不同的属性(频率,大小,尾端)。
______________ | | | SWVoiceOut2 | |______________| ______________ | | | | | SWVoiceOut1 | | thsm<N> := total_hw_samples_mixed |______________| | for SWVoiceOut<N> | | | | |<-----|------------thsm2-->| | | | |<---thsm1-------->| | _______|__________________v________|_______________ | |111111111111111111| v | | |222222222222222222222222222| | |_______|___________________________________________| ^ | HWVoiceOut stereo buffer rpos
一个
ratio值,它是目标 HWVoiceOut 的频率与 SWVoiceOut 的频率的比值,乘以 (1 << 32) 的一个 64 位的整数。
因此,如果 HWVoiceOut 的频率为 44kHz,而 SWVoiceOut 的频率为 11kHz,则 ratio 将是 (44/11*(1 << 32)) = 0x4_0000_0000。
一个当 SWVoiceOut 创建时模拟的硬件提供的回调。这个函数用于混合 SWVoiceOut 的采样到目的 HWVoiceOut 立体声缓冲区(它也必须执行频率插值,音量调整,等等。。。)。
这个回调通常调用音频子系统中的另一个辅助函数 (
AUD_write()) 来从模拟的硬件采样缓冲区混合/调整音量。
这是一个小的图形,更好地解释了它:
SWVoiceOut: 模拟的硬件声音缓冲区: | | (通过 AUD_write() 混合,该函数在用户提供的回到中调用, | 回调本身在每次音频始终滴答时调用 ). | v HWVoiceOut: 立体声采样循环缓冲区 | | (通过 HWVoiceOut 的 `clip` 函数发送,它在 `run_out` 方法中调用, | 也在每次音频时钟滴答时调用) | v 后端特有的声音缓冲区
audio/audio.c 中的
audio_timer()函数被周期性地调用,且被用作一个执行声音缓冲区的传输和混合的脉冲。对于音频输出语音更具体的如下:
对于每个 HWVoiceOut,找到活跃的 SWVoiceOut 的个数,以及已经被写入缓冲区的
total_hw_samples_mixed的最小个数。我们将这个值称为立体声缓冲区中 “live” 采样的数量。
如果
live为 0,如果需要,调用每个活跃的 SWVoiceOut 的回调来填充立体声缓冲区,然后退出。
否则,调用
HWVoiceOut对象的
run_out方法。这将改变
rpos的值,并返回播放的采样的个数。然后所有活跃的
SWVoiceOuts的
total_hw_samples_mixed减去
played,回调被调用以重新填充立体声缓冲区。
重要的是要注意SWVoiceOut回调:
接收一个
free参数,其是可以被发送给硬件立体声缓冲区的立体声声音采样的个数(在比率调整前,比如,不是 SWVoiceOut 模拟的硬件声音缓冲区中声音采样的个数)。
必须调用
AUD_write(sw, buff, count),其中
buff指向模拟的声音采样,且它们的
count,必须 <=
free参数。
AUD_write()的实现将调用目标
HWVoiceOut的
write方法,这反过来调用函数
audio_pcm_sw_write(),其在混合转换到目标立体声缓冲区之前执行标准的 比率/音量 调整。它也增加
SWVoiceOut的
total_hw_samples_mixed值。
audio_pcm_sw_write()返回已经被混合入立体声缓冲区的声音采样的 字节 数,
AUD_write()也是如此。
因此,最后我们有伪代码:
every sound timer ticks: for hw in list_HWVoiceOut: live = MIN([sw.total_hw_samples_mixed for sw in hw.list_SWVoiceOut ]) if live > 0: played = hw.run_out(live) for sw in hw.list_SWVoiceOut: sw.total_hw_samples_mixed -= played for sw in hw.list_SWVoiceOut: free = hw.samples - sw.total_hw_samples_mixed if free > 0: sw.callback(sw, free)
录音详情
情况类似但顺序相反,比如,HWVoiceIn 在它的立体声缓冲区中获取声音采样数据,且 SWVoiceIn 对象必须尽快消费它们。原文 $QEMU/android/docs/AUDIO.TXT
打赏
Done.相关文章推荐
- 一步步教你如何在ubuntu虚拟机中安装QEMU并模拟arm 开发环境(二)rootfs制作
- 一步步教你如何在Ubuntu虚拟机中安装QEMU并模拟模拟arm 开发环境(一)uImage u-boot
- How it works: Linux audio explained--如何工作:Linux音频工作介绍
- Android工作总结之如何做一个优秀的MediaPlayer音频播放器
- 如何进入Google工作? Google的面试题,电话面试和招聘流程介绍
- 如果你知道如何使用这个技能,就根本不需要担心工作了……
- 在腾讯这样的大公司工作,如何才有出头之日?
- IT农民工如何来美国工作
- 如何在Ubuntu中利用Qemu运行BeagleBoard firmware
- 工作之余如何有效学习提升
- 程序员求职之道(《程序员面试笔试宝典》)之如何看待一份工作的稳定性?
- 深度介绍Linux内核是如何工作的
- 如何在创业公司轻松地工作
- Linux内和分析(二)操作系统是如何工作的
- jmeter如何模拟http发送gzip数据
- SSL/TLS的原理以及互联网究竟是如何工作的(2)—“更合适的架构,大家一起努力!”
- 工作总结--如何定位web系统前后台的bug,以及bug分析/测试感想
- oracle是如何工作的
- 工作中如何有效沟通
- Google账户两步验证是如何工作的?