天天看點

S3C2416裸機開發系列十九_Fatfs下播放錄音wav音頻檔案S3C2416裸機開發系列十九

S3C2416裸機開發系列十九

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結構定義,一個錄音檔案的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. 應用執行個體

工程中利用序列槽對耳機音量進行加大、調小,對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;

    }

}

}

S3C2416裸機開發系列十九_Fatfs下播放錄音wav音頻檔案S3C2416裸機開發系列十九

4. 附錄

通過Fatfs的api函數,可以輕易讀寫windows下常見格式檔案,這和windows/Linux下操作檔案差異不大。播放對wav音頻檔案無特殊要求,可任意采樣率、采樣位數、單/雙聲道,插上耳機即能聽到聲音,錄制wav音頻對采樣率、采樣位數、聲道數、錄制長度等均沒有任何限制,錄制好的wav音頻檔案可直接在計算機上播放。雖然wav格式音頻檔案較占用存儲空間,但其是無損的,音質在相同碼率下遠好于mp3等有損壓縮音頻檔案。

Wav_GCC.rar,GCC下wav音頻檔案播放與錄制工程,可直接make。

http://pan.baidu.com/s/1c05s2cg

Wav_MDK.rar,MDK下wav音頻檔案播放與錄制工程

http://pan.baidu.com/s/1i33guiD

test.wav,wav播放測試音頻檔案,11.025k采樣率、16位、單聲道音樂,可通過音頻格式轉換軟體生成wav音頻檔案。

http://pan.baidu.com/s/1eQzOErg

繼續閱讀