• 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 ", 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(" Select: "

                             "0: Play test.wav "

                             "1: Play recording file "

                             "2: Start recorder "

                             "3: Recorder volume up "

                             "4: Recorder volume down "

                             "5: Player volume up "

                             "6: Player volume down "

                            );

            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%% ", VolumeLevel);

                    break;

                case '4': // Mic灵敏度减小

                    VolumeLevel =WM8960_RecorderVolume(VolumeDown);

                    Uart0_Printf("Recorder volume%d%% ", VolumeLevel);

                    break;

                case '5': // 耳机音量添加

                    VolumeLevel =WM8960_HeadphoneVolume(VolumeUp);

                    Uart0_Printf("Player volume%d%% ", VolumeLevel);                       

                    break;

                case '6': // 耳机音量减少

                    VolumeLevel =WM8960_HeadphoneVolume(VolumeDown);

                    Uart0_Printf("Player volume%d%% ", VolumeLevel);

                    break;             

                default:

                    break; 

                }

            }

            Uart0_SendString(" ");

            break;

        case 1: // 開始播放音频

            // 打开wav音频文件

            Res = f_open(&file, FilePath, FA_READ | FA_OPEN_EXISTING);

            if (Res != RES_OK) {

                Uart0_Printf("Open %s failed ",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 ");

                    State = 0; // 进入到操作选择界面

                } else {

                    // 读取一小段音频数据到缓存中

                    Res = f_read(&file,(unsigned int *)AudioBuffer,

                        sizeof(AudioBuffer), (unsignedint *)&ByteRead);

                    if (Res != RES_OK) {

                        Uart0_Printf("Read wavdata error ");   

                        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 ");

                                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 ");

                        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("");

                    // 显示播放进度的百分比

                    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 ");   

                    f_close(&file);

                    State = 0; // 进入到操作选择界面

                } else {       

                    pData = AudioBuffer; // 重定位到数据首位置

                    if (ByteRead <sizeof(AudioBuffer)) {

                        // 到文件结尾,文件重定位到开头,重播放

                        Uart0_Printf(" ");

                        Uart0_Printf("replay%s ", 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 ");

                            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 ");

                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 ");

                    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 ");

                    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("");

                    // 显示已录音的文件大小

                    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 ");

                    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

    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    20170705总结
    20170703总结
    .NET 框架程序使用 Win32 API
    青春 就此别过
    Aptana Studio 2启动时提示 Workspace Cannot Be Created 解决办法
    App_GlobalResources.afvubzdv.resources.dll”--“拒绝访问。“
    c# 一维数组和二维数组的定义几种方式<转>.
    C#中Split分隔字符串的应用(C#、split、分隔、字符串)<转>
    C#操作字符串方法总结<转>
    C# 时间格式大全
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4661527.html
Copyright © 2020-2023  润新知