【问题标题】:readData for 24-bit FLAC and WAV filesreadData 用于 24 位 FLAC 和 WAV 文件
【发布时间】:2011-06-01 04:03:48
【问题描述】:

我使用 readData 成功读取了 16 位音频文件并生成了用于波形显示的峰值文件。但是,我在解释 24 位 FLAC 和 WAV 文件的 PCM 值时遇到了一些问题。

首先,24 位的块大小是多少?

16 位有符号值范围从 -32768 到 +32768,24 位范围从 -8388607 到 +8388607。

我对 16 位文件 (65536 / 16 = 4096) 使用了 4096 字节的块大小。它可以很好地检测峰值。

如果我用 24 位做同样的计算,16777215 / 24 = 699050.625 字节。我弄错了吗?我想我必须使用 32 位变量来存储 24 位值。但是读取文件时我应该使用什么块大小? 699051?如何调整转换为浮点数组?

这是我用来为 16 位 PCM 数据生成峰值文件的完整 C# 代码。我故意将 24 位代码留空,因为它不起作用。一些代码引用了我自己的 FMOD 包装器,但应该很容易理解。

 // Declare variables
            FMOD.RESULT result = FMOD.RESULT.OK;
            FileStream fileStream = null;
            BinaryWriter binaryWriter = null;
            GZipStream gzipStream = null;
            bool generatePeakFile = false;
            int CHUNKSIZE = 0;
            uint length = 0;
            uint read = 0;
            uint bytesread = 0;
            Int16[] left16BitArray = null;
            Int16[] right16BitArray = null;
            Int32[] left32BitArray = null;
            Int32[] right32BitArray = null;
            float[] floatLeft = null;
            float[] floatRight = null;
            byte[] buffer = null;
            IntPtr data = new IntPtr(); // initialized properly later
            WaveDataMinMax minMax = null;

            try
            {
                // Set current file directory
                m_peakFileDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Peak Files\\";

                // Get file name from argument
                string fileName = (string)e.Argument;

                // Create sound system with NOSOUND
                MPfm.Sound.System soundSystem = new MPfm.Sound.System(FMOD.OUTPUTTYPE.NOSOUND, string.Empty);

                // Create sound
                MPfm.Sound.Sound sound = soundSystem.CreateSound(fileName, false);

                // Get sound format; specifically bits per sample (changes the calculations later)
                SoundFormat soundFormat = sound.GetSoundFormat();

                // Get the length of the file in PCM bytes               
                sound.BaseSound.getLength(ref length, FMOD.TIMEUNIT.PCMBYTES);

                // Check if the folder for peak files exists
                if (!Directory.Exists(PeakFileDirectory))
                {
                    // Create directory
                    Directory.CreateDirectory(PeakFileDirectory);
                }

                // Generate the file name for the peak file by using the full path without special characters
                string peakFilePath = PeakFileDirectory + fileName.Replace(@"\", "_").Replace(":", "_").Replace(".", "_") + ".mpfmPeak";

                // Check if peak file exists                
                if(!File.Exists(peakFilePath))
                {
                    // Set flag
                    generatePeakFile = true;

                    // Create peak file
                    fileStream = new FileStream(peakFilePath, FileMode.Create, FileAccess.Write);
                    binaryWriter = new BinaryWriter(fileStream);
                    gzipStream = new GZipStream(fileStream, CompressionMode.Compress);                   
                }

                // Check the bits per sample to determine what chunk size to get                
                if (soundFormat.BitsPerSample == 16)
                {
                    // 4096 bytes for 16-bit PCM data
                    CHUNKSIZE = 4096;
                }
                else if (soundFormat.BitsPerSample == 24)
                {
                    // 699050.625 bytes for 24-bit PCM data (???)   
                    CHUNKSIZE = 699051;
                }

                // Create buffer
                data = Marshal.AllocHGlobal(CHUNKSIZE);
                buffer = new byte[CHUNKSIZE];

                // Loop through file using chunk size
                do
                {
                    // Check for cancel
                    if (m_workerWaveForm.CancellationPending)
                    {
                        return;
                    }

                    // Check the bits per sample
                    if (soundFormat.BitsPerSample == 16)
                    {
                        // Read data chunk (4096 bytes for 16-bit PCM data)
                        result = sound.BaseSound.readData(data, (uint)CHUNKSIZE, ref read);
                        Marshal.Copy(data, buffer, 0, CHUNKSIZE);
                        bytesread += read;

                        // Is freehglobal needed? it crashes after one use.
                        //Marshal.FreeHGlobal(data);

                        // Convert the byte (8-bit) arrays into a short (16-bit) arrays (signed values)
                        left16BitArray = new Int16[buffer.Length / 4];
                        right16BitArray = new Int16[buffer.Length / 4];

                        // Loop through byte (8-bit) array buffer; increment by 4 (i.e. 4 times more data in 16-bit than 8-bit)
                        for (int i = 0; i < buffer.Length; i = i + 4)
                        {
                            // Convert values to 16-bit
                            left16BitArray[i / 4] = BitConverter.ToInt16(buffer, i);
                            right16BitArray[i / 4] = BitConverter.ToInt16(buffer, i + 2); // alternate between left and right channel
                        }

                        // Convert the short arrays to float arrays (signed values)
                        // This will convert the -32768 to 32768 value range to -1 to 1 (useful for wave display) 
                        floatLeft = new float[left16BitArray.Length];
                        floatRight = new float[left16BitArray.Length];
                        for (int i = 0; i < left16BitArray.Length; i++)
                        {
                            // 16-bit data for unsigned values range from 0 to 65536.
                            floatLeft[i] = left16BitArray[i] / 65536.0f;
                            floatRight[i] = right16BitArray[i] / 65536.0f;                            
                        }
                    }
                    else if (soundFormat.BitsPerSample == 24)
                    {
                       // (non-working code removed)

                        // (I have no idea if this works) Convert the short arrays to float arrays (signed values)
                        // This will convert the -8388608 to 8388608value range to -1 to 1 (useful for wave display) 
                        floatLeft = new float[left32BitArray.Length];
                        floatRight = new float[left32BitArray.Length];
                        for (int i = 0; i < left32BitArray.Length; i++)
                        {
                            // 16-bit data for unsigned values range from 0 to 16777215.
                            floatLeft[i] = left32BitArray[i] / 16777215.0f;
                            floatRight[i] = right32BitArray[i] / 16777215.0f;                            
                        }
                    }

                    // Calculate min/max
                    minMax = AudioTools.GetMinMaxFromWaveData(floatLeft, floatRight, false);
                    WaveDataHistory.Add(minMax);

                    // Report progress
                    m_bytesRead = bytesread;
                    m_totalBytes = length;
                    m_percentageDone = ((float)bytesread / (float)length) * 100;

                    // Write peak information to hard disk
                    if (generatePeakFile)
                    {
                        // Write peak information
                        binaryWriter.Write((double)minMax.leftMin);
                        binaryWriter.Write((double)minMax.leftMax);
                        binaryWriter.Write((double)minMax.rightMin);
                        binaryWriter.Write((double)minMax.rightMax);
                        binaryWriter.Write((double)minMax.mixMin);
                        binaryWriter.Write((double)minMax.mixMax);
                    }                  
                }
                while (result == FMOD.RESULT.OK && read == CHUNKSIZE);

                // Release sound from memory
                sound.Release();

                // Close sound system and release from memory
                soundSystem.Close();
                soundSystem.Release();

                // Set nulls for garbage collection               
                sound = null;
                soundSystem = null;
                left16BitArray = null;
                right16BitArray = null;
                left32BitArray = null;
                right32BitArray = null;
                floatLeft = null;
                floatRight = null;                
                buffer = null;
                minMax = null;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                // Did we have to generate a peak file?
                if (generatePeakFile)
                {
                    // Close writer and stream
                    gzipStream.Close();
                    binaryWriter.Close();                   
                    fileStream.Close();

                    // Set nulls
                    gzipStream = null;
                    binaryWriter = null;
                    fileStream = null;
                }
            }

            // Call garbage collector
            GC.Collect();

这是从浮点数组中提取最小值/最大值的方法:

/// <summary>
/// This method takes the left channel and right channel wave raw data and analyses it to get
/// the maximum and minimum values in the float structure. It returns a data structure named
/// WaveDataMinMax (see class description for more information). Negative values can be converted to
/// positive values before min and max comparaison. Set this parameter to true for output meters and
/// false for wave form display controls.
/// </summary>
/// <param name="waveDataLeft">Raw wave data (left channel)</param>
/// <param name="waveDataRight">Raw wave data (right channel)</param>
/// <param name="convertNegativeToPositive">Convert negative values to positive values (ex: true when used for output meters, 
/// false when used with wave form display controls (since the negative value is used to draw the bottom end of the waveform).<</param>
/// <returns>WaveDataMinMax data structure</returns>
public static WaveDataMinMax GetMinMaxFromWaveData(float[] waveDataLeft, float[] waveDataRight, bool convertNegativeToPositive)
{
    // Create default data
    WaveDataMinMax data = new WaveDataMinMax();

    // Loop through values to get min/max
    for (int i = 0; i < waveDataLeft.Length; i++)
    {
        // Set values to compare
        float left = waveDataLeft[i];
        float right = waveDataRight[i];

        // Do we have to convert values before comparaison?
        if (convertNegativeToPositive)
        {
            // Compare values, if negative then remove negative sign
            if (left < 0)
            {
                left = -left;
            }
            if (right < 0)
            {
                right = -right;
            }
        }

        // Calculate min/max for left channel
        if (left < data.leftMin)
        {
            data.leftMin = left;
        }
        if (left > data.leftMax)
        {
            data.leftMax = left;
        }

        // Calculate min/max for right channel
        if (right < data.rightMin)
        {
            data.rightMin = right;
        }
        if (right > data.rightMax)
        {
            data.rightMax = right;
        }

        // Calculate min/max mixing both channels
        if (left < data.mixMin)
        {
            data.mixMin = left;
        }
        if (right < data.mixMin)
        {
            data.mixMin = right;
        }
        if (left > data.mixMax)
        {
            data.mixMax = left;
        }
        if (right > data.mixMax)
        {
            data.mixMax = right;
        }
    }

    return data;
}

有人可以给我一个提示吗?我希望我的代码不会太糟糕,并且可以用作 16 位文件的示例。感谢您的帮助!

编辑:

这里是使用 32 位变量的 3x8 位到 24 位的转换代码:

                left32BitArray = new Int32[buffer.Length / 6];
                right32BitArray = new Int32[buffer.Length / 6];
                for (int i = 0; i < buffer.Length; i = i + 6)
                {
                        // Create smaller array in order to add the 4th 8-bit value
                        byte[] byteArrayLeft = new byte[4] {buffer[i], buffer[i + 1], buffer[i + 2], 0 };
                        byte[] byteArrayRight = new byte[4] { buffer[i + 3], buffer[i + 4], buffer[i + 5], 0 };

                        // Convert values to 32-bit variables
                        left32BitArray[i / 6] = BitConverter.ToInt32(byteArrayLeft, 0);
                        right32BitArray[i / 6] = BitConverter.ToInt32(byteArrayRight, 0);
                }

【问题讨论】:

  • 为什么要猜测?只要找出24位数据的wav文件格式就可以了。
  • 我到底在猜什么?我已经使用 GetSoundFormat 知道它的格式,它会返回频率、比特率、每个样本的位数等。因此我已经知道它是 24 位 WAV 或 FLAC 文件。我知道我的 16 位代码假定文件使用 44100kHz 采样率。我已经忽略了每个样本频率/位数较低的文件。

标签: c# audio io fmod


【解决方案1】:

24 位音频文件的块对齐为 3 * 通道数。为什么不使用 100 毫秒的音频:

int blockSize = 3 * channels * (sampleRate / 10);

这适用于 24 位 WAV。您的 FLAC 阅读器是否允许您读取到该粒度取决于其内部实现。

【讨论】:

  • 感谢您的回答。 16 位文件的相同公式是什么?我今晚要试试这个。我会选择 10 毫秒的数据,所以我猜我必须将 sampleRate 除以 100。如果它有效,我会标记你的答案。
  • 将 3 更改为 2(3 = 24/8;2 = 16/8)。
  • 我在这个问题上休息了几天,抱歉耽搁了。但是,您让我了解了 PCM 数据的存储方式。我对 BitConverter 部分的实际 cmets 是错误的。我想我是偶然让它工作的,但现在很清楚我必须做什么。我必须将 24 位值存储到 32 位变量中(这篇文章帮助了我:stackoverflow.com/questions/3345553/…)。一旦我得到它的工作,我会标记你的答案。感谢您的第二个回答!
  • 它似乎工作正常,除了来自 FMOD readData 的值。数据似乎在 0 或 16777215 左右,绘制出平坦的波形。当我在将数据转换为 24 位之前查看数据时,它主要是零和 255。当我发现问题所在时,我将发布我更新的代码。再次感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-07
相关资源
最近更新 更多