Chapter 29. File I/O and Editing Movies with the Movie Library

This chapter describes how to set up a Movie Library application and how to use the Movie Library routines for handling file input/output (I/O), editing, compression, and other basic tasks. Playback is discussed in the next chapter.

The Movie Library provides these basic file I/O capabilities:

Initializing a Movie Library Application

This section explains the basic program setup you will use. To set up a Movie Library application, you need to:

  • open a file descriptor for the movie (if you are using a file descriptor)

  • create a new movie or open an existing movie

  • test a file to determine whether it is a movie file

  • set and get movie properties

  • add, remove, and find tracks

  • allocate buffers for data passed to and from the Movie Library

Each of these tasks is described in detail in the subsections that follow. It's a good idea to familiarize yourself with the setup procedures described in this section because you'll be performing most of these tasks in every application that works with movies.

Basic routines for creating, verifying, and opening movies are available for each type of interface: filename, file descriptor, and memory-mapped files. Each of these routines features a similar API but has the appropriate arguments for the specified method.

Figure 29-1 shows a diagram of the file I/O routines in the Movie Library.

Figure 29-1. Movie Library File I/O Routines


mvCreateFile(), mvCreateFD(), and mvCreateMem() create a new empty movie, initialized with the given parameters, as set by mvSetMovieDefaults() or by the DM Library routines, and return an identifier for the new movie. Any movie that was already present in the file or memory location will be destroyed.

mvIsMovieFile(), mvIsMovieFD(), and mvIsMovieMem() test whether a movie is present in a file or in memory. Only movies in supported formats (Silicon Graphics and QuickTime) are recognized.

mvOpenFile(), mvOpenFD(), and mvOpenMem() read an existing movie, create a movie instance in memory that holds information about it, and then return an identifier for the new movie.

The following two calls are used when the file I/O or editing is completed:

mvWrite() 

flushes all changes that have been made to a movie and makes sure that they are written to the file, but does not close the file

mvClose() 

flushes all changes that have been made to the movie and makes sure that they are written to the file, and then destroys the movie instance

Each of these calls is described in detail in the sections that follow.

Using File Descriptors with Movies

File descriptors can be used to work with movie files on disk, CD-ROM, or DAT, or with embedded or previously opened movie files.


Note: The Movie Library does not support embedded QuickTime files.

Use the IRIX open() system call (see the open(2) man page) to open a file descriptor.

The movie instance inherits one of three file access modes, which are associated with the file descriptor:

O_RDONLY 

opens a read-only movie file

O_WRONLY 

opens a write-only movie file

O_RDWR 

opens a read-write movie file

The file access mode determines which operations are possible for a particular movie. You can't write data to a movie opened as a read-only file. Similarly, you can't play a movie that has been opened for writing.

Before opening the movie, the file pointer associated with the file descriptor should be positioned at the beginning of the movie file. If the movie data is embedded within a file, such as an application-specific file format containing a movie as a data chunk, use the IRIX lseek() system call (see the lseek(2) man page) to seek the file descriptor to the beginning of the movie data. For example, a movie file might be embedded in the file of a word-processing program. When the word processing program is ready to access the movie, it seeks to the position of the movie within its file and passes the file descriptor (fd) to the Movie Library.

Creating a New Movie

You can create a new movie and associate it with a file descriptor, file name, or memory location. There is a function call corresponding to each type.

mvCreateFD()

creates a movie using a file descriptor returned by

open()

 

mvCreateFile()

creates a movie using a file name

 

mvCreateMem()

creates a memory-mapped movie

The function prototypes are:

DMstatus  mvCreateFD   ( int          fd, 
                         DMparams*    params,
                         DMparams*    returnParamsSetOrNull,
                         MVid*        returnMovie )
DMstatus  mvCreateFile ( const char*  filename,
                         DMparams*    params,
                         DMparams*    returnParamsSetOrNull,
                         MVid*        returnMovie )
DMstatus  mvCreateMem ( void*        pointer, 
                        size_t       size,
                        DMparams*    params,
                        DMparams*    returnParamsSetOrNull,
                        MVid*        returnMovie )

where:

