When QuickTime came out in 1990, it could play movies the size of a postage stamp—barely—on $7,000 hardware. It used audio and video codecs that, although still supported today, have long since been abandoned by users. Yet it's been a smooth transition from Apple Video to Cinepak to MPEG-4. This is thanks to an extraordinarily modular design—most of the heavy lifting in QuickTime is performed by components , or shared code fragments that can be discovered and used dynamically. Components provide support for importing and exporting image and movie formats, performing image and sound compression and decompression, accessing system resources, and much more. The QuickTime installer provides components for many features, and components added later by the end user, from either Apple or third parties, can provide more functionality, like support for more media formats.

Components aren't always front-and-center in the API—after all, the first few chapters have managed to avoid mentioning them entirely. QuickTime has been assumed to just "do the right thing" when it comes to opening files and turning them into movies, decompressing and rendering the data, saving it to disk, etc. When needed, QuickTime looks through its catalog of components for required functionality and gets what it needs.

But sometimes it's desirable or necessary for the developer to work with components more directly, to figure out what's available or to specify behavior. Figuring out what tools are available at runtime can be a powerful asset.

Specifying a Component's Type

In QuickTime, components are identified by a type and a subtype . The type specifies a broad area of functionality, while the subtype is a specific implementation of that functionality. For example, there's a "movie exporter" type, which identifies components that can write a movie into a non-QuickTime format, with subtypes identifying the exporters for AVI, MPEG-4, etc.

These identifiers are 32-bit int values, but typically they're not enumerated constants like you might expect from Java. Usually, the 32 bits are read as four 8-bit ASCII characters, making a short, human-readable name. These are defined in the native API as OSTypes, but when populated with meaningful values, they're called "four character codes," from the native FOUR_CHAR_CODE function that returns an OSType for a string. This often is abbreviated as FCC, or 4CC.

The scheme makes a lot of sense from the C programmer's point of view. For example, defining the 4CC for a movie requires a nice, simple one-liner, as seen in the native Movies.h header file:

MovieResourceType = 'moov'

Note

"moov" shows up a lot in QuickTime: as an identifier for a movie's copy-and-paste type, as its Carbon file type, as the top-level "atom" in the file format, etc. Say it out loud if you don't get the joke: moo-vee.

It turns out that dealing with 4CCs is harder in Java, thanks to Java's more modern approach to text. Specifically, the use of Unicode means Java characters are 2 bytes each, which means help is needed to turn a Java string into a 4CC.

How do I do that?

Fortunately, the QTUtils class provides two methods for converting to and from 4CCs: toOSType() and fromOSType( ). Example 4-1 exercises these methods by converting a Java string to and from its 4CC representation.

Note

Compile and run this example from the downloaded book code with ant run-ch04-fourcharcodetest.

The main( ) method takes a String from the command line, converts it to a 4CC, prints that value in decimal and hex, then converts it back to a String. When it's run with moov as an argument, the output looks like this:

Really hard-core QuickTime developers can read 4CCs in hex without thinking about it. Drop a movie file on a hex editor and you'll probably see 6d6f6f76 (moov) as bytes 4-8.

What just happened?

These utility methods provide some good, old-fashioned bit-munging to do their conversions. toOSType( ) takes a String as its argument, grabbing the low 8 bits of each character and putting them in the proper place in the returned int. In other words, the bottom 8 bits of the first character take up the first 8 bits of the int, then the next character is used for the next 8 bits, and so on. Figure 4-1 shows where the bits end up in the bit-shifted "moov".

Figure 4-1. Bit-wise, hex, and character representation of a FOUR_CHAR_CODE

fromOSType( ) does the opposite conversion, masking off the bits of an int and returning a four-character Java string.

Exporting Movies

One of the most obviously useful components is the MovieExporter, which you can use to convert a QuickTime movie into a non-QuickTime format, such as AVI or MPEG-4.

How do I do that?

The quicktime.std.qtcomponents.MovieExporter class provides a convenient Java wrapper around movie exporter components. It requires that you pass it a subtype indicating which exporter you want—i.e., which format you want to export to. Example 4-2 shows how a MovieExporter can be created and used from a canned list of subtypes.

