recorder.html
来自「Visual C++ has been one of most effectiv」· HTML 代码 · 共 567 行 · 第 1/2 页
HTML
567 行
<html>
<head>
<title>Recording Sounds</title>
<meta name="description" content="Reliable software Win32 Tutorial: Recording Sound">
<meta name="keywords" content="reliable, software, windows, cplusplus, source code, example, tutorial, object oriented, multimedia, sound, sample, sampling, waveInPrepareHeader, waveInAddBuffer, win32, thread">
</head>
<body background="grid.gif" tppabs="http://www.relisoft.com/images/grid.gif" bgcolor="white" text="black">
<table cellpadding=10 width="100%">
<tr>
<td width=100 align=center valign=middle>
<a href="index-11.htm" tppabs="http://www.relisoft.com/index.htm">
<img src="rsbullet.gif" tppabs="http://www.relisoft.com/images/rsbullet.gif" alt="Reliable Software" border=0 width=39 height=39>
<br>Home</a></td>
<td><font face="arial" color="#009966">
<h1 align=center>Sampling Sound in Windows 32</h1>
</font></td> </tr>
</table>
<p>
<table width="100%">
<tr>
<td width=80> <!-- Left margin --></td>
<td> <!-- Middle column, there is also the right margin at the end -->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td bgcolor="white">
<hr>
<font size="+1"><b>Sampling sounds</b></font> in Windows 32 is relatively simple&emdash;once you know how to deal with asynchronous input. Here's how it works: Windows tells your soundcard to start sampling the input from the microphone and store the samples in a client buffer. Once the buffer is full, Windows notifies the client, who is supposed to process the bufferful of data. There are several notification options. The simplest (but pretty much useless) is for the client to keep polling a flag until Windows changes its value. This of course eats up a lot of CPU time that could be spent doing some useful work.
<p>The second option is to get notifications in the form of a Windows messages.
<p>The third and, in my opinion, the best is the solution available only in Win32. The client creates a separate thread which is suspended waiting on an event. The event is triggered by Windows when the samples are ready. The client thread then wakes up, does the necessary work and goes back to sleep waiting for the next event.
<p>I will describe the multi-threaded solution. If you haven't gone through my tutorial on threads, events and active objects, now's the time. Just click <a href="active.html" tppabs="http://www.relisoft.com/win32/active.html">here</a> and come back later.
<p><font size="+1"><b>Now that you're back</b></font>, with the knowledge about active objects fresh in your mind, let me explain how to create a concrete active object that is responsible for processing sound samples. It waits for the buffer, calculates the Fourier transform of the samples and graphs the results. Since its main purpose is to display the results, I called it <b>Painter</b>. It doesn't talk directly to the Windows multimedia subsystem. That's the duty of another object, the <b>Recorder</b>.
<p>Notice the two <b>View</b> objects passed to the Painter's constructor. These objects encapsulate the two panes used for displaying the sample data and its FFT. The sampling parameters are passed to the constructor as initial settings and to the <b>ReInit</b> method as new settings. The FFT transformer and Recorder are hidden inside smart pointers <b>PtrFft</b> and <b>PtrRecorder</b>. Finally, the Painter object contains two synchronization objects&emdash;a mutex to ensure serialized access to Painter's data and an event that is used for synchronization with the multimedia subsystem (that's the event we'll be waiting on). Notice that, as explained in the ActiveObject tutorial, the consturctor of Painter has to call _thread.Resume () to start the execution of the captive thread.
<hr>
<pre><font face="courier">class <font color="#cc0066"><b>Painter</b></font>: public <font color="#cc0066"><b>ActiveObject</b></font>
{
public:
Painter (
HWND hwnd,
ViewWave & viewWave,
ViewFreq & viewFreq,
int samplesPerBuf,
int samplesPerSec,
int fftPoints);
BOOL ReInit (
int samplesPerBuf,
int samplesPerSec,
int fftPoints,
int bitsPerSample);
BOOL Start ();
void Stop ();
int HzToPoint (int hz)
{
Lock lock (_mutex);
return _pFftTransformer->HzToPoint (hz);
}
int Points ()
{
Lock lock (_mutex);
return _pFftTransformer->Points ();
}
private:
// Active object overrides
void <b>InitThread</b> () {}
void <b>Run</b> ();
void <b>FlushThread</b> ();
void LokWaveInData ();
private:
ViewWave & _viewWave;
ViewFreq & _viewFreq;
int _samplesPerBuf;
int _samplesPerSecond;
int _fftPoints;
int _bitsPerSample;
HWND _hwnd;
Mutex _mutex;
Event _event;
PtrRecorder _pRecorder;
PtrFft _pFftTransformer;
};
<font color="#cc0066"><b>Painter::Painter</b></font> (
HWND hwnd,
ViewWave & viewWave,
ViewFreq & viewFreq,
int samplesPerBuf,
int samplesPerSec,
int fftPoints)
: _hwnd (hwnd),
_viewWave (viewWave),
_viewFreq (viewFreq),
_samplesPerBuf (samplesPerBuf),
_samplesPerSecond (samplesPerSec),
_fftPoints (fftPoints),
_bitsPerSample (16),
_pFftTransformer (fftPoints, samplesPerSec),
_pRecorder (samplesPerBuf, samplesPerSec)
{
_thread.Resume ();
}
</font></pre>
<hr>
The Painter's implementation of <i>ActiveObject::Run</i> is pretty typical. It has an "infinite" loop which is exited only when the <i>_isDying</i> flag is set by the <i>Kill</i> method. The thread waits on the event until it is released&emdash; in our case by the multimedia subsystem. We check the state of the buffer and call <i>LokWaveInData</i> under the lock. By convention, the methods that are called under the lock have a prefix <i>Lok</i> (this is not Hungarian&emdash; the prefix has nothing to do with types).
<p>The FlushThread method releases the event, so that the thread has the opportunity to run and check the _isDying flag. It's all pretty standard ActiveObject stuff.
<hr>
<pre><font face="courier">void <font color="#cc0066"><b>Painter::Run</b></font> ()
{
for (;;)
{
_event.Wait ();
if (_isDying)
return;
Lock lock (_mutex);
if (_pRecorder->IsBufferDone ())
LokWaveInData ();
}
}
void <font color="#cc0066"><b>Painter::FlushThread</b></font> ()
{
_event.Release ();
}
</font></pre>
<hr>
When the wave data is in, we create an iterator&emdash;sort of like a tape deck loaded with the "tape" of samples. After the iterator is created we immediately notify the recorder that we are done with the buffer. We then copy the data into the FFT transformer, transform it and update the two views.
<hr>
<pre><font face="courier">void <font color="#cc0066"><b>Painter::LokWaveInData</b></font> ()
{
SampleIter iter (_pRecorder.GetAccess());
// Quickly release the buffer
if (!_pRecorder->BufferDone ())
return;
_pFftTransformer->CopyIn (iter);
_pFftTransformer->Transform();
_viewFreq.Update (_pFftTransformer.GetAccess());
_viewWave.Update (_pFftTransformer.GetAccess());
}
</font></pre>
<hr>
Here's what the ViewWave does with the new data. It creates the canvas object (see <a href="canvas.html" tppabs="http://www.relisoft.com/win32/canvas.html">canvas</a> tutorial), clears the pane's rectangle by overpainting it black and puts the data into the polyline object. Before painting the polyline, it attaches the green pen (see <a href="pens.html" tppabs="http://www.relisoft.com/win32/pens.html">pens</a> tutorial) to the canvas. Notice that painting is <i>not</i> done in response to the <b>WM_PAINT</b> message. It is done every time new data is available.
<hr>
<pre><font face="courier">void <font color="#cc0066"><b>ViewWave::Update</b></font> (Fft const & fftTransformer)
{
UpdateCanvas canvas (Hwnd ());
ClientRect rect (Hwnd ());
canvas.ClearBlack(rect);
int cMaxPoints = min (fftTransformer.Points(), _poly.Points());
for ( int i = 0; i < cMaxPoints; i++ )
{
int s = fftTransformer.Tape(i) / 512 + (rect.bottom - 1) / 2;
if (i >= rect.right)
{
_poly.Add( i, rect.right - 1, (rect.bottom - 1) / 2);
}
else
{
if ( s < 0 )
_poly.Add (i, i, 0);
else if (s >= rect.bottom)
_poly.Add (i, i, rect.bottom - 1);
else
_poly.Add (i, i, s);
}
}
PenHolder pen (canvas, _penGreen);
_poly.Paint (canvas, cMaxPoints);
}
</font></pre>
<hr>
<b>Recorder</b> is the object that bridges the gap between the client code and the multimedia subsystem. It keeps a circular queue of buffers and WaveHeaders and keeps passing them to Windows. You see, when Windows wakes our thread to process data from one buffer, it has to have another buffer ready to be filled with incoming samples. The sound card can't wait while we are calculating the FFT and drawing the graphs. It keeps spitting samples at constant rate.
<p>In our example, the Recorder keeps a pool of eight buffers (probably an overkill) and most of them spend most of their time under the control of the multimedia subsystem. Actually, all 8 buffers are allocated in one chunk of memory; it's the WaveHeaders that have pointers to appropriate areas of this chunk.
<p>Take note of the fact that the method GetSample is defined as pure virtual. That's because the way samples are stored in the buffer depends on the number of bits per sample. GetSample will be implemented differently in different subclasses of the Recorder that we'll see in a moment.
<p>Although we are getting closer to Windows API, there is still one more layer, the <b>WaveInDevice</b>, to protect us from calling it directly.
<hr>
<pre><font face="courier">class <font color="#cc0066"><b>Recorder</b></font>
{
friend class <font color="#cc0066"><b>SampleIter</b></font>;
enum { NUM_BUF = 8 };
public:
Recorder(
int cSamples,
int cSamplePerSec,
int nChannels,
int bitsPerSecond);
~Recorder();
BOOL Start (Event & event);
void Stop ();
BOOL BufferDone ();
BOOL IsBufferDone () const
{
return _header [_iBuf].IsDone ();
}
BOOL IsStarted () const { return _isStarted; }
int SampleCount () const { return _cSamples; }
int BitsPerSample () const { return _bitsPerSample; }
int SamplesPerSecond () const { return _cSamplePerSec; }
protected:
virtual int GetSample (char *pBuf, int i) const = 0;
char * GetData () const { return _header [_iBuf].lpData; }
BOOL _isStarted;
<b>WaveInDevice</b> _waveInDevice;
int _cSamplePerSec; // sampling frequency
int _cSamples; // samples per buffer
int _nChannels;
int _bitsPerSample;
int _cbSampleSize; // bytes per sample
int _cbBuf; // bytes per buffer
int _iBuf; // current buffer #
char *_pBuf; // pool of buffers
WaveHeader _header [NUM_BUF]; // pool of headers
};
<font color="#cc0066"><b>Recorder::Recorder</b></font> (
int cSamples,
int cSamplePerSec,
int nChannels,
int bitsPerSample)
: _iBuf(0),
_cSamplePerSec (cSamplePerSec),
_cSamples (cSamples),
_cbSampleSize (nChannels * bitsPerSample/8),
_cbBuf (cSamples * nChannels * bitsPerSample/8),
_nChannels (nChannels),
_bitsPerSample (bitsPerSample),
_isStarted(FALSE)
{
_pBuf = new char [_cbBuf * NUM_BUF];
}
<font color="#cc0066"><b>Recorder::~Recorder</b></font> ()
{
Stop();
delete []_pBuf;
}
</font></pre>
<hr>
To start the recorder, we first initialize the data structure <b>WaveFormat</b> that contains the parameters of our recording: number of channels, number of samples per second (sampling frequency) and number of bits per sample. We then check if the format is supported by the sound input device 0, the microphone. If you want to use some other input device, CD drive, line-in, etc., you should change this number. Next, we open device 0 to record data in a given format. We pass it the event that is to be used for asynchronous communication. That's the event the multimedia subsystem will trigger whenever a new buffer full of data is ready. And that's the event our captive thread is waiting on inside the Run method.
<p>We initialize <b>WaveHeader</b>s one by one by attaching data buffers and calling the device to prepare them (whatever that means). We send all but one buffer to the device, so that it can make use of them to store a continuous stream of samples. We leave the last buffer unprepared, so that it's ready for recycling when the first buffer arrives with data. Finally, we tell the device to start recording.
<hr>
<pre><font face="courier">BOOL <font color="#cc0066"><b>Recorder::Start</b></font> (Event & event)
{
WaveFormat format (
_nChannels,
_cSamplePerSec,
_bitsPerSample );
if (!format.isInSupported(0))
{
MessageBox (0, "Format not supported", "Recorder", MB_OK);
return FALSE;
}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?