| Index: content/common/gpu/media/omx_video_decode_accelerator_unittest.cc
|
| ===================================================================
|
| --- content/common/gpu/media/omx_video_decode_accelerator_unittest.cc (revision 115422)
|
| +++ content/common/gpu/media/omx_video_decode_accelerator_unittest.cc (working copy)
|
| @@ -1,1024 +0,0 @@
|
| -// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -//
|
| -// The bulk of this file is support code; sorry about that. Here's an overview
|
| -// to hopefully help readers of this code:
|
| -// - RenderingHelper is charged with interacting with X11, EGL, and GLES2.
|
| -// - ClientState is an enum for the state of the decode client used by the test.
|
| -// - ClientStateNotification is a barrier abstraction that allows the test code
|
| -// to be written sequentially and wait for the decode client to see certain
|
| -// state transitions.
|
| -// - EglRenderingVDAClient is a VideoDecodeAccelerator::Client implementation
|
| -// - Finally actual TEST cases are at the bottom of this file, using the above
|
| -// infrastructure.
|
| -
|
| -#include <fcntl.h>
|
| -#include <math.h>
|
| -#include <sys/stat.h>
|
| -#include <sys/types.h>
|
| -
|
| -// Include gtest.h out of order because <X11/X.h> #define's Bool & None, which
|
| -// gtest uses as struct names (inside a namespace). This means that
|
| -// #include'ing gtest after anything that pulls in X.h fails to compile.
|
| -// This is http://code.google.com/p/googletest/issues/detail?id=371
|
| -#include "testing/gtest/include/gtest/gtest.h"
|
| -
|
| -#include "base/at_exit.h"
|
| -#include "base/bind.h"
|
| -#include "base/command_line.h"
|
| -#include "base/file_util.h"
|
| -#include "base/stl_util.h"
|
| -#include "base/string_number_conversions.h"
|
| -#include "base/string_split.h"
|
| -#include "base/stringize_macros.h"
|
| -#include "base/synchronization/condition_variable.h"
|
| -#include "base/synchronization/lock.h"
|
| -#include "base/synchronization/waitable_event.h"
|
| -#include "base/threading/thread.h"
|
| -#include "content/common/gpu/media/omx_video_decode_accelerator.h"
|
| -#include "third_party/angle/include/EGL/egl.h"
|
| -#include "third_party/angle/include/GLES2/gl2.h"
|
| -
|
| -#if !defined(OS_CHROMEOS) || !defined(ARCH_CPU_ARMEL)
|
| -#error This test (and OmxVideoDecodeAccelerator) are only supported on cros/ARM!
|
| -#endif
|
| -
|
| -using media::VideoDecodeAccelerator;
|
| -
|
| -namespace {
|
| -
|
| -// Values optionally filled in from flags; see main() below.
|
| -// The syntax of this variable is:
|
| -// filename:width:height:numframes:numNALUs:minFPSwithRender:minFPSnoRender
|
| -// where only the first field is required. Value details:
|
| -// - |filename| must be an h264 Annex B (NAL) stream.
|
| -// - |width| and |height| are in pixels.
|
| -// - |numframes| is the number of picture frames in the file.
|
| -// - |numNALUs| is the number of NAL units in the stream.
|
| -// - |minFPSwithRender| and |minFPSnoRender| are minimum frames/second speeds
|
| -// expected to be achieved with and without rendering to the screen, resp.
|
| -// (the latter tests just decode speed).
|
| -// - |profile| is the media::H264Profile set during Initialization.
|
| -// An empty value for a numeric field means "ignore".
|
| -const char* test_video_data = "test-25fps.h264:320:240:250:258:50:175:1";
|
| -
|
| -// Parse |data| into its constituent parts and set the various output fields
|
| -// accordingly. CHECK-fails on unexpected or missing required data.
|
| -// Unspecified optional fields are set to -1.
|
| -void ParseTestVideoData(std::string data,
|
| - std::string* file_name,
|
| - int* width, int* height,
|
| - int* num_frames,
|
| - int* num_NALUs,
|
| - int* min_fps_render,
|
| - int* min_fps_no_render,
|
| - int* profile) {
|
| - std::vector<std::string> elements;
|
| - base::SplitString(data, ':', &elements);
|
| - CHECK_GE(elements.size(), 1U) << data;
|
| - CHECK_LE(elements.size(), 8U) << data;
|
| - *file_name = elements[0];
|
| - *width = *height = *num_frames = *num_NALUs = -1;
|
| - *min_fps_render = *min_fps_no_render = -1;
|
| - *profile = -1;
|
| - if (!elements[1].empty())
|
| - CHECK(base::StringToInt(elements[1], width));
|
| - if (!elements[2].empty())
|
| - CHECK(base::StringToInt(elements[2], height));
|
| - if (!elements[3].empty())
|
| - CHECK(base::StringToInt(elements[3], num_frames));
|
| - if (!elements[4].empty())
|
| - CHECK(base::StringToInt(elements[4], num_NALUs));
|
| - if (!elements[5].empty())
|
| - CHECK(base::StringToInt(elements[5], min_fps_render));
|
| - if (!elements[6].empty())
|
| - CHECK(base::StringToInt(elements[6], min_fps_no_render));
|
| - if (!elements[7].empty())
|
| - CHECK(base::StringToInt(elements[7], profile));
|
| -}
|
| -
|
| -
|
| -// Helper for managing X11, EGL, and GLES2 resources. Xlib is not thread-safe,
|
| -// and GL state is thread-specific, so all the methods of this class (except for
|
| -// ctor/dtor) ensure they're being run on a single thread.
|
| -//
|
| -// TODO(fischman): consider moving this into media/ if we can de-dup some of the
|
| -// code that ends up getting copy/pasted all over the place (esp. the GL setup
|
| -// code).
|
| -class RenderingHelper {
|
| - public:
|
| - explicit RenderingHelper();
|
| - ~RenderingHelper();
|
| -
|
| - // Initialize all structures to prepare to render to one or more windows of
|
| - // the specified dimensions. CHECK-fails if any initialization step fails.
|
| - // After this returns, texture creation and rendering can be requested. This
|
| - // method can be called multiple times, in which case all previously-acquired
|
| - // resources and initializations are discarded. If |suppress_swap_to_display|
|
| - // then all the usual work is done, except for the final swap of the EGL
|
| - // surface to the display. This cuts test times over 50% so is worth doing
|
| - // when testing non-rendering-related aspects.
|
| - void Initialize(bool suppress_swap_to_display, int num_windows,
|
| - int width, int height, base::WaitableEvent* done);
|
| -
|
| - // Undo the effects of Initialize() and signal |*done|.
|
| - void UnInitialize(base::WaitableEvent* done);
|
| -
|
| - // Return a newly-created GLES2 texture id rendering to a specific window, and
|
| - // signal |*done|.
|
| - void CreateTexture(int window_id, GLuint* texture_id,
|
| - base::WaitableEvent* done);
|
| -
|
| - // Render |texture_id| to the screen (unless |suppress_swap_to_display_|).
|
| - void RenderTexture(GLuint texture_id);
|
| -
|
| - // Delete |texture_id|.
|
| - void DeleteTexture(GLuint texture_id);
|
| -
|
| - EGLDisplay egl_display() { return egl_display_; }
|
| - EGLContext egl_context() { return egl_context_; }
|
| - MessageLoop* message_loop() { return message_loop_; }
|
| -
|
| - private:
|
| - // Zero-out internal state. Helper for ctor & UnInitialize().
|
| - void Clear();
|
| -
|
| - bool suppress_swap_to_display_;
|
| - int width_;
|
| - int height_;
|
| - Display* x_display_;
|
| - std::vector<Window> x_windows_;
|
| - EGLDisplay egl_display_;
|
| - EGLContext egl_context_;
|
| - std::vector<EGLSurface> egl_surfaces_;
|
| - std::map<GLuint, int> texture_id_to_surface_index_;
|
| - // We ensure all operations are carried out on the same thread by remembering
|
| - // where we were Initialized.
|
| - MessageLoop* message_loop_;
|
| -};
|
| -
|
| -RenderingHelper::RenderingHelper() {
|
| - Clear();
|
| -}
|
| -
|
| -RenderingHelper::~RenderingHelper() {
|
| - CHECK_EQ(width_, 0) << "Must call UnInitialize before dtor.";
|
| -}
|
| -
|
| -void RenderingHelper::Clear() {
|
| - suppress_swap_to_display_ = false;
|
| - width_ = 0;
|
| - height_ = 0;
|
| - x_display_ = NULL;
|
| - x_windows_.clear();
|
| - egl_display_ = EGL_NO_DISPLAY;
|
| - egl_context_ = EGL_NO_CONTEXT;
|
| - egl_surfaces_.clear();
|
| - texture_id_to_surface_index_.clear();
|
| - message_loop_ = NULL;
|
| -}
|
| -
|
| -// Helper for Shader creation.
|
| -static void CreateShader(
|
| - GLuint program, GLenum type, const char* source, int size) {
|
| - GLuint shader = glCreateShader(type);
|
| - glShaderSource(shader, 1, &source, &size);
|
| - glCompileShader(shader);
|
| - int result = GL_FALSE;
|
| - glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
|
| - if (!result) {
|
| - char log[4096];
|
| - glGetShaderInfoLog(shader, arraysize(log), NULL, log);
|
| - LOG(FATAL) << log;
|
| - }
|
| - glAttachShader(program, shader);
|
| - glDeleteShader(shader);
|
| - CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
|
| -}
|
| -
|
| -void RenderingHelper::Initialize(
|
| - bool suppress_swap_to_display,
|
| - int num_windows,
|
| - int width, int height,
|
| - base::WaitableEvent* done) {
|
| - // Use width_ != 0 as a proxy for the class having already been
|
| - // Initialize()'d, and UnInitialize() before continuing.
|
| - if (width_) {
|
| - base::WaitableEvent done(false, false);
|
| - UnInitialize(&done);
|
| - done.Wait();
|
| - }
|
| -
|
| - suppress_swap_to_display_ = suppress_swap_to_display;
|
| - CHECK_GT(width, 0);
|
| - CHECK_GT(height, 0);
|
| - width_ = width;
|
| - height_ = height;
|
| - message_loop_ = MessageLoop::current();
|
| - CHECK_GT(num_windows, 0);
|
| -
|
| - // Per-display X11 & EGL initialization.
|
| - CHECK(x_display_ = XOpenDisplay(NULL));
|
| - int depth = DefaultDepth(x_display_, DefaultScreen(x_display_));
|
| - XSetWindowAttributes window_attributes;
|
| - window_attributes.background_pixel =
|
| - BlackPixel(x_display_, DefaultScreen(x_display_));
|
| - window_attributes.override_redirect = true;
|
| -
|
| - egl_display_ = eglGetDisplay(x_display_);
|
| - EGLint major;
|
| - EGLint minor;
|
| - CHECK(eglInitialize(egl_display_, &major, &minor)) << eglGetError();
|
| - static EGLint rgba8888[] = {
|
| - EGL_RED_SIZE, 8,
|
| - EGL_GREEN_SIZE, 8,
|
| - EGL_BLUE_SIZE, 8,
|
| - EGL_ALPHA_SIZE, 8,
|
| - EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
| - EGL_NONE,
|
| - };
|
| - EGLConfig egl_config;
|
| - int num_configs;
|
| - CHECK(eglChooseConfig(egl_display_, rgba8888, &egl_config, 1, &num_configs))
|
| - << eglGetError();
|
| - CHECK_GE(num_configs, 1);
|
| - static EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
|
| - egl_context_ = eglCreateContext(
|
| - egl_display_, egl_config, EGL_NO_CONTEXT, context_attribs);
|
| - CHECK_NE(egl_context_, EGL_NO_CONTEXT) << eglGetError();
|
| -
|
| - // Per-window/surface X11 & EGL initialization.
|
| - for (int i = 0; i < num_windows; ++i) {
|
| - // Arrange X windows whimsically, with some padding.
|
| - int top_left_x = (width + 20) * (i % 4);
|
| - int top_left_y = (height + 12) * (i % 3);
|
| - Window x_window = XCreateWindow(
|
| - x_display_, DefaultRootWindow(x_display_),
|
| - top_left_x, top_left_y, width_, height_,
|
| - 0 /* border width */,
|
| - depth, CopyFromParent /* class */, CopyFromParent /* visual */,
|
| - (CWBackPixel | CWOverrideRedirect), &window_attributes);
|
| - x_windows_.push_back(x_window);
|
| - XStoreName(x_display_, x_window, "OmxVideoDecodeAcceleratorTest");
|
| - XSelectInput(x_display_, x_window, ExposureMask);
|
| - XMapWindow(x_display_, x_window);
|
| -
|
| - EGLSurface egl_surface =
|
| - eglCreateWindowSurface(egl_display_, egl_config, x_window, NULL);
|
| - egl_surfaces_.push_back(egl_surface);
|
| - CHECK_NE(egl_surface, EGL_NO_SURFACE);
|
| - }
|
| - CHECK(eglMakeCurrent(egl_display_, egl_surfaces_[0],
|
| - egl_surfaces_[0], egl_context_)) << eglGetError();
|
| -
|
| - // GLES2 initialization. Note: This is pretty much copy/pasted from
|
| - // media/tools/player_x11/gles_video_renderer.cc, with some simplification
|
| - // applied.
|
| - static const float kVertices[] =
|
| - { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
|
| - static const float kTextureCoordsEgl[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
|
| - static const char kVertexShader[] = STRINGIZE(
|
| - varying vec2 interp_tc;
|
| - attribute vec4 in_pos;
|
| - attribute vec2 in_tc;
|
| - void main() {
|
| - interp_tc = in_tc;
|
| - gl_Position = in_pos;
|
| - }
|
| - );
|
| - static const char kFragmentShaderEgl[] = STRINGIZE(
|
| - precision mediump float;
|
| - varying vec2 interp_tc;
|
| - uniform sampler2D tex;
|
| - void main() {
|
| - gl_FragColor = texture2D(tex, interp_tc);
|
| - }
|
| - );
|
| - GLuint program = glCreateProgram();
|
| - CreateShader(program, GL_VERTEX_SHADER,
|
| - kVertexShader, arraysize(kVertexShader));
|
| - CreateShader(program, GL_FRAGMENT_SHADER,
|
| - kFragmentShaderEgl, arraysize(kFragmentShaderEgl));
|
| - glLinkProgram(program);
|
| - int result = GL_FALSE;
|
| - glGetProgramiv(program, GL_LINK_STATUS, &result);
|
| - if (!result) {
|
| - char log[4096];
|
| - glGetShaderInfoLog(program, arraysize(log), NULL, log);
|
| - LOG(FATAL) << log;
|
| - }
|
| - glUseProgram(program);
|
| - glDeleteProgram(program);
|
| -
|
| - glUniform1i(glGetUniformLocation(program, "tex"), 0);
|
| - int pos_location = glGetAttribLocation(program, "in_pos");
|
| - glEnableVertexAttribArray(pos_location);
|
| - glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
|
| - int tc_location = glGetAttribLocation(program, "in_tc");
|
| - glEnableVertexAttribArray(tc_location);
|
| - glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0,
|
| - kTextureCoordsEgl);
|
| -
|
| - done->Signal();
|
| -}
|
| -
|
| -void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
|
| - CHECK_EQ(MessageLoop::current(), message_loop_);
|
| - // Destroy resources acquired in Initialize, in reverse-acquisition order.
|
| - CHECK(eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
| - EGL_NO_CONTEXT)) << eglGetError();
|
| - CHECK(eglDestroyContext(egl_display_, egl_context_));
|
| - for (size_t i = 0; i < egl_surfaces_.size(); ++i)
|
| - CHECK(eglDestroySurface(egl_display_, egl_surfaces_[i]));
|
| - CHECK(eglTerminate(egl_display_));
|
| - for (size_t i = 0; i < x_windows_.size(); ++i) {
|
| - CHECK(XUnmapWindow(x_display_, x_windows_[i]));
|
| - CHECK(XDestroyWindow(x_display_, x_windows_[i]));
|
| - }
|
| - // Mimic newly-created object.
|
| - Clear();
|
| - done->Signal();
|
| -}
|
| -
|
| -void RenderingHelper::CreateTexture(int window_id, GLuint* texture_id,
|
| - base::WaitableEvent* done) {
|
| - if (MessageLoop::current() != message_loop_) {
|
| - message_loop_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RenderingHelper::CreateTexture, base::Unretained(this),
|
| - window_id, texture_id, done));
|
| - return;
|
| - }
|
| - CHECK(eglMakeCurrent(egl_display_, egl_surfaces_[window_id],
|
| - egl_surfaces_[window_id], egl_context_))
|
| - << eglGetError();
|
| - glGenTextures(1, texture_id);
|
| - glBindTexture(GL_TEXTURE_2D, *texture_id);
|
| - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA,
|
| - GL_UNSIGNED_BYTE, NULL);
|
| - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
| - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
| - // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
|
| - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
| - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
| - CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
|
| - CHECK(texture_id_to_surface_index_.insert(
|
| - std::make_pair(*texture_id, window_id)).second);
|
| - done->Signal();
|
| -}
|
| -
|
| -void RenderingHelper::RenderTexture(GLuint texture_id) {
|
| - CHECK_EQ(MessageLoop::current(), message_loop_);
|
| - glActiveTexture(GL_TEXTURE0);
|
| - glBindTexture(GL_TEXTURE_2D, texture_id);
|
| - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
| - CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
|
| - CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
|
| - if (!suppress_swap_to_display_) {
|
| - int window_id = texture_id_to_surface_index_[texture_id];
|
| - CHECK(eglMakeCurrent(egl_display_, egl_surfaces_[window_id],
|
| - egl_surfaces_[window_id], egl_context_))
|
| - << eglGetError();
|
| - eglSwapBuffers(egl_display_, egl_surfaces_[window_id]);
|
| - }
|
| - CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
|
| -}
|
| -
|
| -void RenderingHelper::DeleteTexture(GLuint texture_id) {
|
| - glDeleteTextures(1, &texture_id);
|
| - CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
|
| -}
|
| -
|
| -// State of the EglRenderingVDAClient below. Order matters here as the test
|
| -// makes assumptions about it.
|
| -enum ClientState {
|
| - CS_CREATED,
|
| - CS_DECODER_SET,
|
| - CS_INITIALIZED,
|
| - CS_FLUSHING,
|
| - CS_FLUSHED,
|
| - CS_DONE,
|
| - CS_RESETTING,
|
| - CS_RESET,
|
| - CS_ERROR,
|
| - CS_DESTROYED,
|
| - CS_MAX, // Must be last entry.
|
| -};
|
| -
|
| -// Helper class allowing one thread to wait on a notification from another.
|
| -// If notifications come in faster than they are Wait()'d for, they are
|
| -// accumulated (so exactly as many Wait() calls will unblock as Notify() calls
|
| -// were made, regardless of order).
|
| -class ClientStateNotification {
|
| - public:
|
| - ClientStateNotification();
|
| - ~ClientStateNotification();
|
| -
|
| - // Used to notify a single waiter of a ClientState.
|
| - void Notify(ClientState state);
|
| - // Used by waiters to wait for the next ClientState Notification.
|
| - ClientState Wait();
|
| - private:
|
| - base::Lock lock_;
|
| - base::ConditionVariable cv_;
|
| - std::queue<ClientState> pending_states_for_notification_;
|
| -};
|
| -
|
| -ClientStateNotification::ClientStateNotification() : cv_(&lock_) {}
|
| -
|
| -ClientStateNotification::~ClientStateNotification() {}
|
| -
|
| -void ClientStateNotification::Notify(ClientState state) {
|
| - base::AutoLock auto_lock(lock_);
|
| - pending_states_for_notification_.push(state);
|
| - cv_.Signal();
|
| -}
|
| -
|
| -ClientState ClientStateNotification::Wait() {
|
| - base::AutoLock auto_lock(lock_);
|
| - while (pending_states_for_notification_.empty())
|
| - cv_.Wait();
|
| - ClientState ret = pending_states_for_notification_.front();
|
| - pending_states_for_notification_.pop();
|
| - return ret;
|
| -}
|
| -
|
| -// Magic constants for differentiating the reasons for NotifyResetDone being
|
| -// called.
|
| -enum ResetPoint {
|
| - MID_STREAM_RESET = -2,
|
| - END_OF_STREAM_RESET = -1
|
| -};
|
| -
|
| -// Client that can accept callbacks from a VideoDecodeAccelerator and is used by
|
| -// the TESTs below.
|
| -class EglRenderingVDAClient : public VideoDecodeAccelerator::Client {
|
| - public:
|
| - // Doesn't take ownership of |rendering_helper| or |note|, which must outlive
|
| - // |*this|.
|
| - // |reset_after_frame_num| can be a frame number >=0 indicating a mid-stream
|
| - // Reset() should be done after that frame number is delivered, or
|
| - // END_OF_STREAM_RESET to indicate no mid-stream Reset().
|
| - // |delete_decoder_state| indicates when the underlying decoder should be
|
| - // Destroy()'d and deleted and can take values: N<0: delete after -N Decode()
|
| - // calls have been made, N>=0 means interpret as ClientState.
|
| - EglRenderingVDAClient(RenderingHelper* rendering_helper,
|
| - int rendering_window_id,
|
| - ClientStateNotification* note,
|
| - const std::string& encoded_data,
|
| - int num_NALUs_per_decode,
|
| - int num_in_flight_decodes,
|
| - int reset_after_frame_num,
|
| - int delete_decoder_state,
|
| - int profile);
|
| - virtual ~EglRenderingVDAClient();
|
| - void CreateDecoder();
|
| -
|
| - // VideoDecodeAccelerator::Client implementation.
|
| - // The heart of the Client.
|
| - virtual void ProvidePictureBuffers(
|
| - uint32 requested_num_of_buffers,
|
| - const gfx::Size& dimensions);
|
| - virtual void DismissPictureBuffer(int32 picture_buffer_id);
|
| - virtual void PictureReady(const media::Picture& picture);
|
| - // Simple state changes.
|
| - virtual void NotifyInitializeDone();
|
| - virtual void NotifyEndOfStream();
|
| - virtual void NotifyEndOfBitstreamBuffer(int32 bitstream_buffer_id);
|
| - virtual void NotifyFlushDone();
|
| - virtual void NotifyResetDone();
|
| - virtual void NotifyError(VideoDecodeAccelerator::Error error);
|
| -
|
| - // Simple getters for inspecting the state of the Client.
|
| - ClientState state() { return state_; }
|
| - int num_done_bitstream_buffers() { return num_done_bitstream_buffers_; }
|
| - int num_decoded_frames() { return num_decoded_frames_; }
|
| - EGLDisplay egl_display() { return rendering_helper_->egl_display(); }
|
| - EGLContext egl_context() { return rendering_helper_->egl_context(); }
|
| - double frames_per_second();
|
| - bool decoder_deleted() { return !decoder_; }
|
| -
|
| - private:
|
| - typedef std::map<int, media::PictureBuffer*> PictureBufferById;
|
| -
|
| - void SetState(ClientState new_state);
|
| -
|
| - // Delete the associated OMX decoder helper.
|
| - void DeleteDecoder();
|
| -
|
| - // Compute & return in |*end_pos| the end position for the next batch of NALUs
|
| - // to ship to the decoder (based on |start_pos| & |num_NALUs_per_decode_|).
|
| - void GetRangeForNextNALUs(size_t start_pos, size_t* end_pos);
|
| -
|
| - // Request decode of the next batch of NALUs in the encoded data.
|
| - void DecodeNextNALUs();
|
| -
|
| - RenderingHelper* rendering_helper_;
|
| - int rendering_window_id_;
|
| - std::string encoded_data_;
|
| - const int num_NALUs_per_decode_;
|
| - const int num_in_flight_decodes_;
|
| - int outstanding_decodes_;
|
| - size_t encoded_data_next_pos_to_decode_;
|
| - int next_bitstream_buffer_id_;
|
| - ClientStateNotification* note_;
|
| - scoped_refptr<OmxVideoDecodeAccelerator> decoder_;
|
| - std::set<int> outstanding_texture_ids_;
|
| - int reset_after_frame_num_;
|
| - int delete_decoder_state_;
|
| - ClientState state_;
|
| - int num_decoded_frames_;
|
| - int num_done_bitstream_buffers_;
|
| - PictureBufferById picture_buffers_by_id_;
|
| - base::TimeTicks initialize_done_ticks_;
|
| - base::TimeTicks last_frame_delivered_ticks_;
|
| - int profile_;
|
| -};
|
| -
|
| -EglRenderingVDAClient::EglRenderingVDAClient(
|
| - RenderingHelper* rendering_helper,
|
| - int rendering_window_id,
|
| - ClientStateNotification* note,
|
| - const std::string& encoded_data,
|
| - int num_NALUs_per_decode,
|
| - int num_in_flight_decodes,
|
| - int reset_after_frame_num,
|
| - int delete_decoder_state,
|
| - int profile)
|
| - : rendering_helper_(rendering_helper),
|
| - rendering_window_id_(rendering_window_id),
|
| - encoded_data_(encoded_data), num_NALUs_per_decode_(num_NALUs_per_decode),
|
| - num_in_flight_decodes_(num_in_flight_decodes), outstanding_decodes_(0),
|
| - encoded_data_next_pos_to_decode_(0), next_bitstream_buffer_id_(0),
|
| - note_(note), reset_after_frame_num_(reset_after_frame_num),
|
| - delete_decoder_state_(delete_decoder_state),
|
| - state_(CS_CREATED),
|
| - num_decoded_frames_(0), num_done_bitstream_buffers_(0),
|
| - profile_(profile) {
|
| - CHECK_GT(num_NALUs_per_decode, 0);
|
| - CHECK_GT(num_in_flight_decodes, 0);
|
| -}
|
| -
|
| -EglRenderingVDAClient::~EglRenderingVDAClient() {
|
| - DeleteDecoder(); // Clean up in case of expected error.
|
| - CHECK(decoder_deleted());
|
| - STLDeleteValues(&picture_buffers_by_id_);
|
| - SetState(CS_DESTROYED);
|
| -}
|
| -
|
| -void EglRenderingVDAClient::CreateDecoder() {
|
| - CHECK(decoder_deleted());
|
| - decoder_ = new OmxVideoDecodeAccelerator(this);
|
| - decoder_->SetEglState(egl_display(), egl_context());
|
| - SetState(CS_DECODER_SET);
|
| - if (decoder_deleted())
|
| - return;
|
| -
|
| - // Configure the decoder.
|
| - media::VideoDecodeAccelerator::Profile profile = media::H264PROFILE_BASELINE;
|
| - if (profile_ != -1)
|
| - profile = static_cast<media::VideoDecodeAccelerator::Profile>(profile_);
|
| - CHECK(decoder_->Initialize(profile));
|
| -}
|
| -
|
| -void EglRenderingVDAClient::ProvidePictureBuffers(
|
| - uint32 requested_num_of_buffers,
|
| - const gfx::Size& dimensions) {
|
| - if (decoder_deleted())
|
| - return;
|
| - std::vector<media::PictureBuffer> buffers;
|
| -
|
| - for (uint32 i = 0; i < requested_num_of_buffers; ++i) {
|
| - uint32 id = picture_buffers_by_id_.size();
|
| - GLuint texture_id;
|
| - base::WaitableEvent done(false, false);
|
| - rendering_helper_->CreateTexture(rendering_window_id_, &texture_id, &done);
|
| - done.Wait();
|
| - CHECK(outstanding_texture_ids_.insert(texture_id).second);
|
| - media::PictureBuffer* buffer =
|
| - new media::PictureBuffer(id, dimensions, texture_id);
|
| - CHECK(picture_buffers_by_id_.insert(std::make_pair(id, buffer)).second);
|
| - buffers.push_back(*buffer);
|
| - }
|
| - decoder_->AssignPictureBuffers(buffers);
|
| - CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
|
| - CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
|
| -}
|
| -
|
| -void EglRenderingVDAClient::DismissPictureBuffer(int32 picture_buffer_id) {
|
| - PictureBufferById::iterator it =
|
| - picture_buffers_by_id_.find(picture_buffer_id);
|
| - CHECK(it != picture_buffers_by_id_.end());
|
| - CHECK_EQ(outstanding_texture_ids_.erase(it->second->texture_id()), 1U);
|
| - rendering_helper_->DeleteTexture(it->second->texture_id());
|
| - delete it->second;
|
| - picture_buffers_by_id_.erase(it);
|
| -}
|
| -
|
| -void EglRenderingVDAClient::PictureReady(const media::Picture& picture) {
|
| - // We shouldn't be getting pictures delivered after Reset has completed.
|
| - CHECK_LT(state_, CS_RESET);
|
| -
|
| - if (decoder_deleted())
|
| - return;
|
| - last_frame_delivered_ticks_ = base::TimeTicks::Now();
|
| -
|
| - // Because we feed the decoder a limited number of NALUs at a time, we can be
|
| - // sure that the bitstream buffer from which a frame comes has a limited
|
| - // range. Assert that.
|
| - CHECK_GE((picture.bitstream_buffer_id() + 1) * num_NALUs_per_decode_,
|
| - num_decoded_frames_);
|
| - CHECK_LE(picture.bitstream_buffer_id(), next_bitstream_buffer_id_);
|
| - ++num_decoded_frames_;
|
| -
|
| - if (reset_after_frame_num_ == num_decoded_frames_) {
|
| - reset_after_frame_num_ = MID_STREAM_RESET;
|
| - decoder_->Reset();
|
| - // Re-start decoding from the beginning of the stream to avoid needing to
|
| - // know how to find I-frames and so on in this test.
|
| - encoded_data_next_pos_to_decode_ = 0;
|
| - }
|
| -
|
| - media::PictureBuffer* picture_buffer =
|
| - picture_buffers_by_id_[picture.picture_buffer_id()];
|
| - CHECK(picture_buffer);
|
| - rendering_helper_->RenderTexture(picture_buffer->texture_id());
|
| -
|
| - decoder_->ReusePictureBuffer(picture.picture_buffer_id());
|
| -}
|
| -
|
| -void EglRenderingVDAClient::NotifyInitializeDone() {
|
| - SetState(CS_INITIALIZED);
|
| - initialize_done_ticks_ = base::TimeTicks::Now();
|
| - for (int i = 0; i < num_in_flight_decodes_; ++i)
|
| - DecodeNextNALUs();
|
| -}
|
| -
|
| -void EglRenderingVDAClient::NotifyEndOfStream() {
|
| - SetState(CS_DONE);
|
| -}
|
| -
|
| -void EglRenderingVDAClient::NotifyEndOfBitstreamBuffer(
|
| - int32 bitstream_buffer_id) {
|
| - ++num_done_bitstream_buffers_;
|
| - --outstanding_decodes_;
|
| - DecodeNextNALUs();
|
| -}
|
| -
|
| -void EglRenderingVDAClient::NotifyFlushDone() {
|
| - if (decoder_deleted())
|
| - return;
|
| - SetState(CS_FLUSHED);
|
| - if (decoder_deleted())
|
| - return;
|
| - decoder_->Reset();
|
| - SetState(CS_RESETTING);
|
| -}
|
| -
|
| -void EglRenderingVDAClient::NotifyResetDone() {
|
| - if (decoder_deleted())
|
| - return;
|
| - if (reset_after_frame_num_ == MID_STREAM_RESET) {
|
| - reset_after_frame_num_ = END_OF_STREAM_RESET;
|
| - return;
|
| - }
|
| - SetState(CS_RESET);
|
| - if (!decoder_deleted())
|
| - DeleteDecoder();
|
| -}
|
| -
|
| -void EglRenderingVDAClient::NotifyError(VideoDecodeAccelerator::Error error) {
|
| - SetState(CS_ERROR);
|
| -}
|
| -
|
| -static bool LookingAtNAL(const std::string& encoded, size_t pos) {
|
| - return pos + 3 < encoded.size() &&
|
| - encoded[pos] == 0 && encoded[pos + 1] == 0 &&
|
| - encoded[pos + 2] == 0 && encoded[pos + 3] == 1;
|
| -}
|
| -
|
| -void EglRenderingVDAClient::SetState(ClientState new_state) {
|
| - note_->Notify(new_state);
|
| - state_ = new_state;
|
| - if (new_state == delete_decoder_state_) {
|
| - CHECK(!decoder_deleted());
|
| - DeleteDecoder();
|
| - }
|
| -}
|
| -
|
| -void EglRenderingVDAClient::DeleteDecoder() {
|
| - if (decoder_deleted())
|
| - return;
|
| - decoder_->Destroy();
|
| - decoder_ = NULL;
|
| - STLClearObject(&encoded_data_);
|
| - for (std::set<int>::iterator it = outstanding_texture_ids_.begin();
|
| - it != outstanding_texture_ids_.end(); ++it) {
|
| - rendering_helper_->DeleteTexture(*it);
|
| - }
|
| - outstanding_texture_ids_.clear();
|
| - // Cascade through the rest of the states to simplify test code below.
|
| - for (int i = state_ + 1; i < CS_MAX; ++i)
|
| - SetState(static_cast<ClientState>(i));
|
| -}
|
| -
|
| -void EglRenderingVDAClient::GetRangeForNextNALUs(
|
| - size_t start_pos, size_t* end_pos) {
|
| - *end_pos = start_pos;
|
| - CHECK(LookingAtNAL(encoded_data_, start_pos));
|
| - for (int i = 0; i < num_NALUs_per_decode_; ++i) {
|
| - *end_pos += 4;
|
| - while (*end_pos + 3 < encoded_data_.size() &&
|
| - !LookingAtNAL(encoded_data_, *end_pos)) {
|
| - ++*end_pos;
|
| - }
|
| - if (*end_pos + 3 >= encoded_data_.size()) {
|
| - *end_pos = encoded_data_.size();
|
| - return;
|
| - }
|
| - }
|
| -}
|
| -
|
| -void EglRenderingVDAClient::DecodeNextNALUs() {
|
| - if (decoder_deleted())
|
| - return;
|
| - if (encoded_data_next_pos_to_decode_ == encoded_data_.size()) {
|
| - if (outstanding_decodes_ == 0) {
|
| - decoder_->Flush();
|
| - SetState(CS_FLUSHING);
|
| - }
|
| - return;
|
| - }
|
| - size_t start_pos = encoded_data_next_pos_to_decode_;
|
| - size_t end_pos;
|
| - GetRangeForNextNALUs(start_pos, &end_pos);
|
| -
|
| - // Populate the shared memory buffer w/ the NALU, duplicate its handle, and
|
| - // hand it off to the decoder.
|
| - base::SharedMemory shm;
|
| - CHECK(shm.CreateAndMapAnonymous(end_pos - start_pos))
|
| - << start_pos << ", " << end_pos;
|
| - memcpy(shm.memory(), encoded_data_.data() + start_pos, end_pos - start_pos);
|
| - base::SharedMemoryHandle dup_handle;
|
| - CHECK(shm.ShareToProcess(base::Process::Current().handle(), &dup_handle));
|
| - media::BitstreamBuffer bitstream_buffer(
|
| - next_bitstream_buffer_id_++, dup_handle, end_pos - start_pos);
|
| - decoder_->Decode(bitstream_buffer);
|
| - ++outstanding_decodes_;
|
| - encoded_data_next_pos_to_decode_ = end_pos;
|
| -
|
| - if (-delete_decoder_state_ == next_bitstream_buffer_id_)
|
| - DeleteDecoder();
|
| -}
|
| -
|
| -double EglRenderingVDAClient::frames_per_second() {
|
| - base::TimeDelta delta = last_frame_delivered_ticks_ - initialize_done_ticks_;
|
| - if (delta.InSecondsF() == 0)
|
| - return 0;
|
| - return num_decoded_frames_ / delta.InSecondsF();
|
| -}
|
| -
|
| -// Test parameters:
|
| -// - Number of NALUs per Decode() call.
|
| -// - Number of concurrent decoders.
|
| -// - Number of concurrent in-flight Decode() calls per decoder.
|
| -// - reset_after_frame_num: see EglRenderingVDAClient ctor.
|
| -// - delete_decoder_phase: see EglRenderingVDAClient ctor.
|
| -class OmxVideoDecodeAcceleratorTest
|
| - : public ::testing::TestWithParam<
|
| - Tuple5<int, int, int, ResetPoint, ClientState> > {
|
| -};
|
| -
|
| -// Wait for |note| to report a state and if it's not |expected_state| then
|
| -// assert |client| has deleted its decoder.
|
| -static void AssertWaitForStateOrDeleted(ClientStateNotification* note,
|
| - EglRenderingVDAClient* client,
|
| - ClientState expected_state) {
|
| - ClientState state = note->Wait();
|
| - if (state == expected_state) return;
|
| - ASSERT_TRUE(client->decoder_deleted())
|
| - << "Decoder not deleted but Wait() returned " << state
|
| - << ", instead of " << expected_state;
|
| -}
|
| -
|
| -// We assert a minimal number of concurrent decoders we expect to succeed.
|
| -// Different platforms can support more concurrent decoders, so we don't assert
|
| -// failure above this.
|
| -enum { kMinSupportedNumConcurrentDecoders = 3 };
|
| -
|
| -// Test the most straightforward case possible: data is decoded from a single
|
| -// chunk and rendered to the screen.
|
| -TEST_P(OmxVideoDecodeAcceleratorTest, TestSimpleDecode) {
|
| - // Can be useful for debugging VLOGs from OVDA.
|
| - // logging::SetMinLogLevel(-1);
|
| -
|
| - // Required for Thread to work. Not used otherwise.
|
| - base::ShadowingAtExitManager at_exit_manager;
|
| -
|
| - const int num_NALUs_per_decode = GetParam().a;
|
| - const size_t num_concurrent_decoders = GetParam().b;
|
| - const size_t num_in_flight_decodes = GetParam().c;
|
| - const int reset_after_frame_num = GetParam().d;
|
| - const int delete_decoder_state = GetParam().e;
|
| -
|
| - std::string test_video_file;
|
| - int frame_width, frame_height;
|
| - int num_frames, num_NALUs, min_fps_render, min_fps_no_render, profile;
|
| - ParseTestVideoData(test_video_data, &test_video_file, &frame_width,
|
| - &frame_height, &num_frames, &num_NALUs,
|
| - &min_fps_render, &min_fps_no_render, &profile);
|
| - min_fps_render /= num_concurrent_decoders;
|
| - min_fps_no_render /= num_concurrent_decoders;
|
| -
|
| - // If we reset mid-stream and start playback over, account for frames that are
|
| - // decoded twice in our expectations.
|
| - if (num_frames > 0 && reset_after_frame_num >= 0)
|
| - num_frames += reset_after_frame_num;
|
| -
|
| - // Suppress EGL surface swapping in all but a few tests, to cut down overall
|
| - // test runtime.
|
| - const bool suppress_swap_to_display = num_NALUs_per_decode > 1;
|
| -
|
| - std::vector<ClientStateNotification*> notes(num_concurrent_decoders, NULL);
|
| - std::vector<EglRenderingVDAClient*> clients(num_concurrent_decoders, NULL);
|
| -
|
| - // Read in the video data.
|
| - std::string data_str;
|
| - CHECK(file_util::ReadFileToString(FilePath(test_video_file), &data_str));
|
| -
|
| - // Initialize the rendering helper.
|
| - base::Thread rendering_thread("EglRenderingVDAClientThread");
|
| - rendering_thread.Start();
|
| - RenderingHelper rendering_helper;
|
| -
|
| - base::WaitableEvent done(false, false);
|
| - rendering_thread.message_loop()->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RenderingHelper::Initialize,
|
| - base::Unretained(&rendering_helper),
|
| - suppress_swap_to_display, num_concurrent_decoders,
|
| - frame_width, frame_height, &done));
|
| - done.Wait();
|
| -
|
| - // First kick off all the decoders.
|
| - for (size_t index = 0; index < num_concurrent_decoders; ++index) {
|
| - ClientStateNotification* note = new ClientStateNotification();
|
| - notes[index] = note;
|
| - EglRenderingVDAClient* client = new EglRenderingVDAClient(
|
| - &rendering_helper, index,
|
| - note, data_str, num_NALUs_per_decode,
|
| - num_in_flight_decodes,
|
| - reset_after_frame_num, delete_decoder_state, profile);
|
| - clients[index] = client;
|
| -
|
| - rendering_thread.message_loop()->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&EglRenderingVDAClient::CreateDecoder,
|
| - base::Unretained(client)));
|
| -
|
| - ASSERT_EQ(note->Wait(), CS_DECODER_SET);
|
| - }
|
| - // Then wait for all the decodes to finish.
|
| - bool saw_init_failure = false;
|
| - for (size_t i = 0; i < num_concurrent_decoders; ++i) {
|
| - ClientStateNotification* note = notes[i];
|
| - ClientState state = note->Wait();
|
| - if (state != CS_INITIALIZED) {
|
| - saw_init_failure = true;
|
| - // We expect initialization to fail only when more than the supported
|
| - // number of decoders is instantiated. Assert here that something else
|
| - // didn't trigger failure.
|
| - ASSERT_GT(num_concurrent_decoders, kMinSupportedNumConcurrentDecoders);
|
| - continue;
|
| - }
|
| - ASSERT_EQ(state, CS_INITIALIZED);
|
| - // InitializeDone kicks off decoding inside the client, so we just need to
|
| - // wait for Flush.
|
| - ASSERT_NO_FATAL_FAILURE(
|
| - AssertWaitForStateOrDeleted(note, clients[i], CS_FLUSHING));
|
| - ASSERT_NO_FATAL_FAILURE(
|
| - AssertWaitForStateOrDeleted(note, clients[i], CS_FLUSHED));
|
| - // FlushDone requests Reset().
|
| - ASSERT_NO_FATAL_FAILURE(
|
| - AssertWaitForStateOrDeleted(note, clients[i], CS_RESETTING));
|
| - ASSERT_NO_FATAL_FAILURE(
|
| - AssertWaitForStateOrDeleted(note, clients[i], CS_RESET));
|
| - // ResetDone requests Destroy().
|
| - ASSERT_NO_FATAL_FAILURE(
|
| - AssertWaitForStateOrDeleted(note, clients[i], CS_DESTROYED));
|
| - }
|
| - // Finally assert that decoding went as expected.
|
| - for (size_t i = 0; i < num_concurrent_decoders && !saw_init_failure; ++i) {
|
| - // We can only make performance/correctness assertions if the decoder was
|
| - // allowed to finish.
|
| - if (delete_decoder_state < CS_FLUSHED)
|
| - continue;
|
| - EglRenderingVDAClient* client = clients[i];
|
| - if (num_frames > 0)
|
| - EXPECT_EQ(client->num_decoded_frames(), num_frames);
|
| - if (num_NALUs > 0 && reset_after_frame_num < 0) {
|
| - EXPECT_EQ(client->num_done_bitstream_buffers(),
|
| - ceil(static_cast<double>(num_NALUs) / num_NALUs_per_decode));
|
| - }
|
| - LOG(INFO) << "Decoder " << i << " fps: " << client->frames_per_second();
|
| - int min_fps = suppress_swap_to_display ? min_fps_no_render : min_fps_render;
|
| - if (min_fps > 0)
|
| - EXPECT_GT(client->frames_per_second(), min_fps);
|
| - }
|
| -
|
| - rendering_thread.message_loop()->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&STLDeleteElements<std::vector<EglRenderingVDAClient*> >,
|
| - &clients));
|
| - rendering_thread.message_loop()->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&STLDeleteElements<std::vector<ClientStateNotification*> >,
|
| - ¬es));
|
| - rendering_thread.message_loop()->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RenderingHelper::UnInitialize,
|
| - base::Unretained(&rendering_helper),
|
| - &done));
|
| - done.Wait();
|
| - rendering_thread.Stop();
|
| -};
|
| -
|
| -// Test that Reset() mid-stream works fine and doesn't affect decoding even when
|
| -// Decode() calls are made during the reset.
|
| -INSTANTIATE_TEST_CASE_P(
|
| - MidStreamReset, OmxVideoDecodeAcceleratorTest,
|
| - ::testing::Values(
|
| - MakeTuple(1, 1, 1, static_cast<ResetPoint>(100), CS_RESET)));
|
| -
|
| -// Test that Destroy() mid-stream works fine (primarily this is testing that no
|
| -// crashes occur).
|
| -INSTANTIATE_TEST_CASE_P(
|
| - TearDownTiming, OmxVideoDecodeAcceleratorTest,
|
| - ::testing::Values(
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_DECODER_SET),
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_INITIALIZED),
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_FLUSHING),
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_FLUSHED),
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_RESETTING),
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_RESET),
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, static_cast<ClientState>(-1)),
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, static_cast<ClientState>(-10)),
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET,
|
| - static_cast<ClientState>(-100))));
|
| -
|
| -// Test that decoding various variation works: multiple concurrent decoders and
|
| -// multiple NALUs per Decode() call.
|
| -INSTANTIATE_TEST_CASE_P(
|
| - DecodeVariations, OmxVideoDecodeAcceleratorTest,
|
| - ::testing::Values(
|
| - MakeTuple(1, 1, 1, END_OF_STREAM_RESET, CS_RESET),
|
| - MakeTuple(1, 1, 10, END_OF_STREAM_RESET, CS_RESET),
|
| - MakeTuple(1, 1, 15, END_OF_STREAM_RESET, CS_RESET), // Tests queuing.
|
| - MakeTuple(1, 3, 1, END_OF_STREAM_RESET, CS_RESET),
|
| - MakeTuple(2, 1, 1, END_OF_STREAM_RESET, CS_RESET),
|
| - MakeTuple(3, 1, 1, END_OF_STREAM_RESET, CS_RESET),
|
| - MakeTuple(5, 1, 1, END_OF_STREAM_RESET, CS_RESET),
|
| - MakeTuple(8, 1, 1, END_OF_STREAM_RESET, CS_RESET),
|
| - // TODO(fischman): decoding more than 15 NALUs at once breaks decode -
|
| - // visual artifacts are introduced as well as spurious frames are
|
| - // delivered (more pictures are returned than NALUs are fed to the
|
| - // decoder). Increase the "15" below when
|
| - // http://code.google.com/p/chrome-os-partner/issues/detail?id=4378 is
|
| - // fixed.
|
| - MakeTuple(15, 1, 1, END_OF_STREAM_RESET, CS_RESET)));
|
| -
|
| -// Find out how many concurrent decoders can go before we exhaust system
|
| -// resources.
|
| -INSTANTIATE_TEST_CASE_P(
|
| - ResourceExhaustion, OmxVideoDecodeAcceleratorTest,
|
| - ::testing::Values(
|
| - // +0 hack below to promote enum to int.
|
| - MakeTuple(1, kMinSupportedNumConcurrentDecoders + 0, 1,
|
| - END_OF_STREAM_RESET, CS_RESET),
|
| - MakeTuple(1, kMinSupportedNumConcurrentDecoders + 1, 1,
|
| - END_OF_STREAM_RESET, CS_RESET)));
|
| -
|
| -// TODO(fischman, vrk): add more tests! In particular:
|
| -// - Test life-cycle: Seek/Stop/Pause/Play/RePlay for a single decoder.
|
| -// - Test alternate configurations
|
| -// - Test failure conditions.
|
| -// - Test frame size changes mid-stream
|
| -
|
| -} // namespace
|
| -
|
| -int main(int argc, char **argv) {
|
| - testing::InitGoogleTest(&argc, argv); // Removes gtest-specific args.
|
| - CommandLine cmd_line(argc, argv); // Must run after InitGoogleTest.
|
| - CommandLine::SwitchMap switches = cmd_line.GetSwitches();
|
| - for (CommandLine::SwitchMap::const_iterator it = switches.begin();
|
| - it != switches.end(); ++it) {
|
| - if (it->first == "test_video_data") {
|
| - test_video_data = it->second.c_str();
|
| - continue;
|
| - }
|
| - LOG(FATAL) << "Unexpected switch: " << it->first << ":" << it->second;
|
| - }
|
| -
|
| - return RUN_ALL_TESTS();
|
| -}
|
|
|