Solved

Recording PCM from Microphone

Posted on 1998-08-07
2
1,699 Views
Last Modified: 2011-04-14
Hi! I am a college student and working with an audio compression project. Actually I am having 2 great problems on my project. They are recording and playing audio in Delphi. So please help me to solve the first problem, recording sound from microphone.

I am having a sound blaster 16 sound card in my PC. I hope that I can get 8bit mono PCM data from it, and then I will compress the data and save it to disk.

Can you show me how can I get the raw data from sound card?

Thanks you very much.

regards,
James.
0
Comment
Question by:jhui
2 Comments
 
LVL 8

Accepted Solution

by:
ZifNab earned 50 total points
Comment Utility
jhui,

didn't you asked a question before? (a day ago?) And without any notification about the comments, just deleted it??? Here, we all like what people think of our comments, bad or good.

Anyway, here is your answer :
(by Dr Darryl Gove - D.J.Gove@open.ac.uk)

article from UNDU :

.

Do you have a soundcard?

The first thing that youll need to do when writing an audio application is to handle the possibility that the computer the application is running on
does not have a soundcard.

To do this we need to use the calls WaveOutGetNumDevs and WaveInGetNumDevs - they return the number of audio playing devices (Out)
and the number of recording devices (In). Most of the time there will be one of each - one soundcard.

if WaveOutGetNumDevs=0 then application.messagebox('Error', 'No sound playing card', mb_OK);
if waveInGetNumDevs=0 then application.messagebox('Error','No recording sound card',mb_ok);


The best place for this code would probably be in the On Create handler for the form, however before you compile the code, make sure that you
have included mmsystem in the uses list.

What kind of sound do you want?

You should be aware that there is a variety of options for sound quality - whether it is mono or stereo, 8 or 16 bit, and the sampling frequency.
You need to ask the soundcard whether it supports the format.

The basic wave format information is handled by the TWaveFormat block

TWaveFormat = record
    wFormatTag: Word;         {format type}
    nChannels: Word;          {number of channels 1 for mono 2 for stereo}
    nSamplesPerSec: Longint;  {sample rate}
    nAvgBytesPerSec: Longint; {number of bytes per second recorded}
    nBlockAlign: Word;        {size of a single sample}
  end;


However, you wont directly use this, since you need to use a wrapper which relates to the particular format that you want to store the data in -
basically you dont only ask Do you support this sample rate etc? but ask Do you support this way of saving the data?. The only really supported
format is PCM, but potentially there could be other formats supported by the multimedia subsystem, and you as a programmer would not need
to worry about them.

In order to ask about PCM data, you need to use the TPCMWaveFormat block:

TPCMWaveFormat = record
   wf: TWaveFormat;
   wBitsPerSample: Word;
end;


Which is just the TWaveFormat block with an additional word telling the computer the bits per sample (8,16, or 32). Each sample (the number
of samples per second is the sampling frequency) is either 8 bit or 16 bit, and either stereo or mono - so the smallest size is 8 bit mono or 8 bits
per sample, and the largest size is 16 bit stereo or 32 bits per sample. This calculation follows directly from the data you specify in the
TWaveFormat block - so besides questions of why do you need to tell the computer again, well have a look at setting up the TWaveFormat
block:

WaveFormat:=new(PPCMwaveFormat);
with WaveFormat^.wf do
  begin
    WFormatTag := WAVE_FORMAT_PCM; {PCM format - the only option!}
    NChannels:=1; {mono}
    NSamplesPerSec:=11000; {11kHz sampling}
    NAvgBytesPerSec:=11000; {we aim to use 8 bit sound so only 11k per second}
    NBlockAlign:=1; {only one byte in each sample}
    waveformat^.wBitsPerSample:=8; {8 bits in each sample}
  end;


So weve set up the type of audio we want to record, the next thing to do is to ask the soundcard if it can do it.

i:=waveOutOpen(nil,0,PWaveFormat(WaveFormat),0,0,WAVE_FORMAT_QUERY);
if i<>0 then
  application.messagebox('Error', 'Play format not supported', mb_OK);

i:=waveInOpen(nil,0,PWaveFormat(WaveFormat),0,0,WAVE_FORMAT_QUERY);
if i<>0 then
  application.messagebox('Error', 'Record format not supported', mb_OK);


Getting a handle on it.

Like most things in Windows, we end up referring to the soundcard using a handle; we need one handle to record and one to playback.

Having set up our WaveFormat block, we can ask for a handle to a device that can either play or record that format.

HwaveOut:=new(PHwaveOut);
i:=waveOutOpen(HWaveOut,0,Pwaveformat(WaveFormat),form1.handle,0,CALLBACK_WINDOW);
if i<>0 then
  application.messagebox('Error', 'Problem creating play handle', mb_OK);

HwaveIn:=new(PHwaveIn);
i:=waveInOpen(HWaveIn,0,Pwaveformat(WaveFormat),form1.handle,0,CALLBACK_WINDOW);
if i<>0 then
  application.messagebox('Error', 'Problem creating record handle', mb_OK);


In this instance, were going to use the messages to handle the playback and recording of audio. We could use a callback function. To use
messages, we need to pass the handle of a window that will receive the messages, and the CALLBACK_WINDOW value to tell the multimedia
subsystem that were passing a handle to it.

Being prepared

The final thing to do is to start either playing sound or recording sound. To do this we need to send packets of memory to the sound card either
to play or to record on.

When you send data out to be played, the playing starts immediately you add a packet of data, extra packets of data are added to a queue, and
played in sequence. If youre recording then the blocks of memory are once again added to a queue - but they are not recorded on until you tell
the computer to start recording. If the computer runs out of packets to record on then the recording stops.

