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.
PlaySoundFillBuffer.c
/* |
File: PlaySoundFillBuffer.c |
Description: Play Sound shows how to use the Sound Description Extention atom information with the |
SoundConverterFillBuffer APIs to play VBR and Non-VBR MP3 files. It will also play |
.aiff files using various encoding methods and .wav files. |
NOTE: This Sample requires CarbonLib 1.1 or above. |
Author: mc, era |
Copyright: © Copyright 2000 Apple Computer, Inc. All rights reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): <2> 10/31/00 modified to use SoundConverterFillBuffer APIs |
<1> 7/26/00 initial release as PlaySound.c |
*/ |
#include "MP3Player.h" |
// globals |
Boolean gBufferDone; |
// * ---------------------------- |
// MySoundCallBackFunction |
// |
// used to signal when a buffer is done playing |
static pascal void MySoundCallBackFunction(SndChannelPtr theChannel, SndCommand *theCmd) |
{ |
#pragma unused(theChannel) |
#ifndef TARGET_API_MAC_CARBON |
#if !GENERATINGCFM |
long oldA5; |
oldA5 = SetA5(theCmd->param2); |
#else |
#pragma unused(theCmd) |
#endif |
#else |
#pragma unused(theCmd) |
#endif // TARGET_API_MAC_CARBON |
gBufferDone = true; |
#ifndef TARGET_API_MAC_CARBON |
#if !GENERATINGCFM |
oldA5 = SetA5(oldA5); |
#endif |
#endif // TARGET_API_MAC_CARBON |
} |
// * ---------------------------- |
// SoundConverterFillBufferDataProc |
// |
// the callback routine that provides the source data for conversion - it provides data by setting |
// outData to a pointer to a properly filled out ExtendedSoundComponentData structure |
static pascal Boolean SoundConverterFillBufferDataProc(SoundComponentDataPtr *outData, void *inRefCon) |
{ |
SCFillBufferDataPtr pFillData = (SCFillBufferDataPtr)inRefCon; |
OSErr err = noErr; |
// if after getting the last chunk of data the total time is over the duration, we're done |
if (pFillData->getMediaAtThisTime >= pFillData->sourceDuration) { |
pFillData->isThereMoreSource = false; |
pFillData->compData.desc.buffer = NULL; |
pFillData->compData.desc.sampleCount = 0; |
pFillData->compData.bufferSize = 0; |
} |
if (pFillData->isThereMoreSource) { |
long sourceBytesReturned; |
long numberOfSamples; |
TimeValue sourceReturnedTime, durationPerSample; |
HUnlock(pFillData->hSource); |
err = GetMediaSample(pFillData->sourceMedia, // specifies the media for this operation |
pFillData->hSource, // function returns the sample data into this handle |
pFillData->maxBufferSize, // maximum number of bytes of sample data to be returned |
&sourceBytesReturned, // the number of bytes of sample data returned |
pFillData->getMediaAtThisTime, // starting time of the sample to be retrieved (must be in Media's TimeScale) |
&sourceReturnedTime, // indicates the actual time of the returned sample data |
&durationPerSample, // duration of each sample in the media |
NULL, // sample description corresponding to the returned sample data (NULL to ignore) |
NULL, // index value to the sample description that corresponds to the returned sample data (NULL to ignore) |
0, // maximum number of samples to be returned (0 to use a value that is appropriate for the media) |
&numberOfSamples, // number of samples it actually returned |
NULL); // flags that describe the sample (NULL to ignore) |
HLock(pFillData->hSource); |
if ((noErr != err) || (sourceBytesReturned == 0)) { |
pFillData->isThereMoreSource = false; |
pFillData->compData.desc.buffer = NULL; |
pFillData->compData.desc.sampleCount = 0; |
if ((err != noErr) && (sourceBytesReturned > 0)) |
DebugStr("\pGetMediaSample - Failed in FillBufferDataProc"); |
} |
pFillData->getMediaAtThisTime = sourceReturnedTime + (durationPerSample * numberOfSamples); |
pFillData->compData.bufferSize = sourceBytesReturned; |
} |
// set outData to a properly filled out ExtendedSoundComponentData struct |
*outData = (SoundComponentDataPtr)&pFillData->compData; |
return (pFillData->isThereMoreSource); |
} |
// * ---------------------------- |
// GetMovieMedia |
// |
// returns a Media identifier - if the file is a System 7 Sound a non-in-place import is done and |
// a handle to the data is passed back to the caller who is responsible for disposing of it |
static OSErr GetMovieMedia(const FSSpec *inFile, Media *outMedia, Handle *outHandle) |
{ |
Movie theMovie = 0; |
Track theTrack; |
FInfo fndrInfo; |
OSErr err = noErr; |
err = FSpGetFInfo(inFile, &fndrInfo); |
BailErr(err); |
if (kQTFileTypeSystemSevenSound == fndrInfo.fdType) { |
// if this is an 'sfil' handle it appropriately |
// QuickTime can't import these files in place, but that's ok, |
// we just need a new place to put the data |
MovieImportComponent theImporter = 0; |
Handle hDataRef = NULL; |
// create a new movie |
theMovie = NewMovie(newMovieActive); |
// allocate the data handle and create a data reference for this handle |
// the caller is responsible for disposing of the data handle once done with the sound |
*outHandle = NewHandle(0); |
err = PtrToHand(outHandle, &hDataRef, sizeof(Handle)); |
if (noErr == err) { |
SetMovieDefaultDataRef(theMovie, hDataRef, HandleDataHandlerSubType); |
OpenADefaultComponent(MovieImportType, kQTFileTypeSystemSevenSound, &theImporter); |
if (theImporter) { |
Track ignoreTrack; |
TimeValue ignoreDuration; |
long ignoreFlags; |
err = MovieImportFile(theImporter, inFile, theMovie, 0, &ignoreTrack, 0, &ignoreDuration, 0, &ignoreFlags); |
CloseComponent(theImporter); |
} |
} else { |
if (*outHandle) { |
DisposeHandle(*outHandle); |
*outHandle = NULL; |
} |
} |
if (hDataRef) DisposeHandle(hDataRef); |
BailErr(err); |
} else { |
// import in place |
short theRefNum; |
short theResID = 0; // we want the first movie |
Boolean wasChanged; |
// open the movie file |
err = OpenMovieFile(inFile, &theRefNum, fsRdPerm); |
BailErr(err); |
// instantiate the movie |
err = NewMovieFromFile(&theMovie, theRefNum, &theResID, NULL, newMovieActive, &wasChanged); |
CloseMovieFile(theRefNum); |
BailErr(err); |
} |
// get the first sound track |
theTrack = GetMovieIndTrackType(theMovie, 1, SoundMediaType, movieTrackMediaType); |
if (NULL == theTrack ) BailErr(invalidTrack); |
// get and return the sound track media |
*outMedia = GetTrackMedia(theTrack); |
if (NULL == *outMedia) err = invalidMedia; |
bail: |
return err; |
} |
// * ---------------------------- |
// MyGetSoundDescriptionExtension |
// |
// this function will extract the information needed to decompress the sound file, this includes |
// retrieving the sample description, the decompression atom and setting up the sound header |
static OSErr MyGetSoundDescriptionExtension(Media inMedia, AudioFormatAtomPtr *outAudioAtom, CmpSoundHeaderPtr outSoundHeader) |
{ |
OSErr err = noErr; |
Size size; |
Handle extension; |
// Version 1 of this record includes four extra fields to store information about compression ratios. It also defines |
// how other extensions are added to the SoundDescription record. |
// All other additions to the SoundDescription record are made using QT atoms. That means one or more |
// atoms can be appended to the end of the SoundDescription record using the standard [size, type] |
// mechanism used throughout the QuickTime movie resource architecture. |
// http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm |
SoundDescriptionV1Handle sourceSoundDescription = (SoundDescriptionV1Handle)NewHandle(0); |
// get the description of the sample data |
GetMediaSampleDescription(inMedia, 1, (SampleDescriptionHandle)sourceSoundDescription); |
BailErr(GetMoviesError()); |
extension = NewHandle(0); |
// get the "magic" decompression atom |
// This extension to the SoundDescription information stores data specific to a given audio decompressor. |
// Some audio decompression algorithms require a set of out-of-stream values to configure the decompressor |
// which are stored in a siDecompressionParams atom. The contents of the siDecompressionParams atom are dependent |
// on the audio decompressor. |
err = GetSoundDescriptionExtension((SoundDescriptionHandle)sourceSoundDescription, &extension, siDecompressionParams); |
if (noErr == err) { |
size = GetHandleSize(extension); |
HLock(extension); |
*outAudioAtom = (AudioFormatAtom*)NewPtr(size); |
err = MemError(); |
// copy the atom data to our buffer... |
BlockMoveData(*extension, *outAudioAtom, size); |
HUnlock(extension); |
} else { |
// if it doesn't have an atom, that's ok |
err = noErr; |
} |
// set up our sound header |
outSoundHeader->format = (*sourceSoundDescription)->desc.dataFormat; |
outSoundHeader->numChannels = (*sourceSoundDescription)->desc.numChannels; |
outSoundHeader->sampleSize = (*sourceSoundDescription)->desc.sampleSize; |
outSoundHeader->sampleRate = (*sourceSoundDescription)->desc.sampleRate; |
DisposeHandle(extension); |
DisposeHandle((Handle)sourceSoundDescription); |
bail: |
return err; |
} |
// * ---------------------------- |
// PlaySound |
// |
// this function does the actual work of playing the sound file, it sets up the sound converter environment, allocates play buffers, |
// creates the sound channel and sends the appropriate sound commands to play the converted sound data |
OSErr PlaySound(const FSSpec *inFileToPlay) |
{ |
AudioCompressionAtomPtr theDecompressionAtom; |
SoundComponentData theInputFormat, |
theOutputFormat; |
SoundConverter mySoundConverter = NULL; |
CmpSoundHeader mySndHeader0, |
mySndHeader1; |
SCFillBufferData scFillBufferData = {NULL}; |
Media theSoundMedia = NULL; |
Ptr pSourceBuffer = NULL; |
Ptr pDecomBuffer0 = NULL, |
pDecomBuffer1 = NULL; |
Handle hSys7SoundData = NULL; |
Boolean isSoundDone = false; |
OSErr err = noErr; |
err = GetMovieMedia(inFileToPlay, &theSoundMedia, &hSys7SoundData); |
BailErr(err); |
err = MyGetSoundDescriptionExtension(theSoundMedia, (AudioFormatAtomPtr *)&theDecompressionAtom, &mySndHeader0); |
if (noErr == err) { |
// setup input/output format for sound converter |
theInputFormat.flags = 0; |
theInputFormat.format = mySndHeader0.format; |
theInputFormat.numChannels = mySndHeader0.numChannels; |
theInputFormat.sampleSize = mySndHeader0.sampleSize; |
theInputFormat.sampleRate = mySndHeader0. sampleRate; |
theInputFormat.sampleCount = 0; |
theInputFormat.buffer = NULL; |
theInputFormat.reserved = 0; |
theOutputFormat.flags = 0; |
theOutputFormat.format = kSoundNotCompressed; |
theOutputFormat.numChannels = theInputFormat.numChannels; |
theOutputFormat.sampleSize = theInputFormat.sampleSize; |
theOutputFormat.sampleRate = theInputFormat.sampleRate; |
theOutputFormat.sampleCount = 0; |
theOutputFormat.buffer = NULL; |
theOutputFormat.reserved = 0; |
err = SoundConverterOpen(&theInputFormat, &theOutputFormat, &mySoundConverter); |
BailErr(err); |
// set up the sound converters decompresson 'environment' by passing in the 'magic' decompression atom |
err = SoundConverterSetInfo(mySoundConverter, siDecompressionParams, theDecompressionAtom); |
if (siUnknownInfoType == err) { |
// clear this error, the decompressor didn't |
// need the decompression atom and that's OK |
err = noErr; |
} else BailErr(err); |
UInt32 targetBytes = 32768, |
outputBytes = 0; |
// find out how much buffer space to alocate for our output buffers |
// The differences between SoundConverterConvertBuffer begin with how the buffering is done. SoundConverterFillBuffer will do as much or as |
// little work as is required to satisfy a given request. This means that you can pass in buffers of any size you like and expect that |
// the Sound Converter will never overflow the output buffer. SoundConverterFillBufferDataProc function will be called as many times as |
// necessary to fulfill a request. This means that the SoundConverterFillBufferDataProc routine is free to provide data in whatever chunk size |
// it likes. Of course with both sides, the buffer sizes will control how many times you need to request data and there is a certain amount of |
// overhead for each call. You will want to balance this against the performance you require. While a call to SoundConverterGetBufferSizes is |
// not required by the SoundConverterFillBuffer function, it is useful as a guide for non-VBR formats |
do { |
UInt32 inputFrames, inputBytes; |
targetBytes *= 2; |
err = SoundConverterGetBufferSizes(mySoundConverter, targetBytes, &inputFrames, &inputBytes, &outputBytes); |
} while (notEnoughBufferSpace == err && targetBytes < (MaxBlock() / 4)); |
// allocate play buffers |
pDecomBuffer0 = NewPtr(outputBytes); |
BailErr(MemError()); |
pDecomBuffer1 = NewPtr(outputBytes); |
BailErr(MemError()); |
err = SoundConverterBeginConversion(mySoundConverter); |
BailErr(err); |
// setup first header |
mySndHeader0.samplePtr = pDecomBuffer0; |
mySndHeader0.numChannels = theOutputFormat.numChannels; |
mySndHeader0.sampleRate = theOutputFormat.sampleRate; |
mySndHeader0.loopStart = 0; |
mySndHeader0.loopEnd = 0; |
mySndHeader0.encode = cmpSH; // compressed sound header encode value |
mySndHeader0.baseFrequency = kMiddleC; |
// mySndHeader0.AIFFSampleRate; // this is not used |
mySndHeader0.markerChunk = NULL; |
mySndHeader0.format = theOutputFormat.format; |
mySndHeader0.futureUse2 = 0; |
mySndHeader0.stateVars = NULL; |
mySndHeader0.leftOverSamples = NULL; |
mySndHeader0.compressionID = fixedCompression; // compression ID for fixed-sized compression, even uncompressed sounds use fixedCompression |
mySndHeader0.packetSize = 0; // the Sound Manager will figure this out for us |
mySndHeader0.snthID = 0; |
mySndHeader0.sampleSize = theOutputFormat.sampleSize; |
mySndHeader0.sampleArea[0] = 0; // no samples here because we use samplePtr to point to our buffer instead |
// setup second header, only the buffer ptr is different |
BlockMoveData(&mySndHeader0, &mySndHeader1, sizeof(mySndHeader0)); |
mySndHeader1.samplePtr = pDecomBuffer1; |
// fill in struct that gets passed to SoundConverterFillBufferDataProc via the refcon |
// this includes the ExtendedSoundComponentData record |
scFillBufferData.sourceMedia = theSoundMedia; |
scFillBufferData.getMediaAtThisTime = 0; |
scFillBufferData.sourceDuration = GetMediaDuration(theSoundMedia); |
scFillBufferData.isThereMoreSource = true; |
scFillBufferData.maxBufferSize = kMaxInputBuffer; |
scFillBufferData.hSource = NewHandle((long)scFillBufferData.maxBufferSize); // allocate source media buffer |
BailErr(MemError()); |
scFillBufferData.compData.desc = theInputFormat; |
scFillBufferData.compData.desc.buffer = (Byte *)*scFillBufferData.hSource; |
scFillBufferData.compData.desc.flags = kExtendedSoundData; |
scFillBufferData.compData.recordSize = sizeof(ExtendedSoundComponentData); |
scFillBufferData.compData.extendedFlags = kExtendedSoundSampleCountNotValid | kExtendedSoundBufferSizeValid; |
scFillBufferData.compData.bufferSize = 0; |
// setup the callback, create the sound channel and play the sound |
// we will continue to convert the sound data into the free (non playing) buffer |
SndCallBackUPP theSoundCallBackUPP = NewSndCallBackUPP(MySoundCallBackFunction); |
SndChannelPtr pSoundChannel = NULL; |
err = SndNewChannel(&pSoundChannel, sampledSynth, 0, theSoundCallBackUPP); |
if (err == noErr) { |
SndCommand thePlayCmd0, |
thePlayCmd1, |
theCallBackCmd; |
SndCommand *pPlayCmd; |
Ptr pDecomBuffer = NULL; |
CmpSoundHeaderPtr pSndHeader = NULL; |
UInt32 outputFrames, |
actualOutputBytes, |
outputFlags; |
eBufferNumber whichBuffer = kFirstBuffer; |
SoundConverterFillBufferDataUPP theFillBufferDataUPP = NewSoundConverterFillBufferDataUPP(SoundConverterFillBufferDataProc); |
thePlayCmd0.cmd = bufferCmd; |
thePlayCmd0.param1 = 0; // not used, but clear it out anyway just to be safe |
thePlayCmd0.param2 = (long)&mySndHeader0; |
thePlayCmd1.cmd = bufferCmd; |
thePlayCmd1.param1 = 0; // not used, but clear it out anyway just to be safe |
thePlayCmd1.param2 = (long)&mySndHeader1; |
whichBuffer = kFirstBuffer; // buffer 1 will be free when callback runs |
theCallBackCmd.cmd = callBackCmd; |
theCallBackCmd.param2 = SetCurrentA5(); |
gBufferDone = true; |
if (noErr == err) { |
while (!isSoundDone && !Button()) { |
if (gBufferDone == true) { |
if (kFirstBuffer == whichBuffer) { |
pPlayCmd = &thePlayCmd0; |
pDecomBuffer = pDecomBuffer0; |
pSndHeader = &mySndHeader0; |
whichBuffer = kSecondBuffer; |
} else { |
pPlayCmd = &thePlayCmd1; |
pDecomBuffer = pDecomBuffer1; |
pSndHeader = &mySndHeader1; |
whichBuffer = kFirstBuffer; |
} |
err = SoundConverterFillBuffer(mySoundConverter, // a sound converter |
theFillBufferDataUPP, // the callback UPP |
&scFillBufferData, // refCon passed to FillDataProc |
pDecomBuffer, // the decompressed data 'play' buffer |
outputBytes, // size of the 'play' buffer |
&actualOutputBytes, // number of output bytes |
&outputFrames, // number of output frames |
&outputFlags); // fillbuffer retured advisor flags |
if (err) break; |
if((outputFlags & kSoundConverterHasLeftOverData) == false) { |
isSoundDone = true; |
} |
pSndHeader->numFrames = outputFrames; |
gBufferDone = false; |
if (!isSoundDone) { |
SndDoCommand(pSoundChannel, &theCallBackCmd, true); // reuse callBackCmd |
} |
SndDoCommand(pSoundChannel, pPlayCmd, true); // play the next buffer |
} |
} // while |
} |
SoundConverterEndConversion(mySoundConverter, pDecomBuffer, &outputFrames, &outputBytes); |
if (noErr == err && outputFrames) { |
pSndHeader->numFrames = outputFrames; |
SndDoCommand(pSoundChannel, pPlayCmd, true); // play the last buffer. |
} |
if (theFillBufferDataUPP) |
DisposeSoundConverterFillBufferDataUPP(theFillBufferDataUPP); |
} |
if (pSoundChannel) |
err = SndDisposeChannel(pSoundChannel, false); // wait until sounds stops playing before disposing of channel |
if (theSoundCallBackUPP) |
DisposeSndCallBackUPP(theSoundCallBackUPP); |
} |
bail: |
if (scFillBufferData.hSource) |
DisposeHandle(scFillBufferData.hSource); |
if (mySoundConverter) |
SoundConverterClose(mySoundConverter); |
if (pDecomBuffer0) |
DisposePtr(pDecomBuffer0); |
if (pDecomBuffer1) |
DisposePtr(pDecomBuffer1); |
if (hSys7SoundData) |
DisposeHandle(hSys7SoundData); |
return err; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14