3

I have audio files (WAV and OGG) that are stored inside another data file ([some misc data...][flag indicating ogg and length][OGG file data][more misc data...]). I can extract the OGG and WAV file data into byte arrays easily enough. How do I convert the byte arrays to AudioClip objects? I don't want to decode all the audio myself or add extensions. I also need to do this cross platform. Speed is not really critical so a solution like writing the data to a temp file then using WWW to load it seems viable, but I don't know how to create a cross platform, sandbox safe, temp file, and I'm hoping there's a way to do this all in memory so I don't need to thrash the disk (but if I have to then so be it).

ima747
  • 4,667
  • 3
  • 36
  • 46
  • Hi ima, I am running into this same problem. Did you ever find a solution? – Connor Clark Feb 19 '15 at 20:27
  • No. I'm sure it's possible but probably requires a plugin to be able to push the encoded data into the right place to then be decoded by the handler. I don't have the time to resolve that path. – ima747 Feb 20 '15 at 16:31
  • Regarding OGG I guess you will need to use some kind of decoder. However, you can find a solution for the WAV scenario here: https://stackoverflow.com/a/68965193/1934546 – fibriZo raZiel Aug 28 '21 at 14:25

2 Answers2

0

After extracting the file data into a float array - or converting the byte array into float array in your case, you can create an AudioClip instance using the static Create function and fill that instance in by SetData function respectively.

Here is an example code doing the exact same thing you refer to: create AudioClip from byte[]

Community
  • 1
  • 1
anokta
  • 486
  • 2
  • 9
  • I thought the AudioClip SetData expected raw audio stream data, not encoded/compressed wav/ogg data... – ima747 Jun 03 '14 at 18:28
  • I don't think this is possible as described, as all I have is a byte array, I don't know the channels, frequency, samples, etc. This would be shoving and encoded audio file into a memory space expecting raw audio samples. – ima747 Jun 04 '14 at 15:06
0

This is probably redundant, but you can use the implementation I published here to create an AudioClip from a WAV PCM byte[]:

PcmHeader

private readonly struct PcmHeader
{
    #region Public types & data

    public int    BitDepth         { get; }
    public int    AudioSampleSize  { get; }
    public int    AudioSampleCount { get; }
    public ushort Channels         { get; }
    public int    SampleRate       { get; }
    public int    AudioStartIndex  { get; }
    public int    ByteRate         { get; }
    public ushort BlockAlign       { get; }

    #endregion

    #region Constructors & Finalizer

    private PcmHeader(int bitDepth,
        int               audioSize,
        int               audioStartIndex,
        ushort            channels,
        int               sampleRate,
        int               byteRate,
        ushort            blockAlign)
    {
        BitDepth       = bitDepth;
        _negativeDepth = Mathf.Pow(2f, BitDepth - 1f);
        _positiveDepth = _negativeDepth - 1f;

        AudioSampleSize  = bitDepth / 8;
        AudioSampleCount = Mathf.FloorToInt(audioSize / (float)AudioSampleSize);
        AudioStartIndex  = audioStartIndex;

        Channels   = channels;
        SampleRate = sampleRate;
        ByteRate   = byteRate;
        BlockAlign = blockAlign;
    }

    #endregion

    #region Public Methods

    public static PcmHeader FromBytes(byte[] pcmBytes)
    {
        using var memoryStream = new MemoryStream(pcmBytes);
        return FromStream(memoryStream);
    }

    public static PcmHeader FromStream(Stream pcmStream)
    {
        pcmStream.Position = SizeIndex;
        using BinaryReader reader = new BinaryReader(pcmStream);

        int    headerSize      = reader.ReadInt32();  // 16
        ushort audioFormatCode = reader.ReadUInt16(); // 20

        string audioFormat = GetAudioFormatFromCode(audioFormatCode);
        if (audioFormatCode != 1 && audioFormatCode == 65534)
        {
            // Only uncompressed PCM wav files are supported.
            throw new ArgumentOutOfRangeException(nameof(pcmStream),
                                                  $"Detected format code '{audioFormatCode}' {audioFormat}, but only PCM and WaveFormatExtensible uncompressed formats are currently supported.");
        }

        ushort channelCount = reader.ReadUInt16(); // 22
        int    sampleRate   = reader.ReadInt32();  // 24
        int    byteRate     = reader.ReadInt32();  // 28
        ushort blockAlign   = reader.ReadUInt16(); // 32
        ushort bitDepth     = reader.ReadUInt16(); //34

        pcmStream.Position = SizeIndex + headerSize + 2 * sizeof(int); // Header end index
        int audioSize = reader.ReadInt32();                            // Audio size index

        return new PcmHeader(bitDepth, audioSize, (int)pcmStream.Position, channelCount, sampleRate, byteRate, blockAlign); // audio start index
    }

    public float NormalizeSample(float rawSample)
    {
        float sampleDepth = rawSample < 0 ? _negativeDepth : _positiveDepth;
        return rawSample / sampleDepth;
    }

    #endregion

    #region Private Methods

    private static string GetAudioFormatFromCode(ushort code)
    {
        switch (code)
        {
            case 1:     return "PCM";
            case 2:     return "ADPCM";
            case 3:     return "IEEE";
            case 7:     return "?-law";
            case 65534: return "WaveFormatExtensible";
            default:    throw new ArgumentOutOfRangeException(nameof(code), code, "Unknown wav code format.");
        }
    }

    #endregion

    #region Private types & Data

    private const int SizeIndex = 16;

    private readonly float _positiveDepth;
    private readonly float _negativeDepth;

    #endregion
}

