S3C2416裸机开发系列19_Fatfs播放录像wav音频文件
S3C2416裸机开发系列19
Fatfs播放录像wav音频文件
国际象棋男孩 1048272975
多媒体资源,一般都是以文件的形式存储在固化存储器中。Fatfs所支持的fat32为windows支持的文件系统,因此在嵌入式系统中採用Fatfs文件系统可极大地扩展系统的应用。
比如,把计算机上图片。音频,视频。文本等资源直接复制到嵌入式系统中的固化存储器中,在系统中就可以直接应用这些资源。
把嵌入式系统中录制的音频、视频直接保存成一定的格式,在计算机上可直接播放处理。把传感器採集的数据保存成txt或dat文件,在计算机上通过处理生成数据曲线分析等。
笔者此处就wav音频文件的播放与录音进行简单的介绍。
1. wav音频格式
Wave是录音时用的标准windows文件格式,文件扩展名为”.wav”,数据本身的格式为PCM或压缩型,它是由微软与IBM联合开发的用于音频数字存储的标准。採用RIFF文件格式结构。
RIFF全称资源互换文件格式。是windows下大部分多媒体文件遵循的一种文件结构,除了本文所说的波形格式数据(.wav),採用RIFF格式结构的文件还有音频视频交错格式(.avi)、位图格式(.rdi)、MIDI格式(.rmi)、调色板格式(.pal)、多媒体电影(.rmn)、动画光标(.ani)。
RIFF结构的基本单元为chunk,它的结构例如以下:
struct chunk {
unsignedint id; /* 块标志 */
unsignedint size; /* 块大小 */
unsigned chardata[size]; /* 块内容 */
}
Id为4个ascii字符组成,用来识别块中所包括的数据,如”RIFF”、”WAV ”、”data”、”fmt ”等;size是存储在data域中数据的长度,不包括id与size域的大小;data[size]为该块保存的数据,以字为单位排列。
wav音频文件作为RIFF结构。其由若干个chunk组成。依照在文件里的位置包括:RIFF chunk。fmt chunk,fact chunk(可选)。data chunk。全部RIFF结构文件均会首先包括RIFF chunk,并指明RIFF类型,此处为”WAVE”。对于wav文件,在fmt chunk中指明音频文件的信息,比如採样位数、採样频率、声道数、编码方式等。
对于压缩型wav音频。还会有一个fact chunk。用以指明解压后音频数据的大小,对于PCM非压缩wav文件,并没有该chunk。音频数据保存在data
chunk中,依据fmt chunk中指明的声道数以及採样位数。wav音频数据存放形式有不同的方式。
一个PCM格式的wav结构定义Wav.h例如以下:
#ifndef __WAV_H__
#define __WAV_H__
#ifdef __cplusplus
extern "C" {
#endif
//资源互换文件格式RIFF,树状结构,基本单位是chunk。整个文件由chunk构成
typedef struct RIFF_HEADER {
char Riff_ID[4];
unsigned int Riff_Size;//记录整个RIFF文件的大小。除ID和Size这两个变量
char Riff_Format[4];
} RIFF_HEADER;
typedef struct WAVE_FORMAT {
unsigned short FormatTag; //声音的格式代号
unsigned short Channels; //声音通道
unsigned int SamplesPerSec; //採样率
unsigned int AvgBytesPerSec; //=採样率*区块对其单位
unsigned short BlockAlign; //区块对其单位=每一个取样所需位数*声音通道/8
unsigned short BitsPerSample; //每一个取样所需位数
} WAVE_FORMAT;
typedef struct FMT_CHUNK {
char Fmt_ID[4];
unsigned int Fmt_Size;//记录fmt的大小
WAVE_FORMAT WaveFormat;
} FMT_CHUNK;
typedef struct DATA_CHUNK {
char Data_ID[4];
unsigned int Data_Size;//记录data区的大小
} DATA_CHUNK;
typedef struct WAVE_HEADER {
RIFF_HEADER RiffHeader;
FMT_CHUNK FmtChunk;
DATA_CHUNK DataChunk;
} WAVE_HEADER;
#ifdef __cplusplus
}
#endif
#endif /*__WAV_H__*/
依据以上的wav结构定义。一个录音文件的wav文件头可例如以下定义:
static WAVE_HEADER RecorderWaveHeader = {
'R', 'I', 'F', 'F',
sizeof(WAVE_HEADER) - 8,//整个wave文件大小,初始化值
'W', 'A', 'V', 'E',
'f', 'm', 't', ' ',
sizeof(FMT_CHUNK) - 8,
1,//编码方式。线性PCM编码
1,//单声道
10000,//採样率为10k
20000,//每一个採样2个字节
2,//每一个採样2个字节
16,//每一个採样需16位
'd', 'a', 't', 'a',
0, //data长度初始化为0
};
2. wav音频文件的播放或录音
sd卡因为其可插拔、灵活性好,通常应用于设备的扩展存储应用。
计算机上wav音频等文件可轻易地复制到fat32格式的sd卡上。在嵌入式系统中要使用sd卡。首先需实现sd卡驱动。这在前面的章节有具体的介绍。此处不再详述。fat32文件的读写还须要对应文件系统的接口支持,此处选用Fatfs,对于不同的嵌入式系统。这是须要移植的部分。关于s3c2416下Fatfs文件系统的移植在前面章节有具体的介绍,此处不再详述。
从wav文件读出音频数据后(播放),还须要把音频传输数据给声卡,声卡还原出声音模拟信号。就可以听到声音。音频数据的处理须要用到音频编解码器,数据的传输也有一定的音频总线要求,因此还须要音频驱动的实现,这部分在前面的章节有具体的介绍。此处不再详述。
3. 应用实例
project中利用串口对耳机音量进行加大、调小。对Mic录音进行灵敏度的调节,通过串口输入进行播放wav音频或開始录音。播放时实时显示播放进度并可按下’s’后停止播放,录音时实时显示录音wav文件的大小并可按下’s’后停止录音。
main.c的内容例如以下:
#include"s3c2416.h"
#include"UART0.h"
#include"ff.h"
#include"diskio.h"
#include "RTC.h"
#include"Wav.h"
#include"IIS.h"
#include"IIC.h"
#include"WM8960.h"
staticWAVE_HEADER RecorderWaveHeader = {
'R', 'I', 'F', 'F',
sizeof(WAVE_HEADER) - 8,//整个wave文件大小
'W', 'A', 'V', 'E',
'f', 'm', 't', ' ',
sizeof(FMT_CHUNK) - 8,
1,//编码方式,线性PCM编码
1,//单声道
10000,//採样率为10k
20000,//每一个採样2个字节
2,//每一个採样2个字节
16,//每一个採样需16位
'd', 'a', 't', 'a',
0, //data长度初始化为0
};
// 音频数据缓存20KB
unsigned charAudioBuffer[20*1024];
int main()
{
FATFS fs;
FIL file;
FRESULT Res;
unsigned int i;
unsigned int BufferLen;
unsigned char *pData;
WAVE_HEADER WaveHeader;
int ByteWrite, ByteRead;
unsigned char State;
unsigned char VolumeLevel;
unsigned short Command;
const char Path1[] = "test.wav";
const char Path2[] = "1.wav";
char FilePath[256];
unsigned int Size = 0;
unsigned int AudioSize = 0;
unsigned int TotalSize = 0;
RTC_Time Time = {
2014, 5, 22, 23, 00, 0, 5
};
RTC_Init(&Time); // RTC初始化
Uart0_Init(); // 串口初始化
IIC_Init(); //IIC初始化,音频芯片控制
IIS_Init(); // IIS音频接口初始化
WM8960_Init(); // 音频编解码器初始化
RTC_GetTime(&Time); // 显示RTC时间
Uart0_Printf("Time: %4d/%02d/%02d%02d:%02d:%02d\r\n", Time.Year,
Time.Month, Time.Day, Time.Hour,Time.Min, Time.Sec);
f_mount(&fs, "" , 0);
ByteRead = 0;
pData = (unsigned char *)0;
for (i=0; i<sizeof(Path1); i++) {
FilePath[i] = Path1[i];
}
State = 1; // 进入播放test.wav状态
while(1) {
switch (State) {
case 0: // 操作选择
WM8960_HeadphoneStop();
WM8960_RecorderStop();
IIS_TxPause();
IIS_RxPause();
Uart0_SendString("\r\nSelect:\r\n"
"0: Play test.wav\r\n"
"1: Play recording file\r\n"
"2: Start recorder\r\n"
"3: Recorder volume up\r\n"
"4: Recorder volume down\r\n"
"5: Player volume up\r\n"
"6: Player volume down\r\n"
);
while(State == 0) {
// 等待串口选择操作,堵塞型
Command = Uart0_ReceiveByte();
switch (Command) {
case '0': // 播放test.wav
for (i=0; i<sizeof(Path1);i++) {
FilePath[i] = Path1[i];
}
State = 1; // 转到開始播放wav状态
break;
case '1': // 播放录音wav
for (i=0; i<sizeof(Path2);i++) {
FilePath[i] = Path2[i];
}
State = 1; // 转到開始播放wav状态
break;
case '2':
State = 3; // 转到開始录音状态
break;
case '3': // Mic灵敏度添加
VolumeLevel = WM8960_RecorderVolume(VolumeUp);
Uart0_Printf("Recordervolume %d%%\r\n", VolumeLevel);
break;
case '4': // Mic灵敏度减小
VolumeLevel =WM8960_RecorderVolume(VolumeDown);
Uart0_Printf("Recorder volume%d%%\r\n", VolumeLevel);
break;
case '5': // 耳机音量添加
VolumeLevel =WM8960_HeadphoneVolume(VolumeUp);
Uart0_Printf("Player volume%d%%\r\n", VolumeLevel);
break;
case '6': // 耳机音量减少
VolumeLevel =WM8960_HeadphoneVolume(VolumeDown);
Uart0_Printf("Player volume%d%%\r\n", VolumeLevel);
break;
default:
break;
}
}
Uart0_SendString("\r\n");
break;
case 1: // 開始播放音频
// 打开wav音频文件
Res = f_open(&file, FilePath, FA_READ | FA_OPEN_EXISTING);
if (Res != RES_OK) {
Uart0_Printf("Open %s failed\r\n",FilePath);
State = 0; // 进入到操作选择界面
} else {
// 读取wav音频文件头,获得音频採样率,位数,声道数信息
Res = f_read(&file, (unsignedchar *)&WaveHeader,
sizeof(WAVE_HEADER),(unsigned int *)&ByteRead);
if (Res != RES_OK) {
f_close(&file);
Uart0_Printf("Read wavheader error\r\n");
State = 0; // 进入到操作选择界面
} else {
// 读取一小段音频数据到缓存中
Res = f_read(&file,(unsigned int *)AudioBuffer,
sizeof(AudioBuffer), (unsignedint *)&ByteRead);
if (Res != RES_OK) {
Uart0_Printf("Read wavdata error\r\n");
f_close(&file);
State = 0; // 进入到操作选择界面
} else {
if (ByteRead <sizeof(AudioBuffer)) { // 文件到结尾
// 已播放到文件的结尾,重定位到音频文件的開始
Res = f_lseek(&file,sizeof(WAVE_HEADER));
if (Res != RES_OK) {
Uart0_Printf("f_lseek error\r\n");
f_close(&file);
State= 0; // 进入到操作选择界面
break;
}
}
// 依据wav文件头的音频信息初始化音频驱动的播放參数
IIS_TxInit(WaveHeader.FmtChunk.WaveFormat.SamplesPerSec,
WaveHeader.FmtChunk.WaveFormat.BitsPerSample,
WaveHeader.FmtChunk.WaveFormat.Channels);
// wav文件的总文件大小,bytes计
TotalSize =WaveHeader.RiffHeader.Riff_Size;
pData = AudioBuffer; // 播放指向音频缓存区
// 把pData指向的数据写入音频缓存,最大同意写入
// ByteRead字节,返回实际写入到音频缓存的字节数
BufferLen = IIS_WriteBuffer(pData,ByteRead);
ByteRead -= BufferLen; // 数据剩余字节数
pData += BufferLen; // 数据下一次開始写入的位置
Size = BufferLen; // 播放的长度
AudioSize = 0; // 已播放的音频长度
WM8960_HeadphoneStart(); // 打开耳机播放通道
IIS_TxStart(); // IIS開始传输音频播放
State= 2; // 转入正在播放音频状态
Uart0_SendString("PlayingMusic, press 's' to stop"
"playing at any time\r\n");
Uart0_SendString("Playbackprogress: 00.0%");
}
}
}
break;
case 2:// 正在播放音频
if (ByteRead > 0) {
// 返回值的高8位不为0,说明低8位键值有效,查询是否有串口输入,非堵塞
Command = Uart0_Peek();
if (Command & (0xff<<8)) {// 有按键按下
if ((Command & 0xff) == 's'){ // 按下了's'
f_close(&file);
State = 0; // 返回到操作选择界面
break;
}
}
if (Size > 20*1024) { // 播放了20k大小的音频数据
AudioSize += (Size>>10);// 累计己播放的总音频数据大小(KB)
Size = 0;
Uart0_SendString("\b\b\b\b\b");
// 显示播放进度的百分比
Uart0_Printf("%02d.%d%%",(AudioSize*100)/(TotalSize>>10),
((AudioSize*100)%(TotalSize>>10))*10/(TotalSize>>10));
}
// 连续写入音频数据到音频缓存中,实现连续播放
BufferLen = IIS_WriteBuffer(pData,ByteRead);
ByteRead -= BufferLen; // 数据剩余字节数
pData += BufferLen; // 数据下一次開始写入的位置
Size += BufferLen;
} else { // 播放完缓存中的音频数据,再从sd卡载入下一段音频数据
// 一段音频数据播放完后,从sd卡中载入下一段数据
Res = f_read(&file, (unsignedchar *)AudioBuffer,
sizeof(AudioBuffer),(unsigned int *)&ByteRead);
if (Res != RES_OK) {
Uart0_Printf("Read wav dataerror\r\n");
f_close(&file);
State = 0; // 进入到操作选择界面
} else {
pData = AudioBuffer; // 重定位到数据首位置
if (ByteRead <sizeof(AudioBuffer)) {
// 到文件结尾,文件重定位到开头,重播放
Uart0_Printf("\r\n");
Uart0_Printf("replay%s\r\n", FilePath);
Uart0_SendString("Playbackprogress: 00.0%");
Size = 0;
AudioSize = 0;
Res = f_lseek(&file,sizeof(WAVE_HEADER));
if (Res != RES_OK) {
Uart0_Printf("Replayaudio error\r\n");
f_close(&file);
State = 0; // 进入到操作选择界面
}
}
}
}
break;
case 3: // 開始录音
// 创建录音保存1.wav文件
Res = f_open(&file, "1.wav",FA_WRITE | FA_CREATE_ALWAYS);
if (Res != RES_OK) {
Uart0_Printf("Create 1.wavfailed\r\n");
State = 0; // 进入到操作选择界面
} else {
// 写入wav文件头
Res = f_write(&file, (unsignedchar *)&RecorderWaveHeader,
sizeof(WAVE_HEADER), (unsignedint *)&ByteWrite);
if (Res != RES_OK) {
f_close(&file);
Uart0_Printf("Write wavheader error\r\n");
State = 0; // 进入到操作选择界面
} else {
// 初始化录音參数,採样率,採样位数,声道数
IIS_RxInit(RecorderWaveHeader.FmtChunk.WaveFormat.SamplesPerSec,
RecorderWaveHeader.FmtChunk.WaveFormat.BitsPerSample,
RecorderWaveHeader.FmtChunk.WaveFormat.Channels);
Size = 0;
AudioSize = 0; // 总录音文件大小初始化0
pData = AudioBuffer; // 指向录音缓存区
ByteWrite = sizeof(AudioBuffer);// 一段音频缓存的大小
WM8960_RecorderStart(); //WM8960打开录音通道
IIS_RxStart(); // IIS開始接收录音数据
State = 4; // 转到正在录音状态
Uart0_SendString("Recording,press 's' to stop recording"
"at any time\r\n");
Uart0_SendString("Recording(KB): ");
}
}
break;
case 4:// 正在录音
if (ByteWrite > 0) {
// 返回值的高8位不为0,说明低8位键值有效,查询是否有串口输入,非堵塞
Command = Uart0_Peek();
if (Command & (0xff<<8)) {// 有按键按下
if ((Command & 0xff) == 's'){ // 按下了's'
// 停止录音,更改wav文件头文件大小
f_lseek(&file, 0); // 定位到文件头
// 数据大小改为录音的音频大小
RecorderWaveHeader.DataChunk.Data_Size= AudioSize;
// RIFF大小改为整个文件文件的大小
RecorderWaveHeader.RiffHeader.Riff_Size=
(sizeof(WAVE_HEADER)-8) + AudioSize;
// 更改wav文件头信息
f_write(&file, (unsignedchar *)&RecorderWaveHeader,
sizeof(WAVE_HEADER),(unsigned int *)&ByteWrite);
f_close(&file);
State = 0; // 进入到操作选择界面
break;
}
}
if (Size > 20*1024) { // 记录了20k大小的音频数据
AudioSize += Size; // 累计己播放的总音频数据大小
Size = 0;
Uart0_SendString("\b\b\b\b\b\b");
// 显示已录音的文件大小
Uart0_Printf("%6d",(AudioSize>>10));
}
// 从音频缓存中读取录音数据到pData中,最大同意读取ByteWrite
// 字节大小,返回实际从音频缓存中读取的字节数
BufferLen = IIS_ReadBuffer(pData,ByteWrite);
ByteWrite -= BufferLen; // 剩余内存空间字节数
pData += BufferLen; // 下一位读開始存入的内存位置
Size += BufferLen;
} else { // 缓存已满,写入缓存数据到sd卡中
Res = f_write(&file, (unsignedchar *)&AudioBuffer,
sizeof(AudioBuffer),(unsigned int *)&ByteWrite);
if (Res != RES_OK) {
f_close(&file);
Uart0_Printf("Write 1.waverror\r\n");
State = 0; // 进入到操作选择界面
} else {
pData = AudioBuffer;
ByteWrite = sizeof(AudioBuffer);
}
}
break;
default:
break;
}
}
}
4. 附录
通过Fatfs的api函数,能够轻易读写windows下常见格式文件,这和windows/Linux下操作文件差异不大。播放对wav音频文件无特殊要求。可随意採样率、採样位数、单/双声道,插上耳机即能听到声音,录制wav音频对採样率、採样位数、声道数、录制长度等均没有不论什么限制。录制好的wav音频文件可直接在计算机上播放。尽管wav格式音频文件较占用存储空间。但其是无损的,音质在同样码率下远好于mp3等有损压缩音频文件。
Wav_GCC.rar,GCC下wav音频文件播放与录制project。可直接make。
http://pan.baidu.com/s/1c05s2cg
Wav_MDK.rar,MDK下wav音频文件播放与录制project
http://pan.baidu.com/s/1i33guiD
test.wav,wav播放測试音频文件,11.025k採样率、16位、单声道音乐。可通过音频格式转换软件生成wav音频文件。
http://pan.baidu.com/s/1eQzOErg
版权声明:本文博客原创文章,博客,未经同意,不得转载。
S3C2416裸机开发系列19_Fatfs播放录像wav音频文件的更多相关文章
- S3C2416裸机开发系列十六_sd卡驱动实现
S3C2416裸机开发系列十六 sd卡驱动实现 象棋小子 1048272975 SD卡(Secure Digital Memory Card)具有体积小.容量大.传输数据快.可插拔.安全性好等长 ...
- Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件
原文 Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件 第21部分:永久保存Wav音频文件 原文地址:http://channel9.msdn.com/Series/Win ...
- Windows Phone 8初学者开发—第20部分:录制Wav音频文件
原文 Windows Phone 8初学者开发—第20部分:录制Wav音频文件 原文地址:http://channel9.msdn.com/Series/Windows-Phone-8-Develop ...
- 用 Qt 的 QAudioOutput 类播放 WAV 音频文件
用 Qt 的 QAudioOutput 类播放 WAV 音频文件 最近有一个项目,需要同时控制 4 个声卡播放不同的声音,声音文件很简单就是没有任何压缩的 wav 文件. 如果只是播放 wav 文件, ...
- C语言解析WAV音频文件
C语言解析WAV音频文件 代码地址: Github : https://github.com/CasterWx/c-wave-master 目录 前言 了解WAV音频文件 什么是二进制文件 WAV的二 ...
- iOS Dev (20) 用 AVAudioPlayer 播放一个本地音频文件
iOS Dev (20) 用 AVAudioPlayer 播放一个本地音频文件 作者:CSDN 大锐哥 博客:http://blog.csdn.net/prevention 步骤 第一步:在 Proj ...
- iOS Dev (21) 用 AVPlayer 播放一个本地音频文件
iOS Dev (21) 用 AVPlayer 播放一个本地音频文件 作者:CSDN 大锐哥 博客:http://blog.csdn.net/prevention 前言 这篇文章与上一篇极其相似,要注 ...
- 解析WAV音频文件----》生成WAV音频文件头
前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i WAV音频文件介绍: WAV文件是在PC机平台上很常见的.最经典的多媒体音频文件,最早于1991年8月出现在Windows3.1操作系统 ...
- Android音频开发之——如何播放一帧音频
本文重点关注如何在Android平台上播放一帧音频数据.阅读本文之前,建议先读一下<Android音频开发(1):基础知识>,因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的 ...
随机推荐
- 设计模式——辛格尔顿(Singleton)
要想正确理解设计模式,首先必须明白它是为了解决什么问题而提出来的. 设计模式学习笔记 --Shulin 转载请注明出处:http://blog.csdn.net/zhshulin 单例模式属于设计模式 ...
- MySQL保留关键字
今天在使用hibernate关联映射导出表的时候因为映射了一个表名为option,是MYSQL的关键字,总是生成错误,一开始以为是映射文件和代码问题,检查不出问题才想到可能用到数据库的保留关键字了,查 ...
- Debug with Eclipse
In this post we are going to see how to develop applications using Eclipse and Portofino 4. The trad ...
- 使用Spring的@Autowired 实现DAO, Service, Controller三层的注入(转)
简述: 结合Spring和Hibernate进行开发 使用@Autowired实现依赖注入, 实现一个学生注册的功能,做一个技术原型 从DAO(Repository) -> Service -& ...
- ajax j跨域请求sonp
需求 遇到的问题 解决方案 需求 如今,该项目需要获得数据访问外部链接.它是跨域.使用ajax 直提示: 遇到的问题 1. 怎样使用ajax 跨域请求数据 2. 能不能post请求 解决的方法 经过网 ...
- tolower (Function)
this is a function that Convert uppercase letter to lowercase Converts c to its lowercase equivalent ...
- CF417D--- Cunning Gena(序列+像缩进dp)
A boy named Gena really wants to get to the "Russian Code Cup" finals, or at least get a t ...
- Duanxx的Design abroad: C++矩阵运算库Eigen 概要
一.概要 这两天想起来要做神经网络的作业了,要求用C++完毕神经网络的算法. 摆在面前的第一个问题就是,神经网络算法中大量用到了矩阵运算.可是C++不像matlab那样对矩阵运算有非常好的支持.本来准 ...
- 用SourceTree轻巧Git项目图解
用SourceTree轻松Git项目图解 这篇文档的目的是:让使用Git更轻松. 看完这篇文档你能做到的是: 1.简单的用Git管理项目. 2.怎样既要开发又要处理发布出去的版本bug情况. Sour ...
- TCP/IP 网络精讲:开宗明义及第一课
内容简介 1.课程大纲 2.第一部分第一课:互联网历史 3.第一部分第二课预告:互联网的创立,OSI七层模型 课程大纲 我们将带大家一起来学习很多网络方面的技能,向大家介绍TCP/IP的基础知识点.你 ...