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.
5 #include "media/cdm/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 "media/ffmpeg/ffmpeg_common.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>
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 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
:
56 NOTREACHED() << "Unsupported cdm::VideoCodec: " << video_codec
;
57 return AV_CODEC_ID_NONE
;
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 : is_initialized_(false),
136 FFmpegCdmVideoDecoder::~FFmpegCdmVideoDecoder() {
137 ReleaseFFmpegResources();
140 bool FFmpegCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig
& config
) {
141 DVLOG(1) << "Initialize()";
143 if (!IsValidOutputConfig(config
.format
, config
.coded_size
)) {
144 LOG(ERROR
) << "Initialize(): invalid video decoder configuration.";
148 if (is_initialized_
) {
149 LOG(ERROR
) << "Initialize(): Already initialized.";
153 // Initialize AVCodecContext structure.
154 codec_context_
.reset(avcodec_alloc_context3(NULL
));
155 CdmVideoDecoderConfigToAVCodecContext(config
, codec_context_
.get());
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
;
165 AVCodec
* codec
= avcodec_find_decoder(codec_context_
->codec_id
);
167 LOG(ERROR
) << "Initialize(): avcodec_find_decoder failed.";
172 if ((status
= avcodec_open2(codec_context_
.get(), codec
, NULL
)) < 0) {
173 LOG(ERROR
) << "Initialize(): avcodec_open2 failed: " << status
;
177 av_frame_
.reset(avcodec_alloc_frame());
178 is_initialized_
= true;
183 void FFmpegCdmVideoDecoder::Deinitialize() {
184 DVLOG(1) << "Deinitialize()";
185 ReleaseFFmpegResources();
186 is_initialized_
= false;
189 void FFmpegCdmVideoDecoder::Reset() {
190 DVLOG(1) << "Reset()";
191 avcodec_flush_buffers(codec_context_
.get());
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
);
205 cdm::Status
FFmpegCdmVideoDecoder::DecodeFrame(
206 const uint8_t* compressed_frame
,
207 int32_t compressed_frame_size
,
209 cdm::VideoFrame
* decoded_frame
) {
210 DVLOG(1) << "DecodeFrame()";
211 DCHECK(decoded_frame
);
213 // Create a packet for input data.
215 av_init_packet(&packet
);
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
;
221 // Let FFmpeg handle presentation timestamp reordering.
222 codec_context_
->reordered_opaque
= timestamp
;
224 // Reset frame to default values.
225 avcodec_get_frame_defaults(av_frame_
.get());
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
;
231 int frame_decoded
= 0;
232 int result
= avcodec_decode_video2(codec_context_
.get(),
236 // Log the problem when we can't decode a video frame and exit early.
238 LOG(ERROR
) << "DecodeFrame(): Error decoding video frame with timestamp: "
239 << timestamp
<< " us, packet size: " << packet
.size
<< " bytes";
240 return cdm::kDecodeError
;
243 // If no frame was produced then signal that more data is required to produce
245 if (frame_decoded
== 0)
246 return cdm::kNeedMoreData
;
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
;
257 if (!CopyAvFrameTo(decoded_frame
)) {
258 LOG(ERROR
) << "DecodeFrame() could not copy video frame to output buffer.";
259 return cdm::kDecodeError
;
262 return cdm::kSuccess
;
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);
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);
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() cdm::Host::Allocate failed.";
281 cdm_video_frame
->FrameBuffer()->SetSize(space_required
);
283 CopyPlane(av_frame_
->data
[cdm::VideoFrame::kYPlane
],
284 av_frame_
->linesize
[cdm::VideoFrame::kYPlane
],
288 cdm_video_frame
->FrameBuffer()->Data());
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
],
297 cdm_video_frame
->FrameBuffer()->Data() + y_size
);
299 CopyPlane(av_frame_
->data
[cdm::VideoFrame::kVPlane
],
300 av_frame_
->linesize
[cdm::VideoFrame::kVPlane
],
304 cdm_video_frame
->FrameBuffer()->Data() + y_size
+ uv_size
);
306 PixelFormat format
= static_cast<PixelFormat
>(av_frame_
->format
);
307 cdm_video_frame
->SetFormat(PixelFormatToCdmVideoFormat(format
));
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
);
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
,
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
);
323 cdm_video_frame
->SetTimestamp(av_frame_
->reordered_opaque
);
328 void FFmpegCdmVideoDecoder::ReleaseFFmpegResources() {
329 DVLOG(1) << "ReleaseFFmpegResources()";
331 codec_context_
.reset();