bigtime_t StreamBase::Duration() const { // TODO: This is not working correctly for all stream types... // It seems that the calculations here are correct, because they work // for a couple of streams and are in line with the documentation, but // unfortunately, libavformat itself seems to set the time_base and // duration wrongly sometimes. :-( if ((int64)fStream->duration != kNoPTSValue) return _ConvertFromStreamTimeBase(fStream->duration); else if ((int64)fContext->duration != kNoPTSValue) return (bigtime_t)fContext->duration; return 0; }
status_t StreamBase::GetNextChunk(const void** chunkBuffer, size_t* chunkSize, media_header* mediaHeader) { BAutolock _(fStreamLock); TRACE_PACKET("StreamBase::GetNextChunk()\n"); // Get the last stream DTS before reading the next packet, since // then it points to that one. int64 lastStreamDTS = fStream->cur_dts; status_t ret = _NextPacket(false); if (ret != B_OK) { *chunkBuffer = NULL; *chunkSize = 0; return ret; } // NOTE: AVPacket has a field called "convergence_duration", for which // the documentation is quite interesting. It sounds like it could be // used to know the time until the next I-Frame in streams that don't // let you know the position of keyframes in another way (like through // the index). // According to libavformat documentation, fPacket is valid until the // next call to av_read_frame(). This is what we want and we can share // the memory with the least overhead. *chunkBuffer = fPacket.data; *chunkSize = fPacket.size; if (mediaHeader != NULL) { mediaHeader->type = fFormat.type; mediaHeader->buffer = 0; mediaHeader->destination = -1; mediaHeader->time_source = -1; mediaHeader->size_used = fPacket.size; if (fPacket.pts != kNoPTSValue) { //TRACE(" PTS: %lld (time_base.num: %d, .den: %d), stream DTS: %lld\n", //fPacket.pts, fStream->time_base.num, fStream->time_base.den, //fStream->cur_dts); mediaHeader->start_time = _ConvertFromStreamTimeBase(fPacket.pts); } else { //TRACE(" PTS (stream): %lld (time_base.num: %d, .den: %d), stream DTS: %lld\n", //lastStreamDTS, fStream->time_base.num, fStream->time_base.den, //fStream->cur_dts); mediaHeader->start_time = _ConvertFromStreamTimeBase(lastStreamDTS); } mediaHeader->file_pos = fPacket.pos; mediaHeader->data_offset = 0; switch (mediaHeader->type) { case B_MEDIA_RAW_AUDIO: break; case B_MEDIA_ENCODED_AUDIO: mediaHeader->u.encoded_audio.buffer_flags = (fPacket.flags & AV_PKT_FLAG_KEY) ? B_MEDIA_KEY_FRAME : 0; break; case B_MEDIA_RAW_VIDEO: mediaHeader->u.raw_video.line_count = fFormat.u.raw_video.display.line_count; break; case B_MEDIA_ENCODED_VIDEO: mediaHeader->u.encoded_video.field_flags = (fPacket.flags & AV_PKT_FLAG_KEY) ? B_MEDIA_KEY_FRAME : 0; mediaHeader->u.encoded_video.line_count = fFormat.u.encoded_video.output.display.line_count; break; default: break; } } // static bigtime_t pts[2]; // static bigtime_t lastPrintTime = system_time(); // static BLocker printLock; // if (fStream->index < 2) { // if (fPacket.pts != kNoPTSValue) // pts[fStream->index] = _ConvertFromStreamTimeBase(fPacket.pts); // printLock.Lock(); // bigtime_t now = system_time(); // if (now - lastPrintTime > 1000000) { // printf("PTS: %.4f/%.4f, diff: %.4f\r", pts[0] / 1000000.0, // pts[1] / 1000000.0, (pts[0] - pts[1]) / 1000000.0); // fflush(stdout); // lastPrintTime = now; // } // printLock.Unlock(); // } return B_OK; }
status_t StreamBase::Seek(uint32 flags, int64* frame, bigtime_t* time) { BAutolock _(fStreamLock); if (fContext == NULL || fStream == NULL) return B_NO_INIT; TRACE_SEEK("StreamBase::Seek(%ld,%s%s%s%s, %lld, " "%lld)\n", VirtualIndex(), (flags & B_MEDIA_SEEK_TO_FRAME) ? " B_MEDIA_SEEK_TO_FRAME" : "", (flags & B_MEDIA_SEEK_TO_TIME) ? " B_MEDIA_SEEK_TO_TIME" : "", (flags & B_MEDIA_SEEK_CLOSEST_BACKWARD) ? " B_MEDIA_SEEK_CLOSEST_BACKWARD" : "", (flags & B_MEDIA_SEEK_CLOSEST_FORWARD) ? " B_MEDIA_SEEK_CLOSEST_FORWARD" : "", *frame, *time); double frameRate = FrameRate(); if ((flags & B_MEDIA_SEEK_TO_FRAME) != 0) { // Seeking is always based on time, initialize it when client seeks // based on frame. *time = (bigtime_t)(*frame * 1000000.0 / frameRate + 0.5); } int64_t timeStamp = *time; int searchFlags = AVSEEK_FLAG_BACKWARD; if ((flags & B_MEDIA_SEEK_CLOSEST_FORWARD) != 0) searchFlags = 0; if (fSeekByBytes) { searchFlags |= AVSEEK_FLAG_BYTE; BAutolock _(fSourceLock); int64_t fileSize; if (fSource->GetSize(&fileSize) != B_OK) return B_NOT_SUPPORTED; int64_t duration = Duration(); if (duration == 0) return B_NOT_SUPPORTED; timeStamp = int64_t(fileSize * ((double)timeStamp / duration)); if ((flags & B_MEDIA_SEEK_CLOSEST_BACKWARD) != 0) { timeStamp -= 65536; if (timeStamp < 0) timeStamp = 0; } bool seekAgain = true; bool seekForward = true; bigtime_t lastFoundTime = -1; int64_t closestTimeStampBackwards = -1; while (seekAgain) { if (avformat_seek_file(fContext, -1, INT64_MIN, timeStamp, INT64_MAX, searchFlags) < 0) { TRACE(" avformat_seek_file() (by bytes) failed.\n"); return B_ERROR; } seekAgain = false; // Our last packet is toast in any case. Read the next one so we // know where we really seeked. fReusePacket = false; if (_NextPacket(true) == B_OK) { while (fPacket.pts == kNoPTSValue) { fReusePacket = false; if (_NextPacket(true) != B_OK) return B_ERROR; } if (fPacket.pos >= 0) timeStamp = fPacket.pos; bigtime_t foundTime = _ConvertFromStreamTimeBase(fPacket.pts); if (foundTime != lastFoundTime) { lastFoundTime = foundTime; if (foundTime > *time) { if (closestTimeStampBackwards >= 0) { timeStamp = closestTimeStampBackwards; seekAgain = true; seekForward = false; continue; } int64_t diff = int64_t(fileSize * ((double)(foundTime - *time) / (2 * duration))); if (diff < 8192) break; timeStamp -= diff; TRACE_SEEK(" need to seek back (%lld) (time: %.2f " "-> %.2f)\n", timeStamp, *time / 1000000.0, foundTime / 1000000.0); if (timeStamp < 0) foundTime = 0; else { seekAgain = true; continue; } } else if (seekForward && foundTime < *time - 100000) { closestTimeStampBackwards = timeStamp; int64_t diff = int64_t(fileSize * ((double)(*time - foundTime) / (2 * duration))); if (diff < 8192) break; timeStamp += diff; TRACE_SEEK(" need to seek forward (%lld) (time: " "%.2f -> %.2f)\n", timeStamp, *time / 1000000.0, foundTime / 1000000.0); if (timeStamp > duration) foundTime = duration; else { seekAgain = true; continue; } } } TRACE_SEEK(" found time: %lld -> %lld (%.2f)\n", *time, foundTime, foundTime / 1000000.0); *time = foundTime; *frame = (uint64)(*time * frameRate / 1000000LL + 0.5); TRACE_SEEK(" seeked frame: %lld\n", *frame); } else { TRACE_SEEK(" _NextPacket() failed!\n"); return B_ERROR; } } } else { // We may not get a PTS from the next packet after seeking, so // we try to get an expected time from the index. int64_t streamTimeStamp = _ConvertToStreamTimeBase(*time); int index = av_index_search_timestamp(fStream, streamTimeStamp, searchFlags); if (index < 0) { TRACE(" av_index_search_timestamp() failed\n"); } else { if (index > 0) { const AVIndexEntry& entry = fStream->index_entries[index]; streamTimeStamp = entry.timestamp; } else { // Some demuxers use the first index entry to store some // other information, like the total playing time for example. // Assume the timeStamp of the first entry is alays 0. // TODO: Handle start-time offset? streamTimeStamp = 0; } bigtime_t foundTime = _ConvertFromStreamTimeBase(streamTimeStamp); bigtime_t timeDiff = foundTime > *time ? foundTime - *time : *time - foundTime; if (timeDiff > 1000000 && (fStreamBuildsIndexWhileReading || index == fStream->nb_index_entries - 1)) { // If the stream is building the index on the fly while parsing // it, we only have entries in the index for positions already // decoded, i.e. we cannot seek into the future. In that case, // just assume that we can seek where we want and leave // time/frame unmodified. Since successfully seeking one time // will generate index entries for the seeked to position, we // need to remember this in fStreamBuildsIndexWhileReading, // since when seeking back there will be later index entries, // but we still want to ignore the found entry. fStreamBuildsIndexWhileReading = true; TRACE_SEEK(" Not trusting generic index entry. " "(Current count: %d)\n", fStream->nb_index_entries); } else { // If we found a reasonably time, write it into *time. // After seeking, we will try to read the sought time from // the next packet. If the packet has no PTS value, we may // still have a more accurate time from the index lookup. *time = foundTime; } } if (avformat_seek_file(fContext, -1, INT64_MIN, timeStamp, INT64_MAX, searchFlags) < 0) { TRACE(" avformat_seek_file() failed.\n"); // Try to fall back to av_seek_frame() timeStamp = _ConvertToStreamTimeBase(timeStamp); if (av_seek_frame(fContext, fStream->index, timeStamp, searchFlags) < 0) { TRACE(" avformat_seek_frame() failed as well.\n"); // Fall back to seeking to the beginning by bytes timeStamp = 0; if (av_seek_frame(fContext, fStream->index, timeStamp, AVSEEK_FLAG_BYTE) < 0) { TRACE(" avformat_seek_frame() by bytes failed as " "well.\n"); // Do not propagate error in any case. We fail if we can't // read another packet. } else *time = 0; } } // Our last packet is toast in any case. Read the next one so // we know where we really sought. bigtime_t foundTime = *time; fReusePacket = false; if (_NextPacket(true) == B_OK) { if (fPacket.pts != kNoPTSValue) foundTime = _ConvertFromStreamTimeBase(fPacket.pts); else TRACE_SEEK(" no PTS in packet after seeking\n"); } else TRACE_SEEK(" _NextPacket() failed!\n"); *time = foundTime; TRACE_SEEK(" sought time: %.2fs\n", *time / 1000000.0); *frame = (uint64)(*time * frameRate / 1000000.0 + 0.5); TRACE_SEEK(" sought frame: %lld\n", *frame); } return B_OK; }
status_t AVFormatReader::Stream::GetStreamInfo(int64* frameCount, bigtime_t* duration, media_format* format, const void** infoBuffer, size_t* infoSize) const { BAutolock _(&fLock); TRACE("AVFormatReader::Stream::GetStreamInfo(%ld)\n", VirtualIndex()); double frameRate = FrameRate(); TRACE(" frameRate: %.4f\n", frameRate); #ifdef TRACE_AVFORMAT_READER if (fStream->start_time != kNoPTSValue) { bigtime_t startTime = _ConvertFromStreamTimeBase(fStream->start_time); TRACE(" start_time: %lld or %.5fs\n", startTime, startTime / 1000000.0); // TODO: Handle start time in FindKeyFrame() and Seek()?! } #endif // TRACE_AVFORMAT_READER *duration = Duration(); TRACE(" duration: %lld or %.5fs\n", *duration, *duration / 1000000.0); #if 0 if (fStream->nb_index_entries > 0) { TRACE(" dump of index entries:\n"); int count = 5; int firstEntriesCount = min_c(fStream->nb_index_entries, count); int i = 0; for (; i < firstEntriesCount; i++) { AVIndexEntry& entry = fStream->index_entries[i]; bigtime_t timeGlobal = entry.timestamp; bigtime_t timeNative = _ConvertFromStreamTimeBase(timeGlobal); TRACE(" [%d] native: %.5fs global: %.5fs\n", i, timeNative / 1000000.0f, timeGlobal / 1000000.0f); } if (fStream->nb_index_entries - count > i) { i = fStream->nb_index_entries - count; TRACE(" ...\n"); for (; i < fStream->nb_index_entries; i++) { AVIndexEntry& entry = fStream->index_entries[i]; bigtime_t timeGlobal = entry.timestamp; bigtime_t timeNative = _ConvertFromStreamTimeBase(timeGlobal); TRACE(" [%d] native: %.5fs global: %.5fs\n", i, timeNative / 1000000.0f, timeGlobal / 1000000.0f); } } } #endif *frameCount = fStream->nb_frames; // if (*frameCount == 0) { // Calculate from duration and frame rate *frameCount = (int64)(*duration * frameRate / 1000000LL); TRACE(" frameCount calculated: %lld, from context: %lld\n", *frameCount, fStream->nb_frames); // } else // TRACE(" frameCount: %lld\n", *frameCount); *format = fFormat; *infoBuffer = fStream->codec->extradata; *infoSize = fStream->codec->extradata_size; return B_OK; }
status_t StreamBase::GetNextChunk(const void** chunkBuffer, size_t* chunkSize, media_header* mediaHeader) { BAutolock _(fStreamLock); TRACE_PACKET("StreamBase::GetNextChunk()\n"); // Get the last stream DTS before reading the next packet, since // then it points to that one. int64 lastStreamDTS = fStream->cur_dts; status_t ret = _NextPacket(false); if (ret != B_OK) { *chunkBuffer = NULL; *chunkSize = 0; return ret; } // NOTE: AVPacket has a field called "convergence_duration", for which // the documentation is quite interesting. It sounds like it could be // used to know the time until the next I-Frame in streams that don't // let you know the position of keyframes in another way (like through // the index). // According to libavformat documentation, fPacket is valid until the // next call to av_read_frame(). This is what we want and we can share // the memory with the least overhead. *chunkBuffer = fPacket.data; *chunkSize = fPacket.size; if (mediaHeader != NULL) { mediaHeader->type = fFormat.type; mediaHeader->buffer = 0; mediaHeader->destination = -1; mediaHeader->time_source = -1; mediaHeader->size_used = fPacket.size; // FFmpeg recommends to use the decoding time stamps as primary source // for presentation time stamps, especially for video formats that are // using frame reordering. More over this way it is ensured that the // returned start times are ordered in a monotonically increasing time // series (even for videos that contain B-frames). // \see http://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavformat/avformat.h;h=1e8a6294890d580cd9ebc684eaf4ce57c8413bd8;hb=9153b33a742c4e2a85ff6230aea0e75f5a8b26c2#l1623 bigtime_t presentationTimeStamp; if (fPacket.dts != kNoPTSValue) presentationTimeStamp = fPacket.dts; else if (fPacket.pts != kNoPTSValue) presentationTimeStamp = fPacket.pts; else presentationTimeStamp = lastStreamDTS; mediaHeader->start_time = _ConvertFromStreamTimeBase(presentationTimeStamp); mediaHeader->file_pos = fPacket.pos; mediaHeader->data_offset = 0; switch (mediaHeader->type) { case B_MEDIA_RAW_AUDIO: break; case B_MEDIA_ENCODED_AUDIO: mediaHeader->u.encoded_audio.buffer_flags = (fPacket.flags & AV_PKT_FLAG_KEY) ? B_MEDIA_KEY_FRAME : 0; break; case B_MEDIA_RAW_VIDEO: mediaHeader->u.raw_video.line_count = fFormat.u.raw_video.display.line_count; break; case B_MEDIA_ENCODED_VIDEO: mediaHeader->u.encoded_video.field_flags = (fPacket.flags & AV_PKT_FLAG_KEY) ? B_MEDIA_KEY_FRAME : 0; mediaHeader->u.encoded_video.line_count = fFormat.u.encoded_video.output.display.line_count; break; default: break; } } // static bigtime_t pts[2]; // static bigtime_t lastPrintTime = system_time(); // static BLocker printLock; // if (fStream->index < 2) { // if (fPacket.pts != kNoPTSValue) // pts[fStream->index] = _ConvertFromStreamTimeBase(fPacket.pts); // printLock.Lock(); // bigtime_t now = system_time(); // if (now - lastPrintTime > 1000000) { // printf("PTS: %.4f/%.4f, diff: %.4f\r", pts[0] / 1000000.0, // pts[1] / 1000000.0, (pts[0] - pts[1]) / 1000000.0); // fflush(stdout); // lastPrintTime = now; // } // printLock.Unlock(); // } return B_OK; }