Server/AudioManager.cs
author StephaneLenclud
Sat, 07 Jan 2017 20:21:42 +0100
changeset 277 71ba0dd622a5
child 278 2481c46d1f93
permissions -rw-r--r--
Created Audio Manager class.
Clean up CScore audio usage.
Fixing broken audio device change handler.
Fixed various audio Dispose deadlock due to Invoke usage.
Thus now using BeginInvoke instead.
StephaneLenclud@277
     1
using System;
StephaneLenclud@277
     2
using System.Collections.Generic;
StephaneLenclud@277
     3
using System.Linq;
StephaneLenclud@277
     4
using System.Text;
StephaneLenclud@277
     5
using System.Threading.Tasks;
StephaneLenclud@277
     6
StephaneLenclud@277
     7
//CSCore
StephaneLenclud@277
     8
using CSCore;
StephaneLenclud@277
     9
using CSCore.Win32;
StephaneLenclud@277
    10
using CSCore.DSP;
StephaneLenclud@277
    11
using CSCore.Streams;
StephaneLenclud@277
    12
using CSCore.CoreAudioAPI;
StephaneLenclud@277
    13
using CSCore.SoundIn;
StephaneLenclud@277
    14
// Visualization
StephaneLenclud@277
    15
using Visualization;
StephaneLenclud@277
    16
StephaneLenclud@277
    17
StephaneLenclud@277
    18
