C++將音頻PCM數(shù)據(jù)封裝成wav文件的方法
前言
使用聲音設(shè)備采集的聲音數(shù)據(jù)通常是PCM數(shù)據(jù),直接寫入文件是無法播放的,通常的做法是將其封裝成wav格式,這樣播放器就能夠識別且播放了。本文將介紹如何將PCM封裝成wav的方法。
一、如何實現(xiàn)?
首先需要構(gòu)造wav頭部,wav文件音頻信息全部保存在頭部,我們要做的就是在PCM數(shù)據(jù)的前面加入wav頭,并且記錄PCM的相關(guān)參數(shù)。
1.定義頭結(jié)構(gòu)
只定義PCM格式的wav文件頭
//WAV頭部結(jié)構(gòu)-PCM格式 struct WavPCMFileHeader;
2.預(yù)留頭部空間
創(chuàng)建文件時預(yù)留頭部空間
FILE*f = fopen(fileName.c_str(), "wb+"); //預(yù)留頭部位置 fseek(f, sizeof(WavPCMFileHeader), SEEK_SET);
3.寫入PCM數(shù)據(jù)
寫入數(shù)據(jù),并記錄數(shù)據(jù)總長度。
fwrite(data, 1, dataLength, f); //錄數(shù)據(jù)總長度 _totalDataLength += dataLength;
4.寫入頭部信息
關(guān)閉文件時,回到起始位置寫入頭部信息
//寫入頭部信息 fseek(f, 0, SEEK_SET); WavPCMFileHeader h(_channels, _sampleRate, _bitsPerSample, _totalDataLength); fwrite(&h, 1, sizeof(h), f); fclose(f);
二、完整代碼
WavWapper.h
#pragma once #include<string> namespace AC { ?? ?class ?WavWapper { ?? ?public: ?? ??? ?WavWapper(); ?? ??? ?~WavWapper(); ?? ??? ?/// <summary> ?? ??? ?/// 創(chuàng)建wav文件 ?? ??? ?/// </summary> ?? ??? ?/// <param name="fileName">文件名</param> ?? ??? ?/// <param name="channels">聲道數(shù)</param> ?? ??? ?/// <param name="sampleRate">采樣率,單位hz</param> ?? ??? ?/// <param name="bitsPerSample">位深</param> ?? ??? ?void CreateWavFile(const std::string &fileName, int channels, int ?sampleRate, int ?bitsPerSample); ?? ??? ?/// <summary> ?? ??? ?/// 寫入PCM數(shù)據(jù) ?? ??? ?/// </summary> ?? ??? ?/// <param name="data">PCM數(shù)據(jù)</param> ?? ??? ?/// <param name="dataLength">數(shù)據(jù)長度</param> ?? ??? ?void WriteToFile(unsigned char* data, int dataLength); ?? ??? ?/// <summary> ?? ??? ?/// 關(guān)閉文件 ?? ??? ?/// </summary> ?? ??? ?void CloseFile(); ?? ?private: ?? ??? ?void* _file=nullptr; ?? ??? ?uint32_t _totalDataLength=0; ?? ?}; }
WavWapper.cpp
#include"WavWapper.h" #include<stdio.h> namespace AC { ?? ?//WAV頭部結(jié)構(gòu)-PCM格式 ?? ?struct WavPCMFileHeader ?? ?{ ?? ??? ?struct RIFF { ?? ??? ??? ?const?? ?char rift[4] = { 'R','I', 'F', 'F' }; ?? ??? ??? ?uint32_t fileLength; ?? ??? ??? ?const?? ?char wave[4] = { 'W','A', 'V', 'E' }; ?? ??? ?}riff; ?? ??? ?struct Format ?? ??? ?{ ?? ??? ??? ?const?? ?char fmt[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; ?? ??? ?}format; ?? ??? ?struct ?Data ?? ??? ?{ ?? ??? ??? ?const?? ?char data[4] = { 'd','a', 't', 'a' }; ?? ??? ??? ?uint32_t dataLength; ?? ??? ?}data; ?? ??? ?WavPCMFileHeader() {} ?? ??? ?WavPCMFileHeader(int nCh, int ?nSampleRate, int ?bitsPerSample, int dataSize) { ?? ??? ??? ?riff.fileLength = 36 + dataSize; ?? ??? ??? ?format.formatTag = 1; ?? ??? ??? ?format.channels = nCh; ?? ??? ??? ?format.samplesPerSec = nSampleRate; ?? ??? ??? ?format.avgBytesPerSec = nSampleRate * nCh * bitsPerSample / 8; ?? ??? ??? ?format.blockAlign = nCh * bitsPerSample / 8; ?? ??? ??? ?format.bitsPerSample = bitsPerSample; ?? ??? ??? ?data.dataLength = dataSize; ?? ??? ?} ?? ?}; ?? ?WavWapper::WavWapper() ?? ?{ ?? ?} ?? ?WavWapper::~WavWapper() ?? ?{ ?? ??? ?CloseFile(); ?? ?} ?? ?int _channels; ?? ?int _sampleRate; ?? ?int _bitsPerSample; ?? ?void WavWapper::CreateWavFile(const std::string& fileName, int channels, int sampleRate, int bitsPerSample) ?? ?{ ?? ??? ?if (!_file) ?? ??? ?{ ?? ??? ??? ?_channels = channels; ?? ??? ??? ?_sampleRate = sampleRate; ?? ??? ??? ?_bitsPerSample = bitsPerSample; ?? ??? ??? ?_totalDataLength = 0; ?? ??? ??? ?_file = fopen(fileName.c_str(), "wb+"); ?? ??? ??? ?//預(yù)留頭部位置 ?? ??? ??? ?fseek(static_cast<FILE*>(_file), sizeof(WavPCMFileHeader), SEEK_SET); ?? ??? ?} ?? ?} ?? ?void WavWapper::WriteToFile(unsigned char* data, int dataLength) ?? ?{ ?? ??? ?fwrite(data, 1, dataLength, static_cast<FILE*>(_file)); ?? ??? ?_totalDataLength += dataLength; ?? ?} ?? ?void WavWapper::CloseFile() ?? ?{ ?? ??? ?if (_file) ?? ??? ?{ ?? ??? ??? ?if (_totalDataLength > 0) ?? ??? ??? ?{ ?? ??? ??? ??? ?//寫入頭部信息 ?? ??? ??? ??? ?fseek(static_cast<FILE*>(_file), 0, SEEK_SET); ?? ??? ??? ??? ?WavPCMFileHeader h(_channels, _sampleRate, _bitsPerSample, _totalDataLength); ?? ??? ??? ??? ?fwrite(&h, 1, sizeof(h), static_cast<FILE*>(_file)); ?? ??? ??? ?} ?? ??? ??? ?fclose(static_cast<FILE*>(_file)); ?? ??? ??? ?_file = nullptr; ?? ??? ?} ?? ?} }
三、使用示例
#include "WavWapper.h" #include<Windows.h> int main() {?? ? ?? ?AC::WavWapper ww; ?? ?//創(chuàng)建wav文件,確保pcm聲音格式與參數(shù)一致 ?? ?ww.CreateWavFile("sound.wav",2, 44100, 16); ?? ?while (flag) ?? ?{ ?? ??? ?//獲取PCM數(shù)據(jù) ?? ??? ?//略 ?? ??? ?//獲取PCM數(shù)據(jù)-end ?? ? ? ?//寫入PCM數(shù)據(jù) ?? ??? ?ww.WriteToFile(data, dataLength); ?? ?} ?? ?//關(guān)閉文件 ?? ?ww.CloseFile();?? ? }
總結(jié)
以上就是今天要講的內(nèi)容,PCM封裝成wav還是相對較簡單的,只要了解wav頭結(jié)構(gòu),然后自定義其頭結(jié)構(gòu),然后再進(jìn)行一定的測試,就可以實現(xiàn)這樣一個功能。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C語言進(jìn)階之字符串查找?guī)旌瘮?shù)詳解
字符串是一種非常重要的數(shù)據(jù)類型,但是C語言不存在顯式的字符串類型,C語言中的字符串都以字符串常量的形式出現(xiàn)或存儲在字符數(shù)組中,下面這篇文章主要給大家介紹了關(guān)于C語言進(jìn)階之字符串查找?guī)旌瘮?shù)的相關(guān)資料,需要的朋友可以參考下2023-01-01C語言 詳細(xì)講解接續(xù)符和轉(zhuǎn)義符的使用
接續(xù)符是用來告訴編譯器行為的符號,那編譯器遇到接續(xù)符是什么行為呢,就是去掉接續(xù)符,然后把下一行連接到現(xiàn)在這行上面,轉(zhuǎn)義符是主要用于表示無回顯字符,也用于表示常規(guī)字符,轉(zhuǎn)義符必須放在單引號或者雙引號里面2022-04-04