基本原理
有两种变速播放方法,一种是改变playback的频率,例如22050hz采样用44100hz播放。但是这种方法会导致声音快的同时,声调也相应变化,会比较难以接受。
第二种方法,也是本次讨论中用到的办法,通过某种方法(未深究其原理)重新计算声音采样,使其语速变快变慢而音调不变。
变速解决方案
大概搜索了一下变速的库和方法,基于一篇经验分享选择了sonic。算法只有一对c和h文件,使用极其简单,据分享说效果不错不会破音
git地址:https://github.com/waywardgeek/sonic.git
有ndk版的,主要区别是有jni,但是.c文件直接可以在安卓环境编译,用QT正合适
变速播放器
有变速解决方案后,接下来需要一个能支持变速的播放方案。原来是QMediaPlayer可以很省心的播放,但是由于没有暴露音频解码和像播放设备喂数据的过程,没办法拿到数据喂给sonic进行变速处理。
经过一番查找阅读确认,必须自己调用mp3lame解码,然后sonic处理,然后自行喂数据给QAudioOutput
由于做录音热插拔支持的时候,用mp3lame编码,喂空白数据等行为,对mp3lame有一定的了解,做解码也比较顺利。而QAudioOutput没有用过,问题主要出在format的填写,对SampleRate、ChannelCount、SamepleSize的理解不够。
SampleRate是采样频率,44100hz、32000hz、16000hz、8000hz等,代表每秒要采这么多份数据;
SampleSize是采样数据的大小。采样数据的格式不同,有8bit的16bit的,有int也有float的。但是最常见的还是16bit的,那么SampleSize就是2.因为sizeof(short)=16
ChannelCount是声道数量。如果双声道,那么一份数据里面有两个采样挨在一起,比如双通道44100hz的话,一秒钟有44100 x 2个 x 2字节每个 = 88200字节。
至于mp3
lame里面的bitrate,是一毫秒对应的数据量,以bit为单位。单通道32000hz的话,bitrate=8bit每字节 x 1个通道 x 2字节每份 x 32份每毫秒 = 512
lame里面的framesize,固定是1152或者576或者384,取决于mpeg版本和layer。流行的mp3是mpeg2的layer3,是1152。注意这里的1152单位是采样份数,代表mp3一帧里面包含的采样份数
lame里面的nsamp是总的采样份数,只在VBR模式的mp3文件解码时赋值,totalframes是总的帧数。
如果只是播放的话,以上不用操心太多,控制QAudioOutput缺数据前就驱动decoder解码获取待播放sample给sonic最后送往QAudioOutput,即可播放。
但是作为一个取代QMediaPlayer播放的播放器功能,一个不可避免的问题是要支持播放位置的随机拖拽跳转。
拖拽随机播放
这个问题的难度来了。难题主要是:
- lame作为编解码器,理论上不需要支持解码位置的随机变更,所以它没有直接的支持
- mp3编码有VBR模式,提供变化的编码频率以节约某些片段的采样输出大小,很常见必须支持
研究过程中的一些情况
- lame是开源库,有部分作者为随机播放增加了几个不痛不痒的函数做辅助(仍然没有直接支持)
- 了解到一个libzplay及其解码的libmad好像能支持seek,却也在源码中看到了lame,一番小研究后放弃了
- 认为seek需要准确定位到mp3的frame头,通过其他讨论seek的文章,以及一段比较权威的代码发现,其实seek到的位置很草率
- 将seek方法尝试性用到了非vbr编码的mp3文件,居然可以用
- 强行让自己分析了lame的代码,原来它会根据mp3 frame头的特征,游走寻找它认为的frame头。(跳转前需要清除大部分状态和中间数据,这种情况下会触发它游走找头)
- vbr编码seek需要暴露toc信息,修改了lame库的代码。(check_vbr_header函数和mpstr_tag)
- 用vbr编码的mp3继续试验却大概率崩溃或异常音响,异常点有很多,和特定mp3的多个特定时间位置有关
- 经过分析后得知是seek后,游走找mp3 frame头的过程,误将其他数据当做了mp3头导致。
- 由于mp3 frame头的特征不太显著,代码用排除法判断是否帧头。但是由于跳转前清掉了layer=3的信息,导致未验证layer。1. 由于我们只考虑mp3,所以在head_check中固定要求验证layer=3
- 以上基本是主要的难点解决,代码中参考了lame自带的get_audio代码,主要抄袭文件头文件尾的相关处理。理论上我们已 经可以为lame贡献代码增加seek功能啦。
没代码说个屁啊
上传了代码 https://download.youkuaiyun.com/download/jinzeyu_cn/10312011 使用qt5.9.1可以直接打开编译运行
在windows、Android、Mac、IOS均测试通过。下载是为了在手机上测试方便,填入本地路径可以免去下载过程只测试播放。