PYNQ开发板上使用USB声卡+OSS兼容层播放音频
PYNQ开发板上使用USB声卡+OSS兼容层播放音频需要经过联网装库,编译驱动模块,运行三个步骤。
联网装库
首先需要将PYNQ开发板连上网,才能安装所需的库。方法是在电脑上设置共享网络,但要注意大部分操作系统在共享网络的时候无法自己指定本机IP,所以需要修改PYNQ开发板的IP来适应电脑自动设置的IP。设置共享网络的方法网上教程有很多,这里不再赘述。
设置完共享网络后,注意不要关闭开发板,因为电脑的IP已经改了,如果重启开发板网关不对SSH就连不上了。这时需要查看电脑的ip(注意是有线局域网下的ip),Windows下使用ipconfig
,Linux下使用ifconfig
。如我的Ubuntu在有线局域网下的ip是10.42.0.1,由于网关是255.255.255.0,所以PYNQ开发板的IP必须设置为10.42.0.*。在我的环境下,设置IP的方法是编辑/etc/network/interfaces.d/eth0
,然后将其中的address这一行的IP地址修改为10.42.0.2。
然后禁用再启用一下电脑的有线网功能并重启开发板,尝试能否连进去,连进去以后再尝试ping外网,都可以才算成功。接下来就可以装库了,执行:
sudo apt update
sudo apt install alsa-base alsa-utils alsa-oss bc
编译驱动模块
下载内核源代码,我PYNQ上的Linux内核版本为5.4.0,可以在xilinx的linux内核仓库下载,其他版本的内核源码也可以在该仓库的其他分支下载。
下载并传到开发板上后,执行:
unzip linux-xlnx-xlnx_rebase_v5.4_2020.1.zip
解压结束后进入该文件夹,然后需要将当前系统的内核配置文件复制过来,执行:
sudo cp /lib/modules/$(uname -r)/build/.config .
配置需要编译的驱动模块,执行:
make menuconfig
进入Device Drivers
-> Sound card support
-> Advanced Linux Sound Architecture
-> USB sound devices
,光标移至USB Audio/MIDI driver
,按M键选择编译此模块,保存然后一直按Esc键退出。执行以下命令:
make prepare
make -C . M=sound
编译驱动模块,生成扩展名为ko的模块文件,将它们复制到内核文件夹中:
sudo mkdir -p /lib/modules/$(uname -r)/kernel/sound/core
sudo mkdir -p /lib/modules/$(uname -r)/kernel/sound/usb
sudo cp sound/core/*.ko /lib/modules/$(uname -r)/kernel/sound/core
sudo cp sound/usb/*.ko /lib/modules/$(uname -r)/kernel/sound/usb
然后安装这些模块:
sudo depmod
之后插上USB声卡,驱动模块应该会自动加载,可以用命令lsmod
查看模块是否被成功加载,在我的系统上被加载的有以下4个模块:
Module Size Used by
snd_usb_audio 167936 0
snd_hwdep 16384 1 snd_usb_audio
snd_usbmidi_lib 24576 1 snd_usb_audio
snd_rawmidi 24576 1 snd_usbmidi_lib
如果没有自动加载,可以手动加载:
sudo modprobe snd-usb-audio
此时可以查看声卡是否已被识别:
sudo aplay -l
如果输出中出现card 1,则需要配置一下声卡编号,因为alsa音频库默认使用的声卡是0号声卡,而USB声卡被分配的编号是1,所以需要修改alsa的配置文件,路径为/usr/share/alsa/alsa.conf
,在里面找到defaults.pcm.card 0
这一行,把0改成1。
这时候应该可以播放音乐了:
sudo aplay test.wav
运行
目前我们使用的音频驱动库一般是alsa,oss驱动库已经被淘汰了。但是相比于alsa,oss可能编程更简单一点,因为可以直接以传统文件读写的方式播放音乐;而且有很多老程序使用的是oss。为此,alsa提供了对oss的兼容,主要有两种方式,一种是加载驱动模块snd-pcm-oss,这种方式可以从内核层面将对oss的操作转发成对alsa的操作,使用范围广;另一种是安装alsa-oss库,然后在运行使用oss的程序时使用LD_PRELOAD把对oss的文件操作替换成这个库里对alsa的操作,只能替换固定的几个文件操作(像openat就不能替换),但是原理简单,不用编译驱动模块。
目前我不知道为什么编译出来的snd-pcm-oss模块用不了,运行时总是说找不到函数符号,但是它的依赖模块能编译出来的我都已经编译并加载了,不知道是不是链接有什么问题。所以只能用alsa-oss库,这里给出示例程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/soundcard.h>
int main() {
// 打开设备文件
int fd = open("/dev/dsp", O_RDWR);
if (fd < 0) {
perror("open error\n"); return 1;
}
// test.wav为16位,双声道,采样率为20000的波形文件
int bit = 16, rate = 20000, channel = 2;
ioctl(fd, SOUND_PCM_WRITE_BITS, &bit);
ioctl(fd, SOUND_PCM_WRITE_RATE, &rate);
ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &channel);
/*打开音乐文件*/
int fp = open("test.wav", O_RDONLY);
if(fp < 0){
perror("open fp error!\n"); return 1;
}
/*求文件的大小*/
//这里直接给出了,要计算可以用fstat函数或fseek+ftell函数
int len = 800180;
char *buf = (char *)malloc(len);
memset(buf, 0, len);
// 读文件到buf中
int rd = read(fp, buf, len);
if(rd < 0){
perror("read wav error!\n"); return 1;
}
close(fp);
// 把buf写到设备文件中
int wr = write(fd, buf, len);
if(wr < 0){
perror("write dsp error!\n"); return 1;
}
free(buf); buf = NULL;
return 0;
}
可以看到这里打开声卡直接用文件/dev/dsp
,这个文件在使用alsa音频驱动库时是不存在的,alsa-oss需要你加载libaoss.so这个动态库并用库里的open函数替换掉这个open操作,而它自己的open函数里会进行检查,如果尝试打开oss的声卡文件,就会执行alsa的操作,否则按原来的文件打开方式,其他文件操作函数也是一样。所以假设上面的测试文件编译出来的程序为audio
,则运行时执行:
sudo LD_PRELOAD=libaoss.so ./audio