使用 BinaryReader 解析 Wave 文件

Posted

技术标签:

【中文标题】使用 BinaryReader 解析 Wave 文件【英文标题】:Parsing Through Wave File with BinaryReader 【发布时间】:2011-05-12 19:28:43 【问题描述】:

在 .NET Assembly mscorlib System.IO 命名空间中,我使用 ReadInt16() 方法循环音频数据字节并将有符号整数值转储到文本文件中。如何解释与一个采样率相关的两个值?也就是说,如果我有一秒钟的单声道数据将有 88200 个字节,因此使用 ReadInt16() 返回 88200 个离散整数。这信息太多了,我应该只有 44100 个整数。所以我是否需要使用不同的方法,或者每次迭代将循环提前 1 次。

非常感谢........米奇

【问题讨论】:

【参考方案1】:
using System;
using System.IO;

public struct WaveFormat


    private short m_FormatTag;       // most often PCM = 1      
    private short m_nChannels;       // number of channels      
    private int m_SamplesPerSecond;  // samples per second eg 44100     
    private int m_AvgBytesPerSecond; // bytes per second eg 176000      
    private short m_BlockAlign;      // blockalign (byte per sample) eg 4 bytes         
    private short m_BitsPerSample;   // bits per sample, 8, 16, 24

    public WaveFormat(byte BPS, int SPS, byte nChn)
    
        m_FormatTag = 1; //PCM
        m_nChannels = nChn;
        m_SamplesPerSecond = SPS;
        m_BitsPerSample = BPS;
        m_BlockAlign = (short)(m_nChannels * m_BitsPerSample / 8);
        m_AvgBytesPerSecond = (int)(m_BlockAlign * m_SamplesPerSecond);
    
    public short FormatTag
    
        get  return m_FormatTag; 
        set  m_FormatTag = value; 
    
    public short Channels
    
        get  return m_nChannels; 
    
    public int SamplesPerSecond
    
        get  return m_SamplesPerSecond; 
    
    public int AvgBytesPerSecond
    
        get  return m_AvgBytesPerSecond; 
    
    public short BlockAlign
    
        get  return m_BlockAlign; 
    
    public short BitsPerSample
    
        get  return m_BitsPerSample; 
    
    public void Read(BinaryReader br)
    
        m_FormatTag = br.ReadInt16();
        m_nChannels = br.ReadInt16();
        m_SamplesPerSecond = br.ReadInt32();
        m_AvgBytesPerSecond = br.ReadInt32();
        m_BlockAlign = br.ReadInt16();
        m_BitsPerSample = br.ReadInt16();
    
    public void Write(BinaryWriter bw)
    
        bw.Write(m_FormatTag);
        bw.Write(m_nChannels);
        bw.Write(m_SamplesPerSecond);
        bw.Write(m_AvgBytesPerSecond);
        bw.Write(m_BlockAlign);
        bw.Write(m_BitsPerSample);
    
    public override string ToString()
    
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        sb.AppendLine("FormatTag:         " + m_FormatTag.ToString());
        sb.AppendLine("nChannels:         " + m_nChannels.ToString());
        sb.AppendLine("SamplesPerSecond:  " + m_SamplesPerSecond.ToString());
        sb.AppendLine("AvgBytesPerSecond: " + m_AvgBytesPerSecond.ToString());
        sb.AppendLine("BlockAlign:        " + m_BlockAlign.ToString());
        sb.AppendLine("BitsPerSample:     " + m_BitsPerSample.ToString());
        return sb.ToString();
    

【讨论】:

【参考方案2】:

通常,当您读取数据数组时,您的代码应如下所示:

