1 // Copyright (c) 2012 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.
5 #include "webkit/media/crypto/ppapi/ffmpeg_cdm_video_decoder.h"
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 "webkit/media/crypto/ppapi/cdm/content_decryption_module.h"
13 // Include FFmpeg header files.
15 // Temporarily disable possible loss of data warning.
16 MSVC_PUSH_DISABLE_WARNING(4244);
17 #include <libavcodec/avcodec.h>
21 namespace webkit_media
{
23 static const int kDecodeThreads
= 1;
25 static cdm::VideoFormat
PixelFormatToCdmVideoFormat(PixelFormat pixel_format
) {
26 switch (pixel_format
) {
30 DVLOG(1) << "Unsupported PixelFormat: " << pixel_format
;
32 return cdm::kUnknownVideoFormat
;
35 static PixelFormat
CdmVideoFormatToPixelFormat(cdm::VideoFormat video_format
) {
36 switch (video_format
) {
39 return PIX_FMT_YUV420P
;
40 case cdm::kUnknownVideoFormat
:
42 DVLOG(1) << "Unsupported cdm::VideoFormat: " << video_format
;
47 static CodecID
CdmVideoCodecToCodecID(
48 cdm::VideoDecoderConfig::VideoCodec video_codec
) {
49 switch (video_codec
) {
50 case cdm::VideoDecoderConfig::kCodecVp8
:
52 case cdm::VideoDecoderConfig::kCodecH264
:
54 case cdm::VideoDecoderConfig::kUnknownVideoCodec
:
56 NOTREACHED() << "Unsupported cdm::VideoCodec: " << video_codec
;
61 static int CdmVideoCodecProfileToProfileID(
62 cdm::VideoDecoderConfig::VideoCodecProfile 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
:
82 NOTREACHED() << "Unknown cdm::VideoCodecProfile: " << profile
;
83 return FF_PROFILE_UNKNOWN
;
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
);
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
);
106 codec_context
->extradata
= NULL
;
107 codec_context
->extradata_size
= 0;
111 static void CopyPlane(const uint8_t* source
,
112 int32_t source_stride
,
113 int32_t target_stride
,
115 int32_t copy_bytes_per_row
,
119 DCHECK_LE(copy_bytes_per_row
, source_stride
);
120 DCHECK_LE(copy_bytes_per_row
, target_stride
);
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
,
131 FFmpegCdmVideoDecoder::FFmpegCdmVideoDecoder(cdm::Host
* host
)
132 : codec_context_(NULL
),
134 is_initialized_(false),
138 FFmpegCdmVideoDecoder::~FFmpegCdmVideoDecoder() {
139 ReleaseFFmpegResources();
142 bool FFmpegCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig
& config
) {
143 DVLOG(1) << "Initialize()";
145 if (!IsValidOutputConfig(config
.format
, config
.coded_size
)) {
146 LOG(ERROR
) << "Initialize(): invalid video decoder configuration.";
150 if (is_initialized_
) {
151 LOG(ERROR
) << "Initialize(): Already initialized.";
155 // Initialize AVCodecContext structure.
156 codec_context_
= avcodec_alloc_context3(NULL
);
157 CdmVideoDecoderConfigToAVCodecContext(config
, codec_context_
);
159 // Enable motion vector search (potentially slow), strong deblocking filter
160 // for damaged macroblocks, and set our error detection sensitivity.
161 codec_context_
->error_concealment
= FF_EC_GUESS_MVS
| FF_EC_DEBLOCK
;
162 codec_context_
->err_recognition
= AV_EF_CAREFUL
;
163 codec_context_
->thread_count
= kDecodeThreads
;
164 codec_context_
->opaque
= this;
165 codec_context_
->flags
|= CODEC_FLAG_EMU_EDGE
;
167 AVCodec
* codec
= avcodec_find_decoder(codec_context_
->codec_id
);
169 LOG(ERROR
) << "Initialize(): avcodec_find_decoder failed.";
174 if ((status
= avcodec_open2(codec_context_
, codec
, NULL
)) < 0) {
175 LOG(ERROR
) << "Initialize(): avcodec_open2 failed: " << status
;
179 av_frame_
= avcodec_alloc_frame();
180 is_initialized_
= true;
185 void FFmpegCdmVideoDecoder::Deinitialize() {
186 DVLOG(1) << "Deinitialize()";
187 ReleaseFFmpegResources();
188 is_initialized_
= false;
191 void FFmpegCdmVideoDecoder::Reset() {
192 DVLOG(1) << "Reset()";
193 avcodec_flush_buffers(codec_context_
);
197 bool FFmpegCdmVideoDecoder::IsValidOutputConfig(cdm::VideoFormat format
,
198 const cdm::Size
& data_size
) {
199 return ((format
== cdm::kYv12
|| format
== cdm::kI420
) &&
200 (data_size
.width
% 2) == 0 && (data_size
.height
% 2) == 0 &&
201 data_size
.width
> 0 && data_size
.height
> 0 &&
202 data_size
.width
<= media::limits::kMaxDimension
&&
203 data_size
.height
<= media::limits::kMaxDimension
&&
204 data_size
.width
* data_size
.height
<= media::limits::kMaxCanvas
);
207 cdm::Status
FFmpegCdmVideoDecoder::DecodeFrame(
208 const uint8_t* compressed_frame
,
209 int32_t compressed_frame_size
,
211 cdm::VideoFrame
* decoded_frame
) {
212 DVLOG(1) << "DecodeFrame()";
213 DCHECK(decoded_frame
);
215 // Create a packet for input data.
217 av_init_packet(&packet
);
219 // The FFmpeg API does not allow us to have const read-only pointers.
220 packet
.data
= const_cast<uint8_t*>(compressed_frame
);
221 packet
.size
= compressed_frame_size
;
223 // Let FFmpeg handle presentation timestamp reordering.
224 codec_context_
->reordered_opaque
= timestamp
;
226 // Reset frame to default values.
227 avcodec_get_frame_defaults(av_frame_
);
229 // This is for codecs not using get_buffer to initialize
230 // |av_frame_->reordered_opaque|
231 av_frame_
->reordered_opaque
= codec_context_
->reordered_opaque
;
233 int frame_decoded
= 0;
234 int result
= avcodec_decode_video2(codec_context_
,
238 // Log the problem when we can't decode a video frame and exit early.
240 LOG(ERROR
) << "DecodeFrame(): Error decoding video frame with timestamp: "
241 << timestamp
<< " us, packet size: " << packet
.size
<< " bytes";
242 return cdm::kDecodeError
;
245 // If no frame was produced then signal that more data is required to produce
247 if (frame_decoded
== 0)
248 return cdm::kNeedMoreData
;
250 // The decoder is in a bad state and not decoding correctly.
251 // Checking for NULL avoids a crash.
252 if (!av_frame_
->data
[cdm::VideoFrame::kYPlane
] ||
253 !av_frame_
->data
[cdm::VideoFrame::kUPlane
] ||
254 !av_frame_
->data
[cdm::VideoFrame::kVPlane
]) {
255 LOG(ERROR
) << "DecodeFrame(): Video frame has invalid frame data.";
256 return cdm::kDecodeError
;
259 if (!CopyAvFrameTo(decoded_frame
)) {
260 LOG(ERROR
) << "DecodeFrame() could not copy video frame to output buffer.";
261 return cdm::kDecodeError
;
264 return cdm::kSuccess
;
267 bool FFmpegCdmVideoDecoder::CopyAvFrameTo(cdm::VideoFrame
* cdm_video_frame
) {
268 DCHECK(cdm_video_frame
);
269 DCHECK_EQ(av_frame_
->format
, PIX_FMT_YUV420P
);
270 DCHECK_EQ(av_frame_
->width
% 2, 0);
271 DCHECK_EQ(av_frame_
->height
% 2, 0);
273 const int y_size
= av_frame_
->width
* av_frame_
->height
;
274 const int uv_size
= y_size
/ 2;
275 const int space_required
= y_size
+ (uv_size
* 2);
277 DCHECK(!cdm_video_frame
->FrameBuffer());
278 cdm_video_frame
->SetFrameBuffer(host_
->Allocate(space_required
));
279 if (!cdm_video_frame
->FrameBuffer()) {
280 LOG(ERROR
) << "CopyAvFrameTo() cdm::Host::Allocate failed.";
283 cdm_video_frame
->FrameBuffer()->SetSize(space_required
);
285 CopyPlane(av_frame_
->base
[cdm::VideoFrame::kYPlane
],
286 av_frame_
->linesize
[cdm::VideoFrame::kYPlane
],
290 cdm_video_frame
->FrameBuffer()->Data());
292 const int uv_stride
= av_frame_
->width
/ 2;
293 const int uv_rows
= av_frame_
->height
/ 2;
294 CopyPlane(av_frame_
->base
[cdm::VideoFrame::kUPlane
],
295 av_frame_
->linesize
[cdm::VideoFrame::kUPlane
],
299 cdm_video_frame
->FrameBuffer()->Data() + y_size
);
301 CopyPlane(av_frame_
->base
[cdm::VideoFrame::kVPlane
],
302 av_frame_
->linesize
[cdm::VideoFrame::kVPlane
],
306 cdm_video_frame
->FrameBuffer()->Data() + y_size
+ uv_size
);
308 PixelFormat format
= static_cast<PixelFormat
>(av_frame_
->format
);
309 cdm_video_frame
->SetFormat(PixelFormatToCdmVideoFormat(format
));
311 cdm::Size video_frame_size
;
312 video_frame_size
.width
= av_frame_
->width
;
313 video_frame_size
.height
= av_frame_
->height
;
314 cdm_video_frame
->SetSize(video_frame_size
);
316 cdm_video_frame
->SetPlaneOffset(cdm::VideoFrame::kYPlane
, 0);
317 cdm_video_frame
->SetPlaneOffset(cdm::VideoFrame::kUPlane
, y_size
);
318 cdm_video_frame
->SetPlaneOffset(cdm::VideoFrame::kVPlane
,
321 cdm_video_frame
->SetStride(cdm::VideoFrame::kYPlane
, av_frame_
->width
);
322 cdm_video_frame
->SetStride(cdm::VideoFrame::kUPlane
, uv_stride
);
323 cdm_video_frame
->SetStride(cdm::VideoFrame::kVPlane
, uv_stride
);
325 cdm_video_frame
->SetTimestamp(av_frame_
->reordered_opaque
);
330 void FFmpegCdmVideoDecoder::ReleaseFFmpegResources() {
331 DVLOG(1) << "ReleaseFFmpegResources()";
333 if (codec_context_
) {
334 av_free(codec_context_
->extradata
);
335 avcodec_close(codec_context_
);
336 av_free(codec_context_
);
337 codec_context_
= NULL
;
345 } // namespace webkit_media