Chromium Code Reviews| Index: media/filters/ffmpeg_demuxer.cc |
| diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc |
| index f5b4fddad3b97f03040d8f5f209f940f9d8d3d4c..2bd6a6f5facb3591e042f3390c90ae7dc271aeaf 100644 |
| --- a/media/filters/ffmpeg_demuxer.cc |
| +++ b/media/filters/ffmpeg_demuxer.cc |
| @@ -55,19 +55,58 @@ static base::TimeDelta FramesToTimeDelta(int frames, double sample_rate) { |
| frames * base::Time::kMicrosecondsPerSecond / sample_rate); |
| } |
| +static base::TimeDelta ExtractStartTime(AVFormatContext* format_context, |
| + AVStream* stream) { |
| + if (stream->start_time == static_cast<int64_t>(AV_NOPTS_VALUE)) |
| + return kNoTimestamp(); |
| + |
| + // First try to use the |start_time| value directly. |
| + base::TimeDelta start_time = |
| + ConvertFromTimeBase(stream->time_base, stream->start_time); |
| + |
| + // 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; |
| + } |
| + |
| + if (!format_context->packet_buffer) |
| + return start_time; |
| + |
| + // Compare against the packets buffered during avformat_find_stream_info(). |
| + struct AVPacketList* packet_buffer = format_context->packet_buffer; |
| + while (packet_buffer != format_context->packet_buffer_end) { |
|
acolwell GONE FROM CHROMIUM
2014/06/17 17:41:22
How deep could this buffer be? Should we be concer
DaleCurtis
2014/06/17 20:52:30
2500 packets across all streams: https://code.goog
DaleCurtis
2014/06/17 21:12:51
Note: With the vector approach, it'd be easy to ru
acolwell GONE FROM CHROMIUM
2014/06/17 21:39:43
You could still do that with a local vector instea
DaleCurtis
2014/06/17 22:30:41
Done.
|
| + if (packet_buffer->pkt.stream_index == stream->index) { |
| + const base::TimeDelta packet_pts = |
| + ConvertFromTimeBase(stream->time_base, packet_buffer->pkt.pts); |
| + if (packet_pts < start_time) |
| + start_time = packet_pts; |
| + } |
| + packet_buffer = packet_buffer->next; |
| + } |
| + |
| + // 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 +265,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 entier 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 +335,6 @@ void FFmpegDemuxerStream::Stop() { |
| end_of_stream_ = true; |
| } |
| -base::TimeDelta FFmpegDemuxerStream::duration() { |
| - return duration_; |
| -} |
| - |
| DemuxerStream::Type FFmpegDemuxerStream::type() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| return type_; |
| @@ -445,21 +508,43 @@ 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_; |
| + |
| + // Find the first video stream which contains |seek_time| or if none exists, |
| + // the stream with the lowest starting timestamp that contains |seek_time|. |
| + AVStream* seeking_stream = NULL; |
| + for (size_t i = 0; i < stream_start_times_.size(); ++i) { |
| + if (stream_start_times_[i] == kNoTimestamp()) |
| + continue; |
| + |
| + if (seek_time < stream_start_times_[i]) |
| + continue; |
| + |
| + // If we find a video stream in range, always use it for seeking. Otherwise |
| + // we'll get corruption during playback if we land off of a keyframe. |
| + if (streams_[i]->type() == DemuxerStream::VIDEO) { |
| + seeking_stream = glue_->format_context()->streams[i]; |
| + break; |
| + } |
| + |
| + // Otherwise use the stream with the lowest starting timestamp. |
| + if (!seeking_stream || |
| + stream_start_times_[i] < stream_start_times_[seeking_stream->index]) { |
| + seeking_stream = glue_->format_context()->streams[i]; |
| + } |
| + } |
| - // 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 +593,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_; |
| } |
| @@ -621,6 +701,7 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, |
| // partial playback. At least one audio or video stream must be playable. |
| AVFormatContext* format_context = glue_->format_context(); |
| streams_.resize(format_context->nb_streams); |
| + stream_start_times_.resize(format_context->nb_streams); |
| AVStream* audio_stream = NULL; |
| AudioDecoderConfig audio_config; |
| @@ -628,11 +709,16 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, |
| 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; |
| + stream_start_times_[i] = kNoTimestamp(); |
| if (codec_type == AVMEDIA_TYPE_AUDIO) { |
| if (audio_stream) |
| @@ -647,6 +733,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,14 +762,16 @@ 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; |
| + // Find the lowest stream start time. It will be used to rebase all other |
| + // stream timestamps to expose a zero-based timeline. |
| + stream_start_times_[i] = ExtractStartTime(format_context, stream); |
| + if (stream_start_times_[i] != kNoTimestamp() && |
| + stream_start_times_[i] < start_time_) { |
| + start_time_ = stream_start_times_[i]; |
| } |
| } |
| @@ -700,17 +795,30 @@ 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(); |
| + for (size_t i = 0; i < format_context->nb_streams; ++i) { |
| + if (streams_[i]) |
| + stream_start_times_[i] = start_time_; |
| + } |
| + } |
| // 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 +891,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_); |