/* This function imports the avi represented by the AVFormatContext to the movie media represented * in the map function. The aviheader_offset is used to calculate the packet offset from the * beginning of the file. It returns whether it was successful or not (i.e. whether the file had an index) */ int import_using_index(ff_global_ptr storage, int *hadIndex, TimeValue *addedDuration) { int j, k, l; NCStream *map; NCStream *ncstr; AVFormatContext *ic; AVStream *stream; AVCodecContext *codec; SampleReference64Ptr sampleRec; int64_t header_offset, offset, duration; short flags; int sampleNum; ComponentResult result = noErr; map = storage->stream_map; ic = storage->format_context; header_offset = storage->header_offset; if(*hadIndex == 0) goto bail; //FLVs have unusable indexes, so don't even bother. if(storage->componentType == 'FLV ') goto bail; /* process each stream in ic */ for(j = 0; j < ic->nb_streams; j++) { ncstr = &map[j]; stream = ncstr->str; codec = stream->codec; /* no stream we can read */ if(!ncstr->valid) continue; /* no index, we might as well skip */ if(stream->nb_index_entries == 0) continue; sampleNum = 0; ncstr->sampleTable = calloc(stream->nb_index_entries, sizeof(SampleReference64Record)); /* now parse the index entries */ for(k = 0; k < stream->nb_index_entries; k++) { /* file offset */ offset = header_offset + stream->index_entries[k].pos; /* flags */ flags = 0; if((stream->index_entries[k].flags & AVINDEX_KEYFRAME) == 0) flags |= mediaSampleNotSync; sampleRec = &ncstr->sampleTable[sampleNum++]; /* set as many fields in sampleRec as possible */ sampleRec->dataOffset.hi = offset >> 32; sampleRec->dataOffset.lo = (uint32_t)offset; sampleRec->dataSize = stream->index_entries[k].size; sampleRec->sampleFlags = flags; /* some samples have a data_size of zero. if that's the case, ignore them * they seem to be used to stretch the frame duration & are already handled * by the previous pkt */ if(sampleRec->dataSize <= 0) { sampleNum--; continue; } /* switch for the remaining fields */ if(codec->codec_type == AVMEDIA_TYPE_VIDEO) { /* Calculate the frame duration */ duration = 1; for(l = k+1; l < stream->nb_index_entries; l++) { if(stream->index_entries[l].size > 0) break; duration++; } sampleRec->durationPerSample = map->base.num * duration; sampleRec->numberOfSamples = 1; } else if(codec->codec_type == AVMEDIA_TYPE_AUDIO) { /* FIXME: check if that's really the right thing to do here */ if(ncstr->vbr) { sampleRec->numberOfSamples = 1; if (k + 1 < stream->nb_index_entries) sampleRec->durationPerSample = (stream->index_entries[k+1].timestamp - stream->index_entries[k].timestamp) * ncstr->base.num; else if (sampleNum - 2 >= 0) // if we're at the last index entry, use the duration of the previous sample // FIXME: this probably could be better sampleRec->durationPerSample = ncstr->sampleTable[sampleNum-2].durationPerSample; } else { sampleRec->durationPerSample = 1; sampleRec->numberOfSamples = (stream->index_entries[k].size * ncstr->asbd.mFramesPerPacket) / ncstr->asbd.mBytesPerPacket; } } } if(sampleNum != 0) { /* Add all of the samples to the media */ AddMediaSampleReferences64(ncstr->media, ncstr->sampleHdl, sampleNum, ncstr->sampleTable, NULL); /* The index is both present and not empty */ *hadIndex = 1; } free(ncstr->sampleTable); } if(*hadIndex == 0) //No index, the remainder of this function will fail. goto bail; // insert media and set addedDuration; for(j = 0; j < storage->map_count && result == noErr; j++) { ncstr = &map[j]; if(ncstr->valid) { Media media = ncstr->media; Track track; TimeRecord time; TimeValue mediaDuration; TimeScale mediaTimeScale; TimeScale movieTimeScale; int startTime = map[j].str->index_entries[0].timestamp; mediaDuration = GetMediaDuration(media); mediaTimeScale = GetMediaTimeScale(media); movieTimeScale = GetMovieTimeScale(storage->movie); /* we could handle this stream. * convert the atTime parameter to track scale. * FIXME: check if that's correct */ time.value.hi = 0; time.value.lo = storage->atTime; time.scale = movieTimeScale; time.base = NULL; ConvertTimeScale(&time, mediaTimeScale); track = GetMediaTrack(media); result = InsertMediaIntoTrack(track, time.value.lo, 0, mediaDuration, fixed1); // set audio/video start delay // note str.start_time exists but is always 0 for AVI if (startTime) { TimeRecord startTimeRec; startTimeRec.value.hi = 0; startTimeRec.value.lo = startTime * map[j].str->time_base.num; startTimeRec.scale = map[j].str->time_base.den; startTimeRec.base = NULL; ConvertTimeScale(&startTimeRec, movieTimeScale); SetTrackOffset(track, startTimeRec.value.lo); } if(result != noErr) goto bail; time.value.hi = 0; time.value.lo = mediaDuration; time.scale = mediaTimeScale; time.base = NULL; ConvertTimeScale(&time, movieTimeScale); if(time.value.lo > *addedDuration) *addedDuration = time.value.lo; } } storage->loadedTime = *addedDuration; bail: return result; } /* import_using_index() */
/* Import function for movies that lack an index. * Supports progressive importing, but will not idle if maxFrames == 0. */ ComponentResult import_with_idle(ff_global_ptr storage, long inFlags, long *outFlags, int minFrames, int maxFrames, bool addSamples) { SampleReference64Record sampleRec; AVFormatContext *formatContext; AVCodecContext *codecContext; AVStream *stream; AVPacket packet; NCStream *ncstream; ComponentResult dataResult; //used for data handler operations that can fail. ComponentResult result; TimeValue minLoadedTime; TimeValue movieTimeScale = GetMovieTimeScale(storage->movie); int64_t availableSize, margin; long idling; int readResult, framesProcessed, i; int firstPts[storage->map_count]; short flags; formatContext = storage->format_context; result = noErr; minLoadedTime = -1; availableSize = 0; idling = (inFlags & movieImportWithIdle); framesProcessed = 0; if(idling) { //get the size of immediately available data if(storage->dataHandlerSupportsWideOffsets) { wide wideSize; dataResult = DataHGetAvailableFileSize64(storage->dataHandler, &wideSize); if(dataResult == noErr) availableSize = ((int64_t)wideSize.hi << 32) + wideSize.lo; } else { long longSize; dataResult = DataHGetAvailableFileSize(storage->dataHandler, &longSize); if(dataResult == noErr) availableSize = longSize; } } for(i = 0; i < storage->map_count; i++) { ncstream = &storage->stream_map[i]; Media media = ncstream->media; firstPts[i] = -1; if(media && ncstream->duration == -1) ncstream->duration = GetMediaDuration(media); } while((readResult = av_read_frame(formatContext, &packet)) == 0) { bool trustPacketDuration = true; int64_t dts = packet.dts; ncstream = &storage->stream_map[packet.stream_index]; stream = ncstream->str; codecContext = stream->codec; flags = 0; if (!ncstream->valid) continue; if((packet.flags & AV_PKT_FLAG_KEY) == 0) flags |= mediaSampleNotSync; if(IS_NUV(storage->componentType) && codecContext->codec_id == CODEC_ID_MP3) trustPacketDuration = false; if(IS_FLV(storage->componentType)) trustPacketDuration = false; memset(&sampleRec, 0, sizeof(sampleRec)); sampleRec.dataOffset.hi = packet.pos >> 32; sampleRec.dataOffset.lo = (uint32_t)packet.pos; sampleRec.dataSize = packet.size; sampleRec.sampleFlags = flags; if (packet.pos <= 0) continue; if(firstPts[packet.stream_index] < 0) firstPts[packet.stream_index] = packet.pts; if(packet.size > storage->largestPacketSize) storage->largestPacketSize = packet.size; if(sampleRec.dataSize <= 0) continue; if(codecContext->codec_type == AVMEDIA_TYPE_AUDIO && !ncstream->vbr) sampleRec.numberOfSamples = (packet.size * ncstream->asbd.mFramesPerPacket) / ncstream->asbd.mBytesPerPacket; else sampleRec.numberOfSamples = 1; //packet.duration; //add any samples waiting to be added if(ncstream->lastSample.numberOfSamples > 0) { //calculate the duration of the sample before adding it ncstream->lastSample.durationPerSample = (dts - ncstream->lastdts) * ncstream->base.num; AddMediaSampleReferences64(ncstream->media, ncstream->sampleHdl, 1, &ncstream->lastSample, NULL); } #if 0 if (0) { Codecprintf(NULL, "Stream:%d Pts:%lld Dts:%lld DtsUsed:%lld Pos:%lld Size:%d\n", packet.stream_index, packet.pts, packet.dts, dts, packet.pos, packet.size); Codecprintf(NULL, "Stream:%d Nsamples:%ld RealDuration:%d CalcDuration:%ld TimeDts:%lld TimeDurations:%lld FrameDts:%d FrameGuess:%lld\n", packet.stream_index, sampleRec.numberOfSamples, packet.duration, ncstream->lastSample.durationPerSample, packet.dts, ncstream->timeByDurations, (int)((packet.dts * stream->time_base.num * ncstream->asbd.mSampleRate) / stream->time_base.den), ncstream->timeByFrames); ncstream->timeByDurations += packet.duration; ncstream->timeByFrames += ncstream->asbd.mFramesPerPacket; } #endif ncstream->lastSample = sampleRec; ncstream->lastdts = packet.dts; // If this is a nuv file, then we want to set the duration to zero. // This is because the nuv container doesn't have the framesize info // for audio. if(packet.duration == 0 || !trustPacketDuration) { //no duration, we'll have to wait for the next packet to calculate it // keep the duration of the last sample, so we can use it if it's the last frame sampleRec.durationPerSample = ncstream->lastSample.durationPerSample; } else { ncstream->lastSample.numberOfSamples = 0; if(codecContext->codec_type == AVMEDIA_TYPE_AUDIO && !ncstream->vbr) sampleRec.durationPerSample = 1; else sampleRec.durationPerSample = ncstream->base.num * packet.duration; AddMediaSampleReferences64(ncstream->media, ncstream->sampleHdl, 1, &sampleRec, NULL); } framesProcessed++; //if we're idling, try really not to read past the end of available data //otherwise we will cause blocking i/o. if(idling && framesProcessed >= minFrames && availableSize > 0 && availableSize < storage->dataSize) { margin = availableSize - (packet.pos + packet.size); if(margin < (storage->largestPacketSize * 8)) { // 8x fudge factor for comfortable margin, could be tweaked. av_free_packet(&packet); break; } } av_free_packet(&packet); //stop processing if we've hit the max frame limit if(maxFrames > 0 && framesProcessed >= maxFrames) break; } if(readResult != 0) { //if readResult != 0, we've hit the end of the stream. //add any pending last frames. for(i = 0; i < formatContext->nb_streams; i++) { ncstream = &storage->stream_map[i]; if(ncstream->lastSample.numberOfSamples > 0) AddMediaSampleReferences64(ncstream->media, ncstream->sampleHdl, 1, &ncstream->lastSample, NULL); } } for(i = 0; i < storage->map_count && result == noErr; i++) { ncstream = &storage->stream_map[i]; Media media = ncstream->media; if(ncstream->valid && (addSamples || readResult != 0)) { Track track = GetMediaTrack(media); TimeScale mediaTimeScale = GetMediaTimeScale(media); TimeValue prevDuration = ncstream->duration; TimeValue mediaDuration = GetMediaDuration(media); TimeValue addedDuration = mediaDuration - prevDuration; TimeValue mediaLoadedTime = movieTimeScale * mediaDuration / mediaTimeScale; if(minLoadedTime == -1 || mediaLoadedTime < minLoadedTime) minLoadedTime = mediaLoadedTime; if(addedDuration > 0) { result = InsertMediaIntoTrack(track, -1, prevDuration, addedDuration, fixed1); } if (!prevDuration && firstPts[i] > 0) { TimeRecord startTimeRec; startTimeRec.value.hi = 0; startTimeRec.value.lo = firstPts[i] * formatContext->streams[i]->time_base.num; startTimeRec.scale = formatContext->streams[i]->time_base.den; startTimeRec.base = NULL; ConvertTimeScale(&startTimeRec, movieTimeScale); SetTrackOffset(track, startTimeRec.value.lo); } ncstream->duration = -1; } } //set the loaded time to the length of the shortest track. if(minLoadedTime > 0) storage->loadedTime = minLoadedTime; if(readResult != 0) { //remove the placeholder track if(storage->placeholderTrack != NULL) { DisposeMovieTrack(storage->placeholderTrack); storage->placeholderTrack = NULL; } //set the movie load state to complete, as well as mark the import output flag. storage->movieLoadState = kMovieLoadStateComplete; *outFlags |= movieImportResultComplete; } else { //if we're not yet done with the import, calculate the movie load state. int64_t timeToCompleteFile; //time until the file should be completely available, in terms of AV_TIME_BASE long dataRate = 0; dataResult = DataHGetDataRate(storage->dataHandler, 0, &dataRate); if(dataResult == noErr && dataRate > 0) { timeToCompleteFile = (AV_TIME_BASE * (storage->dataSize - availableSize)) / dataRate; if(storage->loadedTime > (10 * GetMovieTimeScale(storage->movie)) && timeToCompleteFile < (storage->format_context->duration * .85)) storage->movieLoadState = kMovieLoadStatePlaythroughOK; else storage->movieLoadState = kMovieLoadStatePlayable; } else { storage->movieLoadState = kMovieLoadStatePlayable; } *outFlags |= movieImportResultNeedIdles; } send_movie_changed_notification(storage->movie); //tell the idle manager to idle us again in 500ms. if(idling && storage->idleManager && storage->isStreamed) QTIdleManagerSetNextIdleTimeDelta(storage->idleManager, 1, 2); return(result); } /* import_with_idle() */
ComponentResult FFAvi_MovieImportDataRef(ff_global_ptr storage, Handle dataRef, OSType dataRefType, Movie theMovie, Track targetTrack, Track *usedTrack, TimeValue atTime, TimeValue *addedDuration, long inFlags, long *outFlags) { ComponentResult result = noErr; ByteIOContext *byteContext; AVFormatContext *ic = NULL; AVFormatParameters params; OSType mediaType; Media media; int count, hadIndex, j; /* make sure that in case of error, the flag movieImportResultComplete is not set */ *outFlags = 0; /* probe the format first */ UInt8 valid = 0; FFAvi_MovieImportValidateDataRef(storage, dataRef, dataRefType, &valid); if(valid != 255) goto bail; /* Prepare the iocontext structure */ result = url_open_dataref(&byteContext, dataRef, dataRefType, &storage->dataHandler, &storage->dataHandlerSupportsWideOffsets, &storage->dataSize); storage->isStreamed = dataRefType == URLDataHandlerSubType; require_noerr(result, bail); /* Open the Format Context */ memset(¶ms, 0, sizeof(params)); result = av_open_input_stream(&ic, byteContext, "", storage->format, ¶ms); require_noerr(result,bail); storage->format_context = ic; // AVIs without an index currently add a few entries to the index so it can // determine codec parameters. Check for index existence here before it // reads any packets. hadIndex = 1; for (j = 0; j < ic->nb_streams; j++) { if (ic->streams[j]->nb_index_entries <= 1) { hadIndex = 0; break; } } /* Get the Stream Infos if not already read */ result = av_find_stream_info(ic); // -1 means it couldn't understand at least one stream // which might just mean we don't have its video decoder enabled if(result < 0 && result != -1) goto bail; // we couldn't find any streams, bail with an error. if(ic->nb_streams == 0) { result = -1; //is there a more appropriate error code? goto bail; } //determine a header offset (needed by index-based import). result = determine_header_offset(storage); if(result < 0) goto bail; /* Initialize the Movie */ storage->movie = theMovie; if(inFlags & movieImportMustUseTrack) { storage->map_count = 1; prepare_track(storage, targetTrack, dataRef, dataRefType); } else { storage->map_count = ic->nb_streams; result = prepare_movie(storage, theMovie, dataRef, dataRefType); if (result != 0) goto bail; } /* replace the SampleDescription if user called MovieImportSetSampleDescription() */ if(storage->imgHdl) { for(j = 0; j < storage->map_count; j++) { NCStream ncstream = storage->stream_map[j]; GetMediaHandlerDescription(ncstream.media, &mediaType, NULL, NULL); if(mediaType == VideoMediaType && ncstream.sampleHdl) { DisposeHandle((Handle)ncstream.sampleHdl); ncstream.sampleHdl = (SampleDescriptionHandle)storage->imgHdl; } } } if(storage->sndHdl) { for(j = 0; j < storage->map_count; j++) { NCStream ncstream = storage->stream_map[j]; GetMediaHandlerDescription(ncstream.media, &mediaType, NULL, NULL); if(mediaType == SoundMediaType && ncstream.sampleHdl) { DisposeHandle((Handle)ncstream.sampleHdl); ncstream.sampleHdl = (SampleDescriptionHandle)storage->sndHdl; } } } count = 0; media = NULL; for(j = 0; j < storage->map_count; j++) { media = storage->stream_map[j].media; if(media) count++; } if(count > 1) *outFlags |= movieImportResultUsedMultipleTracks; /* The usedTrack parameter. Count the number of Tracks and set usedTrack if we operated * on a single track. Note that this requires the media to be set by track counting above*/ if(usedTrack && count == 1 && media) *usedTrack = GetMediaTrack(media); result = noErr; *addedDuration = 0; //attempt to import using indexes. result = import_using_index(storage, &hadIndex, addedDuration); require_noerr(result, bail); if(hadIndex) { //file had an index and was imported; we are done. *outFlags |= movieImportResultComplete; } else if(inFlags & movieImportWithIdle) { if(addedDuration && ic->duration > 0) { TimeScale movieTimeScale = GetMovieTimeScale(theMovie); *addedDuration = movieTimeScale * ic->duration / AV_TIME_BASE; //create a placeholder track so that progress displays correctly. create_placeholder_track(storage->movie, &storage->placeholderTrack, *addedDuration, dataRef, dataRefType); //give the data handler a hint as to how fast we need the data. //suggest a speed that's faster than the bare minimum. //if there's an error, the data handler probably doesn't support //this, so we can just ignore. DataHPlaybackHints(storage->dataHandler, 0, 0, -1, (storage->dataSize * 1.15) / ((double)ic->duration / AV_TIME_BASE)); } //import with idle. Decode a little bit of data now. import_with_idle(storage, inFlags, outFlags, 10, 300, true); } else { //QuickTime didn't request import with idle, so do it all now. import_with_idle(storage, inFlags, outFlags, 0, 0, true); } LoadExternalSubtitlesFromFileDataRef(dataRef, dataRefType, theMovie); bail: if(result == noErr) storage->movieLoadState = kMovieLoadStateLoaded; else storage->movieLoadState = kMovieLoadStateError; if (result == -1) result = invalidMovie; // a bit better error message return result; } /* FFAvi_MovieImportDataRef */