问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

谁能给一份c#录音机的代码?谢谢。

发布网友 发布时间:2024-09-13 03:01

我来回答

1个回答

热心网友 时间:2024-09-13 03:34

C#中使用DirectSound录音

一.声卡录音的基本原理

为了实现一个录音的基本过程,至少需要以下对象的支持:

1. 录音设备,对我们的PC设备就是声卡。这个录音设备可以进行的操作应该有开始和关闭。

2. 缓冲区,也就是录制的声音放在哪里的问题。

二.DirectSound对录音的描述模型

1. DirectSound对录音的支持类

Ø Capture,设备对象,可以看作是声卡的描述。

Ø CaptureBuffer,缓冲区对象,存放录入的音频数据。

Ø Notify,事件通知对象,由于录音是一个长时间的过程,因此使用一个缓冲队列(多个缓冲区)接收数据,每当一个缓冲区满的时候,系统使用这个对象通知应用程序取走这个缓冲区,并继续录音。

以上三个对象是进行录音操作的主要对象,由于在C++中对DirectSound的操作DirectX帮助文档中已经有很详细的说明,这里就不再赘述了。本文是针对Managed Code。除了以上三个主要的DirectSound类,还需要以下几个辅助类。

Ø WaveFormat,描述了进行录制的声音波形的格式,例如采样率,单声道还是立体声,每个采样点的长度等等。

Ø Thread,线程类,由于录音的过程是需要不断处理缓冲区满的事件,因此新建一个线程对此进行单独处理。

Ø AutoResetEvent,通知的事件,当缓冲区满的时候,使用该事件作为通知事件。

三.代码解析(SoundRecord类)

1.需要引用的程序集

using System;

using System.Windows.Forms;

using System.Threading;

using System.IO;

// 对DirectSound的支持

using Microsoft.DirectX;

using Microsoft.DirectX.DirectSound;

2. SoundRecord的成员数据

public const int cNotifyNum = 16; // 缓冲队列的数目

private int mNextCaptureOffset = 0; // 该次录音缓冲区的起始点

private int mSampleCount = 0; // 录制的样本数目

private int mNotifySize = 0; // 每次通知大小

private int mBufferSize = 0; // 缓冲队列大小

private string mFileName = string.Empty; // 文件名

private FileStream mWaveFile = null; // 文件流

private BinaryWriter mWriter = null; // 写文件

private Capture mCapDev = null; // 音频捕捉设备

private CaptureBuffer mRecBuffer = null; // 缓冲区对象

private Notify mNotify = null; // 消息通知对象

private WaveFormat mWavFormat; // 录音的格式

private Thread mNotifyThread = null; // 处理缓冲区消息的线程

private AutoResetEvent mNotificationEvent = null; // 通知事件

3. 对外操作的函数

/// <summary>

/// 构造函数,设定录音设备,设定录音格式.

/// </summary>

public SoundRecord()

{

// 初始化音频捕捉设备

InitCaptureDevice();

// 设定录音格式

mWavFormat = CreateWaveFormat();

}

/// <summary>

/// 设定录音结束后保存的文件,包括路径

/// </summary>

/// <param name="filename">保存wav文件的路径名</param>

public void SetFileName(string filename)

{

mFileName = filename;

}

/// <summary>

/// 开始录音

/// </summary>

public void RecStart()

{

// 创建录音文件

CreateSoundFile();

// 创建一个录音缓冲区,并开始录音

CreateCaptureBuffer();

// 建立通知消息,当缓冲区满的时候处理方法

InitNotifications();

mRecBuffer.Start(true);

}

/// <summary>

/// 停止录音

/// </summary>

public void RecStop()

{

// 关闭通知消息

if (null != mNotificationEvent)

mNotificationEvent.Set();

// 停止录音

mRecBuffer.Stop();

// 写入缓冲区最后的数据

RecordCapturedData();

// 回写长度信息

mWriter.Seek(4, SeekOrigin.Begin);

mWriter.Write((int)(mSampleCount + 36)); // 写文件长度

mWriter.Seek(40, SeekOrigin.Begin);

mWriter.Write(mSampleCount); // 写数据长度

mWriter.Close();

mWaveFile.Close();

mWriter = null;

mWaveFile = null;

}

4.内部调用函数

/// <summary>

/// 初始化录音设备,此处使用主录音设备.

/// </summary>

/// <returns>调用成功返回true,否则返回false</returns>

private bool InitCaptureDevice()

{

// 获取默认音频捕捉设备

CaptureDevicesCollection devices = new CaptureDevicesCollection(); // 枚举音频捕捉设备

Guid deviceGuid = Guid.Empty; // 音频捕捉设备的ID

if (devices.Count>0)

deviceGuid = devices[0].DriverGuid;

else

{

MessageBox.Show("系统中没有音频捕捉设备");

return false;

}

// 用指定的捕捉设备创建Capture对象

try

{

mCapDev = new Capture(deviceGuid);

}

catch (DirectXException e)

{

MessageBox.Show(e.ToString());

return false;

}

return true;

}

/// <summary>

/// 创建录音格式,此处使用16bit,16KHz,Mono的录音格式

/// </summary>

/// <returns>WaveFormat结构体</returns>

private WaveFormat CreateWaveFormat()

{

WaveFormat format = new WaveFormat();

format.FormatTag = WaveFormatTag.Pcm; // PCM

format.SamplesPerSecond = 16000; // 16KHz

format.BitsPerSample = 16; // 16Bit

format.Channels = 1; // Mono

format.BlockAlign = (short)(format.Channels * (format.BitsPerSample / 8));

format.AverageBytesPerSecond = format.BlockAlign * format.SamplesPerSecond;

return format;

}

/// <summary>

/// 创建录音使用的缓冲区

/// </summary>

private void CreateCaptureBuffer()

{

// 缓冲区的描述对象

CaptureBufferDescription bufferdescription = new CaptureBufferDescription();

if (null != mNotify)

{

mNotify.Dispose();

mNotify = null;

}

if (null != mRecBuffer)

{

mRecBuffer.Dispose();

mRecBuffer = null;

}

// 设定通知的大小,默认为1s钟

mNotifySize = (1024 > mWavFormat.AverageBytesPerSecond / 8) ? 1024 : (mWavFormat.AverageBytesPerSecond / 8);

mNotifySize -= mNotifySize % mWavFormat.BlockAlign;

// 设定缓冲区大小

mBufferSize = mNotifySize * cNotifyNum;

// 创建缓冲区描述

bufferdescription.BufferBytes = mBufferSize;

bufferdescription.Format = mWavFormat; // 录音格式

// 创建缓冲区

mRecBuffer = new CaptureBuffer(bufferdescription, mCapDev);

mNextCaptureOffset = 0;

}

/// <summary>

/// 初始化通知事件,将原缓冲区分成16个缓冲队列,在每个缓冲队列的结束点设定通知点.

/// </summary>

/// <returns>是否成功</returns>

private bool InitNotifications()

{

if (null == mRecBuffer)

{

MessageBox.Show("未创建录音缓冲区");

return false;

}

// 创建一个通知事件,当缓冲队列满了就激发该事件.

mNotificationEvent = new AutoResetEvent(false);

// 创建一个线程管理缓冲区事件

if (null == mNotifyThread)

{

mNotifyThread = new Thread(new ThreadStart(WaitThread));

mNotifyThread.Start();

}

// 设定通知的位置

BufferPositionNotify[] PositionNotify = new BufferPositionNotify[cNotifyNum + 1];

for (int i = 0; i < cNotifyNum; i++)

{

PositionNotify[i].Offset = (mNotifySize * i) + mNotifySize - 1;

PositionNotify[i].EventNotifyHandle = mNotificationEvent.Handle;

}

mNotify = new Notify(mRecBuffer);

mNotify.SetNotificationPositions(PositionNotify, cNotifyNum);

return true;

}

/// <summary>

/// 将录制的数据写入wav文件

/// </summary>

private void RecordCapturedData()

{

byte[] CaptureData = null;

int ReadPos;

int CapturePos;

int LockSize;

mRecBuffer.GetCurrentPosition(out CapturePos, out ReadPos);

LockSize = ReadPos - mNextCaptureOffset;

if (LockSize < 0)

LockSize += mBufferSize;

// 对齐缓冲区边界,实际上由于开始设定完整,这个操作是多余的.

LockSize -= (LockSize % mNotifySize);

if (0 == LockSize)

return;

// 读取缓冲区内的数据

CaptureData = (byte[])mRecBuffer.Read(mNextCaptureOffset, typeof(byte), LockFlag.None, LockSize);

// 写入Wav文件

mWriter.Write(CaptureData, 0, CaptureData.Length);

// 更新已经录制的数据长度.

mSampleCount += CaptureData.Length;

// 移动录制数据的起始点,通知消息只负责指示产生消息的位置,并不记录上次录制的位置

mNextCaptureOffset += CaptureData.Length;

mNextCaptureOffset %= mBufferSize; // Circular buffer

}

/// <summary>

/// 接收缓冲区满消息的处理线程

/// </summary>

private void WaitThread()

{

while(true)

{

// 等待缓冲区的通知消息

mNotificationEvent.WaitOne(Timeout.Infinite, true);

// 录制数据

RecordCapturedData();

}

}

/// <summary>

/// 创建保存的波形文件,并写入必要的文件头.

/// </summary>

private void CreateSoundFile()

