using NAudio.Wave; using System; using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; // ReSharper disable AccessToModifiedClosure namespace AudioNTR { internal class Program { private static void Main(string[] args) { var op = args.FirstOrDefault() ?? ""; var argsToOp = args.Skip(1).ToArray(); Console.WriteLine("Audio Net Transmitter Receiver"); switch (op.ToLower()) { default: Console.WriteLine("Usage: AudioNTR [args1[ args2[...]]]"); Console.WriteLine(" capture"); Console.WriteLine(" Test run WasapiLoopbackCapture"); Console.WriteLine(" playback"); Console.WriteLine(" Test run WasapiOut (sin wave)"); Console.WriteLine(" playback-echo"); Console.WriteLine(" Test run WasapiOut (WasapiLoopbackCapture)"); Console.WriteLine(" playback-echo-2"); Console.WriteLine(" Test run WasapiOut (WasapiLoopbackCapture)"); Console.WriteLine(" receiver"); Console.WriteLine(" [bind-address] Run as Receiver"); Console.WriteLine(" transmitter"); Console.WriteLine(" Run as Transmitter Captures default audio output"); Console.WriteLine(" transmitter-sine-wave"); Console.WriteLine(" Run as Transmitter Sine wave"); Console.WriteLine(" receiver-as-client"); Console.WriteLine(" Run receiver as client"); Console.WriteLine(" transmitter-as-server"); Console.WriteLine(" Run transmitter as server Captures default audio output"); break; case "capture": TestRunCapture(argsToOp); break; case "playback": TestRunPlayBack(argsToOp); break; case "playback-echo": TestRunPlayBackEcho(argsToOp); break; case "playback-echo-2": TestRunPlayBackEcho2(argsToOp); break; case "receiver": RunReceiver(argsToOp); break; case "transmitter": RunTransmitter(argsToOp); break; case "transmitter-sine-wave": RunTransmitterSineWave(argsToOp); break; case "receiver-as-client": RunReceiverAsClient(argsToOp); break; case "transmitter-as-server": RunTransmitterAsServer(argsToOp); break; } } private static void RunTransmitterAsServer(string[] argsToOp) { if (argsToOp.Length < 1) throw new ArgumentException("least 1 args for listen port"); if (false == int.TryParse(argsToOp[0], out var port)) throw new ArgumentException("invalid port"); IPAddress address = null; if (argsToOp.Length > 1 && false == IPAddress.TryParse(argsToOp[1], out address)) throw new ArgumentException("invalid address"); Console.WriteLine("Creating TcpListener..."); var listener = new TcpListener(address ?? IPAddress.Any, port); Console.WriteLine("Start Listening..."); listener.Start(); Console.WriteLine($"Listening on {listener.LocalEndpoint}"); var running = true; var task = Task.Run(() => { while (running) { Console.WriteLine("Waiting connection..."); var client = listener.AcceptTcpClient(); client.NoDelay = true; Console.WriteLine($"Accept connection from {client.Client.RemoteEndPoint}..."); var stream = client.GetStream(); Console.WriteLine("Creating WasapiLoopbackCapture Instance..."); var provider = new WasapiLoopbackCapture(); Console.WriteLine($"Encoding: {provider.WaveFormat.Encoding}"); Console.WriteLine($"Channels: {provider.WaveFormat.Channels}"); Console.WriteLine($"SampleRate: {provider.WaveFormat.SampleRate}"); Console.WriteLine($"BitsPerSample: {provider.WaveFormat.BitsPerSample}"); Console.WriteLine("Sending wave format ..."); var writer = new BinaryWriter(stream, Encoding.UTF8, true); provider.WaveFormat.Serialize(writer); writer.Close(); Console.WriteLine("Begin sending chunks..."); long totalSent = 0; var up = DateTime.Now; var connectAlive = true; provider.DataAvailable += (sender, args) => { Console.Title = $"Sent bytes: {totalSent:N0}, UP: {DateTime.Now - up}"; try { stream.Write(args.Buffer, 0, args.BytesRecorded); stream.Flush(); } catch (Exception e) { Console.WriteLine($"connection lost:{e.Message}"); connectAlive = false; return; } totalSent += args.BytesRecorded; }; provider.StartRecording(); while (running && connectAlive) Thread.Sleep(100); provider.StopRecording(); } }); Console.WriteLine("Press ENTER to Exit."); Console.ReadLine(); running = false; task.Wait(); Console.WriteLine("Finished."); } private static void RunReceiverAsClient(string[] argsToOp) { const int bufferMs = 500; if (argsToOp.Length != 2) throw new ArgumentException("required 2 args for address and port"); if (false == IPAddress.TryParse(argsToOp[0], out var address)) throw new ArgumentException("invalid address"); if (false == int.TryParse(argsToOp[1], out var port)) throw new ArgumentException("invalid port"); var running = true; var task = Task.Run(() => { do { TcpClient client = null; try { Console.WriteLine("Creating TcpClient ..."); client = new TcpClient { NoDelay = true }; var ipEndPoint = new IPEndPoint(address, port); Console.WriteLine($"Connecting to {ipEndPoint} ..."); client.Connect(ipEndPoint); var stream = client.GetStream(); Console.WriteLine("Parsing wave format and create BufferedWaveProvider..."); var reader = new BinaryReader(stream, Encoding.UTF8, true); var provider = new BufferedWaveProvider(new WaveFormat(reader)) { BufferDuration = TimeSpan.FromMilliseconds(bufferMs), DiscardOnBufferOverflow = true, }; reader.Dispose(); Console.WriteLine($"Encoding: {provider.WaveFormat.Encoding}"); Console.WriteLine($"Channels: {provider.WaveFormat.Channels}"); Console.WriteLine($"SampleRate: {provider.WaveFormat.SampleRate}"); Console.WriteLine($"BitsPerSample: {provider.WaveFormat.BitsPerSample}"); Console.WriteLine($"Buffer Duration set to {provider.BufferDuration}"); Console.WriteLine("Creating WasapiOut Instance..."); var output = new WasapiOut(); Console.WriteLine("Setup..."); output.Init(provider); const int fillSleep = bufferMs * 11 / 20; Console.WriteLine($"Filling buffer, sleep {fillSleep}ms"); Thread.Sleep(fillSleep); Console.WriteLine("Starting play..."); output.Play(); try { var buf = new byte[1024 * 1024];//1MB int readCount; Console.WriteLine("Begin read chunks..."); do { Console.Title = $"Buffered bytes: {provider.BufferedBytes:N0}"; readCount = stream.Read(buf, 0, buf.Length); provider.AddSamples(buf, 0, readCount); } while (readCount > 0 && running); } catch (Exception e) { Console.WriteLine(e); } Console.WriteLine("Closing connection..."); client.Close(); provider.ClearBuffer(); Console.WriteLine("Stopping Playing..."); output.Stop(); Console.WriteLine("Free resources"); client.Dispose(); } catch (Exception e) { Console.WriteLine(e); if (running) { Console.WriteLine("Wait 10 Seconds to re connection"); Thread.Sleep(10 * 1000); } } finally { Console.WriteLine("Free resources"); client?.Close(); client?.Dispose(); } } while (running); }); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); running = false; task.Wait(); Console.WriteLine("Finished."); } private static void TestRunCapture(string[] argsToOp) { Console.WriteLine("Creating WasapiLoopbackCapture Instance..."); var capture = new WasapiLoopbackCapture(); Console.WriteLine($"Encoding: {capture.WaveFormat.Encoding}"); Console.WriteLine($"Channels: {capture.WaveFormat.Channels}"); Console.WriteLine($"SampleRate: {capture.WaveFormat.SampleRate:N0}"); Console.WriteLine($"BitsPerSample:{capture.WaveFormat.BitsPerSample:N0}"); var bytes = 0; capture.DataAvailable += (_, e) => { bytes += e.BytesRecorded; Console.Title = $"Buffer: {e.Buffer.Length} Bytes: {e.BytesRecorded:N0} Total: {bytes:N0}"; }; Console.WriteLine("Starting capture..."); capture.StartRecording(); Console.WriteLine("Capturing, Press ENTER to exit"); Console.ReadLine(); Console.WriteLine("Stopping capture..."); capture.StopRecording(); Console.WriteLine("Stopped."); } private static void TestRunPlayBack(string[] argsToOp) { Console.WriteLine("Creating WasapiOut Instance..."); var output = new WasapiOut(); Console.WriteLine("Creating SineWaveSampleProvider Instance..."); var provider = new SineWaveSampleProvider(); Console.WriteLine("Init..."); output.Init(provider); Console.WriteLine("Starting play..."); output.Play(); Console.WriteLine("Playing, Press ENTER to exit"); Console.ReadLine(); Console.WriteLine("Stopping Playing..."); output.Stop(); Console.WriteLine("Stopped."); } private static void TestRunPlayBackEcho(string[] argsToOp) { Console.WriteLine("Creating WasapiOut Instance..."); var output = new WasapiOut(); Console.WriteLine("Creating WaveInProvider Instance by WasapiLoopbackCapture..."); var provider = new WaveInProvider(new WasapiLoopbackCapture()); Console.WriteLine("Init..."); output.Init(provider); //output.Volume = 0.5f; Console.WriteLine("Starting Capture..."); provider.StartRecording(); Console.WriteLine("Starting play..."); output.Play(); Console.WriteLine("Playing, Press ENTER to exit"); Console.ReadLine(); Console.WriteLine("Stopping Capture..."); provider.StopRecording(); Console.WriteLine("Stopping Playing..."); output.Stop(); Console.WriteLine("Stopped."); } private static void TestRunPlayBackEcho2(string[] argsToOp) { Console.WriteLine("Creating WasapiOut Instance..."); var output = new WasapiOut(); Console.WriteLine("Creating WasapiLoopbackCapture Instance..."); var capture = new WasapiLoopbackCapture(); Console.WriteLine("Creating WasapiLoopbackCapture Instance..."); var provider = new BufferedWaveProvider(capture.WaveFormat); Console.WriteLine("Setup..."); capture.DataAvailable += (sender, args) => provider.AddSamples(args.Buffer, 0, args.BytesRecorded); output.Init(provider); Console.WriteLine("Starting Capture..."); capture.StartRecording(); Console.WriteLine("Starting play..."); output.Play(); Console.WriteLine("Playing, Press ENTER to exit"); Console.ReadLine(); Console.WriteLine("Stopping Capture..."); capture.StopRecording(); Console.WriteLine("Free resoures..."); provider.ClearBuffer(); Console.WriteLine("Stopping Playing..."); output.Stop(); Console.WriteLine("Stopped."); } private static void RunReceiver(string[] argsToOp) { const int bufferMs = 500; if (argsToOp.Length < 1) throw new ArgumentException("least 1 args for listen port"); if (false == int.TryParse(argsToOp[0], out var port)) throw new ArgumentException("invalid port"); IPAddress address = null; if (argsToOp.Length > 1 && false == IPAddress.TryParse(argsToOp[1], out address)) throw new ArgumentException("invalid address"); Console.WriteLine("Creating TcpListener..."); var listener = new TcpListener(address ?? IPAddress.Any, port); Console.WriteLine("Start Listening..."); listener.Start(); Console.WriteLine($"Listening on {listener.LocalEndpoint}"); var running = true; var task = Task.Run(() => { while (running) { Console.WriteLine("Waiting connection..."); var client = listener.AcceptTcpClient(); client.NoDelay = true; Console.WriteLine($"Accept connection from {client.Client.RemoteEndPoint}..."); var stream = client.GetStream(); Console.WriteLine("Parsing wave format and create BufferedWaveProvider..."); var reader = new BinaryReader(stream, Encoding.UTF8, true); var provider = new BufferedWaveProvider(new WaveFormat(reader)) { BufferDuration = TimeSpan.FromMilliseconds(bufferMs), DiscardOnBufferOverflow = true, }; reader.Dispose(); Console.WriteLine($"Encoding: {provider.WaveFormat.Encoding}"); Console.WriteLine($"Channels: {provider.WaveFormat.Channels}"); Console.WriteLine($"SampleRate: {provider.WaveFormat.SampleRate}"); Console.WriteLine($"BitsPerSample: {provider.WaveFormat.BitsPerSample}"); Console.WriteLine($"Buffer Duration set to {provider.BufferDuration}"); Console.WriteLine("Creating WasapiOut Instance..."); var output = new WasapiOut(); Console.WriteLine("Setup..."); output.Init(provider); const int fillSleep = bufferMs * 11 / 20; Console.WriteLine($"Filling buffer, sleep {fillSleep}ms"); Thread.Sleep(fillSleep); Console.WriteLine("Starting play..."); output.Play(); try { var buf = new byte[1024 * 1024];//1MB int readCount; //Console.WriteLine("Sending ACK..."); //stream.WriteByte(1); Console.WriteLine("Begin read chunks..."); do { Console.Title = $"Buffered bytes: {provider.BufferedBytes:N0}"; readCount = stream.Read(buf, 0, buf.Length); provider.AddSamples(buf, 0, readCount); } while (readCount > 0 && running); } catch (Exception e) { Console.WriteLine(e); } Console.WriteLine("Closing connection..."); client.Close(); provider.ClearBuffer(); Console.WriteLine("Stopping Playing..."); output.Stop(); Console.WriteLine("Free resources"); client.Dispose(); } }); Console.WriteLine("Press ENTER to Exit."); Console.ReadLine(); running = false; task.Wait(); Console.WriteLine("Finished."); } private static void RunTransmitterSineWave(string[] argsToOp) { if (argsToOp.Length != 2) throw new ArgumentException("required 2 args for address and port"); if (false == IPAddress.TryParse(argsToOp[0], out var address)) throw new ArgumentException("invalid address"); if (false == int.TryParse(argsToOp[1], out var port)) throw new ArgumentException("invalid port"); var running = true; var task = Task.Run(() => { do { TcpClient client = null; try { Console.WriteLine("Creating TcpClient ..."); client = new TcpClient(); var ipEndPoint = new IPEndPoint(address, port); Console.WriteLine($"Connecting to {ipEndPoint} ..."); client.Connect(ipEndPoint); var stream = client.GetStream(); Console.WriteLine("Connected, creating SineWaveSampleProvider ..."); var provider = new SineWaveSampleProvider(); Console.WriteLine($"Encoding: {provider.WaveFormat.Encoding}"); Console.WriteLine($"Channels: {provider.WaveFormat.Channels}"); Console.WriteLine($"SampleRate: {provider.WaveFormat.SampleRate}"); Console.WriteLine($"BitsPerSample: {provider.WaveFormat.BitsPerSample}"); Console.WriteLine("Sending wave format ..."); var writer = new BinaryWriter(stream, Encoding.UTF8, true); provider.WaveFormat.Serialize(writer); writer.Close(); Console.WriteLine("Begin sending chunks..."); var buffer = new byte[provider.WaveFormat.AverageBytesPerSecond / 4]; while (running) { provider.Read(buffer, 0, buffer.Length); stream.Write(buffer, 0, buffer.Length); } } catch (Exception e) { Console.WriteLine(e); if (running) { Console.WriteLine("Wait 10 Seconds to re connection"); Thread.Sleep(10 * 1000); } } finally { Console.WriteLine("Free resources"); client?.Close(); client?.Dispose(); } } while (running); }); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); running = false; task.Wait(); Console.WriteLine("Finished."); } private static void RunTransmitter(string[] argsToOp) { if (argsToOp.Length != 2) throw new ArgumentException("required 2 args for address and port"); if (false == IPAddress.TryParse(argsToOp[0], out var address)) throw new ArgumentException("invalid address"); if (false == int.TryParse(argsToOp[1], out var port)) throw new ArgumentException("invalid port"); var running = true; var task = Task.Run(() => { do { TcpClient client = null; try { Console.WriteLine("Creating TcpClient ..."); client = new TcpClient { NoDelay = true }; var ipEndPoint = new IPEndPoint(address, port); Console.WriteLine($"Connecting to {ipEndPoint} ..."); client.Connect(ipEndPoint); var stream = client.GetStream(); Console.WriteLine("Creating WasapiLoopbackCapture Instance..."); var provider = new WasapiLoopbackCapture(); Console.WriteLine("Sending wave format ..."); var writer = new BinaryWriter(stream, Encoding.UTF8, true); provider.WaveFormat.Serialize(writer); writer.Close(); //Console.WriteLine("Waiting ACK ..."); //var ack = stream.ReadByte(); //if (ack != 1) throw new Exception("Unexpected ACK"); Console.WriteLine("Begin sending chunks..."); long totalSent = 0; var up = DateTime.Now; provider.DataAvailable += (sender, args) => { Console.Title = $"Sent bytes: {totalSent:N0}, UP: {DateTime.Now - up}"; stream.Write(args.Buffer, 0, args.BytesRecorded); stream.Flush(); totalSent += args.BytesRecorded; }; provider.StartRecording(); while (running) Thread.Sleep(100); provider.StopRecording(); } catch (Exception e) { Console.WriteLine(e); if (running) { Console.WriteLine("Wait 10 Seconds to re connection"); Thread.Sleep(10 * 1000); } } finally { Console.WriteLine("Free resources"); client?.Close(); client?.Dispose(); } } while (running); }); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); running = false; task.Wait(); Console.WriteLine("Finished."); } } internal class WaveInProvider : IWaveProvider { private readonly IWaveIn _input; private readonly BlockingCollection _buffer; private readonly CancellationTokenSource _cancellationTokenSource = new(); public WaveFormat WaveFormat { get; } public WaveInProvider(IWaveIn input, int msBuffer = 1000) { _input = input; WaveFormat = _input.WaveFormat; _input.DataAvailable += Input_DataAvailable; _buffer = new((int)Math.Ceiling(WaveFormat.SampleRate / 1000f * msBuffer * WaveFormat.Channels * (WaveFormat.BitsPerSample / 8f))); } private void Input_DataAvailable(object sender, WaveInEventArgs e) { if (_cancellationTokenSource.IsCancellationRequested) return; for (var i = 0; i < e.BytesRecorded; i++) _buffer.Add(e.Buffer[i], _cancellationTokenSource.Token); } public void StartRecording() { _input.StartRecording(); } public void StopRecording() { _input.StopRecording(); _cancellationTokenSource.Cancel(); } public int Read(byte[] buffer, int offset, int count) { var readCount = 0; var eof = offset + count; do { try { buffer[offset] = _buffer.Take(_cancellationTokenSource.Token); } catch (OperationCanceledException) { return readCount; } readCount++; offset++; } while (_buffer.Count > 0 && offset < eof && _cancellationTokenSource.IsCancellationRequested == false); return readCount; } } internal class SineWaveSampleProvider : IWaveProvider, ISampleProvider { public int Freq { get; } public float Amp { get; } public WaveFormat WaveFormat { get; } private int _sample; public SineWaveSampleProvider(int sampleRate = 441000, int channels = 1, int freq = 1000, float amp = 0.25f) { Freq = freq; Amp = amp; WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels); } public int Read(byte[] buffer, int offset, int count) { var waveBuffer = new WaveBuffer(buffer); var samplesRequired = count / 4; var samplesRead = Read(waveBuffer.FloatBuffer, offset / 4, samplesRequired); return samplesRead * 4; } public int Read(float[] buffer, int offset, int sampleCount) { var sampleRate = WaveFormat.SampleRate; for (var n = 0; n < sampleCount; n++) { buffer[n + offset] = (float)(Amp * Math.Sin((2 * Math.PI * _sample * Freq) / sampleRate)); _sample++; if (_sample >= sampleRate) _sample = 0; } return sampleCount; } } }