fd 

is a file descriptor returned by the open(2) system call that is assigned to the new movie

filename 

is a filename assigned to the new movie

pointer 

is a pointer to a memory location that is assigned to the new movie

size 

is the size of the block of memory to use—the Movie Library will not read or write beyond this block size

params 

is a pointer to parameters that describe the movie attributes

returnParamsSetOrNull 


is a pointer to a parameter-value list into which the Movie Library loads those parameters and values that it recognized and was able to set; if returnParamsSetOrNull is NULL, the Movie Library will not return such a list

returnMovie 

is a pointer into which the movie identifier is returned

Example 29-1 is a code fragment that shows how to create a movie.

Example 29-1. Creating a Movie

#include <movie.h>

void CreateMovie()
{
    DMparams* params;
    MVid      movie;

    if ( dmParamsCreate( &params ) != DM_SUCCESS ) {
        /* handle error */
    }

    if ( mvSetMovieDefaults( params, MV_FORMAT_SGI_3 )
         != DM_SUCCESS ) {
        /* handle error */
    }

    if ( mvCreateFile( "new-movie", params, NULL, &movie )
         != DM_SUCCESS ) {
        /* handle error */
    }

    /* Add tracks, insert frames, etc. */
}


Checking for the Presence of a Movie

Before attempting to open an existing movie file from a file descriptor, file name, or memory, your application should check to see whether a movie is present. There is a separate function call to verify the existence of a movie for each source type.

mvIsMovieFD()

checks for a movie identified by the given file descriptor

mvIsMovieFile()

checks for a movie identified by the given file name

mvIsMovieMem()

checks for a movie stored in the given memory location

Their function prototypes are:

DMboolean mvIsMovieFD ( int fd )
DMboolean mvIsMovieFile ( const char* filename )
DMboolean mvIsMovieMem ( void* pointer, size_t size )

where:

fd  

is the file descriptor of the movie file you are checking

filename  

is the name of the movie file you are checking

pointer  

is a pointer to a memory location where the movie file you are checking is stored

size 

is the size of the block of memory to use—the Movie Library will not read or write beyond this block size

DM_TRUE is returned if the given file is a movie file; otherwise, DM_FALSE is returned.

The following code fragment determines whether a given file is a movie file and prints an error message if it is not:

if ( !mvIsMovieFile( filename ) ) {
    PrintError ("Not a movie file.");
}

Opening an Existing Movie

Movies can be opened from file descriptors, filenames, or memory. There is a function call corresponding to each source type:

mvOpenFD()

opens a movie using a file descriptor that has already been obtained from another source, such as open(2)

mvOpenFile()

opens a movie from a file name

 

mvOpenMem()

opens a memory-mapped movie


Opening a Movie from a File Descriptor

To open a movie file from a file descriptor, call mvOpenFD(). Its function prototype is:

DMstatus mvOpenFD (int fd, MVid* returnMovie)

where:

fd 

is a file descriptor that references the movie file you want to open

returnMovie 

is a pointer into which the movie identifier is returned

Opening a Movie from a Filename

To open a movie from a filename, call mvOpenFile(). Its function prototype is:

DMstatus mvOpenFile ( const char* filename, int oflag,
                      MVid* returnMovie )

where:

filename 

is the filename of the movie file you want to open

oflag 

is the file access mode, either O_RDONLY or O_RDWR

returnMovie 

is a pointer into which the movie identifier is returned

Opening Memory-mapped Movies

To open a movie that resides entirely in memory, beginning at the location pointed to by pointer, call mvOpenMem(). Its function prototype is:

DMstatus mvOpenMem ( void* pointer, size_t size, MVid*
                     returnMovie )

where:

pointer 

is a pointer to the starting memory location of a movie

size 

is the size of the memory buffer—the Movie Library will not read or write beyond this block size

returnMovie 

is a pointer into which the movie identifier is returned

Your application must allocate and free the memory buffer used by mvOpenMem().

Adding, Locating, and Deleting Audio and Image Tracks

The operations that can be performed on a track include:

  • adding a new track

  • removing an existing track

  • finding a track

  • mapping frames between tracks in the same movie or another movie

  • reading and writing data in a track

  • editing operations (copying from one movie to another)

