|
@@ -1,7 +1,6 @@
|
|
using NAudio.Wave;
|
|
using NAudio.Wave;
|
|
using System;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Concurrent;
|
|
-using System.Diagnostics.CodeAnalysis;
|
|
|
|
using System.IO;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net;
|
|
@@ -40,6 +39,10 @@ namespace AudioNTR
|
|
Console.WriteLine(" <target-address> <port> Run as Transmitter Captures default audio output");
|
|
Console.WriteLine(" <target-address> <port> Run as Transmitter Captures default audio output");
|
|
Console.WriteLine(" transmitter-sine-wave");
|
|
Console.WriteLine(" transmitter-sine-wave");
|
|
Console.WriteLine(" <target-address> <port> Run as Transmitter Sine wave");
|
|
Console.WriteLine(" <target-address> <port> Run as Transmitter Sine wave");
|
|
|
|
+ Console.WriteLine(" receiver-as-client");
|
|
|
|
+ Console.WriteLine(" <target-address> <port> Run receiver as client");
|
|
|
|
+ Console.WriteLine(" transmitter-as-server");
|
|
|
|
+ Console.WriteLine(" <target-address> <port> Run transmitter as server Captures default audio output");
|
|
break;
|
|
break;
|
|
|
|
|
|
case "capture": TestRunCapture(argsToOp); break;
|
|
case "capture": TestRunCapture(argsToOp); break;
|
|
@@ -49,9 +52,177 @@ namespace AudioNTR
|
|
case "receiver": RunReceiver(argsToOp); break;
|
|
case "receiver": RunReceiver(argsToOp); break;
|
|
case "transmitter": RunTransmitter(argsToOp); break;
|
|
case "transmitter": RunTransmitter(argsToOp); break;
|
|
case "transmitter-sine-wave": RunTransmitterSineWave(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("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)
|
|
private static void TestRunCapture(string[] argsToOp)
|
|
{
|
|
{
|
|
Console.WriteLine("Creating WasapiLoopbackCapture Instance...");
|
|
Console.WriteLine("Creating WasapiLoopbackCapture Instance...");
|
|
@@ -190,7 +361,7 @@ namespace AudioNTR
|
|
Console.WriteLine("Setup...");
|
|
Console.WriteLine("Setup...");
|
|
output.Init(provider);
|
|
output.Init(provider);
|
|
|
|
|
|
- const int fillSleep = bufferMs*11/20;
|
|
|
|
|
|
+ const int fillSleep = bufferMs * 11 / 20;
|
|
Console.WriteLine($"Filling buffer, sleep {fillSleep}ms");
|
|
Console.WriteLine($"Filling buffer, sleep {fillSleep}ms");
|
|
Thread.Sleep(fillSleep);
|
|
Thread.Sleep(fillSleep);
|
|
|
|
|
|
@@ -270,7 +441,6 @@ namespace AudioNTR
|
|
provider.Read(buffer, 0, buffer.Length);
|
|
provider.Read(buffer, 0, buffer.Length);
|
|
stream.Write(buffer, 0, buffer.Length);
|
|
stream.Write(buffer, 0, buffer.Length);
|
|
}
|
|
}
|
|
-
|
|
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
catch (Exception e)
|
|
{
|
|
{
|
|
@@ -288,8 +458,6 @@ namespace AudioNTR
|
|
client?.Close();
|
|
client?.Close();
|
|
client?.Dispose();
|
|
client?.Dispose();
|
|
}
|
|
}
|
|
-
|
|
|
|
-
|
|
|
|
} while (running);
|
|
} while (running);
|
|
});
|
|
});
|
|
|
|
|
|
@@ -337,7 +505,7 @@ namespace AudioNTR
|
|
var up = DateTime.Now;
|
|
var up = DateTime.Now;
|
|
provider.DataAvailable += (sender, args) =>
|
|
provider.DataAvailable += (sender, args) =>
|
|
{
|
|
{
|
|
- Console.Title = $"Sent bytes: {totalSent:N0}, UP: {DateTime.Now-up}";
|
|
|
|
|
|
+ Console.Title = $"Sent bytes: {totalSent:N0}, UP: {DateTime.Now - up}";
|
|
stream.Write(args.Buffer, 0, args.BytesRecorded);
|
|
stream.Write(args.Buffer, 0, args.BytesRecorded);
|
|
stream.Flush();
|
|
stream.Flush();
|
|
totalSent += args.BytesRecorded;
|
|
totalSent += args.BytesRecorded;
|
|
@@ -345,7 +513,6 @@ namespace AudioNTR
|
|
provider.StartRecording();
|
|
provider.StartRecording();
|
|
while (running) Thread.Sleep(100);
|
|
while (running) Thread.Sleep(100);
|
|
provider.StopRecording();
|
|
provider.StopRecording();
|
|
-
|
|
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
catch (Exception e)
|
|
{
|
|
{
|
|
@@ -363,8 +530,6 @@ namespace AudioNTR
|
|
client?.Close();
|
|
client?.Close();
|
|
client?.Dispose();
|
|
client?.Dispose();
|
|
}
|
|
}
|
|
-
|
|
|
|
-
|
|
|
|
} while (running);
|
|
} while (running);
|
|
});
|
|
});
|
|
|
|
|
|
@@ -376,59 +541,6 @@ namespace AudioNTR
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- //internal class BufferedWaveProvider : IWaveProvider, IDisposable
|
|
|
|
- //{
|
|
|
|
- // private readonly BlockingCollection<byte> _buffer;
|
|
|
|
- // private readonly CancellationTokenSource _cancellationTokenSource = new();
|
|
|
|
-
|
|
|
|
- // public BufferedWaveProvider(WaveFormat waveFormat, int msBuffer = 1000)
|
|
|
|
- // {
|
|
|
|
- // WaveFormat = waveFormat;
|
|
|
|
- // _buffer = new BlockingCollection<byte>((int)Math.Ceiling(WaveFormat.SampleRate / 1000f * msBuffer * WaveFormat.Channels * (WaveFormat.BitsPerSample / 8f)));
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // public void Feed(byte[] buffer, int count)
|
|
|
|
- // {
|
|
|
|
- // if (_cancellationTokenSource.IsCancellationRequested) return;
|
|
|
|
- // for (var i = 0; i < count; i++) _buffer.Add(buffer[i], _cancellationTokenSource.Token);
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // public int Read(byte[] buffer, int offset, int count)
|
|
|
|
- // {
|
|
|
|
- // if (_cancellationTokenSource.IsCancellationRequested) return 0;
|
|
|
|
-
|
|
|
|
- // 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;
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // public WaveFormat WaveFormat { get; }
|
|
|
|
- // public object BufferedBytes => _buffer.Count;
|
|
|
|
-
|
|
|
|
- // public void Dispose()
|
|
|
|
- // {
|
|
|
|
- // _cancellationTokenSource?.Cancel();
|
|
|
|
- // _buffer?.Dispose();
|
|
|
|
- // _cancellationTokenSource?.Dispose();
|
|
|
|
- // }
|
|
|
|
- //}
|
|
|
|
-
|
|
|
|
internal class WaveInProvider : IWaveProvider
|
|
internal class WaveInProvider : IWaveProvider
|
|
{
|
|
{
|
|
private readonly IWaveIn _input;
|
|
private readonly IWaveIn _input;
|