Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Record_Sound.c
/* |
File: Record_sound.c |
Contains: This sample shows how to use SPBRecord to record to memory and then |
write the recorded samples to disk, asynchronously, using PBWriteAsync. |
This sample is useful for those developers who wish more flexibility |
than what is offered with SPBRecordToFile. |
This sample uses two parameter blocks because as Macs get faster, and |
drives can't quite keep up, using just one parameter block for two writes |
is just asking for trouble. It's not that much extra memory and it will |
be a weird bug you never have to find. The pbInUse field of each parameter |
block is supposed to keep us from reusing the parameter block in case things |
get really stopped up. |
These routines are designed to be easliy portable (in fact they were ripped |
from another sample), so you should be able to use them with little modification. |
It checks for errors returned from SPBRecord and from the PBWrites. If there |
is a disk error returned from a PBWrite the SPBRecord completion routine will |
not start the next record, killing the recording process and the error will be |
returned in the Vars structure (in the theErr field). |
This sample records three seconds of sound but doesn't call WaitNextEvent, so |
don't worry, your Mac isn't locked up, just wait a few seconds. |
Happy recording! |
Written by: Mark Cookson |
Copyright: Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved. |
You may incorporate this Apple sample source code into your program(s) without |
restriction. This Apple sample source code has been provided "AS IS" and the |
responsibility for its operation is yours. You are not permitted to redistribute |
this Apple sample source code as "Apple sample source code" after having made |
changes. If you're going to re-distribute the source, we require that you make |
it clear in the source that the code was descended from Apple sample source |
code, but that you've made changes. |
Change History (most recent first): |
8/13/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1 |
*/ |
#include <Quickdraw.h> |
#include <Windows.h> |
#include <dialogs.h> |
#include <Events.h> |
#include <Memory.h> |
#include <Packages.h> |
#include <Sound.h> |
#include <SoundInput.h> |
#include <OSUtils.h> |
#include <Files.h> |
#include <StandardFile.h> |
#include <Fonts.h> |
#include <Devices.h> |
//So we can pass our A5 and other info to our PBWrite completion routines |
typedef struct myParamBlockRec { |
ParamBlockRec pb; |
long myA5; |
Boolean pbInUse; |
}myParamBlockRec, *myParmBlkPtr; |
//Keep track of the info needed to record |
typedef struct { |
long sanitycheck; |
SPBPtr recordRec; |
myParmBlkPtr pb0, |
pb1; |
Ptr recBuffer0, |
recBuffer1; |
Fixed sampleRate; |
OSType compression; |
unsigned long totalBytes; |
long myA5, |
devBuffer, |
soundRefNum; |
short whichBuffer, |
fileRefNum, |
numChannels, |
sampleSize; |
OSErr theErr; //last error returned by SPBRecord or PBWrite |
} Vars, *VarsPtr; |
OSErr PrepairToRecordToDisk (VarsPtr myVars); |
OSErr RecordToDisk (VarsPtr myVars); |
pascal void MyRecComp (SPBPtr inParamPtr); |
OSErr FinishRecording (VarsPtr myVars); |
#if GENERATINGCFM |
pascal void MyPB0WriteComp (myParmBlkPtr passedPB); |
pascal void MyPB1WriteComp (myParmBlkPtr passedPB); |
#else |
pascal void MyPB0WriteComp (myParmBlkPtr passedPB:__a0); |
pascal void MyPB1WriteComp (myParmBlkPtr passedPB:__a0); |
#endif |
static void ToolBoxInit (void) |
{ |
MaxApplZone(); |
InitGraf (&qd.thePort); |
InitFonts (); |
InitWindows (); |
InitMenus (); |
TEInit (); |
InitDialogs ((long)nil); |
InitCursor (); |
return; |
} |
/* |
void main (void) { |
Vars myVars; |
long temp; |
OSErr err; |
ToolBoxInit (); |
err = PrepairToRecordToDisk (&myVars); |
err = RecordToDisk (&myVars); |
Delay (180, &temp); //record for about three seconds |
err = myVars.theErr; //were any errors returned from the recording? |
if (err != noErr && err != abortErr) { |
DebugStr ("\pRecording didn't complete without error"); |
} |
//if the error was that we ran out of disk space we can still write the |
//header because we preallocated space for it. The file may be bad, |
//but it doesn't have to be. At any rate we want to close the file and |
//dispose of memory. |
err = FinishRecording (&myVars); |
} |
*/ |
static OSErr SetupDevice (long inputDevice, |
short *numChannels, |
short *sampleSize, |
Fixed *sampleRate, |
OSType *compression, |
long *devBuffer) { |
OSErr err; |
Fixed gain = 0x00008000; |
short on = 1; |
err = SPBSetDeviceInfo (inputDevice, siSampleRate, (Ptr) sampleRate); |
if (err != noErr) |
DebugStr("\pcouldn't set sample rate"); |
err = SPBSetDeviceInfo (inputDevice, siSampleSize, (Ptr) sampleSize); |
if (err != noErr) |
DebugStr("\pcouldn't set sample size"); |
err = SPBSetDeviceInfo (inputDevice, siTwosComplementOnOff, (Ptr) &on); |
if (err != noErr) |
DebugStr("\pcouldn't set twos complement"); |
err = SPBSetDeviceInfo (inputDevice, siNumberChannels, (Ptr) numChannels); |
if (err != noErr) |
DebugStr("\pcouldn't set number of channels"); |
err = SPBSetDeviceInfo (inputDevice, siCompressionType, (Ptr) compression); |
if (err != noErr) |
DebugStr("\pcouldn't set compression type"); |
//turn on continuous recording to "warm up" the input device |
err = SPBSetDeviceInfo (inputDevice, siContinuous, (Ptr) &on); |
if (err != noErr) |
DebugStr("\pcouldn't turn on continuous recording"); |
//turn on Automatic Gain Control |
err = SPBSetDeviceInfo (inputDevice, siAGCOnOff, (Ptr) &on); |
if (err != noErr) { |
//If AGC isn't available, just turn it all the way down to avoid over driving |
err = SPBSetDeviceInfo (inputDevice, siInputGain, (Ptr) &gain); |
if (err != noErr) |
DebugStr("\pcouldn't get siInputGain"); |
} |
//check to see what we really got |
err = SPBGetDeviceInfo (inputDevice, siSampleRate, (Ptr) sampleRate); |
if (err != noErr) |
DebugStr("\pcouldn't get sample rate"); |
err = SPBGetDeviceInfo (inputDevice, siSampleSize, (Ptr) sampleSize); |
if (err != noErr) |
DebugStr("\pcouldn't get sample size"); |
err = SPBGetDeviceInfo (inputDevice, siNumberChannels, (Ptr) numChannels); |
if (err != noErr) |
DebugStr("\pcouldn't get number of channels"); |
err = SPBGetDeviceInfo (inputDevice, siDeviceBufferInfo, (Ptr) devBuffer); |
if (err != noErr) |
DebugStr("\pcouldn't get number of channels"); |
err = SPBGetDeviceInfo (inputDevice, siCompressionType, (Ptr) compression); |
if (err != noErr) |
DebugStr("\pcouldn't get compression type"); |
return err; |
} |
OSErr PrepairToRecordToDisk (VarsPtr myVars) { |
OSErr err; |
short volRefNum; |
long buffSize, parID; |
StandardFileReply sfReply; |
StringPtr nameString; |
StandardPutFile ("\pPut recorded AIFF where...", "\pRecorded sound", &sfReply); |
if (sfReply.sfGood) { |
volRefNum = sfReply.sfFile.vRefNum; |
parID = sfReply.sfFile.parID; |
nameString = (StringPtr)&sfReply.sfFile.name; |
err = SPBOpenDevice (nil, siWritePermission, &myVars->soundRefNum); |
if (err != noErr) { |
DebugStr("\pcouldn't open the device"); |
} |
myVars->numChannels = 2; |
myVars->sampleSize = 16; |
myVars->sampleRate = rate44khz; |
myVars->compression = 'NONE'; |
err = SetupDevice (myVars->soundRefNum, &myVars->numChannels, |
&myVars->sampleSize, |
&myVars->sampleRate, |
&myVars->compression, |
&myVars->devBuffer); |
buffSize = myVars->devBuffer * 20; //make our buffer a multiple of the hardware's |
myVars->recBuffer0 = NewPtrClear(buffSize); |
if (MemError() != noErr || myVars->recBuffer0 == nil) { |
DebugStr("\pcouldn't get memory for recording buffer"); |
err = memFullErr; |
} |
myVars->recBuffer1 = NewPtrClear(buffSize); |
if (MemError() != noErr || myVars->recBuffer1 == nil) { |
DebugStr("\pcouldn't get memory for recording buffer"); |
err = memFullErr; |
} |
if (err == noErr) { |
myVars->recordRec = (SPBPtr) NewPtrClear(sizeof (SPB)); |
if (MemError() != noErr || myVars->recordRec == nil) { |
DebugStr("\pcouldn't get memory for recording record"); |
err = memFullErr; |
} |
} |
if (err == noErr) { |
myVars->pb0 = (myParmBlkPtr) NewPtrClear(sizeof (myParamBlockRec)); |
if (MemError() != noErr || myVars->pb0 == nil) { |
DebugStr("\pcouldn't get memory for param block"); |
err = memFullErr; |
} |
} |
if (err == noErr) { |
myVars->pb1 = (myParmBlkPtr) NewPtrClear(sizeof (myParamBlockRec)); |
if (MemError() != noErr || myVars->pb1 == nil) { |
DebugStr("\pcouldn't get memory for param block"); |
err = memFullErr; |
} |
} |
if (err == noErr) { |
HParamBlockRec Hpb; |
IOCompletionUPP MyPB0WriteCompUPP, |
MyPB1WriteCompUPP; |
SICompletionUPP MyRecCompUPP; |
myVars->sanitycheck = 'SANE'; |
myVars->myA5 = SetCurrentA5 (); |
myVars->whichBuffer = 0; |
myVars->pb0->myA5 = SetCurrentA5 (); |
myVars->pb1->myA5 = SetCurrentA5 (); |
//set up the record parameters |
MyRecCompUPP = NewSICompletionProc (MyRecComp); |
myVars->recordRec->inRefNum = myVars->soundRefNum; |
myVars->recordRec->count = buffSize; |
myVars->recordRec->milliseconds = 0; |
myVars->recordRec->bufferLength = buffSize; |
myVars->recordRec->bufferPtr = myVars->recBuffer0; |
myVars->recordRec->completionRoutine = MyRecCompUPP; |
myVars->recordRec->interruptRoutine = nil; |
myVars->recordRec->userLong = (long)myVars; |
myVars->recordRec->error = 0; |
myVars->recordRec->unused1 = 0; |
//create the file |
Hpb.ioParam.ioCompletion = nil; |
Hpb.ioParam.ioNamePtr = nameString; |
Hpb.ioParam.ioVRefNum = volRefNum; |
Hpb.fileParam.ioDirID = parID; |
err = PBHCreateSync (&Hpb); |
if (err == dupFNErr) { |
err = noErr; //overwriting the file is not an error |
} |
if (err != noErr) |
DebugStr("\pPBHCreateSync failed"); |
if (err == noErr) { |
//set the file type and creator |
Hpb.fileParam.ioVRefNum = volRefNum; |
Hpb.fileParam.ioDirID = parID; |
Hpb.fileParam.ioNamePtr = nameString; |
Hpb.fileParam.ioFVersNum = 0; |
Hpb.fileParam.ioFDirIndex = 0; |
err = PBHGetFInfoSync(&Hpb); |
if (err != noErr) |
DebugStr("\pPBHGetFInfoSync failed"); |
} |
if (err == noErr) { |
Hpb.fileParam.ioFlFndrInfo.fdType = 'AIFF'; |
Hpb.fileParam.ioFlFndrInfo.fdCreator = 'Csdo'; |
Hpb.fileParam.ioDirID = parID; |
err = PBHSetFInfoSync (&Hpb); |
if (err != noErr) |
DebugStr("\pPBHSetFInfoSync failed"); |
} |
if (err == noErr) { |
//open the file for writing |
Hpb.ioParam.ioCompletion = nil; |
Hpb.ioParam.ioNamePtr = nameString; |
Hpb.ioParam.ioVRefNum = volRefNum; |
Hpb.ioParam.ioPermssn = fsRdWrPerm; |
Hpb.fileParam.ioDirID = parID; |
err = PBHOpenDFSync (&Hpb); |
if (err != noErr) |
DebugStr("\pPBHOpenDFSync failed"); |
} |
if (err == noErr) { |
myVars->fileRefNum = Hpb.ioParam.ioRefNum; |
//set up the parameter blocks for the coming writes |
MyPB0WriteCompUPP = NewIOCompletionProc (MyPB0WriteComp); |
MyPB1WriteCompUPP = NewIOCompletionProc (MyPB1WriteComp); |
myVars->pb0->pb.ioParam.ioCompletion = MyPB0WriteCompUPP; |
myVars->pb0->pb.ioParam.ioVRefNum = volRefNum; |
myVars->pb0->pb.ioParam.ioRefNum = myVars->fileRefNum; |
myVars->pb0->pb.ioParam.ioPosMode = fsAtMark; |
myVars->pb1->pb.ioParam.ioCompletion = MyPB1WriteCompUPP; |
myVars->pb1->pb.ioParam.ioVRefNum = volRefNum; |
myVars->pb1->pb.ioParam.ioRefNum = myVars->fileRefNum; |
myVars->pb1->pb.ioParam.ioPosMode = fsAtMark; |
//write a temp AIFF header so file pointer for data is in the right place |
err = SetupAIFFHeader (myVars->fileRefNum, myVars->numChannels, myVars->sampleRate, myVars->sampleSize, myVars->compression, 0, 0); |
if (err != noErr) |
DebugStr("\pSetupAIFFHeader failed"); |
} |
} |
} else { |
err = userCanceledErr; |
} |
return err; |
} |
OSErr RecordToDisk (VarsPtr myVars) { |
OSErr err = noErr; |
myVars->totalBytes = 0; |
err = SPBRecord (myVars->recordRec, true); |
if (err != noErr) { |
DebugStr("\pSPBRecord failed"); |
} |
return err; |
} |
/* |
Stops the recording by calling SPBStopRecording and then writes the |
correct file header to the file, closes the file and then disposes of |
our pointers. |
*/ |
OSErr FinishRecording (VarsPtr myVars) { |
OSErr err; |
ParamBlockRec pb; |
err = SPBStopRecording (myVars->soundRefNum); |
if (err != noErr) |
DebugStr("\pSPBStopRecording failed"); |
err = SPBCloseDevice (myVars->soundRefNum); |
if (err != noErr) |
DebugStr("\pSPBCloseDevice failed"); |
//Put the file pointer back to the start of the file to |
//write the correct header over the temp header |
pb.ioParam.ioCompletion = nil; |
pb.ioParam.ioRefNum = myVars->fileRefNum; |
pb.ioParam.ioPosMode = fsFromStart; |
pb.ioParam.ioPosOffset = 0; |
err = PBSetFPosSync (&pb); |
if (err != noErr) |
DebugStr("\pPBSetFPosSync failed"); |
if (err == noErr) { |
//write the header with the correct information |
err = SetupAIFFHeader (myVars->fileRefNum, myVars->numChannels, myVars->sampleRate, myVars->sampleSize, myVars->compression, myVars->totalBytes, myVars->totalBytes/(myVars->sampleSize*myVars->numChannels)); |
if (err != noErr) |
DebugStr("\pSetupAIFFHeader in FinishRecording failed"); |
} |
err = PBCloseSync(&pb); |
if (err != noErr) |
DebugStr("\pPBCloseSync failed"); |
DisposePtr ((Ptr)myVars->recordRec); |
DisposePtr (myVars->recBuffer0); |
DisposePtr (myVars->recBuffer1); |
DisposePtr ((Ptr)myVars->pb0); |
DisposePtr ((Ptr)myVars->pb1); |
return err; |
} |
/* |
This gets called at the end of each record, it writes the recorded data to |
disk (asynchronously) and then starts a new record using the same SPBPtr. |
Make sure you have continous recording turned on otherwise the recording |
may loose data between calls to SPBRecord. |
*/ |
pascal void MyRecComp (SPBPtr inParamPtr) { |
VarsPtr myVarsPtr; |
OSErr err; |
#if !GENERATINGCFM |
long oldA5; |
oldA5 = SetA5 (((VarsPtr)inParamPtr->userLong)->myA5); |
#endif |
myVarsPtr = (VarsPtr)inParamPtr->userLong; |
//setting this now will avoid a race condition of the record finishing before |
//we can toggle which buffer to use |
myVarsPtr->whichBuffer = !myVarsPtr->whichBuffer; |
//If the last write returned with no error then continue recording |
if (myVarsPtr->pb0->pb.ioParam.ioResult == noErr && myVarsPtr->pb1->pb.ioParam.ioResult == noErr) { |
//If we are aborting (stopping) the recording, we still want to write the last buffer |
if (inParamPtr->error == noErr || inParamPtr->error == abortErr) { |
if (myVarsPtr->whichBuffer == 1) { |
myVarsPtr->pb0->pb.ioParam.ioBuffer = myVarsPtr->recBuffer0; |
myVarsPtr->pb0->pb.ioParam.ioReqCount = myVarsPtr->recordRec->count; |
myVarsPtr->totalBytes += myVarsPtr->recordRec->count; |
if (myVarsPtr->pb0->pbInUse == false) { |
myVarsPtr->pb0->pbInUse = true; |
err = PBWriteAsync(&myVarsPtr->pb0->pb); |
} |
inParamPtr->bufferPtr = myVarsPtr->recBuffer1; |
if (inParamPtr->error == noErr) { |
err = SPBRecord (inParamPtr, true); |
if (err != noErr) |
DebugStr("\pSPBRecord1 failed"); |
} |
} else { |
myVarsPtr->pb1->pb.ioParam.ioBuffer = myVarsPtr->recBuffer1; |
myVarsPtr->pb1->pb.ioParam.ioReqCount = myVarsPtr->recordRec->count; |
myVarsPtr->totalBytes += myVarsPtr->recordRec->count; |
if (myVarsPtr->pb1->pbInUse == false) { |
myVarsPtr->pb1->pbInUse = true; |
err = PBWriteAsync (&myVarsPtr->pb1->pb); |
} |
inParamPtr->bufferPtr = myVarsPtr->recBuffer0; |
if (inParamPtr->error == noErr) { |
err = SPBRecord (inParamPtr, true); |
if (err != noErr) |
DebugStr("\pSPBRecord2 failed"); |
} |
} |
myVarsPtr->theErr = err; |
} |
} else { |
//There was an error from PBWrite, return the error in theErr of our structure |
if (myVarsPtr->pb0->pb.ioParam.ioResult != noErr) |
myVarsPtr->theErr = myVarsPtr->pb0->pb.ioParam.ioResult; |
else myVarsPtr->theErr = myVarsPtr->pb1->pb.ioParam.ioResult; |
} |
#if !GENERATINGCFM |
oldA5 = SetA5 (oldA5); |
#endif |
return; |
} |
/* |
These routines are here just for error checking in this example, |
you may wish to update a status record or something similar. |
What do you do if you get a write error during async recording? |
For this example we will simply terminate the recording process, |
you should probably display an error. Since the myParamBlockRec |
doesn't contain the sound input device number we will check for |
an error in the record completion routine and not start a new |
recording if there was an error. You can call SPBStopRecording |
at interrupt time, but that isn't needed in this example. |
These routines display the error, but the record completion |
routine checks the parameter blocks to make sure there was no |
error before continuing the recording. |
*/ |
#if GENERATINGCFM |
pascal void MyPB0WriteComp (myParmBlkPtr passedPB) |
#else |
pascal void MyPB0WriteComp (myParmBlkPtr passedPB:__a0) |
#endif |
{ |
if (passedPB->pb.ioParam.ioResult != noErr) { |
DebugStr("\pPBWrite0 failed!"); |
} |
passedPB->pbInUse = false; |
} |
#if GENERATINGCFM |
pascal void MyPB1WriteComp (myParmBlkPtr passedPB) |
#else |
pascal void MyPB1WriteComp (myParmBlkPtr passedPB:__a0) |
#endif |
{ |
if (passedPB->pb.ioParam.ioResult != noErr) { |
DebugStr("\pPBWrite1 failed!"); |
} |
passedPB->pbInUse = false; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-03-14