自己做了个V3S的开发板,现在慢慢研究一下软件,这两天调试好了音频功能,简单做了一些记录分享给需要的朋友。
下载alsa-lib和alsa-utils
https://www.alsa-project.org/main/index.php/Download
这里可以下载最新版本的alsa-lib和alsa-utils,我下载的都是1.2.8版的。
编译alsa-lib
tar xvf alsa-lib-1.2.8.tar.bz2
cd alsa-lib-1.2.8
配置生成Makefile
./configure --host=arm-linux-gnueabihf --prefix=/home/jinggx/V3S/audio/alsa-lib --enable-shared –with-configdir=/usr/share/alsa-arm
1. --host 指定编译器,这里指定为交叉编译器。
2. --prefix 指定编译后文件的安装路径,即make install安装的位置。安装后创建 的lib 和 include都是给ARM开发板用的,宿主机用不到,所以此目录可以指定在任意位置。
3. --with-configdir 指定 conf 文件的安装目录,该目录将会直接移植到你的ARM开发板系统中,并且复制到开发板系统中的目录位置必须与编译时指定的-with-configdir完全一致,这一点非常重要,绝对路径必须完全一致!
./configure 成功之后才可以make。如果要重新./configure,建议先删掉Makefile文件。我这个菜鸟重新./configure 的时候没有注意到命令窗口的错误提示,然后继续make,结果一直make最初生成的Makefile文件,然后我就很蒙圈为什么修改了./configure 一直不起作用?我太傻了!!
make
make install
make instal可能会不成功,我在网上搜索到了别人分享的经验:需要切换到root用户下执行才可以。
sudo su
make
make install
编译alsa-utils
tar xvf alsa-utils-1.2.8tar.bz2
cd alsa-utils-1.2.8
配置生成Makefile
./configure --host=arm-linux-gnueabihf --prefix=/home/jinggx/V3S/audio/alsa-utils --with-alsa-inc-prefix=/home/jinggx/V3S/audio/alsa-lib/include CFLAGS="-I/home/jinggx/V3S/audio/alsa-lib/include" LDFLAGS="-L/home/jinggx/V3S/audio/alsa-lib/lib -lasound" --disable-alsamixer --disable-xmlto
1. --host 指定编译器,与 alsa-lib 的配置选项相同
2. --prefix 指定编译后文件的安装路径,与 alsa-lib 的配置选项相同
3. --with-alsa-inc-prefix 指定头文件目录,因为 ./configure 程序会去该目录检查版本情况。如果不指定的话,则会直接去默认目录 ( 即宿主机对应的 alsalib 目录中寻找,因此可能会有错误
4. CFLAGS 用于编译时指定的编译选项,在这里要使用 alsa-lib 编译后生成的头文件,指定该头文件所在目录,-I与路径之间不能有空格
5. LDFLAGS 用于编译时指定连接库文件,在这里要使用 alsa-lib 编译生成的库文件, 指定该头文件所在目录,-L与路径之间不能有空格
与前面编译alsa-lib类似,应该还是必须在root用户下执行才可以
sudo su
make
make install
如果编译遇到如下问题
make[2]: Entering directory '/home/jinggx/V3S/audio/alsa-utils-1.2.8/alsaconf/po'
mv: cannot stat 't-ja.gmo': No such file or directory
make[2]: *** [Makefile:41: ja.gmo] Error 1
make[2]: Leaving directory '/home/jinggx/V3S/audio/alsa-utils-1.2.8/alsaconf/po'
make[1]: *** [Makefile:480: all-recursive] Error 1
make[1]: Leaving directory '/home/jinggx/V3S/audio/alsa-utils-1.2.8/alsaconf'
make: *** [Makefile:461: all-recursive] Error 1
需要安装gettext
sudo apt install gettext
再次执行./configure ,然后再次编译即可。
lib 和 utils 安装到开发板
1. 把编译alsa-lib安装在$(--prefix)/lib目录中的libasound.la libasound.so libasound.so.2 libasound.so.2.0.0 拷贝到开发板上的/lib目录
2. 把编译alsa-lib时$(–with-configdir)整个目录拷贝到开发板上同样的位置,如果开发板上不存一样的目录则新建,必须保证编译时的$(–with-configdir)与开发板上的路径完全一致
3. 把编译alsa-utils安装在$(--prefix)/bin和$(--prefix)/sbin目录中生成的可以执行文件复制到开发板,我这里用到的文件只有aplay, amixer这2个,amixer用于设置声卡,aplay用于播放测试。aplay复制到开发板的/bin目录;amixer复制到开发板的/sbin目录,测试中发现amixer放在开发板的/bin目录不能运行,不知何故
测试
在开发板终端上ls -l /dev/snd查看,必须保证 /dev/snd/ 目录中应包含以下几个设备文件:
controlC0, pcmC0D0c, pcmC0D0p, timer
开机后默认状态是静音状态,需要取消掉静音状态
amixer -c 0 sset 'Headphone',0 100% unmute
切换到存放音频文件目录下
aplay xxx.wav
耳机连接到开发板的音频输出口,应该可以听到音乐声了。
使用alsa-lib编写应用程序
我自己写的简单wav文件播放代码
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#define FRAMES_PLAYBACK 1024 // define how many frames write to snd device at a time,
// 1 FRAME = 1 block_align
typedef struct _wave_header {
uint8_t chunk_id[4]; // signature "RIFF"
uint32_t chunk_size; // size of next address to file end,
uint8_t format[4]; // signature "WAVE"
uint8_t sub_chunk1_id[4]; // signature "fmt "
uint32_t sub_chunk1_size; // size of sub chunk1 (it seems always 0x00000010)
uint16_t audio_format; // 0x0001 is PCM code
uint16_t num_channels; // audio channels
uint32_t sample_rate; // sample frequence
uint32_t byte_rate; // byte rate pes second
uint16_t block_align; // sample align bytes, 1 block_align = 1 FRAME
uint16_t bits_per_sample; // bits per sample
uint8_t sub_chunk2_id[4]; // signature "data"
uint32_t sub_chunk2_size; // wave samples size
} wave_header_t;
static void wav_info_dump(wave_header_t* wav_header)
{
printf("wave header:\n");
printf("chunk id = %c%c%c%c\n",
wav_header->chunk_id[0],wav_header->chunk_id[1],
wav_header->chunk_id[2],wav_header->chunk_id[3]);
printf("format = %c%c%c%c\n",
wav_header->format[0],wav_header->format[1],
wav_header->format[2],wav_header->format[3]);
printf("channels = %d\n",wav_header->num_channels);
printf("sample rate = %d\n",wav_header->sample_rate);
printf("byte rate = %d\n",wav_header->byte_rate * 8);
printf("block align = %d\n",wav_header->block_align);
printf("bits = %d\n",wav_header->bits_per_sample);
printf("samples size= %d\n",wav_header->sub_chunk2_size);
//printf("Calculated information:\n");
printf("file size = %d\n",wav_header->chunk_size + 8);
printf("data pos = %d\n",(uint32_t)sizeof(wave_header_t));
printf("duration = %d s\n",wav_header->sub_chunk2_size / wav_header->block_align / wav_header->sample_rate);
}
int main(int argc, char *argv[])
{
int i, ret;
char *pbuf;
FILE *fp;
snd_pcm_t *h_playback;
snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t frames, buff_siz;
wave_header_t wav_header;
if (argc != 2) {
printf("cmd error: use ./wav xx.wav\n");
exit(-1);
}
printf("play %s\n", argv[1]);
fp = fopen(argv[1], "rb");
if (fp == NULL) {
printf("fopen %s failed\n", argv[1]);
exit(-1);
}
fread((uint8_t *)&wav_header, 1, sizeof(wave_header_t), fp);
wav_info_dump((wave_header_t*)&wav_header);
// 1. 打开PCM,最后一个参数为0意味着标准配置
if (snd_pcm_open(&h_playback, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
perror("snd_pcm_open");
fclose(fp);
exit(-1);
}
// 2. 分配snd_pcm_hw_params_t结构体
if (snd_pcm_hw_params_malloc(&hw_params) < 0) {
perror("snd_pcm_hw_params_malloc");
goto end;
}
// 3. 初始化hw_params
if (snd_pcm_hw_params_any(h_playback, hw_params) < 0) {
perror("snd_pcm_hw_params_any");
goto end;
}
// 4. 初始化访问权限
if (snd_pcm_hw_params_set_access(h_playback, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
perror("snd_pcm_hw_params_set_access");
goto end;
}
// 5. 初始化采样格式SND_PCM_FORMAT_S16,16位
if (wav_header.bits_per_sample == 24) {
if (snd_pcm_hw_params_set_format(h_playback, hw_params, SND_PCM_FORMAT_S24) < 0) {
perror("snd_pcm_hw_params_set_format");
goto end;
}
} else if (wav_header.bits_per_sample == 16) {
if (snd_pcm_hw_params_set_format(h_playback, hw_params, SND_PCM_FORMAT_S16) < 0) {
perror("snd_pcm_hw_params_set_format");
goto end;
}
} else {
printf("unsupport wav file, bits_per_sample=%d\n", wav_header.bits_per_sample);
goto end;
}
// 6. 设置采样率,如果硬件不支持我们设置的采样率,将使用最接近的
ret = wav_header.sample_rate;
i = 0;
if (snd_pcm_hw_params_set_rate_near(h_playback, hw_params, &ret, &i) < 0) {
perror("snd_pcm_hw_params_set_rate_near");
goto end;
}
// 7. 设置通道数量
if (wav_header.num_channels == 2 || wav_header.num_channels == 1) {
if (snd_pcm_hw_params_set_channels(h_playback, hw_params, wav_header.num_channels ) < 0) {
perror("snd_pcm_hw_params_set_channels");
goto end;
}
} else {
printf("unsupport number of channels, wav_header.num_channels = %d\n", wav_header.num_channels );
goto end;
}
// 8. 设置数据缓冲区大小
frames = FRAMES_PLAYBACK;
buff_siz = frames * wav_header.block_align;
if (snd_pcm_hw_params_set_buffer_size_near(h_playback, hw_params, &buff_siz) < 0) {
perror("snd_pcm_hw_params_set_buffer_size_near");
goto end;
}
// 9. 设置hw_params
if (snd_pcm_hw_params(h_playback, hw_params) < 0) {
perror("snd_pcm_hw_params");
goto end;
}
// allocate file read buffer
pbuf = (char *) malloc(buff_siz);
if (pbuf == NULL) {
perror("pbuf malloc error");
goto end;
}
i = 0;
while (i < wav_header.sub_chunk2_size) {
ret = fread(pbuf, 1, buff_siz, fp);
if (ret != buff_siz) {
if(ret == 0) {
fprintf(stderr, "end of file\n");
break;
} else {
frames = ret / wav_header.block_align;
}
}
i = i + ret;
ret = snd_pcm_writei(h_playback, pbuf, frames);
if (ret < 0){
if (ret == -EPIPE) { // EPIPE means underrun
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(h_playback);// 完成硬件参数设置,使设备准备好
} else {
fprintf(stderr, "error from writei: %s\n", snd_strerror(ret));
}
}
}
printf("play end\n");
free(pbuf);
end:
// 10. 关闭PCM设备句柄
snd_pcm_drain(h_playback);
snd_pcm_close(h_playback);
fclose(fp);
return 0;
}
Makefile代码
PREFIX = /home/jinggx/V3S/audio/alsa-lib
TARGET = wav
OBJS = wav
# set default to nothing for native builds
CROSS_COMPILE ?=
ifeq ($(CROSS_COMPILE),$())
CFLAGS = -I.
LDFLAGS = -L.
else
CFLAGS = -I $(PREFIX)/include
LDFLAGS = -L $(PREFIX)/lib
endif
CC = $(CROSS_COMPILE)gcc
$(TARGET): %:%.c
$(CC) $< -o $@ $(CFLAGS) $(LDFLAGS) -lasound
.PHONY: clean
clean:
rm -f *.o $(OBJS)
编译生成在电脑上运行的可执行文件
make
编译生成在V3S开发板上运行的可执行文件
make CROSS_COMPILE=arm-linux-gnueabihf-
将wav音频文件放在可执行文件生成的目录下
./wav xxx.wav
耳机就可以听到音乐声了。
实际写代码测试个人感觉这个alsa-lib不太好用,比较啰嗦,参数也有点混乱,不如RT-Thread驱动那么简洁明了。
这些东西花费了我三四天的时间才搞出来,现在分享在这里,希望初学者遇到类似问题时能有一点点帮助。
离线