2017-07-09 42 views
0

所以我从来没有真正使用过二进制文件,而我刚刚接触C++。我想读取一个wav文件并将其数据部分输出到txt中(用逗号分隔每个样本的值)。我也设法阅读标题部分,但这段代码在这里并不重要,所以我不会包含它。C++ RIFF WAVE阅读器很痛苦

我的wav文件以32bps的速度存储IEEE 754标准的数据(浮点数)。我首先将整个wav文件读入char向量,然后尝试使用它。该程序的输出是我期望的结果,我可以通过阅读txt而无任何问题地播放Python中的声音。该程序的速度非常慢(需要几分钟才能完成一个长达几秒的wav文件)。

这是wavReader.cpp

#include "stdafx.h" 
#include "wavFile.h" 
#include <fstream> 
#include <iostream> 
#include <vector> 

int main() 
{ 
    std::ifstream file("file.wav", std::ios::binary); 
    std::vector<char> buffer((
     std::istreambuf_iterator<char>(file)), 
     (std::istreambuf_iterator<char>())); 
    std::cout << "Loading complete!\n"; 

    WavFile wavFile = setWavFile(buffer); 

    return 0; 
} 

这是wavFile.h

#pragma once 
#include <iostream> 
#include <vector> 

struct WavFile 
{ 
    uint32_t dataSize; 
}; 

WavFile setWavFile(std::vector<char> buffer); 
uint32_t getUint32(std::vector<char> buffer, std::vector<char>::iterator it); 

这是wavFile.cpp

#include "stdafx.h" 
#include "WavFile.h" 
#include <fstream> 

WavFile setWavFile(std::vector<char> buffer) { 

    WavFile wavFile; 
    std::vector<char>::iterator it = buffer.begin(); 

    // Beginning of data chunk is marked with "data" 
    it += 4; 
    while (*(it - 4) != 'd' || 
     *(it - 3) != 'a' || 
     *(it - 2) != 't' || 
     *(it - 1) != 'a') 
     it++; 

    wavFile.dataSize = getUint32(buffer, it), it += 4; 
    std::ofstream output("data.txt"); 

    while (it != buffer.end()) 
    { 
     char outputChar[4]; 
     for (int i = 0; i < 4; (i++, it++)) 
      outputChar[i] = *it; 
     char* outputStr = outputChar; 
     char** outputStrPtr = &outputStr; 
     float** outputPtr = reinterpret_cast<float**>(outputStrPtr); 
     output << **outputPtr << ", "; 
     std::cout << static_cast<double>(std::distance(buffer.begin(), it)) * 100/wavFile.dataSize << "\%\n"; 
    } 

    return wavFile; 
} 

uint32_t getUint32(std::vector<char> buffer, std::vector<char>::iterator it) 
{ 
    char outputChar[4]; 
    for (int i = 0; i < 4; (i++, it++)) 
     outputChar[i] = *it; 
    char* outputStr = outputChar; 
    char** outputStrPtr = &outputStr; 
    uint32_t** outputPtr = reinterpret_cast<uint32_t**>(outputStrPtr); 
    return **outputPtr; 
} 

我制作的节目打印进展控制台。请注意,这只适用于具有一个通道的wav文件,并将样本存储在IEEE 754标准中。你可以找到我使用的文件here。我只是一个业余爱好程序员,所以请原谅我,我不知道是什么让我的程序变得很慢......是矢量迭代吗?或者它是与reinterpret_cast有点乱的变量声明?

+0

其实你为什么不用python读它?将音频值转换为文本然后再转换为值似乎是一种矫枉过正。 – VTT

+0

@VTT这不是一些实用的用途,而是我为了理解WAVE文件的文件结构以及如何将二进制文件转换为通常可读的文件而进行的练习。 – Keno

回答

0

也许印刷的进度会降低印刷速度?你打印很多。也许你可以尝试仅在百分比的整数值变化时尝试打印,例如:

int lastPercent = -1; 

