服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++读取wav文件中的PCM数据

C++读取wav文件中的PCM数据

2022-08-20 12:18Alfred-N C/C++

这篇文章主要为大家详细介绍了C++读取wav文件中的PCM数据,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

前言

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

延伸 · 阅读

精彩推荐
  • C/C++C语言求向量和的两则问题解答分享

    C语言求向量和的两则问题解答分享

    这篇文章主要介绍了C语言求向量和的两则问题解答分享,分别是求连续子向量的最大和和任何连续最接近0的子向量的和的问题,需要的朋友可以参考下...

    cqnuztq7892021-03-30
  • C/C++C语言中字符和字符串处理(ANSI字符和Unicode字符)

    C语言中字符和字符串处理(ANSI字符和Unicode字符)

    这篇文章主要介绍了C语言与C++中字符和字符串处理(ANSI字符和Unicode字符)的详细内容,非常的全面,这里推荐给大家,希望大家能够喜欢。...

    C语言教程网10632021-02-23
  • C/C++一篇文章带你实现C语言中常用库函数的模拟

    一篇文章带你实现C语言中常用库函数的模拟

    这篇文章主要介绍了C语言中常用库函数的模拟,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以...

    08-0811612022-01-11
  • C/C++二分查找算法在C/C++程序中的应用示例

    二分查找算法在C/C++程序中的应用示例

    这篇文章主要介绍了二分查找算法在C/C++程序中的使用示例,文中最后提到了使用二分查找法一个需要注意的地方,需要的朋友可以参考下...

    wuzhekai19855232021-03-27
  • C/C++C语言程序设计谭浩强第五版课后答案(第三章习题答案)

    C语言程序设计谭浩强第五版课后答案(第三章习题答案)

    这篇文章主要介绍了C语言程序设计谭浩强第五版课后答案(第三章习题答案),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来...

    月已满西楼11842021-10-29
  • C/C++C语言实现个税计算器

    C语言实现个税计算器

    这篇文章主要为大家详细介绍了C语言实现个税计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    朝朝暮暮柳十岁4242022-01-25
  • C/C++include包含头文件的语句中,双引号和尖括号的区别(详解)

    include包含头文件的语句中,双引号和尖括号的区别(详解)

    下面小编就为大家带来一篇include包含头文件的语句中,双引号和尖括号的区别(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随...

    C语言教程网9052021-05-23
  • C/C++C++之boost::array的用法

    C++之boost::array的用法

    这篇文章主要介绍了C++之boost::array的用法,以实例的形式简单讲述了静态数组的容器boost::array的使用技巧,具有一定的参考借鉴价值,需要的朋友可以参考下...

    C++教程网11182021-02-18