2017-07-09 112 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