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.
Relevant replacement documents include:
ConvertMovieSndTrack.c
/* |
File: ConvertMovieSndTrack.c |
Description: This sample demonstrates how to use the Sound Converter to transcode a Movies Sound Track |
from one audio encoding format to another. It uses StdCompression to configure the audio |
compressor and creates a QuickTime Movie containing the newly encoded Sound Track. |
This sample also demonstrates the preferred way to use the Sound Converter - using |
SoundConverterFillBuffer - and shows what you need to do to support the new VBR compression |
formats - in QuickTime 6, an example is the AAC format, part of MPEG4 audio support. |
This sample will also work with any audio file format QuickTime can import as a Movie |
such as AIFF, WAVE and MP3. |
NOTE: This Sample Requires QuickTime 6.0 or greater and also requires Mac OS X. There is |
no CFM target provided in this sample. |
Parts of this code were based on testing code written by baron, and other parts first |
appeared in MP3Player and now appear in it's updated version SoundPlayer. |
Author: era |
Version 1.0 |
Copyright: © Copyright 2002 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): <1> 7/28/02 era initial release as ConvertMovieSndTrack.c |
*/ |
#include "ConvertMovieSndTrack.h" |
#ifndef AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER |
// these didn't make it into the QT6 framework for 10.1.x so include |
// them here if we're not on 10.2 or later - if you have a newer framework |
// or are building a carbon CFM version you shouldn't need these |
enum { |
scSoundVBRCompressionOK = 'cvbr', /* pointer to Boolean*/ |
scSoundInputSampleRateType = 'ssir', /* pointer to UnsignedFixed*/ |
scSoundSampleRateChangeOK = 'rcok', /* pointer to Boolean*/ |
scAvailableCompressionListType = 'avai' /* pointer to OSType Handle*/ |
}; |
#endif |
// globals |
QTAtomContainer gSoundSettings = NULL; |
// * ---------------------------- |
// 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; |
// 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; |
pFillData->compData.commonFrameSize = 0; |
} |
if (pFillData->isThereMoreSource) { |
long sourceBytesReturned; |
long numberOfSamples; |
TimeValue sourceReturnedTime, durationPerSample; |
// in calling GetMediaSample, we'll get a buffer that consists of equal sized frames - the |
// degenerate case is only 1 frame -- for non-self-framed vbr formats (like AAC in QT 6) |
// we need to provide some more framing information - either the frameCount, frameSizeArray pair or |
// the commonFrameSize field must be valid -- because we always get equal sized frames, we can use |
// commonFrameSize and set the kExtendedSoundCommonFrameSizeValid flag -- if there is |
// only 1 frame then (common frame size == media sample size), if there are multiple frames, |
// then (common frame size == media sample size / number of frames). |
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) |
if ((noErr != err) || (sourceBytesReturned == 0)) { |
pFillData->isThereMoreSource = false; |
pFillData->compData.desc.buffer = NULL; |
pFillData->compData.desc.sampleCount = 0; |
pFillData->compData.bufferSize = 0; |
pFillData->compData.commonFrameSize = 0; |
if ((err != noErr) && (sourceBytesReturned > 0)) |
DebugStr("\pGetMediaSample - Failed in FillBufferDataProc"); |
} |
pFillData->getMediaAtThisTime = sourceReturnedTime + (durationPerSample * numberOfSamples); |
// sampleCount is the number of PCM samples |
pFillData->compData.desc.sampleCount = numberOfSamples * durationPerSample; |
// kExtendedSoundBufferSizeValid was specified - make sure this field is filled in correctly |
pFillData->compData.bufferSize = sourceBytesReturned; |
// for VBR audio we specified the kExtendedSoundCommonFrameSizeValid flag - make sure this field is filled in correctly |
if (pFillData->isSourceVBR) pFillData->compData.commonFrameSize = sourceBytesReturned / numberOfSamples; |
} |
// 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, Movie *outMovie, 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; |
*outMovie = theMovie; |
bail: |
return err; |
} |
// * ---------------------------- |
// GetSoundDescriptionInfo |
// |
// this function will extract the information needed to decompress the sound file, this includes |
// retrieving the sample description and the decompression atom saved as a Sample Description Extention |
static OSErr GetSoundDescriptionInfo(Media inMedia, Ptr *outAudioAtom, SoundDescriptionPtr outSoundDesc) |
{ |
OSErr err = noErr; |
Size size; |
Handle extension = NULL; |
SoundDescriptionHandle hSoundDescription = (SoundDescriptionHandle)NewHandle(0); |
// get the description of the sample data |
GetMediaSampleDescription(inMedia, 1, (SampleDescriptionHandle)hSoundDescription); |
BailErr(GetMoviesError()); |
extension = NewHandle(0); |
BailErr(MemError()); |
// 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 sound decompressor. |
err = GetSoundDescriptionExtension(hSoundDescription, &extension, siDecompressionParams); |
if (noErr == err) { |
size = GetHandleSize(extension); |
HLock(extension); |
*outAudioAtom = NewPtr(size); |
BailErr(MemError()); |
// copy the atom data to our Ptr... |
BlockMoveData(*extension, *outAudioAtom, size); |
HUnlock(extension); |
} else { |
// if it doesn't have an atom, that's ok |
err = noErr; |
} |
// set up our sound header |
outSoundDesc->dataFormat = (*hSoundDescription)->dataFormat; |
outSoundDesc->numChannels = (*hSoundDescription)->numChannels; |
outSoundDesc->sampleSize = (*hSoundDescription)->sampleSize; |
outSoundDesc->sampleRate = (*hSoundDescription)->sampleRate; |
outSoundDesc->compressionID = (*hSoundDescription)->compressionID; |
bail: |
if (extension) DisposeHandle(extension); |
if (hSoundDescription) DisposeHandle((Handle)hSoundDescription); |
return err; |
} |
// * ---------------------------- |
// ConvertMovieSndTrackToNewMovieSndTrack |
// |
// this function does the actual work |
OSErr ConvertMovieSndTrackToNewMovieSndTrack(const FSSpecPtr inFileToConvert, const FSSpec inNewFile) |
{ |
SoundConverter mySoundConverter = NULL; |
Movie theSrcMovie = 0, |
theDstMovie = 0; |
Track theDstTrack = 0; |
Media theSrcMedia = 0, |
theDstMedia = 0; |
short theDstMovieRefNum = -1; |
Handle hSys7SoundData = NULL; |
Ptr theDecompressionParams = NULL; |
Handle theCompressionParams = NULL; |
SoundDescription theSrcInputFormatInfo; |
SoundDescriptionV1Handle hSoundDescription = NULL; |
UnsignedFixed theOutputSampleRate; |
SoundComponentData theInputFormat, |
theOutputFormat; |
SCFillBufferData scFillBufferData = { NULL }; |
Ptr pDecomBuffer = NULL; |
Boolean isSoundDone = false; |
OSErr err = noErr; |
// *********** SOURCE: Get sound data info from the first source movie sound track |
err = GetMovieMedia(inFileToConvert, &theSrcMovie, &theSrcMedia, &hSys7SoundData); |
BailErr(err); |
err = GetSoundDescriptionInfo(theSrcMedia, (Ptr *)&theDecompressionParams, &theSrcInputFormatInfo); |
if (noErr == err) { |
// setup input format for sound converter |
theInputFormat.flags = 0; |
theInputFormat.format = theSrcInputFormatInfo.dataFormat; |
theInputFormat.numChannels = theSrcInputFormatInfo.numChannels; |
theInputFormat.sampleSize = theSrcInputFormatInfo.sampleSize; |
theInputFormat.sampleRate = theSrcInputFormatInfo. sampleRate; |
theInputFormat.sampleCount = 0; |
theInputFormat.buffer = NULL; |
theInputFormat.reserved = 0; |
// *********** DESTINATION: Set up the destination format |
// Standard Sound Compression Dialog will configure output settings goodness for us |
ComponentInstance ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubTypeSound); |
Boolean trueBoolean = true; |
// both of the following selectors must be set true for VBR audio compressors like MPEG-4 to shows up in the codec list |
// inform StdCompression we know how to deal with VBR |
SCSetInfo(ci, scSoundVBRCompressionOK, &trueBoolean); |
// inform StdCompression that we know the output sample rate might be changed by the audio codec |
// MPEG4 for example may change the output sample rate if the user selects a low bit rate |
SCSetInfo(ci, scSoundSampleRateChangeOK, &trueBoolean); |
// restore the previous settings if any |
// first, try to grab them from our users preferences - if they're not there that's fine |
GetSoundSettingsPreference(); |
if (gSoundSettings) { |
SCSetSettingsFromAtomContainer(ci, gSoundSettings); |
// Dispose the settings we don't need them anymore |
DisposeHandle(gSoundSettings); |
gSoundSettings = NULL; |
} |
// request the settings ie. bring up the dialog |
err = SCRequestImageSettings(ci); |
if (userCanceledErr == err) { err = noErr; CloseComponent(ci); goto bail; } |
// save the settings for next time |
SCGetSettingsAsAtomContainer(ci, &gSoundSettings); |
SaveSoundSettingsPreference(); |
// fill in our output format for the sound converter |
SCGetInfo(ci, scSoundCompressionType, &theOutputFormat.format); |
// If StdCompression returns 'raw ' (k8BitOffsetBinaryFormat), it really means "uncompressed". You should |
// change 'raw ' to 'NONE', or SoundConverterOpen will return -2003 (cantFindHandler). |
if (theOutputFormat.format == k8BitOffsetBinaryFormat) |
theOutputFormat.format = kSoundNotCompressed; |
SCGetInfo(ci, scSoundChannelCountType, &theOutputFormat.numChannels); |
SCGetInfo(ci, scSoundSampleSizeType, &theOutputFormat.sampleSize); |
// InputSampleRate can be different than the SampleRate so make sure to set up the output format |
// sample rate correctly for the sound converter - see "What's new in QuickTime 6", Table 1 page 38 - |
// few codecs (but some) implement the InputSampleRate selector so make sure to check for an invalid |
// return value and if so get the output sample rate |
SCGetInfo(ci, scSoundInputSampleRateType, &theOutputFormat.sampleRate); |
if (theOutputFormat.sampleRate == 0) |
SCGetInfo(ci, scSoundSampleRateType, &theOutputFormat.sampleRate); |
// we need to get the appropriate codec settings for the compressor |
// these 'magic' settings need to be added to the Sound Description |
SCGetInfo(ci, scCodecSettingsType, &theCompressionParams); |
theOutputFormat.flags = kNoRealtimeProcessing; // not anxious about how long it takes to encode |
theOutputFormat.sampleCount = 0; |
theOutputFormat.buffer = NULL; |
theOutputFormat.reserved = 0; |
// always remember to close any opened components when you're done with them |
CloseComponent(ci); |
// *********** SOUND CONVERTER: Open converter and prepare for buffer conversion...captain! |
err = SoundConverterOpen(&theInputFormat, &theOutputFormat, &mySoundConverter); |
BailErr(err); |
// tell the sound converter we're cool with VBR formats |
SoundConverterSetInfo(mySoundConverter, siClientAcceptsVBR, Ptr(true)); |
// set up the sound converters compression environment |
// pass down siCompressionSampleRate, siCompressionChannels then siCompressionParams |
SoundConverterSetInfo(mySoundConverter, siCompressionSampleRate, &theOutputFormat.sampleRate); // ignore errors |
SoundConverterSetInfo(mySoundConverter, siCompressionChannels, &theOutputFormat.numChannels); |
// set up the compression environment by passing in the 'magic' compression params aquired from |
// standard sound compression eariler |
if (theCompressionParams) { |
HLock(theCompressionParams); |
err = SoundConverterSetInfo(mySoundConverter, siCompressionParams, *theCompressionParams); |
BailErr(err); |
HUnlock(theCompressionParams); |
} |
// set up the decompresson environment by passing in the 'magic' decompression params |
if (theDecompressionParams) { |
// don't check for an error, if the decompressor didn't need the |
// decompression atom for whatever reason we should still be ok |
SoundConverterSetInfo(mySoundConverter, siDecompressionParams, theDecompressionParams); |
} |
// we need to know if the output sample rate was changed so we can write it in the image description |
// few codecs (but some) will implement this - MPEG4 for example may change the output sample rate if |
// the user selects a low bit rate - ignore errors |
theOutputSampleRate = theOutputFormat.sampleRate; |
SoundConverterGetInfo(mySoundConverter, siCompressionOutputSampleRate, &theOutputSampleRate); |
err = SoundConverterBeginConversion(mySoundConverter); |
BailErr(err); |
// *********** DESTINATION MOVIE: Create Movie, Add Sound Track, New Media, Setup Sound Description |
// and Add Sound Description Extenstion if present |
// create the movie file |
err = CreateMovieFile(&inNewFile, 'TVOD', smSystemScript, |
createMovieFileDeleteCurFile | createMovieFileDontCreateResFile, &theDstMovieRefNum, &theDstMovie); |
BailErr(err); |
// create a new track |
theDstTrack = NewMovieTrack(theDstMovie, 0, 0, kFullVolume); |
BailErr(GetMoviesError()); |
// it's a sound track so add a SoundMedia |
// this is the sample rate we got back from our attempt to use siCompressionOutputSampleRate above |
theDstMedia = NewTrackMedia(theDstTrack, SoundMediaType, (theOutputSampleRate >> 16), NULL, 0); |
BailErr(GetMoviesError()); |
// start media editing session |
err = BeginMediaEdits(theDstMedia); |
BailErr(err); |
// we need to get info about data/frame sizes |
// good practice to fill in the size of this structure |
CompressionInfo compressionFactor = { sizeof(compressionFactor), 0 }; |
hSoundDescription = (SoundDescriptionV1Handle)NewHandleClear(sizeof(SoundDescriptionV1)); |
BailErr(MemError()); |
err = SoundConverterGetInfo(mySoundConverter, siCompressionFactor, &compressionFactor); |
BailErr(err); |
HLock((Handle)hSoundDescription); |
(*hSoundDescription)->desc.descSize = sizeof(SoundDescriptionV1); |
(*hSoundDescription)->desc.dataFormat = (long)theOutputFormat.format; // compression format |
(*hSoundDescription)->desc.resvd1 = 0; // must be 0 |
(*hSoundDescription)->desc.resvd2 = 0; // must be 0 |
(*hSoundDescription)->desc.dataRefIndex = 0; // 0 - we'll let AddMediaXXX determine the index |
(*hSoundDescription)->desc.version = 1; // set to 1 |
(*hSoundDescription)->desc.revlevel = 0; // set to 0 |
(*hSoundDescription)->desc.vendor = 0; |
(*hSoundDescription)->desc.numChannels = theOutputFormat.numChannels; // number of channels |
(*hSoundDescription)->desc.sampleSize = theOutputFormat.sampleSize; // bits per sample - everything but 8 bit can be set to 16 |
(*hSoundDescription)->desc.compressionID = compressionFactor.compressionID; // the compression ID (eg. variableCompression) |
(*hSoundDescription)->desc.packetSize = 0; // set to 0 |
(*hSoundDescription)->desc.sampleRate = theOutputSampleRate; // the sample rate |
// version 1 stuff |
(*hSoundDescription)->samplesPerPacket = compressionFactor.samplesPerPacket; // the samples per packet holds the PCM sample count per audio frame (packet) |
(*hSoundDescription)->bytesPerPacket = compressionFactor.bytesPerPacket; // the bytes per packet |
// bytesPerFrame isn't necessarily calculated for us and returned as part of the CompressionFactor - not all codecs that |
// implement siCompressionFactor fill out bytesPerFrame - so we do it here - note that VBR doesn't deserve this treatment |
// but it's not harmful, the Sound Manager would do calculations itself as part of GetCompressionInfo() |
// It should be noted that GetCompressionInfo() doesn't work for codecs that need configuration with 'magic' settings. |
// This requires explicit opening of the codec and the siCompressionFactor selector for SoundComponentGetInfo() |
(*hSoundDescription)->bytesPerFrame = compressionFactor.bytesPerPacket * theOutputFormat.numChannels; |
(*hSoundDescription)->bytesPerSample = compressionFactor.bytesPerSample; |
// the theCompressionParams are not necessarily present |
if (theCompressionParams) { |
// a Sound Description can't be locked when calling AddSoundDescriptionExtension so make sure it's unlocked |
HUnlock((Handle)hSoundDescription); |
err = AddSoundDescriptionExtension((SoundDescriptionHandle)hSoundDescription, theCompressionParams, siDecompressionParams); |
BailErr(err); |
HLock((Handle)hSoundDescription); |
} |
// VBR implies a different media layout, this will affect how AddMediaSample() is called below |
Boolean outputFormatIsVBR = ((*hSoundDescription)->desc.compressionID == variableCompression); |
// *********** SOUND CONVERTER: Create buffers and Convert Data |
// figure out sizes for the input and output buffers |
// the input buffer has to be large enough so GetMediaSample isn't going to fail |
// start with some rough numbers which should work well |
UInt32 inputBytes = ((1000 + (theInputFormat.sampleRate >> 16)) * theInputFormat.numChannels) * 4, |
outputBytes = 0, |
maxPacketSize = 0; |
// ask about maximum packet size (or worst case packet size) so we don't allocate a destination (output) |
// buffer that's too small - an output buffer smaller than MaxPacketSize would be really bad - init maxPacketSize |
// to 0 so if the request isn't understood we can create a number (some multiple of maxPacketSize) and go from there |
// this is likely only implemented by VBR codecs so don't get anxious about it not being implemented |
SoundConverterGetInfo(mySoundConverter, siCompressionMaxPacketSize, &maxPacketSize); |
// start with this - you don't really need to use GetBufferSizes just as long as the output buffer is larger than |
// the MaxPacketSize if implemented - we use kMaxBufferSize which is 64k as a minimum |
SoundConverterGetBufferSizes(mySoundConverter, kMaxBufferSize, NULL, NULL, &outputBytes); |
if (0 == maxPacketSize) |
maxPacketSize = kMaxBufferSize; // kMaxBufferSize is 64k |
if (inputBytes < kMaxBufferSize) // kMaxBufferSize is 64k |
inputBytes = kMaxBufferSize; // note this is still too small for DV (NTSC=120000, PAL=144000) |
if (outputBytes < maxPacketSize) |
outputBytes = maxPacketSize; |
// allocate conversion buffer |
pDecomBuffer = NewPtr(outputBytes); |
BailErr(MemError()); |
// fill in struct that gets passed to SoundConverterFillBufferDataProc via the refcon |
// this includes the ExtendedSoundComponentData information |
scFillBufferData.sourceMedia = theSrcMedia; |
scFillBufferData.getMediaAtThisTime = 0; |
scFillBufferData.sourceDuration = GetMediaDuration(theSrcMedia); |
scFillBufferData.isThereMoreSource = true; |
scFillBufferData.maxBufferSize = inputBytes; |
// if the source is VBR it means we're going to set the kExtendedSoundCommonFrameSizeValid |
// flag and use the commonFrameSize field in the FillBuffer callback |
scFillBufferData.isSourceVBR = (theSrcInputFormatInfo.compressionID == variableCompression); |
scFillBufferData.hSource = NewHandle((long)scFillBufferData.maxBufferSize); // allocate source media buffer |
BailErr(MemError()); |
HLockHi((Handle)scFillBufferData.hSource); |
scFillBufferData.compData.desc = theInputFormat; |
scFillBufferData.compData.desc.buffer = (BytePtr)*scFillBufferData.hSource; |
scFillBufferData.compData.desc.flags = kExtendedSoundData; |
scFillBufferData.compData.recordSize = sizeof(ExtendedSoundComponentData); |
scFillBufferData.compData.extendedFlags = kExtendedSoundBufferSizeValid; |
if (scFillBufferData.isSourceVBR) scFillBufferData.compData.extendedFlags |= kExtendedSoundCommonFrameSizeValid; |
scFillBufferData.compData.bufferSize = 0; // filled in during FillBuffer callback |
if (err == noErr) { |
UInt32 outputFrames, |
actualOutputBytes, |
outputFlags, |
durationPerMediaSample, |
numberOfMediaSamples; |
SoundConverterFillBufferDataUPP theFillBufferDataUPP = NewSoundConverterFillBufferDataUPP(SoundConverterFillBufferDataProc); |
while (!isSoundDone) { |
err = SoundConverterFillBuffer(mySoundConverter, // a sound converter |
theFillBufferDataUPP, // the callback UPP |
&scFillBufferData, // refCon passed to FillDataProc |
pDecomBuffer, // the destination data buffer |
outputBytes, // size of the destination 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; |
} |
// see if output buffer is filled so we can write some data |
if (actualOutputBytes > 0) { |
// so, what are we going to pass to AddMediaSample? |
// |
// for variableCompression, a media sample == an audio packet (compressed), this is also true for uncompressed audio |
// for fixedCompression, a media sample is a portion of an audio packet - it is 1 / compInfo.samplesPerPacket worth |
// of data, there's no way to access just a portion of the samples |
// therefore, we need to know if our compression format is VBR or Fixed and make the correct calculations for |
// either VBR or not - Fixed and uncompressed are treated the same |
if (outputFormatIsVBR) { |
numberOfMediaSamples = outputFrames; |
durationPerMediaSample = compressionFactor.samplesPerPacket; |
} else { |
numberOfMediaSamples = outputFrames * compressionFactor.samplesPerPacket; |
durationPerMediaSample = 1; |
} |
err = AddMediaSample(theDstMedia, |
&pDecomBuffer, |
0, |
actualOutputBytes, |
durationPerMediaSample, |
(SampleDescriptionHandle)hSoundDescription, |
(long)numberOfMediaSamples, |
0, NULL); |
if (err) break; |
} |
} // while |
SoundConverterEndConversion(mySoundConverter, pDecomBuffer, &outputFrames, &actualOutputBytes); |
// if there's any left over data write it out |
if (noErr == err && actualOutputBytes > 0) { |
// see above comments regarding these calculations |
if (outputFormatIsVBR) { |
numberOfMediaSamples = outputFrames; |
durationPerMediaSample = compressionFactor.samplesPerPacket; |
} else { |
numberOfMediaSamples = outputFrames * compressionFactor.samplesPerPacket; |
durationPerMediaSample = 1; |
} |
err = AddMediaSample(theDstMedia, |
&pDecomBuffer, |
0, |
actualOutputBytes, |
durationPerMediaSample, |
(SampleDescriptionHandle)hSoundDescription, |
(long)numberOfMediaSamples, |
0, NULL); |
BailErr(err); |
} |
if (theFillBufferDataUPP) |
DisposeSoundConverterFillBufferDataUPP(theFillBufferDataUPP); |
} |
// *********** DESTINATION MOVIE: All media has been added so clean up |
err = EndMediaEdits(theDstMedia); |
BailErr(err); |
err = InsertMediaIntoTrack(theDstTrack, 0, 0, GetMediaDuration(theDstMedia), fixed1); |
BailErr(err); |
AddUserDataTextToMovie(theDstMovie, "ConvertMovieSndTrack", kUserDataTextEncodedBy); |
if (theOutputFormat.format == FOUR_CHAR_CODE('mp4a')) |
AddUserDataTextToMovie(theDstMovie, "QuickTime 6.0 or greater", kUserDataTextSpecialPlaybackRequirements); |
err = AddMovieResource(theDstMovie, theDstMovieRefNum, NULL, NULL); |
BailErr(err); |
} |
DoAlert("\pConvert Movie Sound Track Done!", err); |
bail: |
if (mySoundConverter) |
SoundConverterClose(mySoundConverter); |
if (theDstMovieRefNum != -1) |
CloseMovieFile(theDstMovieRefNum); |
if (scFillBufferData.hSource) |
DisposeHandle(scFillBufferData.hSource); |
if (pDecomBuffer) |
DisposePtr(pDecomBuffer); |
if (theCompressionParams) |
DisposeHandle(theCompressionParams); |
if (theDecompressionParams) |
DisposePtr((Ptr)theDecompressionParams); |
if (hSoundDescription) |
DisposeHandle((Handle)hSoundDescription); |
if (theSrcMovie) |
DisposeMovie(theSrcMovie); |
if (theDstMovie) |
DisposeMovie(theDstMovie); |
if (hSys7SoundData) |
DisposeHandle(hSys7SoundData); |
return err; |
} |
// * ---------------------------- |
// some utility functions we use |
// add user data to a movie |
OSErr AddUserDataTextToMovie(Movie inMovie, char inText[], OSType inType) |
{ |
UserData userData; |
Handle h = NULL; |
long length = strlen(inText); |
OSErr err = noErr; |
userData = GetMovieUserData(inMovie); |
if (userData == NULL) return paramErr; |
err = PtrToHand(inText, &h, length); |
if (noErr == err) { |
err = AddUserDataText(userData, h, inType, 1, langEnglish); |
DisposeHandle(h); |
} |
return err; |
} |
// some very basic functions for getting and saving the StdCompression codec settings |
// as a user preference, we use a global settings handle because it's easy |
OSErr GetSoundSettingsPreference(void) |
{ |
CFStringRef codecSoundSettingsKey = CFSTR("codecSoundSettings"); |
CFPropertyListRef theSoundSettings; |
OSErr err = noErr; |
// already have our settings so return |
if (gSoundSettings) return err; |
// Read the preference |
theSoundSettings = CFPreferencesCopyAppValue(codecSoundSettingsKey, kCFPreferencesCurrentApplication); |
if (theSoundSettings) { |
err = PtrToHand(CFDataGetBytePtr((CFDataRef)theSoundSettings), &gSoundSettings, CFDataGetLength((CFDataRef)theSoundSettings)); |
if (err) gSoundSettings = NULL; |
CFRelease(theSoundSettings); |
} |
return err; |
} |
void SaveSoundSettingsPreference(void) |
{ |
CFStringRef codecSoundSettingsKey = CFSTR("codecSoundSettings"); |
CFDataRef theSoundSettings; |
HLock(gSoundSettings); |
theSoundSettings = CFDataCreate(NULL, (UInt8 *)*gSoundSettings, GetHandleSize(gSoundSettings)); |
HUnlock(gSoundSettings); |
CFPreferencesSetAppValue(codecSoundSettingsKey, theSoundSettings, kCFPreferencesCurrentApplication); |
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); |
CFRelease(theSoundSettings); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14