Program.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. using NAudio.Wave;
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.Sockets;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. // ReSharper disable AccessToModifiedClosure
  13. namespace AudioNTR
  14. {
  15. internal class Program
  16. {
  17. private static void Main(string[] args)
  18. {
  19. var op = args.FirstOrDefault() ?? "";
  20. var argsToOp = args.Skip(1).ToArray();
  21. Console.WriteLine("Audio Net Transmitter Receiver");
  22. switch (op.ToLower())
  23. {
  24. default:
  25. Console.WriteLine("Usage: AudioNTR <op> [args1[ args2[...]]]");
  26. Console.WriteLine(" capture");
  27. Console.WriteLine(" Test run WasapiLoopbackCapture");
  28. Console.WriteLine(" playback");
  29. Console.WriteLine(" Test run WasapiOut (sin wave)");
  30. Console.WriteLine(" playback-echo");
  31. Console.WriteLine(" Test run WasapiOut (WasapiLoopbackCapture)");
  32. Console.WriteLine(" playback-echo-2");
  33. Console.WriteLine(" Test run WasapiOut (WasapiLoopbackCapture)");
  34. Console.WriteLine(" receiver");
  35. Console.WriteLine(" <port> [bind-address] Run as Receiver");
  36. Console.WriteLine(" transmitter");
  37. Console.WriteLine(" <target-address> <port> Run as Transmitter Captures default audio output");
  38. Console.WriteLine(" transmitter-sine-wave");
  39. Console.WriteLine(" <target-address> <port> Run as Transmitter Sine wave");
  40. break;
  41. case "capture": TestRunCapture(argsToOp); break;
  42. case "playback": TestRunPlayBack(argsToOp); break;
  43. case "playback-echo": TestRunPlayBackEcho(argsToOp); break;
  44. case "playback-echo-2": TestRunPlayBackEcho2(argsToOp); break;
  45. case "receiver": RunReceiver(argsToOp); break;
  46. case "transmitter": RunTransmitter(argsToOp); break;
  47. case "transmitter-sine-wave": RunTransmitterSineWave(argsToOp); break;
  48. }
  49. }
  50. private static void TestRunCapture(string[] argsToOp)
  51. {
  52. Console.WriteLine("Creating WasapiLoopbackCapture Instance...");
  53. var capture = new WasapiLoopbackCapture();
  54. Console.WriteLine($"Encoding: {capture.WaveFormat.Encoding}");
  55. Console.WriteLine($"Channels: {capture.WaveFormat.Channels}");
  56. Console.WriteLine($"SampleRate: {capture.WaveFormat.SampleRate:N0}");
  57. Console.WriteLine($"BitsPerSample:{capture.WaveFormat.BitsPerSample:N0}");
  58. var bytes = 0;
  59. capture.DataAvailable += (_, e) =>
  60. {
  61. bytes += e.BytesRecorded;
  62. Console.Title = $"Buffer: {e.Buffer.Length} Bytes: {e.BytesRecorded:N0} Total: {bytes:N0}";
  63. };
  64. Console.WriteLine("Starting capture...");
  65. capture.StartRecording();
  66. Console.WriteLine("Capturing, Press ENTER to exit");
  67. Console.ReadLine();
  68. Console.WriteLine("Stopping capture...");
  69. capture.StopRecording();
  70. Console.WriteLine("Stopped.");
  71. }
  72. private static void TestRunPlayBack(string[] argsToOp)
  73. {
  74. Console.WriteLine("Creating WasapiOut Instance...");
  75. var output = new WasapiOut();
  76. Console.WriteLine("Creating SineWaveSampleProvider Instance...");
  77. var provider = new SineWaveSampleProvider();
  78. Console.WriteLine("Init...");
  79. output.Init(provider);
  80. Console.WriteLine("Starting play...");
  81. output.Play();
  82. Console.WriteLine("Playing, Press ENTER to exit");
  83. Console.ReadLine();
  84. Console.WriteLine("Stopping Playing...");
  85. output.Stop();
  86. Console.WriteLine("Stopped.");
  87. }
  88. private static void TestRunPlayBackEcho(string[] argsToOp)
  89. {
  90. Console.WriteLine("Creating WasapiOut Instance...");
  91. var output = new WasapiOut();
  92. Console.WriteLine("Creating WaveInProvider Instance by WasapiLoopbackCapture...");
  93. var provider = new WaveInProvider(new WasapiLoopbackCapture());
  94. Console.WriteLine("Init...");
  95. output.Init(provider);
  96. //output.Volume = 0.5f;
  97. Console.WriteLine("Starting Capture...");
  98. provider.StartRecording();
  99. Console.WriteLine("Starting play...");
  100. output.Play();
  101. Console.WriteLine("Playing, Press ENTER to exit");
  102. Console.ReadLine();
  103. Console.WriteLine("Stopping Capture...");
  104. provider.StopRecording();
  105. Console.WriteLine("Stopping Playing...");
  106. output.Stop();
  107. Console.WriteLine("Stopped.");
  108. }
  109. private static void TestRunPlayBackEcho2(string[] argsToOp)
  110. {
  111. Console.WriteLine("Creating WasapiOut Instance...");
  112. var output = new WasapiOut();
  113. Console.WriteLine("Creating WasapiLoopbackCapture Instance...");
  114. var capture = new WasapiLoopbackCapture();
  115. Console.WriteLine("Creating WasapiLoopbackCapture Instance...");
  116. var provider = new BufferedWaveProvider(capture.WaveFormat);
  117. Console.WriteLine("Setup...");
  118. capture.DataAvailable += (sender, args) => provider.AddSamples(args.Buffer, 0, args.BytesRecorded);
  119. output.Init(provider);
  120. Console.WriteLine("Starting Capture...");
  121. capture.StartRecording();
  122. Console.WriteLine("Starting play...");
  123. output.Play();
  124. Console.WriteLine("Playing, Press ENTER to exit");
  125. Console.ReadLine();
  126. Console.WriteLine("Stopping Capture...");
  127. capture.StopRecording();
  128. Console.WriteLine("Free resoures...");
  129. provider.ClearBuffer();
  130. Console.WriteLine("Stopping Playing...");
  131. output.Stop();
  132. Console.WriteLine("Stopped.");
  133. }
  134. private static void RunReceiver(string[] argsToOp)
  135. {
  136. const int bufferMs = 500;
  137. if (argsToOp.Length < 1) throw new ArgumentException("least 1 args for listen port");
  138. if (false == int.TryParse(argsToOp[0], out var port)) throw new ArgumentException("invalid port");
  139. IPAddress address = null;
  140. if (argsToOp.Length > 1 && false == IPAddress.TryParse(argsToOp[1], out address)) throw new ArgumentException("invalid address");
  141. Console.WriteLine("Creating TcpListener...");
  142. var listener = new TcpListener(address ?? IPAddress.Any, port);
  143. Console.WriteLine("Start Listening...");
  144. listener.Start();
  145. Console.WriteLine($"Listening on {listener.LocalEndpoint}");
  146. var running = true;
  147. var task = Task.Run(() =>
  148. {
  149. while (running)
  150. {
  151. Console.WriteLine("Waiting connection...");
  152. var client = listener.AcceptTcpClient();
  153. client.NoDelay = true;
  154. Console.WriteLine($"Accept connection from {client.Client.RemoteEndPoint}...");
  155. var stream = client.GetStream();
  156. Console.WriteLine("Parsing wave format and create BufferedWaveProvider...");
  157. var reader = new BinaryReader(stream, Encoding.UTF8, true);
  158. var provider = new BufferedWaveProvider(new WaveFormat(reader))
  159. {
  160. BufferDuration = TimeSpan.FromMilliseconds(bufferMs),
  161. DiscardOnBufferOverflow = true,
  162. };
  163. reader.Dispose();
  164. Console.WriteLine($"Encoding: {provider.WaveFormat.Encoding}");
  165. Console.WriteLine($"Channels: {provider.WaveFormat.Channels}");
  166. Console.WriteLine($"SampleRate: {provider.WaveFormat.SampleRate}");
  167. Console.WriteLine($"BitsPerSample: {provider.WaveFormat.BitsPerSample}");
  168. Console.WriteLine($"Buffer Duration set to {provider.BufferDuration}");
  169. Console.WriteLine("Creating WasapiOut Instance...");
  170. var output = new WasapiOut();
  171. Console.WriteLine("Setup...");
  172. output.Init(provider);
  173. const int fillSleep = bufferMs*11/20;
  174. Console.WriteLine($"Filling buffer, sleep {fillSleep}ms");
  175. Thread.Sleep(fillSleep);
  176. Console.WriteLine("Starting play...");
  177. output.Play();
  178. try
  179. {
  180. var buf = new byte[1024 * 1024];//1MB
  181. int readCount;
  182. //Console.WriteLine("Sending ACK...");
  183. //stream.WriteByte(1);
  184. Console.WriteLine("Begin read chunks...");
  185. do
  186. {
  187. Console.Title = $"Buffered bytes: {provider.BufferedBytes:N0}";
  188. readCount = stream.Read(buf, 0, buf.Length);
  189. provider.AddSamples(buf, 0, readCount);
  190. } while (readCount > 0 && running);
  191. }
  192. catch (Exception e)
  193. {
  194. Console.WriteLine(e);
  195. }
  196. Console.WriteLine("Closing connection...");
  197. client.Close();
  198. provider.ClearBuffer();
  199. Console.WriteLine("Stopping Playing...");
  200. output.Stop();
  201. Console.WriteLine("Free resources");
  202. client.Dispose();
  203. }
  204. });
  205. Console.WriteLine("Press ENTER to Exit.");
  206. Console.ReadLine();
  207. running = false;
  208. task.Wait();
  209. Console.WriteLine("Finished.");
  210. }
  211. private static void RunTransmitterSineWave(string[] argsToOp)
  212. {
  213. if (argsToOp.Length != 2) throw new ArgumentException("required 2 args for address and port");
  214. if (false == IPAddress.TryParse(argsToOp[0], out var address)) throw new ArgumentException("invalid address");
  215. if (false == int.TryParse(argsToOp[1], out var port)) throw new ArgumentException("invalid port");
  216. var running = true;
  217. var task = Task.Run(() =>
  218. {
  219. do
  220. {
  221. TcpClient client = null;
  222. try
  223. {
  224. Console.WriteLine("Creating TcpClient ...");
  225. client = new TcpClient();
  226. var ipEndPoint = new IPEndPoint(address, port);
  227. Console.WriteLine($"Connecting to {ipEndPoint} ...");
  228. client.Connect(ipEndPoint);
  229. var stream = client.GetStream();
  230. Console.WriteLine("Connected, creating SineWaveSampleProvider ...");
  231. var provider = new SineWaveSampleProvider();
  232. Console.WriteLine("Sending wave format ...");
  233. var writer = new BinaryWriter(stream, Encoding.UTF8, true);
  234. provider.WaveFormat.Serialize(writer);
  235. writer.Close();
  236. Console.WriteLine("Begin sending chunks...");
  237. var buffer = new byte[provider.WaveFormat.AverageBytesPerSecond / 4];
  238. while (running)
  239. {
  240. provider.Read(buffer, 0, buffer.Length);
  241. stream.Write(buffer, 0, buffer.Length);
  242. }
  243. }
  244. catch (Exception e)
  245. {
  246. Console.WriteLine(e);
  247. if (running)
  248. {
  249. Console.WriteLine("Wait 10 Seconds to re connection");
  250. Thread.Sleep(10 * 1000);
  251. }
  252. }
  253. finally
  254. {
  255. Console.WriteLine("Free resources");
  256. client?.Close();
  257. client?.Dispose();
  258. }
  259. } while (running);
  260. });
  261. Console.WriteLine("Press ENTER to exit");
  262. Console.ReadLine();
  263. running = false;
  264. task.Wait();
  265. Console.WriteLine("Finished.");
  266. }
  267. private static void RunTransmitter(string[] argsToOp)
  268. {
  269. if (argsToOp.Length != 2) throw new ArgumentException("required 2 args for address and port");
  270. if (false == IPAddress.TryParse(argsToOp[0], out var address)) throw new ArgumentException("invalid address");
  271. if (false == int.TryParse(argsToOp[1], out var port)) throw new ArgumentException("invalid port");
  272. var running = true;
  273. var task = Task.Run(() =>
  274. {
  275. do
  276. {
  277. TcpClient client = null;
  278. try
  279. {
  280. Console.WriteLine("Creating TcpClient ...");
  281. client = new TcpClient { NoDelay = true };
  282. var ipEndPoint = new IPEndPoint(address, port);
  283. Console.WriteLine($"Connecting to {ipEndPoint} ...");
  284. client.Connect(ipEndPoint);
  285. var stream = client.GetStream();
  286. Console.WriteLine("Creating WasapiLoopbackCapture Instance...");
  287. var provider = new WasapiLoopbackCapture();
  288. Console.WriteLine("Sending wave format ...");
  289. var writer = new BinaryWriter(stream, Encoding.UTF8, true);
  290. provider.WaveFormat.Serialize(writer);
  291. writer.Close();
  292. //Console.WriteLine("Waiting ACK ...");
  293. //var ack = stream.ReadByte();
  294. //if (ack != 1) throw new Exception("Unexpected ACK");
  295. Console.WriteLine("Begin sending chunks...");
  296. long totalSent = 0;
  297. var up = DateTime.Now;
  298. provider.DataAvailable += (sender, args) =>
  299. {
  300. Console.Title = $"Sent bytes: {totalSent:N0}, UP: {DateTime.Now-up}";
  301. stream.Write(args.Buffer, 0, args.BytesRecorded);
  302. stream.Flush();
  303. totalSent += args.BytesRecorded;
  304. };
  305. provider.StartRecording();
  306. while (running) Thread.Sleep(100);
  307. provider.StopRecording();
  308. }
  309. catch (Exception e)
  310. {
  311. Console.WriteLine(e);
  312. if (running)
  313. {
  314. Console.WriteLine("Wait 10 Seconds to re connection");
  315. Thread.Sleep(10 * 1000);
  316. }
  317. }
  318. finally
  319. {
  320. Console.WriteLine("Free resources");
  321. client?.Close();
  322. client?.Dispose();
  323. }
  324. } while (running);
  325. });
  326. Console.WriteLine("Press ENTER to exit");
  327. Console.ReadLine();
  328. running = false;
  329. task.Wait();
  330. Console.WriteLine("Finished.");
  331. }
  332. }
  333. //internal class BufferedWaveProvider : IWaveProvider, IDisposable
  334. //{
  335. // private readonly BlockingCollection<byte> _buffer;
  336. // private readonly CancellationTokenSource _cancellationTokenSource = new();
  337. // public BufferedWaveProvider(WaveFormat waveFormat, int msBuffer = 1000)
  338. // {
  339. // WaveFormat = waveFormat;
  340. // _buffer = new BlockingCollection<byte>((int)Math.Ceiling(WaveFormat.SampleRate / 1000f * msBuffer * WaveFormat.Channels * (WaveFormat.BitsPerSample / 8f)));
  341. // }
  342. // public void Feed(byte[] buffer, int count)
  343. // {
  344. // if (_cancellationTokenSource.IsCancellationRequested) return;
  345. // for (var i = 0; i < count; i++) _buffer.Add(buffer[i], _cancellationTokenSource.Token);
  346. // }
  347. // public int Read(byte[] buffer, int offset, int count)
  348. // {
  349. // if (_cancellationTokenSource.IsCancellationRequested) return 0;
  350. // var readCount = 0;
  351. // var eof = offset + count;
  352. // do
  353. // {
  354. // try
  355. // {
  356. // buffer[offset] = _buffer.Take(_cancellationTokenSource.Token);
  357. // }
  358. // catch (OperationCanceledException)
  359. // {
  360. // return readCount;
  361. // }
  362. // readCount++;
  363. // offset++;
  364. // } while (_buffer.Count > 0 && offset < eof && _cancellationTokenSource.IsCancellationRequested == false);
  365. // return readCount;
  366. // }
  367. // public WaveFormat WaveFormat { get; }
  368. // public object BufferedBytes => _buffer.Count;
  369. // public void Dispose()
  370. // {
  371. // _cancellationTokenSource?.Cancel();
  372. // _buffer?.Dispose();
  373. // _cancellationTokenSource?.Dispose();
  374. // }
  375. //}
  376. internal class WaveInProvider : IWaveProvider
  377. {
  378. private readonly IWaveIn _input;
  379. private readonly BlockingCollection<byte> _buffer;
  380. private readonly CancellationTokenSource _cancellationTokenSource = new();
  381. public WaveFormat WaveFormat { get; }
  382. public WaveInProvider(IWaveIn input, int msBuffer = 1000)
  383. {
  384. _input = input;
  385. WaveFormat = _input.WaveFormat;
  386. _input.DataAvailable += Input_DataAvailable;
  387. _buffer = new((int)Math.Ceiling(WaveFormat.SampleRate / 1000f * msBuffer * WaveFormat.Channels * (WaveFormat.BitsPerSample / 8f)));
  388. }
  389. private void Input_DataAvailable(object sender, WaveInEventArgs e)
  390. {
  391. if (_cancellationTokenSource.IsCancellationRequested) return;
  392. for (var i = 0; i < e.BytesRecorded; i++) _buffer.Add(e.Buffer[i], _cancellationTokenSource.Token);
  393. }
  394. public void StartRecording()
  395. {
  396. _input.StartRecording();
  397. }
  398. public void StopRecording()
  399. {
  400. _input.StopRecording();
  401. _cancellationTokenSource.Cancel();
  402. }
  403. public int Read(byte[] buffer, int offset, int count)
  404. {
  405. var readCount = 0;
  406. var eof = offset + count;
  407. do
  408. {
  409. try
  410. {
  411. buffer[offset] = _buffer.Take(_cancellationTokenSource.Token);
  412. }
  413. catch (OperationCanceledException)
  414. {
  415. return readCount;
  416. }
  417. readCount++;
  418. offset++;
  419. } while (_buffer.Count > 0 && offset < eof && _cancellationTokenSource.IsCancellationRequested == false);
  420. return readCount;
  421. }
  422. }
  423. internal class SineWaveSampleProvider : IWaveProvider, ISampleProvider
  424. {
  425. public int Freq { get; }
  426. public float Amp { get; }
  427. public WaveFormat WaveFormat { get; }
  428. private int _sample;
  429. public SineWaveSampleProvider(int sampleRate = 441000, int channels = 1, int freq = 1000, float amp = 0.25f)
  430. {
  431. Freq = freq;
  432. Amp = amp;
  433. WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels);
  434. }
  435. public int Read(byte[] buffer, int offset, int count)
  436. {
  437. var waveBuffer = new WaveBuffer(buffer);
  438. var samplesRequired = count / 4;
  439. var samplesRead = Read(waveBuffer.FloatBuffer, offset / 4, samplesRequired);
  440. return samplesRead * 4;
  441. }
  442. public int Read(float[] buffer, int offset, int sampleCount)
  443. {
  444. var sampleRate = WaveFormat.SampleRate;
  445. for (var n = 0; n < sampleCount; n++)
  446. {
  447. buffer[n + offset] = (float)(Amp * Math.Sin((2 * Math.PI * _sample * Freq) / sampleRate));
  448. _sample++;
  449. if (_sample >= sampleRate) _sample = 0;
  450. }
  451. return sampleCount;
  452. }
  453. }
  454. }