Try It -> Kromo the ultimate iShell plugin!
register | login
  • Archive
    • 07/01/2006 - 08/01/2006
    • 09/01/2006 - 10/01/2006
    • 11/01/2006 - 12/01/2006
    • 01/01/2007 - 02/01/2007
    • 11/01/2007 - 12/01/2007
  • Feed
    • RSS
    • Atom
Media Blog


Wednesday, November 22, 2006

The Joys and Troubles of Writing Software on top of the QuickTime API - with some sample code

What a double-sided sword this wonderful piece of software can be at times. For the last release of iShell, we needed to migrate pieces of our source code from QuickTime (QT) 5 code base to the more modern QuickTime 7 code base. This is logical and makes sense. And now, the rest of the story.

One thing to remember about iShell and tribalmedia's approach to software is we always write software with a cross-platform mind and a focus on compact -- as little code as needed -- software. With this in mind we quickly eliminated Cocoa/Objective-C as it is not a good solution in a cross-platform world. We could argue the merits of C++, but for us C is the way to go, and we use C in many abstracted and wonderful ways.

What prompted this article was using the latest QT API's to open a QT movie (should be really easy) and to create 64bit (larger than 2GB) media files with the QT API on both Mac and Windows platforms. Along the way there is some useful information and some good old ranting.

Opening a Movie

Just for a quick reference here are the options for opening a movie. *1

OpenMovieFile and NewMovieFromFile - Apple says, "Don't use this crazy FSSpec business." Even though it works everywhere etc. I totally understand the reasons why, but an easy cross-platform replacement would have been so welcomed. I guess they could have done something crazy and adopted FSRefs.

OpenMovieStorage - Not sure why this is not recommended. DataReferenceRecords are the recommended approach for many other APIs. Most of us really just want to add our a drawing world (Quartz, QuickDraw, OpenGL, HBITMAP, HDC, etc.) And if it is a just a single track sound file we would really like QT to not require a drawing world. I would think this could be done with a simple flag and parameter...but maybe not.

NewMovieFromHandle - OK. How often do we have an entire movie in file? Oh...but wait this is actually just the movie resource it is looking for in the handle. And this gets us to a problem we will look at when writing movie data.

NewMovie - So temptingly simple, but not what you want. Sorry.

NewMovieFromScrap - Not even sure what "Scrap" is. Easy enough to eliminate.

NewMovieFromStorageOffset - OK. Then name here helps me see when this could be useful. Useful when you want to create your own movie file etc. iShell needs things like this. But you are actually supposed to use the next function for must cases.

NewMovieFromDataFork - Or its 64bit sister NewMovieFromDataFork64. This does not imply offset. And damned it requires a fileRefNum again and this is only available on Windows with FSSpecs. So that takes us back to NewMovieFromStorageOffset, but at least I have an intimate knowledge of DataRef's thanks to part 2 of this story.

NewMovieFromDataRef - This is not really ever recommend as you cannot set the drawing world...and it crashes if one does not have an antiquated GWorld in place for the life of the movie. No CGContext or OpenGL world for this almost easy to use function.

NewMovieFromProperties - WE HIT THE JACKPOT!!! (Had to scream sorry. This is just not intuitive when all someone wants to do is open a movie file! Open is such a good word.) NewMovieFromProperties is the way Apple recommends us to do this now. And you have to really understand many nuances to get it to work. Yes it is more powerful, and useful for many of the things iShell needs to do. But for newbies this looks like the start of maze they will never make it out of.

Now for some useful examples of this and why it works.

/* ___________________________ The Old Way _______________________ */
// NOTE: keyIfError() is simply a error checking macro we use.
// Requires FSSpecs and active GWorld
keyIfError(OpenMovieFile(&aFSSpec, &aRefNum, fsRdPerm));
keyIfError(NewMovieFromFile(&aMovie, aRefNum, NULL, NULL, newMovieActive | newMovieAsyncOK, NULL));
CloseMovieFile(aRefNum);
aRefNum = 0;


/* ___________________________ The New Way _______________________ */
// First define these...
DataReferenceRecord aQTDataRef = {0};
Boolean isActive = true;
QTVisualContextRef aVisualContext = NULL;
QTNewMoviePropertyElement aMovieProperties[] = {
{kQTPropertyClass_DataLocation, kQTDataLocationPropertyID_DataReference, sizeof(aQTDataRef), &aQTDataRef, 0},
{kQTPropertyClass_NewMovieProperty, kQTNewMoviePropertyID_Active, sizeof(isActive), &isActive, 0},
{kQTPropertyClass_Context, kQTContextPropertyID_VisualContext, sizeof(aVisualContext), &aVisualContext, 0},
};

/* Just to note, it is not clear what the visual context is, but I assume we need this now so as not to crash. */

// Now Create the Data ref.
keyIfError(FSPathMakeRef(aPath, &aFSRef, NULL));
keyIfError(QTNewDataReferenceFromFSRef(&aFSRef, 0, &aQTDataRef.dataRef, &aQTDataRef.dataRefType));

