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

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: cleanup++ 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
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_); 37 DCHECK_EQ(MessageLoop::current(), message_loop_);
41 DCHECK(!demuxer_stream_); 38 DCHECK(!demuxer_stream_);
42 DCHECK(initialize_callback_.is_null());
43 39
44 if (!demuxer_stream) { 40 if (!demuxer_stream) {
45 host()->SetError(PIPELINE_ERROR_DECODE); 41 host()->SetError(PIPELINE_ERROR_DECODE);
46 callback.Run(); 42 callback.Run();
47 return; 43 return;
48 } 44 }
49 45
50 demuxer_stream_ = demuxer_stream; 46 demuxer_stream_ = demuxer_stream;
51 initialize_callback_ = callback;
52 statistics_callback_ = stats_callback; 47 statistics_callback_ = stats_callback;
53 48
54 const VideoDecoderConfig& config = demuxer_stream->video_decoder_config(); 49 const VideoDecoderConfig& config = demuxer_stream->video_decoder_config();
55 50
56 pts_stream_.Initialize(GetFrameDuration(config)); 51 pts_stream_.Initialize(GetFrameDuration(config));
57 52
58 natural_size_ = config.natural_size(); 53 natural_size_ = config.natural_size();
59 if (natural_size_.width() > Limits::kMaxDimension || 54 if (natural_size_.width() > Limits::kMaxDimension ||
60 natural_size_.height() > Limits::kMaxDimension || 55 natural_size_.height() > Limits::kMaxDimension ||
61 natural_size_.GetArea() > Limits::kMaxCanvas) { 56 natural_size_.GetArea() > Limits::kMaxCanvas) {
62 OnInitializeComplete(false); 57 host()->SetError(PIPELINE_ERROR_DECODE);
58 callback.Run();
acolwell GONE FROM CHROMIUM 2011/10/28 17:38:47 I'd like to see callback converted to a PipelineSt
scherkus (not reviewing) 2011/11/01 04:18:26 TODO added in filters.h there are a few related f
63 return; 59 return;
64 } 60 }
65 61
66 state_ = kInitializing; 62 if (!decode_engine_->Initialize(config)) {
67 decode_engine_->Initialize(this, config); 63 host()->SetError(PIPELINE_ERROR_DECODE);
68 } 64 callback.Run();
65 return;
66 }
69 67
70 void FFmpegVideoDecoder::OnInitializeComplete(bool success) { 68 state_ = kNormal;
71 DCHECK_EQ(MessageLoop::current(), message_loop_); 69 callback.Run();
72 DCHECK(!initialize_callback_.is_null());
73
74 if (success) {
75 state_ = kNormal;
76 } else {
77 host()->SetError(PIPELINE_ERROR_DECODE);
78 }
79 ResetAndRunCB(&initialize_callback_);
80 } 70 }
81 71
82 void FFmpegVideoDecoder::Stop(const base::Closure& callback) { 72 void FFmpegVideoDecoder::Stop(const base::Closure& callback) {
83 if (MessageLoop::current() != message_loop_) { 73 if (MessageLoop::current() != message_loop_) {
84 message_loop_->PostTask(FROM_HERE, base::Bind( 74 message_loop_->PostTask(FROM_HERE, base::Bind(
85 &FFmpegVideoDecoder::Stop, this, callback)); 75 &FFmpegVideoDecoder::Stop, this, callback));
86 return; 76 return;
87 } 77 }
88 78
89 DCHECK_EQ(MessageLoop::current(), message_loop_); 79 DCHECK_EQ(MessageLoop::current(), message_loop_);
90 DCHECK(uninitialize_callback_.is_null());
91 80
92 uninitialize_callback_ = callback; 81 decode_engine_->Uninitialize();
93 if (state_ != kUnInitialized) 82 state_ = kUninitialized;
94 decode_engine_->Uninitialize(); 83 callback.Run();
95 else
96 OnUninitializeComplete();
97 } 84 }
98 85
99 void FFmpegVideoDecoder::OnUninitializeComplete() { 86 void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
87 if (MessageLoop::current() != message_loop_) {
88 message_loop_->PostTask(FROM_HERE, base::Bind(
89 &FFmpegVideoDecoder::Seek, this, time, cb));
90 return;
91 }
92
100 DCHECK_EQ(MessageLoop::current(), message_loop_); 93 DCHECK_EQ(MessageLoop::current(), message_loop_);
101 DCHECK(!uninitialize_callback_.is_null());
102 94
103 state_ = kStopped; 95 pts_stream_.Seek(time);
104 96 cb.Run(PIPELINE_OK);
105 ResetAndRunCB(&uninitialize_callback_);
106 } 97 }
107 98
108 void FFmpegVideoDecoder::Pause(const base::Closure& callback) { 99 void FFmpegVideoDecoder::Pause(const base::Closure& callback) {
109 if (MessageLoop::current() != message_loop_) { 100 if (MessageLoop::current() != message_loop_) {
110 message_loop_->PostTask(FROM_HERE, base::Bind( 101 message_loop_->PostTask(FROM_HERE, base::Bind(
111 &FFmpegVideoDecoder::Pause, this, callback)); 102 &FFmpegVideoDecoder::Pause, this, callback));
112 return; 103 return;
113 } 104 }
114 105
115 state_ = kPausing;
116 callback.Run(); 106 callback.Run();
117 } 107 }
118 108
119 void FFmpegVideoDecoder::Flush(const base::Closure& callback) { 109 void FFmpegVideoDecoder::Flush(const base::Closure& callback) {
120 if (MessageLoop::current() != message_loop_) { 110 if (MessageLoop::current() != message_loop_) {
121 message_loop_->PostTask(FROM_HERE, base::Bind( 111 message_loop_->PostTask(FROM_HERE, base::Bind(
122 &FFmpegVideoDecoder::Flush, this, callback)); 112 &FFmpegVideoDecoder::Flush, this, callback));
123 return; 113 return;
124 } 114 }
125 115
126 DCHECK_EQ(MessageLoop::current(), message_loop_); 116 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 117
135 decode_engine_->Flush(); 118 decode_engine_->Flush();
119 pts_stream_.Flush();
120 state_ = kNormal;
121 callback.Run();
136 } 122 }
137 123
138 void FFmpegVideoDecoder::OnFlushComplete() { 124 void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& buffer) {
139 DCHECK_EQ(MessageLoop::current(), message_loop_); 125 DCHECK_EQ(MessageLoop::current(), message_loop_);
140 DCHECK(!flush_callback_.is_null()); 126 DCHECK_NE(state_, kUninitialized);
141 127 DCHECK_NE(state_, kDecodeFinished);
142 // Everything in the presentation time queue is invalid, clear the queue.
143 pts_stream_.Flush();
144
145 // Mark flush operation had been done.
146 state_ = kNormal;
147
148 ResetAndRunCB(&flush_callback_);
149 }
150
151 void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
152 if (MessageLoop::current() != message_loop_) {
153 message_loop_->PostTask(FROM_HERE,
154 base::Bind(&FFmpegVideoDecoder::Seek, this,
155 time, cb));
156 return;
157 }
158
159 DCHECK_EQ(MessageLoop::current(), message_loop_);
160 DCHECK(seek_cb_.is_null());
161
162 pts_stream_.Seek(time);
163 seek_cb_ = cb;
164 decode_engine_->Seek();
165 }
166
167 void FFmpegVideoDecoder::OnSeekComplete() {
168 DCHECK_EQ(MessageLoop::current(), message_loop_);
169 DCHECK(!seek_cb_.is_null());
170
171 ResetAndRunCB(&seek_cb_, PIPELINE_OK);
172 }
173
174 void FFmpegVideoDecoder::OnError() {
175 VideoFrameReady(NULL);
176 }
177
178 void FFmpegVideoDecoder::OnReadComplete(Buffer* buffer_in) {
179 scoped_refptr<Buffer> buffer(buffer_in);
180 message_loop_->PostTask(FROM_HERE, base::Bind(
181 &FFmpegVideoDecoder::OnReadCompleteTask, this, buffer));
182 }
183
184 void FFmpegVideoDecoder::OnReadCompleteTask(scoped_refptr<Buffer> buffer) {
185 DCHECK_EQ(MessageLoop::current(), message_loop_);
186 DCHECK_NE(state_, kStopped); // because of Flush() before Stop().
187 128
188 // During decode, because reads are issued asynchronously, it is possible to 129 // 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 130 // 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 131 // 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 132 // 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 133 // giving sensible data. After that, the decoder should output empty
193 // frames. There are three states the decoder can be in: 134 // frames. There are three states the decoder can be in:
194 // 135 //
195 // kNormal: This is the starting state. Buffers are decoded. Decode errors 136 // kNormal: This is the starting state. Buffers are decoded. Decode errors
196 // are discarded. 137 // are discarded.
197 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2 138 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2
198 // until no more data is returned to flush out remaining 139 // until no more data is returned to flush out remaining
199 // frames. The input buffer is ignored at this point. 140 // frames. The input buffer is ignored at this point.
200 // kDecodeFinished: All calls return empty frames. 141 // kDecodeFinished: All calls return empty frames.
201 // 142 //
202 // These are the possible state transitions. 143 // These are the possible state transitions.
203 // 144 //
204 // kNormal -> kFlushCodec: 145 // kNormal -> kFlushCodec:
205 // When buffer->IsEndOfStream() is first true. 146 // When buffer->IsEndOfStream() is first true.
206 // kNormal -> kDecodeFinished: 147 // kNormal -> kDecodeFinished:
207 // A catastrophic failure occurs, and decoding needs to stop. 148 // A catastrophic failure occurs, and decoding needs to stop.
208 // kFlushCodec -> kDecodeFinished: 149 // kFlushCodec -> kDecodeFinished:
209 // When avcodec_decode_video2() returns 0 data or errors out. 150 // When avcodec_decode_video2() returns 0 data or errors out.
210 // (any state) -> kNormal: 151 // (any state) -> kNormal:
211 // Any time buffer->IsDiscontinuous() is true. 152 // Any time Flush() is called.
212 153
213 // Transition to kFlushCodec on the first end of stream buffer. 154 // Transition to kFlushCodec on the first end of stream buffer.
214 if (state_ == kNormal && buffer->IsEndOfStream()) { 155 if (state_ == kNormal && buffer->IsEndOfStream()) {
215 state_ = kFlushCodec; 156 state_ = kFlushCodec;
216 } 157 }
217 158
218 // Push all incoming timestamps into the priority queue as long as we have 159 // 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 160 // not yet received an end of stream buffer. It is important that this line
220 // stay below the state transition into kFlushCodec done above. 161 // stay below the state transition into kFlushCodec done above.
221 if (state_ == kNormal) { 162 if (state_ == kNormal) {
222 pts_stream_.EnqueuePts(buffer.get()); 163 pts_stream_.EnqueuePts(buffer.get());
223 } 164 }
224 165
225 // Otherwise, attempt to decode a single frame. 166 // XXXXXXXXXXXXXXX
226 decode_engine_->ConsumeVideoSample(buffer); 167 // This will cause every frame to get allocated.
168 scoped_refptr<VideoFrame> video_frame;
scherkus (not reviewing) 2011/10/28 16:54:04 I'm not entirely sure if I should attempt to keep
acolwell GONE FROM CHROMIUM 2011/10/28 17:38:47 I think we should remove the buffer recycling for
scherkus (not reviewing) 2011/11/01 04:18:26 as discussed offline it looks like there was no wa
169
170 if (!decode_engine_->Decode(buffer, &video_frame)) {
171 state_ = kDecodeFinished;
172 host()->SetError(PIPELINE_ERROR_DECODE);
173 return;
174 }
175
176 // Any successful decode counts!
177 if (buffer->GetDataSize()) {
178 PipelineStatistics statistics;
179 statistics.video_bytes_decoded = buffer->GetDataSize();
180 statistics_callback_.Run(statistics);
181 }
182
183 // If we didn't get a frame then we've either completely finished decoding or
184 // we need more data.
185 if (!video_frame) {
186 if (state_ == kFlushCodec) {
187 state_ = kDecodeFinished;
188 VideoFrameReady(VideoFrame::CreateEmptyFrame());
189 return;
190 }
191
192 ReadFromDemuxerStream();
193 return;
194 }
195
196 // If we got a frame make sure its timestamp is correct before sending it off.
197 pts_stream_.UpdatePtsAndDuration(video_frame.get());
198 video_frame->SetTimestamp(pts_stream_.current_pts());
199 video_frame->SetDuration(pts_stream_.current_duration());
200
201 VideoFrameReady(video_frame);
227 } 202 }
228 203
229 void FFmpegVideoDecoder::ProduceVideoFrame( 204 void FFmpegVideoDecoder::ProduceVideoFrame(
230 scoped_refptr<VideoFrame> video_frame) { 205 scoped_refptr<VideoFrame> video_frame) {
231 if (MessageLoop::current() != message_loop_) { 206 if (MessageLoop::current() != message_loop_) {
232 if (state_ != kStopped) { 207 message_loop_->PostTask(FROM_HERE, base::Bind(
233 message_loop_->PostTask(FROM_HERE, base::Bind( 208 &FFmpegVideoDecoder::ProduceVideoFrame, this, video_frame));
234 &FFmpegVideoDecoder::ProduceVideoFrame, this, video_frame));
235 }
236 return; 209 return;
237 } 210 }
238 211
239 DCHECK_EQ(MessageLoop::current(), message_loop_); 212 DCHECK_EQ(MessageLoop::current(), message_loop_);
240 213
241 // Synchronized flushing before stop should prevent this. 214 // This can happen during shutdown after Stop() has been called.
242 DCHECK_NE(state_, kStopped); 215 if (state_ == kUninitialized) {
243 216 return;
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 } 217 }
251 218
252 // Notify decode engine the available of new frame. 219 // Return empty frames if decoding has finished.
253 decode_engine_->ProduceVideoFrame(video_frame); 220 if (state_ == kDecodeFinished) {
254 } 221 VideoFrameReady(VideoFrame::CreateEmptyFrame());
222 return;
223 }
255 224
256 void FFmpegVideoDecoder::ConsumeVideoFrame( 225 // XXX: Queue frame?
257 scoped_refptr<VideoFrame> video_frame, 226 // XXXXXXXXXXXXX right now we drop video_frame on the ground, causing all
258 const PipelineStatistics& statistics) { 227 // frame to be re-allocated on the fly.
scherkus (not reviewing) 2011/10/28 16:54:04 this is where we could either: 1) cache the video
acolwell GONE FROM CHROMIUM 2011/10/28 17:38:47 Remove the video_frame pointer for now. I want to
scherkus (not reviewing) 2011/11/01 04:18:26 Done.
259 DCHECK_EQ(MessageLoop::current(), message_loop_);
260 DCHECK_NE(state_, kStopped);
261 228
262 statistics_callback_.Run(statistics); 229 ReadFromDemuxerStream();
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;
270 }
271
272 // If we actually got data back, enqueue a frame.
273 pts_stream_.UpdatePtsAndDuration(video_frame.get());
274
275 video_frame->SetTimestamp(pts_stream_.current_pts());
276 video_frame->SetDuration(pts_stream_.current_duration());
277
278 VideoFrameReady(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 }
290
291 void FFmpegVideoDecoder::ProduceVideoSample(
292 scoped_refptr<Buffer> buffer) {
293 DCHECK_EQ(MessageLoop::current(), message_loop_);
294 DCHECK_NE(state_, kStopped);
295
296 demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::OnReadComplete,
297 this));
298 } 230 }
299 231
300 gfx::Size FFmpegVideoDecoder::natural_size() { 232 gfx::Size FFmpegVideoDecoder::natural_size() {
301 return natural_size_; 233 return natural_size_;
302 } 234 }
303 235
304 void FFmpegVideoDecoder::FlushBuffers() { 236 void FFmpegVideoDecoder::ReadFromDemuxerStream() {
305 while (!frame_queue_flushed_.empty()) { 237 DCHECK_NE(state_, kUninitialized);
306 scoped_refptr<VideoFrame> video_frame; 238 DCHECK_NE(state_, kDecodeFinished);
307 video_frame = frame_queue_flushed_.front();
308 frame_queue_flushed_.pop_front();
309 239
310 // Return frames back to the decode engine. 240 demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::DecodeBuffer, this));
311 decode_engine_->ProduceVideoFrame(video_frame);
312 }
313 } 241 }
314 242
315 void FFmpegVideoDecoder::SetVideoDecodeEngineForTest( 243 void FFmpegVideoDecoder::DecodeBuffer(Buffer* buffer) {
316 VideoDecodeEngine* engine) { 244 // TODO(scherkus): change DemuxerStream::Read() to use scoped_refptr<> for
317 decode_engine_.reset(engine); 245 // callback.
246 message_loop_->PostTask(FROM_HERE, base::Bind(
247 &FFmpegVideoDecoder::DoDecodeBuffer, this, make_scoped_refptr(buffer)));
318 } 248 }
319 249
320 } // namespace media 250 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698