/// Version 1.0.0 /// Last modified 24.5.2014 /// /// Copyright (C) 2014 Veli-Mikko Puupponen /// /// Halyri-system is a prototype emergency call system. Its purpose is to /// demonstrate the use of the advanced capabilities available in the current /// generation smartphones in facilitating the emergency service dispatcher's /// capability to determine the nature of the emergency and to dispatch help. /// /// For more information, see the README file of this package. /// /// The MIT License (MIT) /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to /// deal in the Software without restriction, including without limitation the /// rights to use, copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit persons to whom the /// Software is furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in /// all copies or substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS /// IN THE SOFTWARE. /// using Hake_WPF.CallCenterReference; using Hake_WPF.Conversion; using NAudio.Wave; using NSpeex; using SimpleUdpMediaClient.Packets; using System.Diagnostics; using System.IO; namespace Hake_WPF.AudioVideoManagers { /// /// The class delegate for image received events. /// /// The publishing AudioVideoTransferManager instance. /// The byte representation of a jpg compressed image. public delegate void JpgImageReceived(object sender, byte[] imageData); /// Veli-Mikko Puupponen /// /// The class is used for processing incoming and outgoing audio and images. It handles /// media data received from the server. It reproduces speex encoded audio and /// Wave file segments using the primary audio output device in the system. /// /// It captures audio from the default audio input device in the system using /// a NAudio WaveIn instance. If outgoing audio is enabled, it uploads the /// captured audio in a speex encoded format to the server. /// /// It publishes a JpgImageReceivedEvent upon receiving an image from the server. /// class AudioVideoTransferManager { /// /// It is a event handler for JpgImageReceivedEvent delegate. /// public JpgImageReceived JpgImageReceivedEvent; private MediaInformation outgoingSpeexInformation = new MediaInformation(MediaFormat.Speex, TransferCompression.None); private WaveFormat AudioCaptureFormat; private const int speexFramesInSecond = 50; private const int defaultSpeexQuality = 2; private SpeexDecoder decoder; private SpeexEncoder encoder; private IWaveIn recorder; private WaveOut player; private bool audioPlaybackActive = false; private BufferWaveStream bufferedWaveStream; private object playbackConfigurationLock = new object(); private Connection connection; private bool useOutgoingUdp = true; /// /// Gets and sets the UseOutGoingUdp. It specifies whether to use UDP or not. /// public bool UseOutgoingUdp { get { return useOutgoingUdp; } set { useOutgoingUdp = value; } } /// /// The function initializes a new AudioVideoTransferManager that uses the provided /// connection to receive and transmit audio and pictures. /// /// The valid Connection instance. public AudioVideoTransferManager(Connection c) { connection = c; InitializeSpeex(); InitializeAudioIODevices(); connection.receiver.TcpMediaDataReceivedEvent += TcpMediaReceivedHandler; connection.UdpMediaDataReceivedEvent += UdpMediaReceivedHandler; } /// /// The function initializes speex audio encoder and decoder. /// private void InitializeSpeex() { encoder = new SpeexEncoder(BandMode.Wide); encoder.Quality = defaultSpeexQuality; decoder = new SpeexDecoder(BandMode.Wide); AudioCaptureFormat = new WaveFormat(speexFramesInSecond * encoder.FrameSize, 16, 1); } /// /// The function initializes NAudio WaveIn and WaveOut used to capture and /// reproduce audio. /// private void InitializeAudioIODevices() { recorder = new WaveIn { WaveFormat = AudioCaptureFormat, BufferMilliseconds = 500, NumberOfBuffers = 2 }; player = new WaveOut(); } /// /// The function enables audio recording and publishing to the server from the /// primary audio capture in the system. /// public void EnableOutgoingAudio() { recorder.DataAvailable += MicrophoneDataAvailable; try { recorder.StartRecording(); } catch (NAudio.MmException) { Debug.WriteLine("no microphone"); recorder.DataAvailable -= MicrophoneDataAvailable; } } /// /// The function stops recording audio and publishing it to the server. /// public void DisableOutgoingAudio() { recorder.StopRecording(); recorder.DataAvailable -= MicrophoneDataAvailable; } /// /// The function starts receiving audio from the server and reproducing it using the /// primary audio output device in the system. /// public void EnableIncomingAudio() { lock (playbackConfigurationLock) { if (!audioPlaybackActive) { bufferedWaveStream = new BufferWaveStream(16000, 2, 1); player.Init(bufferedWaveStream); player.Volume = 1; player.Play(); audioPlaybackActive = true; } } connection.RequestAudioMedia(); } /// /// The function stops receiving and reproducing audio. /// public void DisableIncomingAudio() { lock (playbackConfigurationLock) { audioPlaybackActive = false; if(player.PlaybackState == PlaybackState.Playing) player.Stop(); bufferedWaveStream.Dispose(); bufferedWaveStream = null; } } /// /// Handles incoming speex audio segments. Decompresses them into PCM /// that is added into the bufferedWaveStream from which the active /// WaveOut instance reads and reproduces them. /// /// Speex encoded audio /// The count of 16 bit PCM samples that were /// encoded to produce the provided encoded data private void SpeexAudioReceived(byte[] speexData, int originatingLength) { byte[] decodedBytes = SpeexCompression.DecompressSpeex(decoder, speexData, originatingLength); if (bufferedWaveStream != null && decodedBytes.Length > 0) bufferedWaveStream.Write(decodedBytes, 0, decodedBytes.Length); } /// /// Handles PCM data segments captured by the active WaveIn instance. /// /// The publishing WaveIn instance /// WaveInEventArgs containing the captured audio private void MicrophoneDataAvailable(object sender, WaveInEventArgs e) { if (e.BytesRecorded > 0) { byte[] speexCompressedAudio; int compressedLength = SpeexCompression.CompressSpeex(e.Buffer, e.BytesRecorded, 2, encoder, out speexCompressedAudio); if (compressedLength > 0) connection.UdpSendMedia(outgoingSpeexInformation, speexCompressedAudio, compressedLength); } } /// /// Handles incoming media received by the active Connection instance over the UDP channel. /// Supported formats are jpg images and speex encoded audio. /// /// Connection instance publishing the event /// MediaPacket containing the media data and description private void UdpMediaReceivedHandler(object sender, MediaPacket mediaPacket) { if (mediaPacket.PacketMediaInformation != null && mediaPacket.CompletePayloadData != null && mediaPacket.CompletePayloadData.Length > 0 && mediaPacket.PacketMediaInformation != null) { byte[] payload = mediaPacket.CompletePayloadData; if (mediaPacket.PacketMediaInformation.Compression == TransferCompression.GZip) payload = CompressionHelper.DecompressGZip(mediaPacket.CompletePayloadData); switch (mediaPacket.PacketMediaInformation.Format) { case MediaFormat.Jpg: if (JpgImageReceivedEvent != null) JpgImageReceivedEvent(this, mediaPacket.CompletePayloadData); break; case MediaFormat.Speex: SpeexAudioReceived(mediaPacket.CompletePayloadData, mediaPacket.OriginatingDataLength); break; } } } /// /// Handles incoming media received by the active Connection instance over the WCF TCP channel. /// Supported formats are jpg images and wave audio fragments with pcm payload. /// /// Connection instance publishing the event /// MediaInformationDto describing the media data /// Media data payload private void TcpMediaReceivedHandler(object sender, MediaInformationDto info, byte[] data) { if (data != null && data.Length > 0 && info != null) { byte[] payload = data; if (info.CompressedGZip) payload = CompressionHelper.DecompressGZip(data); switch (info.MediaType) { case MediaTypeDto.Jpeg: if (JpgImageReceivedEvent != null) JpgImageReceivedEvent(this, data); break; case MediaTypeDto.Wave: PlayWaveFragment(data); break; } } } /// /// Plays a wave file using a new SoundPlayer instance. /// /// Audio in wave format private void PlayWaveFragment(byte[] audio) { using (MemoryStream ms = new MemoryStream(audio)) { new System.Media.SoundPlayer(ms).Play(); } } } }