| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/filters/ffmpeg_video_decoder.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/sys_info.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/limits.h" |
| #include "media/base/media_log.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/timestamp_constants.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_util.h" |
| #include "media/ffmpeg/ffmpeg_common.h" |
| #include "media/filters/ffmpeg_glue.h" |
| |
| namespace media { |
| |
| // Always use 2 or more threads for video decoding. Most machines today will |
| // have 2-8 execution contexts. Using more cores generally doesn't seem to |
| // increase power usage and allows us to decode video faster. |
| // |
| // Handling decoding on separate threads also frees up the pipeline thread to |
| // continue processing. Although it'd be nice to have the option of a single |
| // decoding thread, FFmpeg treats having one thread the same as having zero |
| // threads (i.e., avcodec_decode_video() will execute on the calling thread). |
| // Yet another reason for having two threads :) |
| static const int kDecodeThreads = 2; |
| static const int kMaxDecodeThreads = 16; |
| |
| // Returns the number of threads given the FFmpeg CodecID. Also inspects the |
| // command line for a valid --video-threads flag. |
| static int GetThreadCount(const VideoDecoderConfig& config) { |
| // Refer to http://crbug.com/93932 for tsan suppressions on decoding. |
| int decode_threads = kDecodeThreads; |
| |
| const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
| std::string threads(cmd_line->GetSwitchValueASCII(switches::kVideoThreads)); |
| if (threads.empty() || !base::StringToInt(threads, &decode_threads)) { |
| // Some ffmpeg codecs don't actually benefit from using more threads. |
| // Only add more threads for those codecs that we know will benefit. |
| switch (config.codec()) { |
| case kUnknownVideoCodec: |
| case kCodecVC1: |
| case kCodecMPEG2: |
| case kCodecHEVC: |
| case kCodecVP9: |
| case kCodecDolbyVision: |
| // We do not compile ffmpeg with support for any of these codecs. |
| break; |
| |
| case kCodecTheora: |
| // No extra threads for these codecs. |
| break; |
| |
| case kCodecH264: |
| case kCodecMPEG4: |
| case kCodecVP8: |
| // Normalize to three threads for 1080p content, then scale linearly |
| // with number of pixels. |
| // Examples: |
| // 4k: 12 threads |
| // 1440p: 5 threads |
| // 1080p: 3 threads |
| // anything lower than 1080p: 2 threads |
| decode_threads = config.coded_size().width() * |
| config.coded_size().height() * 3 / 1920 / 1080; |
| |
| int cores = base::SysInfo::NumberOfProcessors(); |
| // Leave two execution contexts for other things to run. |
| decode_threads = std::min(decode_threads, cores - 2); |
| // Use at least two threads, or ffmpeg will decode on the calling |
| // thread. |
| decode_threads = std::max(decode_threads, kDecodeThreads); |
| } |
| } |
| |
| decode_threads = std::max(decode_threads, 0); |
| decode_threads = std::min(decode_threads, kMaxDecodeThreads); |
| return decode_threads; |
| } |
| |
| static int GetVideoBufferImpl(struct AVCodecContext* s, |
| AVFrame* frame, |
| int flags) { |
| FFmpegVideoDecoder* decoder = static_cast<FFmpegVideoDecoder*>(s->opaque); |
| return decoder->GetVideoBuffer(s, frame, flags); |
| } |
| |
| static void ReleaseVideoBufferImpl(void* opaque, uint8_t* data) { |
| if (opaque) |
| static_cast<VideoFrame*>(opaque)->Release(); |
| } |
| |
| // static |
| bool FFmpegVideoDecoder::IsCodecSupported(VideoCodec codec) { |
| FFmpegGlue::InitializeFFmpeg(); |
| return avcodec_find_decoder(VideoCodecToCodecID(codec)) != nullptr; |
| } |
| |
| FFmpegVideoDecoder::FFmpegVideoDecoder(MediaLog* media_log) |
| : media_log_(media_log), state_(kUninitialized), decode_nalus_(false) { |
| thread_checker_.DetachFromThread(); |
| } |
| |
| int FFmpegVideoDecoder::GetVideoBuffer(struct AVCodecContext* codec_context, |
| AVFrame* frame, |
| int flags) { |
| // Don't use |codec_context_| here! With threaded decoding, |
| // it will contain unsynchronized width/height/pix_fmt values, |
| // whereas |codec_context| contains the current threads's |
| // updated width/height/pix_fmt, which can change for adaptive |
| // content. |
| const VideoPixelFormat format = |
| AVPixelFormatToVideoPixelFormat(codec_context->pix_fmt); |
| |
| if (format == PIXEL_FORMAT_UNKNOWN) |
| return AVERROR(EINVAL); |
| DCHECK(format == PIXEL_FORMAT_YV12 || format == PIXEL_FORMAT_YV16 || |
| format == PIXEL_FORMAT_YV24 || format == PIXEL_FORMAT_YUV420P9 || |
| format == PIXEL_FORMAT_YUV420P10 || format == PIXEL_FORMAT_YUV422P9 || |
| format == PIXEL_FORMAT_YUV422P10 || format == PIXEL_FORMAT_YUV444P9 || |
| format == PIXEL_FORMAT_YUV444P10 || format == PIXEL_FORMAT_YUV420P12 || |
| format == PIXEL_FORMAT_YUV422P12 || format == PIXEL_FORMAT_YUV444P12); |
| |
| gfx::Size size(codec_context->width, codec_context->height); |
| const int ret = av_image_check_size(size.width(), size.height(), 0, NULL); |
| if (ret < 0) |
| return ret; |
| |
| gfx::Size natural_size; |
| if (codec_context->sample_aspect_ratio.num > 0) { |
| natural_size = GetNaturalSize(size, |
| codec_context->sample_aspect_ratio.num, |
| codec_context->sample_aspect_ratio.den); |
| } else { |
| natural_size = config_.natural_size(); |
| } |
| |
| // FFmpeg has specific requirements on the allocation size of the frame. The |
| // following logic replicates FFmpeg's allocation strategy to ensure buffers |
| // are not overread / overwritten. See ff_init_buffer_info() for details. |
| // |
| // When lowres is non-zero, dimensions should be divided by 2^(lowres), but |
| // since we don't use this, just DCHECK that it's zero. |
| DCHECK_EQ(codec_context->lowres, 0); |
| gfx::Size coded_size(std::max(size.width(), codec_context->coded_width), |
| std::max(size.height(), codec_context->coded_height)); |
| |
| if (!VideoFrame::IsValidConfig(format, VideoFrame::STORAGE_UNKNOWN, |
| coded_size, gfx::Rect(size), natural_size)) { |
| return AVERROR(EINVAL); |
| } |
| |
| // FFmpeg expects the initialize allocation to be zero-initialized. Failure |
| // to do so can lead to unitialized value usage. See http://crbug.com/390941 |
| scoped_refptr<VideoFrame> video_frame = frame_pool_.CreateFrame( |
| format, coded_size, gfx::Rect(size), natural_size, kNoTimestamp); |
| |
| if (!video_frame) |
| return AVERROR(EINVAL); |
| |
| // Prefer the color space from the codec context. If it's not specified (or is |
| // set to an unsupported value), fall back on the value from the config. |
| ColorSpace color_space = AVColorSpaceToColorSpace(codec_context->colorspace, |
| codec_context->color_range); |
| if (color_space == COLOR_SPACE_UNSPECIFIED) |
| color_space = config_.color_space(); |
| video_frame->metadata()->SetInteger(VideoFrameMetadata::COLOR_SPACE, |
| color_space); |
| |
| if (codec_context->color_primaries != AVCOL_PRI_UNSPECIFIED || |
| codec_context->color_trc != AVCOL_TRC_UNSPECIFIED || |
| codec_context->colorspace != AVCOL_SPC_UNSPECIFIED) { |
| video_frame->set_color_space(gfx::ColorSpace::CreateVideo( |
| codec_context->color_primaries, codec_context->color_trc, |
| codec_context->colorspace, |
| codec_context->color_range != AVCOL_RANGE_MPEG |
| ? gfx::ColorSpace::RangeID::FULL |
| : gfx::ColorSpace::RangeID::LIMITED)); |
| } |
| |
| for (size_t i = 0; i < VideoFrame::NumPlanes(video_frame->format()); i++) { |
| frame->data[i] = video_frame->data(i); |
| frame->linesize[i] = video_frame->stride(i); |
| } |
| |
| frame->width = coded_size.width(); |
| frame->height = coded_size.height(); |
| frame->format = codec_context->pix_fmt; |
| frame->reordered_opaque = codec_context->reordered_opaque; |
| |
| // Now create an AVBufferRef for the data just allocated. It will own the |
| // reference to the VideoFrame object. |
| VideoFrame* opaque = video_frame.get(); |
| opaque->AddRef(); |
| frame->buf[0] = |
| av_buffer_create(frame->data[0], |
| VideoFrame::AllocationSize(format, coded_size), |
| ReleaseVideoBufferImpl, |
| opaque, |
| 0); |
| return 0; |
| } |
| |
| std::string FFmpegVideoDecoder::GetDisplayName() const { |
| return "FFmpegVideoDecoder"; |
| } |
| |
| void FFmpegVideoDecoder::Initialize(const VideoDecoderConfig& config, |
| bool low_delay, |
| CdmContext* /* cdm_context */, |
| const InitCB& init_cb, |
| const OutputCB& output_cb) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(config.IsValidConfig()); |
| DCHECK(!output_cb.is_null()); |
| |
| InitCB bound_init_cb = BindToCurrentLoop(init_cb); |
| |
| if (config.is_encrypted()) { |
| bound_init_cb.Run(false); |
| return; |
| } |
| |
| FFmpegGlue::InitializeFFmpeg(); |
| config_ = config; |
| |
| // TODO(xhwang): Only set |config_| after we successfully configure the |
| // decoder. |
| if (!ConfigureDecoder(low_delay)) { |
| bound_init_cb.Run(false); |
| return; |
| } |
| |
| output_cb_ = output_cb; |
| |
| // Success! |
| state_ = kNormal; |
| bound_init_cb.Run(true); |
| } |
| |
| void FFmpegVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer, |
| const DecodeCB& decode_cb) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(buffer.get()); |
| DCHECK(!decode_cb.is_null()); |
| CHECK_NE(state_, kUninitialized); |
| |
| DecodeCB decode_cb_bound = BindToCurrentLoop(decode_cb); |
| |
| if (state_ == kError) { |
| decode_cb_bound.Run(DecodeStatus::DECODE_ERROR); |
| return; |
| } |
| |
| if (state_ == kDecodeFinished) { |
| decode_cb_bound.Run(DecodeStatus::OK); |
| return; |
| } |
| |
| DCHECK_EQ(state_, kNormal); |
| |
| // During decode, because reads are issued asynchronously, it is possible to |
| // receive multiple end of stream buffers since each decode is acked. When the |
| // first end of stream buffer is read, FFmpeg may still have frames queued |
| // up in the decoder so we need to go through the decode loop until it stops |
| // giving sensible data. After that, the decoder should output empty |
| // frames. There are three states the decoder can be in: |
| // |
| // kNormal: This is the starting state. Buffers are decoded. Decode errors |
| // are discarded. |
| // kDecodeFinished: All calls return empty frames. |
| // kError: Unexpected error happened. |
| // |
| // These are the possible state transitions. |
| // |
| // kNormal -> kDecodeFinished: |
| // When EOS buffer is received and the codec has been flushed. |
| // kNormal -> kError: |
| // A decoding error occurs and decoding needs to stop. |
| // (any state) -> kNormal: |
| // Any time Reset() is called. |
| |
| bool has_produced_frame; |
| do { |
| has_produced_frame = false; |
| if (!FFmpegDecode(buffer, &has_produced_frame)) { |
| state_ = kError; |
| decode_cb_bound.Run(DecodeStatus::DECODE_ERROR); |
| return; |
| } |
| // Repeat to flush the decoder after receiving EOS buffer. |
| } while (buffer->end_of_stream() && has_produced_frame); |
| |
| if (buffer->end_of_stream()) |
| state_ = kDecodeFinished; |
| |
| // VideoDecoderShim expects that |decode_cb| is called only after |
| // |output_cb_|. |
| decode_cb_bound.Run(DecodeStatus::OK); |
| } |
| |
| void FFmpegVideoDecoder::Reset(const base::Closure& closure) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| avcodec_flush_buffers(codec_context_.get()); |
| state_ = kNormal; |
| // PostTask() to avoid calling |closure| inmediately. |
| base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, closure); |
| } |
| |
| FFmpegVideoDecoder::~FFmpegVideoDecoder() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (state_ != kUninitialized) |
| ReleaseFFmpegResources(); |
| } |
| |
| bool FFmpegVideoDecoder::FFmpegDecode( |
| const scoped_refptr<DecoderBuffer>& buffer, |
| bool* has_produced_frame) { |
| DCHECK(!*has_produced_frame); |
| |
| // Create a packet for input data. |
| // Due to FFmpeg API changes we no longer have const read-only pointers. |
| AVPacket packet; |
| av_init_packet(&packet); |
| if (buffer->end_of_stream()) { |
| packet.data = NULL; |
| packet.size = 0; |
| } else { |
| packet.data = const_cast<uint8_t*>(buffer->data()); |
| packet.size = buffer->data_size(); |
| |
| // Let FFmpeg handle presentation timestamp reordering. |
| codec_context_->reordered_opaque = buffer->timestamp().InMicroseconds(); |
| } |
| |
| int frame_decoded = 0; |
| int result = avcodec_decode_video2(codec_context_.get(), |
| av_frame_.get(), |
| &frame_decoded, |
| &packet); |
| // Log the problem if we can't decode a video frame and exit early. |
| if (result < 0) { |
| MEDIA_LOG(DEBUG, media_log_) |
| << GetDisplayName() |
| << ": avcodec_decode_video2(): " << AVErrorToString(result) << ", at " |
| << buffer->AsHumanReadableString(); |
| return false; |
| } |
| |
| // FFmpeg says some codecs might have multiple frames per packet. Previous |
| // discussions with rbultje@ indicate this shouldn't be true for the codecs |
| // we use. |
| DCHECK_EQ(result, packet.size); |
| |
| // If no frame was produced then signal that more data is required to |
| // produce more frames. This can happen under two circumstances: |
| // 1) Decoder was recently initialized/flushed |
| // 2) End of stream was reached and all internal frames have been output |
| if (frame_decoded == 0) { |
| return true; |
| } |
| |
| // TODO(fbarchard): Work around for FFmpeg http://crbug.com/27675 |
| // The decoder is in a bad state and not decoding correctly. |
| // Checking for NULL avoids a crash in CopyPlane(). |
| if (!av_frame_->data[VideoFrame::kYPlane] || |
| !av_frame_->data[VideoFrame::kUPlane] || |
| !av_frame_->data[VideoFrame::kVPlane]) { |
| LOG(ERROR) << "Video frame was produced yet has invalid frame data."; |
| av_frame_unref(av_frame_.get()); |
| return false; |
| } |
| |
| scoped_refptr<VideoFrame> frame = |
| reinterpret_cast<VideoFrame*>(av_buffer_get_opaque(av_frame_->buf[0])); |
| frame->set_timestamp( |
| base::TimeDelta::FromMicroseconds(av_frame_->reordered_opaque)); |
| *has_produced_frame = true; |
| output_cb_.Run(frame); |
| |
| av_frame_unref(av_frame_.get()); |
| return true; |
| } |
| |
| void FFmpegVideoDecoder::ReleaseFFmpegResources() { |
| codec_context_.reset(); |
| av_frame_.reset(); |
| } |
| |
| bool FFmpegVideoDecoder::ConfigureDecoder(bool low_delay) { |
| DCHECK(config_.IsValidConfig()); |
| DCHECK(!config_.is_encrypted()); |
| |
| // Release existing decoder resources if necessary. |
| ReleaseFFmpegResources(); |
| |
| // Initialize AVCodecContext structure. |
| codec_context_.reset(avcodec_alloc_context3(NULL)); |
| VideoDecoderConfigToAVCodecContext(config_, codec_context_.get()); |
| |
| codec_context_->thread_count = GetThreadCount(config_); |
| codec_context_->thread_type = |
| FF_THREAD_SLICE | (low_delay ? 0 : FF_THREAD_FRAME); |
| codec_context_->opaque = this; |
| codec_context_->flags |= CODEC_FLAG_EMU_EDGE; |
| codec_context_->get_buffer2 = GetVideoBufferImpl; |
| codec_context_->refcounted_frames = 1; |
| |
| if (decode_nalus_) |
| codec_context_->flags2 |= CODEC_FLAG2_CHUNKS; |
| |
| AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); |
| if (!codec || avcodec_open2(codec_context_.get(), codec, NULL) < 0) { |
| ReleaseFFmpegResources(); |
| return false; |
| } |
| |
| av_frame_.reset(av_frame_alloc()); |
| return true; |
| } |
| |
| } // namespace media |