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.
PlaySoundConvertBuffer.c
/* |
File: PlaySoundConvertBuffer.c |
Description: Play Sound shows how to use the Sound Description Extention atom information with the |
SoundConverter APIs to play non VBR MP3 files as well as other types of sound files |
using various encoding methods. |
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> 7/26/00 sans hickup and carbonized for CarbonLib 1.04 |
<1> 4/01/00 initial release |
*/ |
#include "MP3Player.h" |
// globals |
Boolean gBufferDone = false; |
// * ---------------------------- |
// MySoundCallBackFunction |
// |
// used to signal when a buffer is done playing |
static pascal void MySoundCallBackFunction(SndChannelPtr theChannel, SndCommand *theCmd); |
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 |
} |
// * ---------------------------- |
// MyGetSoundDescriptionExtension |
// |
// this function will extract the information needed to decompress the sound file, this includes retrieving the sample description, |
// the decompression atom, setting up the sound header, copying the sample data into a sample buffer and calculating it's length |
static OSErr MyGetSoundDescriptionExtension(const FSSpec *inMP3file, AudioFormatAtomPtr *outAudioAtom, CmpSoundHeaderPtr outSoundHeader, Handle outSampleBuf, UInt32 *outLength); |
static OSErr MyGetSoundDescriptionExtension(const FSSpec *inMP3file, AudioFormatAtomPtr *outAudioAtom, CmpSoundHeaderPtr outSoundHeader, Handle outSampleBuf, UInt32 *outLength) |
{ |
Movie theMovie; |
Track theTrack; |
Media theMedia; |
short theRefNum; |
short theResID = 0; // we want the first movie |
Boolean wasChanged; |
OSErr err = noErr; |
// open the movie file |
err = OpenMovieFile(inMP3file, &theRefNum, fsRdPerm); |
BailErr(err); |
// instantiate the movie |
err = NewMovieFromFile(&theMovie, theRefNum, &theResID, NULL, newMovieActive, &wasChanged); |
BailErr(err); |
CloseMovieFile(theRefNum); |
theRefNum = 0; |
// get the first sound track |
theTrack = GetMovieIndTrackType(theMovie, 1, SoundMediaType, movieTrackMediaType); |
if (theTrack != NULL) { |
// get the sound track media |
theMedia = GetTrackMedia(theTrack); |
if (theMedia != NULL) { |
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(theMedia, 1, (SampleDescriptionHandle)sourceSoundDescription); |
err = 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; |
if (noErr == err) { |
// copy the sample data into our buffer |
TimeValue trackDuration = GetTrackDuration(theTrack); |
TimeValue startTime = 0; |
long numMediaSamples = GetMediaSampleCount(theMedia); |
long numberOfSamples; |
Handle hTemp = NewHandle(0); |
do { |
err = GetMediaSample(theMedia, hTemp, 0L, NULL, startTime, NULL, 0, NULL, NULL, numMediaSamples, &numberOfSamples, NULL); |
startTime += numberOfSamples; |
HandAndHand(hTemp, outSampleBuf); |
} while (startTime < numMediaSamples && noErr == err ); |
DisposeHandle(hTemp); |
// calculate the duration of the data |
// although we should be able to use the samplesPerPacket value returned in the SoundDescriptionV1 record to see if we're dealing with multiple samples per packet |
// as per the documentation, and I quote "If these fields are not used, they are set to 0. File readers only need to check to see if samplesPerPacket is 0." |
// in the case when a sound track is exported as a .mov using for example µLaw 2:1 compression these fields are filed with 'spore' aka 'grunge' so... |
// use GetCompressionInfo and check samplesPerPacket NOTE: Although better than nothing this will not always work, for example a .mov file with a SoundTrack |
// encoded using MACE where samples per packet will be greater than 1 but the information in the SoundDescriptionV1 record will be wrong |
CompressionInfo compInfo; |
err = GetCompressionInfo(fixedCompression, (*sourceSoundDescription)->desc.dataFormat, (*sourceSoundDescription)->desc.numChannels, (*sourceSoundDescription)->desc.sampleSize, &compInfo); |
// numFrames = samples / samplesPerPacket |
// duration = numFrames * bytesPerFrame |
if (compInfo.samplesPerPacket == 1) { |
*outLength = (((numMediaSamples / compInfo.samplesPerPacket) * compInfo.bytesPerFrame)); |
} else { |
*outLength = (((numMediaSamples / (*sourceSoundDescription)->samplesPerPacket) * (*sourceSoundDescription)->bytesPerFrame)); |
} |
} |
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) |
{ |
Handle hSoundData = NULL; |
UInt32 theLength = 0; |
AudioCompressionAtomPtr theDecompressionAtom; |
SoundComponentData theInputFormat, |
theOutputFormat; |
SoundConverter mySoundConverter = NULL; |
CmpSoundHeader mySndHeader0, |
mySndHeader1; |
Ptr pSourceBuffer = NULL; |
Ptr pDecomBuffer0 = NULL, |
pDecomBuffer1 = NULL; |
Boolean isSoundDone = false; |
FInfo fndrInfo; |
OSErr err = noErr; |
err = FSpGetFInfo(inFileToPlay, &fndrInfo); |
BailErr(err); |
hSoundData = NewHandle(0); |
if (hSoundData == NULL || MemError()) goto bail; |
// get what we need to do what we need to do |
err = MyGetSoundDescriptionExtension(inFileToPlay, (AudioFormatAtomPtr *)&theDecompressionAtom, &mySndHeader0, hSoundData, &theLength); |
if (noErr == err) { |
HLock(hSoundData); |
pSourceBuffer = *hSoundData; // source buffer |
// 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, |
inputFrames = 0, |
inputBytes = 0, |
outputFrames = 0, |
outputBytes = 0, |
actualOutputBytes = 0, |
bytesConverted = 0, |
bytesPerFrame = 0; |
// find out how much buffer space to alocate for our output buffers |
do { |
targetBytes *= 2; |
err = SoundConverterGetBufferSizes(mySoundConverter, targetBytes, &inputFrames, &inputBytes, &outputBytes); |
} while (notEnoughBufferSpace == err && targetBytes < (MaxBlock() / 4)); |
bytesPerFrame = inputBytes / inputFrames; |
pDecomBuffer0 = NewPtr(outputBytes + kOverflowRoom); |
BailErr(MemError();); |
pDecomBuffer1 = NewPtr(outputBytes + kOverflowRoom); |
BailErr(MemError();); |
// convert two buffers of sound before we begin to do anything else |
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; |
bytesConverted = 0; |
err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer, inputFrames, pDecomBuffer0, &outputFrames, &actualOutputBytes); |
BailErr(err); |
if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer"); |
bytesConverted += inputBytes; |
mySndHeader0.numFrames = outputFrames; |
if (bytesConverted > theLength) { |
isSoundDone = true; |
inputBytes = 0; |
inputFrames = 0; |
} else if (bytesConverted + inputBytes > theLength) { |
isSoundDone = true; |
inputBytes = theLength - bytesConverted; |
inputFrames = inputBytes / bytesPerFrame; |
} |
err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer + bytesConverted, inputFrames, pDecomBuffer1, &outputFrames, &actualOutputBytes); |
BailErr(err); |
if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer"); |
bytesConverted += inputBytes; |
mySndHeader1.numFrames = outputFrames; |
// 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; |
eBufferNumber whichBuffer = kFirstBuffer; |
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 = false; |
err = SndDoCommand(pSoundChannel, &thePlayCmd0, true); |
if (noErr == err) { |
err = SndDoCommand(pSoundChannel, &theCallBackCmd, true); |
} |
if (noErr == err) { |
err = SndDoCommand(pSoundChannel, &thePlayCmd1, true); |
} |
Ptr pDecomBuffer = NULL; |
CmpSoundHeaderPtr pSndHeader = NULL; |
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; |
} |
if (bytesConverted < theLength) { |
if (bytesConverted + inputBytes >= theLength) { |
isSoundDone = true; |
inputBytes = theLength - bytesConverted; |
inputFrames = inputBytes / bytesPerFrame; |
} |
err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer + bytesConverted, inputFrames, pDecomBuffer, &outputFrames, &actualOutputBytes); |
if (err) break; |
if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer"); |
bytesConverted += inputBytes; |
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 (pSoundChannel) |
err = SndDisposeChannel(pSoundChannel, false); // wait until sounds stops playing before disposing of channel |
if (theSoundCallBackUPP) |
DisposeSndCallBackUPP(theSoundCallBackUPP); |
} |
bail: |
if (mySoundConverter) |
SoundConverterClose(mySoundConverter); |
if (pDecomBuffer0) |
DisposePtr(pDecomBuffer0); |
if (pDecomBuffer1) |
DisposePtr(pDecomBuffer1); |
if (hSoundData) |
DisposeHandle(hSoundData); |
return err; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14