recorder.html

来自「Visual C++ has been one of most effectiv」· HTML 代码 · 共 567 行 · 第 1/2 页

HTML
567
字号
    _waveInDevice.Open (0, format, event);
    if (!_waveInDevice.Ok())
    {
        char buf[164];
        if (_waveInDevice.isInUse())
        {
            strcpy (buf,
                    "Another application is recording audio. Stop recording "
                    "with this other application and then try again.");
        }
        else
            _waveInDevice.GetErrorText (buf, sizeof (buf));
        MessageBox (0, buf, "Recorder", MB_OK);
        return FALSE;
    }

    // Don't initialize the last buffer
    // It will be initialized in the
    // first call to BufferDone
    for ( int i = 0; i < NUM_BUF - 1; i++ )
    {
        _header[i].lpData = & _pBuf [i * _cbBuf];
        _header[i].dwBufferLength = _cbBuf;
        _header[i].dwFlags = 0;
        _header[i].dwLoops = 0;

        _waveInDevice.Prepare (& _header[i]);

        _waveInDevice.SendBuffer (& _header [i]);
    }
    _isStarted = TRUE;
    _iBuf = 0;
    _waveInDevice.Start();
    return TRUE;
}
</font></pre>
<hr>
<b>BufferDone</b> is called whenever our captive thread is woken up and starts processing data from the buffer. The recorder keeps track of which buffer is current and it unprepares it (whatever that means). Then it takes the previous buffer from the circular queue, recycles it and sends it back to the device. The buffer on which we are currently operating will be recycled the same way next time BufferDone is called.
<p>Notice what happens the first time around. Current buffer index _iBuf is zero. Previous buffer will therefore have index -1, or, after adjusting for the circularity of our queue, NUM_BUF - 1. It so happens that this is exactly the index of the buffer we left uninitialized in the Start method. We initialize it now and send it to the device. While we are processing buffer 0, the device is already busy filling buffer number 1, which becomes our current buffer after _iBuf is incremented. In fact, we gave the device seven buffers which it will fill one by one even if we don't call BufferDone on time. These are the emmergency buffers&emdash;to be used in case our thread has to wait for its time slice longer than usual.
<hr>
<pre><font face="courier">BOOL <font color="#cc0066"><b>Recorder::BufferDone</b></font> ()
{
    Assert (IsBufferDone ());
    _waveInDevice.UnPrepare (&amp; _header [_iBuf]);
    int prevBuf = _iBuf - 1;
    if (prevBuf &lt; 0)
        prevBuf = NUM_BUF - 1;

    // Next buffer to be filled
    _iBuf++;
    if ( _iBuf == NUM_BUF )
        _iBuf = 0;

    _header[prevBuf].lpData = &amp; _pBuf [prevBuf * _cbBuf];
    _header[prevBuf].dwBufferLength = _cbBuf;
    _header[prevBuf].dwFlags = 0;
    _header[prevBuf].dwLoops = 0;
    _waveInDevice.Prepare (&amp; _header [prevBuf]);

    _waveInDevice.SendBuffer (&amp; _header [prevBuf]);
    return TRUE;
}

void <font color="#cc0066"><b>Recorder::Stop</b></font> ()
{
    _isStarted = FALSE;
    _waveInDevice.Reset ();
    _waveInDevice.Close ();
}
</font></pre>
<hr>
Below are two subclasses of Recorder with their own implementations of GetSample. The first one is used for mono recordings with the accuracy of 8 bits per sample and the second one for mono recordings using 16 bits per sample.
<p>Each byte in the 8-bit recording corresponds to one sample. There are only 256 possible values of the sample and they are biased, so that no signal at all corresponds to all samples being equal to 128. We subtract the bias from the sample and multiply the value by 64 to normalize its range to that of a 16-bit recording.
<p>In a 16-bit recording, each pair of bytes stores a signed number corresponding to a single sample. We cast the buffer to a pointer to short and simply access it as an array of (signed 16-bit) shorts.
<p>Similarly, for 8-bit stereo we would return the sum of two channels as
<pre><font face="courier">    (pBuf[2*i] + pBuf[2*i+1] - 2 * 128) * 128;</font></pre>
and for 16 bit stereo as
<pre><font face="courier">    ( ((short *) pBuf)[2*i] +  ((short *) pBuf)[2*i+1] ) / 2</font></pre>
Of course, you can build a stereo iterator that would separately return the left channel sample and the right channel sample.
<hr>
<pre><font face="courier">class <font color="#cc0066"><b>RecorderM8</b></font>: public <font color="#cc0066"><b>Recorder</b></font>  // 8 bit mono
{
public:
    RecorderM8 (int cSamples, int cSamplesPerSec)
    : Recorder (cSamples, cSamplesPerSec, 1, 8) {}
protected:
    int GetSample (char *pBuf, int i) const
    {
        return ((unsigned char)pBuf[i] - 128) * 64;
    }
};

