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

Unified Diff: content/common/gpu/omx_video_decode_accelerator_unittest.cc

Issue 7088021: OmxVideoDecodeAcceleratorTest is born! (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 9 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « content/common/gpu/omx_video_decode_accelerator.cc ('k') | content/common/gpu/testdata/test-25fps.h264 » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: content/common/gpu/omx_video_decode_accelerator_unittest.cc
diff --git a/content/common/gpu/omx_video_decode_accelerator_unittest.cc b/content/common/gpu/omx_video_decode_accelerator_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6f29638cae94bbf27f900ddeb26154530e807a7c
--- /dev/null
+++ b/content/common/gpu/omx_video_decode_accelerator_unittest.cc
@@ -0,0 +1,540 @@
+// 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.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.
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "base/at_exit.h"
+#include "base/file_util.h"
+#include "base/stl_util-inl.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/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 {
+
+// General-purpose constants for this test.
+enum {
+ kFrameWidth = 320,
+ kFrameHeight = 240,
+};
+
+// Helper for managing X11, EGL, and GLES2 resources. Because GL state is
+// thread-specific, all the methods of this class (except for ctor/dtor) CHECK
+// for 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 a window of the specified
+ // dimensions. CHECK-fails if any initialization step fails. After this
+ // returns, texture creation and rendering (swaps) can be requested.
+ // This method can be called multiple times, in which case all
+ // previously-acquired resources and initializations are discarded.
+ void Initialize(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.
+ GLuint CreateTexture();
+
+ // Render |texture_id| to the screen.
+ void RenderTexture(GLuint texture_id);
+
+ EGLDisplay egl_display() { return egl_display_; }
+ EGLContext egl_context() { return egl_context_; }
+
+ private:
+ int width_;
+ int height_;
+ Display* x_display_;
+ Window x_window_;
+ EGLDisplay egl_display_;
+ EGLContext egl_context_;
+ EGLSurface egl_surface_;
+ // Since GL carries per-thread state, we ensure all operations are carried out
+ // on the same thread by remembering where we were Initialized.
+ MessageLoop* message_loop_;
+};
+
+RenderingHelper::RenderingHelper() {
+ memset(this, 0, sizeof(this));
+}
+
+RenderingHelper::~RenderingHelper() {
+ CHECK_EQ(width_, 0) << "Must call UnInitialize before dtor.";
+}
+
+// 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(
+ 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();
+ }
+
+ CHECK_GT(width, 0);
+ CHECK_GT(height, 0);
+ width_ = width;
+ height_ = height;
+ message_loop_ = MessageLoop::current();
+
+ // X11 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;
+ x_window_ = XCreateWindow(
+ x_display_, DefaultRootWindow(x_display_),
+ 100, 100, /* x/y of top-left corner */
+ width_, height_,
+ 0 /* border width */,
+ depth, CopyFromParent /* class */, CopyFromParent /* visual */,
+ (CWBackPixel | CWOverrideRedirect), &window_attributes);
+ XStoreName(x_display_, x_window_, "OmxVideoDecodeAcceleratorTest");
+ XSelectInput(x_display_, x_window_, ExposureMask);
+ XMapWindow(x_display_, x_window_);
+
+ // EGL initialization.
+ egl_display_ = eglGetDisplay(x_display_);
+ EGLint major;
+ EGLint minor;
+ CHECK(eglInitialize(egl_display_, &major, &minor)) << eglGetError();
+ 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);
+ egl_surface_ =
+ eglCreateWindowSurface(egl_display_, egl_config, x_window_, NULL);
+ CHECK_NE(egl_surface_, EGL_NO_SURFACE);
+ EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+ egl_context_ = eglCreateContext(
+ egl_display_, egl_config, EGL_NO_CONTEXT, context_attribs);
+ CHECK(eglMakeCurrent(egl_display_, egl_surface_, egl_surface_, 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, sizeof(kVertexShader));
+ CreateShader(program, GL_FRAGMENT_SHADER,
+ kFragmentShaderEgl, sizeof(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));
+ CHECK(eglDestroyContext(egl_display_, egl_context_));
+ CHECK(eglDestroySurface(egl_display_, egl_surface_));
+ CHECK(eglTerminate(egl_display_));
+ CHECK(XUnmapWindow(x_display_, x_window_));
+ CHECK(XDestroyWindow(x_display_, x_window_));
+ // Mimic newly-created object.
+ memset(this, 0, sizeof(this));
+ done->Signal();
+}
+
+GLuint RenderingHelper::CreateTexture() {
+ CHECK_EQ(MessageLoop::current(), message_loop_);
+ GLuint texture_id;
+ 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);
+ CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
+ return texture_id;
+}
+
+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);
+ DCHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
+ eglSwapBuffers(egl_display_, egl_surface_);
+ DCHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
+}
+
+// State of the EglRenderingVDAClient below.
+enum ClientState {
+ CS_CREATED,
+ CS_DECODER_SET,
+ CS_INITIALIZED,
+ CS_FLUSHED,
+ CS_DONE,
+ CS_ABORTED,
+ CS_ERROR,
+ CS_DESTROYED,
+};
+
+// 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;
+}
+
+// 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 |note|, which must outlive |*this|.
+ EglRenderingVDAClient(ClientStateNotification* note);
+ virtual ~EglRenderingVDAClient();
+
+ // VideoDecodeAccelerator::Client implementation.
+ // The heart of the Client.
+ virtual void ProvidePictureBuffers(
+ uint32 requested_num_of_buffers,
+ const gfx::Size& dimensions,
+ VideoDecodeAccelerator::MemoryType type);
+ 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 NotifyAbortDone();
+ virtual void NotifyError(VideoDecodeAccelerator::Error error);
+
+ // Doesn't take ownership of |decoder|, which must outlive |*this|.
+ void SetDecoder(VideoDecodeAccelerator* decoder);
+
+ // Simple getters for inspecting the state of the Client.
+ ClientState state() { return state_; }
+ VideoDecodeAccelerator::Error error() { return error_; }
+ int num_done_bitstream_buffers() { return num_done_bitstream_buffers_; }
+ int num_decoded_frames() { return num_decoded_frames_; }
+ MessageLoop* message_loop() { return thread_.message_loop(); }
+ EGLDisplay egl_display() { return rendering_helper_.egl_display(); }
+ EGLContext egl_context() { return rendering_helper_.egl_context(); }
+
+ private:
+ void SetState(ClientState new_state) {
+ note_->Notify(new_state);
+ state_ = new_state;
+ }
+
+ VideoDecodeAccelerator* decoder_;
+ ClientStateNotification* note_;
+ ClientState state_;
+ VideoDecodeAccelerator::Error error_;
+ int num_decoded_frames_;
+ int num_done_bitstream_buffers_;
+ std::map<int, media::GLESBuffer*> picture_buffers_by_id_;
+ // Required for Thread to work. Not used otherwise.
+ base::ShadowingAtExitManager at_exit_manager_;
+ base::Thread thread_;
+ RenderingHelper rendering_helper_;
+};
+
+EglRenderingVDAClient::EglRenderingVDAClient(ClientStateNotification* note)
+ : decoder_(NULL), note_(note), state_(CS_CREATED),
+ error_(VideoDecodeAccelerator::VIDEODECODERERROR_NONE),
+ num_decoded_frames_(0), num_done_bitstream_buffers_(0),
+ thread_("EglRenderingVDAClientThread") {
+ CHECK(thread_.Start());
+ base::WaitableEvent done(false, false);
+ message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&RenderingHelper::Initialize,
+ base::Unretained(&rendering_helper_),
+ static_cast<int>(kFrameWidth), static_cast<int>(kFrameHeight),
+ &done));
+ done.Wait();
+}
+
+EglRenderingVDAClient::~EglRenderingVDAClient() {
+ base::WaitableEvent done(false, false);
+ message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&RenderingHelper::UnInitialize,
+ base::Unretained(&rendering_helper_),
+ &done));
+ done.Wait();
+ thread_.Stop();
+ STLDeleteValues(&picture_buffers_by_id_);
+ SetState(CS_DESTROYED);
+}
+
+void EglRenderingVDAClient::ProvidePictureBuffers(
+ uint32 requested_num_of_buffers,
+ const gfx::Size& dimensions,
+ VideoDecodeAccelerator::MemoryType type) {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+ CHECK_EQ(type, VideoDecodeAccelerator::PICTUREBUFFER_MEMORYTYPE_GL_TEXTURE);
+ std::vector<media::GLESBuffer> buffers;
+ CHECK_EQ(dimensions.width(), kFrameWidth);
+ CHECK_EQ(dimensions.height(), kFrameHeight);
+
+ for (uint32 i = 0; i < requested_num_of_buffers; ++i) {
+ uint32 id = picture_buffers_by_id_.size();
+ GLuint texture_id = rendering_helper_.CreateTexture();
+ // TODO(fischman): context_id is always 0. Can it be removed from the API?
+ // (since it's always inferrable from context).
+ media::GLESBuffer* buffer =
+ new media::GLESBuffer(id, dimensions, texture_id, 0 /* context_id */);
+ CHECK(picture_buffers_by_id_.insert(std::make_pair(id, buffer)).second);
+ buffers.push_back(*buffer);
+ }
+ decoder_->AssignGLESBuffers(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) {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+ delete picture_buffers_by_id_[picture_buffer_id];
+ CHECK_EQ(1U, picture_buffers_by_id_.erase(picture_buffer_id));
+}
+
+void EglRenderingVDAClient::PictureReady(const media::Picture& picture) {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+
+ ++num_decoded_frames_;
+
+ media::GLESBuffer* gles_buffer =
+ picture_buffers_by_id_[picture.picture_buffer_id()];
+ CHECK(gles_buffer);
+ rendering_helper_.RenderTexture(gles_buffer->texture_id());
+
+ decoder_->ReusePictureBuffer(picture.picture_buffer_id());
+}
+
+void EglRenderingVDAClient::NotifyInitializeDone() {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+ SetState(CS_INITIALIZED);
+}
+
+void EglRenderingVDAClient::NotifyEndOfStream() {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+ SetState(CS_DONE);
+}
+
+void EglRenderingVDAClient::NotifyEndOfBitstreamBuffer(
+ int32 bitstream_buffer_id) {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+ ++num_done_bitstream_buffers_;
+
+ // TODO(fischman): this is hokey! It should be possible to call Flush() from
+ // outside a callback.
+ if (num_done_bitstream_buffers_ == 1)
+ decoder_->Flush();
+}
+
+void EglRenderingVDAClient::NotifyFlushDone() {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+ SetState(CS_FLUSHED);
+}
+
+void EglRenderingVDAClient::NotifyAbortDone() {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+ SetState(CS_ABORTED);
+}
+
+void EglRenderingVDAClient::NotifyError(VideoDecodeAccelerator::Error error) {
+ CHECK_EQ(message_loop(), MessageLoop::current());
+ SetState(CS_ERROR);
+ error_ = error;
+}
+
+void EglRenderingVDAClient::SetDecoder(VideoDecodeAccelerator* decoder) {
+ decoder_ = decoder;
+ SetState(CS_DECODER_SET);
+}
+
+// Test the most straightforward case possible: data is decoded from a single
+// chunk and rendered to the screen.
+TEST(OmxVideoDecodeAcceleratorTest, TestSimpleDecode) {
+ logging::SetMinLogLevel(-1);
+ ClientStateNotification note;
+ ClientState state;
+ EglRenderingVDAClient client(&note);
+ OmxVideoDecodeAccelerator decoder(&client, client.message_loop());
+ client.SetDecoder(&decoder);
+ decoder.SetEglState(client.egl_display(), client.egl_context());
+ ASSERT_EQ((state = note.Wait()), CS_DECODER_SET);
+
+ int32 config_array[] = {
+ media::VIDEOATTRIBUTEKEY_BITSTREAMFORMAT_FOURCC,
+ media::VIDEOCODECFOURCC_H264,
+ media::VIDEOATTRIBUTEKEY_BITSTREAMFORMAT_WIDTH, kFrameWidth,
+ media::VIDEOATTRIBUTEKEY_BITSTREAMFORMAT_HEIGHT, kFrameHeight,
+ media::VIDEOATTRIBUTEKEY_VIDEOCOLORFORMAT, media::VIDEOCOLORFORMAT_RGBA,
+ media::VIDEOATTRIBUTEKEY_TERMINATOR
+ };
+ std::vector<uint32> config(
+ config_array, config_array + arraysize(config_array));
+ CHECK(decoder.Initialize(config));
+ ASSERT_EQ((state = note.Wait()), CS_INITIALIZED);
+
+ // Set up buffers to pass to Decode.
+ char const* data_filename = "test-25fps.h264";
+ int data_fd = open(data_filename, O_RDONLY);
+ CHECK_GE(data_fd, 0);
+ int64 file_size = -1;
+ CHECK(file_util::GetFileSize(FilePath(std::string(data_filename)),
+ &file_size));
+ base::SharedMemory data_shm(base::FileDescriptor(data_fd, false), true);
+ CHECK(data_shm.Map(file_size));
+ media::BitstreamBuffer bitstream_buffer(0, data_shm.handle(), file_size);
+
+ decoder.Decode(bitstream_buffer);
+ ASSERT_EQ((state = note.Wait()), CS_FLUSHED);
+
+ EXPECT_EQ(client.num_decoded_frames(), 25 /* fps */ * 10 /* seconds */);
+ EXPECT_EQ(client.num_done_bitstream_buffers(), 1);
+}
+
+// TODO(fischman, vrk): add more tests! In particular:
+// - Test that breaking up the data buffers into many Decode() calls works.
+// - Test decode speed. Ideally we can beat 60fps esp on simple test.mp4.
+// - Test alternate configurations
+// - Test failure conditions.
+// - Test multiple concurrent decoders going at once.
+// - Test frame size changes mid-stream
+
+} // namespace
« no previous file with comments | « content/common/gpu/omx_video_decode_accelerator.cc ('k') | content/common/gpu/testdata/test-25fps.h264 » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698