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

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

Issue 8510039: Initial implementation of the DXVA 2.0 H.264 hardware decoder for pepper for Windows. The decodin... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | 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 <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 &notes));
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698