Files
ichni_Creator_Studio/Assets/NLayer/MpegFrameDecoder.cs
2025-07-12 18:27:10 +08:00

192 lines
7.5 KiB
C#

using System;
namespace NLayer
{
public class MpegFrameDecoder
{
Decoder.LayerIDecoder _layerIDecoder;
Decoder.LayerIIDecoder _layerIIDecoder;
Decoder.LayerIIIDecoder _layerIIIDecoder;
float[] _eqFactors;
// channel buffers for getting data out of the decoders...
// we do it this way so the stereo interleaving code is in one place: DecodeFrameImpl(...)
// if we ever add support for multi-channel, we'll have to add a pass after the initial
// stereo decode (since multi-channel basically uses the stereo channels as a reference)
float[] _ch0, _ch1;
public MpegFrameDecoder()
{
_ch0 = new float[1152];
_ch1 = new float[1152];
}
/// <summary>
/// Set the equalizer.
/// </summary>
/// <param name="eq">The equalizer, represented by an array of 32 adjustments in dB.</param>
public void SetEQ(float[] eq)
{
if (eq != null)
{
var factors = new float[32];
for (int i = 0; i < eq.Length; i++)
{
// convert from dB -> scaling
factors[i] = (float)Math.Pow(2, eq[i] / 6);
}
_eqFactors = factors;
}
else
{
_eqFactors = null;
}
}
/// <summary>
/// Stereo mode used in decoding.
/// </summary>
public StereoMode StereoMode { get; set; }
/// <summary>
/// Decode the Mpeg frame into provided buffer. Do exactly the same as <see cref="DecodeFrame(IMpegFrame, float[], int)"/>
/// except that the data is written in type as byte array, while still representing single-precision float (in local endian).
/// </summary>
/// <param name="frame">The Mpeg frame to be decoded.</param>
/// <param name="dest">Destination buffer. Decoded PCM (single-precision floating point array) will be written into it.</param>
/// <param name="destOffset">Writing offset on the destination buffer.</param>
/// <returns></returns>
public int DecodeFrame(IMpegFrame frame, byte[] dest, int destOffset)
{
if (frame == null) throw new ArgumentNullException("frame");
if (dest == null) throw new ArgumentNullException("dest");
if (destOffset % 4 != 0) throw new ArgumentException("Must be an even multiple of 4", "destOffset");
var bufferAvailable = (dest.Length - destOffset) / 4;
if (bufferAvailable < (frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2) * frame.SampleCount)
{
throw new ArgumentException("Buffer not large enough! Must be big enough to hold the frame's entire output. This is up to 9,216 bytes.", "dest");
}
return DecodeFrameImpl(frame, dest, destOffset / 4) * 4;
}
/// <summary>
/// Decode the Mpeg frame into provided buffer.
/// Result varies with different <see cref="StereoMode"/>:
/// <list type="bullet">
/// <item>
/// <description>For <see cref="NLayer.StereoMode.Both"/>, sample data on both two channels will occur in turn (left first).</description>
/// </item>
/// <item>
/// <description>For <see cref="NLayer.StereoMode.LeftOnly"/> and <see cref="NLayer.StereoMode.RightOnly"/>, only data on
/// specified channel will occur.</description>
/// </item>
/// <item>
/// <description>For <see cref="NLayer.StereoMode.DownmixToMono"/>, two channels will be down-mixed into single channel.</description>
/// </item>
/// </list>
/// </summary>
/// <param name="frame">The Mpeg frame to be decoded.</param>
/// <param name="dest">Destination buffer. Decoded PCM (single-precision floating point array) will be written into it.</param>
/// <param name="destOffset">Writing offset on the destination buffer.</param>
/// <returns></returns>
public int DecodeFrame(IMpegFrame frame, float[] dest, int destOffset)
{
if (frame == null) throw new ArgumentNullException("frame");
if (dest == null) throw new ArgumentNullException("dest");
if (dest.Length - destOffset < (frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2) * frame.SampleCount)
{
throw new ArgumentException("Buffer not large enough! Must be big enough to hold the frame's entire output. This is up to 2,304 elements.", "dest");
}
return DecodeFrameImpl(frame, dest, destOffset);
}
int DecodeFrameImpl(IMpegFrame frame, Array dest, int destOffset)
{
frame.Reset();
Decoder.LayerDecoderBase curDecoder = null;
switch (frame.Layer)
{
case MpegLayer.LayerI:
if (_layerIDecoder == null)
{
_layerIDecoder = new Decoder.LayerIDecoder();
}
curDecoder = _layerIDecoder;
break;
case MpegLayer.LayerII:
if (_layerIIDecoder == null)
{
_layerIIDecoder = new Decoder.LayerIIDecoder();
}
curDecoder = _layerIIDecoder;
break;
case MpegLayer.LayerIII:
if (_layerIIIDecoder == null)
{
_layerIIIDecoder = new Decoder.LayerIIIDecoder();
}
curDecoder = _layerIIIDecoder;
break;
}
if (curDecoder != null)
{
curDecoder.SetEQ(_eqFactors);
curDecoder.StereoMode = StereoMode;
var cnt = curDecoder.DecodeFrame(frame, _ch0, _ch1);
if (frame.ChannelMode == MpegChannelMode.Mono)
{
Buffer.BlockCopy(_ch0, 0, dest, destOffset * sizeof(float), cnt * sizeof(float));
}
else
{
// This is kinda annoying... if we're doing a downmix, we should technically only output a single channel
// The problem is, our caller is probably expecting stereo output. Grrrr....
// We use Buffer.BlockCopy here because we don't know dest's type, but do know it's big enough to do the copy
for (int i = 0; i < cnt; i++)
{
Buffer.BlockCopy(_ch0, i * sizeof(float), dest, destOffset * sizeof(float), sizeof(float));
++destOffset;
Buffer.BlockCopy(_ch1, i * sizeof(float), dest, destOffset * sizeof(float), sizeof(float));
++destOffset;
}
cnt *= 2;
}
return cnt;
}
return 0;
}
/// <summary>
/// Reset the decoder.
/// </summary>
public void Reset()
{
// the synthesis filters need to be cleared
if (_layerIDecoder != null)
{
_layerIDecoder.ResetForSeek();
}
if (_layerIIDecoder != null)
{
_layerIIDecoder.ResetForSeek();
}
if (_layerIIIDecoder != null)
{
_layerIIIDecoder.ResetForSeek();
}
}
}
}