读取24位FLAC和WAV文件的数据

本文关键字:文件 数据 WAV 24位 FLAC 读取 | 更新日期: 2023-09-27 17:58:11

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

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

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

我为16位文件使用了4096字节的块大小(65536/16=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位文件的示例。谢谢你的帮助!

编辑:

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

                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位FLAC和WAV文件的数据

24位音频文件具有3*个通道的块对齐。为什么不去100毫秒的音频:

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

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