Copyright 2011-01-06 Werner Randelshofer

ch.randelshofer.media.quicktime
Class QuickTimeWriter

java.lang.Object
  extended by ch.randelshofer.media.quicktime.QuickTimeWriter

public class QuickTimeWriter
extends java.lang.Object

Supports writing of time-based video and audio data into a QuickTime movie file (.MOV) without the need of native code.

QuickTimeWriter works with tracks and samples. After creating a QuickTimeWriter one or more video and audio tracks can be added to it. Then samples can be written into the track(s). A sample is a single element in a sequence of time-ordered data. For video data a sample typically consists of a single video frame, for uncompressed stereo audio data a sample contains one PCM impulse per channel. Samples of compressed media data may encompass larger time units.

Tracks support edit lists. An edit list specifies when to play which portion of the media data at what speed. An empty edit can be used to insert an empty time span, for example to offset a track from the start of the movie. Edits can also be used to play the same portion of media data multiple times without having it to store it more than once in the track.
Moreover edit lists are useful for lossless cutting of media data at non-sync frames. For example, MP3 layer III audio data can not be cut at arbitrary frames, because audio data can be 'borrowed' from previous frames. An edit list can be used to select the desired portion of the audio data, while the track stores the media starting from the nearest sync frame.

Samples are stored in a QuickTime file in the same sequence as they are written. In order to get optimal movie playback, the samples from different tracks should be interleaved from time to time. An interleave should occur about twice per second. Furthermore, to overcome any latencies in sound playback, at least one second of sound data needs to be placed at the beginning of the movie. So that the sound and video data is offset from each other in the file by one second.

For convenience, this class has built-in encoders for video frames in the following formats: RAW, RLE, JPEG and PNG. Media data in other formats, including all audio data, must be encoded before it can be written with QuickTimeWriter.

Example: Writing 10 seconds of a movie with 640x480 pixel, 30 fps, PNG-encoded video and 16-bit stereo, 44100 Hz, PCM-encoded audio.

 QuickTimeWriter w = new QuickTimeWriter(new File("mymovie.mov"));
 w.addAudioTrack(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED), 44100, 2, 16, 2, 44100, true)); // audio in track 0
 w.addVideoTrack(QuickTimeWriter.VideoFormat.PNG, 30, 640, 480);  // video in track 1

 // calculate total movie duration in media time units for each track
 long atmax = w.getMediaTimeScale(0) * 10;
 long vtmax = w.getMediaTimeScale(1) * 10;

 // duration of a single sample
 long asduration = 1;
 long vsduration = 1;

 // half a second in media time units (we interleave twice per second)
 long atstep = w.getMediaTimeScale(0) / 2;
 long vtstep = w.getMediaTimeScale(1) / 2;

 // the time when the next interleave occurs in media time units
 long atnext = w.getMediaTimeScale(0); // offset audio by 1 second
 long vtnext = 0;

 // the current time in media time units
 long atime = 0;
 long vtime = 0;

 // create buffers
 int asamplesize = 2 * 2; // 16-bit stereo * 2 channels
 byte[] audio=new byte[atstep * asamplesize];
 BufferedImage img=new BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB);

 // main loop
 while (atime < atmax || vtime < vtmax) {
      atnext = Math.min(atmax, atnext + atstep); // advance audio to next interleave time
      while (atime < atnext) { // catch up with audio time
          int duration = (int) Math.min(audio.length / asamplesize, atmax - atime);
          ...fill in audio data for time "atime" and duration "duration" here...
          w.writeSamples(0, duration, audio, 0, duration * asamplesize, asduration);
          atime += duration;
      }
      vtnext = Math.min(vtmax, vtnext + vtstep); // advance video to next interleave time
      while (vtime < vtnext) { // catch up with video time
          int duration = (int) Math.min(1, vtmax - vtime);
          ...fill in image data for time "vtime" and duration "duration" here...
          w.writeFrame(1, img, vsduration);
          vtime += duration;
      }
 }
 w.close();
 

For information about the QuickTime file format see the "QuickTime File Format Specification", Apple Inc. 2010-08-03. (qtff) http://developer.apple.com/library/mac/documentation/QuickTime/QTFF/qtff.pdf

