// 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 "webkit/media/crypto/ppapi/ffmpeg_cdm_video_decoder.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "media/base/buffers.h" #include "media/base/limits.h" #include "webkit/media/crypto/ppapi/cdm/content_decryption_module.h" // Include FFmpeg header files. extern "C" { // Temporarily disable possible loss of data warning. MSVC_PUSH_DISABLE_WARNING(4244); #include MSVC_POP_WARNING(); } // extern "C" namespace webkit_media { static const int kDecodeThreads = 1; static cdm::VideoFormat PixelFormatToCdmVideoFormat(PixelFormat pixel_format) { switch (pixel_format) { case PIX_FMT_YUV420P: return cdm::kYv12; default: DVLOG(1) << "Unsupported PixelFormat: " << pixel_format; } return cdm::kUnknownVideoFormat; } static PixelFormat CdmVideoFormatToPixelFormat(cdm::VideoFormat video_format) { switch (video_format) { case cdm::kYv12: case cdm::kI420: return PIX_FMT_YUV420P; case cdm::kUnknownVideoFormat: default: DVLOG(1) << "Unsupported cdm::VideoFormat: " << video_format; } return PIX_FMT_NONE; } static CodecID CdmVideoCodecToCodecID( cdm::VideoDecoderConfig::VideoCodec video_codec) { switch (video_codec) { case cdm::VideoDecoderConfig::kCodecVp8: return CODEC_ID_VP8; case cdm::VideoDecoderConfig::kCodecH264: return CODEC_ID_H264; case cdm::VideoDecoderConfig::kUnknownVideoCodec: default: NOTREACHED() << "Unsupported cdm::VideoCodec: " << video_codec; return CODEC_ID_NONE; } } static int CdmVideoCodecProfileToProfileID( cdm::VideoDecoderConfig::VideoCodecProfile profile) { switch (profile) { case cdm::VideoDecoderConfig::kVp8ProfileMain: return FF_PROFILE_UNKNOWN; // VP8 does not define an FFmpeg profile. case cdm::VideoDecoderConfig::kH264ProfileBaseline: return FF_PROFILE_H264_BASELINE; case cdm::VideoDecoderConfig::kH264ProfileMain: return FF_PROFILE_H264_MAIN; case cdm::VideoDecoderConfig::kH264ProfileExtended: return FF_PROFILE_H264_EXTENDED; case cdm::VideoDecoderConfig::kH264ProfileHigh: return FF_PROFILE_H264_HIGH; case cdm::VideoDecoderConfig::kH264ProfileHigh10: return FF_PROFILE_H264_HIGH_10; case cdm::VideoDecoderConfig::kH264ProfileHigh422: return FF_PROFILE_H264_HIGH_422; case cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive: return FF_PROFILE_H264_HIGH_444_PREDICTIVE; case cdm::VideoDecoderConfig::kUnknownVideoCodecProfile: default: NOTREACHED() << "Unknown cdm::VideoCodecProfile: " << profile; return FF_PROFILE_UNKNOWN; } } static void CdmVideoDecoderConfigToAVCodecContext( const cdm::VideoDecoderConfig& config, AVCodecContext* codec_context) { codec_context->codec_type = AVMEDIA_TYPE_VIDEO; codec_context->codec_id = CdmVideoCodecToCodecID(config.codec); codec_context->profile = CdmVideoCodecProfileToProfileID(config.profile); codec_context->coded_width = config.coded_size.width; codec_context->coded_height = config.coded_size.height; codec_context->pix_fmt = CdmVideoFormatToPixelFormat(config.format); if (config.extra_data) { codec_context->extradata_size = config.extra_data_size; codec_context->extradata = reinterpret_cast( av_malloc(config.extra_data_size + FF_INPUT_BUFFER_PADDING_SIZE)); memcpy(codec_context->extradata, config.extra_data, config.extra_data_size); memset(codec_context->extradata + config.extra_data_size, 0, FF_INPUT_BUFFER_PADDING_SIZE); } else { codec_context->extradata = NULL; codec_context->extradata_size = 0; } } static void CopyPlane(const uint8_t* source, int32_t source_stride, int32_t target_stride, int32_t rows, int32_t copy_bytes_per_row, uint8_t* target) { DCHECK(source); DCHECK(target); DCHECK_LE(copy_bytes_per_row, source_stride); DCHECK_LE(copy_bytes_per_row, target_stride); for (int i = 0; i < rows; ++i) { const int source_offset = i * source_stride; const int target_offset = i * target_stride; memcpy(target + target_offset, source + source_offset, copy_bytes_per_row); } } FFmpegCdmVideoDecoder::FFmpegCdmVideoDecoder(cdm::Host* host) : codec_context_(NULL), av_frame_(NULL), is_initialized_(false), host_(host) { } FFmpegCdmVideoDecoder::~FFmpegCdmVideoDecoder() { ReleaseFFmpegResources(); } bool FFmpegCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) { DVLOG(1) << "Initialize()"; if (!IsValidOutputConfig(config.format, config.coded_size)) { LOG(ERROR) << "Initialize(): invalid video decoder configuration."; return false; } if (is_initialized_) { LOG(ERROR) << "Initialize(): Already initialized."; return false; } // Initialize AVCodecContext structure. codec_context_ = avcodec_alloc_context3(NULL); CdmVideoDecoderConfigToAVCodecContext(config, codec_context_); // Enable motion vector search (potentially slow), strong deblocking filter // for damaged macroblocks, and set our error detection sensitivity. codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK; codec_context_->err_recognition = AV_EF_CAREFUL; codec_context_->thread_count = kDecodeThreads; codec_context_->opaque = this; codec_context_->flags |= CODEC_FLAG_EMU_EDGE; AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); if (!codec) { LOG(ERROR) << "Initialize(): avcodec_find_decoder failed."; return false; } int status; if ((status = avcodec_open2(codec_context_, codec, NULL)) < 0) { LOG(ERROR) << "Initialize(): avcodec_open2 failed: " << status; return false; } av_frame_ = avcodec_alloc_frame(); is_initialized_ = true; return true; } void FFmpegCdmVideoDecoder::Deinitialize() { DVLOG(1) << "Deinitialize()"; ReleaseFFmpegResources(); is_initialized_ = false; } void FFmpegCdmVideoDecoder::Reset() { DVLOG(1) << "Reset()"; avcodec_flush_buffers(codec_context_); } // static bool FFmpegCdmVideoDecoder::IsValidOutputConfig(cdm::VideoFormat format, const cdm::Size& data_size) { return ((format == cdm::kYv12 || format == cdm::kI420) && (data_size.width % 2) == 0 && (data_size.height % 2) == 0 && data_size.width > 0 && data_size.height > 0 && data_size.width <= media::limits::kMaxDimension && data_size.height <= media::limits::kMaxDimension && data_size.width * data_size.height <= media::limits::kMaxCanvas); } cdm::Status FFmpegCdmVideoDecoder::DecodeFrame( const uint8_t* compressed_frame, int32_t compressed_frame_size, int64_t timestamp, cdm::VideoFrame* decoded_frame) { DVLOG(1) << "DecodeFrame()"; DCHECK(decoded_frame); // Create a packet for input data. AVPacket packet; av_init_packet(&packet); // The FFmpeg API does not allow us to have const read-only pointers. packet.data = const_cast(compressed_frame); packet.size = compressed_frame_size; // Let FFmpeg handle presentation timestamp reordering. codec_context_->reordered_opaque = timestamp; // Reset frame to default values. avcodec_get_frame_defaults(av_frame_); // This is for codecs not using get_buffer to initialize // |av_frame_->reordered_opaque| av_frame_->reordered_opaque = codec_context_->reordered_opaque; int frame_decoded = 0; int result = avcodec_decode_video2(codec_context_, av_frame_, &frame_decoded, &packet); // Log the problem when we can't decode a video frame and exit early. if (result < 0) { LOG(ERROR) << "DecodeFrame(): Error decoding video frame with timestamp: " << timestamp << " us, packet size: " << packet.size << " bytes"; return cdm::kDecodeError; } // If no frame was produced then signal that more data is required to produce // more frames. if (frame_decoded == 0) return cdm::kNeedMoreData; // The decoder is in a bad state and not decoding correctly. // Checking for NULL avoids a crash. if (!av_frame_->data[cdm::VideoFrame::kYPlane] || !av_frame_->data[cdm::VideoFrame::kUPlane] || !av_frame_->data[cdm::VideoFrame::kVPlane]) { LOG(ERROR) << "DecodeFrame(): Video frame has invalid frame data."; return cdm::kDecodeError; } if (!CopyAvFrameTo(decoded_frame)) { LOG(ERROR) << "DecodeFrame() could not copy video frame to output buffer."; return cdm::kDecodeError; } return cdm::kSuccess; } bool FFmpegCdmVideoDecoder::CopyAvFrameTo(cdm::VideoFrame* cdm_video_frame) { DCHECK(cdm_video_frame); DCHECK_EQ(av_frame_->format, PIX_FMT_YUV420P); DCHECK_EQ(av_frame_->width % 2, 0); DCHECK_EQ(av_frame_->height % 2, 0); const int y_size = av_frame_->width * av_frame_->height; const int uv_size = y_size / 2; const int space_required = y_size + (uv_size * 2); DCHECK(!cdm_video_frame->FrameBuffer()); cdm_video_frame->SetFrameBuffer(host_->Allocate(space_required)); if (!cdm_video_frame->FrameBuffer()) { LOG(ERROR) << "CopyAvFrameTo() cdm::Host::Allocate failed."; return false; } cdm_video_frame->FrameBuffer()->SetSize(space_required); CopyPlane(av_frame_->base[cdm::VideoFrame::kYPlane], av_frame_->linesize[cdm::VideoFrame::kYPlane], av_frame_->width, av_frame_->height, av_frame_->width, cdm_video_frame->FrameBuffer()->Data()); const int uv_stride = av_frame_->width / 2; const int uv_rows = av_frame_->height / 2; CopyPlane(av_frame_->base[cdm::VideoFrame::kUPlane], av_frame_->linesize[cdm::VideoFrame::kUPlane], uv_stride, uv_rows, uv_stride, cdm_video_frame->FrameBuffer()->Data() + y_size); CopyPlane(av_frame_->base[cdm::VideoFrame::kVPlane], av_frame_->linesize[cdm::VideoFrame::kVPlane], uv_stride, uv_rows, uv_stride, cdm_video_frame->FrameBuffer()->Data() + y_size + uv_size); PixelFormat format = static_cast(av_frame_->format); cdm_video_frame->SetFormat(PixelFormatToCdmVideoFormat(format)); cdm::Size video_frame_size; video_frame_size.width = av_frame_->width; video_frame_size.height = av_frame_->height; cdm_video_frame->SetSize(video_frame_size); cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, 0); cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, y_size); cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane, y_size + uv_size); cdm_video_frame->SetStride(cdm::VideoFrame::kYPlane, av_frame_->width); cdm_video_frame->SetStride(cdm::VideoFrame::kUPlane, uv_stride); cdm_video_frame->SetStride(cdm::VideoFrame::kVPlane, uv_stride); cdm_video_frame->SetTimestamp(av_frame_->reordered_opaque); return true; } void FFmpegCdmVideoDecoder::ReleaseFFmpegResources() { DVLOG(1) << "ReleaseFFmpegResources()"; if (codec_context_) { av_free(codec_context_->extradata); avcodec_close(codec_context_); av_free(codec_context_); codec_context_ = NULL; } if (av_frame_) { av_free(av_frame_); av_frame_ = NULL; } } } // namespace webkit_media