summaryrefslogtreecommitdiff
path: root/chromium/media/filters/ffmpeg_demuxer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/media/filters/ffmpeg_demuxer.cc')
-rw-r--r--chromium/media/filters/ffmpeg_demuxer.cc244
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());
}