Version:
1.2 2011-01-05 Adds support for "RLE" encoded video.
1.1 2011-01-04 Adds "depth" parameter to addVideoTrack method.
1.0 2011-01-02 Adds support for edit lists. Adds support for MP3 audio format.
0.1.1 2010-12-05 Updates the link to the QuickTime file format specification.
0.1 2010-09-30 Created.
Author:
Werner Randelshofer

Nested Class Summary
static class QuickTimeWriter.Edit
          An Edit define the portions of the media that are to be used to build up a track for a movie.
static class QuickTimeWriter.VideoFormat
          Built-in video formats.
 
Constructor Summary
QuickTimeWriter(java.io.File file)
          Creates a new QuickTime writer.
QuickTimeWriter(javax.imageio.stream.ImageOutputStream out)
          Creates a new QuickTime writer.
 
Method Summary
 void addAudioTrack(javax.sound.sampled.AudioFormat format)
          Adds an audio track to the movie, and configures using an AudioFormat object from the javax.sound API.
 void addAudioTrack(java.lang.String compressionType, long timeScale, double sampleRate, int numberOfChannels, int sampleSizeInBits, boolean isCompressed, int frameDuration, int frameSize)
          Adds an audio track to the movie.
 void addVideoTrack(QuickTimeWriter.VideoFormat encoding, long timeScale, int width, int height)
          Adds a video track to the movie.
 void addVideoTrack(java.lang.String compressionType, java.lang.String compressorName, long timeScale, int width, int height, int depth, int syncInterval)
          Adds a video track to the movie.
 void close()
          Closes the movie file as well as the stream being filtered.
 void finish()
          Finishes writing the contents of the QuickTime output stream without closing the underlying stream.
 float getCompressionQuality(int track)
          Returns the compression quality of a track.
 long getMediaDuration(int track)
          Returns the media duration of a track in the media's time scale.
 long getMediaTimeScale(int track)
          Returns the time scale of the media in a track.
 long getMovieDuration()
          Returns the total duration of the movie in the movie's time scale.
 long getMovieTimeScale()
          Returns the time scale of the movie.
 long getTrackDuration(int track)
          Returns the track duration in the movie's time scale.
 long getUneditedTrackDuration(int track)
          Returns the track duration in the movie's time scale without taking the edit list into account.
 java.awt.image.IndexColorModel getVideoColorTable(int track)
          Gets the preferred color table for displaying the movie on devices that support only 256 colors.
 void setCompressionQuality(int track, float newValue)
          Sets the compression quality of a track.
 void setEditList(int track, QuickTimeWriter.Edit[] editList)
          Sets the edit list for the specified track.
 void setMovieTimeScale(long timeScale)
          Sets the time scale for this movie, that is, the number of time units that pass per second in its time coordinate system.
 void setVideoColorTable(int track, java.awt.image.IndexColorModel icm)
          Sets the color table for videos with indexed color models.
 void writeFrame(int track, java.awt.image.BufferedImage image, long duration)
          Encodes an image as a video frame and writes it into a video track.
 void writeSample(int track, byte[] data, int off, int len, long duration)
          Writes an already encoded sync sample from a byte array into a track.
 void writeSample(int track, byte[] data, int off, int len, long duration, boolean isSync)
          Writes an already encoded sample from a byte array into a track.
 void writeSample(int track, byte[] data, long duration)
          Writes an already encoded sync sample from a byte array into a track.
 void writeSample(int track, byte[] data, long duration, boolean isSync)
          Writes an already encoded sample from a byte array into a track.
 void writeSample(int track, java.io.File file, long duration)
          Writes an already encoded sync sample from a file into a track.
 void writeSample(int track, java.io.File file, long duration, boolean isSync)
          Writes an already encoded sample from a file into a track.
 void writeSample(int track, java.io.InputStream in, long duration)
          Writes an already encoded sync sample from an input stream into a track.
 void writeSample(int track, java.io.InputStream in, long duration, boolean isSync)
          Writes an already encoded sample from an input stream into a track.
 void writeSamples(int track, int sampleCount, byte[] data, int sampleDuration)
          Writes multiple sync samples from a byte array into a track.
 void writeSamples(int track, int sampleCount, byte[] data, int off, int len, int sampleDuration)
          Writes multiple sync samples from a byte array into a track.
 void writeSamples(int track, int sampleCount, byte[] data, int off, int len, int sampleDuration, boolean isSync)
          Writes multiple samples from a byte array into a track.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Constructor Detail

QuickTimeWriter

public QuickTimeWriter(java.io.File file)
                throws java.io.IOException