namespace SharpDisplayManager
StephaneLenclud@277
    19
{
StephaneLenclud@277
    20
    class AudioManager
StephaneLenclud@277
    21
    {
StephaneLenclud@277
    22
        // Volume management
StephaneLenclud@277
    23
        private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
StephaneLenclud@277
    24
        private MMNotificationClient iMultiMediaNotificationClient;
StephaneLenclud@277
    25
        private MMDevice iMultiMediaDevice;
StephaneLenclud@277
    26
        private AudioEndpointVolume iAudioEndpointVolume;
StephaneLenclud@277
    27
        private AudioEndpointVolumeCallback iAudioEndpointVolumeCallback;
StephaneLenclud@277
    28
        EventHandler<DefaultDeviceChangedEventArgs> iDefaultDeviceChangedHandler;
StephaneLenclud@277
    29
        EventHandler<AudioEndpointVolumeCallbackEventArgs> iVolumeChangedHandler;
StephaneLenclud@277
    30
StephaneLenclud@277
    31
        // Audio visualization
StephaneLenclud@277
    32
        private WasapiCapture iSoundIn;
StephaneLenclud@277
    33
        private IWaveSource iWaveSource;
StephaneLenclud@277
    34
        private LineSpectrum iLineSpectrum;
StephaneLenclud@277
    35
StephaneLenclud@277
    36
StephaneLenclud@277
    37
        public LineSpectrum Spectrum { get { return iLineSpectrum; } }
StephaneLenclud@277
    38
        public AudioEndpointVolume Volume { get { return iAudioEndpointVolume; } }
StephaneLenclud@277
    39
        public MMDevice DefaultDevice { get { return iMultiMediaDevice; } }
StephaneLenclud@277
    40
StephaneLenclud@277
    41
        /// <summary>
StephaneLenclud@277
    42
        /// 
StephaneLenclud@277
    43
        /// </summary>
StephaneLenclud@277
    44
        /// <param name="aDefaultDeviceChangedHandler"></param>
StephaneLenclud@277
    45
        /// <param name="aVolumeChangedHandler"></param>
StephaneLenclud@277
    46
        public void Open(   EventHandler<DefaultDeviceChangedEventArgs> aDefaultDeviceChangedHandler,
StephaneLenclud@277
    47
                            EventHandler<AudioEndpointVolumeCallbackEventArgs> aVolumeChangedHandler)
StephaneLenclud@277
    48
        {
StephaneLenclud@277
    49
            //Create device and register default device change notification
StephaneLenclud@277
    50
            iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
StephaneLenclud@277
    51
            iMultiMediaNotificationClient = new MMNotificationClient(iMultiMediaDeviceEnumerator);
StephaneLenclud@277
    52
            iMultiMediaNotificationClient.DefaultDeviceChanged += iDefaultDeviceChangedHandler = aDefaultDeviceChangedHandler;
StephaneLenclud@277
    53
            iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render,Role.Multimedia);
StephaneLenclud@277
    54
            //Register to get volume modifications
StephaneLenclud@277
    55
            iAudioEndpointVolume = AudioEndpointVolume.FromDevice(iMultiMediaDevice);
StephaneLenclud@277
    56
            iAudioEndpointVolumeCallback = new AudioEndpointVolumeCallback();
StephaneLenclud@277
    57
            iAudioEndpointVolumeCallback.NotifyRecived += iVolumeChangedHandler = aVolumeChangedHandler;
StephaneLenclud@277
    58
            iAudioEndpointVolume.RegisterControlChangeNotify(iAudioEndpointVolumeCallback);
StephaneLenclud@277
    59
StephaneLenclud@277
    60
            StartAudioVisualization();
StephaneLenclud@277
    61
        }
StephaneLenclud@277
    62
StephaneLenclud@277
    63
        /// <summary>
StephaneLenclud@277
    64
        /// 
StephaneLenclud@277
    65
        /// </summary>
StephaneLenclud@277
    66
        public void Close()
StephaneLenclud@277
    67
        {
StephaneLenclud@277
    68
            StopAudioVisualization();
StephaneLenclud@277
    69
StephaneLenclud@277
    70
            // Client up our MM objects in reverse order
StephaneLenclud@277
    71
            if (iAudioEndpointVolumeCallback != null && iAudioEndpointVolume != null)
StephaneLenclud@277
    72
            {
StephaneLenclud@277
    73
                iAudioEndpointVolume.UnregisterControlChangeNotify(iAudioEndpointVolumeCallback);
StephaneLenclud@277
    74
            }
StephaneLenclud@277
    75
StephaneLenclud@277
    76
            if (iAudioEndpointVolumeCallback != null)
StephaneLenclud@277
    77
            {
StephaneLenclud@277
    78
                iAudioEndpointVolumeCallback.NotifyRecived -= iVolumeChangedHandler;
StephaneLenclud@277
    79
                iAudioEndpointVolumeCallback = null;
StephaneLenclud@277
    80
            }
StephaneLenclud@277
    81
StephaneLenclud@277
    82
            if (iAudioEndpointVolume != null)
StephaneLenclud@277
    83
            {
StephaneLenclud@277
    84
                iAudioEndpointVolume.Dispose();
StephaneLenclud@277
    85
                iAudioEndpointVolume = null;
StephaneLenclud@277
    86
            }
StephaneLenclud@277
    87
StephaneLenclud@277
    88
            if (iMultiMediaDevice != null)
StephaneLenclud@277
    89
            {
StephaneLenclud@277
    90
                iMultiMediaDevice.Dispose();
StephaneLenclud@277
    91
                iMultiMediaDevice = null;
StephaneLenclud@277
    92
            }
StephaneLenclud@277
    93
StephaneLenclud@277
    94
            if (iMultiMediaNotificationClient != null)
StephaneLenclud@277
    95
            {
StephaneLenclud@277
    96
                iMultiMediaNotificationClient.DefaultDeviceChanged -= iDefaultDeviceChangedHandler;
StephaneLenclud@277
    97
                iMultiMediaNotificationClient.Dispose();
StephaneLenclud@277
    98
                iMultiMediaNotificationClient = null;
StephaneLenclud@277
    99
            }
StephaneLenclud@277
   100
StephaneLenclud@277
   101
            if (iMultiMediaDeviceEnumerator != null)
StephaneLenclud@277
   102
            {
StephaneLenclud@277
   103
                iMultiMediaDeviceEnumerator.Dispose();
StephaneLenclud@277
   104
                iMultiMediaDeviceEnumerator = null;
StephaneLenclud@277
   105
            }
StephaneLenclud@277
   106
StephaneLenclud@277
   107
        }
StephaneLenclud@277
   108
StephaneLenclud@277
   109
StephaneLenclud@277
   110
        /// <summary>
StephaneLenclud@277
   111
        /// 
StephaneLenclud@277
   112
        /// </summary>
StephaneLenclud@277
   113
        private void StartAudioVisualization()
StephaneLenclud@277
   114
        {
StephaneLenclud@277
   115
            //Open the default device 
StephaneLenclud@277
   116
            iSoundIn = new WasapiLoopbackCapture();
StephaneLenclud@277
   117
            //Our loopback capture opens the default render device by default so the following is not needed
StephaneLenclud@277
   118
            //iSoundIn.Device = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Console);
StephaneLenclud@277
   119
            iSoundIn.Initialize();
StephaneLenclud@277
   120
StephaneLenclud@277
   121
            SoundInSource soundInSource = new SoundInSource(iSoundIn);
StephaneLenclud@277
   122
            ISampleSource source = soundInSource.ToSampleSource();
StephaneLenclud@277
   123
StephaneLenclud@277
   124
            const FftSize fftSize = FftSize.Fft2048;
StephaneLenclud@277
   125
            //create a spectrum provider which provides fft data based on some input
StephaneLenclud@277
   126
            BasicSpectrumProvider spectrumProvider = new BasicSpectrumProvider(source.WaveFormat.Channels, source.WaveFormat.SampleRate, fftSize);
StephaneLenclud@277
   127
StephaneLenclud@277
   128
            //linespectrum and voiceprint3dspectrum used for rendering some fft data
StephaneLenclud@277
   129
            //in oder to get some fft data, set the previously created spectrumprovider 
StephaneLenclud@277
   130
            iLineSpectrum = new LineSpectrum(fftSize)
StephaneLenclud@277
   131
            {
StephaneLenclud@277
   132
                SpectrumProvider = spectrumProvider,
StephaneLenclud@277
   133
                UseAverage = false,
StephaneLenclud@277
   134
                BarCount = 16,
StephaneLenclud@277
   135
                BarSpacing = 1,
StephaneLenclud@277
   136
                IsXLogScale = true,
StephaneLenclud@277
   137
                ScalingStrategy = ScalingStrategy.Decibel
StephaneLenclud@277
   138
            };
StephaneLenclud@277
   139
StephaneLenclud@277
   140
StephaneLenclud@277
   141
            //the SingleBlockNotificationStream is used to intercept the played samples
StephaneLenclud@277
   142
            var notificationSource = new SingleBlockNotificationStream(source);
StephaneLenclud@277
   143
            //pass the intercepted samples as input data to the spectrumprovider (which will calculate a fft based on them)
StephaneLenclud@277
   144
            notificationSource.SingleBlockRead += (s, a) => spectrumProvider.Add(a.Left, a.Right);
StephaneLenclud@277
   145
StephaneLenclud@277
   146
            iWaveSource = notificationSource.ToWaveSource(16);
StephaneLenclud@277
   147
StephaneLenclud@277
   148
StephaneLenclud@277
   149
            // We need to read from our source otherwise SingleBlockRead is never called and our spectrum provider is not populated
StephaneLenclud@277
   150
            byte[] buffer = new byte[iWaveSource.WaveFormat.BytesPerSecond / 2];
StephaneLenclud@277
   151
            soundInSource.DataAvailable += (s, aEvent) =>
StephaneLenclud@277
   152
            {
StephaneLenclud@277
   153
                int read;
StephaneLenclud@277
   154
                while ((read = iWaveSource.Read(buffer, 0, buffer.Length)) > 0) ;
StephaneLenclud@277
   155
            };
StephaneLenclud@277
   156
StephaneLenclud@277
   157
StephaneLenclud@277
   158
            //Start recording
StephaneLenclud@277
   159
            iSoundIn.Start();
StephaneLenclud@277
   160
        }
StephaneLenclud@277
   161
StephaneLenclud@277
   162
        /// <summary>
StephaneLenclud@277
   163
        /// 
StephaneLenclud@277
   164
        /// </summary>
StephaneLenclud@277
   165
        private void StopAudioVisualization()
StephaneLenclud@277
   166
        {
StephaneLenclud@277
   167
            if (iWaveSource != null)
StephaneLenclud@277
   168
            {
StephaneLenclud@277
   169
                iWaveSource.Dispose();
StephaneLenclud@277
   170
                iWaveSource = null;
StephaneLenclud@277
   171
            }
StephaneLenclud@277
   172
StephaneLenclud@277
   173
            if (iSoundIn != null)
StephaneLenclud@277
   174
            {
StephaneLenclud@277
   175
                iSoundIn.Stop();
StephaneLenclud@277
   176
                iSoundIn.Dispose();
StephaneLenclud@277
   177
                iSoundIn = null;
StephaneLenclud@277
   178
            }
StephaneLenclud@277
   179
StephaneLenclud@277
   180
        }
StephaneLenclud@277
   181
StephaneLenclud@277
   182
StephaneLenclud@277
   183
    }
StephaneLenclud@277
   184
}