class <font color="#cc0066"><b>RecorderM16</b></font>: public <font color="#cc0066"><b>Recorder</b></font>  // 16 bit mono
{
public:
    RecorderM16 (int cSamples, int cSamplesPerSec)
    : Recorder (cSamples, cSamplesPerSec,  1, 16) {}
protected:
    int GetSample (char *pBuf, int i) const
    {
        return ((short *) pBuf)[i];
    }
};
</font></pre>
<hr>
We went through the trouble of subclassing the Recorder so that we can use one universal iterator to access any recording. The iterator just calls the virtual method of the recorder to decode a given sample. Of course, if you wanted to restrict your recordings to 16-bit only, you could build the decoding into the iterator and forget about subclassing the Recorder. It would save you one virtual call per sample. When I did the profiling of the Frequency Analyzer, I found out that the time it took to decode the samples was infinitesimal in comparison with all the complex number multiplications done in the FFT. So I didn't bother optimizing that part.
<hr>
<pre><font face="courier">class <font color="#cc0066"><b>SampleIter</b></font>
{
public:
    SampleIter (Recorder const &amp; recorder);
    BOOL AtEnd () const { return _iCur == _iEnd;}
    void Advance () { _iCur++; }
    void Rewind () { _iCur = _iEnd - _recorder.SampleCount(); }
    int  GetSample () const { return _recorder.GetSample(_pBuffer, _iCur);}
    int  Count () const { return _recorder.SampleCount(); }
private:
    char       *_pBuffer;
    Recorder const &amp; _recorder;
    int         _iCur;
    int         _iEnd;
};

// Call BufferDone after creating the iterator
<font color="#cc0066"><b>SampleIter::SampleIter</b></font> (Recorder const &amp; recorder)
: _recorder (recorder), _iCur(0)
{
    _pBuffer = recorder.GetData ();
    _iEnd = recorder.SampleCount();
}
</font></pre>
<hr>
Finally we are getting down to Windows API. Class <b>WaveFormat</b> is a simple encapsulation of structure <b>WAVEFORMATEX</b> used by Windows. It has a method to check whether a given format is supported by a given input device. Similarly, <b>WaveHeader</b> is a thin veneer over Windows' <b>WAVEHDR</b>.
<hr>
<pre><font face="courier">class <font color="#cc0066"><b>WaveFormat</b></font>: public <font color="#009966">WAVEFORMATEX</font>
{
public:
    WaveFormat (
        WORD    nCh, // number of channels (mono, stereo)
        DWORD   nSampleRate, // sample rate
        WORD    BitsPerSample)
    {
        wFormatTag = <font color="#009966">WAVE_FORMAT_PCM</font>;
        nChannels = nCh;
        nSamplesPerSec = nSampleRate;
        nAvgBytesPerSec = nSampleRate * nCh * BitsPerSample/8;
        nBlockAlign = nChannels * BitsPerSample/8;
        wBitsPerSample = BitsPerSample;
        cbSize = 0;
    }

    BOOL isInSupported (UINT idDev)
    {
        <font color="#009966">MMRESULT</font> result = <font color="#000099"><b>waveInOpen</b></font>
            (0, idDev, this, 0, 0, <font color="#009966">WAVE_FORMAT_QUERY</font>);
        return result == <font color="#009966">MMSYSERR_NOERROR</font>;
    }
};

class <font color="#cc0066"><b>WaveHeader</b></font>: public <font color="#009966">WAVEHDR</font>
{
public:
    BOOL IsDone () const { return dwFlags &amp; <font color="#009966">WHDR_DONE</font>; }
};
</font></pre>
<hr>
Class <b>WaveInDevice</b> combines a bunch of Windows API that deal with sound input devices. These functions share the same prefix <i>waveIn</i>, somehow suggesting their grouping into a single class. Notice how we are calling the function <b>waveInOpen</b> with a handle to an event and the flag  <b>CALLBACK_EVENT</b> that tells the multimedia subsystem that we are expecting to be notified by its triggering this event.
<hr>
<pre><font face="courier">class <font color="#cc0066"><b>WaveInDevice</b></font>
{
public:
    WaveInDevice ();
    WaveInDevice (UINT idDev, WaveFormat &amp; format, Event &amp; event);
    ~WaveInDevice ();
    BOOL    Open (UINT idDev, WaveFormat &amp; format, Event &amp; event);
    void    Reset ();
    BOOL    Close ();
    void    Prepare (WaveHeader * pHeader);
    void    UnPrepare (WaveHeader * pHeader);
    void    SendBuffer (WaveHeader * pHeader);
    BOOL    Ok () { return _status == <font color="#009966">MMSYSERR_NOERROR</font>; }
    void    Start () { waveInStart(_handle); }
    void    Stop () { waveInStop(_handle); }
    BOOL    isInUse () { return _status == <font color="#009966">MMSYSERR_ALLOCATED</font>; }
    UINT    GetError () { return _status; }
    void    GetErrorText (char* buf, int len);
private:
    <font color="#009966">HWAVEIN</font>     _handle;
    <font color="#009966">MMRESULT</font>    _status;
};

