Hi,
I have created a program (copied most of it from CaptureSound) that
allows me to copy from one soundcard to another. The problem is that
whenever I use another application, or do something that causes a
slight CPU increase, Play starts to skip. what am I doing wrong?
//----------------------------------------------------------------------------
// File: Main.cs
//
// Copyright (c) Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using Microsoft.DirectX;
using Microsoft.DirectX.DirectSound;
public class MainForm : Form
{
private Label labelMainInputformatText;
private CheckBox checkboxRecord;
private Button buttonSoundfile;
private Label labelFilename;
private Label labelStatic;
public BufferPositionNotify[] PositionNotify = new
BufferPositionNotify[NumberRecordNotifications + 1];
public const int NumberRecordNotifications = 16;
public AutoResetEvent NotificationEvent = null;
public CaptureBuffer applicationBuffer = null;
public SecondaryBuffer playBuffer = null;
public Device playDevice = null;
public Guid CaptureDeviceGuid = Guid.Empty;
public Capture applicationDevice = null;
private string FileName = string.Empty;
public Notify applicationNotify = null;
public Notify PlayNotify = null;
private Thread InputNotifyThread = null;
private FileStream WaveFile = null;
private BinaryWriter Writer = null;
private string Path = string.Empty;
public int CaptureBufferSize = 0;
public int NextCaptureOffset = 0;
private bool Recording = false;
public WaveFormat InputFormat;
private int SampleCount = 0;
public int NotifySize = 0;
private bool Capturing = false;
byte[] CaptureData = null;
byte[] PlayData = null;
MemoryStream mystream = null;
[STAThread]
public static void Main()
{
try
{
using(MainForm form = new MainForm())
{
form.ShowDialog();
}
}
catch{}
}
private void MainForm_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
if (null != NotificationEvent)
{
Capturing = false;
NotificationEvent.Set();
}
if (null != applicationBuffer)
if (applicationBuffer.Capturing)
StartOrStopRecord(false);
}
public MainForm()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
DevicesForm devices = new DevicesForm(this);
devices.ShowDialog(this);
InitDirectSound();
if (null == applicationDevice)
Close();
else
{
FormatsForm formats = new FormatsForm(this);
if (formats.ShowDialog(this) == DialogResult.OK)
{
labelMainInputformatText.Text = string.Format("{0} Hz,
{1}-bit ",
InputFormat.SamplesPerSecond,
InputFormat.BitsPerSample) +
((1 == InputFormat.Channels) ? "Mono" : "Stereo");
CreateCaptureBuffer();
}
else
{
this.Close();
}
}
}
#region InitializeComponent code
private void InitializeComponent()
{
this.labelStatic = new System.Windows.Forms.Label();
this.labelMainInputformatText = new System.Windows.Forms.Label();
this.checkboxRecord = new System.Windows.Forms.CheckBox();
this.buttonSoundfile = new System.Windows.Forms.Button();
this.labelFilename = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// labelStatic
//
this.labelStatic.Location = new System.Drawing.Point(10, 46);
this.labelStatic.Name = "labelStatic";
this.labelStatic.Size = new System.Drawing.Size(75, 13);
this.labelStatic.TabIndex = 0;
this.labelStatic.Text = "Input Format:";
//
// labelMainInputformatText
//
this.labelMainInputformatText.Location = new
System.Drawing.Point(90, 46);
this.labelMainInputformatText.Name = "labelMainInputformatText";
this.labelMainInputformatText.Size = new System.Drawing.Size(262,
13);
this.labelMainInputformatText.TabIndex = 1;
//
// checkboxRecord
//
this.checkboxRecord.Appearance =
System.Windows.Forms.Appearance.Button;
this.checkboxRecord.Enabled = false;
this.checkboxRecord.FlatStyle =
System.Windows.Forms.FlatStyle.System;
this.checkboxRecord.Location = new System.Drawing.Point(369, 40);
this.checkboxRecord.Name = "checkboxRecord";
this.checkboxRecord.Size = new System.Drawing.Size(75, 23);
this.checkboxRecord.TabIndex = 2;
this.checkboxRecord.Text = "&Record";
this.checkboxRecord.CheckedChanged += new
System.EventHandler(this.checkboxRecord_CheckedChanged);
//
// buttonSoundfile
//
this.buttonSoundfile.Location = new System.Drawing.Point(10, 11);
this.buttonSoundfile.Name = "buttonSoundfile";
this.buttonSoundfile.Size = new System.Drawing.Size(78, 21);
this.buttonSoundfile.TabIndex = 3;
this.buttonSoundfile.Text = "Sound &file...";
this.buttonSoundfile.Click += new
System.EventHandler(this.buttonSoundfile_Click);
//
// labelFilename
//
this.labelFilename.BorderStyle =
System.Windows.Forms.BorderStyle.Fixed3D;
this.labelFilename.Location = new System.Drawing.Point(96, 11);
this.labelFilename.Name = "labelFilename";
this.labelFilename.Size = new System.Drawing.Size(348, 21);
this.labelFilename.TabIndex = 4;
this.labelFilename.Text = "No file loaded.";
this.labelFilename.TextAlign =
System.Drawing.ContentAlignment.MiddleLeft;
//
// MainForm
//
this.AcceptButton = this.buttonSoundfile;
this.ClientSize = new System.Drawing.Size(454, 77);
this.Controls.AddRange(new
System.Windows.Forms.Control[] {
this.labelStatic,
this.labelMainInputformatText,
this.checkboxRecord,
this.buttonSoundfile,
this.labelFilename});
this.FormBorderStyle =
System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Name = "MainForm";
this.Text = "CaptureSound";
this.Closing += new
System.ComponentModel.CancelEventHandler(this.MainForm_Closing);
this.ResumeLayout(false);
}
#endregion
private void InitDirectSound()
{
CaptureBufferSize = 0;
NotifySize = 0;
// Create DirectSound.Capture using the preferred capture device
try
{
applicationDevice = new Capture(CaptureDeviceGuid);
}
catch {}
}
void CreateCaptureBuffer()
{
//-----------------------------------------------------------------------------
// Name: CreateCaptureBuffer()
// Desc: Creates a capture buffer and sets the format
//-----------------------------------------------------------------------------
CaptureBufferDescription dscheckboxd = new
CaptureBufferDescription();
if (null != applicationNotify)
{
applicationNotify.Dispose();
applicationNotify = null;
}
if (null != applicationBuffer)
{
applicationBuffer.Dispose();
applicationBuffer = null;
}
if (0 == InputFormat.Channels)
return;
// Set the notification size
NotifySize = (1024 > InputFormat.AverageBytesPerSecond / 8) ? 1024 :
(InputFormat.AverageBytesPerSecond / 8);
NotifySize -= NotifySize %% InputFormat.BlockAlign;
// Set the buffer sizes
CaptureBufferSize = NotifySize * NumberRecordNotifications;
// Create the capture buffer
dscheckboxd.BufferBytes = CaptureBufferSize;
InputFormat.FormatTag = WaveFormatTag.Pcm;
dscheckboxd.Format = InputFormat; // Set the format during
creatation
applicationBuffer = new CaptureBuffer(dscheckboxd,
applicationDevice);
NextCaptureOffset = 0;
// we start to play here
playDevice = new Device();
playDevice.SetCooperativeLevel(this,
CooperativeLevel.Priority);
BufferDescription PlayDesc = new BufferDescription();
// Create the play buffer
PlayDesc.BufferBytes = CaptureBufferSize;
PlayDesc.Format = InputFormat; // Set the format during
creatation
PlayDesc.Flags =
BufferDescriptionFlags.ControlPositionNotify
| BufferDescriptionFlags.GlobalFocus |
BufferDescriptionFlags.StickyFocus |
BufferDescriptionFlags.ControlFrequency |
BufferDescriptionFlags.CanGetCurrentPosition;
try
{
playBuffer = new SecondaryBuffer(PlayDesc, playDevice);
}
catch (Exception e)
{
}
InitNotifications();
}
void InitNotifications()
{
//-----------------------------------------------------------------------------
// Name: InitNotifications()
// Desc: Inits the notifications on the capture buffer which are
handled
// in the notify thread.
//-----------------------------------------------------------------------------
if (null == applicationBuffer)
throw new NullReferenceException();
// Create a thread to monitor the input notify events
if (null == InputNotifyThread)
{
InputNotifyThread = new Thread(new ThreadStart(WaitThread));
Capturing = true;
InputNotifyThread.Start();
// Create a notification event, for when the sound stops playing
NotificationEvent = new AutoResetEvent(false);
}
// Setup the notification positions
for (int i = 0; i < NumberRecordNotifications; i++)
{
PositionNotify[i].Offset = (NotifySize * i) + NotifySize - 1;
PositionNotify[i].EventNotifyHandle = NotificationEvent.Handle;
}
applicationNotify = new Notify(applicationBuffer);
try
{
// PlayNotify = new Notify(playBuffer);
}
catch (Exception e)
{
}
// Tell DirectSound when to notify the app. The notification will
come in the from
// of signaled events that are handled in the notify thread.
applicationNotify.SetNotificationPositions(PositionNotify,
NumberRecordNotifications);
// PlayNotify.SetNotificationPositions(PositionNotify,
NumberRecordNotifications);
}
private void checkboxRecord_CheckedChanged(object sender,
System.EventArgs e)
{
Recording = !Recording;
StartOrStopRecord(Recording);
if (!Recording)
checkboxRecord.Enabled = false;
}
void StartOrStopRecord(bool StartRecording)
{
//-----------------------------------------------------------------------------
// Name: StartOrStopRecord()
// Desc: Starts or stops the capture buffer from recording
//-----------------------------------------------------------------------------
if (StartRecording)
{
// Create a capture buffer, and tell the capture
// buffer to start recording
CreateCaptureBuffer();
applicationBuffer.Start(true);
playBuffer.Play(0, BufferPlayFlags.Looping);
}
else
{
// Stop the buffer, and read any data that was not
// caught by a notification
applicationBuffer.Stop();
RecordCapturedData();
Writer.Seek(4, SeekOrigin.Begin); // Seek to the length descriptor
of the RIFF file.
Writer.Write((int)(SampleCount + 36)); // Write the file length,
minus first 8 bytes of RIFF description.
Writer.Seek(40, SeekOrigin.Begin); // Seek to the data length
descriptor of the RIFF file.
Writer.Write(SampleCount); // Write the length of the sample data
in bytes.
Writer.Close(); // Close the file now.
Writer = null; // Set the writer to null.
WaveFile = null; // Set the FileStream to null.
}
}
void CreateRIFF()
{
/
**************************************************************************
Here is where the file will be created. A
wave file is a RIFF file, which has chunks
of data that describe what the file contains.
A wave RIFF file is put together like this:
The 12 byte RIFF chunk is constructed like this:
Bytes 0 - 3 : 'R' 'I' 'F' 'F'
Bytes 4 - 7 : Length of file, minus the first 8 bytes of the RIFF
description.
(4 bytes for "WAVE" + 24 bytes for format chunk length +
8 bytes for data chunk description + actual sample data size.)
Bytes 8 - 11: 'W' 'A' 'V' 'E'
The 24 byte FORMAT chunk is constructed like this:
Bytes 0 - 3 : 'f' 'm' 't' ' '
Bytes 4 - 7 : The format chunk length. This is always 16.
Bytes 8 - 9 : File padding. Always 1.
Bytes 10- 11: Number of channels. Either 1 for mono, or 2 for
stereo.
Bytes 12- 15: Sample rate.
Bytes 16- 19: Number of bytes per second.
Bytes 20- 21: Bytes per sample. 1 for 8 bit mono, 2 for 8 bit
stereo or
16 bit mono, 4 for 16 bit stereo.
Bytes 22- 23: Number of bits per sample.
The DATA chunk is constructed like this:
Bytes 0 - 3 : 'd' 'a' 't' 'a'
Bytes 4 - 7 : Length of data, in bytes.
Bytes 8 -...: Actual sample data.
***************************************************************************/
// Open up the wave file for writing.
WaveFile = new FileStream(FileName, FileMode.Create);
Writer = new BinaryWriter(WaveFile);
// Set up file with RIFF chunk info.
char[] ChunkRiff = {'R','I','F','F'};
char[] ChunkType = {'W','A','V','E'};
char[] ChunkFmt = {'f','m','t',' '};
char[] ChunkData = {'d','a','t','a'};
short shPad = 1; // File padding
int nFormatChunkLength = 0x10; // Format chunk length.
int nLength = 0; // File length, minus first 8 bytes of RIFF
description. This will be filled in later.
short shBytesPerSample = 0; // Bytes per sample.
// Figure out how many bytes there will be per sample.
if (8 == InputFormat.BitsPerSample && 1 == InputFormat.Channels)
shBytesPerSample = 1;
else if ((8 == InputFormat.BitsPerSample && 2 ==
InputFormat.Channels) || (16 == InputFormat.BitsPerSample && 1 ==
InputFormat.Channels))
shBytesPerSample = 2;
else if (16 == InputFormat.BitsPerSample && 2 ==
InputFormat.Channels)
shBytesPerSample = 4;
// Fill in the riff info for the wave file.
Writer.Write(ChunkRiff);
Writer.Write(nLength);
Writer.Write(ChunkType);
// Fill in the format info for the wave file.
Writer.Write(ChunkFmt);
Writer.Write(nFormatChunkLength);
Writer.Write(shPad);
Writer.Write(InputFormat.Channels);
Writer.Write(InputFormat.SamplesPerSecond);
Writer.Write(InputFormat.AverageBytesPerSecond);
Writer.Write(shBytesPerSample);
Writer.Write(InputFormat.BitsPerSample);
// Now fill in the data chunk.
Writer.Write(ChunkData);
Writer.Write((int)0); // The sample length will be written in later.
}
void RecordCapturedData()
{
//-----------------------------------------------------------------------------
// Name: RecordCapturedData()
// Desc: Copies data from the capture buffer to the output buffer
//-----------------------------------------------------------------------------
int ReadPos;
int CapturePos;
int LockSize;
applicationBuffer.GetCurrentPosition(out CapturePos, out ReadPos);
LockSize = ReadPos - NextCaptureOffset;
if (LockSize < 0)
LockSize += CaptureBufferSize;
// Block align lock size so that we are always write on a boundary
LockSize -= (LockSize %% NotifySize);
if (0 == LockSize)
return;
// Read the capture buffer.
CaptureData =
(byte[])applicationBuffer.Read(NextCaptureOffset, typeof(byte),
LockFlag.None, LockSize);
// Write the data into the wav file
//Writer.Write(CaptureData, 0, CaptureData.Length);
// Update the number of samples, in bytes, of the file so far.
SampleCount += CaptureData.Length;
// Move the capture offset along
NextCaptureOffset += CaptureData.Length;
NextCaptureOffset %%= CaptureBufferSize; // Circular buffer
int write_pos = 0;
int read_pos = 0;
#if VIC
applicationBuffer.GetCurrentPosition(out write_pos, out
read_pos);
//
System.Diagnostics.Debug.WriteLine(NextCaptureOffset,"W:"+write_pos
+"/"+read_pos);
short [] buf = null;
// pointer to the data is here
buf = (short[])applicationBuffer.Read(read_pos, typeof(short),
LockFlag.None, NotifySize);
// we want to copy this data to play buffer so we need to
calculate where to:
#endif
playBuffer.GetCurrentPosition(out read_pos, out write_pos);
int place;
place = write_pos + CaptureData.Length - (write_pos %%
CaptureData.Length);
place %%= CaptureBufferSize;
System.Diagnostics.Debug.WriteLine(NextCaptureOffset, "RB:" +
place + "/" + read_pos);
playBuffer.Write(place, CaptureData, LockFlag.None);
playBuffer.GetCurrentPosition(out write_pos, out read_pos);
}
private void buttonSoundfile_Click(object sender, System.EventArgs e)
{
OnCreateSoundFile();
}
private void OnCreateSoundFile()
{
//-----------------------------------------------------------------------------
// Name: OnCreateSoundFile()
// Desc: Called when the user requests to save to a sound file
//-----------------------------------------------------------------------------
SaveFileDialog ofd = new SaveFileDialog();
WaveFormat wf = new WaveFormat();
DialogResult result;
// Get the default media path (something like C:\WINDOWS
\MEDIA)
if (string.Empty == Path)
Path = Environment.SystemDirectory.Substring(0,
Environment.SystemDirectory.LastIndexOf("\\")) + "\\media";
if (Recording)
{
// Stop the capture and read any data that
// was not caught by a notification
StartOrStopRecord(false);
Recording = false;
}
ofd.DefaultExt = ".wav";
ofd.Filter = "Wave Files|*.wav|All Files|*.*";
ofd.FileName = FileName;
ofd.InitialDirectory = Path;
// Update the UI controls to show the sound as loading a file
checkboxRecord.Enabled = false;
labelFilename.Text = "Saving file...";
// Display the OpenFileName dialog. Then, try to load the
specified file
result = ofd.ShowDialog(this);
if (DialogResult.Abort == result || DialogResult.Cancel ==
result)
{
labelFilename.Text = "Save aborted.";
return;
}
FileName = ofd.FileName;
try
{
CreateRIFF();
}
catch
{
labelFilename.Text = "Could not create wave file.";
return;
}
// Update the UI controls to show the sound as the file is
loaded
labelFilename.Text = FileName;
checkboxRecord.Enabled = true;
// Remember the path for next time
Path = FileName.Substring(0, FileName.LastIndexOf("\\"));
}
private void WaitThread()
{
while(Capturing)
{
//Sit here and wait for a message to arrive
NotificationEvent.WaitOne(Timeout.Infinite, true);
RecordCapturedData();
}
}
}