C++讀取wav文件中的PCM數據
前言
wav文件通常會使用PCM格式數據存儲音頻,這種格式的數據讀取出來直接就可以播放,要在wav文件中讀取數據,我們首先要獲取頭部信息,wav的文件結構裡面分為多個chunk,我們要做的就是識別這些chunk的信息,獲取音頻的格式以及數據。
一、如何實現?
首先需要構造wav頭部,wav文件音頻信息全部保存在頭部,我們要做的就是讀取wav頭部信息,並且記錄PCM的相關參數。
1.定義頭結構
隻定義PCM格式的wav文件頭,對於PCM格式的數據隻需要下面3個結構體即可。
struct WaveRIFF; struct WaveFormat; struct WaveData;
2.讀取頭部信息
打開文件後需要讀取頭部信息,需要獲取聲音的格式以及數據長度。
WaveRIFF riff; WaveFormat format; WaveData data; int userDataSize; f= fopen(fileName.c_str(), "rb+"); //讀取頭部信息 fread(&riff, 1, sizeof(riff), f); fread(&format, 1, sizeof(format),f); //判讀頭部信息是否正確 //略 //查找data chunk //略 //記錄數據起始位置
3.讀取數據
獲取頭部信息後,就知道數據在位置及長度瞭,隻需要直接讀文件即可。
//跳到數據起始位置 seek(f, _dataOffset, SEEK_SET);
//讀取數據 fread(buf, 1, bufLength, f);
二、完整代碼
完整代碼總用有3部分,頭結構、WavFileReader.h、WavFileReader.cpp。
1.頭結構
#pragma pack(push,1) struct WaveRIFF { const char id[4] = { 'R','I', 'F', 'F' }; uint32_t fileLength; const char waveFlag[4] = { 'W','A', 'V', 'E' }; }; struct WaveFormat { const char id[4] = { 'f','m', 't', ' ' }; uint32_t blockSize = 16; uint16_t formatTag; uint16_t channels; uint32_t samplesPerSec; uint32_t avgBytesPerSec; uint16_t blockAlign; uint16_t bitsPerSample; }; struct WaveData { const char id[4] = { 'd','a', 't', 'a' }; uint32_t dataLength; }; #pragma pack(pop)
2.WavFileReader.h
#pragma once #include<string> /************************************************************************ * @Project: AC.WavFileWriter * @Decription: wav文件讀取工具 * 本版本隻支持pcm讀取,且未處理字節順序。 riff文件是小端,通常在intel的設備上是沒問題的,在java虛擬機上則需要處理。 * @Verision: v1.0.0.0 * @Author: Xin Nie * @Create: 2019/4/10 11:10:17 * @LastUpdate: 2019/4/16 10:45:00 ************************************************************************ * Copyright @ 2019. All rights reserved. ************************************************************************/ namespace AC { /// <summary> /// wav文件讀取對象 /// </summary> class WavFileReader { public: /// <summary> /// 構造方法 /// </summary> WavFileReader(); /// <summary> /// 析構方法 /// </summary> ~WavFileReader(); /// <summary> /// 打開wav文件 /// </summary> /// <param name="fileName">文件名</param> /// <returns>是否打開成功</returns> bool OpenWavFile(const std::string& fileName); /// <summary> /// 關閉文件 /// </summary> void CloseFlie(); /// <summary> /// 讀取音頻數據 /// </summary> /// <param name="buf">外部緩存</param> /// <param name="bufLength">緩存長度</param> /// <returns>讀取長度</returns> int ReadData(unsigned char* buf, int bufLength); /// <summary> /// 設置讀取位置 /// </summary> /// <param name="position"> 讀取位置</param> void SetPosition(int position); /// <summary> /// 獲取讀取位置 /// </summary> /// <returns>讀取位置</returns> int GetPosition(); /// <summary> /// 獲取文件長度 /// </summary> /// <returns>文件長度</returns> int GetFileLength(); /// <summary> /// 獲取音頻數據長度 /// </summary> /// <returns>音頻數據長度</returns> int GetDataLength(); /// <summary> /// 獲取聲道數 /// </summary> /// <returns>聲道數</returns> int GetChannels(); /// <summary> /// 獲取采樣率 /// </summary> /// <returns>采樣率,單位:hz</returns> int GetSampleRate(); /// <summary> /// 獲取位深 /// </summary> /// <returns>位深,單位:bits</returns> int GetBitsPerSample(); private: void* _file = nullptr; uint32_t _fileLength = 0; uint32_t _dataLength = 0; int _channels = 0; int _sampleRate = 0; int _bitsPerSample = 0; int _dataOffset = 0; }; }
3.WavFileReader.cpp
#include"WavFileReader.h" namespace AC { WavFileReader::WavFileReader() { } WavFileReader::~WavFileReader() { CloseFlie(); } bool WavFileReader::OpenWavFile(const std::string& fileName) { if (_file) { printf("已經打開瞭文件!\n"); return false; } WaveRIFF riff; WaveFormat format; WaveData data; int userDataSize; _file = fopen(fileName.c_str(), "rb+"); if (!_file) { printf("打開文件失敗!\n"); return false; } //讀取頭部信息 if (fread(&riff, 1, sizeof(riff), static_cast<FILE*>(_file)) != sizeof(riff)) { printf("文件讀取錯誤,讀取riff失敗!\n"); goto error; } if (std::string(riff.id, 4) != "RIFF" || std::string(riff.waveFlag, 4) != "WAVE") { printf("頭部信息不正確,不是wav文件!\n"); goto error; } if (fread(&format, 1, sizeof(format), static_cast<FILE*>(_file)) != sizeof(format)) { printf("文件讀取錯誤,讀取format失敗!\n"); goto error; } if (std::string(format.id, 4) != "fmt ") { printf("頭部信息不正確,缺少fmt!\n"); goto error; } if (format.formatTag != 1) { printf("程序不支持,數據格式非pcm,隻支持pcm格式的數據!\n"); goto error; } userDataSize = format.blockSize - sizeof(format) + 8; if (userDataSize < 0) { printf("頭部信息不正確,blockSize大小異常!\n"); goto error; } else if (userDataSize > 0) { if (fseek(static_cast<FILE*>(_file), userDataSize, SEEK_CUR) != 0) { printf("文件讀取錯誤!\n"); goto error; } } while (1) { if (fread(&data, 1, sizeof(data), static_cast<FILE*>(_file)) != sizeof(data)) { printf("文件讀取錯誤!\n"); goto error; }; if (std::string(data.id, 4) != "data") { if (fseek(static_cast<FILE*>(_file), data.dataLength, SEEK_CUR) != 0) { printf("文件讀取錯誤!\n"); goto error; } continue; } break; } _dataOffset = ftell(static_cast<FILE*>(_file)); _fileLength = riff.fileLength+8; _dataLength = data.dataLength; _channels = format.channels; _sampleRate = format.samplesPerSec; _bitsPerSample = format.bitsPerSample; return true; error: if (fclose(static_cast<FILE*>(_file)) == EOF) { printf("文件關閉失敗!\n"); } _file = nullptr; return false; } void WavFileReader::CloseFlie() { if (_file) { if (fclose(static_cast<FILE*>(_file)) == EOF) { printf("文件關閉失敗!\n"); } _file = nullptr; } } int WavFileReader::ReadData(unsigned char* buf, int bufLength) { if (ftell(static_cast<FILE*>(_file)) >= _dataOffset + _dataLength) return 0; return fread(buf, 1, bufLength, static_cast<FILE*>(_file)); } void WavFileReader::SetPosition(int postion) { if (fseek(static_cast<FILE*>(_file), _dataOffset + postion, SEEK_SET) != 0) { printf("定位失敗!\n"); } } int WavFileReader::GetPosition() { return ftell(static_cast<FILE*>(_file)) - _dataOffset; } int WavFileReader::GetFileLength() { return _fileLength; } int WavFileReader::GetDataLength() { return _dataLength; } int WavFileReader::GetChannels() { return _channels; } int WavFileReader::GetSampleRate() { return _sampleRate; } int WavFileReader::GetBitsPerSample() { return _bitsPerSample; } }
三、使用示例
1、播放
#include "WavFileReader.h" int main(int argc, char** argv) { AC::WavFileReader read; unsigned char buf[1024]; if (read.OpenWavFile("test_music.wav")) { int channels, sampleRate, bitsPerSample; //獲取聲音格式 channels = read.GetChannels(); sampleRate = read.GetSampleRate(); bitsPerSample = read.GetBitsPerSample(); //打開聲音設備(channels,sampleRate,bitsPerSample) int size; do { //讀取音頻數據 size = read.ReadData(buf,1024); if (size > 0) { //播放(buf,1024) } } while (size); read.CloseFlie(); } return 0; }
2、循環播放
#include "WavFileReader.h" int main(int argc, char** argv) { AC::WavFileReader read; unsigned char buf[1024]; bool exitFlag = false; if (read.OpenWavFile("test_music.wav")) { int channels, sampleRate, bitsPerSample; //獲取聲音格式 channels = read.GetChannels(); sampleRate = read.GetSampleRate(); bitsPerSample = read.GetBitsPerSample(); //打開聲音設備(channels,sampleRate,bitsPerSample) int size; while (!exitFlag) { //讀取音頻數據 size = read.ReadData(buf, 1024); if (size > 0) { //播放(buf,1024) } else { //回到數據起始位置 read.SetPosition(0); } } read.CloseFlie(); } return 0; }
總結
以上就是今天要講的內容,wav文件中讀取PCM還是相對較簡單的,隻要瞭解wav頭結構,然後自定義其頭結構,讀取頭部信息,校驗頭部信息,然後再讀取數據所在的chunk,就可以實現這樣一個功能。
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。