File: Internal\Synthesis\AudioFileOut.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.IO;
using System.Speech.AudioFormat;
using System.Threading;

namespace System.Speech.Internal.Synthesis
{
    /// <summary>
    /// Encapsulates Waveform Audio Interface playback functions and provides a simple
    /// interface for playing audio.
    /// </summary>
    internal class AudioFileOut : AudioBase, IDisposable
    {
        #region Constructors

        /// <summary>
        /// Create an instance of AudioFileOut.
        /// </summary>
        internal AudioFileOut(Stream stream, SpeechAudioFormatInfo? formatInfo, bool headerInfo, IAsyncDispatch asyncDispatch)
        {
            _asyncDispatch = asyncDispatch;
            _stream = stream;
            _startStreamPosition = _stream.Position;
            _hasHeader = headerInfo;

            _wfxOut = new WAVEFORMATEX();
            // if we have a formatInfo object, format conversion may be necessary
            if (formatInfo != null)
            {
                // Build the Wave format from the formatInfo
                _wfxOut.wFormatTag = (short)formatInfo.EncodingFormat;
                _wfxOut.wBitsPerSample = (short)formatInfo.BitsPerSample;
                _wfxOut.nSamplesPerSec = formatInfo.SamplesPerSecond;
                _wfxOut.nChannels = (short)formatInfo.ChannelCount;
            }
            else
            {
                // Set the default values
                _wfxOut = WAVEFORMATEX.Default;
            }
            _wfxOut.nBlockAlign = (short)(_wfxOut.nChannels * _wfxOut.wBitsPerSample / 8);
            _wfxOut.nAvgBytesPerSec = _wfxOut.wBitsPerSample * _wfxOut.nSamplesPerSec * _wfxOut.nChannels / 8;
        }

        public void Dispose()
        {
            _evt.Close();
            GC.SuppressFinalize(this);
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Begin to play
        /// </summary>
        internal override void Begin(byte[] wfx)
        {
            if (_deviceOpen)
            {
                System.Diagnostics.Debug.Assert(false);
                throw new InvalidOperationException();
            }

            // Get the audio format if conversion is needed
            _wfxIn = WAVEFORMATEX.ToWaveHeader(wfx);
            _doConversion = _pcmConverter.PrepareConverter(ref _wfxIn, ref _wfxOut);

            if (_totalByteWrittens == 0 && _hasHeader)
            {
                WriteWaveHeader(_stream, _wfxOut, _startStreamPosition, 0);
            }

            _bytesWritten = 0;

            // set the flags
            _aborted = false;
            _deviceOpen = true;
        }

        /// <summary>
        /// Begin to play
        /// </summary>
        internal override void End()
        {
            if (!_deviceOpen)
            {
                System.Diagnostics.Debug.Assert(false);
                throw new InvalidOperationException();
            }
            _deviceOpen = false;

            if (!_aborted)
            {
                if (_hasHeader)
                {
                    long position = _stream.Position;
                    WriteWaveHeader(_stream, _wfxOut, _startStreamPosition, _totalByteWrittens);
                    _stream.Seek(position, SeekOrigin.Begin);
                }
            }
        }

        #region AudioDevice implementation

        /// <summary>
        /// Play a wave file.
        /// </summary>
        internal override void Play(byte[] buffer)
        {
            if (!_deviceOpen)
            {
                System.Diagnostics.Debug.Assert(false);
            }
            else
            {
                byte[] abOut = _doConversion ? _pcmConverter.ConvertSamples(buffer) : buffer;

                if (_paused)
                {
                    _evt.WaitOne();
                    _evt.Reset();
                }
                if (!_aborted)
                {
                    _stream.Write(abOut, 0, abOut.Length);
                    _totalByteWrittens += abOut.Length;
                    _bytesWritten += abOut.Length;
                }
            }
        }

        /// <summary>
        /// Pause the playback of a sound.
        /// </summary>
        internal override void Pause()
        {
            if (!_aborted && !_paused)
            {
                lock (_noWriteOutLock)
                {
                    _paused = true;
                }
            }
        }

        /// <summary>
        /// Resume the playback of a paused sound.
        /// </summary>
        internal override void Resume()
        {
            if (!_aborted && _paused)
            {
                lock (_noWriteOutLock)
                {
                    _paused = false;
                    _evt.Set();
                }
            }
        }

        /// <summary>
        /// Wait for all the queued buffers to be played
        /// </summary>
        internal override void Abort()
        {
            lock (_noWriteOutLock)
            {
                _aborted = true;
                _paused = false;
                _evt.Set();
            }
        }

        internal override void InjectEvent(TTSEvent ttsEvent)
        {
            if (!_aborted && _asyncDispatch != null)
            {
                _asyncDispatch.Post(ttsEvent);
            }
        }

        /// <summary>
        /// File operation are basically synchronous
        /// </summary>
        internal override void WaitUntilDone()
        {
            lock (_noWriteOutLock)
            {
            }
        }

        #endregion

        #endregion

        #region Internal Fields

        internal override TimeSpan Duration
        {
            get
            {
                if (_wfxIn.nAvgBytesPerSec == 0)
                {
                    return new TimeSpan(0);
                }
                return new TimeSpan((_bytesWritten * TimeSpan.TicksPerSecond) / _wfxIn.nAvgBytesPerSec);
            }
        }

        internal override long Position
        {
            get
            {
                return _stream.Position;
            }
        }

        internal override byte[] WaveFormat
        {
            get
            {
                return _wfxOut.ToBytes();
            }
        }

        #endregion

        #region Private Fields

        protected ManualResetEvent _evt = new(false);
        protected bool _deviceOpen;

        protected Stream _stream;

        protected PcmConverter _pcmConverter = new();
        protected bool _doConversion;

        protected bool _paused;
        protected int _totalByteWrittens;
        protected int _bytesWritten;

        #endregion

        #region Private Fields

        private IAsyncDispatch _asyncDispatch;
        private object _noWriteOutLock = new();

        private WAVEFORMATEX _wfxIn;
        private WAVEFORMATEX _wfxOut;
        private bool _hasHeader;

        private long _startStreamPosition;

        #endregion
    }
}