Adding an Audio or Image Track to a Movie

To add a track to a movie, call mvAddTrack(). Its function prototype is:

DMstatus mvAddTrack ( MVid movie, DMmedium medium, DMparams* params,
                   DMparams* returnParamsSetOrNull, MVid* returnTrack )

where:

medium 

is the type of track, either DM_AUDIO or DM_IMAGE

params 

is a pointer to a parameter-value list for configuring the new track, which should be filled in using either dmSetImageDefaults() or dmSetAudioDefaults(), depending on the medium type

returnParamsSetOrNull 


is a pointer to a parameter-value list into which the Movie Library loads those parameters and values that it recognized and was able to set—if returnParamsSetOrNull is NULL, the Movie Library will not return such a list

returnTrack 

is a pointer into which the ID of the newly added track is returned

DM_SUCCESS is returned if the new track was created successfully; otherwise DM_FAILURE is returned.

The DM_IMAGE_COMPRESSION parameter that is given when creating an image track is important to get right, because different settings can result in large differences in image quality and in playback performance. See “Getting the Image Compression Scheme” in Chapter 28 for information on choosing a compression setting when configuring an image track.

Example 29-2 shows how to add an audio track to a movie.

Example 29-2. Adding an Audio Track to a Movie

#include <movie.h>

void AddAudioTrack( MVid movie )
{
    DMparams* audioParams;
    MVid      audioTrack;

    if ( dmParamsCreate( &audioParams ) != DM_SUCCESS ) {
        /* handle error */
    }
    if ( dmSetAudioDefaults( audioParams,
                             8,     /* bits per sample */
                             22050, /* sample rate */
                             1,     /* number of channels */
                             ) != DM_SUCCESS ) {
        /* handle error */
    }
    if ( mvAddTrack( movie,
                     DM_AUDIO,
                     audioParams,
                     NULL,
                     &audioTrack ) != DM_SUCCESS ) {
         /* handle error */
    }
    /* Write audio data to track */
    /* with mvInsertFrames(3mv) */
}


Removing an Audio or Image Track from a Movie

To remove a track from a movie, which deletes all the data from the track, call mvRemoveTrack(). Its function prototype is:

DMstatus mvRemoveTrack(MVid movie, MVid track)

where:

track 

is the track you want to remove

DM_SUCCESS is returned if the track was removed successfully; otherwise, DM_FAILURE is returned.

Locating an Existing Track

To get a handle for a track that already exists, call mvFindTrackByMedium(). Its function prototype is:

DMstatus mvFindTrackByMedium ( MVid movie, DMmedium medium,
                               MVid* returnTrack )

where:

medium 

is the medium, either DM_AUDIO or DM_IMAGE

returnTrack 

is a pointer into which the track identifier is returned

mvFindTrackByMedium() returns DM_SUCCESS if a track of the given medium exists in the movie instance identified by movie; otherwise, it returns DM_FAILURE.

Mapping Frames from One Track to Another Track

Because the image and audio tracks are separate, your application must manage the synchronization between tracks when editing movies; for example, when deleting a portion of the image track, the corresponding portion of the audio track must be located and also deleted.

Figure 29-2 shows an image track and a corresponding audio track. (The audio track would actually have many more samples per frame, but for clarity, only a few samples are shown.)

Figure 29-2. Mapping Frames from One Track to Another


As shown in Figure 29-2, the frame numbers in one track do not have a one-to-one time correspondence with the frame numbers in another track. When mapping frame numbers from one track to another, the Movie Library chooses the frame that matches the starting time of the given frame. The frame boundaries are not necessarily aligned, so the mapping can differ by one frame, depending on which track you are mapping from. For example, in Figure 29-2, frame 3 in the image track maps to frame 9 of the audio track, but frame 9 of the audio track maps to frame 2 of the image track.

Before performing any operation on a frame in one track that affects its corresponding frame in the second track, you must locate the frame number in the second track that corresponds to the frame number in the first track.

