In order to capture and process further sound data, a class must inherit from the IReceiver
abstract class and implement the IReceiver::ReceiveBuffer(...)
method. Further, an instance of the IReceiver
derivate is passed to CWaveINSimple
via CWaveINSimple::Start(IReceiver *pReceiver)
.
class IReceiver {
public:
virtual void ReceiveBuffer(LPSTR lpData, DWORD dwBytesRecorded) = 0;
};
...
class CWaveINSimple {
private:
...
void _Start(IReceiver *pReceiver);
...
public:
...
void Start(IReceiver *pReceiver);
...
};
Let's see some examples.
Examples
- How would we list all the Wave In devices in the system?
const vector<CWaveINSimple*>& wInDevices = CWaveINSimple::GetDevices();
UINT i;
for (i = 0; i < wInDevices.size(); i++) {
printf("%s\n", wInDevices[i]->GetName());
}
- How would we list a Wave In device's lines (supposing that
strDeviceName
= e.g., "SoundMAX Digital Audio")?
CWaveINSimple& WaveInDevice = CWaveINSimple::GetDevice(strDeviceName);
CHAR szName[MIXER_LONG_NAME_CHARS];
UINT j;
try {
CMixer& mixer = WaveInDevice.OpenMixer();
const vector<CMixerLine*>& mLines = mixer.GetLines();
for (j = 0; j < mLines.size(); j++) {
::CharToOem(mLines[j]->GetName(), szName);
printf("%s\n", szName);
}
mixer.Close();
}
catch (const char *err) {
printf("%s\n",err);
}
- How would we record and encode in MP3 actually?
First of all, we define a class like:
class mp3Writer: public IReceiver {
private:
CMP3Simple m_mp3Enc;
FILE *f;
public:
mp3Writer(unsigned int bitrate = 128,
unsigned int finalSimpleRate = 0):
m_mp3Enc(bitrate, 44100, finalSimpleRate) {
f = fopen("music.mp3", "wb");
if (f == NULL) throw "Can't create MP3 file.";
};
~mp3Writer() {
fclose(f);
};
virtual void ReceiveBuffer(LPSTR lpData, DWORD dwBytesRecorded) {
BYTE mp3Out[44100 * 4];
DWORD dwOut;
m_mp3Enc.Encode((PSHORT) lpData, dwBytesRecorded/2,
mp3Out, &dwOut);
fwrite(mp3Out, dwOut, 1, f);
};
};
and (supposing that strLineName
= e.g., "Microphone"):
try {
CWaveINSimple& device = CWaveINSimple::GetDevice(strDeviceName);
CMixer& mixer = device.OpenMixer();
CMixerLine& mixerline = mixer.GetLine(strLineName);
mixerline.UnMute();
mixerline.SetVolume(0);
mixerline.Select();
mixer.Close();
mp3Writer *mp3Wr = new mp3Writer();
device.Start((IReceiver *) mp3Wr);
while( !_kbhit() ) ::Sleep(100);
device.Stop();
delete mp3Wr;
}
catch (const char *err) {
printf("%s\n",err);
}
CWaveINSimple::CleanUp();
Remark 1
mixerline.SetVolume(0)
is a pretty tricky point. For some sound cards, SetVolume(0)
gives original (good) sound's quality, for others, SetVolume(100)
does the same. However, you can find sound cards where SetVolume(15)
is the best quality. I have no good advices here, just try and check.
Remark 2
Almost every sound card supports "Wave Out Mix" or "Stereo Mix" (the list is extensible) Mixer's Line. Recording from such a line (mixerline.Select()
) will actually record everything going to the sound card's Wave Out (read "speakers"). So, leave WinAmp or Windows Media Player to play for a while, and start the application to record the sound at the same time, you'll see the result.
Remark 3
Rather than calling:
mp3Writer *mp3Wr = new mp3Writer();
it is also possible to instantiate an instance of the mp3Writer
as following (see the class definition above):
mp3Writer *mp3Wr = new mp3Writer(64, 32000);
This will produce a final MP3 at a 64 Kbps bitrate and 32 Khz sample rate.
Comments on using the demo application
The demo application (see the links at the top of this article) is a console application supporting two command line options. Executing the application without specifying any of the command line options will simply print the usage guideline, e.g.:
...>mp3_stream.exe
mp3_stream.exe -devices
Will list WaveIN devices.
mp3_stream.exe -device=<device_name>
Will list recording lines of the WaveIN <device_name> device.
mp3_stream.exe -device=<device_name> -line=<line_name>
[-v=<volume>] [-br=<bitrate>] [-sr=<samplerate>]
Will record from the <line_name>
at the given voice <volume>, output <bitrate> (in Kbps)
and output <samplerate> (in Hz).
<volume>, <bitrate> and <samplerate> are optional parameters.
<volume> - integer value between (0..100), defaults to 0 if not set.
<bitrate> - integer value (16, 24, 32, .., 64, etc.),
defaults to 128 if not set.
<samplerate> - integer value (44100, 32000, 22050, etc.),
defaults to 44100 if not set.
Executing the application with the "-devices" command line option will print the names of the Wave In devices currently installed in the system, e.g.:
...>mp3_stream.exe -devices
Realtek AC97 Audio
Executing the application with the "-device=<device_name>" command line option will list all the lines of the selected Wave In device, e.g.:
...>mp3_stream.exe "-device=Realtek AC97 Audio"
Mono Mix
Stereo Mix
Aux
TV Tuner Audio
CD Player
Line In
Microphone
Phone Line
At the end, the application will start recording (and encoding) sound from the selected Wave In device/line (microphone in this example) when executing with the following command line options:
...>mp3_stream.exe "-device=Realtek AC97 Audio" -line=Microphone
Recording at 128Kbps, 44100Hz
from Microphone (Realtek AC97 Audio).
Volume 0%.
hit <ENTER> to stop ...
Recorded and encoded sound is saved in the "music.mp3" file, in the same folder from where you executed the application.
If you want to record sound that is currently playing (e.g., AVI movie, or Video DVD, or ...) through the soundcard Wave Out, you can run the application with the following options:
...>mp3_stream.exe "-device=Realtek AC97 Audio" "-line=Stereo Mix"
However, this may be specific for my configuration only (also explained in the "Remark 2" above).
You can specify additional command line parameters, e.g.:
...>mp3_stream.exe "-device=Realtek AC97 Audio"
"-line=Stereo Mix" -v=100 -br=32 -sr=32000
This will set the line’s volume at 100%, and will produce the final MP3 at 32 Kbps and 32 Khz.
Conclusion
In this article, I covered couple of months I spent investigating MP3 encoding APIs and recording (capturing actually) sound going to the sound card's speakers. I used all this techniques for implementing an internet based radio station (MP3 streaming server). I found this topic very interesting, and decided to share some of my code. In one of my next articles, I will try to cover some of the aspects related to MP3 streaming and IO Completion Ports, but, until that time, I have to clean existing code, comment it, and prepare the article :).