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

Side by Side Diff: content/common/gpu/omx_video_decode_accelerator_unittest.cc

Issue 7088021: OmxVideoDecodeAcceleratorTest is born! (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 9 years, 6 months 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
(Empty)
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.
4 //
5 // The bulk of this file is support code; sorry about that. Here's an overview
6 // to hopefully help readers of this code:
7 // - RenderingHelper is charged with interacting with X11, EGL, and GLES2.
8 // - ClientState is an enum for the state of the decode client used by the test.
9 // - ClientStateNotification is a barrier abstraction that allows the test code
10 // to be written sequentially and wait for the decode client to see certain
11 // state transitions.
12 // - EglRenderingVDAClient is a VideoDecodeAccelerator::Client implementation
13 // - Finally actual TEST cases are at the bottom of this file, using the above
14 // infrastructure.
15
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19
20 // Include gtest.h out of order because <X11/X.h> #define's Bool & None, which
21 // gtest uses as struct names (inside a namespace). This means that
22 // #include'ing gtest after anything that pulls in X.h fails to compile.
23 #include "testing/gtest/include/gtest/gtest.h"
24
25 #include "base/at_exit.h"
26 #include "base/file_util.h"
27 #include "base/stl_util-inl.h"
28 #include "base/stringize_macros.h"
29 #include "base/synchronization/condition_variable.h"
30 #include "base/synchronization/lock.h"
31 #include "base/synchronization/waitable_event.h"
32 #include "base/threading/thread.h"
33 #include "content/common/gpu/omx_video_decode_accelerator.h"
34 #include "third_party/angle/include/EGL/egl.h"
35 #include "third_party/angle/include/GLES2/gl2.h"
36
37 #if !defined(OS_CHROMEOS) || !defined(ARCH_CPU_ARMEL)
38 #error This test (and OmxVideoDecodeAccelerator) are only supported on cros/ARM!
39 #endif
40
41 using media::VideoDecodeAccelerator;
42
43 namespace {
44
45 // General-purpose constants for this test.
46 enum {
47 kFrameWidth = 320,
48 kFrameHeight = 240,
49 };
50
51 // Helper for managing X11, EGL, and GLES2 resources. Because GL state is
52 // thread-specific, all the methods of this class (except for ctor/dtor) CHECK
53 // for being run on a single thread.
54 //
55 // TODO(fischman): consider moving this into media/ if we can de-dup some of the
56 // code that ends up getting copy/pasted all over the place (esp. the GL setup
57 // code).
58 class RenderingHelper {
59 public:
60 explicit RenderingHelper();
61 ~RenderingHelper();
62
63 // Initialize all structures to prepare to render to a window of the specified
64 // dimensions. CHECK-fails if any initialization step fails. After this
65 // returns, texture creation and rendering (swaps) can be requested.
66 // This method can be called multiple times, in which case all
67 // previously-acquired resources and initializations are discarded.
68 void Initialize(int width, int height, base::WaitableEvent* done);
69
70 // Undo the effects of Initialize() and signal |*done|.
71 void UnInitialize(base::WaitableEvent* done);
72
73 // Return a newly-created GLES2 texture id.
74 GLuint CreateTexture();
75
76 // Render |texture_id| to the screen.
77 void RenderTexture(GLuint texture_id);
78
79 EGLDisplay egl_display() { return egl_display_; }
80 EGLContext egl_context() { return egl_context_; }
81
82 private:
83 int width_;
84 int height_;
85 Display* x_display_;
86 Window x_window_;
87 EGLDisplay egl_display_;
88 EGLContext egl_context_;
89 EGLSurface egl_surface_;
90 // Since GL carries per-thread state, we ensure all operations are carried out
91 // on the same thread by remembering where we were Initialized.
92 MessageLoop* message_loop_;
93 };
94
95 RenderingHelper::RenderingHelper() {
96 memset(this, 0, sizeof(this));
97 }
98
99 RenderingHelper::~RenderingHelper() {
100 CHECK_EQ(width_, 0) << "Must call UnInitialize before dtor.";
101 }
102
103 // Helper for Shader creation.
104 static void CreateShader(
105 GLuint program, GLenum type, const char* source, int size) {
106 GLuint shader = glCreateShader(type);
107 glShaderSource(shader, 1, &source, &size);
108 glCompileShader(shader);
109 int result = GL_FALSE;
110 glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
111 if (!result) {
112 char log[4096];
113 glGetShaderInfoLog(shader, arraysize(log), NULL, log);
114 LOG(FATAL) << log;
115 }
116 glAttachShader(program, shader);
117 glDeleteShader(shader);
118 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
119 }
120
121 void RenderingHelper::Initialize(
122 int width, int height, base::WaitableEvent* done) {
123 // Use width_ != 0 as a proxy for the class having already been
124 // Initialize()'d, and UnInitialize() before continuing.
125 if (width_) {
126 base::WaitableEvent done(false, false);
127 UnInitialize(&done);
128 done.Wait();
129 }
130
131 CHECK_GT(width, 0);
132 CHECK_GT(height, 0);
133 width_ = width;
134 height_ = height;
135 message_loop_ = MessageLoop::current();
136
137 // X11 initialization.
138 CHECK(x_display_ = XOpenDisplay(NULL));
139 int depth = DefaultDepth(x_display_, DefaultScreen(x_display_));
140 XSetWindowAttributes window_attributes;
141 window_attributes.background_pixel =
142 BlackPixel(x_display_, DefaultScreen(x_display_));
143 window_attributes.override_redirect = true;
144 x_window_ = XCreateWindow(
145 x_display_, DefaultRootWindow(x_display_),
146 100, 100, /* x/y of top-left corner */
147 width_, height_,
148 0 /* border width */,
149 depth, CopyFromParent /* class */, CopyFromParent /* visual */,
150 (CWBackPixel | CWOverrideRedirect), &window_attributes);
151 XStoreName(x_display_, x_window_, "OmxVideoDecodeAcceleratorTest");
152 XSelectInput(x_display_, x_window_, ExposureMask);
153 XMapWindow(x_display_, x_window_);
154
155 // EGL initialization.
156 egl_display_ = eglGetDisplay(x_display_);
157 EGLint major;
158 EGLint minor;
159 CHECK(eglInitialize(egl_display_, &major, &minor)) << eglGetError();
160 EGLint rgba8888[] = {
161 EGL_RED_SIZE, 8,
162 EGL_GREEN_SIZE, 8,
163 EGL_BLUE_SIZE, 8,
164 EGL_ALPHA_SIZE, 8,
165 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
166 EGL_NONE,
167 };
168
169 EGLConfig egl_config;
170 int num_configs;
171 CHECK(eglChooseConfig(egl_display_, rgba8888, &egl_config, 1, &num_configs))
172 << eglGetError();
173 CHECK_GE(num_configs, 1);
174 egl_surface_ =
175 eglCreateWindowSurface(egl_display_, egl_config, x_window_, NULL);
176 CHECK_NE(egl_surface_, EGL_NO_SURFACE);
177 EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
178 egl_context_ = eglCreateContext(
179 egl_display_, egl_config, EGL_NO_CONTEXT, context_attribs);
180 CHECK(eglMakeCurrent(egl_display_, egl_surface_, egl_surface_, egl_context_))
181 << eglGetError();
182
183 // GLES2 initialization. Note: This is pretty much copy/pasted from
184 // media/tools/player_x11/gles_video_renderer.cc, with some simplification
185 // applied.
186 static const float kVertices[] =
187 { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
188 static const float kTextureCoordsEgl[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
189 static const char kVertexShader[] = STRINGIZE(
190 varying vec2 interp_tc;
191 attribute vec4 in_pos;
192 attribute vec2 in_tc;
193 void main() {
194 interp_tc = in_tc;
195 gl_Position = in_pos;
196 }
197 );
198 static const char kFragmentShaderEgl[] = STRINGIZE(
199 precision mediump float;
200 varying vec2 interp_tc;
201 uniform sampler2D tex;
202 void main() {
203 gl_FragColor = texture2D(tex, interp_tc);
204 }
205 );
206 GLuint program = glCreateProgram();
207 CreateShader(program, GL_VERTEX_SHADER, kVertexShader, sizeof(kVertexShader));
208 CreateShader(program, GL_FRAGMENT_SHADER,
209 kFragmentShaderEgl, sizeof(kFragmentShaderEgl));
210 glLinkProgram(program);
211 int result = GL_FALSE;
212 glGetProgramiv(program, GL_LINK_STATUS, &result);
213 if (!result) {
214 char log[4096];
215 glGetShaderInfoLog(program, arraysize(log), NULL, log);
216 LOG(FATAL) << log;
217 }
218 glUseProgram(program);
219 glDeleteProgram(program);
220
221 glUniform1i(glGetUniformLocation(program, "tex"), 0);
222 int pos_location = glGetAttribLocation(program, "in_pos");
223 glEnableVertexAttribArray(pos_location);
224 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
225 int tc_location = glGetAttribLocation(program, "in_tc");
226 glEnableVertexAttribArray(tc_location);
227 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0,
228 kTextureCoordsEgl);
229 done->Signal();
230 }
231
232 void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
233 CHECK_EQ(MessageLoop::current(), message_loop_);
234 // Destroy resources acquired in Initialize, in reverse-acquisition order.
235 CHECK(eglMakeCurrent(
236 egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
237 CHECK(eglDestroyContext(egl_display_, egl_context_));
238 CHECK(eglDestroySurface(egl_display_, egl_surface_));
239 CHECK(eglTerminate(egl_display_));
240 CHECK(XUnmapWindow(x_display_, x_window_));
241 CHECK(XDestroyWindow(x_display_, x_window_));
242 // Mimic newly-created object.
243 memset(this, 0, sizeof(this));
244 done->Signal();
245 }
246
247 GLuint RenderingHelper::CreateTexture() {
248 CHECK_EQ(MessageLoop::current(), message_loop_);
249 GLuint texture_id;
250 glGenTextures(1, &texture_id);
251 glBindTexture(GL_TEXTURE_2D, texture_id);
252 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA,
253 GL_UNSIGNED_BYTE, NULL);
254 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
255 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
256 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
257 return texture_id;
258 }
259
260 void RenderingHelper::RenderTexture(GLuint texture_id) {
261 CHECK_EQ(MessageLoop::current(), message_loop_);
262 glActiveTexture(GL_TEXTURE0);
263 glBindTexture(GL_TEXTURE_2D, texture_id);
264 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
265 DCHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
266 eglSwapBuffers(egl_display_, egl_surface_);
267 DCHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
268 }
269
270 // State of the EglRenderingVDAClient below.
271 enum ClientState {
272 CS_CREATED,
273 CS_DECODER_SET,
274 CS_INITIALIZED,
275 CS_FLUSHED,
276 CS_DONE,
277 CS_ABORTED,
278 CS_ERROR,
279 CS_DESTROYED,
280 };
281
282 // Helper class allowing one thread to wait on a notification from another.
283 // If notifications come in faster than they are Wait()'d for, they are
284 // accumulated (so exactly as many Wait() calls will unblock as Notify() calls
285 // were made, regardless of order).
286 class ClientStateNotification {
287 public:
288 ClientStateNotification();
289 ~ClientStateNotification();
290
291 // Used to notify a single waiter of a ClientState.
292 void Notify(ClientState state);
293 // Used by waiters to wait for the next ClientState Notification.
294 ClientState Wait();
295 private:
296 base::Lock lock_;
297 base::ConditionVariable cv_;
298 std::queue<ClientState> pending_states_for_notification_;
299 };
300
301 ClientStateNotification::ClientStateNotification() : cv_(&lock_) {}
302 ClientStateNotification::~ClientStateNotification() {}
303
304 void ClientStateNotification::Notify(ClientState state) {
305 base::AutoLock auto_lock(lock_);
306 pending_states_for_notification_.push(state);
307 cv_.Signal();
308 }
309
310 ClientState ClientStateNotification::Wait() {
311 base::AutoLock auto_lock(lock_);
312 while (pending_states_for_notification_.empty())
313 cv_.Wait();
314 ClientState ret = pending_states_for_notification_.front();
315 pending_states_for_notification_.pop();
316 return ret;
317 }
318
319 // Client that can accept callbacks from a VideoDecodeAccelerator and is used by
320 // the TESTs below.
321 class EglRenderingVDAClient : public VideoDecodeAccelerator::Client {
322 public:
323 // Doesn't take ownership of |note|, which must outlive |*this|.
324 EglRenderingVDAClient(ClientStateNotification* note);
325 virtual ~EglRenderingVDAClient();
326
327 // VideoDecodeAccelerator::Client implementation.
328 // The heart of the Client.
329 virtual void ProvidePictureBuffers(
330 uint32 requested_num_of_buffers,
331 const gfx::Size& dimensions,
332 VideoDecodeAccelerator::MemoryType type);
333 virtual void DismissPictureBuffer(int32 picture_buffer_id);
334 virtual void PictureReady(const media::Picture& picture);
335 // Simple state changes.
336 virtual void NotifyInitializeDone();
337 virtual void NotifyEndOfStream();
338 virtual void NotifyEndOfBitstreamBuffer(int32 bitstream_buffer_id);
339 virtual void NotifyFlushDone();
340 virtual void NotifyAbortDone();
341 virtual void NotifyError(VideoDecodeAccelerator::Error error);
342
343 // Doesn't take ownership of |decoder|, which must outlive |*this|.
344 void SetDecoder(VideoDecodeAccelerator* decoder);
345
346 // Simple getters for inspecting the state of the Client.
347 ClientState state() { return state_; }
348 VideoDecodeAccelerator::Error error() { return error_; }
349 int num_done_bitstream_buffers() { return num_done_bitstream_buffers_; }
350 int num_decoded_frames() { return num_decoded_frames_; }
351 MessageLoop* message_loop() { return thread_.message_loop(); }
352 EGLDisplay egl_display() { return rendering_helper_.egl_display(); }
353 EGLContext egl_context() { return rendering_helper_.egl_context(); }
354
355 private:
356 void SetState(ClientState new_state) {
357 note_->Notify(new_state);
358 state_ = new_state;
359 }
360
361 VideoDecodeAccelerator* decoder_;
362 ClientStateNotification* note_;
363 ClientState state_;
364 VideoDecodeAccelerator::Error error_;
365 int num_decoded_frames_;
366 int num_done_bitstream_buffers_;
367 std::map<int, media::GLESBuffer*> picture_buffers_by_id_;
368 // Required for Thread to work. Not used otherwise.
369 base::ShadowingAtExitManager at_exit_manager_;
370 base::Thread thread_;
371 RenderingHelper rendering_helper_;
372 };
373
374 EglRenderingVDAClient::EglRenderingVDAClient(ClientStateNotification* note)
375 : decoder_(NULL), note_(note), state_(CS_CREATED),
376 error_(VideoDecodeAccelerator::VIDEODECODERERROR_NONE),
377 num_decoded_frames_(0), num_done_bitstream_buffers_(0),
378 thread_("EglRenderingVDAClientThread") {
379 CHECK(thread_.Start());
380 base::WaitableEvent done(false, false);
381 message_loop()->PostTask(
382 FROM_HERE,
383 base::Bind(&RenderingHelper::Initialize,
384 base::Unretained(&rendering_helper_),
385 static_cast<int>(kFrameWidth), static_cast<int>(kFrameHeight),
386 &done));
387 done.Wait();
388 }
389
390 EglRenderingVDAClient::~EglRenderingVDAClient() {
391 base::WaitableEvent done(false, false);
392 message_loop()->PostTask(
393 FROM_HERE,
394 base::Bind(&RenderingHelper::UnInitialize,
395 base::Unretained(&rendering_helper_),
396 &done));
397 done.Wait();
398 thread_.Stop();
399 STLDeleteValues(&picture_buffers_by_id_);
400 SetState(CS_DESTROYED);
401 }
402
403 void EglRenderingVDAClient::ProvidePictureBuffers(
404 uint32 requested_num_of_buffers,
405 const gfx::Size& dimensions,
406 VideoDecodeAccelerator::MemoryType type) {
407 CHECK_EQ(message_loop(), MessageLoop::current());
408 CHECK_EQ(type, VideoDecodeAccelerator::PICTUREBUFFER_MEMORYTYPE_GL_TEXTURE);
409 std::vector<media::GLESBuffer> buffers;
410 CHECK_EQ(dimensions.width(), kFrameWidth);
411 CHECK_EQ(dimensions.height(), kFrameHeight);
412
413 for (uint32 i = 0; i < requested_num_of_buffers; ++i) {
414 uint32 id = picture_buffers_by_id_.size();
415 GLuint texture_id = rendering_helper_.CreateTexture();
416 // TODO(fischman): context_id is always 0. Can it be removed from the API?
417 // (since it's always inferrable from context).
418 media::GLESBuffer* buffer =
419 new media::GLESBuffer(id, dimensions, texture_id, 0 /* context_id */);
420 CHECK(picture_buffers_by_id_.insert(std::make_pair(id, buffer)).second);
421 buffers.push_back(*buffer);
422 }
423 decoder_->AssignGLESBuffers(buffers);
424 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
425 CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
426 }
427
428 void EglRenderingVDAClient::DismissPictureBuffer(int32 picture_buffer_id) {
429 CHECK_EQ(message_loop(), MessageLoop::current());
430 delete picture_buffers_by_id_[picture_buffer_id];
431 CHECK_EQ(1U, picture_buffers_by_id_.erase(picture_buffer_id));
432 }
433
434 void EglRenderingVDAClient::PictureReady(const media::Picture& picture) {
435 CHECK_EQ(message_loop(), MessageLoop::current());
436
437 ++num_decoded_frames_;
438
439 media::GLESBuffer* gles_buffer =
440 picture_buffers_by_id_[picture.picture_buffer_id()];
441 CHECK(gles_buffer);
442 rendering_helper_.RenderTexture(gles_buffer->texture_id());
443
444 decoder_->ReusePictureBuffer(picture.picture_buffer_id());
445 }
446
447 void EglRenderingVDAClient::NotifyInitializeDone() {
448 CHECK_EQ(message_loop(), MessageLoop::current());
449 SetState(CS_INITIALIZED);
450 }
451
452 void EglRenderingVDAClient::NotifyEndOfStream() {
453 CHECK_EQ(message_loop(), MessageLoop::current());
454 SetState(CS_DONE);
455 }
456
457 void EglRenderingVDAClient::NotifyEndOfBitstreamBuffer(
458 int32 bitstream_buffer_id) {
459 CHECK_EQ(message_loop(), MessageLoop::current());
460 ++num_done_bitstream_buffers_;
461
462 // TODO(fischman): this is hokey! It should be possible to call Flush() from
463 // outside a callback.
464 if (num_done_bitstream_buffers_ == 1)
465 decoder_->Flush();
466 }
467
468 void EglRenderingVDAClient::NotifyFlushDone() {
469 CHECK_EQ(message_loop(), MessageLoop::current());
470 SetState(CS_FLUSHED);
471 }
472
473 void EglRenderingVDAClient::NotifyAbortDone() {
474 CHECK_EQ(message_loop(), MessageLoop::current());
475 SetState(CS_ABORTED);
476 }
477
478 void EglRenderingVDAClient::NotifyError(VideoDecodeAccelerator::Error error) {
479 CHECK_EQ(message_loop(), MessageLoop::current());
480 SetState(CS_ERROR);
481 error_ = error;
482 }
483
484 void EglRenderingVDAClient::SetDecoder(VideoDecodeAccelerator* decoder) {
485 decoder_ = decoder;
486 SetState(CS_DECODER_SET);
487 }
488
489 // Test the most straightforward case possible: data is decoded from a single
490 // chunk and rendered to the screen.
491 TEST(OmxVideoDecodeAcceleratorTest, TestSimpleDecode) {
492 logging::SetMinLogLevel(-1);
493 ClientStateNotification note;
494 ClientState state;
495 EglRenderingVDAClient client(&note);
496 OmxVideoDecodeAccelerator decoder(&client, client.message_loop());
497 client.SetDecoder(&decoder);
498 decoder.SetEglState(client.egl_display(), client.egl_context());
499 ASSERT_EQ((state = note.Wait()), CS_DECODER_SET);
500
501 int32 config_array[] = {
502 media::VIDEOATTRIBUTEKEY_BITSTREAMFORMAT_FOURCC,
503 media::VIDEOCODECFOURCC_H264,
504 media::VIDEOATTRIBUTEKEY_BITSTREAMFORMAT_WIDTH, kFrameWidth,
505 media::VIDEOATTRIBUTEKEY_BITSTREAMFORMAT_HEIGHT, kFrameHeight,
506 media::VIDEOATTRIBUTEKEY_VIDEOCOLORFORMAT, media::VIDEOCOLORFORMAT_RGBA,
507 media::VIDEOATTRIBUTEKEY_TERMINATOR
508 };
509 std::vector<uint32> config(
510 config_array, config_array + arraysize(config_array));
511 CHECK(decoder.Initialize(config));
512 ASSERT_EQ((state = note.Wait()), CS_INITIALIZED);
513
514 // Set up buffers to pass to Decode.
515 char const* data_filename = "test-25fps.h264";
516 int data_fd = open(data_filename, O_RDONLY);
517 CHECK_GE(data_fd, 0);
518 int64 file_size = -1;
519 CHECK(file_util::GetFileSize(FilePath(std::string(data_filename)),
520 &file_size));
521 base::SharedMemory data_shm(base::FileDescriptor(data_fd, false), true);
522 CHECK(data_shm.Map(file_size));
523 media::BitstreamBuffer bitstream_buffer(0, data_shm.handle(), file_size);
524
525 decoder.Decode(bitstream_buffer);
526 ASSERT_EQ((state = note.Wait()), CS_FLUSHED);
527
528 EXPECT_EQ(client.num_decoded_frames(), 25 /* fps */ * 10 /* seconds */);
529 EXPECT_EQ(client.num_done_bitstream_buffers(), 1);
530 }
531
532 // TODO(fischman, vrk): add more tests! In particular:
533 // - Test that breaking up the data buffers into many Decode() calls works.
534 // - Test decode speed. Ideally we can beat 60fps esp on simple test.mp4.
535 // - Test alternate configurations
536 // - Test failure conditions.
537 // - Test multiple concurrent decoders going at once.
538 // - Test frame size changes mid-stream
539
540 } // namespace
OLDNEW
« no previous file with comments | « content/common/gpu/omx_video_decode_accelerator.cc ('k') | content/common/gpu/testdata/test-25fps.h264 » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698