Windows 读取wav文件字节流并播放

Windows 读取wav文件字节流并播放

使用Windows Wave相关API播放wav文件,实现文件读取进内存,按照一定字节数播放,
对wav文件音频格式进行检测,只能播放48kHz采样率,16bit位深,单通道格式的音频文件。

 * @brief   wav文件读取解析和使用Windows api输出
 * @date    2024-08-02
 * @author  shentujia@qq.com
 */

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include "WAVHeader.h"
#include<Windows.h>
#include <MMSystem.h>
#pragma comment(lib, "winmm.lib")
// 48k 16bit 1channels 音频采样100ms数据大小为9600字节
#define  WAVE_BUFFER_SIZE 9600
using namespace std;

HWAVEOUT hWaveOut; // waveOut设备句柄
WAVEHDR waveOutHdr; // waveOut数据块头

int main()
{
    string audio_file = "rain_48khz_1ch_16bit.wav";
    ifstream fin(audio_file, ios::binary);
    if (!fin) {
        cout << "open file failed!" << endl;
        return 1;
    }
    WAVHeader header;
    //读取wav文件头并保存到header对象中
    fin.read((char*)&header, sizeof(header));
    if (strncmp(header.riff.chunkID, "RIFF", 4) != 0 || strncmp(header.riff.format, "WAVE", 4) != 0
        || strncmp(header.fmt.chunkID, "fmt ", 4) != 0 || strncmp(header.data.chunkID, "data", 4) != 0) {
        cout << "file is not a valid WAV file" << endl;
        return 1;
    }
    //判断音频文件是否为16bit 1channels 采样率为48000的音频文件
    if(header.fmt.numChannels != 1 || header.fmt.bitsPerSample != 16||header.fmt.sampleRate!=48000){
		cout << "only support 8bit 1channels audio file" << endl;
		return 1;
	}

    WAVEFORMATEX waveFormat;
    /*
        WAVEFORMATEX是一种数据结构,用于指定波形音频流的数据格式。它包含以下字段:
        wFormatTag:设置波形声音的格式。
        nChannels:设置音频文件的通道数量,对于单声道的声音,此值为1;对于立体声,此值为2。
        nSamplesPerSec:设置每个声道播放和记录时的样本频率。
        nAvgBytesPerSec:设置每秒平均字节数。
        nBlockAlign:设置数据块的对齐方式,即最小数据的原子大小。
        wBitsPerSample:设置每个样本的位数。
        cbSize:设置此结构的大小。
    */
    waveFormat.wFormatTag = WAVE_FORMAT_PCM;
    waveFormat.nChannels = header.fmt.numChannels;
    waveFormat.nSamplesPerSec = header.fmt.sampleRate;
    waveFormat.nBlockAlign = header.fmt.blickAlign;
    waveFormat.wBitsPerSample = header.fmt.bitsPerSample;
    waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.wBitsPerSample / 8;
    waveFormat.cbSize = 0;
	if (waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat, (DWORD_PTR)0, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
		cout << "无法打开音频设备" << endl;
		return 1;
	}
	char* pcmData = new char[header.data.chunkSize];
	//读取wav文件的pcm数据部分,保存到char 数组中
	fin.read(pcmData, header.data.chunkSize);

	// 计算1毫秒内的样本字节数
	int bytesPerMs = (header.fmt.sampleRate / 1000) * (header.fmt.bitsPerSample / 8) * header.fmt.numChannels;

    WAVEHDR* waveHdr = new WAVEHDR();
    std::vector<char*>char_points;
	for (int i = 0; i < header.data.chunkSize; i += WAVE_BUFFER_SIZE) {
        int buffersize = min(WAVE_BUFFER_SIZE, header.data.chunkSize - i);
        char* perFrameData = new char[WAVE_BUFFER_SIZE];
        char_points.push_back(perFrameData);
		memcpy(perFrameData, pcmData + i, WAVE_BUFFER_SIZE);
		cout << "play " << i << " to " << i + WAVE_BUFFER_SIZE << " bytes" <<",total:" <<header.data.chunkSize<< endl;
        
        waveHdr->lpData = perFrameData;
        waveHdr->dwBufferLength = buffersize;
		waveHdr->dwFlags = 0;
		waveHdr->dwLoops = 0;
		if (waveOutPrepareHeader(hWaveOut, waveHdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
			cout << "无法准备音频数据" << endl;
			break;
		}

		if (waveOutWrite(hWaveOut, waveHdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
			cout << "无法播放音频" << endl;
			waveOutUnprepareHeader(hWaveOut, waveHdr, sizeof(WAVEHDR));
			break;
		}
        Sleep(90);

	}
    getchar();
    fin.close();
    waveOutClose(hWaveOut);
    //使用new创建的对象需要手动delete
    delete waveHdr;
    delete[] pcmData;
	for (auto& p : char_points) {
		delete[] p;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

申徒嘉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值