So the first thing to do is to get a block of memory and to set up the data block that will tell the multimedia subsystem about it.

Tmemblock=array[0..memblocklength] of byte;
memBlock:=new(PmemBlock);


Nows a good time to put your audio data into the memory block - if youre playing audio.

Header:=new(PwaveHdr);
with header^ do
  begin
    lpdata:=pointer(memBlock);
    dwbufferlength:=memblocklength;
    dwbytesrecorded:=0;
    dwUser:=0;
    dwflags:=0;
    dwloops:=0;
  end;


Except for setting the pointer to a block of memory, and the length of the block of memory, all the other fields should be set to zero - unless you
want to play the same block of data multiple times.

The next step is to prepare the data, why this is necessary, I dont know!

i:=waveOutPrepareHeader(HWaveOut^,Header,sizeof(TWavehdr));
if i<>0 then
  application.messagebox('Out Prepare error','error',mb_ok);

i:=waveInPrepareHeader(HWaveIn^,Header,sizeof(TWavehdr));
if i<>0 then
  application.messagebox('In Prepare error','error',mb_ok);


Then we need to send the new block of data to the audio device - either to be played or to be recorded on.

i:=waveOutWrite(HWaveOut^,Header,sizeof(TWaveHdr));
if i<>0 then
  application.messagebox('Wave out error','error',mb_ok);

i:=waveInAddBuffer(HWaveIn^,Header,sizeof(TWaveHdr));
if i<>0 then
  application.messagebox('Add buffer error','error',mb_ok);


The final thing to do when recording sound is to start!

i:=waveInStart(HwaveIn^);
if i<>0 then
   application.messagebox('Start error','error',mb_ok);


There are also commands to stop and pause recording.

Messages

If youre using messages to control the recording an playback of audio, then you need to have some handlers for the messages. The handlers
should be something like

procedure

 MMOutOpen(var msg: Tmessage); message MM_WOM_OPEN;
procedure MMOutClose(var msg: Tmessage); message MM_WOM_CLOSE;
procedure MMOutDone(var msg: Tmessage); message MM_WOM_DONE;
procedure MMInOpen(var msg: Tmessage); message MM_WIM_OPEN;
procedure MMInClose(var msg: Tmessage); message MM_WIM_CLOSE;
procedure MMInDone(var msg: Tmessage); message MM_WIM_DATA;


WOM messages are send by audio out devices, and WIM messages are sent by audio in devices.

The open and close messages are sent when the device is either opened or closed (closing is covered in the next section) - these are not really
very useful messages to trap. The important messages are the DONE and DATA.

MM_WOM_DONE tells you that the block of data that you were playing has been played, and you should now get rid of it.
MM_WIM_DATA tells you that the block of data has been recorded on and you should now deal with it as appropriate. For both messages,
youll probably want to send some more data to the audio device.

The first thing to do with your returned data is to unprepare it, a pointer to the header that identifies the block is passed as the lparam of the
message.

Header:=PWaveHdr(msg.lparam);
i:=waveOutUnPrepareHeader(HWaveOut^,Header,sizeof(TWavehdr));
if i<>0 then
  application.messagebox('Out Un Prepare error','error',mb_ok);

Header:=PWaveHdr(msg.lparam);
i:=waveInUnPrepareHeader(HWaveIn^,Header,sizeof(TWavehdr));
if i<>0 then
  application.messagebox('In Un Prepare error','error',mb_ok);


You can then do as you will with the block of data, but remember to dispose of any memory that you dont want any more.

dispose(Header^.lpdata);
dispose(Header);


Disposing of the evidence.

Once weve finished with the soundcard, we need to get rid of the handle to audio device. Before we can do that, we need to reset the device so
that any unused buffers are returned to the application for disposal.

if HWaveOut<>nil then WaveOutReset(HWaveOut^);
if HwaveOut<>nil then WaveOutClose(HWaveOut^);

if HwaveIn<>nil then WaveInReset(HWaveIn^);
if HwaveIn<>nil then WaveInClose(HWaveIn^);


Notice that the multimedia subsystem sometimes requires a pointer to a handler, and sometimes a handler - just one of those things.

A problem youll find if you try this code, is that the reset is asynchronous so you reset the audio device, and close it - then your handler gets
called with a block of data that needs to be unprepared, of course the handle you had is invalid now, and so you get a GPF if you use it (well you
dont seem to but Im not prepared to take that kind of a risk). So to get around this count the number of packets sent to the audio device, and
only execute the close when there are no remaining packets - you can do this in the handler that deals with returned packets of data.

The example program

The listing is a complete program for recording audio and playing it directly back to the speaker - an echo! To use it make a blank form, with an
On Create and a Close Query handler, then replace the entire unit with the code in the listing. Its written for Delphi 1, but I would expect it to
work with Delphi 2.

Note: The code given uses a sampling rate of 11,000 samples per second; not all sound cards can support this rate. If your sound card does not
support it, then you will need to adjust the rate to 11,025 samples per second, which should be supported.

Conclusion

Using the sound card at a low level to record and playback audio, feels like it should be really complex, but in fact it isnt thats the power of the
Windows API. However there are a number of quirks in it, but its fun!

sample source : www.undu.com/DN970901/00000038.htm

Regards, Zif.
0
 

Author Comment

by:jhui
Comment Utility
Thanks a lot, Zif! This is the first time I get reply on this subject.
And it is very helpful, thanks.

regards,
James.
0

Featured Post

What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

16 Experts available now in Live!

Get 1:1 Help Now