File: Internal\SapiInterop\SpeechEvent.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.Runtime.InteropServices;
using System.Speech.AudioFormat;

namespace System.Speech.Internal.SapiInterop
{
    // Internal helper class that wraps a SAPI event structure.
    // A new instance is created by calling SpeechEvent.TryCreateSpeechEvent
    // Disposing this class will dispose all unmanaged memory.
    internal class SpeechEvent : IDisposable
    {
        #region Constructors

        private SpeechEvent(SPEVENTENUM eEventId, SPEVENTLPARAMTYPE elParamType,
            ulong ullAudioStreamOffset, IntPtr wParam, IntPtr lParam)
        {
            // We make a copy of the SPEVENTEX data but that's okay because the lParam will only be deleted once.
            _eventId = eEventId;
            _paramType = elParamType;
            _audioStreamOffset = ullAudioStreamOffset;
            _wParam = (ulong)wParam.ToInt64();
            _lParam = (ulong)lParam;

            // Let the GC know if we have a unmanaged object with a given size
            if (_paramType == SPEVENTLPARAMTYPE.SPET_LPARAM_IS_POINTER || _paramType == SPEVENTLPARAMTYPE.SPET_LPARAM_IS_STRING)
            {
                GC.AddMemoryPressure(_sizeMemoryPressure = sizeof(ulong));
            }
        }

        private SpeechEvent(SPEVENT sapiEvent, SpeechAudioFormatInfo? audioFormat)
            : this(sapiEvent.eEventId, sapiEvent.elParamType, sapiEvent.ullAudioStreamOffset, sapiEvent.wParam, sapiEvent.lParam)
        {
            if (audioFormat == null || audioFormat.EncodingFormat == 0)
            {
                _audioPosition = TimeSpan.Zero;
            }
            else
            {
                _audioPosition = audioFormat.AverageBytesPerSecond > 0 ? new TimeSpan((long)((sapiEvent.ullAudioStreamOffset * TimeSpan.TicksPerSecond) / (ulong)audioFormat.AverageBytesPerSecond)) : TimeSpan.Zero;
            }
        }

        private SpeechEvent(SPEVENTEX sapiEventEx) : this(sapiEventEx.eEventId, sapiEventEx.elParamType, sapiEventEx.ullAudioStreamOffset, sapiEventEx.wParam, sapiEventEx.lParam)
        {
            _audioPosition = new TimeSpan((long)sapiEventEx.ullAudioTimeOffset);
        }

        ~SpeechEvent()
        {
            Dispose();
        }

        public void Dispose()
        {
            // General code to free event data
            if (_lParam != 0)
            {
                if (_paramType == SPEVENTLPARAMTYPE.SPET_LPARAM_IS_TOKEN || _paramType == SPEVENTLPARAMTYPE.SPET_LPARAM_IS_OBJECT)
                {
                    Marshal.Release((IntPtr)_lParam);
                }
                else
                {
                    if (_paramType == SPEVENTLPARAMTYPE.SPET_LPARAM_IS_POINTER || _paramType == SPEVENTLPARAMTYPE.SPET_LPARAM_IS_STRING)
                    {
                        Marshal.FreeCoTaskMem((IntPtr)_lParam);
                    }
                }

                // Update the GC
                if (_sizeMemoryPressure > 0)
                {
                    GC.RemoveMemoryPressure(_sizeMemoryPressure);
                    _sizeMemoryPressure = 0;
                }

                // Mark the object as being freed
                _lParam = 0;
            }
            GC.SuppressFinalize(this);
        }

        #endregion

        #region Internal Methods

        // This tries to get an event from the ISpEventSource.
        // If there are no events queued then null is returned.
        // Otherwise a new SpeechEvent is created and returned.
        internal static SpeechEvent? TryCreateSpeechEvent(ISpEventSource sapiEventSource, bool additionalSapiFeatures, SpeechAudioFormatInfo? audioFormat)
        {
            uint fetched;
            SpeechEvent? speechEvent = null;
            if (additionalSapiFeatures)
            {
                SPEVENTEX sapiEventEx;
                ((ISpEventSource2)sapiEventSource).GetEventsEx(1, out sapiEventEx, out fetched);
                if (fetched == 1)
                {
                    speechEvent = new SpeechEvent(sapiEventEx);
                }
            }
            else
            {
                SPEVENT sapiEvent;
                sapiEventSource.GetEvents(1, out sapiEvent, out fetched);
                if (fetched == 1)
                {
                    speechEvent = new SpeechEvent(sapiEvent, audioFormat);
                }
            }

            return speechEvent;
        }

        #endregion

        #region Internal Properties

        internal SPEVENTENUM EventId
        {
            get { return _eventId; }
        }
        internal ulong AudioStreamOffset
        {
            get { return _audioStreamOffset; }
        }

        // The WParam is returned as a 64-bit value since unmanaged wParam is always 32 or 64 depending on architecture.
        // This is always some kind of numeric value in SAPI - it is never a pointer that needs to freed.
        internal ulong WParam
        {
            get { return _wParam; }
        }

        internal ulong LParam
        {
            get { return _lParam; }
        }

        internal TimeSpan AudioPosition
        {
            get { return _audioPosition; }
        }

        #endregion

        #region Private Fields

        private SPEVENTENUM _eventId;
        private SPEVENTLPARAMTYPE _paramType;
        private ulong _audioStreamOffset;
        private ulong _wParam;
        private ulong _lParam;
        private TimeSpan _audioPosition;
        private int _sizeMemoryPressure;

        #endregion
    }

    internal enum SPEVENTLPARAMTYPE : ushort
    {
        SPET_LPARAM_IS_UNDEFINED = 0x0000,
        SPET_LPARAM_IS_TOKEN = 0x0001,
        SPET_LPARAM_IS_OBJECT = 0x0002,
        SPET_LPARAM_IS_POINTER = 0x0003,
        SPET_LPARAM_IS_STRING = 0x0004
    }
}