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
3 // found in the LICENSE file.
5 #include "media/filters/ffmpeg_video_decoder.h"
8 #include "base/message_loop.h"
9 #include "media/base/demuxer_stream.h"
10 #include "media/base/filter_host.h"
11 #include "media/base/limits.h"
12 #include "media/base/video_decoder_config.h"
13 #include "media/base/video_frame.h"
14 #include "media/ffmpeg/ffmpeg_common.h"
15 #include "media/video/ffmpeg_video_decode_engine.h"
19 FFmpegVideoDecoder::FFmpegVideoDecoder(MessageLoop
* message_loop
)
20 : message_loop_(message_loop
),
21 state_(kUninitialized
),
22 decode_engine_(new FFmpegVideoDecodeEngine()) {
25 FFmpegVideoDecoder::~FFmpegVideoDecoder() {}
27 void FFmpegVideoDecoder::Initialize(DemuxerStream
* demuxer_stream
,
28 const base::Closure
& callback
,
29 const StatisticsCallback
& stats_callback
) {
30 if (MessageLoop::current() != message_loop_
) {
31 message_loop_
->PostTask(FROM_HERE
, base::Bind(
32 &FFmpegVideoDecoder::Initialize
, this,
33 make_scoped_refptr(demuxer_stream
), callback
, stats_callback
));
37 DCHECK(!demuxer_stream_
);
39 if (!demuxer_stream
) {
40 host()->SetError(PIPELINE_ERROR_DECODE
);
45 demuxer_stream_
= demuxer_stream
;
46 statistics_callback_
= stats_callback
;
48 const VideoDecoderConfig
& config
= demuxer_stream
->video_decoder_config();
50 // TODO(scherkus): this check should go in PipelineImpl prior to creating
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();
69 host()->SetError(PIPELINE_ERROR_DECODE
);
74 pts_stream_
.Initialize(GetFrameDuration(config
));
75 natural_size_
= config
.natural_size();
77 if (!decode_engine_
->Initialize(config
)) {
78 host()->SetError(PIPELINE_ERROR_DECODE
);
87 void FFmpegVideoDecoder::Stop(const base::Closure
& callback
) {
88 if (MessageLoop::current() != message_loop_
) {
89 message_loop_
->PostTask(FROM_HERE
, base::Bind(
90 &FFmpegVideoDecoder::Stop
, this, callback
));
94 decode_engine_
->Uninitialize();
95 state_
= kUninitialized
;
99 void FFmpegVideoDecoder::Seek(base::TimeDelta time
, const FilterStatusCB
& cb
) {
100 if (MessageLoop::current() != message_loop_
) {
101 message_loop_
->PostTask(FROM_HERE
, base::Bind(
102 &FFmpegVideoDecoder::Seek
, this, time
, cb
));
106 pts_stream_
.Seek(time
);
110 void FFmpegVideoDecoder::Pause(const base::Closure
& callback
) {
111 if (MessageLoop::current() != message_loop_
) {
112 message_loop_
->PostTask(FROM_HERE
, base::Bind(
113 &FFmpegVideoDecoder::Pause
, this, callback
));
120 void FFmpegVideoDecoder::Flush(const base::Closure
& callback
) {
121 if (MessageLoop::current() != message_loop_
) {
122 message_loop_
->PostTask(FROM_HERE
, base::Bind(
123 &FFmpegVideoDecoder::Flush
, this, callback
));
127 decode_engine_
->Flush();
133 void FFmpegVideoDecoder::Read(const ReadCB
& callback
) {
134 // TODO(scherkus): forced task post since VideoRendererBase::FrameReady() will
135 // call Read() on FFmpegVideoDecoder's thread as we executed |read_cb_|.
136 message_loop_
->PostTask(FROM_HERE
, base::Bind(
137 &FFmpegVideoDecoder::DoRead
, this, callback
));
140 const gfx::Size
& FFmpegVideoDecoder::natural_size() {
141 return natural_size_
;
144 void FFmpegVideoDecoder::DoRead(const ReadCB
& callback
) {
145 DCHECK_EQ(MessageLoop::current(), message_loop_
);
146 CHECK(!callback
.is_null());
147 CHECK(read_cb_
.is_null()) << "Overlapping decodes are not supported.";
149 // This can happen during shutdown after Stop() has been called.
150 if (state_
== kUninitialized
) {
154 // Return empty frames if decoding has finished.
155 if (state_
== kDecodeFinished
) {
156 callback
.Run(VideoFrame::CreateEmptyFrame());
161 ReadFromDemuxerStream();
165 void FFmpegVideoDecoder::ReadFromDemuxerStream() {
166 DCHECK_NE(state_
, kUninitialized
);
167 DCHECK_NE(state_
, kDecodeFinished
);
168 DCHECK(!read_cb_
.is_null());
170 demuxer_stream_
->Read(base::Bind(&FFmpegVideoDecoder::DecodeBuffer
, this));
173 void FFmpegVideoDecoder::DecodeBuffer(const scoped_refptr
<Buffer
>& buffer
) {
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
));
180 void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr
<Buffer
>& buffer
) {
181 DCHECK_EQ(MessageLoop::current(), message_loop_
);
182 DCHECK_NE(state_
, kUninitialized
);
183 DCHECK_NE(state_
, kDecodeFinished
);
184 DCHECK(!read_cb_
.is_null());
186 // During decode, because reads are issued asynchronously, it is possible to
187 // receive multiple end of stream buffers since each read is acked. When the
188 // first end of stream buffer is read, FFmpeg may still have frames queued
189 // up in the decoder so we need to go through the decode loop until it stops
190 // giving sensible data. After that, the decoder should output empty
191 // frames. There are three states the decoder can be in:
193 // kNormal: This is the starting state. Buffers are decoded. Decode errors
195 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2
196 // until no more data is returned to flush out remaining
197 // frames. The input buffer is ignored at this point.
198 // kDecodeFinished: All calls return empty frames.
200 // These are the possible state transitions.
202 // kNormal -> kFlushCodec:
203 // When buffer->IsEndOfStream() is first true.
204 // kNormal -> kDecodeFinished:
205 // A decoding error occurs and decoding needs to stop.
206 // kFlushCodec -> kDecodeFinished:
207 // When avcodec_decode_video2() returns 0 data or errors out.
208 // (any state) -> kNormal:
209 // Any time Flush() is called.
211 // Transition to kFlushCodec on the first end of stream buffer.
212 if (state_
== kNormal
&& buffer
->IsEndOfStream()) {
213 state_
= kFlushCodec
;
216 // 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
218 // stay below the state transition into kFlushCodec done above.
219 if (state_
== kNormal
) {
220 pts_stream_
.EnqueuePts(buffer
.get());
223 scoped_refptr
<VideoFrame
> video_frame
;
224 if (!decode_engine_
->Decode(buffer
, &video_frame
)) {
225 state_
= kDecodeFinished
;
226 DeliverFrame(VideoFrame::CreateEmptyFrame());
227 host()->SetError(PIPELINE_ERROR_DECODE
);
231 // Any successful decode counts!
232 if (buffer
->GetDataSize()) {
233 PipelineStatistics statistics
;
234 statistics
.video_bytes_decoded
= buffer
->GetDataSize();
235 statistics_callback_
.Run(statistics
);
238 // If we didn't get a frame then we've either completely finished decoding or
239 // we need more data.
241 if (state_
== kFlushCodec
) {
242 state_
= kDecodeFinished
;
243 DeliverFrame(VideoFrame::CreateEmptyFrame());
247 ReadFromDemuxerStream();
251 // If we got a frame make sure its timestamp is correct before sending it off.
252 pts_stream_
.UpdatePtsAndDuration(video_frame
.get());
253 video_frame
->SetTimestamp(pts_stream_
.current_pts());
254 video_frame
->SetDuration(pts_stream_
.current_duration());
256 DeliverFrame(video_frame
);
259 void FFmpegVideoDecoder::DeliverFrame(
260 const scoped_refptr
<VideoFrame
>& video_frame
) {
261 // Reset the callback before running to protect against reentrancy.
262 ReadCB read_cb
= read_cb_
;
264 read_cb
.Run(video_frame
);