首頁 > 軟體

C++解析wav檔案方法介紹

2022-09-05 14:01:44

一、前言

一開始本來在網上找程式碼,不過改了好幾個都不是很好用。因為很多wav檔案的fmt塊後面並不是data塊,經常還帶有其他塊,正確的方法應該是按MSDN的方法,找到data塊再讀取。

二、介面

最後介面如下:

class AudioReader
{
public:
	struct PCM
	{
		int _numChannel;//通道數 1,2 AL_FORMAT_MONO8,AL_FORMAT_STEREO8
		int _bitPerSample;//取樣數 8,16 
		byte* _data;
		size_t _size;
		size_t _freq;//取樣率
		void Delete() { delete[] _data; }
	}; 
	static bool ReadWAV(string_view path_name, PCM& pcm);
};

三、具體步驟

開啟檔案,這裡就是普通的檔案流,按二進位制、唯讀開啟檔案即可:

ifstream ifs;
if (!g_file->GetFile(path_name, ifs))
{
	debug_err(format("開啟檔案失敗:{}", path_name));
	return false;
}

查詢riff塊:

uint32_t dwChunkSize;
uint32_t dwChunkPosition;
//查詢riff塊
FindChunk(ifs, fourccRIFF, dwChunkSize, dwChunkPosition);
uint32_t filetype;
ReadChunkData(ifs, &filetype, sizeof(uint32_t), dwChunkPosition);
if (filetype != fourccWAVE)
{
	debug_err(format("匹配標記失敗(fourccWAVE):{}", path_name));
	return false;
}

其中fourccRIFF和fourccWAVE是我們定義的標記,也就是處理了下大小端,如下:

#ifdef DND_ENDIAN_BIG
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'
#endif
#ifdef DND_ENDIAN_LITTLE
#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'
#endif

而FindChunk和ReadChunkData兩個函數,分別是查詢一個塊,和讀取一個塊。程式碼實現有點長,可以參考後面我給出的完整原始碼。

接著,查詢並讀取fmt塊,這個塊描述了wav檔案的音訊屬性,結構如下(部分欄位會用到):

//16位元組
struct WAVEFormat 
{
	int16_t audioFormat;
	int16_t numChannels;
	int32_t sampleRate;
	int32_t byteRate;
	int16_t blockAlign;
	int16_t bitsPerSample;
};
//查詢fmt塊
if (!FindChunk(ifs, fourccFMT, dwChunkSize, dwChunkPosition))
{
	debug_err(format("查詢塊失敗(fourccFMT):{}", path_name));
	return false;
}
//讀wave資訊
WAVEFormat wave_format;
if (!ReadChunkData(ifs, &wave_format, dwChunkSize, dwChunkPosition))
{
	debug_err(format("讀取塊失敗(wave_format):{}", path_name));
	return false;
};

接下來查詢data塊,根據返回的大小分配記憶體:

//查詢音訊資料
if (!FindChunk(ifs, fourccDATA, dwChunkSize, dwChunkPosition))
{
	debug_err(format("查詢塊失敗(fourccDATA):{}", path_name));
	return false;
};
pcm._data = new byte[dwChunkSize];

然後讀取data塊,將資料讀取到我們分配的記憶體pcm._data。然後記錄下一些重要的欄位。由於OpenaAL不能直接播放32位元(只8、16)的資料,這裡簡單返回失敗。

if (!ReadChunkData(ifs, pcm._data, dwChunkSize, dwChunkPosition))
{
	debug_err(format("讀取塊失敗(pcm資料):{}", path_name));
	pcm.Delete();
	return false;
};
pcm._size = dwChunkSize;
pcm._numChannel = wave_format.numChannels;
pcm._bitPerSample = wave_format.bitsPerSample;
pcm._freq = wave_format.sampleRate;
if (pcm._bitPerSample == 32)
{
	debug_err(format("不支援32位元:{}", path_name));
	pcm.Delete();
	return false;
}
return true;

四、完整原始碼

可以此處獲取最新的原始碼(我將來會新增ogg格式的解析),也可以用下面的:傳送門

