/* 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() */
OSErr makeMovieFromVideoFramesFile(char *inDestMovieFile) { Handle dataRef = NULL; OSType dataRefType; ImageDescriptionHandle videoDescH = NULL; OSErr err; #if TARGET_OS_WIN32 err = InitializeQTML(0); if ((err = GetMoviesError()) != noErr) goto bail; #endif err = EnterMovies(); if ((err = GetMoviesError()) != noErr) goto bail; // create a data reference from the full path to the video frames file makeDataRefFromFullPath(inDestMovieFile, &dataRef, &dataRefType); if (dataRef == NULL) goto bail; Movie m = NewMovie(0); if ((err = GetMoviesError()) != noErr) goto bail; // create the video track for the movie Track videoT = NewMovieTrack( m, Long2Fix(kFrameWidth), Long2Fix(kFrameHeight), kNoVolume); if ((err = GetMoviesError()) != noErr) goto bail; // create the video track media Media videoM = NewTrackMedia( videoT, VideoMediaType, kMediaTimeScale, dataRef, dataRefType); if ((err = GetMoviesError()) != noErr) goto bail; videoDescH = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription)); if (videoDescH == NULL) goto bail; // create the ImageDescription that will describe our video track media samples videoDescH[0]->idSize = sizeof(ImageDescription); videoDescH[0]->cType = kCodecType; // the codec type for your data videoDescH[0]->temporalQuality = codecNormalQuality; videoDescH[0]->spatialQuality = codecNormalQuality; videoDescH[0]->width = kFrameWidth; videoDescH[0]->height = kFrameHeight; videoDescH[0]->hRes = 72L << 16; videoDescH[0]->vRes = 72L << 16; videoDescH[0]->depth = 32; videoDescH[0]->clutID = -1; SampleReference64Record videoRef; videoRef.dataOffset.hi = 0; videoRef.dataOffset.lo = 0; videoRef.dataSize = (kFrameWidth*kFrameHeight*4) * kNumberOfSamples; videoRef.durationPerSample = kDurationPerSample; videoRef.numberOfSamples = kNumberOfSamples; videoRef.sampleFlags = 0; // now add all of our media samples to the movie data file. err = AddMediaSampleReferences64(videoM, (SampleDescriptionHandle)videoDescH, 1, &videoRef, 0); if (err != noErr) goto bail; TimeValue mediaDuration = kNumberOfSamples * kDurationPerSample; // inserts a reference to our media samples into the track. err = InsertMediaIntoTrack(videoT, 0, 0, mediaDuration, fixed1); // media's rate (1.0 = media's natural playback rate). if (err != noErr) goto bail; DataHandler outDataHandler; // opens a data handler for our movie storage (the video frames file) err = OpenMovieStorage (dataRef, dataRefType, kDataHCanWrite, &outDataHandler ); if (err != noErr) goto bail; // add a movie to our movie storage container err = AddMovieToStorage (m, outDataHandler ); if (err != noErr) goto bail; err = CloseMovieStorage (outDataHandler); outDataHandler = NULL; bail: if (videoDescH) { DisposeHandle((Handle)videoDescH); } return err; }
/* 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() */