To locate the frames in one track that correspond (in time) to frames from another track, call mvMapBetweenTracks(). mvMapBetweenTracks() determines which frame number in toTrack corresponds to the frame numbered fromFrameIndex in fromTrack and writes the result into the location pointed to by toFrameIndex. Its function prototype is:

DMstatus mvMapBetweenTracks ( MVid fromTrack, MVid toTrack,
                              MVframe fromFrameIndex,
                              MVframe* toFrameIndex )

where:

fromTrack 

is the track for which you want to locate a corresponding frame in toTrack

toTrack 

is the track in which to locate the frame number corresponding to the frame numbered fromFrameIndex in fromTrack

fromFrameIndex 


is the frame number in fromTrack for which you want to locate the corresponding frame number in toTrack

toFrameIndex 

is a pointer into which the frame number in toTrack that corresponds to the frame numbered fromFrameIndex in fromTrack is written

DM_SUCCESS is returned if a corresponding frame was located; otherwise, DM_FAILURE is returned.


Tip: You can also use mvMapBetweenTracks() to find corresponding frame numbers in tracks from two different movies.


Editing Movies

The Movie Library provides these editing operations:

  • optimizing a movie

  • inserting raw image or audio frames from a buffer into a track

  • reading frames from a track into a buffer

  • deleting frames from a track

  • reading and inserting compressed images directly

  • copying frames from one movie to another


Note: Movies should not be edited during playback.

When you edit a movie, the Movie Library changes pointers to frames rather than operating on the actual frames themselves. After a series of editing calls, the movie frames might not be arranged in the order in which they play, and there are probably empty spaces in the movie. Such a movie does not provide optimum playback, but you can optimize the movie as described in “Optimizing a Movie File” before closing it.

Optimizing a Movie File

To get the best playback performance from an edited movie, you should optimize the movie file after an editing session. Optimization streamlines the movie file by coalescing the empty space and by flattening the data structure into the most linear structure possible. This is especially helpful for minimizing the excessive seeks that occur during playback that are caused by editing a movie file repeatedly. Optimization does not occur in place; instead, the Movie Library makes a copy of the movie to optimize.

To optimize a movie, call mvOptimize(). Its function prototype is:

DMstatus mvOptimize ( MVid fromMovie, MVid toMovie )

where:

fromMovie 

is the movie you want to optimize

toMovie 

is a name for the optimized movie

Using a Buffer for Editing

This section explains how to use a buffer for editing. The routines described in this section work on uncompressed data; there is a similar group of routines described in “Reading and Inserting Compressed Images” for working with compressed data.

Allocating Buffers

Memory must be allocated to hold audio or image data that is passed to or obtained from the Movie Library. The buffer that is passed to these routines points to a block of memory that holds an array of frames. Your application is responsible for allocating a buffer large enough to hold the desired number of frames.


Note: A playback-only application that does not perform any file I/O operations need not allocate separate memory.

Use dmImageFrameSize() to determine the number of bytes needed to hold one frame of raw image data; similarly, use dmAudioFrameSize() to determine the number of bytes needed to hold one frame of raw audio data.

Before allocating memory, determine the required buffer size as demonstrated in Example 29-3. Allocate the appropriate amount of memory by using one of the IRIX system calls for memory allocation, such as malloc(), and then check the malloc() return to make sure there was enough memory. See the malloc(3X, 3C) man page for information about memory allocation. See the IRIX Programming Guide for information about using shared memory.

To determine the buffer size needed to store the uncompressed frames, multiply the number of frames by the frame size, as shown in Example 29-3.

Example 29-3. Determining What Size Buffer to Allocate

