Click here to Skip to main content
13,626,436 members
Click here to Skip to main content

Stats

663.6K views
29.1K downloads
274 bookmarked
Posted 14 Sep 2010
Licenced CPOL

PVS.AVPlayer - MCI Audio and Video Library

, 1 Jun 2018
Windows Media Control Interface (MCI) library with many added features
PVS.AVPlayer
PVS.AVPlayer .NET 2.0
PVS.AVPlayer.XML
PVS.AVPlayer .NET 3.0
PVS.AVPlayer.XML
PVS.AVPlayer .NET 3.5
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.0
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.5
PVS.AVPlayer .NET 4.5.1
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.5.2
PVS.AVPlayer.XML
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.6
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.6.1
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.6.2
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.7
PVS.AVPlayer .NET 4.7.1
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.7.2
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer All Source Code
AVPlayerExample
AVPlayerExample
AVPlayerExample.csproj.user
bin
Debug
PVS.AVPlayer.XML
Release
Dialogs
Display Overlays
obj
Debug
Release
x86
Debug
Release
Properties
Resources
Crystal Italic1.ttf
WingDings3a.ttf
Voice Recorder
FolderView
FolderView
bin
Debug
PVS.AVPlayer.XML
Release
FolderView.csproj.user
obj
Release
x86
Debug
Release
Properties
Resources
Crystal Italic1.ttf
PVS.AVPlayer
AVPlayerExample.csproj.user
PVS.AVPlayer.dll
PVS.AVPlayer.XML
Custom Items
Native Methods
Bob.png
Crystal Italic1.ttf
Dial Green 2.png
Dial Green 4.png
Dial Green.png
Dial Red 2.png
Dial Red.png
media7.ico
media7a.ico
Media8.ico
Media8a.ico
VU Meter.png
WingDings3a.ttf
Sound Recorder
Various
About Dialog
PVS.AVPlayer.dll
PVS.AVPlayer.XML
Custom Items
FolderView.csproj.user
Bob.png
Crystal Italic1.ttf
media7a.ico
media7b.ico
Media8a.ico
Media8b.ico
Subtitles Overlay
Various
How To (C#)
PVSAVPlayerHowTo
bin
Debug
PVS.AVPlayer.dll
PVS.AVPlayer.XML
Release
Properties
How To (VB.NET)
PVSAVPlayerHowToVB
bin
Debug
PVS.AVPlayer.dll
PVS.AVPlayer.XML
Release
My Project
Application.myapp
PVSAVPlayerHowTo.vbproj.user
PVS.AVPlayer Examples
AVPlayerExample.ex_
FolderView.ex_
AVPlayerExample.exe
FolderView.exe
PVS.AVPlayer.dll
    /****************************************************************
    
    PVS.AVPlayer - Version 0.90
    June 2018, The Netherlands
    © Copyright 2018 PVS The Netherlands - Free to Use

    ****************

    For use with Microsoft .NET Framework version 2.0 or higher and any CPU.
    Created with Microsoft Visual Studio.

    Articles on CodeProject with information on the use of the PVS.AVPlayer library:
    About the Player: http://www.codeproject.com/Articles/109714/PVS-AVPlayer-MCI-Audio-and-Video-Library
    About the Sound Recorder: http://www.codeproject.com/Articles/1116698/PVS-AVPlayer-MCI-Sound-Recorder

    ****************

    The PVS.AVPlayer library source code is divided into 10 files:

     1. Player.cs            - Player source code
     2. MouseEvents.cs       - extension source code of Player.cs, provides player display mouse events
     3. OutputLevel.cs       - extension source code of Player.cs, provides player audio output level data
     4. DisplayClones.cs     - extension source code of Player.cs, provides player multiple video displays 
     5. Subtitles.cs         - extension source code of Player.cs, provides player SubRip (.srt) subtitles
     6. Signals.cs           - extension source code of Player.cs, provides player media position signaling
     7. CursorHide.cs        - extension source code of Player.cs, hides the mouse cursor during inactivity
     8. Recorder.cs          - Sound Recorder source code
     9. PlayerRecorder.cs    - code used by both Player.cs (and its extension files) and Recorder.cs
    10. Infolabel.cs         - Info Label (custom ToolTip) source code

    Required references:
    System
    System.Drawing
    System.Windows.Forms

    ****************

    This file: Recorder.cs

    Recorder Class
    Uses file 'PlayerRecorder.cs'.

    ****************

    About Media Control Interface (MCI)
    - for information about MCI, please see https://msdn.microsoft.com/en-us/library/vs/alm/dd757151(v=vs.85).aspx
    - you can find many articles about mci on the internet, search examples: 'c# mci', 'vb mci', 'mcisendstring'
      or the subject of your question.
    - the PVS.AVPlayer library also provides 'direct access' to MCI (e.g. 'Mci.MciSendString').

    ****************

    The PVS.AVPlayer.Recorder 'operating steps':
    1. open recorder (mci device; e.g. 'open new type waveaudio alias myRecorder')
    2. set recorder properties (e.g. 'set myRecorder set ...' (please see Record() method))
    3. start recording (e.g. 'record myRecorder')
    4. stop recording (e.g. 'stop myRecorder')
    5. save recording (e.g. 'save myRecorder C:\MyRecording.wav')
    6. close recorder (mci device; e.g. 'close myRecorder')

    The 'open' and 'close' device commands are 'hidden' from the user:
    The user only has to use the 'record' and 'stop' commands.

    ****************

    Thanks!

    Many thanks to Microsoft (Windows, .NET Framework, Visual Studio Express, etc.), all the people
    writing about programming on the internet (a great source for ideas and solving problems),
    the websites publishing those or other writings about programming, the people responding
    to the PVS.AVPlayer articles with comments and suggestions and, of course, CodeProject:
    thank you Deeksha, Smitha and Sean Ewington for the beautiful articles and all!

    Special thanks to Sean Ewington of CodeProject who also took care of publishing the many code
    updates and changes in the PVS.AVPlayer articles in a friendly, fast, and highly competent manner.
    Thank you very much, Sean!

    Peter Vegter
    June 2018, The Netherlands

    ****************************************************************/

#region Usings

using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;

#endregion

namespace PVS.AVPlayer
{
    // ******************************** PVS.AVPlayer (Recorder) - Enumerations

    #region PVS.AVPlayer (Recorder) - Enumerations - Channels / Bits / SampleRate / MarqueeMode

    /// <summary>
    /// Specifies the number of input channels for recording.
    /// </summary>
    public enum Channels
    {
        /// <summary>
        /// The recorder uses one input channel (mono).
        /// </summary>
        Mono = 1,
        /// <summary>
        /// The recorder uses two input channels (stereo).
        /// </summary>
        Stereo = 2
    }

    /// <summary>
    /// Specifies the number of bits per sample for recording.
    /// </summary>
    public enum Bits
    {
        /// <summary>
        /// The recorder uses 8 bits per sample.
        /// </summary>
        Bits_08 = 8,
        /// <summary>
        /// The recorder uses 16 bits per sample.
        /// </summary>
        Bits_16 = 16,
        /// <summary>
        /// The recorder uses 24 bits per sample.
        /// </summary>
        Bits_24 = 24,
        /// <summary>
        /// The recorder uses 32 bits per sample.
        /// </summary>
        Bits_32 = 32
    }

    /// <summary>
    /// Specifies the number of samples per second for recording.
    /// </summary>
    public enum SampleRate
    {
        /// <summary>
        /// The recorder uses 8000 samples per second.
        /// </summary>
        Samples_8000 = 8000,
        /// <summary>
        /// The recorder uses 11025 samples per second.
        /// </summary>
        Samples_11025 = 11025,
        /// <summary>
        /// The recorder uses 16000 samples per second.
        /// </summary>
        Samples_16000 = 16000,
        /// <summary>
        /// The recorder uses 22050 samples per second.
        /// </summary>
        Samples_22050 = 22050,
        /// <summary>
        /// The recorder uses 32000 samples per second.
        /// </summary>
        Samples_32000 = 32000,
        /// <summary>
        /// The recorder uses 44100 samples per second.
        /// </summary>
        Samples_44100 = 44100,
        /// <summary>
        /// The recorder uses 48000 samples per second.
        /// </summary>
        Samples_48000 = 48000,
        /// <summary>
        /// The recorder uses 64000 samples per second.
        /// </summary>
        Samples_64000 = 64000,
        /// <summary>
        /// The recorder uses 88200 samples per second.
        /// </summary>
        Samples_88200 = 88200,
        /// <summary>
        /// The recorder uses 96000 samples per second.
        /// </summary>
        Samples_96000 = 96000,
        /// <summary>
        /// The recorder uses 176400 samples per second.
        /// </summary>
        Samples_176400 = 176400,
        /// <summary>
        /// The recorder uses 192000 samples per second.
        /// </summary>
        Samples_192000 = 192000
    }

    /// <summary>
    /// Specifies the appearance of the taskbar marquee indicator during recordings.
    /// </summary>
    public enum TaskbarMarqueeMode
    {
        /// <summary>
        /// A pulsing green indicator is displayed in the taskbar button.
        /// </summary>
        PulsingGreen,
        /// <summary>
        /// A non-flashing red indicator is displayed in the taskbar button.
        /// </summary>
        SteadyRed,
        /// <summary>
        /// A flashing red indicator is displayed in the taskbar button.
        /// </summary>
        FlashingRed
    }

    #endregion

    // ******************************** PVS.AVPlayer (Recorder) - EventArgs

    #region PVS.AVPlayer (Recorder) - EventArgs - RecorderPositionEventArgs / InputLevelEventArgs

    /// <summary>
    /// Provides data for the Recorder.RecorderPositionChanged event.
    /// </summary>
    [CLSCompliant(true)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class RecorderPositionEventArgs : HideObjectEventArgs
    {
        internal TimeSpan _position;

        /// <summary>
        /// Gets the current recording position.
        /// </summary>
        public TimeSpan Position
        {
            get { return _position; }
        }
    }

    /// <summary>
    /// Provides data for the Recorder.RecorderInputLevelChanged event.
    /// </summary>
    [CLSCompliant(true)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class InputLevelEventArgs : HideObjectEventArgs
    {
        internal Channels   _channels;
        internal int        _leftLevel;
        internal int        _rightLevel;

        /// <summary>
        /// Gets the current number of input channels (mono or stereo).
        /// </summary>
        public Channels Channels
        {
            get { return _channels; }
        }

        /// <summary>
        /// Gets the current level of the left (or mono) audio input channel. Values from 0 to 32767.
        /// </summary>
        public int LeftLevel
        {
            get { return _leftLevel; }
        }

        /// <summary>
        /// Gets the current level of the right (stereo) audio input channel. Values from 0 to 32767.
        /// </summary>
        public int RightLevel
        {
            get { return _rightLevel; }
        }
    }

    #endregion


    /// <summary>
    /// Represents a recorder that can be used to record audio using Microsoft Windows built-in Media Control Interface (MCI).
    /// </summary>
    [CLSCompliant(true)]
    public sealed class Recorder : HideObjectMembers, IDisposable
    {

        // ******************************** Event Declarations (Recorder Class)

        #region Event Declarations (Recorder Class)

        internal event EventHandler RecorderStarted;
        internal event EventHandler RecorderPaused;
        internal event EventHandler RecorderResumed;
        internal event EventHandler RecorderStopped;
        internal event EventHandler RecorderSaveRequest;
        internal event EventHandler<RecorderPositionEventArgs> RecorderPositionChanged;
        private EventHandler<InputLevelEventArgs> _recorderInputLevelChanged;
        internal event EventHandler<InputLevelEventArgs> RecorderInputLevelChanged
        {
            add
            {
                _lastError = Global.MCIERR_NO_ERROR;
                _recorderInputLevelChanged += value;
                if (_recorderInputLevelChanged != null && !_hasLevelDevice)
                {
                    if (SetLevelDevice(true) != Global.MCIERR_NO_ERROR) _lastError = Global.MCIERR_DEVICE_NOT_READY;
                }
            }

            remove
            {
                _recorderInputLevelChanged -= value;
                if (_recorderInputLevelChanged == null && _hasLevelDevice) CloseLevelDevice();
                _lastError = Global.MCIERR_NO_ERROR;
            }
        }
        internal event EventHandler RecorderInputDeviceChanged;
        internal event EventHandler RecorderChannelsChanged;
        internal event EventHandler RecorderBitsChanged;
        internal event EventHandler RecorderSampleRateChanged;

        #endregion

        // ******************************** On Event Methods (Recorder Class)

        #region On Event Methods (Recorder Class)

        internal void OnRecorderInputDeviceChanged()
        {
            if (RecorderInputDeviceChanged != null) RecorderInputDeviceChanged(this, EventArgs.Empty);
        }

        #endregion


        // ******************************** Fields (Recorder Class)

        #region Fields (Recorder Class)

        // Timer default values
        private const int       DEFAULT_TIMERINTERVAL   = 100;
        private const int       DEFAULT_DELAYINTERVAL   = 50;
        private const int       DEFAULT_DELAYCOUNTER    = 10;

        private const int       TASKBAR_FLASH_INTERVAL  = 600;

        // MCI Devices
        internal bool           _hasDevice;
        internal string         _deviceId;

        internal int            _inputDeviceIndex = -1;                     // -1 = system default input device
        internal bool           _hasInputDeviceName;
        internal string         _inputDeviceName;

        internal bool           _hasLevelDevice;
        private string          _levelDeviceId;

        // EventArgs
        private InputLevelEventArgs         _levelArgs;
        private RecorderPositionEventArgs   _positionArgs;

        // Status
        internal int            _lastError;
        internal bool           _isRecording;
        internal bool           _isPaused;
        private bool            _resumeIsRecord;


        // Settings
        private Channels        _recChannels    = Channels.Mono;             // channels can be 1 (mono) or 2 (stereo)
        private Bits            _recBits        = Bits.Bits_08;              // bits per sample
        private SampleRate      _recSampleRate  = SampleRate.Samples_11025;  // samples per second

        private int             _recBytes       = 11025;                     // average bytes/sec calculate: recChannels * recBits * recSampleRate / 8
        private int             _recAlignment   = 1;                         // block alignment calculate: recChannels * recBits / 8
            
        // Timer used with position changed event and audio level meter
        internal Timer          _timer;
        private Timer           _delayTimer; // used with delayed activation of new level device - discarding spurious values
        private int             _delayCounter;

        // Used with sending MCI command text and error text
        internal StringBuilder  _textBuffer;
        private StringBuilder   _levelBuffer;

        // Miscellaneous
        private bool            _disposed;

        // Member grouping classes
        private InputDevice     _inputClass;
        private MciRecDevice    _mciClass;
        private RecEvents       _eventsClass;
        private TaskbarMarquee  _taskbarMarquee;

        // Taskbar indicator
        internal static TaskbarIndicator.ITaskbarList3 TaskbarInstance;
        internal static bool        _taskbarMarqueeEnabled;
        internal bool               _hasTaskbarMarquee;
        internal TaskbarMarqueeMode _taskbarMode;
        internal int                _taskbarCounter; // flash counter
        internal bool               _taskbarFlashOn; // flash state

        #endregion


        // ******************************** Recorder Constructor / Dispose / Destructor / Version

        #region Recorder Constructor

        /// <summary>
        /// Initializes a new instance of the PVS.AVPlayer.Recorder class (creates a new sound recorder).
        /// </summary>
        public Recorder()
        {
            _deviceId = Global.RandomNumber.Next(10, 100000000) + "AVR";
            _textBuffer = new StringBuilder(Global.BUFFER_SIZE);
            _levelBuffer = new StringBuilder(Global.LEVEL_BUFFER_SIZE);

            _positionArgs = new RecorderPositionEventArgs();
            _levelArgs = new InputLevelEventArgs();

            _timer = new Timer { Interval = DEFAULT_TIMERINTERVAL };
            _timer.Tick += TimerTick;

            _inputClass = new InputDevice(this);
        }

        #endregion

        #region Recorder Dispose / Finalizer

        /// <summary>
        /// Remove the recorder and clean up any resources being used.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Remove the recorder and clean up any resources being used.
        /// </summary>
        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                _disposed = true;
                if (disposing)
                {
                    if (_delayTimer != null)
                    {
                        _delayTimer.Stop();
                        _delayTimer.Dispose();
                        _delayTimer = null;
                    }

                    if (_timer != null)
                    {
                        _timer.Dispose();
                        _timer = null;
                    }

                    if (_hasTaskbarMarquee) TaskbarMarquee.Clear();
                }

                if (_hasDevice)
                {
                    SafeNativeMethods.mciSendString("close " + _deviceId, null, 0, IntPtr.Zero);
                    _hasDevice = false;
                }
                if (_hasLevelDevice)
                {
                    SafeNativeMethods.mciSendString("close " + _levelDeviceId, null, 0, IntPtr.Zero);
                    _hasLevelDevice = false;
                }
            }
        }

        /// <summary>
        /// Remove the recorder and clean up any resources being used.
        /// </summary>
        ~Recorder()
        {
            Dispose(false);
        }

        #endregion

        #region PVS.AVPlayer Version

        /// <summary>
        /// Gets the version number of the PVS.AVPlayer library.
        /// </summary>
        public static float Version
        {
            get { return Global.VERSION; }
        }

        /// <summary>
        /// Gets the version string of the PVS.AVPlayer library.
        /// </summary>
        public static string VersionString
        {
            get { return Global.VERSION_STRING; }
        }

        #endregion

        // ******************************** Error Information

        #region Error Information

        /// <summary>
        /// Gets a value indicating whether an error has occurred with the last recorder instruction.
        /// </summary>
        public bool LastError
        {
            get { return _lastError != Global.MCIERR_NO_ERROR; }
        }

        /// <summary>
        /// Gets the code of the last error of the recorder that has occurred (0 = no error).
        /// </summary>
        public int LastErrorCode
        {
            get { return _lastError; }
        }

        /// <summary>
        /// Gets a description of the last error of the recorder that has occurred.
        /// </summary>
        public string LastErrorString
        {
            get { return GetErrorString(_lastError); }
        }

        /// <summary>
        /// Returns a description of the specified error code.
        /// </summary>
        /// <param name="errorCode">The error code whose description needs to be obtained.</param>
        public string GetErrorString(int errorCode)
        {
            SafeNativeMethods.mciGetErrorString(errorCode, _textBuffer, Global.BUFFER_SIZE);
            return _textBuffer.ToString();
        }

        #endregion

        // ******************************** Channels / Bits / SampleRate

        #region Channels / Bits / SampleRate

        /// <summary>
        /// Gets or sets the number of channels used for recording (default: Channels.Mono).
        /// </summary>
        public Channels Channels
        {
            get { return _recChannels; }
            set
            {
                if (value != _recChannels)
                {
                    _recChannels = value;
                    if (_hasLevelDevice) SetLevelDevice(false);
                    if (RecorderChannelsChanged != null) RecorderChannelsChanged(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Gets or sets the number of bits per sample used for recording (default: Bits.Bits8).
        /// </summary>
        public Bits Bits
        {
            get { return _recBits; }
            set
            {
                if (value != _recBits)
                {
                    _recBits = value;
                    //if (_hasLevelDevice) SetLevelDevice(false);
                    if (RecorderBitsChanged != null) RecorderBitsChanged(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Gets or sets the number of samples per second used for recording (default: SampleRate.Samples11025).
        /// </summary>
        public SampleRate SampleRate
        {
            get { return _recSampleRate; }
            set
            {
                if (value != _recSampleRate)
                {
                    _recSampleRate = value;
                    //if (_hasLevelDevice) SetLevelDevice(false);
                    if (RecorderSampleRateChanged != null) RecorderSampleRateChanged(this, EventArgs.Empty);
                }
            }
        }

        #endregion

        // ******************************** Record / Recording

        #region Record / Recording

        /// <summary>
        /// Starts a new recording.
        /// </summary>
        public int Record()
        {
            int deviceCount = DeviceInfo.GetAllInputDeviceNames().Length;
            if (_inputDeviceIndex < 0 || deviceCount == 0 || _inputDeviceIndex >= deviceCount)
            {
                if (deviceCount == 0) _lastError = Global.MCIERR_WAVE_INPUTSUNSUITABLE;
                else _lastError = Global.MCIERR_WAVE_INPUTUNSPECIFIED;
                return _lastError;
            }

            if (_hasDevice) Stop();
            int aliasRepeat = Global.ALIAS_REPEAT;
            do
            {
                _lastError = SafeNativeMethods.mciSendString("open new type waveaudio alias " + _deviceId, null, 0, IntPtr.Zero);
                Application.DoEvents();
                if (_lastError == Global.MCIERR_DUPLICATE_ALIAS) _deviceId = Global.RandomNumber.Next(10, 100000000) + "AVRD";
            }
            while (_lastError == Global.MCIERR_DUPLICATE_ALIAS && aliasRepeat-- > 0);

            if (_lastError != Global.MCIERR_NO_ERROR) return _lastError;
            _hasDevice = true;

            // calculate the bytes per second and alignment
            _recBytes = (int) _recChannels * (int) _recBits * (int) _recSampleRate / 8;
            _recAlignment = (int) _recChannels * (int) _recBits / 8;

            // apply settings
            _textBuffer.Length = 0;
            // this has to be set in one commandline and in this order
            _textBuffer.Append("set ").Append(_deviceId)
                .Append(" time format ms bitspersample ").Append((int)_recBits)
                .Append(" channels ").Append((int)_recChannels)
                .Append(" alignment ").Append(_recAlignment)
                .Append(" samplespersec ").Append((int)_recSampleRate)
                .Append(" bytespersec ").Append(_recBytes)
                .Append(" format tag pcm");

            _lastError = SafeNativeMethods.mciSendString(_textBuffer.ToString(), null, 0, IntPtr.Zero);
            if (_lastError == Global.MCIERR_NO_ERROR)
            {
                // set inputdevice
                _lastError = SafeNativeMethods.mciSendString("set " + _deviceId + " input " + _inputDeviceIndex.ToString(), null, 0, IntPtr.Zero);
                if (_lastError == Global.MCIERR_NO_ERROR)
                {
                    // start recording
                    _lastError = SafeNativeMethods.mciSendString("record " + _deviceId, null, 0, IntPtr.Zero);
                    if (_lastError == Global.MCIERR_NO_ERROR)
                    {
                        _isRecording = true;
                        if (_isPaused) SafeNativeMethods.mciSendString("pause " + _deviceId, null, 0, IntPtr.Zero);
                        else
                        {
                            if (!_hasLevelDevice && (RecorderPositionChanged != null || (_hasTaskbarMarquee && _taskbarMode == TaskbarMarqueeMode.FlashingRed))) _timer.Start();
                            if (_hasTaskbarMarquee)
                            {
                                _taskbarCounter = 0;
                                _taskbarFlashOn = true;
                                _taskbarMarquee.SetState(TaskbarStates.Indeterminate);
                            }
                        }
                        if (RecorderStarted != null) RecorderStarted(this, EventArgs.Empty);
                    }
                }
            }
            if (_lastError != Global.MCIERR_NO_ERROR)
            {
                SafeNativeMethods.mciSendString("close " + _deviceId + " wait", null, 0, IntPtr.Zero);
                _hasDevice = _isRecording = false;
            }
            return _lastError;
        }

        /// <summary>
        /// Gets a value indicating whether the recorder is recording (includes state paused).
        /// </summary>
        public bool Recording
        {
            get { return _isRecording; }
        }

        #endregion

        // ******************************** Pause / Paused / Resume / Stop / StopAndSave

        #region Pause / Paused / Resume / Stop / StopAndSave

        /// <summary>
        /// Activates the pause mode of the recorder (pauses recording).
        /// </summary>
        public int Pause()
        {
            _lastError = Global.MCIERR_NO_ERROR;
            if (!_isPaused)
            {
                if (_isRecording)
                {
                    _lastError = SafeNativeMethods.mciSendString("pause " + _deviceId, null, 0, IntPtr.Zero);
                    if (!_hasLevelDevice) _timer.Stop();
                    if (_hasTaskbarMarquee) _taskbarMarquee.SetState(TaskbarStates.NoProgress);
                }
                _isPaused = true;
                if (RecorderPaused != null) RecorderPaused(this, EventArgs.Empty);
            }
            return _lastError;
        }

        /// <summary>
        /// Gets or sets a value indicating whether the pause mode of the recorder is activated.
        /// </summary>
        public bool Paused
        {
            get { return _isPaused; }
            set
            {
                if (value) Pause();
                else Resume();
            }
        }

        /// <summary>
        /// Deactivates the pause mode of the recorder (resumes recording).
        /// </summary>
        public int Resume()
        {
            _lastError = Global.MCIERR_NO_ERROR;
            if (_isPaused)
            {
                if (_isRecording)
                {
                    if (_resumeIsRecord) _lastError = SafeNativeMethods.mciSendString("record " + _deviceId, null, 0, IntPtr.Zero);
                    else _lastError = SafeNativeMethods.mciSendString("resume " + _deviceId, null, 0, IntPtr.Zero);
                    _resumeIsRecord = false;
                    if (!_hasLevelDevice && (RecorderPositionChanged != null || (_hasTaskbarMarquee && _taskbarMode == TaskbarMarqueeMode.FlashingRed))) _timer.Start();
                    if (_hasTaskbarMarquee)
                    {
                        _taskbarCounter = 0;
                        _taskbarFlashOn = true;
                        _taskbarMarquee.SetState(TaskbarStates.Indeterminate);
                    }
                }
                _isPaused = false;
                if (RecorderResumed != null) RecorderResumed(this, EventArgs.Empty);
            }
            return _lastError;
        }

        /// <summary>
        /// Stops recording.
        /// </summary>
        public int Stop()
        {
            if (_hasDevice)
            {
                if (_hasTaskbarMarquee) _taskbarMarquee.SetState(TaskbarStates.NoProgress);

                if (_isRecording)
                {
                    SafeNativeMethods.mciSendString("stop " + _deviceId + " wait", null, 0, IntPtr.Zero);
                    if (!_hasLevelDevice) _timer.Stop();
                    _isRecording = false;
                    if (RecorderStopped != null) RecorderStopped(this, EventArgs.Empty);
                    if (RecorderSaveRequest != null && Length > 0) RecorderSaveRequest(this, EventArgs.Empty);
                    if (RecorderPositionChanged != null)
                    {
                        _positionArgs._position = TimeSpan.Zero;
                        RecorderPositionChanged(this, _positionArgs);
                    }
                }

                SafeNativeMethods.mciSendString("close " + _deviceId + " wait", null, 0, IntPtr.Zero);
                _resumeIsRecord = false;
                _hasDevice = false;
            }
            _lastError = Global.MCIERR_NO_ERROR;
            return _lastError;
        }

        /// <summary>
        /// Stops recording and saves the recording to the specified waveform audio file (same as Recorder.SaveAndStop).
        /// </summary>
        /// <param name="fileName">The file to which to save the recording.</param>
        /// <param name="addExtension">A value indicating whether to add the .wav extension to the specified filename if omitted.</param>
        public int StopAndSave(string fileName, bool addExtension)
        {
            int error = Global.MCIERR_DEVICE_NOT_READY;
            if (_hasDevice)
            {
                error = Global.MCIERR_NO_ERROR;
                if (_hasTaskbarMarquee) _taskbarMarquee.SetState(TaskbarStates.NoProgress);

                if (_isRecording)
                {
                    if (Length > 0 && !string.IsNullOrEmpty(fileName))
                    //if (!string.IsNullOrEmpty(fileName))
                    {
                        if (addExtension && !fileName.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
                        {
                            error = SafeNativeMethods.mciSendString("save " + _deviceId + " \"" + fileName + ".wav\"", null, 0, IntPtr.Zero);
                        }
                        else
                        {
                            error = SafeNativeMethods.mciSendString("save " + _deviceId + " \"" + fileName + "\"", null, 0, IntPtr.Zero);
                        }
                    }

                    SafeNativeMethods.mciSendString("stop " + _deviceId + " wait", null, 0, IntPtr.Zero);
                    if (!_hasLevelDevice) _timer.Stop();
                    _isRecording = false;
                    if (RecorderStopped != null) RecorderStopped(this, EventArgs.Empty);
                    if (RecorderPositionChanged != null)
                    {
                        _positionArgs._position = TimeSpan.Zero;
                        RecorderPositionChanged(this, _positionArgs);
                    }
                }
                SafeNativeMethods.mciSendString("close " + _deviceId + " wait", null, 0, IntPtr.Zero);
                _resumeIsRecord = false;
                _hasDevice = false;
            }
            _lastError = error;
            return _lastError;
        }

        #endregion

        // ******************************** Timers Events / Interval

        #region Timers Events / Interval

        private void _delayTimer_Tick(object sender, EventArgs e)
        {
            _levelBuffer.Length = 0;
            SafeNativeMethods.mciSendString("status " + _levelDeviceId + " level", _levelBuffer, Global.LEVEL_BUFFER_SIZE, IntPtr.Zero);
            if (--_delayCounter <= 0)
            {
                _delayTimer.Stop();
                _delayTimer.Dispose();
                _delayTimer = null;
                _hasLevelDevice = true;
            }
        }

        private void TimerTick(object sender, EventArgs e)
        {
            if (_isRecording && !_isPaused)
            {
                if (_hasTaskbarMarquee && _taskbarMode == TaskbarMarqueeMode.FlashingRed)
                {
                    _taskbarCounter += _timer.Interval;
                    if (_taskbarCounter >= TASKBAR_FLASH_INTERVAL)
                    {
                        _taskbarCounter = 0;
                        _taskbarFlashOn = !_taskbarFlashOn;
                        if (_taskbarFlashOn) _taskbarMarquee.SetState(TaskbarStates.Indeterminate);
                        else _taskbarMarquee.SetState(TaskbarStates.NoProgress);
                    }
                }

                if (RecorderPositionChanged != null)
                {
                    _positionArgs._position = Position;
                    RecorderPositionChanged(this, _positionArgs);
                }
            }

            if (_hasLevelDevice)
            {
                _levelBuffer.Length = 0;
                if (SafeNativeMethods.mciSendString("status " + _levelDeviceId + " level", _levelBuffer, Global.LEVEL_BUFFER_SIZE, IntPtr.Zero) == Global.MCIERR_NO_ERROR)
                {
                    int result = 0;
                    for (int count = 0; count < _levelBuffer.Length; ++count)
                    {
                        result = 10 * result + (_levelBuffer[count] - 48);
                    }

                    //if (_recBits == Bits.Bits8)
                    //{
                    //    _levelArgs._leftLevel = (int)(((result - 1) << 8) & 0x7F00);
                    //    _levelArgs._rightLevel = (int)(((result >> 8) - 256) & 0x7F00);
                    //}
                    //else if (_recBits == Bits.Bits24)
                    //{
                    //    _levelArgs._leftLevel = (int)((result - 1) & 0x7FFF);
                    //    _levelArgs._rightLevel = (int)(((result >> 16) - 1) & 0x7FFF);
                    //}
                    //else
                    //{
                    //    _levelArgs._leftLevel = (int)((result - 1) & 0x7FFF);
                    //    _levelArgs._rightLevel = (int)(((result >> 16) - 1) & 0x7FFF);
                    //}

                    _levelArgs._leftLevel = (result - 1) & 0x7FFF;
                    _levelArgs._rightLevel = ((result >> 16) - 1) & 0x7FFF;
                }
                else
                {
                    // input device fail - probably disabled
                    // close input and level device ?

                    //CloseLevelDevice();
                    //_hasInputDeviceName = false;
                    //if (this.RecorderInputDeviceChanged != null) this.RecorderInputDeviceChanged(this, EventArgs.Empty);

                    //InputDeviceIndex = 0;

                    // for now
                    _levelArgs._leftLevel = _levelArgs._rightLevel = -1;
                }

                _levelArgs._channels = _recChannels;
                _recorderInputLevelChanged(this, _levelArgs);
            }
        }

        /// <summary>
        /// Gets or sets the interval of the RecorderPositionChanged and RecorderInputLevelChanged events timer of the recorder. The value is in milliseconds (default: 100 ms).
        /// </summary>
        public int TimerInterval
        {
            get { return _timer.Interval; }
            set
            {
                _timer.Interval = value <= 10 ? 10 : value;
            }
        }

        #endregion

        // ******************************** Position / Length / LengthBytes

        #region Position / Length / LengthBytes

        /// <summary>
        /// Gets the recording position of the current recording. The position is rounded down to full seconds during recording. Pause the recording to get the actual recording time.
        /// </summary>
        public TimeSpan Position
        {
            get
            {
                _textBuffer.Length = 0;
                if (_isRecording && SafeNativeMethods.mciSendString("status " + _deviceId + " position", _textBuffer, Global.BUFFER_SIZE, IntPtr.Zero) == Global.MCIERR_NO_ERROR)
                {
                    int number = 0;
                    for (int count = 0; count < _textBuffer.Length; ++count)
                    {
                        number = 10 *number + (_textBuffer[count] - 48);
                    }
                    return TimeSpan.FromMilliseconds(number);
                }
                return TimeSpan.Zero;
            }
        }

        /// <summary>
        /// Gets the duration (length) of the current recording. The duration is rounded down to full seconds during recording. Pause the recording to get the actual recording duration.
        /// </summary>
        public TimeSpan Duration
        {
            get
            {
                _textBuffer.Length = 0;
                if (_isRecording && SafeNativeMethods.mciSendString("status " + _deviceId + " length", _textBuffer, Global.BUFFER_SIZE, IntPtr.Zero) == Global.MCIERR_NO_ERROR)
                {
                    int number = 0;
                    for (int count = 0; count < _textBuffer.Length; ++count)
                    {
                        number = 10 * number + (_textBuffer[count] - 48);
                    }
                    return TimeSpan.FromMilliseconds(number);
                }
                return TimeSpan.Zero;
            }
        }

        /// <summary>
        /// Gets the length of the current recording in milliseconds. The length is rounded down to full seconds during recording. Pause the recording to get the actual recording length.
        /// </summary>
        public int Length
        {
            get
            {
                return SafeNativeMethods.mciSendString("status " + _deviceId + " length", _textBuffer, Global.BUFFER_SIZE, IntPtr.Zero) == Global.MCIERR_NO_ERROR ? int.Parse(_textBuffer.ToString()) : 0;
            }
        }

        /// <summary>
        /// Gets the length of the current recording in bytes. The byte length is rounded down to full seconds during recording. Pause the recording to get the actual byte length.
        /// </summary>
        public int LengthBytes
        {
            get
            {
                int result = 0;
                if (_hasDevice)
                {
                    _textBuffer.Length = 0;
                    SafeNativeMethods.mciSendString("set " + _deviceId + " time format bytes", null, 0, IntPtr.Zero);
                    result = SafeNativeMethods.mciSendString("status " + _deviceId + " length", _textBuffer, Global.BUFFER_SIZE, IntPtr.Zero) == Global.MCIERR_NO_ERROR ? int.Parse(_textBuffer.ToString()) : 0;
                    SafeNativeMethods.mciSendString("set " + _deviceId + " time format ms", null, 0, IntPtr.Zero);
                }
                return result;
            }
        }

        #endregion

        // ******************************** Remove / Save / SaveAndStop

        #region Remove / Save / SaveAndStop

        /// <summary>
        /// Removes a portion of the current recording.
        /// </summary>
        /// <param name="beginPosition">Specifies the position at which removing begins in milliseconds.</param>
        /// <param name="endPosition">Specifies the position at which removing ends in milliseconds. The value 0 (zero) indicates the end of the recording.</param>
        public int Remove(int beginPosition, int endPosition)
        {
            _lastError = Global.MCIERR_DEVICE_NOT_READY;
            if (_hasDevice)
            {
                _textBuffer.Length = 0;
                _textBuffer.Append("delete ").Append(_deviceId).Append(" from ").Append(beginPosition);
                if (endPosition > 0) _textBuffer.Append(" to ").Append(endPosition);
                _lastError = SafeNativeMethods.mciSendString(_textBuffer.ToString(), null, 0, IntPtr.Zero);
            }
            return _lastError;
        }

        /// <summary>
        /// Saves the current recording to the specified waveform audio file.
        /// </summary>
        /// <param name="fileName">The file to which to save the recording.</param>
        /// /// <param name="addExtension">A value indicating whether to add the .wav extension to the specified filename if omitted.</param>
        public int Save(string fileName, bool addExtension)
        {
            _lastError = Global.MCIERR_DEVICE_NOT_READY;
            if (_hasDevice)
            {
                if (string.IsNullOrEmpty(fileName))
                {
                    _lastError = Global.MCIERR_FILENAME_REQUIRED;
                }
                else
                {
                    if (Length > 0)
                    {
                        if (addExtension && !fileName.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)) _lastError = SafeNativeMethods.mciSendString("save " + _deviceId + " \"" + fileName + ".wav\"", null, 0, IntPtr.Zero);
                        else _lastError = SafeNativeMethods.mciSendString("save " + _deviceId + " \"" + fileName + "\"", null, 0, IntPtr.Zero);

                        if (_isPaused) _resumeIsRecord = true;
                        else
                        {
                            SafeNativeMethods.mciSendString("record " + _deviceId, null, 0, IntPtr.Zero);
                            _resumeIsRecord = false;
                        }
                    }
                    else
                    {
                        _lastError = Global.MCIERR_NO_ERROR; // length = 0
                    }
                }
            }
            return _lastError;
        }

        /// <summary>
        /// Stops recording and saves the recording to the specified waveform audio file (same as Recorder.StopAndSave).
        /// </summary>
        /// <param name="fileName">The file to which to save the recording.</param>
        /// <param name="addExtension">A value indicating whether to add the .wav extension to the specified filename if omitted.</param>
        public int SaveAndStop(string fileName, bool addExtension)
        {
            return StopAndSave(fileName, addExtension);
        }

        #endregion

        // ******************************** TaskbarMarquee

        #region TaskbarMarquee

        /// <summary>
        /// Provides access to the taskbar marquee indicator settings of the recorder (e.g. Recorder.TaskbarMarquee.Add).
        /// </summary>
        public TaskbarMarquee TaskbarMarquee
        {
            get
            {
                if (_taskbarMarquee == null) _taskbarMarquee = new TaskbarMarquee(this);
                return _taskbarMarquee;
            }
        }

        #endregion

        // ******************************** Input Device

        #region Input Device

        /// <summary>
        /// Provides access to the input device settings of the recorder (e.g. Recorder.InputDevice.Name).
        /// </summary>
        public InputDevice InputDevice
        {
            get { return _inputClass; }
        }

        #endregion

        // ******************************** Input Level Device

        #region Input Level Device

        internal int SetLevelDevice(bool set)
        {
            if (_inputDeviceIndex < 0) return Global.MCIERR_OUTOFRANGE;

            int error = Global.MCIERR_NO_ERROR;
            if (set || (_hasLevelDevice || _delayTimer != null))
            {
                if (_hasLevelDevice || _delayTimer != null) CloseLevelDevice();

                _levelDeviceId = "L" + _deviceId;
                error = SafeNativeMethods.mciSendString("open new type waveaudio alias " + _levelDeviceId, null, 0, IntPtr.Zero);
                if (error == Global.MCIERR_NO_ERROR)
                {
                    Application.DoEvents();

                    error = SafeNativeMethods.mciSendString("set " + _levelDeviceId + " input " +  _inputDeviceIndex.ToString(), null, 0, IntPtr.Zero);
                    if (error == Global.MCIERR_NO_ERROR)
                    {
                        //// calculate the bytes per second and alignment
                        //_recBytes = (int)_recChannels * (int)_recBits * (int)_recSampleRate / 8;
                        //_recAlignment = (int)_recChannels * (int)setBits / 8;

                        //_textBuffer.Length = 0;
                        //_textBuffer.Append("set ").Append(_levelDeviceId)
                        //    .Append(" time format ms bitspersample ").Append((int)_recBits)
                        //    .Append(" channels ").Append((int)_recChannels)
                        //    .Append(" alignment ").Append(_recAlignment)
                        //    .Append(" samplespersec ").Append((int)_recSampleRate)
                        //    .Append(" bytespersec ").Append(_recBytes)
                        //    .Append(" format tag pcm");
                        //SafeNativeMethods.mciSendString(_textBuffer.ToString(), null, 0, IntPtr.Zero);

                        _textBuffer.Length = 0;
                        _textBuffer.Append("set ").Append(_levelDeviceId).Append(" time format ms bitspersample 16 channels ");
                        if (_recChannels == Channels.Mono) _textBuffer.Append("1 alignment 2 samplespersec 11025 bytespersec 176400 format tag pcm");
                        else _textBuffer.Append("2 alignment 4 samplespersec 11025 bytespersec 352800 format tag pcm");

                        SafeNativeMethods.mciSendString(_textBuffer.ToString(), null, 0, IntPtr.Zero);
                        Application.DoEvents();

                        if (_recorderInputLevelChanged != null && !_timer.Enabled) _timer.Start();

                        // skip spurious values:
                        _delayTimer = new Timer();
                        _delayTimer.Interval = DEFAULT_DELAYINTERVAL;
                        _delayTimer.Tick += _delayTimer_Tick;
                        _delayCounter = DEFAULT_DELAYCOUNTER;
                        _delayTimer.Start();
                    }
                    else
                    {
                        // error: close the new device
                        SafeNativeMethods.mciSendString("close " + _levelDeviceId, null, 0, IntPtr.Zero);
                        Application.DoEvents();
                    }
                }
            }
            return error;
        }

        private void CloseLevelDevice()
        {
            if (_hasLevelDevice || _delayTimer != null)
            {
                if (_delayTimer != null)
                {
                    _delayTimer.Stop();
                    _delayTimer.Dispose();
                    _delayTimer = null;
                }
                _hasLevelDevice = false;
                if (!_isRecording || RecorderPositionChanged == null) _timer.Stop();

                SafeNativeMethods.mciSendString("close " + _levelDeviceId, null, 0, IntPtr.Zero);
                Application.DoEvents();

                if (_recorderInputLevelChanged != null)
                {
                    _levelArgs._leftLevel = _levelArgs._rightLevel = -1;
                    _levelArgs._channels = _recChannels;
                    _recorderInputLevelChanged(this, _levelArgs);
                }
            }
        }

        #endregion

        // ******************************** MCI Device

        #region MCI Device

        /// <summary>
        /// Provides direct access to the mci device associated with the recording (e.g. Recorder.MciDevice.Command).
        /// </summary>
        public MciRecDevice MciDevice
        {
            get
            {
                if (_mciClass == null) _mciClass = new MciRecDevice(this);
                return _mciClass;
            }
        }

        #endregion

        // ******************************** Show System Audio Input Control Panel

        #region System Audio Input Panel

        // non-static methods by design

        /// <summary>
        /// Opens the System Sound Control Panel (Audio Input Devices).
        /// </summary>
        public bool ShowAudioInputPanel()
        {
            return ShowAudioInputPanel(null);
        }

        /// <summary>
        /// Opens the System Sound Control Panel (Audio Input Devices).
        /// </summary>
        /// <param name="centerForm">The control panel is centered on top of the specified form.</param>
        public bool ShowAudioInputPanel(Form centerForm)
        {
            return SafeNativeMethods.CenterSystemDialog("control", "mmsys.cpl,,1", centerForm);
        }

        #endregion

        // ******************************** Events

        #region Events

        /// <summary>
        /// Provides access to the events of the recorder (e.g. Recorder.Events.RecorderStopped).
        /// </summary>
        public RecEvents Events
        {
            get
            {
                if (_eventsClass == null) _eventsClass = new RecEvents(this);
                return _eventsClass;
            }
        }

        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Peter Vegter
United States United States
No Biography provided

You may also be interested in...

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web02-2016 | 2.8.180712.1 | Last Updated 1 Jun 2018
Article Copyright 2010 by Peter Vegter
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid