前言
wav文件通常会使用PCM格式数据存储音频,这种格式的数据读取出来直接就可以播放,要在wav文件中读取数据,我们首先要获取头部信息,wav的文件结构里面分为多个chunk,我们要做的就是识别这些chunk的信息,获取音频的格式以及数据。
一、如何实现?
首先需要构造wav头部,wav文件音频信息全部保存在头部,我们要做的就是读取wav头部信息,并且记录PCM的相关参数。
1.定义头结构
只定义PCM格式的wav文件头,对于PCM格式的数据只需要下面3个结构体即可。
1
2
3
|
struct WaveRIFF; struct WaveFormat; struct WaveData; |
2.读取头部信息
打开文件后需要读取头部信息,需要获取声音的格式以及数据长度。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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.读取数据
获取头部信息后,就知道数据在位置及长度了,只需要直接读文件即可。
1
2
|
//跳到数据起始位置 seek(f, _dataOffset, SEEK_SET); |
1
2
|
//读取数据 fread (buf, 1, bufLength, f); |
二、完整代码
完整代码总用有3部分,头结构、WavFileReader.h、WavFileReader.cpp。
1.头结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
#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、播放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#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、循环播放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#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,就可以实现这样一个功能。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/u013113678/article/details/122388555