2008年01月26日

也许我们应该相信轮回,这个blog是我两年前用的一个。然后就忘记了更新。

今天在查找一篇文章的时候,正好找到了这个地方,其实已经想不起来是自己的blog。最后觉得名字“生命的奇迹”好像很耳熟。最后发现原来是自己的。

既然是命运安排,我以后会继续使用这个blog。

2005年12月18日

<iostream>
cout
cerr
clog
cin
读写
is>>t
os<<t
is.get(c)
is.unget() 取消最后读入的字符

<fstream>
ifstream is(cp);
ofstream os(cp);

<ios>
os.width()
os.width(n)
os.precision()
os.precision(n)

<iomanip>
os<<endl
os<<flush
os<<setprecision(n)
os<<setw(n)
strm.bad()   最后一次流操作是否因为无效数据失败
strm.clear() 清除失败
strm.eof()   是否文件结束
strm.fail()  是否最近一次操作因为硬件或系统底层失败
strm.good()  最近一次操作是否成功

==共有容器操作==
container<T>::iterator
container<T>::const_iterator
container<T>::reverse_iterator
container<T>::const_reverse_iterator
container<T>::size_type
container<T>::value_type 存储元素的类型

c.begin()
c.end()
c.rbegin()          逆序访问
c.rend()

container<T> c;
container<T> c(c2);

c=c2;           拷贝
c.size()        返回元素对象,类型是size_type
c.empty()       判定c中是否没有元素
c.clear()       清空容器

==顺序操作的容器==
container<T> c(n,t) 定义有n个元素的c,c的元素是t的拷贝
container<T> c(b,e) 拷贝[b,e)的元素

c.insert(it,t)
c.insert(it,n,t)
c.insert(it,b,e)

c.erase(it)
c.erase(b,e)

c.assign(b,e) 把b和e指向内容赋给c

c.front() 首元素的引用
c.back()  末元素的引用

c.push_back(t)
c.pop_back()

inserter(c,it)    在c中it之前插入元素
back_inserter(c)  在c的末尾添加元素,必须支持push_back操作

==其他顺序容器操作==
c[n]            对支持随机访问的容器
c.push_front(t)
c.push_front()
c.pop_front()
front_inserter(c) 在c的开始添加元素,必须支持push_front操作

==关联容器的操作==
container<T>::key_type
container<T> c(cmp)
container<T> c(b,e,cmp)

c.insert(b,e)
c.erase(it)
c.erase(b,e)
c.erase(k)

c.find(k)

<vector>
v.reverse(n) 保留空间以保存n个空间
v.resize(n)  重新分配长度

<list>
l.splice(it,l2)     将l2的元素插入在it之前,并删除l2
l.splice(it,l2,it2) 插入l2中it2指向的元素,并删除相应的
l.splice(it,l2,b,e)

l.remove(t)
l.remove_if(p)

l.sort
l.sort(cmp) 对元素排序

<string>
string s(cp);

os<<s
is>>s

getline(is,s)

s+=s2
s+s2

s relop s2 布尔操作

s.substr(n,n2)

s.c_str()
s.data    不是以\0结束

s.copy(cp,n) 从s中复制前n个到cp

<map>
map<K,V> m;      创建一个空的映射表
map<K,V> m(cmp); 创建一个空的映射表,排序规则为cmp

m[K]  返回key为K的对应值

m.insert(make_pair(k,v))

m.find(k) 返回iterator

*it 具有const K类型的值

<algorithm>
accumulate(b,e,t)      
accumulate(b,e,t, f)    将[b,e)中元素之和加上t之后存在t中 <numeric>

binary_search(b,e,t)

copy(b,e,d)             拷贝指定序列到d

equal(b,e,b2)
equal(b,e,b2,p)

fill(b,e,t)

find(b,e,t)             查找t
find_if(b,e,p)          根据谓词p查找

lexicographical_compare(b,e,b2,e2)
lexicographical_compare(b,e,b2,e2,p)  比较[b,e)和[b2,e2)界定序列的大小

max(t1,t2)
min(t2,t2)

max_element(b,e)
min_element(b,e)

partition(b,e,p)        将谓词p为true的放在头部,返回一个iter指示谓词为false的开始
stable_partition(b,e,p) 同上且保持两个区域内的元素顺序保持不变

remove(b,e,t)           删除不等于t
remove_if(b,e,p)        删除谓词p为假的

remove_copy(b,e,d,t)    拷贝所有不等于t的元素
remove_copy_if(b,e,d,p) 拷贝谓词p为假的元素

replace(b,e,t1,t2)
replace_copy(b,e,d,t1,t2)  替换之后将新序列的元素赋值到d

reverse(b,e)
reverse_copy(b,e,d)

search(b,e,b2,e2)       查找[b2,e2)序列
search(b,e,b2,e2,p)

transform(b,e,d,f)      运行函数f,结果存在d中
transform(b,e,b2,d,f)   函数f的参数是b,e,b2

sort(b,e)               对区间[b,e)中元素非递减排序
sort(b,e,p)
stable_sort(b,e)
stable_sort(b,e,p)

unique(b,e)
unique(b,e,p)
unique_copy(b,e,d,p)

<cctype>
isspace(c) 空白字符
isalpha(c) 字母
isdigit(c) 数字
isalnum(c) 字母或数字
ispunct(c) 标点符号
isupper(c) 大写字母
islower(c) 小写字母
toupper(c) 生成大写字母
tolower(c) 生成小写字母

<stdexcept>
logic_error
domain_error
invalid_argument
length_error
out_of_range
runtime_error
range_error
overflow_error
underflow_error
e.what()            返回错误的位置

<cstdlib>
rand() 产生[0, RAND_MAX]之间的整数

2005年12月14日

一.声卡录音的基本原理

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

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 = 0×10;  // 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