Creates a new QuickTime writer.

Parameters:
file - the output file
Throws:
java.io.IOException

QuickTimeWriter

public QuickTimeWriter(javax.imageio.stream.ImageOutputStream out)
                throws java.io.IOException
Creates a new QuickTime writer.

Parameters:
out - the underlying output stream.
Throws:
java.io.IOException
Method Detail

setMovieTimeScale

public void setMovieTimeScale(long timeScale)
Sets the time scale for this movie, that is, the number of time units that pass per second in its time coordinate system.

The default value is 600.

Parameters:
timeScale -

getMovieTimeScale

public long getMovieTimeScale()
Returns the time scale of the movie.

Returns:
time scale
See Also:
setMovieTimeScale(long)

getMediaTimeScale

public long getMediaTimeScale(int track)
Returns the time scale of the media in a track.

Parameters:
track - Track index.
Returns:
time scale
See Also:
setMovieTimeScale(long)

getMediaDuration

public long getMediaDuration(int track)
Returns the media duration of a track in the media's time scale.

Parameters:
track - Track index.
Returns:
media duration

getUneditedTrackDuration

public long getUneditedTrackDuration(int track)
Returns the track duration in the movie's time scale without taking the edit list into account.

The returned value is the media duration of a track in the movies's time scale.

Parameters:
track - Track index.
Returns:
unedited track duration

getTrackDuration

public long getTrackDuration(int track)
Returns the track duration in the movie's time scale.

Parameters:
track - Track index.
Returns:
track duration

getMovieDuration

public long getMovieDuration()
Returns the total duration of the movie in the movie's time scale.

Returns:
media duration

setVideoColorTable

public void setVideoColorTable(int track,
                               java.awt.image.IndexColorModel icm)
Sets the color table for videos with indexed color models.

Parameters:
track - The track number.
icm - IndexColorModel. Specify null to use the standard Macintosh color table.

getVideoColorTable

public java.awt.image.IndexColorModel getVideoColorTable(int track)
Gets the preferred color table for displaying the movie on devices that support only 256 colors.

Parameters:
track - The track number.
Returns:
The color table or null, if the video uses the standard Macintosh color table.

setEditList

public void setEditList(int track,
                        QuickTimeWriter.Edit[] editList)
Sets the edit list for the specified track.

In the absence of an edit list, the presentation of the track starts immediately. An empty edit is used to offset the start time of a track.

Throws:
java.lang.IllegalArgumentException - If the edit list ends with an empty edit.

addVideoTrack

public void addVideoTrack(QuickTimeWriter.VideoFormat encoding,
                          long timeScale,
                          int width,
                          int height)
Adds a video track to the movie.

Parameters:
encoding - The encoding of the video frames.
timeScale - The media time scale. This is typically the frame rate. If the frame rate is not an integer fraction of a second, specify a multiple of the frame rate and specify a correspondingly multiplied duration when writing frames. For example, for a rate of 23.976 fps specify a time scale of 23976 and multiply the duration of a video frame by 1000.
width - The width of a video image. Must be larger than 0.
height - The height of a video image. Must be larger than 0.
Throws:
java.lang.IllegalArgumentException - if the width or the height is smaller than 1.

addVideoTrack

public void addVideoTrack(java.lang.String compressionType,
                          java.lang.String compressorName,
                          long timeScale,
                          int width,
                          int height,
                          int depth,
                          int syncInterval)
Adds a video track to the movie.

Parameters:
compressionType - The QuickTime "image compression format" 4-Character code. A list of supported 4-Character codes is given in qtff, table 3-1, page 96.
compressorName - The QuickTime compressor name. Can be up to 32 characters long.
timeScale - The media time scale between 1 and 2^32.
width - The width of a video frame.
height - The height of a video frame.
depth - Number of bits per pixel.
syncInterval - Interval for sync-samples. 0=automatic. 1=all frames are keyframes. Values larger than 1 specify that for every n-th frame is a keyframe.
Throws:
java.lang.IllegalArgumentException - if width or height is smaller than 1, if the length of compressionType is not equal to 4, if the length of the compressorName is not between 1 and 32, if the tiimeScale is not between 1 and 2^32.

addAudioTrack

public void addAudioTrack(javax.sound.sampled.AudioFormat format)
Adds an audio track to the movie, and configures using an AudioFormat object from the javax.sound API.

Use this method for writing audio data from an AudioInputStream into a QuickTime Movie file.