inline <font color="#cc0066"><b>WaveInDevice::WaveInDevice</b></font> ()
{
    _status = <font color="#009966">MMSYSERR_BADDEVICEID</font>;
}

inline <font color="#cc0066"><b>WaveInDevice::WaveInDevice</b></font> (UINT idDev, WaveFormat &amp; format, Event &amp; event)
{
    Open (idDev, format, event);
}

inline <font color="#cc0066"><b>WaveInDevice::~WaveInDevice</b></font> ()
{
    if (Ok())
    {
        <font color="#000099"><b>waveInReset</b></font> (_handle);
        <font color="#000099"><b>waveInClose</b></font> (_handle);
    }
}

inline BOOL <font color="#cc0066"><b>WaveInDevice::Open</b></font> (UINT idDev, WaveFormat &amp; format, Event &amp; event)
{
    _status = <font color="#000099"><b>waveInOpen</b></font> (
        &amp; _handle,
        idDev,
        &amp; format,
        (DWORD) (<font color="#009966">HANDLE</font>) event,
        0, // callback instance data
        <font color="#009966">CALLBACK_EVENT</font>);

    return Ok();
}

inline void <font color="#cc0066"><b>WaveInDevice::Reset</b></font> ()
{
    if (Ok())
        <font color="#000099"><b>waveInReset</b></font> (_handle);
}

inline BOOL <font color="#cc0066"><b>WaveInDevice::Close</b></font> ()
{
    if ( Ok() &amp;&amp; <font color="#000099"><b>waveInClose</b></font> (_handle) == 0)
    {
        _status = <font color="#009966">MMSYSERR_BADDEVICEID</font>;
        return TRUE;
    }
    else
        return FALSE;
}

inline void <font color="#cc0066"><b>WaveInDevice::Prepare</b></font> (WaveHeader * pHeader)
{
    <font color="#000099"><b>waveInPrepareHeader</b></font> (_handle, pHeader, sizeof(WAVEHDR));
}

inline void <font color="#cc0066"><b>WaveInDevice::SendBuffer</b></font> (WaveHeader * pHeader)
{
    <font color="#000099"><b>waveInAddBuffer</b></font> (_handle, pHeader, sizeof(WAVEHDR));
}

inline void <font color="#cc0066"><b>WaveInDevice::UnPrepare</b></font> (WaveHeader * pHeader)
{
    <font color="#000099"><b>waveInUnprepareHeader</b></font> (_handle, pHeader, sizeof(WAVEHDR));
}
inline void <font color="#cc0066"><b>WaveInDevice::GetErrorText</b></font> (char* buf, int len)
{
    <font color="#000099"><b>waveInGetErrorText</b></font> (_status, buf, len);
}
</font></pre>
<hr>
There's one more thing: the program has to make sure that the computer has a sound card. This is done by calling <b>waveInGetNumDevs</b> and then checking the capabilities of the appropriate device (in our case device 0, the mike).
<hr>
<pre><font face="courier">
if (<font color="#000099"><b>waveInGetNumDevs</b></font>() == 0)
    throw WinException ("No sound card installed !");

<font color="#009966">WAVEINCAPS</font> waveInCaps;
if (<font color="#000099"><b>waveInGetDevCaps</b></font> (0, &amp; waveInCaps, sizeof(<font color="#009966">WAVEINCAPS</font>)) != <font color="#009966">MMSYSERR_NOERROR</font>)
    throw WinException ("Cannot determine sound card capabilities !");

// waveInCaps.dwFormats contains information about available wave formats.
</font></pre>
<hr>
Now you have all the information to build your own app that could, for instance, display the waveform of the sound in a little window, or measure its intensity by summing up the last 100 samples, etc. Your time and imagination will be the only limiting factors.
<hr></td> </tr>
   </table></td> 
   <td width=60></td> </tr>
</table>


</body>
</html>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?