PcmData

private readonly struct PcmData
{
    #region Public types & data

    public float[] Value      { get; }
    public int     Length     { get; }
    public int     Channels   { get; }
    public int     SampleRate { get; }

    #endregion

    #region Constructors & Finalizer

    private PcmData(float[] value, int channels, int sampleRate)
    {
        Value      = value;
        Length     = value.Length;
        Channels   = channels;
        SampleRate = sampleRate;
    }

    #endregion

    #region Public Methods

    public static PcmData FromBytes(byte[] bytes)
    {
        if (bytes == null)
        {
            throw new ArgumentNullException(nameof(bytes));
        }

        PcmHeader pcmHeader = PcmHeader.FromBytes(bytes);
        if (pcmHeader.BitDepth != 16 && pcmHeader.BitDepth != 32 && pcmHeader.BitDepth != 8)
        {
            throw new ArgumentOutOfRangeException(nameof(pcmHeader.BitDepth), pcmHeader.BitDepth, "Supported values are: 8, 16, 32");
        }

        float[] samples = new float[pcmHeader.AudioSampleCount];
        for (int i = 0; i < samples.Length; ++i)
        {
            int   byteIndex = pcmHeader.AudioStartIndex + i * pcmHeader.AudioSampleSize;
            float rawSample;
            switch (pcmHeader.BitDepth)
            {
                case 8:
                    rawSample = bytes[byteIndex];
                    break;

                case 16:
                    rawSample = BitConverter.ToInt16(bytes, byteIndex);
                    break;

                case 32:
                    rawSample = BitConverter.ToInt32(bytes, byteIndex);
                    break;

                default: throw new ArgumentOutOfRangeException(nameof(pcmHeader.BitDepth), pcmHeader.BitDepth, "Supported values are: 8, 16, 32");
            }

            samples[i] = pcmHeader.NormalizeSample(rawSample); // normalize sample between [-1f, 1f]
        }

        return new PcmData(samples, pcmHeader.Channels, pcmHeader.SampleRate);
    }

    #endregion
}

Usage

public static AudioClip FromPcmBytes(byte[] bytes, string clipName = "pcm")
{
    clipName.ThrowIfNullOrWhitespace(nameof(clipName));
    var pcmData   = PcmData.FromBytes(bytes);
    var audioClip = AudioClip.Create(clipName, pcmData.Length, pcmData.Channels, pcmData.SampleRate, false);
    audioClip.SetData(pcmData.Value, 0);
    return audioClip;
}

Note that AudioClip.Create provides an overload with Read and SetPosition callbacks in case you need to work with a source Stream instead of a chunk of bytes.

fibriZo raZiel
  • 894
  • 11
  • 10