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