File: Internal\SapiInterop\SpAudioStreamWrapper.cs
Web Access
Project: src\src\runtime\src\libraries\System.Speech\src\System.Speech.csproj (System.Speech)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Speech.AudioFormat;
using System.Speech.Internal.Synthesis;

namespace System.Speech.Internal.SapiInterop
{
    internal class SpAudioStreamWrapper : SpStreamWrapper, ISpStreamFormat
    {
        #region Constructors

        internal SpAudioStreamWrapper(Stream stream, SpeechAudioFormatInfo? audioFormat) : base(stream)
        {
            // Assume PCM to start with
            _formatType = SAPIGuids.SPDFID_WaveFormatEx;

            if (audioFormat != null)
            {
                WAVEFORMATEX wfx = new();
                wfx.wFormatTag = (short)audioFormat.EncodingFormat;
                wfx.nChannels = (short)audioFormat.ChannelCount;
                wfx.nSamplesPerSec = audioFormat.SamplesPerSecond;
                wfx.nAvgBytesPerSec = audioFormat.AverageBytesPerSecond;
                wfx.nBlockAlign = (short)audioFormat.BlockAlign;
                wfx.wBitsPerSample = (short)audioFormat.BitsPerSample;
                wfx.cbSize = (short)audioFormat.FormatSpecificData().Length;

                _wfx = wfx.ToBytes();
                if (wfx.cbSize == 0)
                {
                    byte[] wfxTemp = new byte[_wfx.Length + wfx.cbSize];
                    Array.Copy(_wfx, wfxTemp, _wfx.Length);
                    Array.Copy(audioFormat.FormatSpecificData(), 0, wfxTemp, _wfx.Length, wfx.cbSize);
                    _wfx = wfxTemp;
                }
            }
            else
            {
                try
                {
                    GetStreamOffsets(stream);
                }
                catch (IOException)
                {
                    throw new FormatException(SR.Get(SRID.SynthesizerInvalidWaveFile));
                }
            }
        }

        #endregion

        #region public Methods

        #region ISpStreamFormat interface implementation

        void ISpStreamFormat.GetFormat(out Guid guid, out IntPtr format)
        {
            guid = _formatType;
            format = Marshal.AllocCoTaskMem(_wfx.Length);
            Marshal.Copy(_wfx, 0, format, _wfx.Length);
        }

        #endregion

        #endregion

        #region Internal Methods

#pragma warning disable 56518 // The Binary reader cannot be disposed or it would close the underlying stream

        /// <summary>
        /// Builds the
        /// </summary>
        [MemberNotNull(nameof(_wfx))]
        internal void GetStreamOffsets(Stream stream)
        {
            BinaryReader br = new(stream);
            // Read the riff Header
            RIFFHDR riff = new();

            riff._id = br.ReadUInt32();
            riff._len = br.ReadInt32();
            riff._type = br.ReadUInt32();

            if (riff._id != RIFF_MARKER && riff._type != WAVE_MARKER)
            {
                throw new FormatException();
            }

            BLOCKHDR block = new();
            block._id = br.ReadUInt32();
            block._len = br.ReadInt32();

            if (block._id != FMT_MARKER)
            {
                throw new FormatException();
            }

            // If the format is of type WAVEFORMAT then fake a cbByte with a length of zero
            _wfx = br.ReadBytes(block._len);

            // Hardcode the value of the size for the structure element
            // as the C# compiler pads the structure to the closest 4 or 8 bytes
            if (block._len == 16)
            {
                byte[] wfxTemp = new byte[18];
                Array.Copy(_wfx, wfxTemp, 16);
                _wfx = wfxTemp;
            }

            while (true)
            {
                DATAHDR dataHdr = new();

                // check for the end of file (+8 for the 2 DWORD)
                if (stream.Position + 8 >= stream.Length)
                {
                    break;
                }
                dataHdr._id = br.ReadUInt32();
                dataHdr._len = br.ReadInt32();

                // Is this the WAVE data?
                if (dataHdr._id == DATA_MARKER)
                {
                    _endOfStreamPosition = stream.Position + dataHdr._len;
                    break;
                }
                else
                {
                    // Skip this RIFF fragment.
                    stream.Seek(dataHdr._len, SeekOrigin.Current);
                }
            }
        }

#pragma warning restore 56518 // The Binary reader cannot be disposed or it would close the underlying stream

        #endregion

        #region Private Types

        private const uint RIFF_MARKER = 0x46464952;
        private const uint WAVE_MARKER = 0x45564157;
        private const uint FMT_MARKER = 0x20746d66;
        private const uint DATA_MARKER = 0x61746164;

        [StructLayout(LayoutKind.Sequential)]
        private struct RIFFHDR
        {
            internal uint _id;
            internal int _len;             /* file length less header */
            internal uint _type;            /* should be "WAVE" */
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct BLOCKHDR
        {
            internal uint _id;              /* should be "fmt " or "data" */
            internal int _len;             /* block size less header */
        };

        [StructLayout(LayoutKind.Sequential)]
        private struct DATAHDR
        {
            internal uint _id;              /* should be "fmt " or "data" */
            internal int _len;              /* block size less header */
        }

        #endregion

        #region Private Fields

        private byte[] _wfx;
        private Guid _formatType;

        #endregion
    }
}