{

/**************************************************************************

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.

mWaveFile = new FileStream(mFileName, FileMode.Create);

mWriter = new BinaryWriter(mWaveFile);

// 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.

// 一个样本点的字节数目

if (8 == mWavFormat.BitsPerSample && 1 == mWavFormat.Channels)

shBytesPerSample = 1;

else if ((8 == mWavFormat.BitsPerSample && 2 == mWavFormat.Channels) || (16 == mWavFormat.BitsPerSample && 1 == mWavFormat.Channels))

shBytesPerSample = 2;

else if (16 == mWavFormat.BitsPerSample && 2 == mWavFormat.Channels)

shBytesPerSample = 4;

// RIFF 块

mWriter.Write(ChunkRiff);

mWriter.Write(nLength);

mWriter.Write(ChunkType);

// WAVE块

mWriter.Write(ChunkFmt);

mWriter.Write(nFormatChunkLength);

mWriter.Write(shPad);

mWriter.Write(mWavFormat.Channels);

mWriter.Write(mWavFormat.SamplesPerSecond);

mWriter.Write(mWavFormat.AverageBytesPerSecond);

mWriter.Write(shBytesPerSample);

mWriter.Write(mWavFormat.BitsPerSample);

// 数据块

mWriter.Write(ChunkData);

mWriter.Write((int)0); // The sample length will be written in later.

}

5.外部窗体调用方式

声明部分:

private SoundRecord recorder = null; // 录音

窗体构造函数:

recorder = new SoundRecord();

启动录音按钮:

private void btnStart_Click(object sender, System.EventArgs e)

{

//

// 录音设置

//

string wavfile = null;

wavfile = “test.wav”;

recorder.SetFileName(wavfile);

recorder.RecStart();

}

中止录音按钮:

private void btnStop_Click(object sender, System.EventArgs e)

{

recorder.RecStop();

recorder = null;

}

6.需要添加的外部引用文件

在系统的System32目录下添加以下两个引用文件,如果没有,在DirectX的开发包内可以找到。

Microsoft.DirectX.dll

Microsoft.DirectX.DirectSound.dll
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
为什么被告律师理亏还要拼命打官司 植物的细胞器有哪些 30.植物细胞特有的细胞器是 我的世界手机版mod为什莫显示已禁用 我的世界手机版启动器mod为什么输入了却没有效果 我的世界为什么我用不了mod 我的世界为什么我用不了mod ? 鸡翅炖土豆怎么做才能达到完美的口感? 鸡翅土豆条怎么做既省时又美味? 鸡翅土豆怎么烧简单又好吃 怎么做鸡翅烧土豆更好吃? iPhone充电提示音使用自己录音方法介绍_iPhone充电提示音使用自己... 使用Delphi开发录音功能程序介绍_使用Delphi开发录音功能程序是什么_百 ... 音像出版物上的isrc码是什么意思? 王者荣耀镜的连招是什么-镜最强连招攻略 新工作第一天上班的文案开始一份新工作的心情说说 乙羧氟草醚对玉米苗有影响吗 乙羧氟草醚用在玉米田,会对玉米造成伤害吗? 我家一楼是门面爸妈住在二楼我想在三楼主卧 做卫生间管道通到墙外可以... 房屋卫生间门面大门怎么办 我的门面朝南进来对这小卫生间的门卫生间东边是一个小窗户? 门面前后有门卫生间怎么开? 中国移动营业厅里面想要调整流量超市中两个流量包的顺序该怎么办?_百 ... 我想买那种木纹纹理的板材家具,但是又很担心不好打理。 家具如何拉出木纹 为什么金丝木家具那么少人买 木纹家具对心理上有什么不好影响? 桃李杯刘萌萌胶州秧歌音乐,咱爸咱妈就两分钟多一点那个音乐,不要完整... 全新广联达加密锁特惠购买指南!广联达BIM算量计价软件运维软件安装教 ... 如何给广联达加密锁绑定加密狗? Python语法(python语法特色) 为什么发录音的时候名字是一串代码 丈母娘和女婿有矛盾怎么化解 关于一叶孤舟的诗句有哪些? Running man中那些好听的歌有哪些 不用实名认证可以玩的游戏 哪些游戏不需要实名登录 王勃和徐峥合作的电影 王勃和徐峥合作的电影有哪些 沈腾徐峥黄渤演的电影 沈腾徐峥黄渤演的电影名称 阜阳乐富强悦融湾开发商是? 六安乐富强悦融湾在哪里? 测量一个550毫升的瓶子能装多少粒大米 一万袋大米有多少颗 敲诈勒索罪的量刑是哪些 广州最好医院有哪些 什么垃圾可以提炼有用的材料 红米note3怎么无线连接电脑 征信特别花了哪个网贷还能下款 SwitchOLED手柄按键是导电胶吗 我想买illy咖啡,但是不敢买,挂羊头卖狗肉的多,哪位能指点下? “五爪金龙”现身云南景洪!消失10多年,如今突然出现预示着什么