Quick and Easy Audio Recording in C++ Builder

By: David Pankhurst

Abstract: Use the MCIWnd* family of functions to implement something lacking in TMediaPlayer - easily configurable audio recording.

Quick and Easy Audio Recording in C++ Builder

Although the TMediaPlayer component in Builder does just about everything I could want, one problem I've encountered with it is in recording. This article details how to provide recording support in Builder with the MCI Window interface.

Simple Recording?

The problem is fairly simple in TMediaPlayer - you need to have a valid FileName entry before calling Open(), which means the audio has to exist before you record it! As well it is that is it quite awkward to change audio settings, or create brand-new audio, since there are no VCL calls for this kind of access. While none of this is insurmountable, for my recording needs all I wanted was a simple solution, ideally with just two buttons: Record and Stop.

Surveying the Window literature gave me the idea to use the MCIWnd* class of functions, and the result was a very simple solution. The MCI Window wraps all the general media calls in a window you create on your form. Although it can be used for more than recording, there's little need with TMediaPlayer available, so we'll just focus on that aspect here.

The sample code shows how to set up a window, record audio, and close. The program is exceedingly simple, and so makes it straightforward for you to cut and paste recording functionality into your own programs. Discussing the code will show just how easy it is to set up and record.

Coding Our Window

First off, we add code to create an MCI Window and close it. Since the window can exist for the life of the program, we handle construction and destruction in the form's constructor and destructor:
  extern HINSTANCE g_hInstance; 
  __fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
                           WS_CHILD | WS_OVERLAPPED, 
                           NULL );
    if ( NULL==m_hMCIWnd ) // error?
      MessageBox(Handle,"Error Creating MCIWnd Window!",NULL, MB_OK);
  __fastcall TForm1::~TForm1(void)

All of the parameters to MCIWndCreate() are straightforward, with the exception of g_hInstance, which is the program's HINSTANCE value from our WinMain (Project1.cpp). The actual program source code has more options than shown here, since you can choose to show this window and display buttons, framing, and so forth. However, since all we want is recording, we've opted to leave off the WS_VISIBLE flag.

This window is like any other window in the system. You can communicate with it via SendMessage() and PostMessage(), but fortunately there are macros that wrap these calls into simpler formats, the MCIWnd* functions. As a first example we have the commands to set up and begin recording a new file, placed in our Record button handler:

  void __fastcall TForm1::Button1Click(TObject *Sender)
    // create new .WAV file
    MCIWndNew(m_hMCIWnd, "waveaudio");
    // begin recording

Stopping is equally easy, by responding to the Stop button, thereby making audio recording possible in five lines of code (after setup of course):

  void __fastcall TForm1::Button2Click(TObject *Sender)
    // stop recording and save file

As always, there are some caveats. Since the filename is not needed until closing, you rightly can guess that the file is maintained in memory, and so this isn't a solution for recording large audio files. As well, there is no provision for monitoring of buffers or mixing - if you need that kind of low-level control, consider writing your own buffer recording routines. An excellent reference for that is 'Programming Windows' by Charles Petzold.

Changing Settings

We now need to look at altering settings for frequency, channels and the like. Recording defaults to 11 kilohertz, 8 bit sampling, and single channel (mono); if this is what you need, no further adjustments are necessary. However, settings can be changed with one call (which in the program is placed between MCIWndNew() and MCIWndRecord()):

  MCI_WAVE_SET_PARMS set_parms; // audio parameters
  set_parms.wFormatTag      = WAVE_FORMAT_PCM;
  set_parms.wBitsPerSample  = 16;
  set_parms.nChannels       = 1;
  set_parms.nSamplesPerSec  = 44100;
  set_parms.nBlockAlign     = (set_parms.nChannels*set_parms.wBitsPerSample)/8;
  set_parms.nAvgBytesPerSec = ((set_parms.wBitsPerSample) *
                                set_parms.nChannels *
  // now send the format changes with MCI_SET
  int deviceID=MCIWndGetDeviceID(m_hMCIWnd);
  int result = mciSendCommand( deviceID, MCI_SET,
                             | MCI_WAVE_SET_FORMATTAG
                             | MCI_WAVE_SET_BITSPERSAMPLE
                             | MCI_WAVE_SET_CHANNELS
                             | MCI_WAVE_SET_SAMPLESPERSEC
                             | MCI_WAVE_SET_AVGBYTESPERSEC
                             | MCI_WAVE_SET_BLOCKALIGN,
  if ( result ) // failed?
    char buffer[100];
    mciGetErrorString(result, buffer, sizeof(buffer));
    MessageBox( NULL, buffer, "MCI_WAVE_SET_1", MB_OK);

Because there is no MCIWnd* call for setting the audio, we fall back on the mciSendCommand(), which needs a device ID (provided by MCIWndGetDeviceID), a structure containing the parameters, and flags indicating which structure items are to be changed.

The structure values you most likely will change are wBitsPerSample (8 or 16), nChannels (1 for mono, 2 for stereo), and nSamplesPerSec (frequency in hertz). While you can change the other parameters, there might be problems, since not all values work. For instance if you change the wFormatTag value to WAVE_FORMAT_ADPCM you'll get the response 'The parameter is out of range for the specified command'. The morale is to stick to known values, or be prepared to test a lot.

In conclusion, be sure to look up the Builder documentation on the MCWnd* calls. Although TMediaPlayer satisfies most needs, using MCI window calls can give you easy and flexible recording in Builder.

Server Response from: ETNASC03