diff --git a/FileAudioSource/App.config b/FileAudioSource/App.config new file mode 100644 index 0000000..b50c74f --- /dev/null +++ b/FileAudioSource/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/FileAudioSource/App.xaml b/FileAudioSource/App.xaml new file mode 100644 index 0000000..07ef911 --- /dev/null +++ b/FileAudioSource/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/FileAudioSource/App.xaml.cs b/FileAudioSource/App.xaml.cs new file mode 100644 index 0000000..f23539d --- /dev/null +++ b/FileAudioSource/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace FileAudioSource +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/FileAudioSource/FileAudioSource.csproj b/FileAudioSource/FileAudioSource.csproj new file mode 100644 index 0000000..006fa03 --- /dev/null +++ b/FileAudioSource/FileAudioSource.csproj @@ -0,0 +1,122 @@ + + + + + Debug + AnyCPU + {DF44A453-1359-4A30-828F-9D1B169032B8} + WinExe + FileAudioSource + FileAudioSource + v4.6.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + + + x64 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + false + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + ..\packages\OpenTok.Client.2.26.2.1\lib\net462\OpenTokNetStandard.dll + + + + + + + + + + + 4.0 + + + + + + ..\packages\OpenTok.Client.2.26.2.1\lib\net462\WinFormsVideoRenderer.dll + + + ..\packages\OpenTok.Client.2.26.2.1\lib\net462\WPFVideoRenderer.dll + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/FileAudioSource/FileSourceAudioDevice.cs b/FileAudioSource/FileSourceAudioDevice.cs new file mode 100644 index 0000000..b7eff17 --- /dev/null +++ b/FileAudioSource/FileSourceAudioDevice.cs @@ -0,0 +1,287 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using OpenTok; + +namespace FileAudioSource +{ + public class FileSourceAudioDevice : IAudioDevice, IDisposable + { + #region Public + + public void SelectSourceFile(string file) + { + if (!File.Exists(file)) + throw new ArgumentException("File does not exist"); + if (!file.EndsWith(".wav")) + throw new ArgumentException("Invalid file format"); + lock (audioDeviceLock) + { + sourceFile = file; + AudioDevice.RestartInputAudioDevice(); + } + } + + public delegate void AudioPropertiesChangedHandler(int numberOfChannels, int samplingRate); + public event AudioPropertiesChangedHandler AudioPropertiesChanged; + + #endregion + + #region IAudioDevice + + public void InitAudio(AudioDevice.AudioBus audioBus) + { + lock (audioDeviceLock) + { + this.audioBus = audioBus; + } + } + + public void DestroyAudio() + { + lock (audioDeviceLock) + { + audioBus = null; + } + } + + public void InitAudioCapturer() + { + lock (audioDeviceLock) + { + if (IsAudioCapturerInitialized()) + return; + if (!string.IsNullOrWhiteSpace(sourceFile)) + { + sourceFileStream = File.OpenRead(sourceFile); + /* WAV file format header struct can be found at https://docs.fileformat.com/audio/wav/ */ + byte[] wavHeader = new byte[40]; + sourceFileStream.Read(wavHeader, 0, wavHeader.Length); + capturerSettings = new AudioDeviceSettings(); + capturerSettings.NumChannels = wavHeader[22] | (wavHeader[23] << 8); + capturerSettings.SamplingRate = wavHeader[24] | (wavHeader[25] << 8) | (wavHeader[26] << 16) | (wavHeader[27] << 24); + AudioPropertiesChanged?.Invoke(capturerSettings.NumChannels, capturerSettings.SamplingRate); + } + else + { + capturerSettings = new AudioDeviceSettings() { NumChannels = 2, SamplingRate = 48000 }; + AudioPropertiesChanged?.Invoke(0, 0); + } + isAudioCapturerInitialized = true; + } + } + + public void DestroyAudioCapturer() + { + lock (audioDeviceLock) + { + StopAudioCapturer(); + if (!IsAudioCapturerInitialized()) + return; + if (sourceFileStream != null) + { + sourceFileStream.Close(); + sourceFileStream.Dispose(); + sourceFileStream = null; + } + isAudioCapturerInitialized = false; + } + } + + public void StartAudioCapturer() + { + lock (audioDeviceLock) + { + if (IsAudioCapturerStarted()) + return; + if (!IsAudioCapturerInitialized()) + throw new Exception("Audio capturer not initialized"); + + isAudioCapturerStarted = true; + + _ = Task.Factory.StartNew(() => + { + int numberOfSamplesPer10ms = capturerSettings.NumChannels * capturerSettings.SamplingRate / 100; + byte[] buffer = new byte[numberOfSamplesPer10ms * 2]; + unsafe + { + fixed (byte* bufferPtr = buffer) + { + DateTime nextBatchTime = DateTime.Now; + while (isAudioCapturerStarted) + { + try + { + if (DateTime.Now >= nextBatchTime) + { + nextBatchTime += new TimeSpan(0, 0, 0, 0, 10); + + int bytesRead = buffer.Length; + if (sourceFileStream != null) + bytesRead = sourceFileStream.Read(buffer, 0, buffer.Length); + audioBus.WriteCaptureData((IntPtr)bufferPtr, bytesRead / (2 * capturerSettings.NumChannels)); + } + Thread.Sleep(1); + } + catch (Exception ex) + { + } + } + } + } + }); + } + } + + public void StopAudioCapturer() + { + lock (audioDeviceLock) + { + if (!IsAudioCapturerStarted()) + return; + isAudioCapturerStarted = false; + Thread.Sleep(20); //Wait two whole cycles to make sure it's stopped + } + } + + public bool IsAudioCapturerInitialized() + { + lock (audioDeviceLock) + { + return isAudioCapturerInitialized; + } + } + + public bool IsAudioCapturerStarted() + { + lock (audioDeviceLock) + { + return isAudioCapturerStarted; + } + } + + public int GetEstimatedAudioCaptureDelay() + { + lock (audioDeviceLock) + { + return 0; + } + } + + public AudioDeviceSettings GetAudioCapturerSettings() + { + lock (audioDeviceLock) + { + return capturerSettings; + } + } + + public void InitAudioRenderer() + { + lock (audioDeviceLock) + { + isAudioRendererInitialized = true; + } + } + + public void DestroyAudioRenderer() + { + lock (audioDeviceLock) + { + StopAudioRenderer(); + isAudioRendererInitialized = false; + } + } + + public void StartAudioRenderer() + { + lock (audioDeviceLock) + { + isAudioRendererStarted = true; + } + } + + public void StopAudioRenderer() + { + lock (audioDeviceLock) + { + isAudioRendererStarted = false; + } + } + + public bool IsAudioRendererInitialized() + { + lock (audioDeviceLock) + { + return isAudioRendererInitialized; + } + } + + public bool IsAudioRendererStarted() + { + lock (audioDeviceLock) + { + return isAudioRendererStarted; + } + } + + public int GetEstimatedAudioRenderDelay() + { + return 0; + } + + public AudioDeviceSettings GetAudioRendererSettings() + { + return new AudioDeviceSettings(); + } + + #endregion + + #region IDisposable + + protected virtual void Dispose(bool disposing) + { + if (isDisposed) + return; + isDisposed = true; + + DestroyAudioCapturer(); + DestroyAudioRenderer(); + + if (disposing) + { + } + } + + ~FileSourceAudioDevice() + { + Dispose(disposing: false); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + #endregion + + #region Private + + private readonly object audioDeviceLock = new object(); + private AudioDevice.AudioBus audioBus; + private bool isAudioCapturerInitialized; + private bool isAudioRendererInitialized; + private bool isAudioCapturerStarted; + private bool isAudioRendererStarted; + private string sourceFile; + private FileStream sourceFileStream; + private AudioDeviceSettings capturerSettings; + private bool isDisposed; + + #endregion + } +} diff --git a/FileAudioSource/MainWindow.xaml b/FileAudioSource/MainWindow.xaml new file mode 100644 index 0000000..3af6696 --- /dev/null +++ b/FileAudioSource/MainWindow.xaml @@ -0,0 +1,16 @@ + + + +