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进行音乐播放,并打印出音频格式信息,运行程序,开始播放音乐。
|