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

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

Issue 465044: Refactor FFmpegVideoDecoder to try and generalize code common to all video decoders. (Closed)
Patch Set: Fix SCOPED_TRACE since VS faults on %zd. Created 11 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
« 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) 2009 The Chromium Authors. All rights reserved. Use of this 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
2 // source code is governed by a BSD-style license that can be found in the 2 // source code is governed by a BSD-style license that can be found in the
3 // LICENSE file. 3 // LICENSE file.
4 4
5 #include "media/filters/ffmpeg_video_decoder.h"
6
7 #include "base/task.h"
8 #include "base/waitable_event.h"
9 #include "media/base/callback.h"
5 #include "media/base/limits.h" 10 #include "media/base/limits.h"
6 #include "media/base/video_frame_impl.h" 11 #include "media/base/video_frame_impl.h"
12 #include "media/ffmpeg/ffmpeg_util.h"
7 #include "media/filters/ffmpeg_common.h" 13 #include "media/filters/ffmpeg_common.h"
8 #include "media/filters/ffmpeg_demuxer.h" 14 #include "media/filters/ffmpeg_demuxer.h"
9 #include "media/filters/ffmpeg_video_decoder.h"
10
11 namespace {
12
13 const AVRational kMicrosBase = { 1, base::Time::kMicrosecondsPerSecond };
14
15 // TODO(ajwong): Move this into a utility function file and dedup with
16 // FFmpegDemuxer ConvertTimestamp.
17 base::TimeDelta ConvertTimestamp(const AVRational& time_base, int64 timestamp) {
18 int64 microseconds = av_rescale_q(timestamp, time_base, kMicrosBase);
19 return base::TimeDelta::FromMicroseconds(microseconds);
20 }
21
22 } // namespace
23 15
24 namespace media { 16 namespace media {
25 17
26 // Always try to use two threads for video decoding. There is little reason 18 FFmpegVideoDecodeEngine::FFmpegVideoDecodeEngine()
27 // not to since current day CPUs tend to be multi-core and we measured 19 : codec_context_(NULL),
28 // performance benefits on older machines such as P4s with hyperthreading. 20 state_(kCreated) {
29 // 21 }
30 // Handling decoding on separate threads also frees up the pipeline thread to
31 // continue processing. Although it'd be nice to have the option of a single
32 // decoding thread, FFmpeg treats having one thread the same as having zero
33 // threads (i.e., avcodec_decode_video() will execute on the calling thread).
34 // Yet another reason for having two threads :)
35 //
36 // TODO(scherkus): some video codecs might not like avcodec_thread_init() being
37 // called on them... should attempt to find out which ones those are!
38 static const int kDecodeThreads = 2;
39 22
40 FFmpegVideoDecoder::FFmpegVideoDecoder() 23 FFmpegVideoDecodeEngine::~FFmpegVideoDecodeEngine() {
24 }
25
26 void FFmpegVideoDecodeEngine::Initialize(AVStream* stream, Task* done_cb) {
27 AutoTaskRunner done_runner(done_cb);
28
29 // Always try to use two threads for video decoding. There is little reason
30 // not to since current day CPUs tend to be multi-core and we measured
31 // performance benefits on older machines such as P4s with hyperthreading.
32 //
33 // Handling decoding on separate threads also frees up the pipeline thread to
34 // continue processing. Although it'd be nice to have the option of a single
35 // decoding thread, FFmpeg treats having one thread the same as having zero
36 // threads (i.e., avcodec_decode_video() will execute on the calling thread).
37 // Yet another reason for having two threads :)
38 //
39 // TODO(scherkus): some video codecs might not like avcodec_thread_init()
40 // being called on them... should attempt to find out which ones those are!
41 static const int kDecodeThreads = 2;
42
43 CHECK(state_ == kCreated);
44
45 codec_context_ = stream->codec;
46 codec_context_->flags2 |= CODEC_FLAG2_FAST; // Enable faster H264 decode.
47 // Enable motion vector search (potentially slow), strong deblocking filter
48 // for damaged macroblocks, and set our error detection sensitivity.
49 codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
50 codec_context_->error_recognition = FF_ER_CAREFUL;
51
52 // Serialize calls to avcodec_open().
53 AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
54 {
55 AutoLock auto_lock(FFmpegLock::get()->lock());
56 if (codec &&
57 avcodec_thread_init(codec_context_, kDecodeThreads) >= 0 &&
58 avcodec_open(codec_context_, codec) >= 0) {
59 state_ = kNormal;
60 } else {
61 state_ = kError;
62 }
63 }
64 }
65
66 // Decodes one frame of video with the given buffer.
67 void FFmpegVideoDecodeEngine::DecodeFrame(const Buffer& buffer,
68 AVFrame* yuv_frame,
69 bool* got_frame,
70 Task* done_cb) {
71 AutoTaskRunner done_runner(done_cb);
72
73 // Create a packet for input data.
74 // Due to FFmpeg API changes we no longer have const read-only pointers.
75 //
76 // TODO(ajwong): This is dangerous since AVPacket may change size with
77 // different ffmpeg versions. Use the alloca verison.
78 AVPacket packet;
79 av_init_packet(&packet);
80 packet.data = const_cast<uint8*>(buffer.GetData());
81 packet.size = buffer.GetDataSize();
82
83 // We don't allocate AVFrame on the stack since different versions of FFmpeg
84 // may change the size of AVFrame, causing stack corruption. The solution is
85 // to let FFmpeg allocate the structure via avcodec_alloc_frame().
86 int frame_decoded = 0;
87 int result =
88 avcodec_decode_video2(codec_context_, yuv_frame, &frame_decoded, &packet);
89
90 // Log the problem if we can't decode a video frame and exit early.
91 if (result < 0) {
92 LOG(INFO) << "Error decoding a video frame with timestamp: "
93 << buffer.GetTimestamp().InMicroseconds() << " us"
94 << " , duration: "
95 << buffer.GetDuration().InMicroseconds() << " us"
96 << " , packet size: "
97 << buffer.GetDataSize() << " bytes";
98 *got_frame = false;
99 } else {
100 // If frame_decoded == 0, then no frame was produced.
101 *got_frame = frame_decoded != 0;
102 }
103 }
104
105 void FFmpegVideoDecodeEngine::Flush(Task* done_cb) {
106 AutoTaskRunner done_runner(done_cb);
107
108 avcodec_flush_buffers(codec_context_);
109 }
110
111 VideoSurface::Format FFmpegVideoDecodeEngine::GetSurfaceFormat() const {
112 // J (Motion JPEG) versions of YUV are full range 0..255.
113 // Regular (MPEG) YUV is 16..240.
114 // For now we will ignore the distinction and treat them the same.
115 switch (codec_context_->pix_fmt) {
116 case PIX_FMT_YUV420P:
117 case PIX_FMT_YUVJ420P:
118 return VideoSurface::YV12;
119 break;
120 case PIX_FMT_YUV422P:
121 case PIX_FMT_YUVJ422P:
122 return VideoSurface::YV16;
123 break;
124 default:
125 // TODO(scherkus): More formats here?
126 return VideoSurface::INVALID;
127 }
128 }
129
130 // static
131 FilterFactory* FFmpegVideoDecoder::CreateFactory() {
132 return new FilterFactoryImpl1<FFmpegVideoDecoder, VideoDecodeEngine*>(
133 new FFmpegVideoDecodeEngine());
134 }
135
136 FFmpegVideoDecoder::FFmpegVideoDecoder(VideoDecodeEngine* engine)
41 : width_(0), 137 : width_(0),
42 height_(0), 138 height_(0),
43 time_base_(new AVRational()), 139 time_base_(new AVRational()),
44 state_(kNormal), 140 state_(kNormal),
45 codec_context_(NULL) { 141 decode_engine_(engine) {
46 } 142 }
47 143
48 FFmpegVideoDecoder::~FFmpegVideoDecoder() { 144 FFmpegVideoDecoder::~FFmpegVideoDecoder() {
49 } 145 }
50 146
51 // static 147 // static
52 bool FFmpegVideoDecoder::IsMediaFormatSupported(const MediaFormat& format) { 148 bool FFmpegVideoDecoder::IsMediaFormatSupported(const MediaFormat& format) {
53 std::string mime_type; 149 std::string mime_type;
54 return format.GetAsString(MediaFormat::kMimeType, &mime_type) && 150 return format.GetAsString(MediaFormat::kMimeType, &mime_type) &&
55 mime_type::kFFmpegVideo == mime_type; 151 mime_type::kFFmpegVideo == mime_type;
56 } 152 }
57 153
58 bool FFmpegVideoDecoder::OnInitialize(DemuxerStream* demuxer_stream) { 154 void FFmpegVideoDecoder::DoInitialize(DemuxerStream* demuxer_stream,
155 bool* success,
156 Task* done_cb) {
157 AutoTaskRunner done_runner(done_cb);
158 *success = false;
159
59 // Get the AVStream by querying for the provider interface. 160 // Get the AVStream by querying for the provider interface.
60 AVStreamProvider* av_stream_provider; 161 AVStreamProvider* av_stream_provider;
61 if (!demuxer_stream->QueryInterface(&av_stream_provider)) { 162 if (!demuxer_stream->QueryInterface(&av_stream_provider)) {
62 return false; 163 return;
63 } 164 }
64 AVStream* av_stream = av_stream_provider->GetAVStream(); 165 AVStream* av_stream = av_stream_provider->GetAVStream();
65 166
167 *time_base_ = av_stream->time_base;
168
169 // TODO(ajwong): We don't need these extra variables if |media_format_| has
170 // them. Remove.
66 width_ = av_stream->codec->width; 171 width_ = av_stream->codec->width;
67 height_ = av_stream->codec->height; 172 height_ = av_stream->codec->height;
68 *time_base_ = av_stream->time_base;
69 if (width_ > Limits::kMaxDimension || height_ > Limits::kMaxDimension || 173 if (width_ > Limits::kMaxDimension || height_ > Limits::kMaxDimension ||
70 width_ * height_ > Limits::kMaxCanvas) 174 width_ * height_ > Limits::kMaxCanvas)
71 return false; 175 return;
72 176
73 media_format_.SetAsString(MediaFormat::kMimeType, 177 media_format_.SetAsString(MediaFormat::kMimeType,
74 mime_type::kUncompressedVideo); 178 mime_type::kUncompressedVideo);
75 media_format_.SetAsInteger(MediaFormat::kWidth, width_); 179 media_format_.SetAsInteger(MediaFormat::kWidth, width_);
76 media_format_.SetAsInteger(MediaFormat::kHeight, height_); 180 media_format_.SetAsInteger(MediaFormat::kHeight, height_);
77 181
78 codec_context_ = av_stream->codec; 182 decode_engine_->Initialize(
79 codec_context_->flags2 |= CODEC_FLAG2_FAST; // Enable faster H264 decode. 183 av_stream,
80 // Enable motion vector search (potentially slow), strong deblocking filter 184 NewRunnableMethod(this,
81 // for damaged macroblocks, and set our error detection sensitivity. 185 &FFmpegVideoDecoder::OnInitializeComplete,
82 codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK; 186 success,
83 codec_context_->error_recognition = FF_ER_CAREFUL; 187 done_runner.release()));
84
85 // Serialize calls to avcodec_open().
86 AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
87 {
88 AutoLock auto_lock(FFmpegLock::get()->lock());
89 if (!codec ||
90 avcodec_thread_init(codec_context_, kDecodeThreads) < 0 ||
91 avcodec_open(codec_context_, codec) < 0) {
92 return false;
93 }
94 }
95 return true;
96 } 188 }
97 189
98 void FFmpegVideoDecoder::OnSeek(base::TimeDelta time) { 190 void FFmpegVideoDecoder::OnInitializeComplete(bool* success, Task* done_cb) {
191 AutoTaskRunner done_runner(done_cb);
192
193 *success = decode_engine_->state() == FFmpegVideoDecodeEngine::kNormal;
194 }
195
196 void FFmpegVideoDecoder::DoSeek(base::TimeDelta time, Task* done_cb) {
99 // Everything in the presentation time queue is invalid, clear the queue. 197 // Everything in the presentation time queue is invalid, clear the queue.
100 while (!pts_heap_.IsEmpty()) 198 while (!pts_heap_.IsEmpty())
101 pts_heap_.Pop(); 199 pts_heap_.Pop();
102 200
103 // We're back where we started. It should be completely safe to flush here 201 // We're back where we started. It should be completely safe to flush here
104 // since DecoderBase uses |expecting_discontinuous_| to verify that the next 202 // since DecoderBase uses |expecting_discontinuous_| to verify that the next
105 // time OnDecode() is called we will have a discontinuous buffer. 203 // time DoDecode() is called we will have a discontinuous buffer.
204 //
205 // TODO(ajwong): Should we put a guard here to prevent leaving kError.
106 state_ = kNormal; 206 state_ = kNormal;
107 avcodec_flush_buffers(codec_context_); 207
208 decode_engine_->Flush(done_cb);
108 } 209 }
109 210
110 void FFmpegVideoDecoder::OnDecode(Buffer* buffer) { 211 void FFmpegVideoDecoder::DoDecode(Buffer* buffer, Task* done_cb) {
212 AutoTaskRunner done_runner(done_cb);
213
214 // TODO(ajwong): This DoDecode and OnDecodeComplete set of functions is too
215 // complicated to easily unittest. The test becomes fragile. Try to find a
216 // way to reorganize into smaller units for testing.
217
111 // During decode, because reads are issued asynchronously, it is possible to 218 // During decode, because reads are issued asynchronously, it is possible to
112 // receive multiple end of stream buffers since each read is acked. When the 219 // receive multiple end of stream buffers since each read is acked. When the
113 // first end of stream buffer is read, FFmpeg may still have frames queued 220 // first end of stream buffer is read, FFmpeg may still have frames queued
114 // up in the decoder so we need to go through the decode loop until it stops 221 // up in the decoder so we need to go through the decode loop until it stops
115 // giving sensible data. After that, the decoder should output empty 222 // giving sensible data. After that, the decoder should output empty
116 // frames. There are three states the decoder can be in: 223 // frames. There are three states the decoder can be in:
117 // 224 //
118 // kNormal: This is the starting state. Buffers are decoded. Decode errors 225 // kNormal: This is the starting state. Buffers are decoded. Decode errors
119 // are discarded. 226 // are discarded.
120 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2 227 // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2
(...skipping 27 matching lines...) Expand all
148 // not yet received an end of stream buffer. It is important that this line 255 // not yet received an end of stream buffer. It is important that this line
149 // stay below the state transition into kFlushCodec done above. 256 // stay below the state transition into kFlushCodec done above.
150 // 257 //
151 // TODO(ajwong): This push logic, along with the pop logic below needs to 258 // TODO(ajwong): This push logic, along with the pop logic below needs to
152 // be reevaluated to correctly handle decode errors. 259 // be reevaluated to correctly handle decode errors.
153 if (state_ == kNormal) { 260 if (state_ == kNormal) {
154 pts_heap_.Push(buffer->GetTimestamp()); 261 pts_heap_.Push(buffer->GetTimestamp());
155 } 262 }
156 263
157 // Otherwise, attempt to decode a single frame. 264 // Otherwise, attempt to decode a single frame.
158 scoped_ptr_malloc<AVFrame, ScopedPtrAVFree> yuv_frame(avcodec_alloc_frame()); 265 AVFrame* yuv_frame = avcodec_alloc_frame();
159 if (DecodeFrame(*buffer, codec_context_, yuv_frame.get())) { 266 bool* got_frame = new bool;
160 last_pts_ = FindPtsAndDuration(*time_base_, 267 decode_engine_->DecodeFrame(
161 pts_heap_, 268 *buffer,
162 last_pts_, 269 yuv_frame,
163 yuv_frame.get()); 270 got_frame,
271 NewRunnableMethod(this,
272 &FFmpegVideoDecoder::OnDecodeComplete,
273 yuv_frame,
274 got_frame,
275 done_runner.release()));
276 }
277
278 void FFmpegVideoDecoder::OnDecodeComplete(AVFrame* yuv_frame, bool* got_frame,
279 Task* done_cb) {
280 // Note: The |done_runner| must be declared *last* to ensure proper
281 // destruction order.
282 scoped_ptr_malloc<AVFrame, ScopedPtrAVFree> yuv_frame_deleter(yuv_frame);
283 scoped_ptr<bool> got_frame_deleter(got_frame);
284 AutoTaskRunner done_runner(done_cb);
285
286 // If we actually got data back, enqueue a frame.
287 if (*got_frame) {
288 last_pts_ = FindPtsAndDuration(*time_base_, pts_heap_, last_pts_,
289 yuv_frame);
164 290
165 // Pop off a pts on a successful decode since we are "using up" one 291 // Pop off a pts on a successful decode since we are "using up" one
166 // timestamp. 292 // timestamp.
167 // 293 //
168 // TODO(ajwong): Do we need to pop off a pts when avcodec_decode_video2() 294 // TODO(ajwong): Do we need to pop off a pts when avcodec_decode_video2()
169 // returns < 0? The rationale is that when get_picture_ptr == 0, we skip 295 // returns < 0? The rationale is that when get_picture_ptr == 0, we skip
170 // popping a pts because no frame was produced. However, when 296 // popping a pts because no frame was produced. However, when
171 // avcodec_decode_video2() returns false, it is a decode error, which 297 // avcodec_decode_video2() returns false, it is a decode error, which
172 // if it means a frame is dropped, may require us to pop one more time. 298 // if it means a frame is dropped, may require us to pop one more time.
173 if (!pts_heap_.IsEmpty()) { 299 if (!pts_heap_.IsEmpty()) {
174 pts_heap_.Pop(); 300 pts_heap_.Pop();
175 } else { 301 } else {
176 NOTREACHED() << "Attempting to decode more frames than were input."; 302 NOTREACHED() << "Attempting to decode more frames than were input.";
177 } 303 }
178 304
179 if (!EnqueueVideoFrame( 305 if (!EnqueueVideoFrame(
180 GetSurfaceFormat(*codec_context_), last_pts_, yuv_frame.get())) { 306 decode_engine_->GetSurfaceFormat(), last_pts_, yuv_frame)) {
181 // On an EnqueueEmptyFrame error, error out the whole pipeline and 307 // On an EnqueueEmptyFrame error, error out the whole pipeline and
182 // set the state to kDecodeFinished. 308 // set the state to kDecodeFinished.
183 SignalPipelineError(); 309 SignalPipelineError();
184 } 310 }
185 } else { 311 } else {
186 // When in kFlushCodec, any errored decode, or a 0-lengthed frame, 312 // When in kFlushCodec, any errored decode, or a 0-lengthed frame,
187 // is taken as a signal to stop decoding. 313 // is taken as a signal to stop decoding.
188 if (state_ == kFlushCodec) { 314 if (state_ == kFlushCodec) {
189 state_ = kDecodeFinished; 315 state_ = kDecodeFinished;
190 EnqueueEmptyFrame(); 316 EnqueueEmptyFrame();
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
251 dest += dest_stride; 377 dest += dest_stride;
252 } 378 }
253 } 379 }
254 380
255 void FFmpegVideoDecoder::EnqueueEmptyFrame() { 381 void FFmpegVideoDecoder::EnqueueEmptyFrame() {
256 scoped_refptr<VideoFrame> video_frame; 382 scoped_refptr<VideoFrame> video_frame;
257 VideoFrameImpl::CreateEmptyFrame(&video_frame); 383 VideoFrameImpl::CreateEmptyFrame(&video_frame);
258 EnqueueResult(video_frame); 384 EnqueueResult(video_frame);
259 } 385 }
260 386
261 bool FFmpegVideoDecoder::DecodeFrame(const Buffer& buffer,
262 AVCodecContext* codec_context,
263 AVFrame* yuv_frame) {
264 // Create a packet for input data.
265 // Due to FFmpeg API changes we no longer have const read-only pointers.
266 AVPacket packet;
267 av_init_packet(&packet);
268 packet.data = const_cast<uint8*>(buffer.GetData());
269 packet.size = buffer.GetDataSize();
270
271 // We don't allocate AVFrame on the stack since different versions of FFmpeg
272 // may change the size of AVFrame, causing stack corruption. The solution is
273 // to let FFmpeg allocate the structure via avcodec_alloc_frame().
274 int frame_decoded = 0;
275 int result =
276 avcodec_decode_video2(codec_context, yuv_frame, &frame_decoded, &packet);
277
278 // Log the problem if we can't decode a video frame and exit early.
279 if (result < 0) {
280 LOG(INFO) << "Error decoding a video frame with timestamp: "
281 << buffer.GetTimestamp().InMicroseconds() << " us"
282 << " , duration: "
283 << buffer.GetDuration().InMicroseconds() << " us"
284 << " , packet size: "
285 << buffer.GetDataSize() << " bytes";
286 return false;
287 }
288
289 // If frame_decoded == 0, then no frame was produced.
290 return frame_decoded != 0;
291 }
292
293 FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoder::FindPtsAndDuration( 387 FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoder::FindPtsAndDuration(
294 const AVRational& time_base, 388 const AVRational& time_base,
295 const PtsHeap& pts_heap, 389 const PtsHeap& pts_heap,
296 const TimeTuple& last_pts, 390 const TimeTuple& last_pts,
297 const AVFrame* frame) { 391 const AVFrame* frame) {
298 TimeTuple pts; 392 TimeTuple pts;
299 393
300 // Default repeat_pict to 0 because if there is no frame information, 394 // Default repeat_pict to 0 because if there is no frame information,
301 // we just assume the frame only plays for one time_base. 395 // we just assume the frame only plays for one time_base.
302 int repeat_pict = 0; 396 int repeat_pict = 0;
(...skipping 23 matching lines...) Expand all
326 } 420 }
327 421
328 // Fill in the duration while accounting for repeated frames. 422 // Fill in the duration while accounting for repeated frames.
329 // 423 //
330 // TODO(ajwong): Make sure this formula is correct. 424 // TODO(ajwong): Make sure this formula is correct.
331 pts.duration = ConvertTimestamp(time_base, 1 + repeat_pict); 425 pts.duration = ConvertTimestamp(time_base, 1 + repeat_pict);
332 426
333 return pts; 427 return pts;
334 } 428 }
335 429
336 VideoSurface::Format FFmpegVideoDecoder::GetSurfaceFormat(
337 const AVCodecContext& codec_context) {
338 // J (Motion JPEG) versions of YUV are full range 0..255.
339 // Regular (MPEG) YUV is 16..240.
340 // For now we will ignore the distinction and treat them the same.
341 switch (codec_context.pix_fmt) {
342 case PIX_FMT_YUV420P:
343 case PIX_FMT_YUVJ420P:
344 return VideoSurface::YV12;
345 break;
346 case PIX_FMT_YUV422P:
347 case PIX_FMT_YUVJ422P:
348 return VideoSurface::YV16;
349 break;
350 default:
351 // TODO(scherkus): More formats here?
352 return VideoSurface::INVALID;
353 }
354 }
355
356 void FFmpegVideoDecoder::SignalPipelineError() { 430 void FFmpegVideoDecoder::SignalPipelineError() {
357 host()->SetError(PIPELINE_ERROR_DECODE); 431 host()->SetError(PIPELINE_ERROR_DECODE);
358 state_ = kDecodeFinished; 432 state_ = kDecodeFinished;
359 } 433 }
360 434
361 } // namespace 435 void FFmpegVideoDecoder::SetVideoDecodeEngineForTest(
436 VideoDecodeEngine* engine) {
437 decode_engine_.reset(engine);
438 }
439
440 } // 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