/// <summary> /// Fills recording buffers. /// </summary> private void EnsureBuffers() { // We keep 3 x buffer. lock (m_pBuffers){ while (m_pBuffers.Count < 3) { byte[] data = new byte[m_BufferSize]; GCHandle dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned); WAVEHDR wavHeader = new WAVEHDR(); wavHeader.lpData = dataHandle.AddrOfPinnedObject(); wavHeader.dwBufferLength = (uint)data.Length; wavHeader.dwBytesRecorded = 0; wavHeader.dwUser = IntPtr.Zero; wavHeader.dwFlags = 0; wavHeader.dwLoops = 0; wavHeader.lpNext = IntPtr.Zero; wavHeader.reserved = 0; GCHandle headerHandle = GCHandle.Alloc(wavHeader, GCHandleType.Pinned); int result = 0; result = WavMethods.waveInPrepareHeader(m_pWavDevHandle, headerHandle.AddrOfPinnedObject(), Marshal.SizeOf(wavHeader)); if (result == MMSYSERR.NOERROR) { m_pBuffers.Add(new BufferItem(ref headerHandle, ref dataHandle, m_BufferSize)); result = WavMethods.waveInAddBuffer(m_pWavDevHandle, headerHandle.AddrOfPinnedObject(), Marshal.SizeOf(wavHeader)); if (result != MMSYSERR.NOERROR) { throw new Exception("Error adding wave in buffer, error: " + result + "."); } } } } }
/// <summary> /// Cleans up any resources being used. /// </summary> public void Dispose() { if (m_IsDisposed) { return; } m_IsDisposed = true; // Release events. this.BufferFull = null; try{ // If recording, we need to reset wav device first. WavMethods.waveInReset(m_pWavDevHandle); // If there are unprepared wav headers, we need to unprepare these. foreach (BufferItem item in m_pBuffers) { WavMethods.waveInUnprepareHeader(m_pWavDevHandle, item.HeaderHandle.AddrOfPinnedObject(), Marshal.SizeOf(item.Header)); item.Dispose(); } // Close input device. WavMethods.waveInClose(m_pWavDevHandle); m_pInDevice = null; m_pWavDevHandle = IntPtr.Zero; } catch { } }
/// <summary> /// Cleans up any resources being used. /// </summary> public void Dispose() { if (m_IsDisposed) { return; } m_IsDisposed = true; lock (m_pPlayItems) { try { // If playing, we need to reset wav device first. WavMethods.waveOutReset(m_pWavDevHandle); // If there are unprepared wav headers, we need to unprepare these. foreach (PlayItem item in m_pPlayItems) { WavMethods.waveOutUnprepareHeader(m_pWavDevHandle, item.HeaderHandle.AddrOfPinnedObject(), Marshal.SizeOf(item.Header)); item.Dispose(); } // Close output device. WavMethods.waveOutClose(m_pWavDevHandle); m_pOutDevice = null; m_pWavDevHandle = IntPtr.Zero; m_pPlayItems = null; m_pWaveOutProc = null; } catch { } } }
/// <summary> /// Processes first first filled buffer in queue and disposes it if done. /// </summary> /// <param name="state">User data.</param> private void ProcessFirstBuffer(object state) { try { lock (m_pBuffers) { if (m_IsDisposed) { return; } BufferItem item = m_pBuffers[0]; // Raise BufferFull event. OnBufferFull(item.Data); if (m_IsDisposed) { return; } // Clean up. WavMethods.waveInUnprepareHeader(m_pWavDevHandle, item.HeaderHandle.AddrOfPinnedObject(), Marshal.SizeOf(item.Header)); m_pBuffers.Remove(item); item.Dispose(); } EnsureBuffers(); } catch { } }
/// <summary> /// Sets audio output volume. /// </summary> /// <param name="left">Left channel volume level.</param> /// <param name="right">Right channel volume level.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> public void SetVolume(ushort left, ushort right) { if (m_IsDisposed) { throw new ObjectDisposedException("WaveOut"); } WavMethods.waveOutSetVolume(m_pWavDevHandle, (right << 16 | left & 0xFFFF)); }
public void Continue() { if (!m_IsPaused) { return; } lock (m_pPlayItems) { m_IsPaused = false; WavMethods.waveOutRestart(m_pWavDevHandle); } }
public void Pause() { if (m_IsPaused) { return; } lock (m_pPlayItems) { m_IsPaused = true; WavMethods.waveOutPause(m_pWavDevHandle); } }
protected virtual void Dispose(bool disposing) { if (!m_IsDisposed) { // Release events. this.BufferFull = null; try { // If recording, we need to reset wav device first. WavMethods.waveInReset(m_pWavDevHandle); // If there are unprepared wav headers, we need to unprepare these. lock (m_pBuffers) { foreach (BufferItem item in m_pBuffers) { try { WavMethods.waveInUnprepareHeader(m_pWavDevHandle, item.HeaderHandle.AddrOfPinnedObject(), Marshal.SizeOf(item.Header)); } catch { Debug.WriteLine("WaveIn.Dispose() - WavMethods.waveInUnprepareHeader()"); } item.Dispose(); } } // Close input device. Debug.WriteLine("WaveIn.Dispose() - waveInClose"); try { WavMethods.waveInClose(m_pWavDevHandle); } catch { Debug.WriteLine("WaveIn.Dispose() - WavMethods.waveInClose()"); } finally { m_pInDevice = null; m_pWavDevHandle = IntPtr.Zero; } } catch (Exception ex) { Debug.WriteLine("WaveIn.Dispose() - " + ex.Message); } } m_IsDisposed = true; }
/// <summary> /// Gets audio output volume. /// </summary> /// <param name="left">Left channel volume level.</param> /// <param name="right">Right channel volume level.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> public void GetVolume(ref ushort left, ref ushort right) { if (m_IsDisposed) { throw new ObjectDisposedException("WaveOut"); } int volume = 0; WavMethods.waveOutGetVolume(m_pWavDevHandle, out volume); left = (ushort)(volume & 0x0000ffff); right = (ushort)(volume >> 16); }
/// <summary> /// Default constructor. /// </summary> /// <param name="outputDevice">Output device.</param> /// <param name="samplesPerSec">Sample rate, in samples per second (hertz). For PCM common values are /// 8.0 kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHz.</param> /// <param name="bitsPerSample">Bits per sample. For PCM 8 or 16 are the only valid values.</param> /// <param name="channels">Number of channels.</param> /// <exception cref="ArgumentNullException">Is raised when <b>outputDevice</b> is null.</exception> /// <exception cref="ArgumentException">Is raised when any of the aruments has invalid value.</exception> public WaveOut(WavOutDevice outputDevice, int samplesPerSec, int bitsPerSample, int channels) { if (outputDevice == null) { throw new ArgumentNullException("outputDevice"); } if (samplesPerSec < 8000) { throw new ArgumentException("Argument 'samplesPerSec' value must be >= 8000."); } if (bitsPerSample < 8) { throw new ArgumentException("Argument 'bitsPerSample' value must be >= 8."); } if (channels < 1) { throw new ArgumentException("Argument 'channels' value must be >= 1."); } m_pOutDevice = outputDevice; m_SamplesPerSec = samplesPerSec; m_BitsPerSample = bitsPerSample; m_Channels = channels; m_BlockSize = m_Channels * (m_BitsPerSample / 8); m_pPlayItems = new List <PlayItem>(); // Try to open wav device. WAVEFORMATEX format = new WAVEFORMATEX(); format.wFormatTag = WavFormat.PCM; format.nChannels = (ushort)m_Channels; format.nSamplesPerSec = (uint)samplesPerSec; //1秒钟数据量(字节/秒)= (采样频率(Hz)*采样位数(bit)*声道数)/ 8 //换算成20ms的数据量则再除50 format.nAvgBytesPerSec = (uint)((m_SamplesPerSec * m_Channels * (m_BitsPerSample / 8))) / 50; format.nBlockAlign = (ushort)m_BlockSize; format.wBitsPerSample = (ushort)m_BitsPerSample; format.cbSize = 0; m_MinBuffer = (int)format.nAvgBytesPerSec * 10; // We must delegate reference, otherwise GC will collect it. m_pWaveOutProc = new waveOutProc(this.OnWaveOutProc); int result = WavMethods.waveOutOpen(out m_pWavDevHandle, m_pOutDevice.Index, format, m_pWaveOutProc, 0, WavConstants.CALLBACK_FUNCTION); if (result != MMSYSERR.NOERROR) { throw new Exception("Failed to open wav device, error: " + result.ToString() + "."); } }
/// <summary> /// Cleans up the first data block in play queue. /// </summary> /// <param name="state">User data.</param> private void OnCleanUpFirstBlock(object state) { try{ lock (m_pPlayItems){ PlayItem item = m_pPlayItems[0]; WavMethods.waveOutUnprepareHeader(m_pWavDevHandle, item.HeaderHandle.AddrOfPinnedObject(), Marshal.SizeOf(item.Header)); m_pPlayItems.Remove(item); m_BytesBuffered -= item.DataSize; item.Dispose(); } } catch { } }
/// <summary> /// Stops recording. /// </summary> public void Stop() { if (!m_IsRecording) { return; } m_IsRecording = false; int result = WavMethods.waveInStop(m_pWavDevHandle); if (result != MMSYSERR.NOERROR) { throw new Exception("Failed to stop wav device, error: " + result + "."); } }
/// <summary> /// Default constructor. /// </summary> /// <param name="outputDevice">Input device.</param> /// <param name="samplesPerSec">Sample rate, in samples per second (hertz). For PCM common values are /// 8.0 kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHz.</param> /// <param name="bitsPerSample">Bits per sample. For PCM 8 or 16 are the only valid values.</param> /// <param name="channels">Number of channels.</param> /// <param name="bufferSize">Specifies recording buffer size.</param> /// <exception cref="ArgumentNullException">Is raised when <b>outputDevice</b> is null.</exception> /// <exception cref="ArgumentException">Is raised when any of the aruments has invalid value.</exception> public WaveIn(WavInDevice device, int samplesPerSec, int bitsPerSample, int channels, int bufferSize) { if (device == null) { throw new ArgumentNullException("device"); } if (samplesPerSec < 8000) { throw new ArgumentException("Argument 'samplesPerSec' value must be >= 8000."); } if (bitsPerSample < 8) { throw new ArgumentException("Argument 'bitsPerSample' value must be >= 8."); } if (channels < 1) { throw new ArgumentException("Argument 'channels' value must be >= 1."); } m_pInDevice = device; m_SamplesPerSec = samplesPerSec; m_BitsPerSample = bitsPerSample; m_Channels = channels; m_BufferSize = bufferSize; m_BlockSize = m_Channels * (m_BitsPerSample / 8); m_pBuffers = new List <BufferItem>(); // Try to open wav device. WAVEFORMATEX format = new WAVEFORMATEX(); format.wFormatTag = WavFormat.PCM; format.nChannels = (ushort)m_Channels; format.nSamplesPerSec = (uint)samplesPerSec; format.nAvgBytesPerSec = (uint)(m_SamplesPerSec * m_Channels * (m_BitsPerSample / 8)); format.nBlockAlign = (ushort)m_BlockSize; format.wBitsPerSample = (ushort)m_BitsPerSample; format.cbSize = 0; // We must delegate reference, otherwise GC will collect it. m_pWaveInProc = new waveInProc(this.OnWaveInProc); int result = WavMethods.waveInOpen(out m_pWavDevHandle, m_pInDevice.Index, format, m_pWaveInProc, 0, WavConstants.CALLBACK_FUNCTION); if (result != MMSYSERR.NOERROR) { throw new Exception("Failed to open wav device, error: " + result.ToString() + "."); } EnsureBuffers(); }
/// <summary> /// Processes first first filled buffer in queue and disposes it if done. /// </summary> /// <param name="state">User data.</param> private void ProcessFirstBuffer(object state) { if (m_IsRecording) { BufferItem item; try { lock (m_pBuffers) { item = m_pBuffers[0]; // Raise BufferFull event. OnBufferFull(item.Data); // Clean up. try { Debug.WriteLine("WaveIn.ProcessFirstBuffer() - waveInUnprepareHeader"); WavMethods.waveInUnprepareHeader(m_pWavDevHandle, item.HeaderHandle.AddrOfPinnedObject(), Marshal.SizeOf(item.Header)); } catch (Exception ex) { Debug.WriteLine("WaveIn.ProcessFirstBuffer() - WavMethods.waveInUnprepareHeader - " + ex.Message); } finally { m_pBuffers.RemoveAt(0); Debug.WriteLine("WaveIn.ProcessFirstBuffer() - cleanup buffers"); item.Dispose(); } } Debug.WriteLine("WaveIn.ProcessFirstBuffer() - EnsureBuffers()"); EnsureBuffers(); } catch (Exception ex) { Debug.WriteLine("WaveIn.ProcessFirstBuffer() - " + ex.Message); } } }
public void Play(byte[] audioData, int offset, int count) { if (m_IsDisposed) { throw new ObjectDisposedException("WaveOut"); } if (audioData == null) { throw new ArgumentNullException("audioData"); } if ((count % m_BlockSize) != 0) { throw new ArgumentException("Audio data is not n * BlockSize."); } while (m_pPlayItems.Count > 10) { Thread.Sleep(10); } byte[] data = new byte[count]; Array.Copy(audioData, offset, data, 0, count); GCHandle dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned); WAVEHDR wavHeader = new WAVEHDR(); wavHeader.lpData = dataHandle.AddrOfPinnedObject(); wavHeader.dwBufferLength = (uint)data.Length; wavHeader.dwBytesRecorded = 0; wavHeader.dwUser = IntPtr.Zero; wavHeader.dwFlags = 0; wavHeader.dwLoops = 0; wavHeader.lpNext = IntPtr.Zero; wavHeader.reserved = 0; GCHandle headerHandle = GCHandle.Alloc(wavHeader, GCHandleType.Pinned); int result = 0; result = WavMethods.waveOutPrepareHeader(m_pWavDevHandle, headerHandle.AddrOfPinnedObject(), Marshal.SizeOf(wavHeader)); if (result == MMSYSERR.NOERROR) { PlayItem item = new PlayItem(ref headerHandle, ref dataHandle, data.Length); lock (m_pPlayItems) { m_pPlayItems.Add(item); m_BytesBuffered += data.Length; if (m_BytesBuffered < m_MinBuffer / 10) { if (!m_IsBuffing) { WavMethods.waveOutPause(m_pWavDevHandle); m_IsBuffing = true; } } else if (m_IsBuffing && m_BytesBuffered > m_MinBuffer) { WavMethods.waveOutRestart(m_pWavDevHandle); m_IsBuffing = false; } result = WavMethods.waveOutWrite(m_pWavDevHandle, headerHandle.AddrOfPinnedObject(), Marshal.SizeOf(wavHeader)); } } else { dataHandle.Free(); headerHandle.Free(); } //-------------------------------------------------------------------------------------------------- }
/// <summary> /// Plays specified audio data bytes. If player is currently playing, data will be queued for playing. /// </summary> /// <param name="audioData">Audio data. Data boundary must n * BlockSize.</param> /// <param name="offset">Offset in the buffer.</param> /// <param name="count">Number of bytes to play form the specified offset.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>audioData</b> is null.</exception> /// <exception cref="ArgumentException">Is raised when <b>audioData</b> is with invalid length.</exception> public void Play(byte[] audioData, int offset, int count) { if (m_IsDisposed) { throw new ObjectDisposedException("WaveOut"); } if (audioData == null) { throw new ArgumentNullException("audioData"); } if ((count % m_BlockSize) != 0) { throw new ArgumentException("Audio data is not n * BlockSize."); } //--- Queue specified audio block for play. -------------------------------------------------------- byte[] data = new byte[count]; Array.Copy(audioData, offset, data, 0, count); GCHandle dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned); // m_BytesBuffered += data.Length; WAVEHDR wavHeader = new WAVEHDR(); wavHeader.lpData = dataHandle.AddrOfPinnedObject(); wavHeader.dwBufferLength = (uint)data.Length; wavHeader.dwBytesRecorded = 0; wavHeader.dwUser = IntPtr.Zero; wavHeader.dwFlags = 0; wavHeader.dwLoops = 0; wavHeader.lpNext = IntPtr.Zero; wavHeader.reserved = 0; GCHandle headerHandle = GCHandle.Alloc(wavHeader, GCHandleType.Pinned); int result = 0; result = WavMethods.waveOutPrepareHeader(m_pWavDevHandle, headerHandle.AddrOfPinnedObject(), Marshal.SizeOf(wavHeader)); if (result == MMSYSERR.NOERROR) { PlayItem item = new PlayItem(ref headerHandle, ref dataHandle, data.Length); m_pPlayItems.Add(item); // We ran out of minimum buffer, we must pause playing while min buffer filled. if (m_BytesBuffered < 1000) { if (!m_IsPaused) { WavMethods.waveOutPause(m_pWavDevHandle); m_IsPaused = true; } //File.AppendAllText("aaaa.txt","Begin buffer\r\n"); } // Buffering completed,we may resume playing. else if (m_IsPaused && m_BytesBuffered > m_MinBuffer) { WavMethods.waveOutRestart(m_pWavDevHandle); m_IsPaused = false; //File.AppendAllText("aaaa.txt","end buffer: " + m_BytesBuffered + "\r\n"); } /* * // TODO: If we ran out of minimum buffer, we must pause playing while min buffer filled. * if(m_BytesBuffered < m_MinBuffer){ * if(!m_IsPaused){ * WavMethods.waveOutPause(m_pWavDevHandle); * m_IsPaused = true; * } * } * else if(m_IsPaused){ * WavMethods.waveOutRestart(m_pWavDevHandle); * }*/ m_BytesBuffered += data.Length; result = WavMethods.waveOutWrite(m_pWavDevHandle, headerHandle.AddrOfPinnedObject(), Marshal.SizeOf(wavHeader)); } else { dataHandle.Free(); headerHandle.Free(); } //-------------------------------------------------------------------------------------------------- }