diff options
Diffstat (limited to 'chromium/media/filters/ffmpeg_demuxer.cc')
-rw-r--r-- | chromium/media/filters/ffmpeg_demuxer.cc | 244 |
1 files changed, 199 insertions, 45 deletions
diff --git a/chromium/media/filters/ffmpeg_demuxer.cc b/chromium/media/filters/ffmpeg_demuxer.cc index 75bf64683cc..155e98079af 100644 --- a/chromium/media/filters/ffmpeg_demuxer.cc +++ b/chromium/media/filters/ffmpeg_demuxer.cc @@ -10,6 +10,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_macros.h" #include "base/metrics/sparse_histogram.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" @@ -19,10 +20,12 @@ #include "base/task_runner_util.h" #include "base/thread_task_runner_handle.h" #include "base/time/time.h" +#include "media/audio/sample_rates.h" #include "media/base/bind_to_current_loop.h" #include "media/base/decrypt_config.h" #include "media/base/limits.h" #include "media/base/media_log.h" +#include "media/base/timestamp_constants.h" #include "media/ffmpeg/ffmpeg_common.h" #include "media/filters/ffmpeg_aac_bitstream_converter.h" #include "media/filters/ffmpeg_bitstream_converter.h" @@ -31,6 +34,10 @@ #include "media/filters/webvtt_util.h" #include "media/formats/webm/webm_crypto_helpers.h" +#if defined(ENABLE_HEVC_DEMUXING) +#include "media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.h" +#endif + namespace media { static base::Time ExtractTimelineOffset(AVFormatContext* format_context) { @@ -80,6 +87,91 @@ static base::TimeDelta ExtractStartTime(AVStream* stream, return start_time; } +// Some videos just want to watch the world burn, with a height of 0; cap the +// "infinite" aspect ratio resulting. +const int kInfiniteRatio = 99999; + +// Common aspect ratios (multiplied by 100 and truncated) used for histogramming +// video sizes. These were taken on 20111103 from +// http://wikipedia.org/wiki/Aspect_ratio_(image)#Previous_and_currently_used_aspect_ratios +const int kCommonAspectRatios100[] = { + 100, 115, 133, 137, 143, 150, 155, 160, 166, + 175, 177, 185, 200, 210, 220, 221, 235, 237, + 240, 255, 259, 266, 276, 293, 400, 1200, kInfiniteRatio, +}; + +template <class T> // T has int width() & height() methods. +static void UmaHistogramAspectRatio(const char* name, const T& size) { + UMA_HISTOGRAM_CUSTOM_ENUMERATION( + name, + // Intentionally use integer division to truncate the result. + size.height() ? (size.width() * 100) / size.height() : kInfiniteRatio, + base::CustomHistogram::ArrayToCustomRanges( + kCommonAspectRatios100, arraysize(kCommonAspectRatios100))); +} + +// Record audio decoder config UMA stats corresponding to a src= playback. +static void RecordAudioCodecStats(const AudioDecoderConfig& audio_config) { + UMA_HISTOGRAM_ENUMERATION("Media.AudioCodec", audio_config.codec(), + kAudioCodecMax + 1); + UMA_HISTOGRAM_ENUMERATION("Media.AudioSampleFormat", + audio_config.sample_format(), kSampleFormatMax + 1); + UMA_HISTOGRAM_ENUMERATION("Media.AudioChannelLayout", + audio_config.channel_layout(), + CHANNEL_LAYOUT_MAX + 1); + AudioSampleRate asr; + if (ToAudioSampleRate(audio_config.samples_per_second(), &asr)) { + UMA_HISTOGRAM_ENUMERATION("Media.AudioSamplesPerSecond", asr, + kAudioSampleRateMax + 1); + } else { + UMA_HISTOGRAM_COUNTS("Media.AudioSamplesPerSecondUnexpected", + audio_config.samples_per_second()); + } +} + +// Record video decoder config UMA stats corresponding to a src= playback. +static void RecordVideoCodecStats(const VideoDecoderConfig& video_config, + AVColorRange color_range) { + UMA_HISTOGRAM_ENUMERATION("Media.VideoCodec", video_config.codec(), + kVideoCodecMax + 1); + + // Drop UNKNOWN because U_H_E() uses one bucket for all values less than 1. + if (video_config.profile() >= 0) { + UMA_HISTOGRAM_ENUMERATION("Media.VideoCodecProfile", video_config.profile(), + VIDEO_CODEC_PROFILE_MAX + 1); + } + UMA_HISTOGRAM_COUNTS_10000("Media.VideoCodedWidth", + video_config.coded_size().width()); + UmaHistogramAspectRatio("Media.VideoCodedAspectRatio", + video_config.coded_size()); + UMA_HISTOGRAM_COUNTS_10000("Media.VideoVisibleWidth", + video_config.visible_rect().width()); + UmaHistogramAspectRatio("Media.VideoVisibleAspectRatio", + video_config.visible_rect()); + UMA_HISTOGRAM_ENUMERATION("Media.VideoPixelFormatUnion", + video_config.format(), PIXEL_FORMAT_MAX + 1); + UMA_HISTOGRAM_ENUMERATION("Media.VideoFrameColorSpace", + video_config.color_space(), COLOR_SPACE_MAX + 1); + + // Note the PRESUBMIT_IGNORE_UMA_MAX below, this silences the PRESUBMIT.py + // check for uma enum max usage, since we're abusing + // UMA_HISTOGRAM_ENUMERATION to report a discrete value. + UMA_HISTOGRAM_ENUMERATION("Media.VideoColorRange", color_range, + AVCOL_RANGE_NB); // PRESUBMIT_IGNORE_UMA_MAX +} + +static int32_t GetCodecHash(const AVCodecContext* context) { + if (context->codec_descriptor) + return HashCodecName(context->codec_descriptor->name); + const AVCodecDescriptor* codec_descriptor = + avcodec_descriptor_get(context->codec_id); + if (codec_descriptor) + return HashCodecName(codec_descriptor->name); + + // If the codec name can't be determined, return none for tracking. + return HashCodecName("none"); +} + // // FFmpegDemuxerStream // @@ -94,7 +186,7 @@ FFmpegDemuxerStream::FFmpegDemuxerStream(FFmpegDemuxer* demuxer, last_packet_timestamp_(kNoTimestamp()), last_packet_duration_(kNoTimestamp()), video_rotation_(VIDEO_ROTATION_0), - fixup_negative_ogg_timestamps_(false) { + fixup_negative_timestamps_(false) { DCHECK(demuxer_); bool is_encrypted = false; @@ -105,12 +197,12 @@ FFmpegDemuxerStream::FFmpegDemuxerStream(FFmpegDemuxer* demuxer, switch (stream->codec->codec_type) { case AVMEDIA_TYPE_AUDIO: type_ = AUDIO; - AVStreamToAudioDecoderConfig(stream, &audio_config_, true); + AVStreamToAudioDecoderConfig(stream, &audio_config_); is_encrypted = audio_config_.is_encrypted(); break; case AVMEDIA_TYPE_VIDEO: type_ = VIDEO; - AVStreamToVideoDecoderConfig(stream, &video_config_, true); + AVStreamToVideoDecoderConfig(stream, &video_config_); is_encrypted = video_config_.is_encrypted(); rotation_entry = av_dict_get(stream->metadata, "rotate", NULL, 0); @@ -294,10 +386,10 @@ void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) { if (stream_timestamp != kNoTimestamp()) { const bool is_audio = type() == AUDIO; - // If this is an OGG file with negative timestamps don't rebase any other - // stream types against the negative starting time. + // If this file has negative timestamps don't rebase any other stream types + // against the negative starting time. base::TimeDelta start_time = demuxer_->start_time(); - if (fixup_negative_ogg_timestamps_ && !is_audio && + if (fixup_negative_timestamps_ && !is_audio && start_time < base::TimeDelta()) { start_time = base::TimeDelta(); } @@ -310,19 +402,35 @@ void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) { buffer->set_timestamp(stream_timestamp - start_time); - // If enabled, mark audio packets with negative timestamps for post-decode - // discard. - if (fixup_negative_ogg_timestamps_ && is_audio && + // If enabled, and no codec delay is present, mark audio packets with + // negative timestamps for post-decode discard. + if (fixup_negative_timestamps_ && is_audio && stream_timestamp < base::TimeDelta() && buffer->duration() != kNoTimestamp()) { - 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())); + if (!stream_->codec->delay) { + DCHECK_EQ(buffer->discard_padding().first, base::TimeDelta()); + + if (stream_timestamp + buffer->duration() < base::TimeDelta()) { + DCHECK_EQ(buffer->discard_padding().second, 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, buffer->discard_padding().second)); + } } else { - // Only discard part of the frame if it overlaps zero. - buffer->set_discard_padding( - std::make_pair(-stream_timestamp, base::TimeDelta())); + // Verify that codec delay would cover discard and that we don't need to + // mark the packet for post decode discard. Since timestamps may be in + // milliseconds and codec delay in nanosecond precision, round up to the + // nearest millisecond. See enable_negative_timestamp_fixups(). + DCHECK_LE(-std::ceil(FramesToTimeDelta( + audio_decoder_config().codec_delay(), + audio_decoder_config().samples_per_second()) + .InMillisecondsF()), + stream_timestamp.InMillisecondsF()); } } } else { @@ -344,7 +452,7 @@ void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) { // Fixing chained ogg is non-trivial, so for now just reuse the last good // timestamp. The decoder will rewrite the timestamps to be sample accurate // later. See http://crbug.com/396864. - if (fixup_negative_ogg_timestamps_ && + if (fixup_negative_timestamps_ && (buffer->timestamp() == kNoTimestamp() || buffer->timestamp() < last_packet_timestamp_)) { buffer->set_timestamp(last_packet_timestamp_ + @@ -456,6 +564,11 @@ void FFmpegDemuxerStream::InitBitstreamConverter() { if (stream_->codec->codec_id == AV_CODEC_ID_H264) { bitstream_converter_.reset( new FFmpegH264ToAnnexBBitstreamConverter(stream_->codec)); +#if defined(ENABLE_HEVC_DEMUXING) + } else if (stream_->codec->codec_id == AV_CODEC_ID_HEVC) { + bitstream_converter_.reset( + new FFmpegH265ToAnnexBBitstreamConverter(stream_->codec)); +#endif } else if (stream_->codec->codec_id == AV_CODEC_ID_AAC) { bitstream_converter_.reset( new FFmpegAACBitstreamConverter(stream_->codec)); @@ -628,9 +741,19 @@ void FFmpegDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) { // Additionally, to workaround limitations in how we expose seekable ranges to // Blink (http://crbug.com/137275), we also want to clamp seeks before the // start time to the start time. - const base::TimeDelta seek_time = - start_time_ < base::TimeDelta() ? time + start_time_ - : time < start_time_ ? start_time_ : time; + base::TimeDelta seek_time = start_time_ < base::TimeDelta() + ? time + start_time_ + : time < start_time_ ? start_time_ : time; + + // When seeking in an opus stream we need to ensure we deliver enough data to + // satisfy the seek preroll; otherwise the audio at the actual seek time will + // not be entirely accurate. + FFmpegDemuxerStream* audio_stream = GetFFmpegStream(DemuxerStream::AUDIO); + if (audio_stream) { + const AudioDecoderConfig& config = audio_stream->audio_decoder_config(); + if (config.codec() == kCodecOpus) + seek_time = std::max(start_time_, seek_time - config.seek_preroll()); + } // Choose the seeking stream based on whether it contains the seek time, if no // match can be found prefer the preferred stream. @@ -882,11 +1005,12 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, continue; // Log the codec detected, whether it is supported or not. - UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedAudioCodec", - codec_context->codec_id); + UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedAudioCodecHash", + GetCodecHash(codec_context)); + // Ensure the codec is supported. IsValidConfig() also checks that the // channel layout and sample format are valid. - AVStreamToAudioDecoderConfig(stream, &audio_config, false); + AVStreamToAudioDecoderConfig(stream, &audio_config); if (!audio_config.IsValidConfig()) continue; audio_stream = stream; @@ -894,12 +1018,34 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, if (video_stream) continue; +#if defined(ENABLE_HEVC_DEMUXING) + if (stream->codec->codec_id == AV_CODEC_ID_HEVC) { + // If ffmpeg is built without HEVC parser/decoder support, it will be + // able to demux HEVC based solely on container-provided information, + // but unable to get some of the parameters without parsing the stream + // (e.g. coded size needs to be read from SPS, pixel format is typically + // deduced from decoder config in hvcC box). These are not really needed + // when using external decoder (e.g. hardware decoder), so override them + // here, to make sure this translates into a valid VideoDecoderConfig. + if (stream->codec->coded_width == 0 && + stream->codec->coded_height == 0) { + DCHECK(stream->codec->width > 0); + DCHECK(stream->codec->height > 0); + stream->codec->coded_width = stream->codec->width; + stream->codec->coded_height = stream->codec->height; + } + if (stream->codec->pix_fmt == AV_PIX_FMT_NONE) { + stream->codec->pix_fmt = AV_PIX_FMT_YUV420P; + } + } +#endif // Log the codec detected, whether it is supported or not. - UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodec", - codec_context->codec_id); + UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodecHash", + GetCodecHash(codec_context)); + // Ensure the codec is supported. IsValidConfig() also checks that the // frame size and visible size are valid. - AVStreamToVideoDecoderConfig(stream, &video_config, false); + AVStreamToVideoDecoderConfig(stream, &video_config); if (!video_config.IsValidConfig()) continue; @@ -913,6 +1059,16 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, } streams_[i] = new FFmpegDemuxerStream(this, stream); + + // Record audio or video src= playback UMA stats for the stream's decoder + // config. + if (codec_type == AVMEDIA_TYPE_AUDIO) { + RecordAudioCodecStats(streams_[i]->audio_decoder_config()); + } else if (codec_type == AVMEDIA_TYPE_VIDEO) { + RecordVideoCodecStats(streams_[i]->video_decoder_config(), + stream->codec->color_range); + } + max_duration = std::max(max_duration, streams_[i]->duration()); const base::TimeDelta start_time = @@ -960,17 +1116,28 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, max_duration = kInfiniteDuration(); } - // Ogg has some peculiarities around negative timestamps, so use this flag to - // setup the FFmpegDemuxerStreams appropriately. + // FFmpeg represents audio data marked as before the beginning of stream as + // having negative timestamps. This data must be discarded after it has been + // decoded, not before since it is used to warmup the decoder. There are + // currently two known cases for this: vorbis in ogg and opus in ogg and webm. + // + // For API clarity, it was decided that the rest of the media pipeline should + // not be exposed to negative timestamps. Which means we need to rebase these + // negative timestamps and mark them for discard post decoding. // // Post-decode frame dropping for packets with negative timestamps is outlined // in section A.2 in the Ogg Vorbis spec: // http://xiph.org/vorbis/doc/Vorbis_I_spec.html - if (strcmp(format_context->iformat->name, "ogg") == 0 && audio_stream && - audio_stream->codec->codec_id == AV_CODEC_ID_VORBIS) { + // + // FFmpeg's use of negative timestamps for opus pre-skip is nonstandard, but + // for more information on pre-skip see section 4.2 of the Ogg Opus spec: + // https://tools.ietf.org/html/draft-ietf-codec-oggopus-08#section-4.2 + if (audio_stream && (audio_stream->codec->codec_id == AV_CODEC_ID_OPUS || + (strcmp(format_context->iformat->name, "ogg") == 0 && + audio_stream->codec->codec_id == AV_CODEC_ID_VORBIS))) { for (size_t i = 0; i < streams_.size(); ++i) { if (streams_[i]) - streams_[i]->enable_negative_timestamp_fixups_for_ogg(); + streams_[i]->enable_negative_timestamp_fixups(); } // Fixup the seeking information to avoid selecting the audio stream simply @@ -1074,7 +1241,7 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, video_codec->time_base.num, video_codec->time_base.den)); media_log_->SetStringProperty( - "video_format", VideoFrame::FormatToString(video_config.format())); + "video_format", VideoPixelFormatToString(video_config.format())); media_log_->SetBooleanProperty("video_is_encrypted", video_config.is_encrypted()); } else { @@ -1204,19 +1371,6 @@ void FFmpegDemuxer::OnReadFrameDone(ScopedAVPacket packet, int result) { packet.swap(new_packet); } - // Special case for opus in ogg. FFmpeg is pre-trimming the codec delay - // from the packet timestamp. Chrome expects to handle this itself inside - // the decoder, so shift timestamps by the delay in this case. - // TODO(dalecurtis): Try to get fixed upstream. See http://crbug.com/328207 - if (strcmp(glue_->format_context()->iformat->name, "ogg") == 0) { - const AVCodecContext* codec_context = - glue_->format_context()->streams[packet->stream_index]->codec; - if (codec_context->codec_id == AV_CODEC_ID_OPUS && - codec_context->delay > 0) { - packet->pts += codec_context->delay; - } - } - FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index]; demuxer_stream->EnqueuePacket(packet.Pass()); } |