When run, this program prompts the user to open a movie file. Once the movie loads, the program offers a dialog with a choice of formats to export to, as shown in Figure 4-2.

Figure 4-2. Choice dialog with canned MovieExporter types

Next, it shows the user a save dialog detailing the proposed export (e.g., "Movie to MPEG-4") and an Options button. The button brings up a dialog specific to the export format. For example, the AVI export dialog is fairly simple, offering only a few settings to choose from. On the other hand, the MPEG-4 export dialog, seen in Figure 4-3, is extraordinarily busy, packed with descriptions of the many options to help end users understand their choices and potentially keep their exported file compliant with MPEG-4 standards.

Figure 4-3. MPEG-4 export dialog

After the user makes his choices and clicks OK, the long export process begins. Because movie export is very computationally intensive—potentially every frame of video and every audio sample must be re-encoded—a progress dialog appears, so the user can see how much of the export has completed and how much longer it will take.

What just happened?

This program uses an inner class called ExportType to wrap a subtype, int, and a String, largely for the purpose of simplifying the JComboBox used in the format-choice dialog. These subtypes come from constants defined in the StdQTConstants class.

Once a choice is made, the program instantiates a MovieExporter by passing the subtype to its constructor. Next, it requests a progress dialog by calling setProgressProc() on the movie.

Finally, the export is performed by calling convertToFile() and passing in the exporter. This method takes several parameters:

A Track to indicate that only this track should be exported, or null for all tracks.

A QTFile to export to.

A file type, such as StdQTConstants.kQTFileTypeMovie.

A creator, such as StdQTConstants.kMoviePlayer.

A script tag, typically IOConstants.smSystemScript.

Behavior flags. This example uses all three of the valid values: showUserSettingsDialog makes the export bring up the Save As dialog that includes the filename and the options button; movieToFileOnlyExport limits the export choices to formats supported by the exporter component; and movieFileSpecValid asserts that the QTFile is valid and should be used as the default name in the dialog.

Note

Including showUserSettingsDialog allows you to pick up the settings and the save-as GUIs in one call, instead of having to show separate dialogs for each. Too bad flags like this aren't described in Javadoc.

The MovieExporter to use for the export.

What about...

...using the MovieExporter itself to do the export? That's an alternative. The exporter's toFile() exports the movie to a file, and its toHandle( ) exports to memory. This also has the advantage of being able to export just part of a movie, as specified by the startTime and duration arguments. Note that doing this requires a different program flow, because first you'd need to get a valid QTFile (perhaps with an AWT file dialog) and then you'd need to call the exporter's doUserDialog( ) to configure the export. Also the Movie class's convertToFile( ) method can be more convenient, because, as seen here, it allows use of the default progress dialog. When using the MovieExporter methods, there's no access to the default dialog. In that case, the only alternative is to provide a custom progress dialog and handle progress callbacks with setProgressProc( ).

Also, a complaint: I tried exporting to MPEG-4 on Windows and didn't get any audio options. When I click the Audio Track menu in the Exporter dialog, I get the useless panel as shown in Figure 4-4.

Figure 4-4. Audio non-options for MPEG-4 export on Windows

This is not a technical issue but a legal one. Apple has licensed MPEG-4 audio encoding for its Mac-based QuickTime users, but not for Windows users. The codecs exist, but apparently you have to contact Dolby about license terms to enable them for Windows.

Exporting Movies to Any Installed Format

Exporting to a list of known formats is limiting—if the end user has installed new movie exporters, either from third parties or via an update to QuickTime itself, a program that uses a canned list of exporters won't be able to pick them up. Fortunately, QuickTime provides a means of querying for installed components of a given type. You can use this strategy to offer a list of all available exporters.

How do I do that?

The AdvancedMovieExport eliminates the three canned entries in the choices array that were used by SimpleMovieExport (shown in Example 4-2) and instead builds the array through a process of discovery; this code would replace the short "build choices" block in the constructor for SimpleMovieExport but needs to go inside the try-catch, because it makes calls that can throw QTException:

When run, the list of supported exporters is surprisingly large, as seen in Figure 4-5. In this case, a "normal" movie, consisting of a video track and an audio track, is being exported, meaning that any audio-only format (Wave, AIFF, etc.) or audio/video format (QuickTime, AVI, MPEG-4, etc.) will work.

Figure 4-5. Discovered Movie Exporters

Note

Hinted Movie, the format selected in Figure 4-5, is a QuickTime movie with "hints" to optimize streaming.

You also should take note of the discovered exporters that cannot export the movie. These are logged to standard out:

These fail because the source movie doesn't contain tracks that can be exported to these formats. With a source movie with different kinds of tracks, some of these would succeed and others would fail.

What just happened?

The process of discovering components by subtype is rather peculiar. It hinges on making repeated calls to a "find" method, passing in the last matching component. Doing this requires a ComponentDescription , used as a template to match against, and a ComponentIdentifier, which refers to a specific component (though not a specific instance of that component). To find movie exporters, initialize a ComponentDescription template with the constant movieExporterType.

The static ComponentIdentifier.find() method finds matching components, but instead of offering an array or other collection of matches, it requires you to repeatedly pass in the ComponentDescription template, along with the previous ComponentIdentifier found by the method. For the first iteration, this will be null. The find( ) call returns a ComponentIdentifier, which you pass to the MovieExporter constructor to create a new exporter. When find( ) returns null, there are no more matches.

Note

Yes, this is totally weird, at least from a Java perspective.

The matched ComponentIdentifier provides information about itself via the getInfo( ) method. This returns another ComponentDescription object, different from the one used as a template. You can use this to get type and subtype information (as FOUR_CHAR_CODEints, of course), a name, an information String, a manufacturer code, etc.

Finding a MovieExporter is no guarantee that it actually will work. You can call validate( ), as this example does, to check that the instantiated exporter can do an export from the given movie. In this example, if validate throws an exception, it's logged to standard out and the exporter is not added to the JComboBox.

What about...

...setting the export parameters programmatically, instead of using the export dialog every time? This is possible, although it will require using the export dialog at least once in development. A configured MovieExporter can return its configured state in the form of an AtomContainer object, by way of the getExportSettingsFromAtomContainer() method. This object can be passed to an exporter via the setExportSettingsFromAtomContainer() method.

Note

"Atoms" are a low-level data structure that do almost all of QuickTime's heavy lifting. Application-level code uses them only for really advanced stuff (see Chapter 9).

Within a single running application, this is pretty straightforward. To persist between sessions, you must save off the native structure by calling getBytes( ) on the AtomContainer and then persist it to disk, database, etc. To recreate the settings in the future, read the bytes into a byte array, create a QTHandle from the array, and then pass that to AtomContainer.fromQTHandle( ) to create the AtomContainer.

QuickTime 6.3 introduced a new API for setting exporters programmatically, but as of this writing, it has not been exposed via QTJ method calls.

Also, if I specify type and subtype, will I always get one match? No, in some cases, you'll get multiple matching components, and you might need to use other criteria to pick which one to use. In a rather infamous case pointed out by one of my tech reviewers:

Sometimes you get more than one exporter with the same subtype and need to use the "manufacturer" code to distinguish them. This applies particularly to AIFF exporters—the first exporter you find of that type only exports MIDI. To export an arbitrary QT audio file to AIFF you need to explicitly iterate and pick the second one!

Importing and Exporting Graphics

QuickTime offers many components whose job is to import from and export to different graphics formats. As you might expect, these components are wrapped by classes called GraphicsImporter and GraphicsExporter.

The GraphicImportExport example application (shown in Example 4-3) uses both of these classes to illustrate the dynamic lookup of importers and exporters.

When run, the program shows a dialog to select a graphic to be imported. On Windows, the "file type" in this dialog is QuickTime Image. Once an image is selected, it appears in a window with an "export" button. When the user clicks the button, she is asked for an export type, as shown in Figure 4-6.

Note

