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

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

Issue 8417019: Simplify VideoDecodeEngine interface by making everything synchronous. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix for CaptureVideoDecoder Created 9 years, 1 month 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/callback.h"
9 #include "base/message_loop.h" 8 #include "base/message_loop.h"
10 #include "base/task.h"
11 #include "media/base/demuxer_stream.h" 9 #include "media/base/demuxer_stream.h"
12 #include "media/base/filter_host.h" 10 #include "media/base/filter_host.h"
13 #include "media/base/limits.h" 11 #include "media/base/limits.h"
12 #include "media/base/video_decoder_config.h"
14 #include "media/base/video_frame.h" 13 #include "media/base/video_frame.h"
15 #include "media/ffmpeg/ffmpeg_common.h" 14 #include "media/ffmpeg/ffmpeg_common.h"
16 #include "media/video/ffmpeg_video_decode_engine.h" 15 #include "media/video/ffmpeg_video_decode_engine.h"
17 16
18 namespace media { 17 namespace media {
19 18
20 FFmpegVideoDecoder::FFmpegVideoDecoder(MessageLoop* message_loop) 19 FFmpegVideoDecoder::FFmpegVideoDecoder(MessageLoop* message_loop)
21 : message_loop_(message_loop), 20 : message_loop_(message_loop),
22 state_(kUnInitialized), 21 state_(kUninitialized),
23 decode_engine_(new FFmpegVideoDecodeEngine()) { 22 decode_engine_(new FFmpegVideoDecodeEngine()) {
24 } 23 }
25 24
26 FFmpegVideoDecoder::~FFmpegVideoDecoder() {} 25 FFmpegVideoDecoder::~FFmpegVideoDecoder() {}
27 26
28 void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream, 27 void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream,
29 const base::Closure& callback, 28 const base::Closure& callback,
30 const StatisticsCallback& stats_callback) { 29 const StatisticsCallback& stats_callback) {
31 if (MessageLoop::current() != message_loop_) { 30 if (MessageLoop::current() != message_loop_) {
32 message_loop_->PostTask( 31 message_loop_->PostTask(FROM_HERE, base::Bind(
33 FROM_HERE, 32 &FFmpegVideoDecoder::Initialize, this,
34 base::Bind(&FFmpegVideoDecoder::Initialize, this, 33 make_scoped_refptr(demuxer_stream), callback, stats_callback));
35 make_scoped_refptr(demuxer_stream),
36 callback, stats_callback));
37 return; 34 return;
38 } 35 }
39 36
40 DCHECK_EQ(MessageLoop::current(), message_loop_);
41 DCHECK(!demuxer_stream_); 37 DCHECK(!demuxer_stream_);
42 DCHECK(initialize_callback_.is_null());
43 38
44 if (!demuxer_stream) { 39 if (!demuxer_stream) {
45 host()->SetError(PIPELINE_ERROR_DECODE); 40 host()->SetError(PIPELINE_ERROR_DECODE);
46 callback.Run(); 41 callback.Run();
47 return; 42 return;
48 } 43 }
49 44
50 demuxer_stream_ = demuxer_stream; 45 demuxer_stream_ = demuxer_stream;
51 initialize_callback_ = callback;
52 statistics_callback_ = stats_callback; 46 statistics_callback_ = stats_callback;
53 47
54 const VideoDecoderConfig& config = demuxer_stream->video_decoder_config(); 48 const VideoDecoderConfig& config = demuxer_stream->video_decoder_config();
55 49
56 pts_stream_.Initialize(GetFrameDuration(config)); 50 // TODO(scherkus): this check should go in PipelineImpl prior to creating
51 // decoder objects.
52 if (!config.IsValidConfig()) {
53 DLOG(ERROR) << "Invalid video stream -"
54 << " codec: " << config.codec()
55 << " format: " << config.format()
56 << " coded size: [" << config.coded_size().width()
57 << "," << config.coded_size().height() << "]"
58 << " visible rect: [" << config.visible_rect().x()
59 << "," << config.visible_rect().y()
60 << "," << config.visible_rect().width()
61 << "," << config.visible_rect().height() << "]"
62 << " natural size: [" << config.natural_size().width()
63 << "," << config.natural_size().height() << "]"
64 << " frame rate: " << config.frame_rate_numerator()
65 << "/" << config.frame_rate_denominator()
66 << " aspect ratio: " << config.aspect_ratio_numerator()
67 << "/" << config.aspect_ratio_denominator();
57 68
58 natural_size_ = config.natural_size(); 69 host()->SetError(PIPELINE_ERROR_DECODE);
59 if (natural_size_.width() > Limits::kMaxDimension || 70 callback.Run();
60 natural_size_.height() > Limits::kMaxDimension ||
61 natural_size_.GetArea() > Limits::kMaxCanvas) {
62 OnInitializeComplete(false);
63 return; 71 return;
64 } 72 }
65 73
66 state_ = kInitializing; 74 pts_stream_.Initialize(GetFrameDuration(config));
67 decode_engine_->Initialize(this, config); 75 natural_size_ = config.natural_size();
68 }
69 76
70 void FFmpegVideoDecoder::OnInitializeComplete(bool success) { 77 if (!decode_engine_->Initialize(config)) {
71 DCHECK_EQ(MessageLoop::current(), message_loop_); 78 host()->SetError(PIPELINE_ERROR_DECODE);
72 DCHECK(!initialize_callback_.is_null()); 79 callback.Run();
80 return;
81 }
73 82
74 if (success) { 83 state_ = kNormal;
75 state_ = kNormal; 84 callback.Run();
76 } else {
77 host()->SetError(PIPELINE_ERROR_DECODE);
78 }
79 ResetAndRunCB(&initialize_callback_);
80 } 85 }
81 86
82 void FFmpegVideoDecoder::Stop(const base::Closure& callback) { 87 void FFmpegVideoDecoder::Stop(const base::Closure& callback) {
83 if (MessageLoop::current() != message_loop_) { 88 if (MessageLoop::current() != message_loop_) {
84 message_loop_->PostTask(FROM_HERE, base::Bind( 89 message_loop_->PostTask(FROM_HERE, base::Bind(
85 &FFmpegVideoDecoder::Stop, this, callback)); 90 &FFmpegVideoDecoder::Stop, this, callback));
86 return; 91 return;
87 } 92 }
88 93
89 DCHECK_EQ(MessageLoop::current(), message_loop_); 94 decode_engine_->Uninitialize();
90 DCHECK(uninitialize_callback_.is_null()); 95 state_ = kUninitialized;
91 96 callback.Run();
92 uninitialize_callback_ = callback;
93 if (state_ != kUnInitialized)
94 decode_engine_->Uninitialize();
95 else
96 OnUninitializeComplete();
97 } 97 }
98 98
99 void FFmpegVideoDecoder::OnUninitializeComplete() { 99 void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
100 DCHECK_EQ(MessageLoop::current(), message_loop_); 100 if (MessageLoop::current() != message_loop_) {
101 DCHECK(!uninitialize_callback_.is_null()); 101 message_loop_->PostTask(FROM_HERE, base::Bind(
102 &FFmpegVideoDecoder::Seek, this, time, cb));
103 return;
104 }
102 105
103 state_ = kStopped; 106 pts_stream_.Seek(time);
104 107 cb.Run(PIPELINE_OK);
105 ResetAndRunCB(&uninitialize_callback_);
106 } 108 }
107 109
108 void FFmpegVideoDecoder::Pause(const base::Closure& callback) { 110 void FFmpegVideoDecoder::Pause(const base::Closure& callback) {
109 if (MessageLoop::current() != message_loop_) { 111 if (MessageLoop::current() != message_loop_) {
110 message_loop_->PostTask(FROM_HERE, base::Bind( 112 message_loop_->PostTask(FROM_HERE, base::Bind(
111 &FFmpegVideoDecoder::Pause, this, callback)); 113 &FFmpegVideoDecoder::Pause, this, callback));
112 return; 114 return;
113 } 115 }
114 116
115 state_ = kPausing;
116 callback.Run(); 117 callback.Run();
117 } 118 }
118 119
119 void FFmpegVideoDecoder::Flush(const base::Closure& callback) { 120 void FFmpegVideoDecoder::Flush(const base::Closure& callback) {
120 if (MessageLoop::current() != message_loop_) { 121 if (MessageLoop::current() != message_loop_) {
121 message_loop_->PostTask(FROM_HERE, base::Bind( 122 message_loop_->PostTask(FROM_HERE, base::Bind(
122 &FFmpegVideoDecoder::Flush, this, callback)); 123 &FFmpegVideoDecoder::Flush, this, callback));
123 return; 124 return;
124 } 125 }
125 126
126 DCHECK_EQ(MessageLoop::current(), message_loop_);
127 DCHECK(flush_callback_.is_null());
128
129 state_ = kFlushing;
130
131 FlushBuffers();
132
133 flush_callback_ = callback;
134
135 decode_engine_->Flush(); 127 decode_engine_->Flush();
128 pts_stream_.Flush();
129 state_ = kNormal;
130 callback.Run();
136 } 131 }
137 132
138 void FFmpegVideoDecoder::OnFlushComplete() { 133 void FFmpegVideoDecoder::Read(const ReadCB& callback) {
139 DCHECK_EQ(MessageLoop::current(), message_loop_); 134 // TODO(scherkus): forced task post since VideoRendererBase::FrameReady() will
140 DCHECK(!flush_callback_.is_null()); 135 // call Read() on FFmpegVideoDecoder's thread as we executed |read_cb_|.
141 136 message_loop_->PostTask(FROM_HERE, base::Bind(
142 // Everything in the presentation time queue is invalid, clear the queue. 137 &FFmpegVideoDecoder::DoRead, this, callback));
143 pts_stream_.Flush();
144
145 // Mark flush operation had been done.
146 state_ = kNormal;
147
148 ResetAndRunCB(&flush_callback_);
149 } 138 }
150 139
151 void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) { 140 const gfx::Size& FFmpegVideoDecoder::natural_size() {
152 if (MessageLoop::current() != message_loop_) { 141 return natural_size_;
153 message_loop_->PostTask(FROM_HERE, 142 }
154 base::Bind(&FFmpegVideoDecoder::Seek, this, 143
155 time, cb)); 144 void FFmpegVideoDecoder::DoRead(const ReadCB& callback) {
156 return; 145 DCHECK_EQ(MessageLoop::current(), message_loop_);
146 CHECK(!callback.is_null());
147 CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
148
149 // This can happen during shutdown after Stop() has been called.
150 if (state_ == kUninitialized) {
151 return;
157 } 152 }
158 153
159 DCHECK_EQ(MessageLoop::current(), message_loop_); 154 // Return empty frames if decoding has finished.
160 DCHECK(seek_cb_.is_null()); 155 if (state_ == kDecodeFinished) {
156 callback.Run(VideoFrame::CreateEmptyFrame());
157 return;
158 }
161 159
162 pts_stream_.Seek(time); 160 read_cb_ = callback;
163 seek_cb_ = cb; 161 ReadFromDemuxerStream();
164 decode_engine_->Seek();
165 } 162 }
166 163
167 void FFmpegVideoDecoder::OnSeekComplete() {
168 DCHECK_EQ(MessageLoop::current(), message_loop_);
169 DCHECK(!seek_cb_.is_null());
170 164
171 ResetAndRunCB(&seek_cb_, PIPELINE_OK); 165 void FFmpegVideoDecoder::ReadFromDemuxerStream() {
166 DCHECK_NE(state_, kUninitialized);
167 DCHECK_NE(state_, kDecodeFinished);
168 DCHECK(!read_cb_.is_null());
169
170 demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::DecodeBuffer, this));
172 } 171 }
173 172
174 void FFmpegVideoDecoder::OnError() { 173 void FFmpegVideoDecoder::DecodeBuffer(const scoped_refptr<Buffer>& buffer) {
175 VideoFrameReady(NULL); 174 // TODO(scherkus): forced task post since FFmpegDemuxerStream::Read() can
175 // immediately execute our callback on FFmpegVideoDecoder's thread.
176 message_loop_->PostTask(FROM_HERE, base::Bind(
177 &FFmpegVideoDecoder::DoDecodeBuffer, this, buffer));
176 } 178 }
177 179
178 void FFmpegVideoDecoder::OnReadComplete(const scoped_refptr<Buffer>& buffer) { 180 void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& buffer) {
179 message_loop_->PostTask(FROM_HERE, base::Bind(
180 &FFmpegVideoDecoder::OnReadCompleteTask, this, buffer));
181 }
182
183 void FFmpegVideoDecoder::OnReadCompleteTask(
184 const scoped_refptr<Buffer>& buffer) {
185 DCHECK_EQ(MessageLoop::current(), message_loop_); 181 DCHECK_EQ(MessageLoop::current(), message_loop_);
186 DCHECK_NE(state_, kStopped); // because of Flush() before Stop(). 182 DCHECK_NE(state_, kUninitialized);
183 DCHECK_NE(state_, kDecodeFinished);
184 DCHECK(!read_cb_.is_null());
187 185
188 // During decode, because reads are issued asynchronously, it is possible to 186 // During decode, because reads are issued asynchronously, it is possible to
189 // receive multiple end of stream buffers since each read is acked. When the 187 // receive multiple end of stream buffers since each read is acked. When the
190 // first end of stream buffer is read, FFmpeg may still have frames queued 188 // first end of stream buffer is read, FFmpeg may still have frames queued
191 // up in the decoder so we need to go through the decode loop until it stops 189 // up in the decoder so we need to go through the decode loop until it stops
192 // giving sensible data. After that, the decoder should output empty 190 // giving sensible data. After that, the decoder should output empty
193 // frames. There are three states the decoder can be in: 191 // frames. There are three states the decoder can be in:
194 // 192 //
195 // kNormal: This is the starting state. Buffers are decoded. Decode errors 193 // kNormal: This is the starting state. Buffers are decoded. Decode errors
196 // are discarded. 194 // are discarded.
197 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2 195 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2
198 // until no more data is returned to flush out remaining 196 // until no more data is returned to flush out remaining
199 // frames. The input buffer is ignored at this point. 197 // frames. The input buffer is ignored at this point.
200 // kDecodeFinished: All calls return empty frames. 198 // kDecodeFinished: All calls return empty frames.
201 // 199 //
202 // These are the possible state transitions. 200 // These are the possible state transitions.
203 // 201 //
204 // kNormal -> kFlushCodec: 202 // kNormal -> kFlushCodec:
205 // When buffer->IsEndOfStream() is first true. 203 // When buffer->IsEndOfStream() is first true.
206 // kNormal -> kDecodeFinished: 204 // kNormal -> kDecodeFinished:
207 // A catastrophic failure occurs, and decoding needs to stop. 205 // A decoding error occurs and decoding needs to stop.
208 // kFlushCodec -> kDecodeFinished: 206 // kFlushCodec -> kDecodeFinished:
209 // When avcodec_decode_video2() returns 0 data or errors out. 207 // When avcodec_decode_video2() returns 0 data or errors out.
210 // (any state) -> kNormal: 208 // (any state) -> kNormal:
211 // Any time buffer->IsDiscontinuous() is true. 209 // Any time Flush() is called.
212 210
213 // Transition to kFlushCodec on the first end of stream buffer. 211 // Transition to kFlushCodec on the first end of stream buffer.
214 if (state_ == kNormal && buffer->IsEndOfStream()) { 212 if (state_ == kNormal && buffer->IsEndOfStream()) {
215 state_ = kFlushCodec; 213 state_ = kFlushCodec;
216 } 214 }
217 215
218 // Push all incoming timestamps into the priority queue as long as we have 216 // Push all incoming timestamps into the priority queue as long as we have
219 // not yet received an end of stream buffer. It is important that this line 217 // not yet received an end of stream buffer. It is important that this line
220 // stay below the state transition into kFlushCodec done above. 218 // stay below the state transition into kFlushCodec done above.
221 if (state_ == kNormal) { 219 if (state_ == kNormal) {
222 pts_stream_.EnqueuePts(buffer.get()); 220 pts_stream_.EnqueuePts(buffer.get());
223 } 221 }
224 222
225 // Otherwise, attempt to decode a single frame. 223 scoped_refptr<VideoFrame> video_frame;
226 decode_engine_->ConsumeVideoSample(buffer); 224 if (!decode_engine_->Decode(buffer, &video_frame)) {
227 } 225 state_ = kDecodeFinished;
228 226 DeliverFrame(VideoFrame::CreateEmptyFrame());
229 void FFmpegVideoDecoder::ProduceVideoFrame( 227 host()->SetError(PIPELINE_ERROR_DECODE);
230 scoped_refptr<VideoFrame> video_frame) {
231 if (MessageLoop::current() != message_loop_) {
232 if (state_ != kStopped) {
233 message_loop_->PostTask(FROM_HERE, base::Bind(
234 &FFmpegVideoDecoder::ProduceVideoFrame, this, video_frame));
235 }
236 return; 228 return;
237 } 229 }
238 230
239 DCHECK_EQ(MessageLoop::current(), message_loop_); 231 // Any successful decode counts!
240 232 if (buffer->GetDataSize()) {
241 // Synchronized flushing before stop should prevent this. 233 PipelineStatistics statistics;
242 DCHECK_NE(state_, kStopped); 234 statistics.video_bytes_decoded = buffer->GetDataSize();
243 235 statistics_callback_.Run(statistics);
244 // If the decoding is finished, we just always return empty frames.
245 if (state_ == kDecodeFinished) {
246 // Signal VideoRenderer the end of the stream event.
247 VideoFrameReady(VideoFrame::CreateEmptyFrame());
248
249 // Fall through, because we still need to keep record of this frame.
250 } 236 }
251 237
252 // Notify decode engine the available of new frame. 238 // If we didn't get a frame then we've either completely finished decoding or
253 decode_engine_->ProduceVideoFrame(video_frame); 239 // we need more data.
254 } 240 if (!video_frame) {
255 241 if (state_ == kFlushCodec) {
256 void FFmpegVideoDecoder::ConsumeVideoFrame( 242 state_ = kDecodeFinished;
257 scoped_refptr<VideoFrame> video_frame, 243 DeliverFrame(VideoFrame::CreateEmptyFrame());
258 const PipelineStatistics& statistics) {
259 DCHECK_EQ(MessageLoop::current(), message_loop_);
260 DCHECK_NE(state_, kStopped);
261
262 statistics_callback_.Run(statistics);
263
264 if (video_frame.get()) {
265 if (kPausing == state_ || kFlushing == state_) {
266 frame_queue_flushed_.push_back(video_frame);
267 if (kFlushing == state_)
268 FlushBuffers();
269 return; 244 return;
270 } 245 }
271 246
272 // If we actually got data back, enqueue a frame. 247 ReadFromDemuxerStream();
273 pts_stream_.UpdatePtsAndDuration(video_frame.get()); 248 return;
249 }
274 250
275 video_frame->SetTimestamp(pts_stream_.current_pts()); 251 // If we got a frame make sure its timestamp is correct before sending it off.
276 video_frame->SetDuration(pts_stream_.current_duration()); 252 pts_stream_.UpdatePtsAndDuration(video_frame.get());
253 video_frame->SetTimestamp(pts_stream_.current_pts());
254 video_frame->SetDuration(pts_stream_.current_duration());
277 255
278 VideoFrameReady(video_frame); 256 DeliverFrame(video_frame);
279 } else {
280 // When in kFlushCodec, any errored decode, or a 0-lengthed frame,
281 // is taken as a signal to stop decoding.
282 if (state_ == kFlushCodec) {
283 state_ = kDecodeFinished;
284
285 // Signal VideoRenderer the end of the stream event.
286 VideoFrameReady(VideoFrame::CreateEmptyFrame());
287 }
288 }
289 } 257 }
290 258
291 void FFmpegVideoDecoder::ProduceVideoSample( 259 void FFmpegVideoDecoder::DeliverFrame(
292 scoped_refptr<Buffer> buffer) { 260 const scoped_refptr<VideoFrame>& video_frame) {
293 DCHECK_EQ(MessageLoop::current(), message_loop_); 261 // Reset the callback before running to protect against reentrancy.
294 DCHECK_NE(state_, kStopped); 262 ReadCB read_cb = read_cb_;
295 263 read_cb_.Reset();
296 demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::OnReadComplete, this)); 264 read_cb.Run(video_frame);
297 }
298
299 gfx::Size FFmpegVideoDecoder::natural_size() {
300 return natural_size_;
301 }
302
303 void FFmpegVideoDecoder::FlushBuffers() {
304 while (!frame_queue_flushed_.empty()) {
305 scoped_refptr<VideoFrame> video_frame;
306 video_frame = frame_queue_flushed_.front();
307 frame_queue_flushed_.pop_front();
308
309 // Return frames back to the decode engine.
310 decode_engine_->ProduceVideoFrame(video_frame);
311 }
312 }
313
314 void FFmpegVideoDecoder::SetVideoDecodeEngineForTest(
315 VideoDecodeEngine* engine) {
316 decode_engine_.reset(engine);
317 } 265 }
318 266
319 } // namespace media 267 } // 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