Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1209)

Unified Diff: media/filters/ffmpeg_demuxer.cc

Issue 335273002: Fix seeking when the start time is non-zero. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Comments. Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/filters/ffmpeg_demuxer.h ('k') | media/filters/ffmpeg_demuxer_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/filters/ffmpeg_demuxer.cc
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index f5b4fddad3b97f03040d8f5f209f940f9d8d3d4c..92c438ea3a7dfea48b5674c50fccf4014aba73a9 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -55,19 +55,47 @@ static base::TimeDelta FramesToTimeDelta(int frames, double sample_rate) {
frames * base::Time::kMicrosecondsPerSecond / sample_rate);
}
+static base::TimeDelta ExtractStartTime(AVStream* stream,
+ base::TimeDelta start_time_estimate) {
+ DCHECK(start_time_estimate != kNoTimestamp());
+ if (stream->start_time == static_cast<int64_t>(AV_NOPTS_VALUE)) {
+ return start_time_estimate == kInfiniteDuration() ? kNoTimestamp()
+ : start_time_estimate;
+ }
+
+ // First try the lower of the estimate and the |start_time| value.
+ base::TimeDelta start_time =
+ std::min(ConvertFromTimeBase(stream->time_base, stream->start_time),
+ start_time_estimate);
+
+ // Next see if the first buffered pts value is usable.
+ if (stream->pts_buffer[0] != static_cast<int64_t>(AV_NOPTS_VALUE)) {
+ const base::TimeDelta buffered_pts =
+ ConvertFromTimeBase(stream->time_base, stream->pts_buffer[0]);
+ if (buffered_pts < start_time)
+ start_time = buffered_pts;
+ }
+
+ // NOTE: Do not use AVStream->first_dts since |start_time| should be a
+ // presentation timestamp.
+ return start_time;
+}
+
//
// FFmpegDemuxerStream
//
FFmpegDemuxerStream::FFmpegDemuxerStream(
FFmpegDemuxer* demuxer,
- AVStream* stream)
+ AVStream* stream,
+ bool discard_negative_timestamps)
: demuxer_(demuxer),
task_runner_(base::MessageLoopProxy::current()),
stream_(stream),
type_(UNKNOWN),
end_of_stream_(false),
last_packet_timestamp_(kNoTimestamp()),
- bitstream_converter_enabled_(false) {
+ bitstream_converter_enabled_(false),
+ discard_negative_timestamps_(discard_negative_timestamps) {
DCHECK(demuxer_);
bool is_encrypted = false;
@@ -226,16 +254,44 @@ void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) {
buffer->set_decrypt_config(decrypt_config.Pass());
}
- buffer->set_timestamp(ConvertStreamTimestamp(
- stream_->time_base, packet->pts));
- buffer->set_duration(ConvertStreamTimestamp(
- stream_->time_base, packet->duration));
- if (buffer->timestamp() != kNoTimestamp() &&
- last_packet_timestamp_ != kNoTimestamp() &&
- last_packet_timestamp_ < buffer->timestamp()) {
- buffered_ranges_.Add(last_packet_timestamp_, buffer->timestamp());
- demuxer_->NotifyBufferingChanged();
+ buffer->set_duration(
+ ConvertStreamTimestamp(stream_->time_base, packet->duration));
+
+ // Note: If pts is AV_NOPTS_VALUE, stream_timestamp will be kNoTimestamp().
+ const base::TimeDelta stream_timestamp =
+ ConvertStreamTimestamp(stream_->time_base, packet->pts);
+
+ if (stream_timestamp != kNoTimestamp()) {
+ buffer->set_timestamp(stream_timestamp - demuxer_->start_time());
+
+ // If enabled, mark packets with negative timestamps for post-decode
+ // discard.
+ if (discard_negative_timestamps_ && stream_timestamp < base::TimeDelta()) {
+ if (stream_timestamp + buffer->duration() < base::TimeDelta()) {
+ // Discard the entire packet if it's entirely before zero.
+ buffer->set_discard_padding(
+ std::make_pair(kInfiniteDuration(), base::TimeDelta()));
+ } else {
+ // Only discard part of the frame if it overlaps zero.
+ buffer->set_discard_padding(
+ std::make_pair(-stream_timestamp, base::TimeDelta()));
+ }
+ }
+
+ if (last_packet_timestamp_ != kNoTimestamp() &&
+ last_packet_timestamp_ < buffer->timestamp()) {
+ buffered_ranges_.Add(last_packet_timestamp_, buffer->timestamp());
+ demuxer_->NotifyBufferingChanged();
+ }
+
+ // The demuxer should always output positive timestamps.
+ DCHECK(buffer->timestamp() >= base::TimeDelta());
+ } else {
+ buffer->set_timestamp(kNoTimestamp());
}
+
+ // TODO(dalecurtis): This allows transitions from <valid ts> -> <no timestamp>
+ // which shouldn't be allowed. See http://crbug.com/384532
last_packet_timestamp_ = buffer->timestamp();
buffer_queue_.Push(buffer);
@@ -268,10 +324,6 @@ void FFmpegDemuxerStream::Stop() {
end_of_stream_ = true;
}
-base::TimeDelta FFmpegDemuxerStream::duration() {
- return duration_;
-}
-
DemuxerStream::Type FFmpegDemuxerStream::type() {
DCHECK(task_runner_->BelongsToCurrentThread());
return type_;
@@ -416,6 +468,8 @@ FFmpegDemuxer::FFmpegDemuxer(
media_log_(media_log),
bitrate_(0),
start_time_(kNoTimestamp()),
+ preferred_stream_for_seeking_(-1, kNoTimestamp()),
+ fallback_stream_for_seeking_(-1, kNoTimestamp()),
liveness_(LIVENESS_UNKNOWN),
text_enabled_(false),
duration_known_(false),
@@ -445,21 +499,30 @@ void FFmpegDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) {
// otherwise we can end up waiting for a pre-seek read to complete even though
// we know we're going to drop it on the floor.
- // Always seek to a timestamp less than or equal to the desired timestamp.
- int flags = AVSEEK_FLAG_BACKWARD;
+ // FFmpeg requires seeks to be adjusted according to the lowest starting time.
+ const base::TimeDelta seek_time = time + start_time_;
+
+ // Choose the preferred stream if |seek_time| occurs after its starting time,
+ // otherwise use the fallback stream.
+ DCHECK(preferred_stream_for_seeking_.second != kNoTimestamp());
+ const int stream_index = seek_time >= preferred_stream_for_seeking_.second
+ ? preferred_stream_for_seeking_.first
+ : fallback_stream_for_seeking_.first;
+ DCHECK_NE(stream_index, -1);
+
+ const AVStream* seeking_stream =
+ glue_->format_context()->streams[stream_index];
- // Passing -1 as our stream index lets FFmpeg pick a default stream. FFmpeg
- // will attempt to use the lowest-index video stream, if present, followed by
- // the lowest-index audio stream.
pending_seek_ = true;
base::PostTaskAndReplyWithResult(
blocking_thread_.message_loop_proxy().get(),
FROM_HERE,
base::Bind(&av_seek_frame,
glue_->format_context(),
- -1,
- time.InMicroseconds(),
- flags),
+ seeking_stream->index,
+ ConvertToTimeBase(seeking_stream->time_base, seek_time),
+ // Always seek to a timestamp <= to the desired timestamp.
+ AVSEEK_FLAG_BACKWARD),
base::Bind(
&FFmpegDemuxer::OnSeekFrameDone, weak_factory_.GetWeakPtr(), cb));
}
@@ -508,11 +571,6 @@ FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream(
return NULL;
}
-base::TimeDelta FFmpegDemuxer::GetStartTime() const {
- DCHECK(task_runner_->BelongsToCurrentThread());
- return start_time_;
-}
-
base::Time FFmpegDemuxer::GetTimelineOffset() const {
return timeline_offset_;
}
@@ -622,17 +680,49 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
AVFormatContext* format_context = glue_->format_context();
streams_.resize(format_context->nb_streams);
+ // Estimate the start time for each stream by looking through the packets
+ // buffered during avformat_find_stream_info(). These values will be
+ // considered later when determining the actual stream start time.
+ //
+ // These packets haven't been completely processed yet, so only look through
+ // these values if the AVFormatContext has a valid start time.
+ //
+ // If no estimate is found, the stream entry will be kInfiniteDuration().
+ std::vector<base::TimeDelta> start_time_estimates(format_context->nb_streams,
+ kInfiniteDuration());
+ if (format_context->packet_buffer &&
+ format_context->start_time != static_cast<int64>(AV_NOPTS_VALUE)) {
+ struct AVPacketList* packet_buffer = format_context->packet_buffer;
+ while (packet_buffer != format_context->packet_buffer_end) {
+ DCHECK_LT(static_cast<size_t>(packet_buffer->pkt.stream_index),
+ start_time_estimates.size());
+ const AVStream* stream =
+ format_context->streams[packet_buffer->pkt.stream_index];
+ if (packet_buffer->pkt.pts != static_cast<int64>(AV_NOPTS_VALUE)) {
+ const base::TimeDelta packet_pts =
+ ConvertFromTimeBase(stream->time_base, packet_buffer->pkt.pts);
+ if (packet_pts < start_time_estimates[stream->index])
+ start_time_estimates[stream->index] = packet_pts;
+ }
+ packet_buffer = packet_buffer->next;
+ }
+ }
+
AVStream* audio_stream = NULL;
AudioDecoderConfig audio_config;
AVStream* video_stream = NULL;
VideoDecoderConfig video_config;
+ // If available, |start_time_| will be set to the lowest stream start time.
+ start_time_ = kInfiniteDuration();
+
base::TimeDelta max_duration;
for (size_t i = 0; i < format_context->nb_streams; ++i) {
AVStream* stream = format_context->streams[i];
- AVCodecContext* codec_context = stream->codec;
- AVMediaType codec_type = codec_context->codec_type;
+ const AVCodecContext* codec_context = stream->codec;
+ const AVMediaType codec_type = codec_context->codec_type;
+ bool discard_negative_timestamps = false;
if (codec_type == AVMEDIA_TYPE_AUDIO) {
if (audio_stream)
@@ -647,6 +737,13 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
if (!audio_config.IsValidConfig())
continue;
audio_stream = stream;
+
+ // Enable post-decode frame dropping for packets with negative timestamps
+ // as outlined in section A.2 in the Ogg Vorbis spec:
+ // http://xiph.org/vorbis/doc/Vorbis_I_spec.html
+ discard_negative_timestamps =
+ audio_config.codec() == kCodecVorbis &&
+ strcmp(glue_->format_context()->iformat->name, "ogg") == 0;
} else if (codec_type == AVMEDIA_TYPE_VIDEO) {
if (video_stream)
continue;
@@ -669,15 +766,27 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
continue;
}
- streams_[i] = new FFmpegDemuxerStream(this, stream);
+ streams_[i] =
+ new FFmpegDemuxerStream(this, stream, discard_negative_timestamps);
max_duration = std::max(max_duration, streams_[i]->duration());
- if (stream->first_dts != static_cast<int64_t>(AV_NOPTS_VALUE)) {
- const base::TimeDelta first_dts = ConvertFromTimeBase(
- stream->time_base, stream->first_dts);
- if (start_time_ == kNoTimestamp() || first_dts < start_time_)
- start_time_ = first_dts;
+ const base::TimeDelta start_time =
+ ExtractStartTime(stream, start_time_estimates[i]);
+ if (start_time == kNoTimestamp())
+ continue;
+
+ if (start_time < start_time_) {
+ start_time_ = start_time;
+
+ // Choose the stream with the lowest starting time as the fallback stream
+ // for seeking. Video should always be preferred.
+ fallback_stream_for_seeking_ = std::make_pair(i, start_time);
}
+
+ // Always prefer the video stream for seeking. If none exists, we'll swap
+ // the fallback stream with the preferred stream below.
+ if (codec_type == AVMEDIA_TYPE_VIDEO)
+ preferred_stream_for_seeking_ = std::make_pair(i, start_time);
}
if (!audio_stream && !video_stream) {
@@ -700,17 +809,31 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
max_duration = kInfiniteDuration();
}
- // Some demuxers, like WAV, do not put timestamps on their frames. We
- // assume the the start time is 0.
- if (start_time_ == kNoTimestamp())
+ // If no start time could be determined, default to zero and prefer the video
+ // stream over the audio stream for seeking. E.g., The WAV demuxer does not
+ // put timestamps on its frames.
+ if (start_time_ == kInfiniteDuration()) {
start_time_ = base::TimeDelta();
+ preferred_stream_for_seeking_ = std::make_pair(
+ video_stream ? video_stream->index : audio_stream->index, start_time_);
+ } else if (!video_stream) {
+ // If no video stream exists, use the audio or text stream found above.
+ preferred_stream_for_seeking_ = fallback_stream_for_seeking_;
+ }
// MPEG-4 B-frames cause grief for a simple container like AVI. Enable PTS
// generation so we always get timestamps, see http://crbug.com/169570
if (strcmp(format_context->iformat->name, "avi") == 0)
format_context->flags |= AVFMT_FLAG_GENPTS;
- timeline_offset_ = ExtractTimelineOffset(format_context);
+ // For testing purposes, don't overwrite the timeline offset if set already.
+ if (timeline_offset_.is_null())
+ timeline_offset_ = ExtractTimelineOffset(format_context);
+
+ // Since we're shifting the externally visible start time to zero, we need to
+ // adjust the timeline offset to compensate.
+ if (!timeline_offset_.is_null())
+ timeline_offset_ += start_time_;
if (max_duration == kInfiniteDuration() && !timeline_offset_.is_null()) {
liveness_ = LIVENESS_LIVE;
@@ -783,7 +906,6 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
media_log_->SetBooleanProperty("found_video_stream", false);
}
-
media_log_->SetTimeProperty("max_duration", max_duration);
media_log_->SetTimeProperty("start_time", start_time_);
media_log_->SetIntegerProperty("bitrate", bitrate_);
« no previous file with comments | « media/filters/ffmpeg_demuxer.h ('k') | media/filters/ffmpeg_demuxer_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698