Home | History | Annotate | Download | only in ppapi
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "media/cdm/ppapi/ffmpeg_cdm_video_decoder.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "media/base/buffers.h"
     10 #include "media/base/limits.h"
     11 #include "media/ffmpeg/ffmpeg_common.h"
     12 
     13 // Include FFmpeg header files.
     14 extern "C" {
     15 // Temporarily disable possible loss of data warning.
     16 MSVC_PUSH_DISABLE_WARNING(4244);
     17 #include <libavcodec/avcodec.h>
     18 MSVC_POP_WARNING();
     19 }  // extern "C"
     20 
     21 namespace media {
     22 
     23 static const int kDecodeThreads = 1;
     24 
     25 static cdm::VideoFormat PixelFormatToCdmVideoFormat(PixelFormat pixel_format) {
     26   switch (pixel_format) {
     27     case PIX_FMT_YUV420P:
     28       return cdm::kYv12;
     29     default:
     30       DVLOG(1) << "Unsupported PixelFormat: " << pixel_format;
     31   }
     32   return cdm::kUnknownVideoFormat;
     33 }
     34 
     35 static PixelFormat CdmVideoFormatToPixelFormat(cdm::VideoFormat video_format) {
     36   switch (video_format) {
     37     case cdm::kYv12:
     38     case cdm::kI420:
     39       return PIX_FMT_YUV420P;
     40     case cdm::kUnknownVideoFormat:
     41     default:
     42       DVLOG(1) << "Unsupported cdm::VideoFormat: " << video_format;
     43   }
     44   return PIX_FMT_NONE;
     45 }
     46 
     47 static AVCodecID CdmVideoCodecToCodecID(
     48     cdm::VideoDecoderConfig::VideoCodec video_codec) {
     49   switch (video_codec) {
     50     case cdm::VideoDecoderConfig::kCodecVp8:
     51       return AV_CODEC_ID_VP8;
     52     case cdm::VideoDecoderConfig::kCodecH264:
     53       return AV_CODEC_ID_H264;
     54     case cdm::VideoDecoderConfig::kUnknownVideoCodec:
     55     default:
     56       NOTREACHED() << "Unsupported cdm::VideoCodec: " << video_codec;
     57       return AV_CODEC_ID_NONE;
     58   }
     59 }
     60 
     61 static int CdmVideoCodecProfileToProfileID(
     62     cdm::VideoDecoderConfig::VideoCodecProfile profile) {
     63   switch (profile) {
     64     case cdm::VideoDecoderConfig::kVp8ProfileMain:
     65       return FF_PROFILE_UNKNOWN;  // VP8 does not define an FFmpeg profile.
     66     case cdm::VideoDecoderConfig::kH264ProfileBaseline:
     67       return FF_PROFILE_H264_BASELINE;
     68     case cdm::VideoDecoderConfig::kH264ProfileMain:
     69       return FF_PROFILE_H264_MAIN;
     70     case cdm::VideoDecoderConfig::kH264ProfileExtended:
     71       return FF_PROFILE_H264_EXTENDED;
     72     case cdm::VideoDecoderConfig::kH264ProfileHigh:
     73       return FF_PROFILE_H264_HIGH;
     74     case cdm::VideoDecoderConfig::kH264ProfileHigh10:
     75       return FF_PROFILE_H264_HIGH_10;
     76     case cdm::VideoDecoderConfig::kH264ProfileHigh422:
     77       return FF_PROFILE_H264_HIGH_422;
     78     case cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive:
     79       return FF_PROFILE_H264_HIGH_444_PREDICTIVE;
     80     case cdm::VideoDecoderConfig::kUnknownVideoCodecProfile:
     81     default:
     82       NOTREACHED() << "Unknown cdm::VideoCodecProfile: " << profile;
     83       return FF_PROFILE_UNKNOWN;
     84   }
     85 }
     86 
     87 static void CdmVideoDecoderConfigToAVCodecContext(
     88     const cdm::VideoDecoderConfig& config,
     89     AVCodecContext* codec_context) {
     90   codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
     91   codec_context->codec_id = CdmVideoCodecToCodecID(config.codec);
     92   codec_context->profile = CdmVideoCodecProfileToProfileID(config.profile);
     93   codec_context->coded_width = config.coded_size.width;
     94   codec_context->coded_height = config.coded_size.height;
     95   codec_context->pix_fmt = CdmVideoFormatToPixelFormat(config.format);
     96 
     97   if (config.extra_data) {
     98     codec_context->extradata_size = config.extra_data_size;
     99     codec_context->extradata = reinterpret_cast<uint8_t*>(
    100         av_malloc(config.extra_data_size + FF_INPUT_BUFFER_PADDING_SIZE));
    101     memcpy(codec_context->extradata, config.extra_data,
    102            config.extra_data_size);
    103     memset(codec_context->extradata + config.extra_data_size, 0,
    104            FF_INPUT_BUFFER_PADDING_SIZE);
    105   } else {
    106     codec_context->extradata = NULL;
    107     codec_context->extradata_size = 0;
    108   }
    109 }
    110 
    111 static void CopyPlane(const uint8_t* source,
    112                       int32_t source_stride,
    113                       int32_t target_stride,
    114                       int32_t rows,
    115                       int32_t copy_bytes_per_row,
    116                       uint8_t* target) {
    117   DCHECK(source);
    118   DCHECK(target);
    119   DCHECK_LE(copy_bytes_per_row, source_stride);
    120   DCHECK_LE(copy_bytes_per_row, target_stride);
    121 
    122   for (int i = 0; i < rows; ++i) {
    123     const int source_offset = i * source_stride;
    124     const int target_offset = i * target_stride;
    125     memcpy(target + target_offset,
    126            source + source_offset,
    127            copy_bytes_per_row);
    128   }
    129 }
    130 
    131 FFmpegCdmVideoDecoder::FFmpegCdmVideoDecoder(ClearKeyCdmHost* host)
    132     : is_initialized_(false),
    133       host_(host) {
    134 }
    135 
    136 FFmpegCdmVideoDecoder::~FFmpegCdmVideoDecoder() {
    137   ReleaseFFmpegResources();
    138 }
    139 
    140 bool FFmpegCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) {
    141   DVLOG(1) << "Initialize()";
    142 
    143   if (!IsValidOutputConfig(config.format, config.coded_size)) {
    144     LOG(ERROR) << "Initialize(): invalid video decoder configuration.";
    145     return false;
    146   }
    147 
    148   if (is_initialized_) {
    149     LOG(ERROR) << "Initialize(): Already initialized.";
    150     return false;
    151   }
    152 
    153   // Initialize AVCodecContext structure.
    154   codec_context_.reset(avcodec_alloc_context3(NULL));
    155   CdmVideoDecoderConfigToAVCodecContext(config, codec_context_.get());
    156 
    157   // Enable motion vector search (potentially slow), strong deblocking filter
    158   // for damaged macroblocks, and set our error detection sensitivity.
    159   codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
    160   codec_context_->err_recognition = AV_EF_CAREFUL;
    161   codec_context_->thread_count = kDecodeThreads;
    162   codec_context_->opaque = this;
    163   codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
    164 
    165   AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
    166   if (!codec) {
    167     LOG(ERROR) << "Initialize(): avcodec_find_decoder failed.";
    168     return false;
    169   }
    170 
    171   int status;
    172   if ((status = avcodec_open2(codec_context_.get(), codec, NULL)) < 0) {
    173     LOG(ERROR) << "Initialize(): avcodec_open2 failed: " << status;
    174     return false;
    175   }
    176 
    177   av_frame_.reset(av_frame_alloc());
    178   is_initialized_ = true;
    179 
    180   return true;
    181 }
    182 
    183 void FFmpegCdmVideoDecoder::Deinitialize() {
    184   DVLOG(1) << "Deinitialize()";
    185   ReleaseFFmpegResources();
    186   is_initialized_ = false;
    187 }
    188 
    189 void FFmpegCdmVideoDecoder::Reset() {
    190   DVLOG(1) << "Reset()";
    191   avcodec_flush_buffers(codec_context_.get());
    192 }
    193 
    194 // static
    195 bool FFmpegCdmVideoDecoder::IsValidOutputConfig(cdm::VideoFormat format,
    196                                                 const cdm::Size& data_size) {
    197   return ((format == cdm::kYv12 || format == cdm::kI420) &&
    198           (data_size.width % 2) == 0 && (data_size.height % 2) == 0 &&
    199           data_size.width > 0 && data_size.height > 0 &&
    200           data_size.width <= limits::kMaxDimension &&
    201           data_size.height <= limits::kMaxDimension &&
    202           data_size.width * data_size.height <= limits::kMaxCanvas);
    203 }
    204 
    205 cdm::Status FFmpegCdmVideoDecoder::DecodeFrame(
    206     const uint8_t* compressed_frame,
    207     int32_t compressed_frame_size,
    208     int64_t timestamp,
    209     cdm::VideoFrame* decoded_frame) {
    210   DVLOG(1) << "DecodeFrame()";
    211   DCHECK(decoded_frame);
    212 
    213   // Create a packet for input data.
    214   AVPacket packet;
    215   av_init_packet(&packet);
    216 
    217   // The FFmpeg API does not allow us to have const read-only pointers.
    218   packet.data = const_cast<uint8_t*>(compressed_frame);
    219   packet.size = compressed_frame_size;
    220 
    221   // Let FFmpeg handle presentation timestamp reordering.
    222   codec_context_->reordered_opaque = timestamp;
    223 
    224   // Reset frame to default values.
    225   avcodec_get_frame_defaults(av_frame_.get());
    226 
    227   // This is for codecs not using get_buffer to initialize
    228   // |av_frame_->reordered_opaque|
    229   av_frame_->reordered_opaque = codec_context_->reordered_opaque;
    230 
    231   int frame_decoded = 0;
    232   int result = avcodec_decode_video2(codec_context_.get(),
    233                                      av_frame_.get(),
    234                                      &frame_decoded,
    235                                      &packet);
    236   // Log the problem when we can't decode a video frame and exit early.
    237   if (result < 0) {
    238     LOG(ERROR) << "DecodeFrame(): Error decoding video frame with timestamp: "
    239                << timestamp << " us, packet size: " << packet.size  << " bytes";
    240     return cdm::kDecodeError;
    241   }
    242 
    243   // If no frame was produced then signal that more data is required to produce
    244   // more frames.
    245   if (frame_decoded == 0)
    246     return cdm::kNeedMoreData;
    247 
    248   // The decoder is in a bad state and not decoding correctly.
    249   // Checking for NULL avoids a crash.
    250   if (!av_frame_->data[cdm::VideoFrame::kYPlane] ||
    251       !av_frame_->data[cdm::VideoFrame::kUPlane] ||
    252       !av_frame_->data[cdm::VideoFrame::kVPlane]) {
    253     LOG(ERROR) << "DecodeFrame(): Video frame has invalid frame data.";
    254     return cdm::kDecodeError;
    255   }
    256 
    257   if (!CopyAvFrameTo(decoded_frame)) {
    258     LOG(ERROR) << "DecodeFrame() could not copy video frame to output buffer.";
    259     return cdm::kDecodeError;
    260   }
    261 
    262   return cdm::kSuccess;
    263 }
    264 
    265 bool FFmpegCdmVideoDecoder::CopyAvFrameTo(cdm::VideoFrame* cdm_video_frame) {
    266   DCHECK(cdm_video_frame);
    267   DCHECK_EQ(av_frame_->format, PIX_FMT_YUV420P);
    268   DCHECK_EQ(av_frame_->width % 2, 0);
    269   DCHECK_EQ(av_frame_->height % 2, 0);
    270 
    271   const int y_size = av_frame_->width * av_frame_->height;
    272   const int uv_size = y_size / 2;
    273   const int space_required = y_size + (uv_size * 2);
    274 
    275   DCHECK(!cdm_video_frame->FrameBuffer());
    276   cdm_video_frame->SetFrameBuffer(host_->Allocate(space_required));
    277   if (!cdm_video_frame->FrameBuffer()) {
    278     LOG(ERROR) << "CopyAvFrameTo() ClearKeyCdmHost::Allocate failed.";
    279     return false;
    280   }
    281   cdm_video_frame->FrameBuffer()->SetSize(space_required);
    282 
    283   CopyPlane(av_frame_->data[cdm::VideoFrame::kYPlane],
    284             av_frame_->linesize[cdm::VideoFrame::kYPlane],
    285             av_frame_->width,
    286             av_frame_->height,
    287             av_frame_->width,
    288             cdm_video_frame->FrameBuffer()->Data());
    289 
    290   const int uv_stride = av_frame_->width / 2;
    291   const int uv_rows = av_frame_->height / 2;
    292   CopyPlane(av_frame_->data[cdm::VideoFrame::kUPlane],
    293             av_frame_->linesize[cdm::VideoFrame::kUPlane],
    294             uv_stride,
    295             uv_rows,
    296             uv_stride,
    297             cdm_video_frame->FrameBuffer()->Data() + y_size);
    298 
    299   CopyPlane(av_frame_->data[cdm::VideoFrame::kVPlane],
    300             av_frame_->linesize[cdm::VideoFrame::kVPlane],
    301             uv_stride,
    302             uv_rows,
    303             uv_stride,
    304             cdm_video_frame->FrameBuffer()->Data() + y_size + uv_size);
    305 
    306   PixelFormat format = static_cast<PixelFormat>(av_frame_->format);
    307   cdm_video_frame->SetFormat(PixelFormatToCdmVideoFormat(format));
    308 
    309   cdm::Size video_frame_size;
    310   video_frame_size.width = av_frame_->width;
    311   video_frame_size.height = av_frame_->height;
    312   cdm_video_frame->SetSize(video_frame_size);
    313 
    314   cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, 0);
    315   cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, y_size);
    316   cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane,
    317                                     y_size + uv_size);
    318 
    319   cdm_video_frame->SetStride(cdm::VideoFrame::kYPlane, av_frame_->width);
    320   cdm_video_frame->SetStride(cdm::VideoFrame::kUPlane, uv_stride);
    321   cdm_video_frame->SetStride(cdm::VideoFrame::kVPlane, uv_stride);
    322 
    323   cdm_video_frame->SetTimestamp(av_frame_->reordered_opaque);
    324 
    325   return true;
    326 }
    327 
    328 void FFmpegCdmVideoDecoder::ReleaseFFmpegResources() {
    329   DVLOG(1) << "ReleaseFFmpegResources()";
    330 
    331   codec_context_.reset();
    332   av_frame_.reset();
    333 }
    334 
    335 }  // namespace media
    336