| 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 <fcntl.h> | |
| 17 #include <math.h> | |
| 18 #include <sys/stat.h> | |
| 19 #include <sys/types.h> | |
| 20 | |
| 21 // Include gtest.h out of order because <X11/X.h> #define's Bool & None, which | |
| 22 // gtest uses as struct names (inside a namespace). This means that | |
| 23 // #include'ing gtest after anything that pulls in X.h fails to compile. | |
| 24 // This is http://code.google.com/p/googletest/issues/detail?id=371 | |
| 25 #include "testing/gtest/include/gtest/gtest.h" | |
| 26 | |
| 27 #include "base/at_exit.h" | |
| 28 #include "base/bind.h" | |
| 29 #include "base/command_line.h" | |
| 30 #include "base/file_util.h" | |
| 31 #include "base/stl_util.h" | |
| 32 #include "base/string_number_conversions.h" | |
| 33 #include "base/string_split.h" | |
| 34 #include "base/stringize_macros.h" | |
| 35 #include "base/synchronization/condition_variable.h" | |
| 36 #include "base/synchronization/lock.h" | |
| 37 #include "base/synchronization/waitable_event.h" | |
| 38 #include "base/threading/thread.h" | |
| 39 #include "content/common/gpu/media/omx_video_decode_accelerator.h" | |
| 40 #include "third_party/angle/include/EGL/egl.h" | |
| 41 #include "third_party/angle/include/GLES2/gl2.h" | |
| 42 | |
| 43 #if !defined(OS_CHROMEOS) || !defined(ARCH_CPU_ARMEL) | |
| 44 #error This test (and OmxVideoDecodeAccelerator) are only supported on cros/ARM! | |
| 45 #endif | |
| 46 | |
| 47 using media::VideoDecodeAccelerator; | |
| 48 | |
| 49 namespace { | |
| 50 | |
| 51 // Values optionally filled in from flags; see main() below. | |
| 52 // The syntax of this variable is: | |
| 53 // filename:width:height:numframes:numNALUs:minFPSwithRender:minFPSnoRender | |
| 54 // where only the first field is required. Value details: | |
| 55 // - |filename| must be an h264 Annex B (NAL) stream. | |
| 56 // - |width| and |height| are in pixels. | |
| 57 // - |numframes| is the number of picture frames in the file. | |
| 58 // - |numNALUs| is the number of NAL units in the stream. | |
| 59 // - |minFPSwithRender| and |minFPSnoRender| are minimum frames/second speeds | |
| 60 // expected to be achieved with and without rendering to the screen, resp. | |
| 61 // (the latter tests just decode speed). | |
| 62 // - |profile| is the media::H264Profile set during Initialization. | |
| 63 // An empty value for a numeric field means "ignore". | |
| 64 const char* test_video_data = "test-25fps.h264:320:240:250:258:50:175:1"; | |
| 65 | |
| 66 // Parse |data| into its constituent parts and set the various output fields | |
| 67 // accordingly. CHECK-fails on unexpected or missing required data. | |
| 68 // Unspecified optional fields are set to -1. | |
| 69 void ParseTestVideoData(std::string data, | |
| 70 std::string* file_name, | |
| 71 int* width, int* height, | |
| 72 int* num_frames, | |
| 73 int* num_NALUs, | |
| 74 int* min_fps_render, | |
| 75 int* min_fps_no_render, | |
| 76 int* profile) { | |
| 77 std::vector<std::string> elements; | |
| 78 base::SplitString(data, ':', &elements); | |
| 79 CHECK_GE(elements.size(), 1U) << data; | |
| 80 CHECK_LE(elements.size(), 8U) << data; | |
| 81 *file_name = elements[0]; | |
| 82 *width = *height = *num_frames = *num_NALUs = -1; | |
| 83 *min_fps_render = *min_fps_no_render = -1; | |
| 84 *profile = -1; | |
| 85 if (!elements[1].empty()) | |
| 86 CHECK(base::StringToInt(elements[1], width)); | |
| 87 if (!elements[2].empty()) | |
| 88 CHECK(base::StringToInt(elements[2], height)); | |
| 89 if (!elements[3].empty()) | |
| 90 CHECK(base::StringToInt(elements[3], num_frames)); | |
| 91 if (!elements[4].empty()) | |
| 92 CHECK(base::StringToInt(elements[4], num_NALUs)); | |
| 93 if (!elements[5].empty()) | |
| 94 CHECK(base::StringToInt(elements[5], min_fps_render)); | |
| 95 if (!elements[6].empty()) | |
| 96 CHECK(base::StringToInt(elements[6], min_fps_no_render)); | |
| 97 if (!elements[7].empty()) | |
| 98 CHECK(base::StringToInt(elements[7], profile)); | |
| 99 } | |
| 100 | |
| 101 | |
| 102 // Helper for managing X11, EGL, and GLES2 resources. Xlib is not thread-safe, | |
| 103 // and GL state is thread-specific, so all the methods of this class (except for | |
| 104 // ctor/dtor) ensure they're being run on a single thread. | |
| 105 // | |
| 106 // TODO(fischman): consider moving this into media/ if we can de-dup some of the | |
| 107 // code that ends up getting copy/pasted all over the place (esp. the GL setup | |
| 108 // code). | |
| 109 class RenderingHelper { | |
| 110 public: | |
| 111 explicit RenderingHelper(); | |
| 112 ~RenderingHelper(); | |
| 113 | |
| 114 // Initialize all structures to prepare to render to one or more windows of | |
| 115 // the specified dimensions. CHECK-fails if any initialization step fails. | |
| 116 // After this returns, texture creation and rendering can be requested. This | |
| 117 // method can be called multiple times, in which case all previously-acquired | |
| 118 // resources and initializations are discarded. If |suppress_swap_to_display| | |
| 119 // then all the usual work is done, except for the final swap of the EGL | |
| 120 // surface to the display. This cuts test times over 50% so is worth doing | |
| 121 // when testing non-rendering-related aspects. | |
| 122 void Initialize(bool suppress_swap_to_display, int num_windows, | |
| 123 int width, int height, base::WaitableEvent* done); | |
| 124 | |
| 125 // Undo the effects of Initialize() and signal |*done|. | |
| 126 void UnInitialize(base::WaitableEvent* done); | |
| 127 | |
| 128 // Return a newly-created GLES2 texture id rendering to a specific window, and | |
| 129 // signal |*done|. | |
| 130 void CreateTexture(int window_id, GLuint* texture_id, | |
| 131 base::WaitableEvent* done); | |
| 132 | |
| 133 // Render |texture_id| to the screen (unless |suppress_swap_to_display_|). | |
| 134 void RenderTexture(GLuint texture_id); | |
| 135 | |
| 136 // Delete |texture_id|. | |
| 137 void DeleteTexture(GLuint texture_id); | |
| 138 | |
| 139 EGLDisplay egl_display() { return egl_display_; } | |
| 140 EGLContext egl_context() { return egl_context_; } | |
| 141 MessageLoop* message_loop() { return message_loop_; } | |
| 142 | |
| 143 private: | |
| 144 // Zero-out internal state. Helper for ctor & UnInitialize(). | |
| 145 void Clear(); | |
| 146 | |
| 147 bool suppress_swap_to_display_; | |
| 148 int width_; | |
| 149 int height_; | |
| 150 Display* x_display_; | |
| 151 std::vector<Window> x_windows_; | |
| 152 EGLDisplay egl_display_; | |
| 153 EGLContext egl_context_; | |
| 154 std::vector<EGLSurface> egl_surfaces_; | |
| 155 std::map<GLuint, int> texture_id_to_surface_index_; | |
| 156 // We ensure all operations are carried out on the same thread by remembering | |
| 157 // where we were Initialized. | |
| 158 MessageLoop* message_loop_; | |
| 159 }; | |
| 160 | |
| 161 RenderingHelper::RenderingHelper() { | |
| 162 Clear(); | |
| 163 } | |
| 164 | |
| 165 RenderingHelper::~RenderingHelper() { | |
| 166 CHECK_EQ(width_, 0) << "Must call UnInitialize before dtor."; | |
| 167 } | |
| 168 | |
| 169 void RenderingHelper::Clear() { | |
| 170 suppress_swap_to_display_ = false; | |
| 171 width_ = 0; | |
| 172 height_ = 0; | |
| 173 x_display_ = NULL; | |
| 174 x_windows_.clear(); | |
| 175 egl_display_ = EGL_NO_DISPLAY; | |
| 176 egl_context_ = EGL_NO_CONTEXT; | |
| 177 egl_surfaces_.clear(); | |
| 178 texture_id_to_surface_index_.clear(); | |
| 179 message_loop_ = NULL; | |
| 180 } | |
| 181 | |
| 182 // Helper for Shader creation. | |
| 183 static void CreateShader( | |
| 184 GLuint program, GLenum type, const char* source, int size) { | |
| 185 GLuint shader = glCreateShader(type); | |
| 186 glShaderSource(shader, 1, &source, &size); | |
| 187 glCompileShader(shader); | |
| 188 int result = GL_FALSE; | |
| 189 glGetShaderiv(shader, GL_COMPILE_STATUS, &result); | |
| 190 if (!result) { | |
| 191 char log[4096]; | |
| 192 glGetShaderInfoLog(shader, arraysize(log), NULL, log); | |
| 193 LOG(FATAL) << log; | |
| 194 } | |
| 195 glAttachShader(program, shader); | |
| 196 glDeleteShader(shader); | |
| 197 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); | |
| 198 } | |
| 199 | |
| 200 void RenderingHelper::Initialize( | |
| 201 bool suppress_swap_to_display, | |
| 202 int num_windows, | |
| 203 int width, int height, | |
| 204 base::WaitableEvent* done) { | |
| 205 // Use width_ != 0 as a proxy for the class having already been | |
| 206 // Initialize()'d, and UnInitialize() before continuing. | |
| 207 if (width_) { | |
| 208 base::WaitableEvent done(false, false); | |
| 209 UnInitialize(&done); | |
| 210 done.Wait(); | |
| 211 } | |
| 212 | |
| 213 suppress_swap_to_display_ = suppress_swap_to_display; | |
| 214 CHECK_GT(width, 0); | |
| 215 CHECK_GT(height, 0); | |
| 216 width_ = width; | |
| 217 height_ = height; | |
| 218 message_loop_ = MessageLoop::current(); | |
| 219 CHECK_GT(num_windows, 0); | |
| 220 | |
| 221 // Per-display X11 & EGL initialization. | |
| 222 CHECK(x_display_ = XOpenDisplay(NULL)); | |
| 223 int depth = DefaultDepth(x_display_, DefaultScreen(x_display_)); | |
| 224 XSetWindowAttributes window_attributes; | |
| 225 window_attributes.background_pixel = | |
| 226 BlackPixel(x_display_, DefaultScreen(x_display_)); | |
| 227 window_attributes.override_redirect = true; | |
| 228 | |
| 229 egl_display_ = eglGetDisplay(x_display_); | |
| 230 EGLint major; | |
| 231 EGLint minor; | |
| 232 CHECK(eglInitialize(egl_display_, &major, &minor)) << eglGetError(); | |
| 233 static EGLint rgba8888[] = { | |
| 234 EGL_RED_SIZE, 8, | |
| 235 EGL_GREEN_SIZE, 8, | |
| 236 EGL_BLUE_SIZE, 8, | |
| 237 EGL_ALPHA_SIZE, 8, | |
| 238 EGL_SURFACE_TYPE, EGL_WINDOW_BIT, | |
| 239 EGL_NONE, | |
| 240 }; | |
| 241 EGLConfig egl_config; | |
| 242 int num_configs; | |
| 243 CHECK(eglChooseConfig(egl_display_, rgba8888, &egl_config, 1, &num_configs)) | |
| 244 << eglGetError(); | |
| 245 CHECK_GE(num_configs, 1); | |
| 246 static EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; | |
| 247 egl_context_ = eglCreateContext( | |
| 248 egl_display_, egl_config, EGL_NO_CONTEXT, context_attribs); | |
| 249 CHECK_NE(egl_context_, EGL_NO_CONTEXT) << eglGetError(); | |
| 250 | |
| 251 // Per-window/surface X11 & EGL initialization. | |
| 252 for (int i = 0; i < num_windows; ++i) { | |
| 253 // Arrange X windows whimsically, with some padding. | |
| 254 int top_left_x = (width + 20) * (i % 4); | |
| 255 int top_left_y = (height + 12) * (i % 3); | |
| 256 Window x_window = XCreateWindow( | |
| 257 x_display_, DefaultRootWindow(x_display_), | |
| 258 top_left_x, top_left_y, width_, height_, | |
| 259 0 /* border width */, | |
| 260 depth, CopyFromParent /* class */, CopyFromParent /* visual */, | |
| 261 (CWBackPixel | CWOverrideRedirect), &window_attributes); | |
| 262 x_windows_.push_back(x_window); | |
| 263 XStoreName(x_display_, x_window, "OmxVideoDecodeAcceleratorTest"); | |
| 264 XSelectInput(x_display_, x_window, ExposureMask); | |
| 265 XMapWindow(x_display_, x_window); | |
| 266 | |
| 267 EGLSurface egl_surface = | |
| 268 eglCreateWindowSurface(egl_display_, egl_config, x_window, NULL); | |
| 269 egl_surfaces_.push_back(egl_surface); | |
| 270 CHECK_NE(egl_surface, EGL_NO_SURFACE); | |
| 271 } | |
| 272 CHECK(eglMakeCurrent(egl_display_, egl_surfaces_[0], | |
| 273 egl_surfaces_[0], egl_context_)) << eglGetError(); | |
| 274 | |
| 275 // GLES2 initialization. Note: This is pretty much copy/pasted from | |
| 276 // media/tools/player_x11/gles_video_renderer.cc, with some simplification | |
| 277 // applied. | |
| 278 static const float kVertices[] = | |
| 279 { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, }; | |
| 280 static const float kTextureCoordsEgl[] = { 0, 1, 0, 0, 1, 1, 1, 0, }; | |
| 281 static const char kVertexShader[] = STRINGIZE( | |
| 282 varying vec2 interp_tc; | |
| 283 attribute vec4 in_pos; | |
| 284 attribute vec2 in_tc; | |
| 285 void main() { | |
| 286 interp_tc = in_tc; | |
| 287 gl_Position = in_pos; | |
| 288 } | |
| 289 ); | |
| 290 static const char kFragmentShaderEgl[] = STRINGIZE( | |
| 291 precision mediump float; | |
| 292 varying vec2 interp_tc; | |
| 293 uniform sampler2D tex; | |
| 294 void main() { | |
| 295 gl_FragColor = texture2D(tex, interp_tc); | |
| 296 } | |
| 297 ); | |
| 298 GLuint program = glCreateProgram(); | |
| 299 CreateShader(program, GL_VERTEX_SHADER, | |
| 300 kVertexShader, arraysize(kVertexShader)); | |
| 301 CreateShader(program, GL_FRAGMENT_SHADER, | |
| 302 kFragmentShaderEgl, arraysize(kFragmentShaderEgl)); | |
| 303 glLinkProgram(program); | |
| 304 int result = GL_FALSE; | |
| 305 glGetProgramiv(program, GL_LINK_STATUS, &result); | |
| 306 if (!result) { | |
| 307 char log[4096]; | |
| 308 glGetShaderInfoLog(program, arraysize(log), NULL, log); | |
| 309 LOG(FATAL) << log; | |
| 310 } | |
| 311 glUseProgram(program); | |
| 312 glDeleteProgram(program); | |
| 313 | |
| 314 glUniform1i(glGetUniformLocation(program, "tex"), 0); | |
| 315 int pos_location = glGetAttribLocation(program, "in_pos"); | |
| 316 glEnableVertexAttribArray(pos_location); | |
| 317 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices); | |
| 318 int tc_location = glGetAttribLocation(program, "in_tc"); | |
| 319 glEnableVertexAttribArray(tc_location); | |
| 320 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, | |
| 321 kTextureCoordsEgl); | |
| 322 | |
| 323 done->Signal(); | |
| 324 } | |
| 325 | |
| 326 void RenderingHelper::UnInitialize(base::WaitableEvent* done) { | |
| 327 CHECK_EQ(MessageLoop::current(), message_loop_); | |
| 328 // Destroy resources acquired in Initialize, in reverse-acquisition order. | |
| 329 CHECK(eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, | |
| 330 EGL_NO_CONTEXT)) << eglGetError(); | |
| 331 CHECK(eglDestroyContext(egl_display_, egl_context_)); | |
| 332 for (size_t i = 0; i < egl_surfaces_.size(); ++i) | |
| 333 CHECK(eglDestroySurface(egl_display_, egl_surfaces_[i])); | |
| 334 CHECK(eglTerminate(egl_display_)); | |
| 335 for (size_t i = 0; i < x_windows_.size(); ++i) { | |
| 336 CHECK(XUnmapWindow(x_display_, x_windows_[i])); | |
| 337 CHECK(XDestroyWindow(x_display_, x_windows_[i])); | |
| 338 } | |
| 339 // Mimic newly-created object. | |
| 340 Clear(); | |
| 341 done->Signal(); | |
| 342 } | |
| 343 | |
| 344 void RenderingHelper::CreateTexture(int window_id, GLuint* texture_id, | |
| 345 base::WaitableEvent* done) { | |
| 346 if (MessageLoop::current() != message_loop_) { | |
| 347 message_loop_->PostTask( | |
| 348 FROM_HERE, | |
| 349 base::Bind(&RenderingHelper::CreateTexture, base::Unretained(this), | |
| 350 window_id, texture_id, done)); | |
| 351 return; | |
| 352 } | |
| 353 CHECK(eglMakeCurrent(egl_display_, egl_surfaces_[window_id], | |
| 354 egl_surfaces_[window_id], egl_context_)) | |
| 355 << eglGetError(); | |
| 356 glGenTextures(1, texture_id); | |
| 357 glBindTexture(GL_TEXTURE_2D, *texture_id); | |
| 358 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA, | |
| 359 GL_UNSIGNED_BYTE, NULL); | |
| 360 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| 361 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| 362 // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures. | |
| 363 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| 364 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| 365 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); | |
| 366 CHECK(texture_id_to_surface_index_.insert( | |
| 367 std::make_pair(*texture_id, window_id)).second); | |
| 368 done->Signal(); | |
| 369 } | |
| 370 | |
| 371 void RenderingHelper::RenderTexture(GLuint texture_id) { | |
| 372 CHECK_EQ(MessageLoop::current(), message_loop_); | |
| 373 glActiveTexture(GL_TEXTURE0); | |
| 374 glBindTexture(GL_TEXTURE_2D, texture_id); | |
| 375 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
| 376 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); | |
| 377 CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS); | |
| 378 if (!suppress_swap_to_display_) { | |
| 379 int window_id = texture_id_to_surface_index_[texture_id]; | |
| 380 CHECK(eglMakeCurrent(egl_display_, egl_surfaces_[window_id], | |
| 381 egl_surfaces_[window_id], egl_context_)) | |
| 382 << eglGetError(); | |
| 383 eglSwapBuffers(egl_display_, egl_surfaces_[window_id]); | |
| 384 } | |
| 385 CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS); | |
| 386 } | |
| 387 | |
| 388 void RenderingHelper::DeleteTexture(GLuint texture_id) { | |
| 389 glDeleteTextures(1, &texture_id); | |
| 390 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); | |
| 391 } | |
| 392 | |
| 393 // State of the EglRenderingVDAClient below. Order matters here as the test | |
| 394 // makes assumptions about it. | |
| 395 enum ClientState { | |
| 396 CS_CREATED, | |
| 397 CS_DECODER_SET, | |
| 398 CS_INITIALIZED, | |
| 399 CS_FLUSHING, | |
| 400 CS_FLUSHED, | |
| 401 CS_DONE, | |
| 402 CS_RESETTING, | |
| 403 CS_RESET, | |
| 404 CS_ERROR, | |
| 405 CS_DESTROYED, | |
| 406 CS_MAX, // Must be last entry. | |
| 407 }; | |
| 408 | |
| 409 // Helper class allowing one thread to wait on a notification from another. | |
| 410 // If notifications come in faster than they are Wait()'d for, they are | |
| 411 // accumulated (so exactly as many Wait() calls will unblock as Notify() calls | |
| 412 // were made, regardless of order). | |
| 413 class ClientStateNotification { | |
| 414 public: | |
| 415 ClientStateNotification(); | |
| 416 ~ClientStateNotification(); | |
| 417 | |
| 418 // Used to notify a single waiter of a ClientState. | |
| 419 void Notify(ClientState state); | |
| 420 // Used by waiters to wait for the next ClientState Notification. | |
| 421 ClientState Wait(); | |
| 422 private: | |
| 423 base::Lock lock_; | |
| 424 base::ConditionVariable cv_; | |
| 425 std::queue<ClientState> pending_states_for_notification_; | |
| 426 }; | |
| 427 | |
| 428 ClientStateNotification::ClientStateNotification() : cv_(&lock_) {} | |
| 429 | |
| 430 ClientStateNotification::~ClientStateNotification() {} | |
| 431 | |
| 432 void ClientStateNotification::Notify(ClientState state) { | |
| 433 base::AutoLock auto_lock(lock_); | |
| 434 pending_states_for_notification_.push(state); | |
| 435 cv_.Signal(); | |
| 436 } | |
| 437 | |
| 438 ClientState ClientStateNotification::Wait() { | |
| 439 base::AutoLock auto_lock(lock_); | |
| 440 while (pending_states_for_notification_.empty()) | |
| 441 cv_.Wait(); | |
| 442 ClientState ret = pending_states_for_notification_.front(); | |
| 443 pending_states_for_notification_.pop(); | |
| 444 return ret; | |
| 445 } | |
| 446 | |
| 447 // Magic constants for differentiating the reasons for NotifyResetDone being | |
| 448 // called. | |
| 449 enum ResetPoint { | |
| 450 MID_STREAM_RESET = -2, | |
| 451 END_OF_STREAM_RESET = -1 | |
| 452 }; | |
| 453 | |
| 454 // Client that can accept callbacks from a VideoDecodeAccelerator and is used by | |
| 455 // the TESTs below. | |
| 456 class EglRenderingVDAClient : public VideoDecodeAccelerator::Client { | |
| 457 public: | |
| 458 // Doesn't take ownership of |rendering_helper| or |note|, which must outlive | |
| 459 // |*this|. | |
| 460 // |reset_after_frame_num| can be a frame number >=0 indicating a mid-stream | |
| 461 // Reset() should be done after that frame number is delivered, or | |
| 462 // END_OF_STREAM_RESET to indicate no mid-stream Reset(). | |
| 463 // |delete_decoder_state| indicates when the underlying decoder should be | |
| 464 // Destroy()'d and deleted and can take values: N<0: delete after -N Decode() | |
| 465 // calls have been made, N>=0 means interpret as ClientState. | |
| 466 EglRenderingVDAClient(RenderingHelper* rendering_helper, | |
| 467 int rendering_window_id, | |
| 468 ClientStateNotification* note, | |
| 469 const std::string& encoded_data, | |
| 470 int num_NALUs_per_decode, | |
| 471 int num_in_flight_decodes, | |
| 472 int reset_after_frame_num, | |
| 473 int delete_decoder_state, | |
| 474 int profile); | |
| 475 virtual ~EglRenderingVDAClient(); | |
| 476 void CreateDecoder(); | |
| 477 | |
| 478 // VideoDecodeAccelerator::Client implementation. | |
| 479 // The heart of the Client. | |
| 480 virtual void ProvidePictureBuffers( | |
| 481 uint32 requested_num_of_buffers, | |
| 482 const gfx::Size& dimensions); | |
| 483 virtual void DismissPictureBuffer(int32 picture_buffer_id); | |
| 484 virtual void PictureReady(const media::Picture& picture); | |
| 485 // Simple state changes. | |
| 486 virtual void NotifyInitializeDone(); | |
| 487 virtual void NotifyEndOfStream(); | |
| 488 virtual void NotifyEndOfBitstreamBuffer(int32 bitstream_buffer_id); | |
| 489 virtual void NotifyFlushDone(); | |
| 490 virtual void NotifyResetDone(); | |
| 491 virtual void NotifyError(VideoDecodeAccelerator::Error error); | |
| 492 | |
| 493 // Simple getters for inspecting the state of the Client. | |
| 494 ClientState state() { return state_; } | |
| 495 int num_done_bitstream_buffers() { return num_done_bitstream_buffers_; } | |
| 496 int num_decoded_frames() { return num_decoded_frames_; } | |
| 497 EGLDisplay egl_display() { return rendering_helper_->egl_display(); } | |
| 498 EGLContext egl_context() { return rendering_helper_->egl_context(); } | |
| 499 double frames_per_second(); | |
| 500 bool decoder_deleted() { return !decoder_; } | |
| 501 | |
| 502 private: | |
| 503 typedef std::map<int, media::PictureBuffer*> PictureBufferById; | |
| 504 | |
| 505 void SetState(ClientState new_state); | |
| 506 | |
| 507 // Delete the associated OMX decoder helper. | |
| 508 void DeleteDecoder(); | |
| 509 | |
| 510 // Compute & return in |*end_pos| the end position for the next batch of NALUs | |
| 511 // to ship to the decoder (based on |start_pos| & |num_NALUs_per_decode_|). | |
| 512 void GetRangeForNextNALUs(size_t start_pos, size_t* end_pos); | |
| 513 | |
| 514 // Request decode of the next batch of NALUs in the encoded data. | |
| 515 void DecodeNextNALUs(); | |
| 516 | |
| 517 RenderingHelper* rendering_helper_; | |
| 518 int rendering_window_id_; | |
| 519 std::string encoded_data_; | |
| 520 const int num_NALUs_per_decode_; | |
| 521 const int num_in_flight_decodes_; | |
| 522 int outstanding_decodes_; | |
| 523 size_t encoded_data_next_pos_to_decode_; | |
| 524 int next_bitstream_buffer_id_; | |
| 525 ClientStateNotification* note_; | |
| 526 scoped_refptr<OmxVideoDecodeAccelerator> decoder_; | |
| 527 std::set<int> outstanding_texture_ids_; | |
| 528 int reset_after_frame_num_; | |
| 529 int delete_decoder_state_; | |
| 530 ClientState state_; | |
| 531 int num_decoded_frames_; | |
| 532 int num_done_bitstream_buffers_; | |
| 533 PictureBufferById picture_buffers_by_id_; | |
| 534 base::TimeTicks initialize_done_ticks_; | |
| 535 base::TimeTicks last_frame_delivered_ticks_; | |
| 536 int profile_; | |
| 537 }; | |
| 538 | |
| 539 EglRenderingVDAClient::EglRenderingVDAClient( | |
| 540 RenderingHelper* rendering_helper, | |
| 541 int rendering_window_id, | |
| 542 ClientStateNotification* note, | |
| 543 const std::string& encoded_data, | |
| 544 int num_NALUs_per_decode, | |
| 545 int num_in_flight_decodes, | |
| 546 int reset_after_frame_num, | |
| 547 int delete_decoder_state, | |
| 548 int profile) | |
| 549 : rendering_helper_(rendering_helper), | |
| 550 rendering_window_id_(rendering_window_id), | |
| 551 encoded_data_(encoded_data), num_NALUs_per_decode_(num_NALUs_per_decode), | |
| 552 num_in_flight_decodes_(num_in_flight_decodes), outstanding_decodes_(0), | |
| 553 encoded_data_next_pos_to_decode_(0), next_bitstream_buffer_id_(0), | |
| 554 note_(note), reset_after_frame_num_(reset_after_frame_num), | |
| 555 delete_decoder_state_(delete_decoder_state), | |
| 556 state_(CS_CREATED), | |
| 557 num_decoded_frames_(0), num_done_bitstream_buffers_(0), | |
| 558 profile_(profile) { | |
| 559 CHECK_GT(num_NALUs_per_decode, 0); | |
| 560 CHECK_GT(num_in_flight_decodes, 0); | |
| 561 } | |
| 562 | |
| 563 EglRenderingVDAClient::~EglRenderingVDAClient() { | |
| 564 DeleteDecoder(); // Clean up in case of expected error. | |
| 565 CHECK(decoder_deleted()); | |
| 566 STLDeleteValues(&picture_buffers_by_id_); | |
| 567 SetState(CS_DESTROYED); | |
| 568 } | |
| 569 | |
| 570 void EglRenderingVDAClient::CreateDecoder() { | |
| 571 CHECK(decoder_deleted()); | |
| 572 decoder_ = new OmxVideoDecodeAccelerator(this); | |
| 573 decoder_->SetEglState(egl_display(), egl_context()); | |
| 574 SetState(CS_DECODER_SET); | |
| 575 if (decoder_deleted()) | |
| 576 return; | |
| 577 | |
| 578 // Configure the decoder. | |
| 579 media::VideoDecodeAccelerator::Profile profile = media::H264PROFILE_BASELINE; | |
| 580 if (profile_ != -1) | |
| 581 profile = static_cast<media::VideoDecodeAccelerator::Profile>(profile_); | |
| 582 CHECK(decoder_->Initialize(profile)); | |
| 583 } | |
| 584 | |
| 585 void EglRenderingVDAClient::ProvidePictureBuffers( | |
| 586 uint32 requested_num_of_buffers, | |
| 587 const gfx::Size& dimensions) { | |
| 588 if (decoder_deleted()) | |
| 589 return; | |
| 590 std::vector<media::PictureBuffer> buffers; | |
| 591 | |
| 592 for (uint32 i = 0; i < requested_num_of_buffers; ++i) { | |
| 593 uint32 id = picture_buffers_by_id_.size(); | |
| 594 GLuint texture_id; | |
| 595 base::WaitableEvent done(false, false); | |
| 596 rendering_helper_->CreateTexture(rendering_window_id_, &texture_id, &done); | |
| 597 done.Wait(); | |
| 598 CHECK(outstanding_texture_ids_.insert(texture_id).second); | |
| 599 media::PictureBuffer* buffer = | |
| 600 new media::PictureBuffer(id, dimensions, texture_id); | |
| 601 CHECK(picture_buffers_by_id_.insert(std::make_pair(id, buffer)).second); | |
| 602 buffers.push_back(*buffer); | |
| 603 } | |
| 604 decoder_->AssignPictureBuffers(buffers); | |
| 605 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); | |
| 606 CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS); | |
| 607 } | |
| 608 | |
| 609 void EglRenderingVDAClient::DismissPictureBuffer(int32 picture_buffer_id) { | |
| 610 PictureBufferById::iterator it = | |
| 611 picture_buffers_by_id_.find(picture_buffer_id); | |
| 612 CHECK(it != picture_buffers_by_id_.end()); | |
| 613 CHECK_EQ(outstanding_texture_ids_.erase(it->second->texture_id()), 1U); | |
| 614 rendering_helper_->DeleteTexture(it->second->texture_id()); | |
| 615 delete it->second; | |
| 616 picture_buffers_by_id_.erase(it); | |
| 617 } | |
| 618 | |
| 619 void EglRenderingVDAClient::PictureReady(const media::Picture& picture) { | |
| 620 // We shouldn't be getting pictures delivered after Reset has completed. | |
| 621 CHECK_LT(state_, CS_RESET); | |
| 622 | |
| 623 if (decoder_deleted()) | |
| 624 return; | |
| 625 last_frame_delivered_ticks_ = base::TimeTicks::Now(); | |
| 626 | |
| 627 // Because we feed the decoder a limited number of NALUs at a time, we can be | |
| 628 // sure that the bitstream buffer from which a frame comes has a limited | |
| 629 // range. Assert that. | |
| 630 CHECK_GE((picture.bitstream_buffer_id() + 1) * num_NALUs_per_decode_, | |
| 631 num_decoded_frames_); | |
| 632 CHECK_LE(picture.bitstream_buffer_id(), next_bitstream_buffer_id_); | |
| 633 ++num_decoded_frames_; | |
| 634 | |
| 635 if (reset_after_frame_num_ == num_decoded_frames_) { | |
| 636 reset_after_frame_num_ = MID_STREAM_RESET; | |
| 637 decoder_->Reset(); | |
| 638 // Re-start decoding from the beginning of the stream to avoid needing to | |
| 639 // know how to find I-frames and so on in this test. | |
| 640 encoded_data_next_pos_to_decode_ = 0; | |
| 641 } | |
| 642 | |
| 643 media::PictureBuffer* picture_buffer = | |
| 644 picture_buffers_by_id_[picture.picture_buffer_id()]; | |
| 645 CHECK(picture_buffer); | |
| 646 rendering_helper_->RenderTexture(picture_buffer->texture_id()); | |
| 647 | |
| 648 decoder_->ReusePictureBuffer(picture.picture_buffer_id()); | |
| 649 } | |
| 650 | |
| 651 void EglRenderingVDAClient::NotifyInitializeDone() { | |
| 652 SetState(CS_INITIALIZED); | |
| 653 initialize_done_ticks_ = base::TimeTicks::Now(); | |
| 654 for (int i = 0; i < num_in_flight_decodes_; ++i) | |
| 655 DecodeNextNALUs(); | |
| 656 } | |
| 657 | |
| 658 void EglRenderingVDAClient::NotifyEndOfStream() { | |
| 659 SetState(CS_DONE); | |
| 660 } | |
| 661 | |
| 662 void EglRenderingVDAClient::NotifyEndOfBitstreamBuffer( | |
| 663 int32 bitstream_buffer_id) { | |
| 664 ++num_done_bitstream_buffers_; | |
| 665 --outstanding_decodes_; | |
| 666 DecodeNextNALUs(); | |
| 667 } | |
| 668 | |
| 669 void EglRenderingVDAClient::NotifyFlushDone() { | |
| 670 if (decoder_deleted()) | |
| 671 return; | |
| 672 SetState(CS_FLUSHED); | |
| 673 if (decoder_deleted()) | |
| 674 return; | |
| 675 decoder_->Reset(); | |
| 676 SetState(CS_RESETTING); | |
| 677 } | |
| 678 | |
| 679 void EglRenderingVDAClient::NotifyResetDone() { | |
| 680 if (decoder_deleted()) | |
| 681 return; | |
| 682 if (reset_after_frame_num_ == MID_STREAM_RESET) { | |
| 683 reset_after_frame_num_ = END_OF_STREAM_RESET; | |
| 684 return; | |
| 685 } | |
| 686 SetState(CS_RESET); | |
| 687 if (!decoder_deleted()) | |
| 688 DeleteDecoder(); | |
| 689 } | |
| 690 | |
| 691 void EglRenderingVDAClient::NotifyError(VideoDecodeAccelerator::Error error) { | |
| 692 SetState(CS_ERROR); | |
| 693 } | |
| 694 | |
| 695 static bool LookingAtNAL(const std::string& encoded, size_t pos) { | |
| 696 return pos + 3 < encoded.size() && | |
| 697 encoded[pos] == 0 && encoded[pos + 1] == 0 && | |
| 698 encoded[pos + 2] == 0 && encoded[pos + 3] == 1; | |
| 699 } | |
| 700 | |
| 701 void EglRenderingVDAClient::SetState(ClientState new_state) { | |
| 702 note_->Notify(new_state); | |
| 703 state_ = new_state; | |
| 704 if (new_state == delete_decoder_state_) { | |
| 705 CHECK(!decoder_deleted()); | |
| 706 DeleteDecoder(); | |
| 707 } | |
| 708 } | |
| 709 | |
| 710 void EglRenderingVDAClient::DeleteDecoder() { | |
| 711 if (decoder_deleted()) | |
| 712 return; | |
| 713 decoder_->Destroy(); | |
| 714 decoder_ = NULL; | |
| 715 STLClearObject(&encoded_data_); | |
| 716 for (std::set<int>::iterator it = outstanding_texture_ids_.begin(); | |
| 717 it != outstanding_texture_ids_.end(); ++it) { | |
| 718 rendering_helper_->DeleteTexture(*it); | |
| 719 } | |
| 720 outstanding_texture_ids_.clear(); | |
| 721 // Cascade through the rest of the states to simplify test code below. | |
| 722 for (int i = state_ + 1; i < CS_MAX; ++i) | |
| 723 SetState(static_cast<ClientState>(i)); | |
| 724 } | |
| 725 | |
| 726 void EglRenderingVDAClient::GetRangeForNextNALUs( | |
| 727 size_t start_pos, size_t* end_pos) { | |
| 728 *end_pos = start_pos; | |
| 729 CHECK(LookingAtNAL(encoded_data_, start_pos)); | |
| 730 for (int i = 0; i < num_NALUs_per_decode_; ++i) { | |
| 731 *end_pos += 4; | |
| 732 while (*end_pos + 3 < encoded_data_.size() && | |
| 733 !LookingAtNAL(encoded_data_, *end_pos)) { | |
| 734 ++*end_pos; | |
| 735 } | |
| 736 if (*end_pos + 3 >= encoded_data_.size()) { | |
| 737 *end_pos = encoded_data_.size(); | |
| 738 return; | |
| 739 } | |
| 740 } | |
| 741 } | |
| 742 | |
| 743 void EglRenderingVDAClient::DecodeNextNALUs() { | |
| 744 if (decoder_deleted()) | |
| 745 return; | |
| 746 if (encoded_data_next_pos_to_decode_ == encoded_data_.size()) { | |
| 747 if (outstanding_decodes_ == 0) { | |
| 748 decoder_->Flush(); | |
| 749 SetState(CS_FLUSHING); | |
| 750 } | |
| 751 return; | |
| 752 } | |
| 753 size_t start_pos = encoded_data_next_pos_to_decode_; | |
| 754 size_t end_pos; | |
| 755 GetRangeForNextNALUs(start_pos, &end_pos); | |
| 756 | |
| 757 // Populate the shared memory buffer w/ the NALU, duplicate its handle, and | |
| 758 // hand it off to the decoder. | |
| 759 base::SharedMemory shm; | |
| 760 CHECK(shm.CreateAndMapAnonymous(end_pos - start_pos)) | |
| 761 << start_pos << ", " << end_pos; | |
| 762 memcpy(shm.memory(), encoded_data_.data() + start_pos, end_pos - start_pos); | |
| 763 base::SharedMemoryHandle dup_handle; | |
| 764 CHECK(shm.ShareToProcess(base::Process::Current().handle(), &dup_handle)); | |
| 765 media::BitstreamBuffer bitstream_buffer( | |
| 766 next_bitstream_buffer_id_++, dup_handle, end_pos - start_pos); | |
| 767 decoder_->Decode(bitstream_buffer); | |
| 768 ++outstanding_decodes_; | |
| 769 encoded_data_next_pos_to_decode_ = end_pos; | |
| 770 | |
| 771 if (-delete_decoder_state_ == next_bitstream_buffer_id_) | |
| 772 DeleteDecoder(); | |
| 773 } | |
| 774 | |
| 775 double EglRenderingVDAClient::frames_per_second() { | |
| 776 base::TimeDelta delta = last_frame_delivered_ticks_ - initialize_done_ticks_; | |
| 777 if (delta.InSecondsF() == 0) | |
| 778 return 0; | |
| 779 return num_decoded_frames_ / delta.InSecondsF(); | |
| 780 } | |
| 781 | |
| 782 // Test parameters: | |
| 783 // - Number of NALUs per Decode() call. | |
| 784 // - Number of concurrent decoders. | |
| 785 // - Number of concurrent in-flight Decode() calls per decoder. | |
| 786 // - reset_after_frame_num: see EglRenderingVDAClient ctor. | |
| 787 // - delete_decoder_phase: see EglRenderingVDAClient ctor. | |
| 788 class OmxVideoDecodeAcceleratorTest | |
| 789 : public ::testing::TestWithParam< | |
| 790 Tuple5<int, int, int, ResetPoint, ClientState> > { | |
| 791 }; | |
| 792 | |
| 793 // Wait for |note| to report a state and if it's not |expected_state| then | |
| 794 // assert |client| has deleted its decoder. | |
| 795 static void AssertWaitForStateOrDeleted(ClientStateNotification* note, | |
| 796 EglRenderingVDAClient* client, | |
| 797 ClientState expected_state) { | |
| 798 ClientState state = note->Wait(); | |
| 799 if (state == expected_state) return; | |
| 800 ASSERT_TRUE(client->decoder_deleted()) | |
| 801 << "Decoder not deleted but Wait() returned " << state | |
| 802 << ", instead of " << expected_state; | |
| 803 } | |
| 804 | |
| 805 // We assert a minimal number of concurrent decoders we expect to succeed. | |
| 806 // Different platforms can support more concurrent decoders, so we don't assert | |
| 807 // failure above this. | |
| 808 enum { kMinSupportedNumConcurrentDecoders = 3 }; | |
| 809 | |
| 810 // Test the most straightforward case possible: data is decoded from a single | |
| 811 // chunk and rendered to the screen. | |
| 812 TEST_P(OmxVideoDecodeAcceleratorTest, TestSimpleDecode) { | |
| 813 // Can be useful for debugging VLOGs from OVDA. | |
| 814 // logging::SetMinLogLevel(-1); | |
| 815 | |
| 816 // Required for Thread to work. Not used otherwise. | |
| 817 base::ShadowingAtExitManager at_exit_manager; | |
| 818 | |
| 819 const int num_NALUs_per_decode = GetParam().a; | |
| 820 const size_t num_concurrent_decoders = GetParam().b; | |
| 821 const size_t num_in_flight_decodes = GetParam().c; | |
| 822 const int reset_after_frame_num = GetParam().d; | |
| 823 const int delete_decoder_state = GetParam().e; | |
| 824 | |
| 825 std::string test_video_file; | |
| 826 int frame_width, frame_height; | |
| 827 int num_frames, num_NALUs, min_fps_render, min_fps_no_render, profile; | |
| 828 ParseTestVideoData(test_video_data, &test_video_file, &frame_width, | |
| 829 &frame_height, &num_frames, &num_NALUs, | |
| 830 &min_fps_render, &min_fps_no_render, &profile); | |
| 831 min_fps_render /= num_concurrent_decoders; | |
| 832 min_fps_no_render /= num_concurrent_decoders; | |
| 833 | |
| 834 // If we reset mid-stream and start playback over, account for frames that are | |
| 835 // decoded twice in our expectations. | |
| 836 if (num_frames > 0 && reset_after_frame_num >= 0) | |
| 837 num_frames += reset_after_frame_num; | |
| 838 | |
| 839 // Suppress EGL surface swapping in all but a few tests, to cut down overall | |
| 840 // test runtime. | |
| 841 const bool suppress_swap_to_display = num_NALUs_per_decode > 1; | |
| 842 | |
| 843 std::vector<ClientStateNotification*> notes(num_concurrent_decoders, NULL); | |
| 844 std::vector<EglRenderingVDAClient*> clients(num_concurrent_decoders, NULL); | |
| 845 | |
| 846 // Read in the video data. | |
| 847 std::string data_str; | |
| 848 CHECK(file_util::ReadFileToString(FilePath(test_video_file), &data_str)); | |
| 849 | |
| 850 // Initialize the rendering helper. | |
| 851 base::Thread rendering_thread("EglRenderingVDAClientThread"); | |
| 852 rendering_thread.Start(); | |
| 853 RenderingHelper rendering_helper; | |
| 854 | |
| 855 base::WaitableEvent done(false, false); | |
| 856 rendering_thread.message_loop()->PostTask( | |
| 857 FROM_HERE, | |
| 858 base::Bind(&RenderingHelper::Initialize, | |
| 859 base::Unretained(&rendering_helper), | |
| 860 suppress_swap_to_display, num_concurrent_decoders, | |
| 861 frame_width, frame_height, &done)); | |
| 862 done.Wait(); | |
| 863 | |
| 864 // First kick off all the decoders. | |
| 865 for (size_t index = 0; index < num_concurrent_decoders; ++index) { | |
| 866 ClientStateNotification* note = new ClientStateNotification(); | |
| 867 notes[index] = note; | |
| 868 EglRenderingVDAClient* client = new EglRenderingVDAClient( | |
| 869 &rendering_helper, index, | |
| 870 note, data_str, num_NALUs_per_decode, | |
| 871 num_in_flight_decodes, | |
| 872 reset_after_frame_num, delete_decoder_state, profile); | |
| 873 clients[index] = client; | |
| 874 | |
| 875 rendering_thread.message_loop()->PostTask( | |
| 876 FROM_HERE, | |
| 877 base::Bind(&EglRenderingVDAClient::CreateDecoder, | |
| 878 base::Unretained(client))); | |
| 879 | |
| 880 ASSERT_EQ(note->Wait(), CS_DECODER_SET); | |
| 881 } | |
| 882 // Then wait for all the decodes to finish. | |
| 883 bool saw_init_failure = false; | |
| 884 for (size_t i = 0; i < num_concurrent_decoders; ++i) { | |
| 885 ClientStateNotification* note = notes[i]; | |
| 886 ClientState state = note->Wait(); | |
| 887 if (state != CS_INITIALIZED) { | |
| 888 saw_init_failure = true; | |
| 889 // We expect initialization to fail only when more than the supported | |
| 890 // number of decoders is instantiated. Assert here that something else | |
| 891 // didn't trigger failure. | |
| 892 ASSERT_GT(num_concurrent_decoders, kMinSupportedNumConcurrentDecoders); | |
| 893 continue; | |
| 894 } | |
| 895 ASSERT_EQ(state, CS_INITIALIZED); | |
| 896 // InitializeDone kicks off decoding inside the client, so we just need to | |
| 897 // wait for Flush. | |
| 898 ASSERT_NO_FATAL_FAILURE( | |
| 899 AssertWaitForStateOrDeleted(note, clients[i], CS_FLUSHING)); | |
| 900 ASSERT_NO_FATAL_FAILURE( | |
| 901 AssertWaitForStateOrDeleted(note, clients[i], CS_FLUSHED)); | |
| 902 // FlushDone requests Reset(). | |
| 903 ASSERT_NO_FATAL_FAILURE( | |
| 904 AssertWaitForStateOrDeleted(note, clients[i], CS_RESETTING)); | |
| 905 ASSERT_NO_FATAL_FAILURE( | |
| 906 AssertWaitForStateOrDeleted(note, clients[i], CS_RESET)); | |
| 907 // ResetDone requests Destroy(). | |
| 908 ASSERT_NO_FATAL_FAILURE( | |
| 909 AssertWaitForStateOrDeleted(note, clients[i], CS_DESTROYED)); | |
| 910 } | |
| 911 // Finally assert that decoding went as expected. | |
| 912 for (size_t i = 0; i < num_concurrent_decoders && !saw_init_failure; ++i) { | |
| 913 // We can only make performance/correctness assertions if the decoder was | |
| 914 // allowed to finish. | |
| 915 if (delete_decoder_state < CS_FLUSHED) | |
| 916 continue; | |
| 917 EglRenderingVDAClient* client = clients[i]; | |
| 918 if (num_frames > 0) | |
| 919 EXPECT_EQ(client->num_decoded_frames(), num_frames); | |
| 920 if (num_NALUs > 0 && reset_after_frame_num < 0) { | |
| 921 EXPECT_EQ(client->num_done_bitstream_buffers(), | |
| 922 ceil(static_cast<double>(num_NALUs) / num_NALUs_per_decode)); | |
| 923 } | |
| 924 LOG(INFO) << "Decoder " << i << " fps: " << client->frames_per_second(); | |
| 925 int min_fps = suppress_swap_to_display ? min_fps_no_render : min_fps_render; | |
| 926 if (min_fps > 0) | |
| 927 EXPECT_GT(client->frames_per_second(), min_fps); | |
| 928 } | |
| 929 | |
| 930 rendering_thread.message_loop()->PostTask( | |
| 931 FROM_HERE, | |
| 932 base::Bind(&STLDeleteElements<std::vector<EglRenderingVDAClient*> >, | |
| 933 &clients)); | |
| 934 rendering_thread.message_loop()->PostTask( | |
| 935 FROM_HERE, | |
| 936 base::Bind(&STLDeleteElements<std::vector<ClientStateNotification*> >, | |
| 937 ¬es)); | |
| 938 rendering_thread.message_loop()->PostTask( | |
| 939 FROM_HERE, | |
| 940 base::Bind(&RenderingHelper::UnInitialize, | |
| 941 base::Unretained(&rendering_helper), | |
| 942 &done)); | |
| 943 done.Wait(); | |
| 944 rendering_thread.Stop(); | |
| 945 }; | |
| 946 | |
| 947 // Test that Reset() mid-stream works fine and doesn't affect decoding even when | |
| 948 // Decode() calls are made during the reset. | |
| 949 INSTANTIATE_TEST_CASE_P( | |
| 950 MidStreamReset, OmxVideoDecodeAcceleratorTest, | |
| 951 ::testing::Values( | |
| 952 MakeTuple(1, 1, 1, static_cast<ResetPoint>(100), CS_RESET))); | |
| 953 | |
| 954 // Test that Destroy() mid-stream works fine (primarily this is testing that no | |
| 955 // crashes occur). | |
| 956 INSTANTIATE_TEST_CASE_P( | |
| 957 TearDownTiming, OmxVideoDecodeAcceleratorTest, | |
| 958 ::testing::Values( | |
| 959 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_DECODER_SET), | |
| 960 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_INITIALIZED), | |
| 961 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_FLUSHING), | |
| 962 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_FLUSHED), | |
| 963 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_RESETTING), | |
| 964 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_RESET), | |
| 965 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, static_cast<ClientState>(-1)), | |
| 966 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, static_cast<ClientState>(-10)), | |
| 967 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, | |
| 968 static_cast<ClientState>(-100)))); | |
| 969 | |
| 970 // Test that decoding various variation works: multiple concurrent decoders and | |
| 971 // multiple NALUs per Decode() call. | |
| 972 INSTANTIATE_TEST_CASE_P( | |
| 973 DecodeVariations, OmxVideoDecodeAcceleratorTest, | |
| 974 ::testing::Values( | |
| 975 MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_RESET), | |
| 976 MakeTuple(1, 1, 10, END_OF_STREAM_RESET, CS_RESET), | |
| 977 MakeTuple(1, 1, 15, END_OF_STREAM_RESET, CS_RESET), // Tests queuing. | |
| 978 MakeTuple(1, 3, 1, END_OF_STREAM_RESET, CS_RESET), | |
| 979 MakeTuple(2, 1, 1, END_OF_STREAM_RESET, CS_RESET), | |
| 980 MakeTuple(3, 1, 1, END_OF_STREAM_RESET, CS_RESET), | |
| 981 MakeTuple(5, 1, 1, END_OF_STREAM_RESET, CS_RESET), | |
| 982 MakeTuple(8, 1, 1, END_OF_STREAM_RESET, CS_RESET), | |
| 983 // TODO(fischman): decoding more than 15 NALUs at once breaks decode - | |
| 984 // visual artifacts are introduced as well as spurious frames are | |
| 985 // delivered (more pictures are returned than NALUs are fed to the | |
| 986 // decoder). Increase the "15" below when | |
| 987 // http://code.google.com/p/chrome-os-partner/issues/detail?id=4378 is | |
| 988 // fixed. | |
| 989 MakeTuple(15, 1, 1, END_OF_STREAM_RESET, CS_RESET))); | |
| 990 | |
| 991 // Find out how many concurrent decoders can go before we exhaust system | |
| 992 // resources. | |
| 993 INSTANTIATE_TEST_CASE_P( | |
| 994 ResourceExhaustion, OmxVideoDecodeAcceleratorTest, | |
| 995 ::testing::Values( | |
| 996 // +0 hack below to promote enum to int. | |
| 997 MakeTuple(1, kMinSupportedNumConcurrentDecoders + 0, 1, | |
| 998 END_OF_STREAM_RESET, CS_RESET), | |
| 999 MakeTuple(1, kMinSupportedNumConcurrentDecoders + 1, 1, | |
| 1000 END_OF_STREAM_RESET, CS_RESET))); | |
| 1001 | |
| 1002 // TODO(fischman, vrk): add more tests! In particular: | |
| 1003 // - Test life-cycle: Seek/Stop/Pause/Play/RePlay for a single decoder. | |
| 1004 // - Test alternate configurations | |
| 1005 // - Test failure conditions. | |
| 1006 // - Test frame size changes mid-stream | |
| 1007 | |
| 1008 } // namespace | |
| 1009 | |
| 1010 int main(int argc, char **argv) { | |
| 1011 testing::InitGoogleTest(&argc, argv); // Removes gtest-specific args. | |
| 1012 CommandLine cmd_line(argc, argv); // Must run after InitGoogleTest. | |
| 1013 CommandLine::SwitchMap switches = cmd_line.GetSwitches(); | |
| 1014 for (CommandLine::SwitchMap::const_iterator it = switches.begin(); | |
| 1015 it != switches.end(); ++it) { | |
| 1016 if (it->first == "test_video_data") { | |
| 1017 test_video_data = it->second.c_str(); | |
| 1018 continue; | |
| 1019 } | |
| 1020 LOG(FATAL) << "Unexpected switch: " << it->first << ":" << it->second; | |
| 1021 } | |
| 1022 | |
| 1023 return RUN_ALL_TESTS(); | |
| 1024 } | |
| OLD | NEW |