Parameters:
format - The javax.sound audio format.

addAudioTrack

public void addAudioTrack(java.lang.String compressionType,
                          long timeScale,
                          double sampleRate,
                          int numberOfChannels,
                          int sampleSizeInBits,
                          boolean isCompressed,
                          int frameDuration,
                          int frameSize)
Adds an audio track to the movie.

Parameters:
compressionType - The QuickTime 4-character code. A list of supported 4-Character codes is given in qtff, table 3-7, page 113.
timeScale - The media time scale between 1 and 2^32.
sampleRate - The sample rate. The integer portion must match the timeScale.
numberOfChannels - The number of channels: 1 for mono, 2 for stereo.
sampleSizeInBits - The number of bits in a sample: 8 or 16.
isCompressed - Whether the sound is compressed.
frameDuration - The frame duration, expressed in the media’s timescale, where the timescale is equal to the sample rate. For uncompressed formats, this field is always 1.
frameSize - For uncompressed audio, the number of bytes in a sample for a single channel (sampleSize divided by 8). For compressed audio, the number of bytes in a frame.
Throws:
java.lang.IllegalArgumentException - if the audioFormat is not 4 characters long, if the time scale is not between 1 and 2^32, if the integer portion of the sampleRate is not equal to the timeScale, if numberOfChannels is not 1 or 2.

setCompressionQuality

public void setCompressionQuality(int track,
                                  float newValue)
Sets the compression quality of a track.

A value of 0 stands for "high compression is important" a value of 1 for "high image quality is important".

Changing this value affects the encoding of video frames which are subsequently written into the track. Frames which have already been written are not changed.

This value has no effect on videos encoded with lossless encoders such as the PNG format.

The default value is 0.97.

Parameters:
newValue -

getCompressionQuality

public float getCompressionQuality(int track)
Returns the compression quality of a track.

Returns:
compression quality

writeFrame

public void writeFrame(int track,
                       java.awt.image.BufferedImage image,
                       long duration)
                throws java.io.IOException
Encodes an image as a video frame and writes it into a video track.

Only the video encodings listed below are supported by this method. For other encodings, you have to encode the image by yourself and then call one of the writeSample methods.

Parameters:
track - The track index.
image - The image of the video frame.
duration - The duration of the video frame in media time scale units.
Throws:
IndexOutofBoundsException - if the track index is out of bounds.
if - the duration is less than 1, or if the dimension of the frame does not match the dimension of the video.
java.lang.UnsupportedOperationException - if the QuickTimeWriter does not have a built-in encoder for this video format.
java.io.IOException - if writing the sample data failed.

writeSample

public void writeSample(int track,
                        java.io.File file,
                        long duration)
                 throws java.io.IOException
Writes an already encoded sync sample from a file into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
file - The file which holds the encoded data sample.
duration - The duration of the sample in media time scale units.
Throws:
IndexOutofBoundsException - if the track index is out of bounds.
java.lang.IllegalArgumentException - if the track does not support video, if the duration is less than 1, or if the dimension of the frame does not match the dimension of the video.
java.io.IOException - if writing the sample data failed.

writeSample

public void writeSample(int track,
                        java.io.File file,
                        long duration,
                        boolean isSync)
                 throws java.io.IOException
Writes an already encoded sample from a file into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
file - The file which holds the encoded data sample.
duration - The duration of the sample in media time scale units.
isSync - whether the sample is a sync sample (key frame).
Throws:
IndexOutofBoundsException - if the track index is out of bounds.
java.lang.IllegalArgumentException - if the track does not support video, if the duration is less than 1, or if the dimension of the frame does not match the dimension of the video.
java.io.IOException - if writing the sample data failed.

writeSample

public void writeSample(int track,
                        java.io.InputStream in,
                        long duration)
                 throws java.io.IOException
Writes an already encoded sync sample from an input stream into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
in - The input stream which holds the encoded sample data.
duration - The duration of the video frame in media time scale units.
Throws:
java.lang.IllegalArgumentException - if the duration is less than 1.
java.io.IOException - if writing the image failed.

writeSample

public void writeSample(int track,
                        java.io.InputStream in,
                        long duration,
                        boolean isSync)
                 throws java.io.IOException