// Wait. That does not work on Windows. So try this...
keyToSystemPascalString(aCPath, aPascalPath, sizeof(aPascalPath));
keyIfError(FSMakeFSSpec(0, 0, aPath, &aFSSpec));
keyIfError(QTNewDataReferenceFromFSSpec(&aFSSpec, 0, &aQTDataRef.dataRef, &aQTDataRef.dataRefType));

/* And since Windows programmers are so used to a Pascal path this makes sense. And again we are using those FSSpecs we are never to touch again so try again. */

// The real trick is this...

aCFStringPath = CFStringCreateWithCString(NULL, aCPath, kCFStringEncodingUTF8);
keyIfError(QTNewDataReferenceFromFullPathCFString(aCFStringPath, kQTNativeDefaultPathStyle, 0, &aQTDataRef.dataRef, &aQTDataRef.dataRefType));
CFRelease(aCFStringPath);
aCFStringPath = NULL;

/* Now raise your hand if you have ever seen any Apple sample code use Core Foundation classes on Windows. I could not find it. Hopefully this exists and I am just blind. Low and behold, this and the CFURL equivalents work, and give you an easy way to handle paths and start creating movies. */

// Now call...and don't forget we have a DataRef which we are not sure what it is in one of those properties.
keyIfError(NewMovieFromProperties(3, aMovieProperties, 0, NULL, &aMovie));

/* And now you have to call SetMovieGWorld() or the other equivalents which I am not sure what they are as I have not migrated my drawing to the only cross platform option of OpenGL. And GWorlds work in crazy hacked ways from HBITMAP's and CGContext's */

// Finally dispose of the DataRef...but wait there is not an compliment to QTNewDataReferenceFrom() functions. So you have to use the most intuitive. DisposeHandle to the pointer of one of the structs values

DisposeHandle(aQTDataRef.dataRef);
aQTDataRef.dataRef = NULL;


Now we know how to open a movie the QT 7 way. And I believe it is not so clear.

Writing Data

I will only touch briefly on writing data the QT 7 way vs. the old way. Basically the old way involved FSSpecs and used the straight classic Mac file system calls. FSOpen, FSWrite, etc, and you could write data buffers, movie data, and movie resources. *2 It was never as easy as on Windows file APIs, but it worked. The new way now says to use DataRef's and DataHandlers. Seems easy enough, but there are some really tricky bits. There is one sample I found (qtfiletransfer.win http://developer.apple.com/samplecode/qtfiletransfer.win/qtfiletransfer.win.zip ) and it did not solve my problem of 64bit files, but at least was a basis to start from.

To get started we need to create the DataRef. We now know to use CFString's or CFURL's so this is much easier. But the problem here is we cannot create a new file with the Core Foundation API, but QuickTime saved us here and will create the file for us if it does not exist.

The next trick is how to take that DataRef and make a DataHandler out of it so we can use the nifty new DataHWrite64, DataHGetFileSize64, etc. You’d think there would be an easy way or sample to do this. And the link above did provide one sample. It is shown below.

aHandler = OpenComponent(GetDataHandler(aQTDataRef.dataRef, aQTDataRef.dataRefType, kDataHCanRead));
anErr = GetMoviesError();


I would have never guessed this without the sample from above. I probably searched for two hours to come across this. And now in the process of writing this article I think I have discovered you could use OpenMovieStorage to get the same thing. I think this is much cleaner if it works.

Now the last trick is to figure out how to read data. When writing data we have DataHWrite64. This works great and as expected. You would think DataHRead64 would exist, but to my greatest amazement, this does not exist. The closest compliment is DataHReadAsync and according to the latest docs is unsupported. But it does work if you play around. And it will work synchronously if you leave out the callback proc. But what are you really suppose to use? After much searching I found out it was DataHScheduleData64. I am sure this just calls DataHReadAsync somewhere underneath. My question here is who would have ever thought to call the compliment of "Write" "ScheduleData"? I don't think I will ever understand that.


Conclusion

Here are my two recommendations for Apple. Please just create a nice OpenMovie function. I don't think this name has ever been used in the QT API, so it probably would be available. The second recommendation is give us simpler file API. The core code here works great. Just abstract it for us.

Hopefully this long read will save you a bit of time as you write your own QT code.

Next time maybe we will write about the Macintosh file APIs so all those Windows only programmers out can feel how lucky they are to have the Windows file API’s.




A quick company pitch...if you ever need help in writing that great media software the engineering team at tribalmedia is available and ready to help you create the next great piece of software.

*1 - When writing software with QuickTime and the Mac in general, reading the header files is one of the best resources you can find.

*2 - When we look at a movie data and information you must always remember that when the documentation says a "movie resource" they are referring to the movie header or description information stored in classic QT Atoms. And "movie data" is a reference to the actually streams of information. The QT storage system is amazingly powerful and flexible but the terminology can be a bit confusing for newbies. And this is just a very simplistic rough explanation.

posted by Matt Veenstra  # 3:31 PM