//.h
class AudioReader
{
public:
	struct PCM
	{
		int _numChannel;//通道數 1,2 AL_FORMAT_MONO8,AL_FORMAT_STEREO8
		int _bitPerSample;//取樣數 8,16 
		byte* _data;
		size_t _size;
		size_t _freq;//取樣率
		void Delete() { delete[] _data; }
	}; 
	static bool ReadWAV(string_view path_name, PCM& pcm);
};
//16位元組
struct WAVEFormat 
{
	int16_t audioFormat;
	int16_t numChannels;
	int32_t sampleRate;
	int32_t byteRate;
	int16_t blockAlign;
	int16_t bitsPerSample;
};
//.cpp
#ifdef DND_ENDIAN_BIG
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'
#endif
#ifdef DND_ENDIAN_LITTLE
#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'
#endif
bool FindChunk(ifstream& ifs, uint32_t fourcc, uint32_t& size, uint32_t& pos)
{
	bool ret = true;
	ifs.seekg(0);
	if (ifs.fail())
		return false;
	uint32_t dwChunkType;
	uint32_t dwChunkDataSize;
	uint32_t dwRIFFDataSize = 0;
	uint32_t dwFileType;
	uint32_t bytesRead = 0;
	uint32_t dwOffset = 0;
	while (ret)
	{
		ifs.read((char*)&dwChunkType, sizeof(uint32_t));
		ifs.read((char*)&dwChunkDataSize, sizeof(uint32_t));
		switch (dwChunkType)
		{
		case fourccRIFF:
			dwRIFFDataSize = dwChunkDataSize;
			dwChunkDataSize = 4;
			ifs.read((char*)&dwFileType, sizeof(uint32_t));
			break;
		default:
			ifs.seekg(dwChunkDataSize, std::ios::cur);
			if (ifs.fail())
				return false;
			break;
		}
		dwOffset += sizeof(uint32_t) * 2;
		if (dwChunkType == fourcc)
		{
			size = dwChunkDataSize;
			pos = dwOffset;
			return true;
		}
		dwOffset += dwChunkDataSize;
		if (bytesRead >= dwRIFFDataSize)
			return false;
	}
	return true;
}
bool ReadChunkData(ifstream& ifs, void* buffer, uint32_t size, uint32_t pos)
{
	ifs.seekg(pos);
	if (ifs.fail())
		return false;
	ifs.read((char*)buffer, size);
	return true;
}
bool AudioReader::ReadWAV(string_view path_name, PCM& pcm)
{
	ifstream ifs;
	if (!g_file->GetFile(path_name, ifs))
	{
		debug_err(format("開啟檔案失敗:{}", path_name));
		return false;
	}
	uint32_t dwChunkSize;
	uint32_t dwChunkPosition;
	//查詢riff塊
	FindChunk(ifs, fourccRIFF, dwChunkSize, dwChunkPosition);
	uint32_t filetype;
	ReadChunkData(ifs, &filetype, sizeof(uint32_t), dwChunkPosition);
	if (filetype != fourccWAVE)
	{
		debug_err(format("匹配標記失敗(fourccWAVE):{}", path_name));
		return false;
	}
	//查詢fmt塊
	if (!FindChunk(ifs, fourccFMT, dwChunkSize, dwChunkPosition))
	{
		debug_err(format("查詢塊失敗(fourccFMT):{}", path_name));
		return false;
	}
	//讀wave資訊
	WAVEFormat wave_format;
	if (!ReadChunkData(ifs, &wave_format, dwChunkSize, dwChunkPosition))
	{
		debug_err(format("讀取塊失敗(wave_format):{}", path_name));
		return false;
	};
	//查詢音訊資料
	if (!FindChunk(ifs, fourccDATA, dwChunkSize, dwChunkPosition))
	{
		debug_err(format("查詢塊失敗(fourccDATA):{}", path_name));
		return false;
	};
	pcm._data = new byte[dwChunkSize];
	if (!ReadChunkData(ifs, pcm._data, dwChunkSize, dwChunkPosition))
	{
		debug_err(format("讀取塊失敗(pcm資料):{}", path_name));
		pcm.Delete();
		return false;
	};
	pcm._size = dwChunkSize;
	pcm._numChannel = wave_format.numChannels;
	pcm._bitPerSample = wave_format.bitsPerSample;
	pcm._freq = wave_format.sampleRate;
	if (pcm._bitPerSample == 32)
	{
		debug_err(format("不支援32位元:{}", path_name));
		pcm.Delete();
		return false;
	}
	return true;
}

到此這篇關於C++解析wav檔案方法介紹的文章就介紹到這了,更多相關C++解析wav內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com