在验证spdif S24_3LE/S24_LE格式的数据播放时,因为我的linux rootfs中没有alsa-lib与aplay,所以我用了android中的tinyalsa。
因为android tinyalsa默认是不支持24bit数据的,所以需要先修改tinyalsa
Carl的Linux rootfs 下, 底层注册了两个声卡,但是在/dev/snd/下面只有第一个声卡的相关节点。从底层的打印消息来看,底层完成了第二个声卡创建节点的工作。当我尝试用下面方式手动创建节点时,创建的节点是和底层一致,可以用的:
mknod /dev/snd/pcmC1D0p c 116 48 // c的意思是字符设备, 116主设备号,48次设备号
mknod /dev/snd/controlC1 c 116 32
另外,还可以在Linux rootfs的etc/目录下增加mdev.conf,里面的内容如下:
# alsa sound devices and audio stuff
pcm.* 0:0 660 =snd/
control.* 0:0 660 =snd/
midi.* 0:0 660 =snd/
seq 0:0 660 =snd/
timer 0:0 660 =snd/
按照mdev.conf的例子,上面红色的0:0应该是用root:audio,但是这样用时却没有效果。不知道具体原因。
用Carl的Linux rootfs中的aplay 播放S24_LE 与S24_3LE文件的音乐时,底层收到的参数都是S24_LE的。这是因为alsa-lib将S24_3LE转换成了S24_LE。
如果想直接播放S24_3LE,而不是将它转换成S24_LE,该如何办?
首先,对于android来讲,默认的tinyalsa只能认出是16bit还是24bit数据,却无法区分24bit中的S24_LE(24bit数据放到4bytes/32bits空间里)或S24_3LE(24bit数据紧接着下一个24bit数据,是放在3bytes空间里面)。
我们先看下wav文件的包头(windows下用HxD/ultraedit打开wav文件, linux下用hexdump):
上面阴影部分的52 49 46 46 是RIFF四个字母的ASCII值。
上面阴影部分的四个值18 55 86 00代表波形文件大小,计算大小的时候是按照00 86 55 18
也就是0x865518 = 8803608 bytes,但是这个值要加上8 bytes(52 49 46 46 18 55 86 00)。
上面阴影部分是WAVEfmt的ASCII, 最后一位0x20=32是空格的ASCII码。
上面阴影部分的02 00应该读成00 02,也就是2,代表两个声道。
上面阴影部分的80 BB 00 00代表采样率,读成0xBB80 = 48000。
上面阴影为每秒钟播放的字节数:48000 * 24 *2 = 采样率*采样位数*声道数= 2304000 bits
2304000/8 = 288000 bytes = 0x 04 65 00。
上面阴影的意思是24 bits 是放到6/2=3 bytes里面存储的,也就是该数据格式是S24_3LE。
对于S24_LE来讲,这个值应该是8,也就是24bits数据是放到4 bytes = 32bits中的。
这两个bytes叫做BlockAlign,我们可以根据它来区分S24_LE/S24_3LE。
上面阴影部分18 00该读成00 18, 0x18=24,是24 bit 的意思。
在android的tinyplay.c中,struct chunk_fmt中有一个block_align可以用来区分S24_LE/S24_3LE:
tinyalsa的这些参数,是怎么传到底层的:
对于S16_LE来讲:
bit = 2,
(bit >> 5) = 0
(1 << (bit & 31)) = (1 << 2) = 4
m->bits[bit >> 5] = m->bits[0]
m->bits[0] = m->bits[0] | 4 = 4
即
m->bits[0] = 4 = 0x0004
m->bits[1] = 0
对于S24_LE来讲:
bit = 6,
(bit >> 5) = 0
(1 << (bit & 31)) = (1 << 6) = 64
m->bits[bit >> 5] = m->bits[0]
m->bits[0] = m->bits[0] | 64 = 64
即
m->bits[0] = 64 = 0x0040
m->bits[1] = 0
对于S24_3LE来讲:
bit = 32,
(bit >> 5) = 1
(1 << (bit & 31)) = (1 << 0) = 1
m->bits[bit >> 5] = m->bits[1]
m->bits[1] = m->bits[1] | 1 = 1
即
m->bits[0] = 0
m->bits[1] = 1 = 0x0001
这些参数,都通过ioctl传递到了底层:
在底层:
在上面会对m->bits[3/2/1/0] 与snd_mask_refine之后的m->bits[3/2/1/0]进行比较。
刚开始,由于底层dummy_codec那边不支持S24_3LE数据格式,在播放S24_3LE格式音乐时,这边的比较通不过。
在底层也要加入S24_3LE格式的支持,尤其是在soc-utils.c中的dummy_codec_dai中,因为我们spdif用的是dummy_dodec,而dummy_codec的默认代码中是不支持S24_3LE格式的。
通过上面的方法,我可以解决S24_3LE无法播放。
但是测试后发现,第一次播放S24_3LE是正常的,停止后继续重新播放,就会有时候播出来的是噪音。用示波器量输出波形,发现是有声音数据的,我自己在声音数据里加入了很多规律性的数据,比如说0xAA(1010 1010),通过示波器也是可以量出来的,所以怀疑是不是数据发生了偏移。
和IC那边讨论后,发现,S24_3LE是比较特殊的,DMA那边搬3笔数据,SPDIF把它解释成四笔:
如果播放结束后,SPDIF FIFO里有残留数据,会破坏下一次数据的时序,所以需要在播放结束的时候,将FIFO,DMA做reset。而我这边是忘了在spdif trigger stop里面将DMA Request Router关掉。
在调试的过程当中,为了更好的观察输出信号的质量,可以播放一些特殊文件,如方波/正弦波。可以通过goldwave来做出这些特殊文件(工具/表达式计算器):
调试S24_3LE_mono时,遇到了很有规律的噪音(正常的音乐之间夹杂着噪音),读数据时,发现有周期性的数据偏移。通过tinyplay -p调整period_size的时候,发现当设置period_size <= 1024的时候,底层拿到的runtime->period_size = 1366,当设置 period_size = 2048\4096的时候,底层拿到的runtime->period_size = 2048\4096。原因可能是底层设置period_bytes_min = 4*1024, period_bytes_max = 16*1024,当你手动设置一个1024的period_size时,因为它不在底层规定的范围内,所以alsa计算出了一个奇怪的period_size值(1366)。当设置period_bytes_min = 1024时,上述问题可以被解决: