回答

收藏

[评测分享] 【米尔-STM32MP135入门级开发板测评】 5.音频播放测试

#板卡评测 #板卡评测 1875 人阅读 | 0 人回复 | 2023-10-03

1.  ALSA 概述
ALSA(Advanced Linux Sound Architecture)是Linux操作系统的音频子系统,负责处理声音和音频。作为Linux内核的一部分,它提供了全面的音频支持,允许应用程序与音频硬件进行通信,实现音频录制、播放和处理。ALSA的关键特点包括多硬件支持,可适用于内置声卡、USB音频设备等各种硬件。它专注于提供低延迟音频处理,因此适用于实时音频应用,如音乐制作和游戏。此外,ALSA具有模块化设计,可轻松添加或替换音频驱动程序和组件,以满足不同需求。它还提供用户空间工具和库,用于配置和操作音频设备。尽管最初设计为Linux,但一些ALSA组件已移植到其他操作系统,实现跨**音频支持。总之,ALSA是Linux上强大的音频架构,为各种音频应用提供了可靠的音频处理功能。



音频示意图
2.  lsa-lib库
alsa-lib 是一套 Linux 应用层的 C 语言函数库,为音频应用程序开发提供了一套统一、标准
的接口,应用程序只需调用这一套 API 即可完成对底层声卡设备的操控,譬如播放与录音。
用户空间的 alsa-lib对应用程序提供了统一的API 接口,这样可以隐藏驱动层的实现细节,简化了应用
程序的实现难度、无需应用程序开发人员直接去读写音频设备节点。
在 Linux 内核设备驱动层、基于 ALSA 音频驱动框架注册的 sound 设备会在/dev/snd 目录下生成相应的
设备节点文件,譬如MP135 开发板出厂系统/dev/snd目录下有如下文件:

3.硬件连接
如下所示为板子的外设接口示意图,引出了音频接口,通过耳机接口引出,需要将耳机线连接到Audio接口。



电路原理图:


实物连接如下所示:

4.应用编程测试
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<alsa/asoundlib.h>
#definePCM_PLAYBACK_DEV "hw:0,0"
static snd_pcm_t*pcm = NULL; //pcm 句柄
static unsignedint buf_bytes; //应用程序缓冲区的大小(字节为单位)
static void *buf= NULL; //指向应用程序缓冲区的指针
static int fd =-1; //指向 WAV 音频文件的文件描述符
staticsnd_pcm_uframes_t period_size = 1024; //周期大小(单位: 帧)
static unsignedint periods = 4; //周期数(设备驱动层 buffer 的大小)
static int snd_pcm_init(void)
{
snd_pcm_hw_params_t *hwparams = NULL;
int ret;
/* 打开 PCM 设备 */
ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV,SND_PCM_STREAM_PLAYBACK, 0);
if (0 > ret) {
fprintf(stderr, "snd_pcm_open error: %s:%s\n",
PCM_PLAYBACK_DEV, snd_strerror(ret));
return -1;
}
/* 实例化 hwparams 对象 */
snd_pcm_hw_params_malloc(&hwparams);
/* 获取 PCM 设备当前硬件配置,对 hwparams 进行初始化 */
ret = snd_pcm_hw_params_any(pcm, hwparams);
if (0 > ret) {
fprintf(stderr, "snd_pcm_hw_params_any error:%s\n", snd_strerror(ret));
goto err2;
}
/**************
设置参数
***************/
/* 设置访问类型: 交错模式 */
ret = snd_pcm_hw_params_set_access(pcm,hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
if (0 > ret) {
fprintf(stderr,"snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
goto err2;
}
/* 设置数据格式: 有符号 16 位、小端模式 */
ret = snd_pcm_hw_params_set_format(pcm,hwparams, SND_PCM_FORMAT_S16_LE);
if (0 > ret) {
fprintf(stderr,"snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
goto err2;
}
/* 设置采样率 */
ret = snd_pcm_hw_params_set_rate(pcm,hwparams, wav_fmt.SampleRate, 0);
if (0 > ret) {
fprintf(stderr,"snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
goto err2;
}
/* 设置声道数: 双声道 */
ret = snd_pcm_hw_params_set_channels(pcm,hwparams, wav_fmt.NumChannels);
if (0 > ret) {
fprintf(stderr,"snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
goto err2;
}
/* 设置周期大小: period_size */
ret = snd_pcm_hw_params_set_period_size(pcm,hwparams, period_size, 0);
if (0 > ret) {
fprintf(stderr,"snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
goto err2;
}
/* 设置周期数(驱动层 buffer 的大小): periods */
ret = snd_pcm_hw_params_set_periods(pcm,hwparams, periods, 0);
if (0 > ret) {
fprintf(stderr,"snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
goto err2;
}
/* 使配置生效 */
ret = snd_pcm_hw_params(pcm, hwparams);
snd_pcm_hw_params_free(hwparams); //释放 hwparams 对象占用的内存
if (0 > ret) {
fprintf(stderr, "snd_pcm_hw_params error:%s\n", snd_strerror(ret));
goto err1;
}
buf_bytes = period_size * wav_fmt.BlockAlign;//变量赋值,一个周期的字节大小
return 0;
err2:
snd_pcm_hw_params_free(hwparams);
err1:
snd_pcm_close(pcm); //关闭 pcm 设备
return -1;
}
static intopen_wav_file(const char *file)
{
RIFF_t wav_riff;
DATA_t wav_data;
int ret;
fd = open(file, O_RDONLY);
if (0 > fd) {
fprintf(stderr, "open error: %s:%s\n", file, strerror(errno));
return -1;
}
ret = read(fd, &wav_riff, sizeof(RIFF_t));
if (sizeof(RIFF_t) != ret) {
if (0 > ret)
perror("read error");
else
fprintf(stderr, "check error: %s\n",file);
close(fd);
return -1;
}
if (strncmp("RIFF",wav_riff.ChunkID, 4) ||//校验
strncmp("WAVE", wav_riff.Format, 4)){
fprintf(stderr, "check error: %s\n",file);
close(fd);
return -1;
}
ret = read(fd, &wav_fmt, sizeof(FMT_t));
if (sizeof(FMT_t) != ret) {
if (0 > ret)
perror("read error");
else
fprintf(stderr, "check error: %s\n",file);
close(fd);
return -1;
}
if (strncmp("fmt ",wav_fmt.Subchunk1ID, 4)) {//校验
fprintf(stderr, "check error: %s\n",file);
close(fd);
return -1;
}
printf("<<<<音频文件格式信息>>>>\n\n");
printf(" file name: %s\n", file);
printf(" Subchunk1Size: %u\n",wav_fmt.Subchunk1Size);
printf(" AudioFormat: %u\n",wav_fmt.AudioFormat);
printf(" NumChannels: %u\n",wav_fmt.NumChannels);
printf(" SampleRate: %u\n",wav_fmt.SampleRate);
printf(" ByteRate: %u\n",wav_fmt.ByteRate);
printf(" BlockAlign: %u\n",wav_fmt.BlockAlign);
printf(" BitsPerSample: %u\n\n",wav_fmt.BitsPerSample);
if (0 >lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,
SEEK_SET)) {
perror("lseek error");
close(fd);
return -1;
}
while(sizeof(DATA_t) == read(fd,&wav_data, sizeof(DATA_t))) {
if(!strncmp("data", wav_data.Subchunk2ID, 4))
return 0;
if (0 > lseek(fd, wav_data.Subchunk2Size,SEEK_CUR)) {
perror("lseek error");
close(fd);
return -1;
}
}
fprintf(stderr, "check error: %s\n",file);
return -1;
}
int main(intargc, char *argv[])
{
int ret;
if (2 != argc) {
fprintf(stderr, "Usage: %s<audio_file>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (open_wav_file(argv[1]))
exit(EXIT_FAILURE);
if (snd_pcm_init())
goto err1;
buf = malloc(buf_bytes);
if (NULL == buf) {
perror("malloc error");
goto err2;
}
While(1) {
memset(buf, 0x00, buf_bytes);
ret = read(fd, buf, buf_bytes);
if (0 >= ret)
goto err3;
ret = snd_pcm_writei(pcm, buf, period_size);
if (0 > ret) {
fprintf(stderr, "snd_pcm_writei error:%s\n", snd_strerror(ret));
goto err3;
}
else if (ret < period_size) {
if (0 >lseek(fd, (ret-period_size) * wav_fmt.BlockAlign, SEEK_CUR)) {
perror("lseek error");
goto err3;
}
}
}
err3:
free(buf); //释放内存
err2:
snd_pcm_close(pcm); //关闭 pcm 设备
err1:
close(fd); //关闭打开的音频文件
exit(EXIT_FAILURE);
}
将编译好的可执行文件添加到开发板,并添加可执行文件,将需要播放的音频同样放在当前路径下,我这里是将下载的音乐通过格式化工厂进行格式转换,重命名为test.wav。然后输入./pcm_playback  ./test.wav进行音乐播放,并打印出音频格式信息,运行程序,开始播放音乐。


分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条