for(int i = 0; i < totalNumberOfEntries; i++)

  // read all data for this entry
  var component1 = reader.ReadXXX();
  var component2 = reader.ReadXXX();

  // deal with data for this entry
  someEntryStroage.Add(new Entry(component1, component2);

最有可能(我不知道 Wave 文件格式)在您的情况下,您要么需要读取 Int16 值对(如果样本在一起),要么如果一个通道的数据一个接一个,则单独读取通道。

【讨论】:

wav文件中的通道是“多路复用的”,这意味着:sample1_chnl_1,sample1_chnl_2,sample2_chnl_1,sample2_chnl_2,sample3_chnl_1,sample3_chnl_2,sample4_chnl_1,sample4_chnl_2...【参考方案3】:

你必须阅读chunkinfos。数据块告诉您必须读取多少字节。 WaveFormat 会告诉您平均每秒多少字节,等等。我有一些 VB 代码...

【讨论】:

【参考方案4】:

已经将带有 sharpdevelop 的 VB 代码转换为 C# 可能会有所帮助...

using System;
using System.IO;

public class ChunkInfo

    private byte[] m_Header;
    private long m_Length;
    private long m_OffSet;
    public ChunkInfo(string Header)
    
        m_Header = new byte[Header.Length];
        for (int i = 0; i <= m_Header.GetUpperBound(0); i++)
        
            m_Header[i] = (byte)Header[i];
        
    
    public ChunkInfo(byte[] Header)
    
        m_Header = Header;
    
    public void Read(BinaryReader br)
    
        m_OffSet = SearchOffset(br);
        if (m_OffSet >= 0)
        
            br.BaseStream.Position = m_OffSet + m_Header.Length;
            m_Length = br.ReadInt32();
        
    
    public void Write(BinaryWriter bw)
    
        bw.Write(m_Header);
        bw.Write(m_Length);
    
    public long Length
    
        get  return m_Length; 
    
    public long OffSet
    
        get  return m_OffSet; 
    
    private long SearchOffset(BinaryReader br)
    
        byte[] haystack = null;
        bool found = false;
        long offset = 0;
        long basepos = 0;
        int hlength = 260;
        long basepos_grow = hlength - m_Header.Length;
        while (!(found || (basepos >= br.BaseStream.Length)))
        
            br.BaseStream.Position = basepos;
            haystack = br.ReadBytes(hlength);
            offset = BoyerMooreHorspool.find(haystack, m_Header);
            found = offset >= 0;
            if (found)
            
                offset += basepos;
                break; 
            
            else
            
                basepos += basepos_grow;
            
        
        return offset;
    

public static class BoyerMooreHorspool

    //detects a needle in the haystack
    const int UBYTE_MAX = 255;
    static int[] bad_char_skip4 = new int[UBYTE_MAX + 3];
    static int[] bad_char_skip8 = new int[UBYTE_MAX + 3];
    static bool IsInitialized = false;
    public static void init()
    
        //little optimization for needles with length 4 or 8
        for (int i = 0; i <= UBYTE_MAX + 2; i++)
        
            bad_char_skip4[i] = 4;
            bad_char_skip8[i] = 8;
        
        IsInitialized = true;
    
    public static int find(byte[] haystack, byte[] needle, int start = 0)
    
        if (!IsInitialized) init();
        int i_n = 0;
        //needle index
        int n_n = needle.Length;
        int[] bad_char_skip = null;
        switch (n_n)
        
            case 4:
                bad_char_skip = bad_char_skip4;
                break;
            case 8:
                bad_char_skip = bad_char_skip8;
                break;
            default:
                bad_char_skip = new int[UBYTE_MAX + 3];
                for (i_n = 0; i_n <= UBYTE_MAX + 2; i_n++)
                
                    bad_char_skip[i_n] = n_n;
                

                break;
        
        int ifind = -1;
        //if not found then return - 1
        int i_h = start;
        //haystack index
        int n_h = haystack.Length;
        if (n_n > n_h)
            throw new ArgumentOutOfRangeException("needle", "needle is to long");
        int last = n_n - 1;
        for (i_n = 0; i_n <= last - 1; i_n++)
        
            bad_char_skip[needle[i_n]] = last - i_n;
        
        byte bcs = 0;
        int bhs = 0;
        while ((n_h - start) >= n_n)
        
            i_n = last;
            while (haystack[i_h + i_n] == needle[i_n])
            
                i_n -= 1;
                if (i_n == 0)
                
                    ifind = i_h;
                    break; 
                
            
            bhs = haystack[i_h + last];
            bcs = (byte)(bad_char_skip[bhs]);
            n_h -= bcs;
            i_h += bcs;
        
        return ifind;
    

【讨论】:

以上是关于使用 BinaryReader 解析 Wave 文件的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发——WAVE音频文件解析

BinaryReader.PeekChar()读取了多少位?

为啥在 BinaryReader 上调用 Dispose() 会导致编译错误?

如果我正在读取的字节还不存在,BinaryReader 会做啥?

使用 BinaryReader/Writer 的 C# Socket 从读取器获取错误数据

一种消耗(所有字节)BinaryReader 的优雅方式?