| // |
| // SlimServer Copyright (C) 2003-2004 Sean Adams, Slim Devices Inc. |
| // This program is free software; you can redistribute it and/or |
| // modify it under the terms of the GNU General Public License, |
| // version 2. |
| // |
| // This program is distributed in the hope that it will be useful, |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| // GNU General Public License for more details. |
| // |
| // |
| // mov123 - A very basic Quicktime decoder command line application. |
| // |
| // usage: mov123 <srcfile> |
| // |
| // opens and decodes the first audio track from a QuickTime compatible file. This includes |
| // Movie files, m4a AAC files, AIFF, WAV and other formats supported natively by quicktime. |
| // Sends to standard out the raw uncompressed audio data in stereo 44.1kS/sec 16bit. |
| // Output goes to stdout |
| // |
| // Todo: - extract channel, sample rate, and sample size information from the movie for |
| // use in reencoding later |
| // - CLI options for: |
| // - specifying output file |
| // - output files for AIFF and WAV |
| // - changing sample rate, sample size, channel count, codec |
| // - usage |
| // - be graceful about failures |
| // |
| // Portions based on Apple's ConvertMovieSndTrack sample code. |
| // |
| |
| #include <stdio.h> |
| #include <fcntl.h> |
| |
| //#include <io.h> |
| |
| |
| #ifdef WIN32 |
| #include "stdafx.h" |
| #include "Movies.h" |
| #include "SoundComponents.h" |
| #include "QTML.h" |
| #else |
| #include <QuickTime/QuickTime.h> |
| #include <QuickTime/QTML.h> |
| #endif |
| |
| #include <projectM.hpp> |
| |
| #define kVideoTimeScale 600 |
| |
| extern projectM_t *globalPM; |
| |
| int path2fss( FSSpec *fss, char *path ) { |
| char buf[256]; |
| char *p = &buf[1]; |
| strcpy( p, path ); |
| buf[0] = strlen( p ); |
| |
| return FSMakeFSSpec( 0, 0, (unsigned char *)buf, fss ); |
| } |
| |
| #define BailErr(x) {err = x; if (err != noErr) { fprintf(stderr, "Failed at line: %d\n", __LINE__); goto bail; } } |
| |
| const UInt32 kMaxBufferSize = 64 * 1024; // max size of input buffer |
| |
| // functions |
| OSErr ConvertMovieSndTrack( const char* inFileToConvert, Movie *movie, |
| Media *videoMedia, Media *audioMedia, |
| Track *videoTrack, Track *audioTrack ); |
| |
| typedef struct { |
| ExtendedSoundComponentData compData; |
| Handle hSource; // source media buffer |
| Media sourceMedia; // sound media identifier |
| TimeValue getMediaAtThisTime; |
| TimeValue sourceDuration; |
| |
| UInt32 maxBufferSize; |
| Boolean isThereMoreSource; |
| Boolean isSourceVBR; |
| } SCFillBufferData, *SCFillBufferDataPtr; |
| |
| FILE* outFile; |
| |
| #ifdef WIN32 |
| int _tmain(int argc, _TCHAR* argv[]) |
| #else |
| int main123(int argc, char *argv[]) |
| #endif |
| { |
| |
| FSSpec theDestFSSpec; |
| OSErr result = 0; |
| OSErr err = noErr; |
| short resRefNum = 0; |
| short resId = movieInDataForkResID; |
| Movie newMovie; |
| Media audioMedia, videoMedia; |
| Track audioTrack, videoTrack; |
| // StringPtr fileName = QTUtils_ConvertCToPascalString( argv[2] ); |
| |
| outFile = stderr; |
| |
| #ifdef WIN32 |
| setmode(fileno(outFile), O_BINARY); |
| InitializeQTML(0); // Initialize QTML |
| #endif |
| EnterMovies(); |
| |
| if (argc > 2) |
| path2fss( &theDestFSSpec, argv[2] ); |
| |
| /** Create the new output movie */ |
| err = |
| CreateMovieFile( &theDestFSSpec, FOUR_CHAR_CODE('TV0D'), |
| smSystemScript, |
| createMovieFileDeleteCurFile | createMovieFileDontCreateResFile | newMovieActive, |
| &resRefNum, &newMovie ); |
| if ( err != noErr ) { |
| printf( "failed to CreateMovieFile()\n" ); |
| } else { |
| printf( "created movie ok\n" ); |
| } |
| |
| /** Create the video track */ |
| videoTrack = |
| NewMovieTrack( newMovie, 512, 512, kNoVolume ); |
| |
| videoMedia = |
| NewTrackMedia( videoTrack, VideoMediaType, kVideoTimeScale, nil, |
| 0 ); |
| |
| /** Establish a media edit session */ |
| err = BeginMediaEdits( videoMedia ); |
| if ( err != noErr ) { |
| printf( "failed to BeginMediaEdits( video )\n" ); |
| } else { |
| printf( "beginning video media edit ok\n" ); |
| } |
| |
| /** Create the audio track */ |
| audioTrack = |
| NewMovieTrack( newMovie, 0, 0, kFullVolume ); |
| audioMedia = |
| NewTrackMedia( audioTrack, SoundMediaType, 44000, nil, 0 ); |
| |
| err = BeginMediaEdits( audioMedia ); |
| if ( err != noErr ) { |
| printf( "failed to BeginMediaEdits( audio )\n" ); |
| } else { |
| printf( "beginning audio media edits ok\n" ); |
| } |
| |
| /** Convert the input sound data into a movie file with video and audio */ |
| result = ConvertMovieSndTrack( argv[1], &newMovie, &videoMedia, &audioMedia, |
| &videoTrack, &audioTrack ); |
| |
| /** End media edits */ |
| err = EndMediaEdits( videoMedia ); |
| |
| err = EndMediaEdits( audioMedia ); |
| |
| /** Insert the audio track into the media */ |
| err = InsertMediaIntoTrack( audioTrack, 0, 0, |
| GetMediaDuration( audioMedia ), fixed1 ); |
| if ( err != noErr ) { |
| printf( "failed to insert audio media into audio track\n" ); |
| } else { |
| printf( "injected audio media into audio track ok\n" ); |
| } |
| |
| /** Add the movie resource to the file */ |
| err = AddMovieResource( newMovie, resRefNum, &resId, argv[2] ); |
| if ( err != noErr ) { |
| printf( "failed to AddMovieResource()\n" ); |
| } |
| if ( resRefNum ) { |
| CloseMovieFile( resRefNum ); |
| } |
| |
| //bail: |
| if (result != 0) { fprintf(stderr, "Conversion failed with error: %d\n", result); } |
| return result; |
| } |
| |
| |
| #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 |
| |
| |
| // * ---------------------------- |
| // 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) |
| { |
| int i, j; |
| |
| float nframes = 0; |
| |
| 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)) |
| printf("GetMediaSample - 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 char* inFile, Movie *outMovie, Media *outMedia) |
| { |
| Movie theMovie = 0; |
| Track theTrack; |
| short theRefNum; |
| short theResID = 0; // we want the first movie |
| OSErr err = noErr; |
| |
| BailErr(err); |
| |
| Boolean wasChanged; |
| |
| // open the movie file |
| if (strncmp(inFile, "http:", strlen("http:")) && |
| strncmp(inFile, "rtsp:", strlen("rtsp:")) && |
| strncmp(inFile, "ftp:", strlen("ftp:") )) { |
| FSSpec theFSSpec; |
| #ifdef WIN32 |
| OSErr result = NativePathNameToFSSpec((char*)inFile, &theFSSpec, 0 /* flags */); |
| #else |
| FSRef ref; // intermediate struct |
| FSPathMakeRef( (const UInt8*)inFile, &ref, NULL ); |
| OSErr result = FSGetCatalogInfo( &ref, kFSCatInfoNone , NULL, NULL, &theFSSpec, NULL); |
| #endif |
| if (result) {printf("NativePathNameToFSSpec failed on source file %s with %d\n", inFile, result); goto bail; } |
| err = OpenMovieFile(&theFSSpec, &theRefNum, fsRdPerm); |
| // instantiate the movie |
| BailErr(err); |
| err = NewMovieFromFile(&theMovie, theRefNum, &theResID, NULL, newMovieActive, &wasChanged); |
| CloseMovieFile(theRefNum); |
| } else { |
| |
| Handle urlDataRef; |
| |
| urlDataRef = NewHandle(strlen(inFile) + 1); |
| if ( ( err = MemError()) != noErr) goto bail; |
| |
| BlockMoveData(inFile, *urlDataRef, strlen(inFile) + 1); |
| |
| err = NewMovieFromDataRef(&theMovie, newMovieActive, nil, urlDataRef, URLDataHandlerSubType); |
| if (err) {printf("NewMovieFrom Data Ref failed on source file %s with %d\n", inFile, err); goto bail; } |
| |
| DisposeHandle(urlDataRef); |
| |
| } |
| |
| 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; |
| } |
| |
| // * ---------------------------- |
| // ConvertMovieSndTrack |
| // |
| // this function does the actual work |
| OSErr ConvertMovieSndTrack(const char* inFileToConvert, Movie *movie, |
| Media *videoMedia, Media *audioMedia, |
| Track *videoTrack, Track *audioTrack ) { |
| |
| SoundConverter mySoundConverter = NULL; |
| |
| Movie theSrcMovie = 0; |
| Media theSrcMedia = 0; |
| |
| 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; |
| |
| int i, j; |
| int sindex = 0; |
| unsigned short pcmdata[2][512]; |
| unsigned short *dptr; |
| |
| // *********** SOURCE: Get sound data info from the first source movie sound track |
| |
| err = GetMovieMedia(inFileToConvert, &theSrcMovie, &theSrcMedia); |
| 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; |
| |
| theOutputFormat.flags = kNoRealtimeProcessing; |
| theOutputFormat.format = k16BitBigEndianFormat; |
| theOutputFormat.numChannels = 2; // theInputFormat.numChannels; |
| theOutputFormat.sampleSize = 16; |
| theOutputFormat.sampleRate = 44100 << 16; //theInputFormat.sampleRate; |
| theOutputFormat.sampleCount = 0; |
| theOutputFormat.buffer = NULL; |
| theOutputFormat.reserved = 0; |
| |
| // *********** 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); |
| |
| // 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) { |
| printf( "."); |
| 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; |
| } |
| |
| i = 0; |
| sindex = 0; |
| dptr = (unsigned short *)pDecomBuffer; |
| while ( i < numberOfMediaSamples ) { |
| for ( j = 0 ; j < 512 ; j++ ) { |
| pcmdata[0][j] = *dptr; |
| *dptr++; |
| pcmdata[1][j] = *dptr; |
| *dptr++; |
| i++; |
| } |
| |
| /** Render the frame */ |
| renderLoop( globalPM, pcmdata ); |
| |
| /** Stuff frame into movie */ |
| |
| /** Stuff audio into movie */ |
| err = |
| AddMediaSample( *audioMedia, pDecomBuffer, |
| 0, actualOutputBytes, |
| durationPerMediaSample, |
| nil, numberOfMediaSamples, |
| 0, nil ); |
| |
| } |
| |
| // printf( "samples: %d (%d bytes)\n", numberOfMediaSamples, actualOutputBytes ); |
| |
| // if (!fwrite(pDecomBuffer, actualOutputBytes, 1, outFile)) goto bail; |
| } |
| |
| } // 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; |
| } |
| |
| // if (!fwrite(pDecomBuffer, actualOutputBytes, 1, outFile)) goto bail; |
| |
| BailErr(err); |
| } |
| |
| if (theFillBufferDataUPP) { |
| DisposeSoundConverterFillBufferDataUPP(theFillBufferDataUPP); |
| } |
| } |
| |
| } |
| |
| bail: |
| if (mySoundConverter) |
| SoundConverterClose(mySoundConverter); |
| |
| 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); |
| |
| return err; |
| } |