| OLD | NEW |
| (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(¬e); |
| 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 |
| OLD | NEW |