OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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 // A test program that drives an OpenMAX video decoder module. This program | 5 // A test program that drives an OpenMAX video decoder module. This program |
6 // will take video in elementary stream and read into the decoder. | 6 // will take video in elementary stream and read into the decoder. |
7 // | 7 // |
8 // Run the following command to see usage: | 8 // Run the following command to see usage: |
9 // ./omx_test | 9 // ./omx_test |
10 | 10 |
11 #include "base/at_exit.h" | 11 #include "base/at_exit.h" |
12 #include "base/callback.h" | 12 #include "base/callback.h" |
13 #include "base/command_line.h" | 13 #include "base/command_line.h" |
14 #include "base/message_loop.h" | 14 #include "base/message_loop.h" |
15 #include "base/scoped_ptr.h" | 15 #include "base/scoped_ptr.h" |
16 #include "base/time.h" | 16 #include "base/time.h" |
| 17 #include "media/base/data_buffer.h" |
17 #include "media/base/media.h" | 18 #include "media/base/media.h" |
| 19 #include "media/base/video_frame.h" |
18 #include "media/ffmpeg/ffmpeg_common.h" | 20 #include "media/ffmpeg/ffmpeg_common.h" |
19 #include "media/ffmpeg/file_protocol.h" | 21 #include "media/ffmpeg/file_protocol.h" |
20 #include "media/filters/bitstream_converter.h" | 22 #include "media/filters/bitstream_converter.h" |
21 #include "media/omx/omx_codec.h" | 23 #include "media/filters/omx_video_decode_engine.h" |
22 #include "media/base/data_buffer.h" | |
23 #include "media/tools/omx_test/color_space_util.h" | 24 #include "media/tools/omx_test/color_space_util.h" |
24 #include "media/tools/omx_test/file_reader_util.h" | 25 #include "media/tools/omx_test/file_reader_util.h" |
25 #include "media/tools/omx_test/file_sink.h" | 26 #include "media/tools/omx_test/file_sink.h" |
26 | 27 |
27 using media::BlockFileReader; | 28 using media::BlockFileReader; |
| 29 using media::Buffer; |
| 30 using media::DataBuffer; |
28 using media::FFmpegFileReader; | 31 using media::FFmpegFileReader; |
29 using media::FileReader; | 32 using media::FileReader; |
30 using media::FileSink; | 33 using media::FileSink; |
31 using media::H264FileReader; | 34 using media::H264FileReader; |
32 using media::OmxCodec; | |
33 using media::OmxConfigurator; | 35 using media::OmxConfigurator; |
34 using media::OmxDecoderConfigurator; | 36 using media::OmxDecoderConfigurator; |
35 using media::OmxEncoderConfigurator; | 37 using media::OmxEncoderConfigurator; |
| 38 using media::OmxVideoDecodeEngine; |
| 39 using media::VideoFrame; |
36 using media::YuvFileReader; | 40 using media::YuvFileReader; |
37 using media::Buffer; | |
38 using media::DataBuffer; | |
39 | 41 |
40 // This is the driver object to feed the decoder with data from a file. | 42 // This is the driver object to feed the decoder with data from a file. |
41 // It also provides callbacks for the decoder to receive events from the | 43 // It also provides callbacks for the decoder to receive events from the |
42 // decoder. | 44 // decoder. |
43 class TestApp { | 45 // TODO(wjia): AVStream should be replaced with a new structure which is |
| 46 // neutral to any video decoder. Also change media.gyp correspondingly. |
| 47 class TestApp : public base::RefCountedThreadSafe<TestApp> { |
44 public: | 48 public: |
45 TestApp(OmxConfigurator* configurator, FileSink* file_sink, | 49 TestApp(AVStream* av_stream, |
| 50 FileSink* file_sink, |
46 FileReader* file_reader) | 51 FileReader* file_reader) |
47 : configurator_(configurator), | 52 : av_stream_(av_stream), |
48 file_reader_(file_reader), | 53 file_reader_(file_reader), |
49 file_sink_(file_sink), | 54 file_sink_(file_sink), |
50 stopped_(false), | 55 stopped_(false), |
51 error_(false) { | 56 error_(false) { |
52 } | 57 } |
53 | 58 |
54 bool Initialize() { | 59 bool Initialize() { |
55 if (!file_reader_->Initialize()) { | 60 if (!file_reader_->Initialize()) { |
56 file_reader_.reset(); | 61 file_reader_.reset(); |
57 LOG(ERROR) << "can't initialize file reader"; | 62 LOG(ERROR) << "can't initialize file reader"; |
58 return false;; | 63 return false;; |
59 } | 64 } |
60 | 65 |
61 if (!file_sink_->Initialize()) { | 66 if (!file_sink_->Initialize()) { |
62 LOG(ERROR) << "can't initialize output writer"; | 67 LOG(ERROR) << "can't initialize output writer"; |
63 return false; | 68 return false; |
64 } | 69 } |
65 return true; | 70 return true; |
66 } | 71 } |
67 | 72 |
| 73 void InitializeDoneCallback() { |
| 74 } |
| 75 |
68 void StopCallback() { | 76 void StopCallback() { |
69 // If this callback is received, mark the |stopped_| flag so that we don't | 77 // If this callback is received, mark the |stopped_| flag so that we don't |
70 // feed more buffers into the decoder. | 78 // feed more buffers into the decoder. |
71 // We need to exit the current message loop because we have no more work | 79 // We need to exit the current message loop because we have no more work |
72 // to do on the message loop. This is done by calling | 80 // to do on the message loop. This is done by calling |
73 // message_loop_.Quit(). | 81 // message_loop_.Quit(). |
74 stopped_ = true; | 82 stopped_ = true; |
75 message_loop_.Quit(); | 83 message_loop_.Quit(); |
76 } | 84 } |
77 | 85 |
(...skipping 13 matching lines...) Expand all Loading... |
91 | 99 |
92 DCHECK_EQ(input_format.video_header.width, | 100 DCHECK_EQ(input_format.video_header.width, |
93 output_format.video_header.width); | 101 output_format.video_header.width); |
94 DCHECK_EQ(input_format.video_header.height, | 102 DCHECK_EQ(input_format.video_header.height, |
95 output_format.video_header.height); | 103 output_format.video_header.height); |
96 | 104 |
97 file_sink_->UpdateSize(input_format.video_header.width, | 105 file_sink_->UpdateSize(input_format.video_header.width, |
98 input_format.video_header.height); | 106 input_format.video_header.height); |
99 } | 107 } |
100 | 108 |
101 void FeedCompleteCallback(scoped_refptr<Buffer> buffer) { | 109 void FeedDoneCallback(scoped_refptr<Buffer> buffer) { |
102 // We receive this callback when the decoder has consumed an input buffer. | 110 // We receive this callback when the decoder has consumed an input buffer. |
103 // In this case, delete the previous buffer and enqueue a new one. | 111 // In this case, delete the previous buffer and enqueue a new one. |
104 // There are some conditions we don't want to enqueue, for example when | 112 // There are some conditions we don't want to enqueue, for example when |
105 // the last buffer is an end-of-stream buffer, when we have stopped, and | 113 // the last buffer is an end-of-stream buffer, when we have stopped, and |
106 // when we have received an error. | 114 // when we have received an error. |
107 bool eos = buffer->IsEndOfStream(); | 115 bool eos = buffer->IsEndOfStream(); |
108 if (!eos && !stopped_ && !error_) | 116 if (!eos && !stopped_ && !error_) |
109 FeedInputBuffer(); | 117 FeedInputBuffer(); |
110 } | 118 } |
111 | 119 |
112 void ReadCompleteCallback(OMX_BUFFERHEADERTYPE* buffer) { | 120 void DecodeDoneCallback(scoped_refptr<VideoFrame> frame) { |
113 // This callback is received when the decoder has completed a decoding | 121 // This callback is received when the decoder has completed a decoding |
114 // task and given us some output data. The buffer is owned by the decoder. | 122 // task and given us some output data. The frame is owned by the decoder. |
115 if (stopped_ || error_) | 123 if (stopped_ || error_) |
116 return; | 124 return; |
117 | 125 |
118 if (!frame_count_) | 126 if (!frame_count_) |
119 first_sample_delivered_time_ = base::TimeTicks::HighResNow(); | 127 first_sample_delivered_time_ = base::TimeTicks::HighResNow(); |
120 | 128 |
121 // If we are readding to the end, then stop. | 129 // If we are readding to the end, then stop. |
122 if (buffer == NULL) { | 130 if (frame.get() == NULL) { |
123 codec_->Stop(NewCallback(this, &TestApp::StopCallback)); | 131 engine_->Stop(NewCallback(this, &TestApp::StopCallback)); |
124 return; | 132 return; |
125 } | 133 } |
126 | 134 |
127 if (file_sink_.get()) | 135 if (file_sink_.get()) { |
128 file_sink_->BufferReady(buffer->nFilledLen, buffer->pBuffer); | 136 for (size_t i = 0; i < frame->planes(); i++) { |
| 137 int plane_size = frame->width() * frame->height(); |
| 138 if (i > 0) plane_size >>= 2; |
| 139 file_sink_->BufferReady(plane_size, frame->data(i)); |
| 140 } |
| 141 } |
129 | 142 |
130 // could OMX IL return patial sample for decoder? | 143 // could OMX IL return patial sample for decoder? |
131 frame_count_++; | 144 frame_count_++; |
132 } | 145 } |
133 | 146 |
134 void FeedInputBuffer() { | 147 void FeedInputBuffer() { |
135 uint8* data; | 148 uint8* data; |
136 int read; | 149 int read; |
137 file_reader_->Read(&data, &read); | 150 file_reader_->Read(&data, &read); |
138 codec_->Feed(new DataBuffer(data, read)); | 151 engine_->EmptyThisBuffer(new DataBuffer(data, read)); |
139 } | 152 } |
140 | 153 |
141 void Run() { | 154 void Run() { |
142 StartProfiler(); | 155 StartProfiler(); |
143 | 156 |
144 // Setup the |codec_| with the message loop of the current thread. Also | 157 // Setup the |engine_| with the message loop of the current thread. Also |
145 // setup component name, codec format and callbacks. | 158 // setup codec format and callbacks. |
146 codec_ = new OmxCodec(&message_loop_); | 159 engine_ = new OmxVideoDecodeEngine(); |
147 codec_->Setup(configurator_.get(), | 160 engine_->Initialize(&message_loop_, |
148 NewCallback(this, &TestApp::FeedCompleteCallback), | 161 av_stream_.get(), |
149 NewCallback(this, &TestApp::ReadCompleteCallback)); | 162 NewCallback(this, &TestApp::FeedDoneCallback), |
150 codec_->SetErrorCallback(NewCallback(this, &TestApp::ErrorCallback)); | 163 NewCallback(this, &TestApp::DecodeDoneCallback), |
151 codec_->SetFormatCallback(NewCallback(this, &TestApp::FormatCallback)); | 164 NewRunnableMethod(this, |
| 165 &TestApp::InitializeDoneCallback)); |
152 | 166 |
153 // Start the |codec_|. | |
154 codec_->Start(); | |
155 for (int i = 0; i < 20; ++i) | 167 for (int i = 0; i < 20; ++i) |
156 FeedInputBuffer(); | 168 FeedInputBuffer(); |
157 | 169 |
158 // Execute the message loop so that we can run tasks on it. This call | 170 // Execute the message loop so that we can run tasks on it. This call |
159 // will return when we call message_loop_.Quit(). | 171 // will return when we call message_loop_.Quit(). |
160 message_loop_.Run(); | 172 message_loop_.Run(); |
161 | 173 |
162 StopProfiler(); | 174 StopProfiler(); |
163 } | 175 } |
164 | 176 |
(...skipping 12 matching lines...) Expand all Loading... |
177 } | 189 } |
178 base::TimeDelta delay = first_sample_delivered_time_ - start_time_; | 190 base::TimeDelta delay = first_sample_delivered_time_ - start_time_; |
179 printf("\n<<< frame delivered : %d >>>", frame_count_); | 191 printf("\n<<< frame delivered : %d >>>", frame_count_); |
180 printf("\n<<< time used(ms) : %d >>>", static_cast<int>(duration_ms)); | 192 printf("\n<<< time used(ms) : %d >>>", static_cast<int>(duration_ms)); |
181 printf("\n<<< fps : %d >>>", static_cast<int>(fps)); | 193 printf("\n<<< fps : %d >>>", static_cast<int>(fps)); |
182 printf("\n<<< initial delay used(us): %d >>>", | 194 printf("\n<<< initial delay used(us): %d >>>", |
183 static_cast<int>(delay.InMicroseconds())); | 195 static_cast<int>(delay.InMicroseconds())); |
184 printf("\n"); | 196 printf("\n"); |
185 } | 197 } |
186 | 198 |
187 scoped_refptr<OmxCodec> codec_; | 199 scoped_refptr<OmxVideoDecodeEngine> engine_; |
188 MessageLoop message_loop_; | 200 MessageLoop message_loop_; |
189 scoped_ptr<OmxConfigurator> configurator_; | 201 scoped_ptr<AVStream> av_stream_; |
190 scoped_ptr<FileReader> file_reader_; | 202 scoped_ptr<FileReader> file_reader_; |
191 scoped_ptr<FileSink> file_sink_; | 203 scoped_ptr<FileSink> file_sink_; |
192 | 204 |
193 // Internal states for execution. | 205 // Internal states for execution. |
194 bool stopped_; | 206 bool stopped_; |
195 bool error_; | 207 bool error_; |
196 | 208 |
197 // Counters for performance. | 209 // Counters for performance. |
198 base::TimeTicks start_time_; | 210 base::TimeTicks start_time_; |
199 base::TimeTicks first_sample_delivered_time_; | 211 base::TimeTicks first_sample_delivered_time_; |
200 int frame_count_; | 212 int frame_count_; |
201 }; | 213 }; |
202 | 214 |
203 static std::string GetStringSwitch(const char* name) { | 215 static std::string GetStringSwitch(const char* name) { |
204 return CommandLine::ForCurrentProcess()->GetSwitchValueASCII(name); | 216 return CommandLine::ForCurrentProcess()->GetSwitchValueASCII(name); |
205 } | 217 } |
206 | 218 |
207 static bool HasSwitch(const char* name) { | 219 static bool HasSwitch(const char* name) { |
208 return CommandLine::ForCurrentProcess()->HasSwitch(name); | 220 return CommandLine::ForCurrentProcess()->HasSwitch(name); |
209 } | 221 } |
210 | 222 |
211 static int GetIntSwitch(const char* name) { | 223 static int GetIntSwitch(const char* name) { |
212 if (HasSwitch(name)) | 224 if (HasSwitch(name)) |
213 return StringToInt(GetStringSwitch(name)); | 225 return StringToInt(GetStringSwitch(name)); |
214 return 0; | 226 return 0; |
215 } | 227 } |
216 | 228 |
217 static bool PrepareDecodeFormats(OmxConfigurator::MediaFormat* input, | 229 static bool PrepareDecodeFormats(AVStream *av_stream) { |
218 OmxConfigurator::MediaFormat* output) { | |
219 std::string codec = GetStringSwitch("codec"); | 230 std::string codec = GetStringSwitch("codec"); |
220 input->codec = OmxConfigurator::kCodecNone; | 231 av_stream->codec->codec_id = CODEC_ID_NONE; |
221 if (codec == "h264") { | 232 if (codec == "h264") { |
222 input->codec = OmxConfigurator::kCodecH264; | 233 av_stream->codec->codec_id = CODEC_ID_H264; |
223 } else if (codec == "mpeg4") { | 234 } else if (codec == "mpeg4") { |
224 input->codec = OmxConfigurator::kCodecMpeg4; | 235 av_stream->codec->codec_id = CODEC_ID_MPEG4; |
225 } else if (codec == "h263") { | 236 } else if (codec == "h263") { |
226 input->codec = OmxConfigurator::kCodecH263; | 237 av_stream->codec->codec_id = CODEC_ID_H263; |
227 } else if (codec == "vc1") { | 238 } else if (codec == "vc1") { |
228 input->codec = OmxConfigurator::kCodecVc1; | 239 av_stream->codec->codec_id = CODEC_ID_VC1; |
229 } else { | 240 } else { |
230 LOG(ERROR) << "Unknown codec."; | 241 LOG(ERROR) << "Unknown codec."; |
231 return false; | 242 return false; |
232 } | 243 } |
233 output->codec = OmxConfigurator::kCodecRaw; | |
234 return true; | 244 return true; |
235 } | 245 } |
236 | 246 |
237 static bool PrepareEncodeFormats(OmxConfigurator::MediaFormat* input, | 247 static bool PrepareEncodeFormats(AVStream *av_stream) { |
238 OmxConfigurator::MediaFormat* output) { | 248 av_stream->codec->width = GetIntSwitch("width"); |
239 input->codec = OmxConfigurator::kCodecRaw; | 249 av_stream->codec->height = GetIntSwitch("height"); |
240 input->video_header.width = GetIntSwitch("width"); | 250 av_stream->avg_frame_rate.num = GetIntSwitch("framerate"); |
241 input->video_header.height = GetIntSwitch("height"); | 251 av_stream->avg_frame_rate.den = 1; |
242 input->video_header.frame_rate = GetIntSwitch("framerate"); | 252 |
243 // TODO(jiesun): make other format available. | 253 std::string codec = GetStringSwitch("codec"); |
244 output->codec = OmxConfigurator::kCodecMpeg4; | 254 av_stream->codec->codec_id = CODEC_ID_NONE; |
245 output->video_header.width = GetIntSwitch("width"); | 255 if (codec == "h264") { |
246 output->video_header.height = GetIntSwitch("height"); | 256 av_stream->codec->codec_id = CODEC_ID_H264; |
247 output->video_header.frame_rate = GetIntSwitch("framerate"); | 257 } else if (codec == "mpeg4") { |
| 258 av_stream->codec->codec_id = CODEC_ID_MPEG4; |
| 259 } else if (codec == "h263") { |
| 260 av_stream->codec->codec_id = CODEC_ID_H263; |
| 261 } else if (codec == "vc1") { |
| 262 av_stream->codec->codec_id = CODEC_ID_VC1; |
| 263 } else { |
| 264 LOG(ERROR) << "Unknown codec."; |
| 265 return false; |
| 266 } |
248 // TODO(jiesun): assume constant bitrate now. | 267 // TODO(jiesun): assume constant bitrate now. |
249 output->video_header.bit_rate = GetIntSwitch("bitrate"); | 268 av_stream->codec->bit_rate = GetIntSwitch("bitrate"); |
250 // TODO(jiesun): one I frame per second now. make it configurable. | 269 |
251 output->video_header.i_dist = output->video_header.frame_rate; | 270 // TODO(wjia): add more configurations needed by encoder |
252 // TODO(jiesun): disable B frame now. does they support it? | |
253 output->video_header.p_dist = 0; | |
254 return true; | 271 return true; |
255 } | 272 } |
256 | 273 |
257 static bool InitFFmpeg() { | 274 static bool InitFFmpeg() { |
258 if (!media::InitializeMediaLibrary(FilePath())) | 275 if (!media::InitializeMediaLibrary(FilePath())) |
259 return false; | 276 return false; |
260 avcodec_init(); | 277 avcodec_init(); |
261 av_register_all(); | 278 av_register_all(); |
262 av_register_protocol(&kFFmpegFileProtocol); | 279 av_register_protocol(&kFFmpegFileProtocol); |
263 return true; | 280 return true; |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
321 return false; | 338 return false; |
322 } | 339 } |
323 | 340 |
324 // If FFmpeg should be used for demuxing load the library here and do | 341 // If FFmpeg should be used for demuxing load the library here and do |
325 // the initialization. | 342 // the initialization. |
326 if (use_ffmpeg && !InitFFmpeg()) { | 343 if (use_ffmpeg && !InitFFmpeg()) { |
327 LOG(ERROR) << "Unable to initialize the media library."; | 344 LOG(ERROR) << "Unable to initialize the media library."; |
328 return -1; | 345 return -1; |
329 } | 346 } |
330 | 347 |
331 // Set the media formats for I/O. | 348 // Create AVStream |
332 OmxConfigurator::MediaFormat input, output; | 349 AVStream *av_stream = new AVStream; |
333 memset(&input, 0, sizeof(input)); | 350 AVCodecContext *av_codec_context = new AVCodecContext; |
334 memset(&output, 0, sizeof(output)); | 351 memset(av_stream, 0, sizeof(AVStream)); |
| 352 memset(av_codec_context, 0, sizeof(AVCodecContext)); |
| 353 scoped_ptr<AVCodecContext> av_codec_context_deleter(av_codec_context); |
| 354 av_stream->codec = av_codec_context; |
| 355 av_codec_context->width = 320; |
| 356 av_codec_context->height = 240; |
335 if (encoder) | 357 if (encoder) |
336 PrepareEncodeFormats(&input, &output); | 358 PrepareEncodeFormats(av_stream); |
337 else | 359 else |
338 PrepareDecodeFormats(&input, &output); | 360 PrepareDecodeFormats(av_stream); |
339 | 361 |
340 // Creates the FileReader to read input file. | 362 // Creates the FileReader to read input file. |
341 FileReader* file_reader; | 363 FileReader* file_reader; |
342 if (encoder) { | 364 if (encoder) { |
343 file_reader = new YuvFileReader( | 365 file_reader = new YuvFileReader( |
344 input_filename.c_str(), input.video_header.width, | 366 input_filename.c_str(), av_stream->codec->width, |
345 input.video_header.height, loop_count, enable_csc); | 367 av_stream->codec->height, loop_count, enable_csc); |
346 } else if (use_ffmpeg) { | 368 } else if (use_ffmpeg) { |
347 // Use ffmepg for reading. | 369 // Use ffmepg for reading. |
348 file_reader = new FFmpegFileReader(input_filename.c_str()); | 370 file_reader = new FFmpegFileReader(input_filename.c_str()); |
349 } else if (EndsWith(input_filename, ".264", false)) { | 371 } else if (EndsWith(input_filename, ".264", false)) { |
350 file_reader = new H264FileReader(input_filename.c_str()); | 372 file_reader = new H264FileReader(input_filename.c_str()); |
351 } else { | 373 } else { |
352 // Creates a reader that reads in blocks of 32KB. | 374 // Creates a reader that reads in blocks of 32KB. |
353 const int kReadSize = 32768; | 375 const int kReadSize = 32768; |
354 file_reader = new BlockFileReader(input_filename.c_str(), kReadSize); | 376 file_reader = new BlockFileReader(input_filename.c_str(), kReadSize); |
355 } | 377 } |
356 | 378 |
357 // Create the configurator. | |
358 OmxConfigurator* configurator; | |
359 if (encoder) | |
360 configurator = new OmxEncoderConfigurator(input, output); | |
361 else | |
362 configurator = new OmxDecoderConfigurator(input, output); | |
363 | |
364 // Create a file sink. | 379 // Create a file sink. |
365 FileSink* file_sink = new FileSink(output_filename, copy, enable_csc); | 380 FileSink* file_sink = new FileSink(output_filename, copy, enable_csc); |
366 | 381 |
367 // Create a test app object and initialize it. | 382 // Create a test app object and initialize it. |
368 TestApp test(configurator, file_sink, file_reader); | 383 scoped_refptr<TestApp> test = new TestApp(av_stream, file_sink, file_reader); |
369 if (!test.Initialize()) { | 384 if (!test->Initialize()) { |
370 LOG(ERROR) << "can't initialize this application"; | 385 LOG(ERROR) << "can't initialize this application"; |
371 return -1; | 386 return -1; |
372 } | 387 } |
373 | 388 |
374 // This will run the decoder until EOS is reached or an error | 389 // This will run the decoder until EOS is reached or an error |
375 // is encountered. | 390 // is encountered. |
376 test.Run(); | 391 test->Run(); |
377 return 0; | 392 return 0; |
378 } | 393 } |
OLD | NEW |