static void insertFrames( MVid theEditMovie,
                          MVid theEditTrack,
                          MVid theSourceMovie,
                          MVid theSourceTrack ) 
{
    MVframe editStartFrame;
    MVframe sourceStartFrame;
    MVframe numFrames = getNumEditFrames();
    size_t  insBuffSize;
    void    *insBuff;

    if ( getEditTrackType() == DM_IMAGE ) {
        insBuffSize = numFrames * 
                    dmImageFrameSize( mvGetParams( theSourceTrack ) );
    }

    else if ( getEditTrackType() == DM_AUDIO ) {
        insBuffSize = numFrames * 
                     dmAudioFrameSize( mvGetParams( theSourceTrack ) );
    }

    insBuff = malloc( ( int ) insBuffSize );
    if ( insBuff == NULL ) {
        fprintf( stderr, "%s: Unable to allocate insert buffer.\n",
                getProgramName() );
        exit( EXIT_FAILURE );
    /* insert frames using mvInsertFrames(3mv) */
    }


Inserting Raw Images and Audio from a Buffer into an Existing Track

You can insert raw image or audio data into an existing movie track—the Movie Library compresses the data as it is inserted into the track. To insert frames from a buffer into a track, call mvInsertFrames(). Its function prototype is:

DMstatus mvInsertFrames ( MVid track, MVframe frameIndex,
                          MVframe frameCount, size_t bufferSize,
                          void* buffer )

where:

track 

is the track into which you want to insert data

frameIndex 

is the frame in front of which you want to insert data

frameCount 

is the number of frames to insert

bufferSize 

is the size of the buffer

buffer 

is a pointer to a buffer that contains the data you want to insert into the track

When you insert frames into an existing track, the new frames are inserted in front of frameIndex. The existing frames immediately following the insertion point, including frame frameIndex, are shifted to the right to make room for the new frames.

Figure 29-3 shows two frames (N1 and N2) inserted at frameIndex 5 into a movie with 7 frames. Frames 5 and 6 move to make room for the new frames.

Figure 29-3. Inserting Frames into a Track


To achieve an effect similar to overwriting the existing data, you must first delete the unwanted frames by calling mvDeleteFrames() before inserting the new frames.

Reading Frames from a Movie into a Buffer for Uncompressed Data

To read a specified number of frames from an existing movie into a buffer that you have allocated for storing movie data, call mvReadFrames(). Its function prototype is:

DMstatus mvReadFrames ( MVid track, MVframe frameIndex,
                        MVframe frameCount,
                        size_t bufferSize, void* buffer)

where:

track 

is the track from which you want to read data

frameIndex 

is the first frame in the sequence that is to be read

frameCount 

is the number of frames of data to read

bufferSize 

is the size of the buffer, obtained by multiplying the number of frames by the value returned from dmAudioFrameSize() for the audio track or by the value returned from dmImageFrameSize() for the image track

buffer 

is a pointer to a buffer that you have allocated for storing the data


Note: The data is decompressed as it is read into the buffer. Use mvReadCompressedImage(), as described in “Reading and Inserting Compressed Images” to read compressed image frames directly into a buffer.


Deleting Frames from a Movie Track

To delete frames from a movie track, call mvDeleteFrames():

DMstatus mvDeleteFrames ( MVid track, MVframe frameIndex,
                          MVframe frameCount )

where:

track 

is the track from which you want to delete frames

frameIndex 

is the first frame in the sequence that is to be deleted

frameCount 

is the number of frames of data to delete

Reading and Inserting Compressed Images

The Movie Library has a special group of routines for working with compressed images. Performing editing operations on compressed images takes less disk space and less time than editing full resolution images. These routines operate on one frame of compressed data at a time because the size of compressed data can vary from frame to frame. Use these routines if you want to read or write compressed image data frame-by-frame, such as reading a frame at a time from a Cosmo Compress™ board into a movie.

Reading a Compressed Image from a Movie into a Buffer

To read a compressed image from an existing movie into a buffer that you have allocated for storing compressed data, call mvReadCompressedImage(). Its function prototype is:

DMstatus mvReadCompressedImage ( MVid track,
                                 MVframe frameIndex,
                                 size_t bufferSize,
                                 void* buffer )

where:

track 

is the movie track from which you want to read a compressed image frame

frameIndex 

is the frame number of the image you want to read

bufferSize 

is the size of the buffer

buffer 

is a pointer to a buffer that you have allocated for storing a compressed image frame

To determine the buffer size (in bytes) needed to hold a compressed image frame, call mvGetCompressedImageSize(). Its function prototype is:

size_t mvGetCompressedImageSize( MVid track,
                                 MVframe frameIndex )

where:

track 

is the movie track from which you want to read a compressed image frame

frameIndex 

is the frame whose image size you want to know

mvGetCompressedImageSize() returns the number of bytes that image number frameIndex requires.

Example 29-4 reads a compressed image from track into buffer.

Example 29-4. Reading a Compressed Image from a Movie into a Buffer

void* ReadFirstImage( MVid track )
{
    size_t size   = mvGetCompressedImageSize( track, 0 );
    void*  buffer = malloc( size );
    if ( buffer = NULL ) { /* handle error */}

    if ( mvReadCompressedImage( track,
                                0,
                                size,
                                buffer ) != DM_SUCCESS ) {
        /* handle error */
    }

    return buffer;
}


Inserting a Compressed Image from a Buffer into an Existing Track

To insert a compressed image from a buffer into an existing image track, call mvInsertCompressedImage(). Its function prototype is:

DMstatus mvInsertCompressedImage ( MVid track,
                                   MVframe frameIndex,
                                   size_t bufferSize,
                                   void* buffer )

where:

track 

is the track into which you want to insert a compressed image

frameIndex 

is the frame number in front of which the compressed frame is to be inserted

bufferSize 

is the size of the buffer

buffer 

is a pointer to buffer that contains the compressed image you want to write to the track

When you insert a compressed frame into an existing track, the new frame is inserted in front of frame frameIndex. The existing frames immediately following the insertion point, including frame frameIndex, are shifted to the right to make room for the new frame (see Figure 29-3 on page 629). To achieve an effect similar to overwriting the existing data, you must first delete the unwanted frames by calling mvDeleteFrames() before inserting the new frame.

Copying and Pasting Frames from One Movie into Another

The Movie Library has routines that let you copy frames from one movie track to another movie track without using a buffer. The two movies must have the same image frame rate and frame size—if they do not, an error is generated.

These routines also let you work directly with compressed data, without decompressing and recompressing the data. You can copy compressed frames from one movie into another, even if the two movies use different compression schemes.

Figure 29-4 shows image frames being pasted from one movie into another.

Figure 29-4. Pasting Image Frames from One Movie into Another Movie



Note: Because a movie's audio and image tracks are independent, the audio samples associated with the original images do not shift with the image frames (see Figure 29-4). Call mvMapBetweenTracks() to locate the audio associated with the displaced frames, then paste the audio frames in the proper location.

To copy a range of frames from one movie and paste them into another movie without overwriting existing data, call mvPasteFrames(). Its function prototype is:

DMstatus mvPasteFrames( MVid fromTrack,
                        MVframe fromFrameIndex,
                        MVframe frameCount,
                        MVid toTrack,
                        MVframe toFrameIndex )

where:

fromTrack 

is the source movie track from which frames are copied

fromFrameIndex 


is the first frame in the sequence of frames to be copied from the source movie

frameCount 

is the number of frames to copy and paste

toTrack 

is the destination movie track into which the copied frames are inserted

toFrameIndex 

is the frame in the destination movie in front of which the new frames will be pasted

When frames are pasted into a non-empty movie, the new frames are inserted in front of frame frameIndex. The existing frames immediately following the insertion point, including frame frameIndex, are shifted to the right to make room for the new frames.

To overwrite the existing data, first call mvDeleteFrames() to delete the unwanted frames before calling mvPasteFrames().

Finalizing Changes and Closing Movies

During an editing session, you can flush the changes to the file and make sure they are written into the file by calling mvWrite(). Data in tracks is always written immediately; mvWrite() flushes the header information. Its function prototype is:

DMstatus mvWrite( MVid movie )

mvWrite() returns DM_SUCCESS if it was able to write the file; otherwise, DM_FAILURE is returned.

When you have finished editing a movie, you write and close the file. You may choose to optimize the movie by calling mvOptimize() before closing it.

To close a movie file, call mvClose(), which flushes all changes that have been made to the movie and makes sure that they are written to the file, and then destroys the movie instance. Its function prototype is:

DMstatus mvClose( MVid movie )

mvClose() returns DM_SUCCESS if it was able to write and close the file; otherwise, DM_FAILURE is returned.