7.1 基本概念
PCM: 脉冲编码调制
声音是 模拟量
计算机能处理的是 数字量,涉及模拟量和数字量的相互转换
录音时是模拟量转数字量
播放时是数字量转模拟量
采样频率:
每秒钟抽取声波幅度样本的次数
当采样频率应该在 40kHz左右,保证声音 不失真
量化位数:
每次采样,模拟音频信号的振幅用多少个bit来表示
一般使用 16bit
声道数:
有单声道和双声道之分, 双声道又称为立体声
MP3:
音频数据的压缩算法
有损压缩(还原后是有失真的)
压缩比可以接近10:1-12:1
使压缩后的文件在回放时能够达到比较接近原音源的声音效果
7.2 MP3文件的解码工作
// mp3.rar/MP3_format_parse.doc
其存储格式:
TAGV2
{ 。。。 }
FRAME /* 一帧数据 */
{
帧头
{。。。。 }
数据实体
}
FRAME
...
TAGV1
{ 。。。 }
mp3文件的解码工作,可以使用开源的库完成: // mp3.rar/libmad-0.15.1b.tar.gz
用于解码的libmad库的使用步骤:
1)$:' cd project/
2)$:' mkdir mp3
$:' cd mp3
3)$:' cp /mnt/hgfs/jy/project/env/mp3/libmad-0.15.1b.tar.gz ./
4)$:' tar xvf libmad-0.15.1b.tar.gz
5)$:' vi README
// 一般开源代码查阅和使用入口,先读readme,README重要信息如下:
// mad.h ---> API
// minimad.c ---> 例子程序:vi minimad.c
// INSTALL ---> configure 生成 Makefile和config.h:vi INSTALL
// $: vi INSTALL
---> 查看详细编译步骤 1.2.3.4.5.....
$:' ./configure --help
// 查看可以加什么参数来配置Makefile
$:' ./configure CC=arm-cortex_a9-linux-gnueabi-gcc --host=arm-linux --prefix=/home/tarena/project/mp3/install
// 用来生成合适的Makefile
--host: 运行目标平台
--prefix: [前缀]安装路径 make install
6)$:' make clean
// 开源代码注意清理不必要的已编译文件,方便编译最新的配置后的内容。
$:' make && make install
// /home/tarena/project/mp3/install下就有两个文件夹include和lib
7)$:' vi minimad.c
// 编译到arm开发板,移植库过去(/home/mp3/lib,profile添加库路径)
libmad API使用的演示demo:
从标准输入获取要解码的数据 ↓
进行解码 ↓
解码后的数据打印到标准输出设备 ↓
计划将解码之后的数据输出到声卡设备 ↓
// Linux_sound_guide.doc
// ALSA Audio API 使用指南(中英版) - 幻雪神界 - 博客频道 - CSDN.NET.html
7.3 声卡的驱动程序
linux下的声卡驱动分两种:
OSS: Open Sound System
最早出现的
非开源程序
/dev/dsp
/dev/mixer
ALSA: 开源
$:' make menuconfig
Device Drivers --->
<*> Sound card support --->
<*> Advanced Linux Sound Architecture --->
// linux内核自带的声卡驱动程序配置选项,默认勾选。
#:' ls /dev/snd/
controlC0
controlC1
pcmC0D0c
pcmC0D0p
pcmC1D0p
timer
// 音频/声卡相关 设备 ↓
7.4 alsa库的使用
如果是 alsa架构,提供了一套用户空间访问以上设备文件的库函数:
// alsa-lib-1.1.1.tar.bz2
alsa库封装了对/dev/snd下设备文件的操作
编译步骤:
1)$:' cd mp3/
2)$:' cp /mnt/hgfs/project/env/mp3/alsa-lib-1.1.1.tar.bz2 ./
$:' tar xf alsa-lib-1.1.1.tar.bz2
$:' cd alsa-lib-1.1.1/
// 开源库如果没有README就从INSTALL文件中查看 $: vi INSTALL
3)$:' ./configure --help
// 查看可添加参数
$:' ./configure CC=arm-cortex_a9-linux-gnueabi-gcc --host=arm-linux --prefix=/home/tarena/project/mp3/install --with-configdir=/opt/alsa --with-plugindir=/opt/alsa_lib
// CC:编译器。--host:运行目标平台。--prefix:安装路径。
// --with-configdir:配置安装目录 /opt/alsa
// --with-plugindir:插件安装目录 /opt/alsa_lib
4) $:' make clean && make && make install
以上库函数的使用可以参考:
alsa使用官网:
http://www.alsa-project.org/alsa-doc/alsa-lib/index.html
http://www.alsa-project.org/main/index.php/Download
或者参考:
// ALSA Audio API 使用指南(中英版) - 幻雪神界 - 博客频道 - CSDN.NET.html
两套API,一套解码,一套访问声卡设备。
以上两个库libmad alsa库的使用实例 // mp3_player.c
实现 #:' ./player xxx.mp3
可以播放xxx.mp3音乐
mp3_player.c和minimad.c相比修改以下内容:
1) 原来从标准输入读取解码数据
现在 从xxx.mp3文件中读取解码数据
2) 设置声卡的工作属性 44.1KHz 16bit 双声道
set_pcm() //参考文档中“一个最简单的回放程序”3) 修改了output
{
/*打开声卡设备*/
rc=snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
//硬件设置为交错模式
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
//设置16位采样精度
rc=snd_pcm_hw_params_set_format(handle, params, SND_PCM _FORMAT_S16_LE);
//设置声道,1表示单声>道,2表示立体声
rc=snd_pcm_hw_params_set_channels(handle, params, channels);
//设置频率
snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir);
}
将解码的音频数据输出到声卡设备
注意:
2)3)参考 //ALSA Audio API 使用指南(中英版)-幻雪神界 - 博客频道 - CSDN.NET.html
/*libmad解码的音频帧数据写入声卡设备*/
snd_pcm_writei(handle, OutputPtr, n);
编译 部署 运行 player程序:
1) $:' cd ~/project/mp3/
$:' cp /mnt/hgfs/jy/project/env/ehome/ehome_day07/mp3/mp3_player.c ./
$:' arm-cortex_a9-linux-gnueabi-gcc mp3_player.c -lmad -lasound -Linstall/lib/ -I install/include/ -o player
// 编译,注意指定的库文件路径
如果出现了以下错误:
libmad.so : III_....
解决方法:
cd libmad-0.15.1b
make clean // 主要是此步骤,清空原有版本.o文件
make
make install
arm-cortex...... mp3_player.c // 再次编译即可
2) $:' cp player ../rootfs/home/bin/
3) $:' cp install/lib/* ../rootfs/home/lib/ -a
// 库文件拷贝,注意 -a 选项拷贝最安全(软连接)
4) $:' cp /opt/alsa/ ../rootfs/opt/ -a
$:' cp /opt/alsa_lib/ ../rootfs/opt/ -a
// 部署配置文件和插件库文件
注意,因为配置时指定了相应的路径:
--with-configdir=/opt/alsa --with-plugindir=/opt/alsa_lib
部署到开发板上时 路径一定要保持一致!!!
5) $:' mkdir ../rootfs/home/songs
$:' cp /mnt/hgfs/project/env/mp3/*.mp3 ../rootfs/home/songs/
6) $:' source /home/etc/profile
// 刷新环境变量
$:' /home/bin/player /home/songs/I\ Will\ Always\ Love\ You Always\ Love\ You.mp3 1>/dev/null 2>&1
// 运行播放拷贝过去的mp3文件。1>/dev/null 2>&1 将标准输入和标准出错都定向到null设备,不显示一些不必要的出错信息,MP3正常播放即可。
练习:
1)看懂mp3_player.c
2)编译、部署到开发板运行
3)/home/bin/player /home/songs/xxx.mp3
//将/home/songs目录下的所有.mp3文件播放一遍
#:' /home/bin/player /home/songs
// unix的目录操作,对函数进行修改。
参考代码: // ehome.tar.gz/server/myplayer.c myplayer_hw.c
有目录操作
/** 代码演示 - 实现随机播放 mp3_player.c **/
int main(int argc, char *argv[])
{
struct stat stat;
void *fdm;
if (argc != 2)
{
printf("Usage: minimad + mp3 file name");
return 1;
}
DIR *p_dir = opendir (argv[1]);
if (!p_dir) {
perror ("opendir");
return 11;
}
printf ("open %s success...\n", argv[1]);
srand (time (0));
char buf[128];
char* path[12];
int i = 0, j = 0, k = 0;
struct dirent *p_dire;
while ( (p_dire = readdir (p_dir)) )
{
if (strstr (p_dire->d_name, ".mp3")) {
path[i] = (char*)malloc (sizeof (buf)); // 最佳方法使用结构体struct
memset (buf, 0, sizeof (buf));
strcpy (buf, argv[1]);
strcat (buf, p_dire->d_name);
strcpy (path[i], buf);
printf ("path[%d] = %s\n", i, path[i]);
i++;
}
}
while (j < i) {
k = rand() % i;
printf ("start playing %s...\n", path[k]);
printf ("rand k = %d\n", k);
int fd = open(path[k], O_RDWR);
printf ("path[%d] = %s\n", k, path[k]);
if(fd<0)
{
perror("open file failed:");
return 1;
}
if (fstat(fd, &stat) == -1 || stat.st_size == 0)
{
printf("fstat failed:\n");
return 2;
}
//printf("stat.st_size=%d\n",stat.st_size);
fdm = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (fdm == MAP_FAILED)
return 3;
if(set_pcm()!=0)
{
printf("set_pcm fialed:\n");
return 1;
}
signal(SIGINT, sig_close);
decode(fdm, stat.st_size);
if (munmap(fdm, stat.st_size) == -1)
return 4;
printf ("end playing %s...\n", p_dire->d_name);
free (path[j]);
printf ("starting next song...\n");
j++;
}
closedir (p_dir);
snd_pcm_drain(handle);
snd_pcm_close(handle);
return 0;
}