/// 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();
}
}
}
}