loop { 
    ... 
    float percent = ...; 
    int integralPercent = (int)percent; 
    if (integralPercent!=lastPercent) { 
    lastPercent = integralPercent; 
    // print percent here 
    } 
} 
+0

哦,现在,这是尴尬。我添加了进度条,因为程序一开始很慢,我想知道它需要多长时间。现在我意识到当我添加进度条时我仍然使用调试版本,并且当时使用了更大的波形文件。完全移除进度条并切换到发布版本解决了问题...谢谢! – Keno

+0

不客气:)只是一个额外的提示:你的代码'reinterpret_cast'不太好。改用'memcpy'。如果你的矢量有'char * ptr',那么float value; memcpy(&value,ptr,4);'做得更好。 – geza

0

你正在读它完全错误的方式。 Wave文件具有RIFF格式。每个文件由RIFF文件头和一系列块组成。

#include <Windows.h> // for DWORD 
#include <MMReg.h> // for PCMWAVEFORMATPCMWAVEFORMAT and FORCC 

struct t_RiffFileHeader 
{ 
    ::FOURCC m_riff;  // must be 'R', 'I', 'F', 'F' 
    ::DWORD m_file_size; // should be less than or equal to the total file zize 
    ::FOURCC m_formtype; // must be 'W', 'A', 'V', 'E' 
}; 
static_assert(12 == sizeof(t_RiffFileHeader), ""); 

因此,您首先阅读此riff标题的12个字节,并验证它是正确的。

size_t remaining_bytes_count(buffer.size()); 
const char * p_cursor(buffer.data()); 
if(remaining_bytes_count <= sizeof(t_RiffFileHeader)) 
{ 
    exit(1); 
} 
const t_RiffFileHeader & riff_header(*reinterpret_cast< const t_RiffFileHeader * >(reinterpret_cast<uintptr_t>(p_cursor))); 
if(static_cast<size_t>(riff_header.m_file_size) < sizeof(t_RiffChunkHeader)) 
{ 
    exit(1); 
} 
p_cursor += sizeof(t_RiffFileHeader); 
remaining_bytes_count -= sizeof(t_RiffFileHeader); 

然后你继续阅读块。

struct t_RiffChunkHeader 
{ 
    ::FOURCC m_id;   
    ::DWORD m_chunk_content_size; 
}; 
static_assert(8 == sizeof(t_RiffFileHeader), ""); 

你读块头部,然后根据块ID读取m_chunk_content_size字节的数据:

  • 'f', 'm', 't', ' '块典型地是第一和应包含PCMWAVEFORMAT结构描述数据的wav;
  • 'd', 'a', 't', 'a'块包含音频数据;
  • 其他块可以跳过。

如果你已经完成了最后一个块的读取,但文件结束还没有到达,那么最有可能是附加了另一个RIFF文件。由于32位长度限制,大文件通过连接几个较小的RIFF文件来构建。

+0

我知道这一点。正如问题中所述,我设法读取标题信息(我也意味着我能够读取fmt块)。这就是我从中获得信息的地方,我的声音文件有1个通道,32位数据并以IEEE 754标准存储数据。我只是没有在这里包含这个代码,因为这不是我担心的。在我的示例代码中,我直接跳到数据块并开始读取数据。现在我只想知道为什么这个节目太慢了。 – Keno

+0

@Keno这实际上是错误的,因为你无法仅仅通过查找数据块,而是开始查找数据块,因为它可能出现在数据块之前的自定义块中。至于速度问题,读完每个样本后,不应写入'std :: cout'。每10000个样本写一次报告。 – VTT

+0

谢谢,进度条实际上是问题所在。关于数据块 - 如何在不查找“数据”的情况下找到它的起点?其他块位于fmt块和数据块之间。假设我读取了fmt块中的所有信息,现在我想继续处理数据块(忽略之间的不必要的东西)。如果在fmt块的结尾和数据块的开始之间存在一个附加的“数据”,我该怎么做? – Keno