迁移基于Microsoft.DirectX的AudioRecoder类到SharpDX上

时间:2021-08-19 06:05:30

最近迁移项目到x64上,要处理的东西还是蛮多的,所以我要在说一次,不到万不得已不要用COM组件,要用COM组件也得首先考虑不需要我们关心平台的做法,或者得有64位版本。

比如Office的COM组件调用,excel可以用NPOI大家都知道了,如果你没用收费的aspose,那么你要操作其他office比如word,ppt等可以用NetOffice组件,虽然同样是调用COM,但是x86 x64都可以使用,而且任意完整本地Office版本即可(97~)

回到正题,这次是把基于 Microsoft.DirectX,Microsoft.DirectX.DirectSound这些32bit类库的AudioRecoder类迁移到SharpDX上来实现跨平台

引用

SharpDX

SharpDX.DirectSound

即可

 ///Record sound from MicroPhone and save to wav file

 // 迁移到SharpDX   by bernard  2018.7.27
//参考:
//https://csharp.hotexamples.com/examples/SharpDX.DirectSound/NotificationPosition/-/php-notificationposition-class-examples.html
//http://daisy-trac.cvsdude.com/urakawa-sdk/browser/trunk/csharp/audio/AudioLib/AudioRecorder.cs?rev=2355 using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using SharpDX.DirectSound;
using SharpDX.Multimedia;
using SharpDX; namespace AudioLib
{
class Recorder
{
private const int NOTIFYNUMBER = ; // 缓冲队列的数目
private int mNextCaptureOffset = ; // 该次录音缓冲区的起始点
private int nSampleCount = ; // 录制的样本数目
private int nNotifySize = ; // 每次通知大小
private int nBufferSize = ; // 缓冲队列大小
private string m_strFileName = string.Empty; // 文件名
private FileStream fsWaveFile = null; // 文件流
private BinaryWriter bwWriter = null; // 写文件
private DirectSoundCapture CapDev = null; // 音频捕捉设备
private CaptureBuffer RecBuffer = null; // 缓冲区对象
private NotificationPosition Notify = null; // 消息通知对象
private WaveFormat WavFormat; // 录音的格式
private Thread NotifyThread = null; // 处理缓冲区消息的线程
private AutoResetEvent NotificationEvent = null; // 通知事件 public Recorder(string strFileName)
{
m_strFileName = strFileName;
// 初始化音频捕捉设备
InitCaptureDevice();
// 设定录音格式
WavFormat = CreateWaveFormat();
} /// <summary> /// 开始录音 /// </summary> public void RecStart()
{
try
{
// 创建录音文件 CreateSoundFile();
// 创建一个录音缓冲区,并开始录音 CreateCaptureBuffer(); // 建立通知消息,当缓冲区满的时候处理方法
InitNotifications(); RecBuffer.Start(true); }
catch (Exception ex)
{
throw new Exception(ex.Message);
} }
/// <summary> /// 停止录音 /// </summary> public void RecStop()
{
try
{
// 关闭通知消息 if (null != NotificationEvent)
{
NotificationEvent.Set(); }
// 停止录音 RecBuffer.Stop();
// 写入缓冲区最后的数据 RecordCapturedData(); // 回写长度信息 bwWriter.Seek(, SeekOrigin.Begin); bwWriter.Write((int)(nSampleCount + )); // 写文件长度 bwWriter.Seek(, SeekOrigin.Begin); bwWriter.Write(nSampleCount); // 写数据长度 bwWriter.Close(); fsWaveFile.Close(); bwWriter = null; fsWaveFile = null;
nSampleCount = ;
// 3. To Dispose the capture
CapDev.Dispose(); // 4. Null the capture
CapDev = null; // 5. To dispose the buffer
RecBuffer.Dispose(); // 6. To Null the buffer
RecBuffer = null; }
catch (Exception ex)
{
//throw new Exception(ex.Message);
System.Diagnostics.Debug.WriteLine(ex.Message);
}
} /// <summary>
/// 继续录音
/// </summary> public void RecContinue()
{
try
{
// 建立通知消息,当缓冲区满的时候处理方法
RecBuffer.Start(true); }
catch (Exception ex)
{
throw new Exception(ex.Message);
} }
/// <summary>
/// 暂停录音
/// </summary> public void RecPause()
{
try
{
// 关闭通知消息 if (null != NotificationEvent)
{
NotificationEvent.Set(); }
// 停止录音
RecBuffer.Stop(); }
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 初始化录音设备,此处使用主录音设备.
/// </summary>
public void InitCaptureDevice()
{
try
{
//// 获取默认音频捕捉设备 var devices = SharpDX.DirectSound.DirectSoundCapture.GetDevices(); // 枚举音频捕捉设备 Guid deviceGuid = Guid.Empty; // 音频捕捉设备的ID
if (devices.Count > )
{
deviceGuid = devices[].DriverGuid;
}
else
{
throw new Exception("Not any sound capture device"); }
//// 用指定的捕捉设备创建Capture对象 try
{
CapDev = new DirectSoundCapture();
} catch (SharpDXException e)
{ throw new Exception(e.ToString()); }
}
catch (Exception ex)
{
throw new Exception(ex.Message);
} } /// <summary>
/// 创建录音格式,此处使用16bit,16KHz,Mono的录音格式
/// </summary>
/// <returns>WaveFormat结构体</returns> private WaveFormat CreateWaveFormat()
{ try
{
WaveFormat format = new WaveFormat(, , );
return format;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
} } /// <summary>
/// 创建录音使用的缓冲区
/// </summary> private void CreateCaptureBuffer()
{ try
{
// 缓冲区的描述对象 CaptureBufferDescription bufferdescription = new CaptureBufferDescription();
if (null != Notify)
{ Notify = null; } if (null != RecBuffer)
{ RecBuffer.Dispose(); RecBuffer = null; } // 设定通知的大小,默认为1s钟
nNotifySize = ( > WavFormat.AverageBytesPerSecond / ) ? : (WavFormat.AverageBytesPerSecond / ); nNotifySize -= nNotifySize % WavFormat.BlockAlign;
// 设定缓冲区大小 nBufferSize = nNotifySize * NOTIFYNUMBER;
// 创建缓冲区描述 bufferdescription.BufferBytes = nBufferSize; bufferdescription.Format = WavFormat; // 录音格式
// 创建缓冲区 RecBuffer = new CaptureBuffer(CapDev, bufferdescription); mNextCaptureOffset = ;
}
catch (Exception ex)
{
throw new Exception("Create recorder object failure, check sound driver or contact with administrator");
} } /// <summary> /// 初始化通知事件,将原缓冲区分成16个缓冲队列,在每个缓冲队列的结束点设定通知点. /// </summary> /// <returns>是否成功</returns> private void InitNotifications()
{
try
{
if (null == RecBuffer)
{
throw new Exception("Not create sound record buffer"); }
// 创建一个通知事件,当缓冲队列满了就激发该事件. NotificationEvent = new AutoResetEvent(false);
// 创建一个线程管理缓冲区事件 if (null == NotifyThread)
{ NotifyThread = new Thread(new ThreadStart(WaitThread)); NotifyThread.Start(); }
// 设定通知的位置 NotificationPosition[] PositionNotify = new NotificationPosition[NOTIFYNUMBER]; for (int i = ; i < NOTIFYNUMBER; i++)
{
PositionNotify[i] = new NotificationPosition();
PositionNotify[i].Offset = (nNotifySize * i) + nNotifySize - ; PositionNotify[i].WaitHandle = NotificationEvent; }
//Notify = new NotificationPosition(RecBuffer); RecBuffer.SetNotificationPositions(PositionNotify);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
} }
/// <summary> /// 将录制的数据写入wav文件 /// </summary> private void RecordCapturedData()
{// 这里瞎改的,需要Review
try
{
byte[] CaptureData = null; int ReadPos; int CapturePos = RecBuffer.CurrentCapturePosition; ReadPos = RecBuffer.CurrentRealPosition;
int sizeBytes = RecBuffer.Capabilities.BufferBytes; int circularBufferBytesAvailableForReading = (CapturePos == mNextCaptureOffset ?
: (CapturePos < mNextCaptureOffset
? CapturePos + (sizeBytes - mNextCaptureOffset)
: CapturePos - mNextCaptureOffset)); circularBufferBytesAvailableForReading -= (circularBufferBytesAvailableForReading % (sizeBytes / NOTIFYNUMBER)); if (circularBufferBytesAvailableForReading == )
{
return;
}
// 读取缓冲区内的数据
CaptureData = new byte[circularBufferBytesAvailableForReading];
RecBuffer.Read(CaptureData, , circularBufferBytesAvailableForReading, mNextCaptureOffset, LockFlags.None);
// 写入Wav文件 bwWriter.Write(CaptureData, , CaptureData.Length);
// 更新已经录制的数据长度.
bwWriter.Flush();
nSampleCount += CaptureData.Length;
// 移动录制数据的起始点,通知消息只负责指示产生消息的位置,并不记录上次录制的位置 mNextCaptureOffset += CaptureData.Length; mNextCaptureOffset %= nBufferSize; // Circular buffer
}
catch (Exception ex)
{
//throw new Exception(ex.Message);
System.Diagnostics.Debug.WriteLine(ex.Message);
} } /// <summary> /// 接收缓冲区满消息的处理线程 /// </summary> private void WaitThread()
{ while (true)
{ // 等待缓冲区的通知消息
NotificationEvent.WaitOne(Timeout.Infinite, true);
// 录制数据 RecordCapturedData(); } } /// <summary> /// 创建保存的波形文件,并写入必要的文件头. /// </summary> private void CreateSoundFile()
{
try
{
/**************************************************************************
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. fsWaveFile = new FileStream(m_strFileName, FileMode.Create); bwWriter = new BinaryWriter(fsWaveFile);
// 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 = ; // File padding
int nFormatChunkLength = 0x10; // Format chunk length.
int nLength = ; // File length, minus first 8 bytes of RIFF description. This will be filled in later.
short shBytesPerSample = ; // Bytes per sample.
// 一个样本点的字节数目
if ( == WavFormat.BitsPerSample && == WavFormat.Channels)
{
shBytesPerSample = ;
} else if (( == WavFormat.BitsPerSample && == WavFormat.Channels) || ( == WavFormat.BitsPerSample && == WavFormat.Channels))
{
shBytesPerSample = ;
} else if ( == WavFormat.BitsPerSample && == WavFormat.Channels)
{
shBytesPerSample = ;
} // RIFF 块 bwWriter.Write(ChunkRiff); bwWriter.Write(nLength); bwWriter.Write(ChunkType);
// WAVE块 bwWriter.Write(ChunkFmt); bwWriter.Write(nFormatChunkLength); bwWriter.Write(shPad); bwWriter.Write((short)WavFormat.Channels); bwWriter.Write(WavFormat.SampleRate); bwWriter.Write(WavFormat.AverageBytesPerSecond); bwWriter.Write(shBytesPerSample); bwWriter.Write((short)WavFormat.BitsPerSample); // 数据块 bwWriter.Write(ChunkData); bwWriter.Write((int)); // The sample length will be written in later.
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
} }
}