GraphicsImporter and GraphicsExporter are in quicktime.std.image, not quicktime.std.qtcomponents like most other components.

Figure 4-6. Selecting a GraphicsExporter

After this, the program displays a configuration dialog specific to the type of exporter selected—at a minimum, this dialog usually offers a choice of color depths (256 colors, 256 grays, millions of colors, etc.). Next, a save dialog requests the location of the exported file. Once approved, the program converts the image to the specified format and saves it to the supplied location.

What just happened?

Notice the QTFile.standardGetFilePreview() . This shows a file-open dialog and takes an array of up to four ints, representing FOUR_CHAR_CODE s of various file format constants, which are used as a filter of what file types to make selectable. You can use kQTFileTypeQuickTimeImage as a convenient wildcard that matches any kind of image QuickTime can open, though it seems to work only on Windows (on the Mac, any file can be selected).

Tip

If you want to specify formats, interesting constants in StdQTConstants include kQTFileTypeGIF, kQTFileTypeJPEG, and kQTFileTypePhotoShop. The StdQTConstants4 class adds similarly named constants for PNG and TIFF. Unfortunately, you can send only four.

Given a file, you can construct a GraphicsImporter object to load it into QuickTime. To put the imported image on-screen, pass the importer to QTFactory.makeQTComponent() , which returns a QTComponent that you can either cast to an AWT Component or, to be type-safe, convert with the asComponent( ) method.

Warning

java.awt and quicktime.std.comp both define a class called Component . If you're casually importing every class from these packages, you're probably headed for a compile-time error. You'll have to make your imports more selective or use a fully qualified class name for one of the Components, like this example does.

To export an image to another format, you can search for graphics exporter subtypes by creating a ComponentDescription template to match components of the graphicsExporterComponentType. In the example, the names of matching components are shown in a JComboBox. With a subtype selected, create the GraphicsExporter by passing the subtype to its constructor.

Note

This method of looking up exporter components was shown in the previous lab.

A GraphicsExporter needs to be wired up to some kind of source image. With a GraphicsImporter, you wire the two together with setInputGraphicsImporter() . The exporter also needs a destination. If writing to a file (as opposed to, say, memory), you set this with setOutputFile( ) —just to be safe, it's wise to sanity-check the user-provided filename extension against the value returned by the exporter's getDefaultFileNameExtension() .

The user probably wants some say in the color depth, image quality, and other settings for the export, a dialog for which is provided with a requestSettings() .

After all that, you finally can do the export with...doExport( ).

What about...

...other sources for the export? The Javadoc for GraphicsExporter shows a bunch of setInputXXX( ) methods. True enough, and in the next chapter, we'll explore some of these, including Picts, QDGraphics, and PixMaps.

And what about setting export parameters programmatically? QTJ exposes some methods that could be used instead of the user dialog, such as setDepth() and setCompressionMethod( ). One interesting method, setTargetDataSize( ) , lets exporters with a "quality" option (like JPEG) find a value that will result in a file of the given size in bytes.

Note

GraphicsExporters have an AtomContainer-based settings scheme that's just as painful as the MovieExporter equivalent from the last lab.

Discovering All Installed Components

I hope that by this point you're at least a little interested in what other kinds of components are available in QuickTime. It's easy to discover them all, in much the same way we discovered the various MovieExporters and GraphicExporters: by providing a ComponentDescription template and using ComponentIdentifier.find( ). With a "blank" template, all components can be revealed.

How do I do that?

Example 4-4 discovers all installed components and logs their type, subtype, and description to standard out.

What just happened?

The key is the line that gets a ComponentDescriptor via a no-arg constructor. This creates a completely blank template for ComponentIdentifier.find( ) to run against. Of course, if you just wanted to tour components of a specific type, you could pass in a type constant such as StdQTConstants.movieImportType, which would limit the search to MovieImporters, and thus indicate what kinds of formats QuickTime can import.

Documenting and explaining every kind of component is beyond the scope of this book—in fact, it filled up a whole volume of the old Inside Macintosh series. Still, a few of the important ones are listed in Table 4-1. Note that not all components have (or need) a Java wrapper class.