| 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_);
|
|
|