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

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: fixes 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_);
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 // Method must immediately return.
140 DCHECK(!flush_callback_.is_null()); 135 message_loop_->PostTask(FROM_HERE, base::Bind(
scherkus (not reviewing) 2011/11/03 04:55:59 FYI forced post task since VideoRendererBase::Fram
Ami GONE FROM CHROMIUM 2011/11/03 16:40:00 This is a better comment than what you have in the
scherkus (not reviewing) 2011/11/03 20:34:38 Done.
141 136 &FFmpegVideoDecoder::DoRead, this, callback));
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 } 137 }
150 138
151 void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) { 139 gfx::Size FFmpegVideoDecoder::natural_size() {
Ami GONE FROM CHROMIUM 2011/11/03 16:40:00 return const&?
scherkus (not reviewing) 2011/11/03 20:34:38 make ALL the return types const&!
152 if (MessageLoop::current() != message_loop_) { 140 return natural_size_;
153 message_loop_->PostTask(FROM_HERE, 141 }
154 base::Bind(&FFmpegVideoDecoder::Seek, this, 142
155 time, cb)); 143 void FFmpegVideoDecoder::DoRead(const ReadCB& callback) {
156 return; 144 DCHECK_EQ(MessageLoop::current(), message_loop_);
145 CHECK(!callback.is_null());
146 CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
147
148 // This can happen during shutdown after Stop() has been called.
149 if (state_ == kUninitialized) {
150 return;
157 } 151 }
158 152
159 DCHECK_EQ(MessageLoop::current(), message_loop_); 153 // Return empty frames if decoding has finished.
160 DCHECK(seek_cb_.is_null()); 154 if (state_ == kDecodeFinished) {
155 callback.Run(VideoFrame::CreateEmptyFrame());
156 return;
157 }
161 158
162 pts_stream_.Seek(time); 159 read_cb_ = callback;
163 seek_cb_ = cb; 160 ReadFromDemuxerStream();
164 decode_engine_->Seek();
165 } 161 }
166 162
167 void FFmpegVideoDecoder::OnSeekComplete() {
168 DCHECK_EQ(MessageLoop::current(), message_loop_);
169 DCHECK(!seek_cb_.is_null());
170 163
171 ResetAndRunCB(&seek_cb_, PIPELINE_OK); 164 void FFmpegVideoDecoder::ReadFromDemuxerStream() {
165 DCHECK_NE(state_, kUninitialized);
166 DCHECK_NE(state_, kDecodeFinished);
167 DCHECK(!read_cb_.is_null());
168
169 demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::DecodeBuffer, this));
172 } 170 }
173 171
174 void FFmpegVideoDecoder::OnError() { 172 void FFmpegVideoDecoder::DecodeBuffer(const scoped_refptr<Buffer>& buffer) {
175 VideoFrameReady(NULL); 173 message_loop_->PostTask(FROM_HERE, base::Bind(
scherkus (not reviewing) 2011/11/03 04:55:59 FYI forced post task since FFmpegDemuxerStream::Re
scherkus (not reviewing) 2011/11/03 20:34:38 Comment added.
174 &FFmpegVideoDecoder::DoDecodeBuffer, this, buffer));
176 } 175 }
177 176
178 void FFmpegVideoDecoder::OnReadComplete(const scoped_refptr<Buffer>& buffer) { 177 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_); 178 DCHECK_EQ(MessageLoop::current(), message_loop_);
186 DCHECK_NE(state_, kStopped); // because of Flush() before Stop(). 179 DCHECK_NE(state_, kUninitialized);
180 DCHECK_NE(state_, kDecodeFinished);
181 DCHECK(!read_cb_.is_null());
187 182
188 // During decode, because reads are issued asynchronously, it is possible to 183 // 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 184 // 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 185 // 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 186 // 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 187 // giving sensible data. After that, the decoder should output empty
193 // frames. There are three states the decoder can be in: 188 // frames. There are three states the decoder can be in:
194 // 189 //
195 // kNormal: This is the starting state. Buffers are decoded. Decode errors 190 // kNormal: This is the starting state. Buffers are decoded. Decode errors
196 // are discarded. 191 // are discarded.
197 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2 192 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2
198 // until no more data is returned to flush out remaining 193 // until no more data is returned to flush out remaining
199 // frames. The input buffer is ignored at this point. 194 // frames. The input buffer is ignored at this point.
200 // kDecodeFinished: All calls return empty frames. 195 // kDecodeFinished: All calls return empty frames.
201 // 196 //
202 // These are the possible state transitions. 197 // These are the possible state transitions.
203 // 198 //
204 // kNormal -> kFlushCodec: 199 // kNormal -> kFlushCodec:
205 // When buffer->IsEndOfStream() is first true. 200 // When buffer->IsEndOfStream() is first true.
206 // kNormal -> kDecodeFinished: 201 // kNormal -> kDecodeFinished:
207 // A catastrophic failure occurs, and decoding needs to stop. 202 // A decoding error occurs and decoding needs to stop.
208 // kFlushCodec -> kDecodeFinished: 203 // kFlushCodec -> kDecodeFinished:
209 // When avcodec_decode_video2() returns 0 data or errors out. 204 // When avcodec_decode_video2() returns 0 data or errors out.
210 // (any state) -> kNormal: 205 // (any state) -> kNormal:
211 // Any time buffer->IsDiscontinuous() is true. 206 // Any time Flush() is called.
212 207
213 // Transition to kFlushCodec on the first end of stream buffer. 208 // Transition to kFlushCodec on the first end of stream buffer.
214 if (state_ == kNormal && buffer->IsEndOfStream()) { 209 if (state_ == kNormal && buffer->IsEndOfStream()) {
215 state_ = kFlushCodec; 210 state_ = kFlushCodec;
216 } 211 }
217 212
218 // Push all incoming timestamps into the priority queue as long as we have 213 // 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 214 // not yet received an end of stream buffer. It is important that this line
220 // stay below the state transition into kFlushCodec done above. 215 // stay below the state transition into kFlushCodec done above.
221 if (state_ == kNormal) { 216 if (state_ == kNormal) {
222 pts_stream_.EnqueuePts(buffer.get()); 217 pts_stream_.EnqueuePts(buffer.get());
223 } 218 }
224 219
225 // Otherwise, attempt to decode a single frame. 220 scoped_refptr<VideoFrame> video_frame;
226 decode_engine_->ConsumeVideoSample(buffer); 221 if (!decode_engine_->Decode(buffer, &video_frame)) {
227 } 222 state_ = kDecodeFinished;
228 223 DeliverFrame(VideoFrame::CreateEmptyFrame());
229 void FFmpegVideoDecoder::ProduceVideoFrame( 224 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; 225 return;
237 } 226 }
238 227
239 DCHECK_EQ(MessageLoop::current(), message_loop_); 228 // Any successful decode counts!
240 229 if (buffer->GetDataSize()) {
241 // Synchronized flushing before stop should prevent this. 230 PipelineStatistics statistics;
242 DCHECK_NE(state_, kStopped); 231 statistics.video_bytes_decoded = buffer->GetDataSize();
243 232 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 } 233 }
251 234
252 // Notify decode engine the available of new frame. 235 // If we didn't get a frame then we've either completely finished decoding or
253 decode_engine_->ProduceVideoFrame(video_frame); 236 // we need more data.
254 } 237 if (!video_frame) {
255 238 if (state_ == kFlushCodec) {
256 void FFmpegVideoDecoder::ConsumeVideoFrame( 239 state_ = kDecodeFinished;
257 scoped_refptr<VideoFrame> video_frame, 240 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; 241 return;
270 } 242 }
271 243
272 // If we actually got data back, enqueue a frame. 244 ReadFromDemuxerStream();
273 pts_stream_.UpdatePtsAndDuration(video_frame.get()); 245 return;
246 }
274 247
275 video_frame->SetTimestamp(pts_stream_.current_pts()); 248 // If we got a frame make sure its timestamp is correct before sending it off.
276 video_frame->SetDuration(pts_stream_.current_duration()); 249 pts_stream_.UpdatePtsAndDuration(video_frame.get());
250 video_frame->SetTimestamp(pts_stream_.current_pts());
251 video_frame->SetDuration(pts_stream_.current_duration());
277 252
278 VideoFrameReady(video_frame); 253 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 } 254 }
290 255
291 void FFmpegVideoDecoder::ProduceVideoSample( 256 void FFmpegVideoDecoder::DeliverFrame(
292 scoped_refptr<Buffer> buffer) { 257 const scoped_refptr<VideoFrame>& video_frame) {
293 DCHECK_EQ(MessageLoop::current(), message_loop_); 258 // Reset the callback before running to protect against reentrancy.
294 DCHECK_NE(state_, kStopped); 259 ReadCB read_cb = read_cb_;
295 260 read_cb_.Reset();
296 demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::OnReadComplete, this)); 261 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 } 262 }
318 263
319 } // namespace media 264 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698