Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(533)

Side by Side Diff: media/filters/ffmpeg_video_decoder.cc

Issue 8772069: Collapse FFmpegVideoDecodeEngine into FFmpegVideoDecoder and remove VideoDecodeEngine. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « media/filters/ffmpeg_video_decoder.h ('k') | media/filters/ffmpeg_video_decoder_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "media/filters/ffmpeg_video_decoder.h" 5 #include "media/filters/ffmpeg_video_decoder.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/command_line.h"
8 #include "base/message_loop.h" 9 #include "base/message_loop.h"
10 #include "base/string_number_conversions.h"
9 #include "media/base/demuxer_stream.h" 11 #include "media/base/demuxer_stream.h"
10 #include "media/base/filter_host.h" 12 #include "media/base/filter_host.h"
11 #include "media/base/limits.h" 13 #include "media/base/limits.h"
14 #include "media/base/media_switches.h"
12 #include "media/base/video_decoder_config.h" 15 #include "media/base/video_decoder_config.h"
13 #include "media/base/video_frame.h" 16 #include "media/base/video_frame.h"
17 #include "media/base/video_util.h"
14 #include "media/ffmpeg/ffmpeg_common.h" 18 #include "media/ffmpeg/ffmpeg_common.h"
15 #include "media/video/ffmpeg_video_decode_engine.h"
16 19
17 namespace media { 20 namespace media {
18 21
19 FFmpegVideoDecoder::FFmpegVideoDecoder(MessageLoop* message_loop) 22 FFmpegVideoDecoder::FFmpegVideoDecoder(MessageLoop* message_loop)
20 : message_loop_(message_loop), 23 : message_loop_(message_loop),
21 state_(kUninitialized), 24 state_(kUninitialized),
22 decode_engine_(new FFmpegVideoDecodeEngine()) { 25 codec_context_(NULL),
26 av_frame_(NULL),
27 frame_rate_numerator_(0),
28 frame_rate_denominator_(0) {
23 } 29 }
24 30
25 FFmpegVideoDecoder::~FFmpegVideoDecoder() {} 31 FFmpegVideoDecoder::~FFmpegVideoDecoder() {
32 ReleaseFFmpegResources();
33 }
26 34
27 void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream, 35 void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream,
28 const base::Closure& callback, 36 const base::Closure& callback,
29 const StatisticsCallback& stats_callback) { 37 const StatisticsCallback& stats_callback) {
30 if (MessageLoop::current() != message_loop_) { 38 if (MessageLoop::current() != message_loop_) {
31 message_loop_->PostTask(FROM_HERE, base::Bind( 39 message_loop_->PostTask(FROM_HERE, base::Bind(
32 &FFmpegVideoDecoder::Initialize, this, 40 &FFmpegVideoDecoder::Initialize, this,
33 make_scoped_refptr(demuxer_stream), callback, stats_callback)); 41 make_scoped_refptr(demuxer_stream), callback, stats_callback));
34 return; 42 return;
35 } 43 }
(...skipping 28 matching lines...) Expand all
64 << " frame rate: " << config.frame_rate_numerator() 72 << " frame rate: " << config.frame_rate_numerator()
65 << "/" << config.frame_rate_denominator() 73 << "/" << config.frame_rate_denominator()
66 << " aspect ratio: " << config.aspect_ratio_numerator() 74 << " aspect ratio: " << config.aspect_ratio_numerator()
67 << "/" << config.aspect_ratio_denominator(); 75 << "/" << config.aspect_ratio_denominator();
68 76
69 host()->SetError(PIPELINE_ERROR_DECODE); 77 host()->SetError(PIPELINE_ERROR_DECODE);
70 callback.Run(); 78 callback.Run();
71 return; 79 return;
72 } 80 }
73 81
74 pts_stream_.Initialize(GetFrameDuration(config)); 82 // Always try to use three threads for video decoding. There is little reason
75 natural_size_ = config.natural_size(); 83 // not to since current day CPUs tend to be multi-core and we measured
84 // performance benefits on older machines such as P4s with hyperthreading.
85 //
86 // Handling decoding on separate threads also frees up the pipeline thread to
87 // continue processing. Although it'd be nice to have the option of a single
88 // decoding thread, FFmpeg treats having one thread the same as having zero
89 // threads (i.e., avcodec_decode_video() will execute on the calling thread).
90 // Yet another reason for having two threads :)
91 static const int kDecodeThreads = 2;
92 static const int kMaxDecodeThreads = 16;
acolwell GONE FROM CHROMIUM 2011/12/05 18:36:04 Don't we typically put constants at the top of the
scherkus (not reviewing) 2011/12/07 17:49:47 We do! FFVDE had them here but I'll move them up
76 93
77 if (!decode_engine_->Initialize(config)) { 94 // Initialize AVCodecContext structure.
95 codec_context_ = avcodec_alloc_context();
96 VideoDecoderConfigToAVCodecContext(config, codec_context_);
97
98 // Enable motion vector search (potentially slow), strong deblocking filter
99 // for damaged macroblocks, and set our error detection sensitivity.
100 codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
101 codec_context_->error_recognition = FF_ER_CAREFUL;
102
103 AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
104 if (!codec) {
78 host()->SetError(PIPELINE_ERROR_DECODE); 105 host()->SetError(PIPELINE_ERROR_DECODE);
79 callback.Run(); 106 callback.Run();
80 return; 107 return;
81 } 108 }
82 109
110 // TODO(fbarchard): Improve thread logic based on size / codec.
111 // TODO(fbarchard): Fix bug affecting video-cookie.html
112 // 07/21/11(ihf): Still about 20 failures when enabling.
113 int decode_threads = (codec_context_->codec_id == CODEC_ID_THEORA) ?
114 1 : kDecodeThreads;
115
116 const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
117 std::string threads(cmd_line->GetSwitchValueASCII(switches::kVideoThreads));
118 if ((!threads.empty() &&
acolwell GONE FROM CHROMIUM 2011/12/05 18:36:04 nit: Consider moving this logic to a GetDecoderThr
scherkus (not reviewing) 2011/12/07 17:49:47 Done.
119 !base::StringToInt(threads, &decode_threads)) ||
120 decode_threads < 0 || decode_threads > kMaxDecodeThreads) {
121 decode_threads = kDecodeThreads;
122 }
123
124 codec_context_->thread_count = decode_threads;
125 if (avcodec_open(codec_context_, codec) < 0) {
126 host()->SetError(PIPELINE_ERROR_DECODE);
127 callback.Run();
128 return;
129 }
130
131 // Success!
83 state_ = kNormal; 132 state_ = kNormal;
133 av_frame_ = avcodec_alloc_frame();
134 pts_stream_.Initialize(GetFrameDuration(config));
135 natural_size_ = config.natural_size();
136 frame_rate_numerator_ = config.frame_rate_numerator();
137 frame_rate_denominator_ = config.frame_rate_denominator();
84 callback.Run(); 138 callback.Run();
85 } 139 }
86 140
87 void FFmpegVideoDecoder::Stop(const base::Closure& callback) { 141 void FFmpegVideoDecoder::Stop(const base::Closure& callback) {
88 if (MessageLoop::current() != message_loop_) { 142 if (MessageLoop::current() != message_loop_) {
89 message_loop_->PostTask(FROM_HERE, base::Bind( 143 message_loop_->PostTask(FROM_HERE, base::Bind(
90 &FFmpegVideoDecoder::Stop, this, callback)); 144 &FFmpegVideoDecoder::Stop, this, callback));
91 return; 145 return;
92 } 146 }
93 147
94 decode_engine_->Uninitialize(); 148 ReleaseFFmpegResources();
95 state_ = kUninitialized; 149 state_ = kUninitialized;
96 callback.Run(); 150 callback.Run();
97 } 151 }
98 152
99 void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) { 153 void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
100 if (MessageLoop::current() != message_loop_) { 154 if (MessageLoop::current() != message_loop_) {
101 message_loop_->PostTask(FROM_HERE, base::Bind( 155 message_loop_->PostTask(FROM_HERE, base::Bind(
102 &FFmpegVideoDecoder::Seek, this, time, cb)); 156 &FFmpegVideoDecoder::Seek, this, time, cb));
103 return; 157 return;
104 } 158 }
(...skipping 12 matching lines...) Expand all
117 callback.Run(); 171 callback.Run();
118 } 172 }
119 173
120 void FFmpegVideoDecoder::Flush(const base::Closure& callback) { 174 void FFmpegVideoDecoder::Flush(const base::Closure& callback) {
121 if (MessageLoop::current() != message_loop_) { 175 if (MessageLoop::current() != message_loop_) {
122 message_loop_->PostTask(FROM_HERE, base::Bind( 176 message_loop_->PostTask(FROM_HERE, base::Bind(
123 &FFmpegVideoDecoder::Flush, this, callback)); 177 &FFmpegVideoDecoder::Flush, this, callback));
124 return; 178 return;
125 } 179 }
126 180
127 decode_engine_->Flush(); 181 avcodec_flush_buffers(codec_context_);
128 pts_stream_.Flush(); 182 pts_stream_.Flush();
129 state_ = kNormal; 183 state_ = kNormal;
130 callback.Run(); 184 callback.Run();
131 } 185 }
132 186
133 void FFmpegVideoDecoder::Read(const ReadCB& callback) { 187 void FFmpegVideoDecoder::Read(const ReadCB& callback) {
134 // TODO(scherkus): forced task post since VideoRendererBase::FrameReady() will 188 // TODO(scherkus): forced task post since VideoRendererBase::FrameReady() will
135 // call Read() on FFmpegVideoDecoder's thread as we executed |read_cb_|. 189 // call Read() on FFmpegVideoDecoder's thread as we executed |read_cb_|.
136 message_loop_->PostTask(FROM_HERE, base::Bind( 190 message_loop_->PostTask(FROM_HERE, base::Bind(
137 &FFmpegVideoDecoder::DoRead, this, callback)); 191 &FFmpegVideoDecoder::DoRead, this, callback));
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
214 } 268 }
215 269
216 // Push all incoming timestamps into the priority queue as long as we have 270 // Push all incoming timestamps into the priority queue as long as we have
217 // not yet received an end of stream buffer. It is important that this line 271 // not yet received an end of stream buffer. It is important that this line
218 // stay below the state transition into kFlushCodec done above. 272 // stay below the state transition into kFlushCodec done above.
219 if (state_ == kNormal) { 273 if (state_ == kNormal) {
220 pts_stream_.EnqueuePts(buffer.get()); 274 pts_stream_.EnqueuePts(buffer.get());
221 } 275 }
222 276
223 scoped_refptr<VideoFrame> video_frame; 277 scoped_refptr<VideoFrame> video_frame;
224 if (!decode_engine_->Decode(buffer, &video_frame)) { 278 if (!Decode(buffer, &video_frame)) {
225 state_ = kDecodeFinished; 279 state_ = kDecodeFinished;
226 DeliverFrame(VideoFrame::CreateEmptyFrame()); 280 DeliverFrame(VideoFrame::CreateEmptyFrame());
227 host()->SetError(PIPELINE_ERROR_DECODE); 281 host()->SetError(PIPELINE_ERROR_DECODE);
228 return; 282 return;
229 } 283 }
230 284
231 // Any successful decode counts! 285 // Any successful decode counts!
232 if (buffer->GetDataSize()) { 286 if (buffer->GetDataSize()) {
233 PipelineStatistics statistics; 287 PipelineStatistics statistics;
234 statistics.video_bytes_decoded = buffer->GetDataSize(); 288 statistics.video_bytes_decoded = buffer->GetDataSize();
(...skipping 14 matching lines...) Expand all
249 } 303 }
250 304
251 // If we got a frame make sure its timestamp is correct before sending it off. 305 // If we got a frame make sure its timestamp is correct before sending it off.
252 pts_stream_.UpdatePtsAndDuration(video_frame.get()); 306 pts_stream_.UpdatePtsAndDuration(video_frame.get());
253 video_frame->SetTimestamp(pts_stream_.current_pts()); 307 video_frame->SetTimestamp(pts_stream_.current_pts());
254 video_frame->SetDuration(pts_stream_.current_duration()); 308 video_frame->SetDuration(pts_stream_.current_duration());
255 309
256 DeliverFrame(video_frame); 310 DeliverFrame(video_frame);
257 } 311 }
258 312
313 bool FFmpegVideoDecoder::Decode(
314 const scoped_refptr<Buffer>& buffer,
315 scoped_refptr<VideoFrame>* video_frame) {
316 DCHECK(video_frame);
317
318 // Create a packet for input data.
319 // Due to FFmpeg API changes we no longer have const read-only pointers.
320 AVPacket packet;
321 av_init_packet(&packet);
322 packet.data = const_cast<uint8*>(buffer->GetData());
323 packet.size = buffer->GetDataSize();
324
325 // Let FFmpeg handle presentation timestamp reordering.
326 codec_context_->reordered_opaque = buffer->GetTimestamp().InMicroseconds();
327
328 // This is for codecs not using get_buffer to initialize
329 // |av_frame_->reordered_opaque|
330 av_frame_->reordered_opaque = codec_context_->reordered_opaque;
331
332 int frame_decoded = 0;
333 int result = avcodec_decode_video2(codec_context_,
334 av_frame_,
335 &frame_decoded,
336 &packet);
337 // Log the problem if we can't decode a video frame and exit early.
338 if (result < 0) {
339 LOG(ERROR) << "Error decoding a video frame with timestamp: "
340 << buffer->GetTimestamp().InMicroseconds() << " us, duration: "
341 << buffer->GetDuration().InMicroseconds() << " us, packet size: "
342 << buffer->GetDataSize() << " bytes";
343 *video_frame = NULL;
344 return false;
345 }
346
347 // If no frame was produced then signal that more data is required to
348 // produce more frames. This can happen under two circumstances:
349 // 1) Decoder was recently initialized/flushed
350 // 2) End of stream was reached and all internal frames have been output
351 if (frame_decoded == 0) {
352 *video_frame = NULL;
353 return true;
354 }
355
356 // TODO(fbarchard): Work around for FFmpeg http://crbug.com/27675
357 // The decoder is in a bad state and not decoding correctly.
358 // Checking for NULL avoids a crash in CopyPlane().
359 if (!av_frame_->data[VideoFrame::kYPlane] ||
360 !av_frame_->data[VideoFrame::kUPlane] ||
361 !av_frame_->data[VideoFrame::kVPlane]) {
362 LOG(ERROR) << "Video frame was produced yet has invalid frame data.";
363 *video_frame = NULL;
364 return false;
365 }
366
367 // We've got a frame! Make sure we have a place to store it.
368 *video_frame = AllocateVideoFrame();
369 if (!(*video_frame)) {
370 LOG(ERROR) << "Failed to allocate video frame";
371 return false;
372 }
373
374 // Determine timestamp and calculate the duration based on the repeat picture
375 // count. According to FFmpeg docs, the total duration can be calculated as
376 // follows:
377 // fps = 1 / time_base
378 //
379 // duration = (1 / fps) + (repeat_pict) / (2 * fps)
380 // = (2 + repeat_pict) / (2 * fps)
381 // = (2 + repeat_pict) / (2 * (1 / time_base))
382 DCHECK_LE(av_frame_->repeat_pict, 2); // Sanity check.
383 AVRational doubled_time_base;
384 doubled_time_base.num = frame_rate_denominator_;
385 doubled_time_base.den = frame_rate_numerator_ * 2;
386
387 (*video_frame)->SetTimestamp(
388 base::TimeDelta::FromMicroseconds(av_frame_->reordered_opaque));
389 (*video_frame)->SetDuration(
390 ConvertFromTimeBase(doubled_time_base, 2 + av_frame_->repeat_pict));
391
392 // Copy the frame data since FFmpeg reuses internal buffers for AVFrame
393 // output, meaning the data is only valid until the next
394 // avcodec_decode_video() call.
395 int y_rows = codec_context_->height;
396 int uv_rows = codec_context_->height;
397 if (codec_context_->pix_fmt == PIX_FMT_YUV420P) {
398 uv_rows /= 2;
399 }
400
401 CopyYPlane(av_frame_->data[0], av_frame_->linesize[0], y_rows, *video_frame);
402 CopyUPlane(av_frame_->data[1], av_frame_->linesize[1], uv_rows, *video_frame);
403 CopyVPlane(av_frame_->data[2], av_frame_->linesize[2], uv_rows, *video_frame);
404
405 return true;
406 }
407
259 void FFmpegVideoDecoder::DeliverFrame( 408 void FFmpegVideoDecoder::DeliverFrame(
260 const scoped_refptr<VideoFrame>& video_frame) { 409 const scoped_refptr<VideoFrame>& video_frame) {
261 // Reset the callback before running to protect against reentrancy. 410 // Reset the callback before running to protect against reentrancy.
262 ReadCB read_cb = read_cb_; 411 ReadCB read_cb = read_cb_;
263 read_cb_.Reset(); 412 read_cb_.Reset();
264 read_cb.Run(video_frame); 413 read_cb.Run(video_frame);
265 } 414 }
266 415
416 void FFmpegVideoDecoder::ReleaseFFmpegResources() {
417 if (codec_context_) {
418 av_free(codec_context_->extradata);
419 avcodec_close(codec_context_);
420 av_free(codec_context_);
421 codec_context_ = NULL;
422 }
423 if (av_frame_) {
424 av_free(av_frame_);
425 av_frame_ = NULL;
426 }
427 }
428
429 scoped_refptr<VideoFrame> FFmpegVideoDecoder::AllocateVideoFrame() {
430 VideoFrame::Format format = PixelFormatToVideoFormat(codec_context_->pix_fmt);
431 size_t width = codec_context_->width;
432 size_t height = codec_context_->height;
433
434 return VideoFrame::CreateFrame(format, width, height,
435 kNoTimestamp, kNoTimestamp);
436 }
437
267 } // namespace media 438 } // namespace media
OLDNEW
« no previous file with comments | « media/filters/ffmpeg_video_decoder.h ('k') | media/filters/ffmpeg_video_decoder_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698