Writes an already encoded sample from an input stream into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
in - The input stream which holds the encoded sample data.
duration - The duration of the video frame in media time scale units.
isSync - Whether the sample is a sync sample (keyframe).
Throws:
java.lang.IllegalArgumentException - if the duration is less than 1.
java.io.IOException - if writing the image failed.

writeSample

public void writeSample(int track,
                        byte[] data,
                        long duration)
                 throws java.io.IOException
Writes an already encoded sync sample from a byte array into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
data - The encoded sample data.
duration - The duration of the sample in media time scale units.
Throws:
java.lang.IllegalArgumentException - if the duration is less than 1.
java.io.IOException - if writing the image failed.

writeSample

public void writeSample(int track,
                        byte[] data,
                        long duration,
                        boolean isSync)
                 throws java.io.IOException
Writes an already encoded sample from a byte array into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
data - The encoded sample data.
duration - The duration of the sample in media time scale units.
isSync - Whether the sample is a sync sample.
Throws:
java.lang.IllegalArgumentException - if the duration is less than 1.
java.io.IOException - if writing the image failed.

writeSample

public void writeSample(int track,
                        byte[] data,
                        int off,
                        int len,
                        long duration)
                 throws java.io.IOException
Writes an already encoded sync sample from a byte array into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
data - The encoded sample data.
off - The start offset in the data.
len - The number of bytes to write.
duration - The duration of the sample in media time scale units.
Throws:
java.lang.IllegalArgumentException - if the duration is less than 1.
java.io.IOException - if writing the image failed.

writeSample

public void writeSample(int track,
                        byte[] data,
                        int off,
                        int len,
                        long duration,
                        boolean isSync)
                 throws java.io.IOException
Writes an already encoded sample from a byte array into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
data - The encoded sample data.
off - The start offset in the data.
len - The number of bytes to write.
duration - The duration of the sample in media time scale units.
isSync - Whether the sample is a sync sample (keyframe).
Throws:
java.lang.IllegalArgumentException - if the duration is less than 1.
java.io.IOException - if writing the image failed.

writeSamples

public void writeSamples(int track,
                         int sampleCount,
                         byte[] data,
                         int sampleDuration)
                  throws java.io.IOException
Writes multiple sync samples from a byte array into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
sampleCount - The number of samples.
data - The encoded sample data. The length of data must be dividable by sampleCount.
sampleDuration - The duration of a sample. All samples must have the same duration.
Throws:
java.lang.IllegalArgumentException - if sampleDuration is less than 1 or if the length of data is not dividable by sampleCount.
java.io.IOException - if writing the chunk failed.

writeSamples

public void writeSamples(int track,
                         int sampleCount,
                         byte[] data,
                         int off,
                         int len,
                         int sampleDuration)
                  throws java.io.IOException
Writes multiple sync samples from a byte array into a track.

This method does not inspect the contents of the samples. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
sampleCount - The number of samples.
data - The encoded sample data.
off - The start offset in the data.
len - The number of bytes to write. Must be dividable by sampleCount.
sampleDuration - The duration of a sample. All samples must have the same duration.
Throws:
java.lang.IllegalArgumentException - if the duration is less than 1.
java.io.IOException - if writing the image failed.

writeSamples

public void writeSamples(int track,
                         int sampleCount,
                         byte[] data,
                         int off,
                         int len,
                         int sampleDuration,
                         boolean isSync)
                  throws java.io.IOException
Writes multiple samples from a byte array into a track.

This method does not inspect the contents of the data. The contents has to match the format and dimensions of the media in this track.

Parameters:
track - The track index.
sampleCount - The number of samples.
data - The encoded sample data.
off - The start offset in the data.
len - The number of bytes to write. Must be dividable by sampleCount.
sampleDuration - The duration of a sample. All samples must have the same duration.
isSync - Whether the samples are sync samples. All samples must either be sync samples or non-sync samples.
Throws:
java.lang.IllegalArgumentException - if the duration is less than 1.
java.io.IOException - if writing the image failed.

close

public void close()
           throws java.io.IOException
Closes the movie file as well as the stream being filtered.

Throws:
java.io.IOException - if an I/O error has occurred

finish

public void finish()
            throws java.io.IOException
Finishes writing the contents of the QuickTime output stream without closing the underlying stream. Use this method when applying multiple filters in succession to the same output stream.

Throws:
java.lang.IllegalStateException - if the dimension of the video track has not been specified or determined yet.
java.io.IOException - if an I/O exception has occurred

Copyright 2011-01-06 Werner Randelshofer