source: trunk/ff_MovieImport.c @ 1221

Revision 1221, 18.4 KB checked in by astrange, 4 years ago (diff)

Revert r1218.

It made opening regular xvid/mp3 AVI take forever.

Enabling the MP3 decoder would fix that, but it
breaks audio sync for at least one complete flv file.

This also brings back "reached max_analyze_duration/MAX_READ_SIZE"
warnings on nearly every file.

Really need to enable parsers, I think.

Refs #455.

Line 
1/*****************************************************************************
2*
3*  Avi Import Component QuickTime Component Interface
4*
5*  Copyright(C) 2006 Christoph Naegeli <chn1@mac.com>
6*
7*  This library is free software; you can redistribute it and/or
8*  modify it under the terms of the GNU Lesser General Public
9*  License as published by the Free Software Foundation; either
10*  version 2.1 of the License, or (at your option) any later version.
11
12*  This library is distributed in the hope that it will be useful,
13*  but WITHOUT ANY WARRANTY; without even the implied warranty of
14*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15*  Lesser General Public License for more details.
16
17*  You should have received a copy of the GNU Lesser General Public
18*  License along with this library; if not, write to the Free Software
19*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20*
21****************************************************************************/
22
23#include "ff_MovieImportVersion.h"
24#include "ff_private.h"
25#include "Codecprintf.h"
26#include "riff.h"
27#include "SubImport.h"
28#include "CommonUtils.h"
29
30/* This one is a little big in ffmpeg and private anyway */
31#define PROBE_BUF_SIZE 64
32
33#include <CoreServices/CoreServices.h>
34#include <QuickTime/QuickTime.h>
35#include <pthread.h>
36
37#define MOVIEIMPORT_BASENAME()          FFAvi_MovieImport
38#define MOVIEIMPORT_GLOBALS()           ff_global_ptr storage
39
40#define CALLCOMPONENT_BASENAME()        MOVIEIMPORT_BASENAME()
41#define CALLCOMPONENT_GLOBALS()         MOVIEIMPORT_GLOBALS()
42
43#define COMPONENT_DISPATCH_FILE         "ff_MovieImportDispatch.h"
44#define COMPONENT_UPP_SELECT_ROOT()     MovieImport
45
46#include <CoreServices/Components.k.h>
47#include <QuickTime/QuickTimeComponents.k.h>
48#include <QuickTime/ComponentDispatchHelper.c>
49
50#pragma mark -
51
52static int PerianLockMgrCallback(void **mutex, enum AVLockOp op)
53{
54        pthread_mutex_t **m = (pthread_mutex_t **)mutex;
55        int ret = 0;
56       
57        switch (op) {
58                case AV_LOCK_CREATE:
59                        *m = malloc(sizeof(pthread_mutex_t));
60                        ret = pthread_mutex_init(*m, NULL);
61                        break;
62                case AV_LOCK_OBTAIN:
63                        ret = pthread_mutex_lock(*m);
64                        break;
65                case AV_LOCK_RELEASE:
66                        ret = pthread_mutex_unlock(*m);
67                        break;
68                case AV_LOCK_DESTROY:
69                        ret = pthread_mutex_destroy(*m);
70                        free(*m);
71        }
72       
73        return ret;
74}
75
76#define REGISTER_MUXER(x) { \
77        extern AVOutputFormat x##_muxer; \
78                av_register_output_format(&x##_muxer); }
79#define REGISTER_DEMUXER(x) { \
80        extern AVInputFormat x##_demuxer; \
81                av_register_input_format(&x##_demuxer); }
82#define REGISTER_MUXDEMUX(x)  REGISTER_MUXER(x); REGISTER_DEMUXER(x)
83#define REGISTER_PROTOCOL(x) { \
84        extern URLProtocol x##_protocol; \
85                register_protocol(&x##_protocol); }
86
87#define REGISTER_ENCODER(x) { \
88        extern AVCodec x##_encoder; \
89                register_avcodec(&x##_encoder); }
90#define REGISTER_DECODER(x) { \
91        extern AVCodec x##_decoder; \
92                avcodec_register(&x##_decoder); }
93#define REGISTER_ENCDEC(x)  REGISTER_ENCODER(x); REGISTER_DECODER(x)
94
95#define REGISTER_PARSER(x) { \
96        extern AVCodecParser x##_parser; \
97                av_register_codec_parser(&x##_parser); }
98#define REGISTER_BSF(x) { \
99        extern AVBitStreamFilter x##_bsf; \
100                av_register_bitstream_filter(&x##_bsf); }
101
102void init_FFmpeg()
103{
104        /* This one is used because Global variables are initialized ONE time
105        * until the application quits. Thus, we have to make sure we're initialize
106        * the libavformat only once or we get an endlos loop when registering the same
107        * element twice!! */
108        static Boolean inited = FALSE;
109        int unlock = PerianInitEnter(&inited);
110       
111        /* Register the Parser of ffmpeg, needed because we do no proper setup of the libraries */
112        if(!inited) {
113                inited = TRUE;
114                avcodec_init();
115                av_lockmgr_register(PerianLockMgrCallback);
116
117                REGISTER_DEMUXER(avi);
118                REGISTER_DEMUXER(flv);
119                REGISTER_DEMUXER(tta);
120                REGISTER_DEMUXER(nuv);
121                register_parsers();
122               
123                REGISTER_DECODER(msmpeg4v1);
124                REGISTER_DECODER(msmpeg4v2);
125                REGISTER_DECODER(msmpeg4v3);
126                REGISTER_DECODER(mpeg4);
127                REGISTER_DECODER(h264);
128                REGISTER_DECODER(flv);
129                REGISTER_DECODER(flashsv);
130                REGISTER_DECODER(vp3);
131                REGISTER_DECODER(vp6);
132                REGISTER_DECODER(vp6f);
133                REGISTER_DECODER(h263i);
134                REGISTER_DECODER(huffyuv);
135                REGISTER_DECODER(ffvhuff);
136                REGISTER_DECODER(mpeg1video);
137                REGISTER_DECODER(mpeg2video);
138                REGISTER_DECODER(fraps);
139                REGISTER_DECODER(snow);
140                REGISTER_DECODER(nuv);
141               
142                REGISTER_DECODER(wmav1);
143                REGISTER_DECODER(wmav2);
144                REGISTER_DECODER(adpcm_swf);
145                REGISTER_DECODER(vorbis);
146                REGISTER_DECODER(mp1);
147                REGISTER_DECODER(mp2);
148                REGISTER_DECODER(tta);
149                REGISTER_DECODER(dca);
150                REGISTER_DECODER(nellymoser);
151               
152                REGISTER_DECODER(dvdsub);
153                REGISTER_DECODER(tscc);
154                REGISTER_DECODER(vp6a);
155                REGISTER_DECODER(zmbv);
156                REGISTER_DECODER(indeo2);
157                REGISTER_DECODER(indeo3);
158               
159                av_log_set_callback(FFMpegCodecprintf);
160        }
161       
162        PerianInitExit(unlock);
163}
164
165/************************************
166** Base Component Manager Routines **
167*************************************/
168
169ComponentResult FFAvi_MovieImportOpen(ff_global_ptr storage, ComponentInstance self)
170{
171        ComponentResult result;
172    ComponentDescription descout;
173       
174        /* Check for Mac OS 10.4 & QT 7 */
175        result = check_system();
176        require_noerr(result,bail);
177       
178    GetComponentInfo((Component)self, &descout, 0, 0, 0);
179       
180        storage = malloc(sizeof(ff_global_context));
181        if(!storage) goto bail;
182       
183        memset(storage, 0, sizeof(ff_global_context));
184        storage->ci = self;
185       
186        SetComponentInstanceStorage(storage->ci, (Handle)storage);
187       
188        storage->componentType = descout.componentSubType;
189        storage->movieLoadState = kMovieLoadStateLoading;
190bail:
191                return result;
192} /* FFAvi_MovieImportOpen() */
193
194ComponentResult FFAvi_MovieImportClose(ff_global_ptr storage, ComponentInstance self)
195{
196        /* Free all global storage */
197        if(storage->imgHdl)
198                DisposeHandle((Handle)storage->imgHdl);
199        if(storage->sndHdl)
200                DisposeHandle((Handle)storage->sndHdl);
201       
202        if(storage->stream_map)
203                av_free(storage->stream_map);
204       
205        if(storage->format_context)
206                av_close_input_file(storage->format_context);
207       
208        int i;
209        for(i=0; i<MAX_STREAMS; i++)
210        {
211                if(storage->firstFrames[i].data != NULL)
212                        av_free_packet(storage->firstFrames + i);
213        }
214       
215        if(storage)
216                free(storage);
217       
218        return noErr;
219} /* FFAvi_MovieImportClose() */
220
221ComponentResult FFAvi_MovieImportVersion(ff_global_ptr storage)
222{
223        return kFFAviComponentVersion;
224} /* FFAvi_MovieImportVersion() */
225
226#pragma mark -
227
228/********************************
229** Configuration Stuff is here **
230*********************************/
231
232ComponentResult FFAvi_MovieImportGetMIMETypeList(ff_global_ptr storage, QTAtomContainer *mimeInfo)
233{
234        ComponentResult err = noErr;
235        switch (storage->componentType) {
236                case 'VfW ':
237                case 'VFW ':
238                case 'AVI ':
239                        err = GetComponentResource((Component)storage->ci, 'mime', kAVIthngResID, (Handle*)mimeInfo);
240                        break;
241                case 'FLV ':
242                        err = GetComponentResource((Component)storage->ci, 'mime', kFLVthngResID, (Handle*)mimeInfo);
243                        break;
244                case 'TTA ':
245                        err = GetComponentResource((Component)storage->ci, 'mime', kTTAthngResID, (Handle*)mimeInfo);
246                        break;
247                case 'NUV ':
248                        err = GetComponentResource((Component)storage->ci, 'mime', kNuvthngResID, (Handle*)mimeInfo);
249                        break;
250                default:
251                        err = GetComponentResource((Component)storage->ci, 'mime', kAVIthngResID, (Handle*)mimeInfo);
252                        break;
253        }
254        return err;
255} /* FFAvi_MovieImportGetMIMETypeList() */
256
257ComponentResult FFAvi_MovieImportSetProgressProc(ff_global_ptr storage, MovieProgressUPP prog, long refcon)
258{
259        storage->prog = prog;
260        storage->refcon = refcon;
261       
262        if(!storage->prog)
263                storage->refcon = 0;
264       
265        return noErr;
266} /* FFAvi_MovieImportSetProgressProc() */
267
268ComponentResult FFAvi_MovieImportSetSampleDescription(ff_global_ptr storage, SampleDescriptionHandle desc, OSType media)
269{
270        ComponentResult result = noErr;
271       
272        switch(media) {
273                case VideoMediaType:
274                        if(storage->imgHdl) /* already one stored */
275                                DisposeHandle((Handle)storage->imgHdl);
276                       
277                        storage->imgHdl = (ImageDescriptionHandle)desc;
278                        if(storage->imgHdl)
279                                result = HandToHand((Handle*)&storage->imgHdl);
280                                break;
281                case SoundMediaType:
282                        if(storage->sndHdl)
283                                DisposeHandle((Handle)storage->sndHdl);
284                       
285                        storage->sndHdl = (SoundDescriptionHandle)desc;
286                        if(storage->sndHdl)
287                                result = HandToHand((Handle*)&storage->sndHdl);
288                                break;
289                default:
290                        break;
291        }
292       
293        return result;
294} /* FFAvi_MovieImportSetSampleDescription() */
295
296ComponentResult FFAvi_MovieImportGetDestinationMediaType(ff_global_ptr storage, OSType *mediaType)
297{
298        *mediaType = VideoMediaType;
299        return noErr;
300} /* FFAvi_MovieImportGetDestinationMediaType() */
301
302#pragma mark -
303
304/****************************
305** Import Stuff comes here **
306*****************************/
307
308ComponentResult FFAvi_MovieImportValidate(ff_global_ptr storage, const FSSpec *theFile, Handle theData, Boolean *valid)
309{
310        ComponentResult result = noErr;
311        Handle dataRef = NULL;
312        OSType dataRefType;
313       
314        *valid = FALSE;
315       
316        result = QTNewDataReferenceFromFSSpec(theFile, 0, &dataRef, &dataRefType);
317        require_noerr(result,bail);
318       
319        result = MovieImportValidateDataRef(storage->ci, dataRef, dataRefType, (UInt8*)valid);
320       
321bail:
322                if(dataRef)
323                        DisposeHandle(dataRef);
324       
325        return result;
326} /* FFAvi_MovieImportValidate() */
327
328// this function is a small avi parser to get the video track's fourcc as
329// fast as possible, so we can decide whether we can handle the necessary
330// image description extensions for the format in ValidateDataRef() quickly
331OSType get_avi_strf_fourcc(ByteIOContext *pb)
332{
333        OSType tag, subtag;
334        unsigned int size;
335       
336        if (get_be32(pb) != 'RIFF')
337                return 0;
338       
339        // file size
340        get_le32(pb);
341       
342        if (get_be32(pb) != 'AVI ')
343                return 0;
344       
345        while (!url_feof(pb)) {
346                tag = get_be32(pb);
347                size = get_le32(pb);
348               
349                if (tag == 'LIST') {
350                        subtag = get_be32(pb);
351                       
352                        // only lists we care about: hdrl & strl, so skip the rest
353                        if (subtag != 'hdrl' && subtag != 'strl')
354                                url_fskip(pb, size - 4 + (size & 1));
355                       
356                } else if (tag == 'strf') {
357                        // 16-byte offset to the fourcc
358                        url_fskip(pb, 16);
359                        return get_be32(pb);
360                } else if (tag == 'strh'){
361                        // 4-byte offset to the fourcc
362                        OSType tag1 = get_be32(pb);
363                        if(tag1 == 'iavs' || tag1 == 'ivas')
364                                return get_be32(pb);
365                        else
366                                url_fskip(pb, size + (size & 1) - 4);
367                } else
368                        url_fskip(pb, size + (size & 1));
369        }
370        return 0;
371}
372
373ComponentResult FFAvi_MovieImportValidateDataRef(ff_global_ptr storage, Handle dataRef, OSType dataRefType, UInt8 *valid)
374{
375        ComponentResult result = noErr;
376        DataHandler dataHandler = NULL;
377        uint8_t buf[PROBE_BUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE] = {0};
378        AVProbeData pd;
379        ByteIOContext *byteContext;
380
381        /* default */
382        *valid = 0;
383       
384        /* Get a data handle and read a probe of data */
385        result = OpenADataHandler(dataRef, dataRefType, NULL, 0, NULL, kDataHCanRead, &dataHandler);
386        if(result || !dataHandler) goto bail;
387       
388        pd.buf = buf;
389        pd.buf_size = PROBE_BUF_SIZE;
390       
391        result = DataHScheduleData(dataHandler, (Ptr)pd.buf, 0, PROBE_BUF_SIZE, 0, NULL, NULL);
392        require_noerr(result,bail);
393       
394        init_FFmpeg();
395        storage->format = av_probe_input_format(&pd, 1);
396        if(storage->format != NULL) {
397                *valid = 255; /* This means we can read the data */
398               
399                /* we don't do MJPEG's Huffman tables right yet, and DV video seems to have
400                an aspect ratio coded in the bitstream that ffmpeg doesn't read, so let Apple's
401                AVI importer handle AVIs with these two video types */
402                if (IS_AVI(storage->componentType)) {
403                        /* Prepare the iocontext structure */
404                        result = url_open_dataref(&byteContext, dataRef, dataRefType, NULL, NULL, NULL);
405                        require_noerr(result, bail);
406                       
407                        OSType fourcc = get_avi_strf_fourcc(byteContext);
408                        enum CodecID id = ff_codec_get_id(ff_codec_bmp_tags, BSWAP(fourcc));
409                       
410                        if (id == CODEC_ID_MJPEG || id == CODEC_ID_DVVIDEO || id == CODEC_ID_RAWVIDEO || id == CODEC_ID_MSVIDEO1 || id == CODEC_ID_MSRLE)
411                                *valid = 0;
412                       
413                        url_fclose(byteContext);
414                }
415        }
416               
417bail:
418                if(dataHandler)
419                        CloseComponent(dataHandler);
420       
421        return result;
422} /* FFAvi_MovieImportValidateDataRef() */
423
424
425ComponentResult FFAvi_MovieImportFile(ff_global_ptr storage, const FSSpec *theFile, Movie theMovie, Track targetTrack,
426                                                                          Track *usedTrack, TimeValue atTime, TimeValue *addedDuration, long inFlags, long *outFlags)
427{
428        ComponentResult result;
429        Handle dataRef = NULL;
430        OSType dataRefType;
431        FSRef theFileFSRef;
432       
433        *outFlags = 0;
434       
435        result = QTNewDataReferenceFromFSSpec(theFile, 0, &dataRef, &dataRefType);
436        require_noerr(result,bail);
437       
438        result = MovieImportDataRef(storage->ci, dataRef, dataRefType, theMovie, targetTrack, usedTrack, atTime, addedDuration,
439                                                                inFlags, outFlags);
440        require_noerr(result, bail);
441       
442        result = FSpMakeFSRef(theFile, &theFileFSRef);
443        require_noerr(result, bail);
444               
445bail:
446                if(dataRef)
447                        DisposeHandle(dataRef);
448       
449        return result;
450} /* FFAvi_MovieImportFile() */
451
452ComponentResult FFAvi_MovieImportDataRef(ff_global_ptr storage, Handle dataRef, OSType dataRefType, Movie theMovie, Track targetTrack,
453                                                                                 Track *usedTrack, TimeValue atTime, TimeValue *addedDuration, long inFlags, long *outFlags)
454{
455        ComponentResult result = noErr;
456        ByteIOContext *byteContext;
457        AVFormatContext *ic = NULL;
458        AVFormatParameters params;
459        OSType mediaType;
460        Media media;
461        int count, hadIndex, j;
462               
463        /* make sure that in case of error, the flag movieImportResultComplete is not set */
464        *outFlags = 0;
465       
466        /* probe the format first */
467        UInt8 valid = 0;
468        FFAvi_MovieImportValidateDataRef(storage, dataRef, dataRefType, &valid);
469        if(valid != 255)
470                goto bail;
471                       
472        /* Prepare the iocontext structure */
473        result = url_open_dataref(&byteContext, dataRef, dataRefType, &storage->dataHandler, &storage->dataHandlerSupportsWideOffsets, &storage->dataSize);
474        storage->isStreamed = dataRefType == URLDataHandlerSubType;
475        require_noerr(result, bail);
476       
477        /* Open the Format Context */
478        memset(&params, 0, sizeof(params));
479        result = av_open_input_stream(&ic, byteContext, "", storage->format, &params);
480        require_noerr(result,bail);
481        storage->format_context = ic;
482       
483        /* Get the Stream Infos if not already read */
484        result = av_find_stream_info(ic);
485       
486        // -1 means it couldn't understand at least one stream
487        // which might just mean we don't have its video decoder enabled
488        if(result < 0 && result != -1)
489                goto bail;
490       
491        // we couldn't find any streams, bail with an error.
492        if(ic->nb_streams == 0) {
493                result = -1; //is there a more appropriate error code?
494                goto bail;
495        }
496       
497        //determine a header offset (needed by index-based import).
498        result = determine_header_offset(storage);
499        if(result < 0)
500                goto bail;
501       
502        /* Initialize the Movie */
503        storage->movie = theMovie;
504        if(inFlags & movieImportMustUseTrack) {
505                storage->map_count = 1;
506                prepare_track(storage, targetTrack, dataRef, dataRefType);
507        } else {
508                storage->map_count = ic->nb_streams;
509                result = prepare_movie(storage, theMovie, dataRef, dataRefType);
510                if (result != 0)
511                        goto bail;
512        }
513       
514        /* replace the SampleDescription if user called MovieImportSetSampleDescription() */
515        if(storage->imgHdl) {
516                for(j = 0; j < storage->map_count; j++) {
517                        NCStream ncstream = storage->stream_map[j];
518                        GetMediaHandlerDescription(ncstream.media, &mediaType, NULL, NULL);
519                        if(mediaType == VideoMediaType && ncstream.sampleHdl) {
520                                DisposeHandle((Handle)ncstream.sampleHdl);
521                                ncstream.sampleHdl = (SampleDescriptionHandle)storage->imgHdl;
522                        }
523                }
524        }
525        if(storage->sndHdl) {
526                for(j = 0; j < storage->map_count; j++) {
527                        NCStream ncstream = storage->stream_map[j];
528                        GetMediaHandlerDescription(ncstream.media, &mediaType, NULL, NULL);
529                        if(mediaType == SoundMediaType && ncstream.sampleHdl) {
530                                DisposeHandle((Handle)ncstream.sampleHdl);
531                                ncstream.sampleHdl = (SampleDescriptionHandle)storage->sndHdl;
532                        }
533                }
534        }
535       
536        count = 0; media = NULL;
537        for(j = 0; j < storage->map_count; j++) {
538                media = storage->stream_map[j].media;
539                if(media)
540                        count++;
541        }
542       
543        if(count > 1)
544                *outFlags |= movieImportResultUsedMultipleTracks;
545       
546        /* The usedTrack parameter. Count the number of Tracks and set usedTrack if we operated
547                * on a single track. Note that this requires the media to be set by track counting above*/
548        if(usedTrack && count == 1 && media)
549                *usedTrack = GetMediaTrack(media);
550       
551        result = noErr;
552
553        *addedDuration = 0;
554       
555        //attempt to import using indexes.
556        result = import_using_index(storage, &hadIndex, addedDuration);
557        require_noerr(result, bail);
558       
559        if(hadIndex) {
560                //file had an index and was imported; we are done.
561                *outFlags |= movieImportResultComplete;
562               
563        } else if(inFlags & movieImportWithIdle) {
564                if(addedDuration && ic->duration > 0) {
565                        TimeScale movieTimeScale = GetMovieTimeScale(theMovie);
566                        *addedDuration = movieTimeScale * ic->duration / AV_TIME_BASE;
567                       
568                        //create a placeholder track so that progress displays correctly.
569                        create_placeholder_track(storage->movie, &storage->placeholderTrack, *addedDuration, dataRef, dataRefType);
570                       
571                        //give the data handler a hint as to how fast we need the data.
572                        //suggest a speed that's faster than the bare minimum.
573                        //if there's an error, the data handler probably doesn't support
574                        //this, so we can just ignore.
575                        DataHPlaybackHints(storage->dataHandler, 0, 0, -1, (storage->dataSize * 1.15) / ((double)ic->duration / AV_TIME_BASE));
576                }
577                       
578                //import with idle. Decode a little bit of data now.
579                import_with_idle(storage, inFlags, outFlags, 10, 300, true);
580        } else {
581                //QuickTime didn't request import with idle, so do it all now.
582                import_with_idle(storage, inFlags, outFlags, 0, 0, true);                       
583        }
584       
585        LoadExternalSubtitlesFromFileDataRef(dataRef, dataRefType, theMovie);
586
587bail:
588        if(result == noErr)
589                storage->movieLoadState = kMovieLoadStateLoaded;
590        else
591                storage->movieLoadState = kMovieLoadStateError;
592               
593        if (result == -1)
594                result = invalidMovie; // a bit better error message
595       
596        return result;
597} /* FFAvi_MovieImportDataRef */
598
599ComponentResult FFAvi_MovieImportSetIdleManager(ff_global_ptr storage, IdleManager im) {
600        storage->idleManager = im;
601        return noErr;
602}
603
604ComponentResult FFAvi_MovieImportIdle(ff_global_ptr storage, long inFlags, long *outFlags) {
605        ComponentResult err = noErr;
606        TimeValue currentIdleTime = GetMovieTime(storage->movie, NULL);
607        TimeScale movieTimeScale = GetMovieTimeScale(storage->movie);
608        int addSamples = false;
609       
610        storage->idlesSinceLastAdd++;
611       
612        if (currentIdleTime == storage->lastIdleTime && storage->idlesSinceLastAdd > 5 || 
613                storage->loadedTime < currentIdleTime + 5*movieTimeScale)
614        {
615                storage->idlesSinceLastAdd = 0;
616                addSamples = true;
617        }
618       
619        err = import_with_idle(storage, inFlags | movieImportWithIdle, outFlags, 0, 1000, addSamples);
620       
621        storage->lastIdleTime = currentIdleTime;
622        return err;
623}
624
625ComponentResult FFAvi_MovieImportGetLoadState(ff_global_ptr storage, long *importerLoadState) {
626        *importerLoadState = storage->movieLoadState;
627        return(noErr);
628}
629
630ComponentResult FFAvi_MovieImportGetMaxLoadedTime(ff_global_ptr storage, TimeValue *time) {
631        *time = storage->loadedTime;
632        return(noErr);
633}
Note: See TracBrowser for help on using the repository browser.