Chromium Code Reviews| 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 |
| index 70f878a03c70fce3762051fcacaa300a06e0e34c..b7db7b97f90a3264c4f87bad4a77712d6e0eb776 100644 |
| --- a/content/common/gpu/omx_video_decode_accelerator_unittest.cc |
| +++ b/content/common/gpu/omx_video_decode_accelerator_unittest.cc |
| @@ -13,9 +13,10 @@ |
| // - 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 <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 |
| @@ -48,9 +49,9 @@ enum { |
| 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. |
| +// 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 |
| @@ -60,46 +61,70 @@ class RenderingHelper { |
| 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); |
| + // 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. |
| - GLuint CreateTexture(); |
| + // 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. |
| + // Render |texture_id| to the screen (unless |suppress_swap_to_display_|). |
| void RenderTexture(GLuint texture_id); |
| EGLDisplay egl_display() { return egl_display_; } |
| EGLContext egl_context() { return egl_context_; } |
| private: |
| + // Zero-out internal state. Helper for ctor & UnInitialize(). |
| + void Clear(); |
| + |
| + bool suppress_swap_to_display_; |
| int width_; |
| int height_; |
| Display* x_display_; |
| - Window x_window_; |
| + std::vector<Window> x_windows_; |
| 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. |
| + 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() { |
| - memset(this, 0, sizeof(this)); |
| + 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) { |
| @@ -119,7 +144,10 @@ static void CreateShader( |
| } |
| void RenderingHelper::Initialize( |
| - int width, int height, base::WaitableEvent* done) { |
| + 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_) { |
| @@ -128,36 +156,26 @@ void RenderingHelper::Initialize( |
| 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(); |
| - // X11 initialization. |
| + // 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; |
| - 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[] = { |
| + static EGLint rgba8888[] = { |
| EGL_RED_SIZE, 8, |
| EGL_GREEN_SIZE, 8, |
| EGL_BLUE_SIZE, 8, |
| @@ -165,20 +183,39 @@ void RenderingHelper::Initialize( |
| 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}; |
| + static 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(); |
| + 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 side by side whimsically. |
|
vrk (LEFT CHROMIUM)
2011/06/09 20:36:11
:)
Ami GONE FROM CHROMIUM
2011/06/09 23:22:26
Done.
|
| + int top_left_x = (width + 50) * i; |
| + int top_left_y = 100 + (i % 2) * 250; |
| + 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_surface, egl_surface, egl_context_)) |
|
vrk (LEFT CHROMIUM)
2011/06/09 20:36:11
What's the reason for this eglMakeCurrent call her
Ami GONE FROM CHROMIUM
2011/06/09 23:22:26
Without it the shaders fail to compile, presumably
|
| + << eglGetError(); |
| + } |
| // GLES2 initialization. Note: This is pretty much copy/pasted from |
| // media/tools/player_x11/gles_video_renderer.cc, with some simplification |
| @@ -204,9 +241,10 @@ void RenderingHelper::Initialize( |
| } |
| ); |
| GLuint program = glCreateProgram(); |
| - CreateShader(program, GL_VERTEX_SHADER, kVertexShader, sizeof(kVertexShader)); |
| + CreateShader(program, GL_VERTEX_SHADER, |
| + kVertexShader, arraysize(kVertexShader)); |
| CreateShader(program, GL_FRAGMENT_SHADER, |
| - kFragmentShaderEgl, sizeof(kFragmentShaderEgl)); |
| + kFragmentShaderEgl, arraysize(kFragmentShaderEgl)); |
| glLinkProgram(program); |
| int result = GL_FALSE; |
| glGetProgramiv(program, GL_LINK_STATUS, &result); |
| @@ -226,35 +264,50 @@ void RenderingHelper::Initialize( |
| 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(eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, |
| + EGL_NO_CONTEXT)) << eglGetError(); |
| CHECK(eglDestroyContext(egl_display_, egl_context_)); |
| - CHECK(eglDestroySurface(egl_display_, egl_surface_)); |
| + for (size_t i = 0; i < egl_surfaces_.size(); ++i) |
| + CHECK(eglDestroySurface(egl_display_, egl_surfaces_[i])); |
| CHECK(eglTerminate(egl_display_)); |
| - CHECK(XUnmapWindow(x_display_, x_window_)); |
| - CHECK(XDestroyWindow(x_display_, x_window_)); |
| + 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. |
| - memset(this, 0, sizeof(this)); |
| + Clear(); |
| done->Signal(); |
| } |
| -GLuint RenderingHelper::CreateTexture() { |
| - CHECK_EQ(MessageLoop::current(), message_loop_); |
| - GLuint texture_id; |
| - glGenTextures(1, &texture_id); |
| - glBindTexture(GL_TEXTURE_2D, texture_id); |
| +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); |
| CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); |
| - return texture_id; |
| + CHECK(texture_id_to_surface_index_.insert( |
| + std::make_pair(*texture_id, window_id)).second); |
| + done->Signal(); |
| } |
| void RenderingHelper::RenderTexture(GLuint texture_id) { |
| @@ -263,7 +316,14 @@ void RenderingHelper::RenderTexture(GLuint texture_id) { |
| 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); |
| + 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]); |
| + } |
| DCHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS); |
| } |
| @@ -322,8 +382,12 @@ class EglRenderingVDAClient : public VideoDecodeAccelerator::Client { |
| public: |
| // Doesn't take ownership of |note| or |encoded_data|, which must outlive |
| // |*this|. |
| - EglRenderingVDAClient(ClientStateNotification* note, |
| - std::string* encoded_data); |
| + EglRenderingVDAClient(RenderingHelper* rendering_helper, |
| + int rendering_window_id, |
| + ClientStateNotification* note, |
| + std::string* encoded_data, |
| + int num_NALs_per_decode, |
| + bool suppress_swap_to_display); |
| virtual ~EglRenderingVDAClient(); |
| // VideoDecodeAccelerator::Client implementation. |
| @@ -350,9 +414,9 @@ class EglRenderingVDAClient : public VideoDecodeAccelerator::Client { |
| 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(); } |
| + EGLDisplay egl_display() { return rendering_helper_->egl_display(); } |
| + EGLContext egl_context() { return rendering_helper_->egl_context(); } |
| + double frames_per_second(); |
| private: |
| void SetState(ClientState new_state) { |
| @@ -360,10 +424,17 @@ class EglRenderingVDAClient : public VideoDecodeAccelerator::Client { |
| state_ = new_state; |
| } |
| - // Request decode of the next NALU in the encoded data. |
| - void DecodeNextNALU(); |
| + // Compute & return in |*end_pos| the end position for the next batch of NALs |
|
vrk (LEFT CHROMIUM)
2011/06/09 20:36:11
teeny tiny nit: should all these NALs be NALUs?
Ami GONE FROM CHROMIUM
2011/06/09 23:22:26
Done.
|
| + // to ship to the decoder (based on |start_pos| & |num_NALs_per_decode_|). |
| + void GetRangeForNextNALs(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_; |
| const std::string* encoded_data_; |
| + const int num_NALs_per_decode_; |
| size_t encoded_data_next_pos_to_decode_; |
| int next_bitstream_buffer_id_; |
| ClientStateNotification* note_; |
| @@ -373,40 +444,27 @@ class EglRenderingVDAClient : public VideoDecodeAccelerator::Client { |
| 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_; |
| + base::TimeTicks initialize_done_ticks_; |
| + base::TimeTicks last_frame_delivered_ticks_; |
| }; |
| -EglRenderingVDAClient::EglRenderingVDAClient(ClientStateNotification* note, |
| - std::string* encoded_data) |
| - : encoded_data_(encoded_data), encoded_data_next_pos_to_decode_(0), |
| - next_bitstream_buffer_id_(0), note_(note), decoder_(NULL), |
| - state_(CS_CREATED), |
| +EglRenderingVDAClient::EglRenderingVDAClient(RenderingHelper* rendering_helper, |
| + int rendering_window_id, |
| + ClientStateNotification* note, |
| + std::string* encoded_data, |
| + int num_NALs_per_decode, |
| + bool suppress_swap_to_display) |
| + : rendering_helper_(rendering_helper), |
| + rendering_window_id_(rendering_window_id), |
| + encoded_data_(encoded_data), num_NALs_per_decode_(num_NALs_per_decode), |
| + encoded_data_next_pos_to_decode_(0), next_bitstream_buffer_id_(0), |
| + note_(note), decoder_(NULL), 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(); |
| + num_decoded_frames_(0), num_done_bitstream_buffers_(0) { |
| + CHECK_GT(num_NALs_per_decode, 0); |
| } |
| 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); |
| } |
| @@ -415,7 +473,6 @@ 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); |
| @@ -423,7 +480,10 @@ void EglRenderingVDAClient::ProvidePictureBuffers( |
| for (uint32 i = 0; i < requested_num_of_buffers; ++i) { |
| uint32 id = picture_buffers_by_id_.size(); |
| - GLuint texture_id = rendering_helper_.CreateTexture(); |
| + GLuint texture_id; |
| + base::WaitableEvent done(false, false); |
| + rendering_helper_->CreateTexture(rendering_window_id_, &texture_id, &done); |
| + done.Wait(); |
| // TODO(fischman): context_id is always 0. Can it be removed from the API? |
| // (since it's always inferrable from context). |
| media::GLESBuffer* buffer = |
| @@ -437,60 +497,54 @@ void EglRenderingVDAClient::ProvidePictureBuffers( |
| } |
| void EglRenderingVDAClient::DismissPictureBuffer(int32 picture_buffer_id) { |
|
vrk (LEFT CHROMIUM)
2011/06/09 20:36:11
Didn't catch this before, but I think you also hav
Ami GONE FROM CHROMIUM
2011/06/09 23:22:26
Done.
|
| - 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()); |
| + last_frame_delivered_ticks_ = base::TimeTicks::Now(); |
| - // Because we feed the decoder one NALU at a time, we can be sure each frame |
| - // comes from a bitstream buffer numbered at least as high as our current |
| - // decoded frame's index, and less than the id of the next bitstream buffer |
| - // we'll send for decoding. Assert that. |
| - CHECK_GE(picture.bitstream_buffer_id(), num_decoded_frames_); |
| + // Because we feed the decoder a limited number of NALUs at a time, we can be |
| + // sure each the bitstream buffer from which a frame comes has a limited |
| + // range. Assert that. |
| + CHECK_GE((picture.bitstream_buffer_id() + 1) * num_NALs_per_decode_, |
| + num_decoded_frames_); |
| CHECK_LE(picture.bitstream_buffer_id(), next_bitstream_buffer_id_); |
| ++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()); |
| + 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); |
| - DecodeNextNALU(); |
| + initialize_done_ticks_ = base::TimeTicks::Now(); |
| + DecodeNextNALUs(); |
| } |
| 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_; |
| - DecodeNextNALU(); |
| + DecodeNextNALUs(); |
| } |
| 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; |
| } |
| @@ -506,20 +560,31 @@ static bool LookingAtNAL(const std::string& encoded, size_t pos) { |
| encoded[pos + 2] == 0 && encoded[pos + 3] == 1; |
| } |
| -void EglRenderingVDAClient::DecodeNextNALU() { |
| +void EglRenderingVDAClient::GetRangeForNextNALs( |
| + size_t start_pos, size_t* end_pos) { |
| + *end_pos = start_pos; |
| + CHECK(LookingAtNAL(*encoded_data_, start_pos)); |
| + for (int i = 0; i < num_NALs_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 (encoded_data_next_pos_to_decode_ == encoded_data_->size()) { |
| decoder_->Flush(); |
| return; |
| } |
| size_t start_pos = encoded_data_next_pos_to_decode_; |
| - CHECK(LookingAtNAL(*encoded_data_, start_pos)); |
| - size_t end_pos = encoded_data_next_pos_to_decode_ + 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(); |
| + size_t end_pos; |
| + GetRangeForNextNALs(start_pos, &end_pos); |
| // Populate the shared memory buffer w/ the NALU, duplicate its handle, and |
| // hand it off to the decoder. |
| @@ -535,52 +600,139 @@ void EglRenderingVDAClient::DecodeNextNALU() { |
| encoded_data_next_pos_to_decode_ = end_pos; |
| } |
| +double EglRenderingVDAClient::frames_per_second() { |
| + base::TimeDelta delta = last_frame_delivered_ticks_ - initialize_done_ticks_; |
| + CHECK_GT(delta.InSecondsF(), 0); |
| + return num_decoded_frames_ / delta.InSecondsF(); |
| +} |
| + |
| +// Test parameters: |
| +// - Number of NALs per Decode() call. |
| +// - Number of concurrent decoders. |
| +class OmxVideoDecodeAcceleratorTest |
| + : public ::testing::TestWithParam<std::pair<int, int> > { |
| +}; |
| + |
| // 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; |
| - // Read in the video data and hand it off to the client for later decoding. |
| +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_; |
|
vrk (LEFT CHROMIUM)
2011/06/09 20:36:11
nit: no ending _
Ami GONE FROM CHROMIUM
2011/06/09 23:22:26
Done.
|
| + |
| + const int num_NALs_per_decode = GetParam().first; |
| + const size_t num_concurrent_decoders = GetParam().second; |
| + |
| + // Suppress EGL surface swapping in all but one test, to cut down overall test |
|
vrk (LEFT CHROMIUM)
2011/06/09 20:36:11
Looks like 2 tests have num_NALs_per_decode == 1?
Ami GONE FROM CHROMIUM
2011/06/09 23:22:26
Done.
|
| + // runtime. |
| + const bool suppress_swap_to_display = num_NALs_per_decode > 1; |
| + |
| + std::vector<ClientStateNotification*> notes(num_concurrent_decoders, NULL); |
| + std::vector<EglRenderingVDAClient*> clients(num_concurrent_decoders, NULL); |
| + std::vector<OmxVideoDecodeAccelerator*> decoders( |
| + num_concurrent_decoders, NULL); |
| + // Read in the video data. |
| std::string data_str; |
| CHECK(file_util::ReadFileToString(FilePath(std::string("test-25fps.h264")), |
| &data_str)); |
| - EglRenderingVDAClient client(¬e, &data_str); |
| - 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); |
| - |
| - |
| - // Configure the decoder. |
| - 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); |
| - // InitializeDone kicks off decoding inside the client, so we just need to |
| - // wait for Flush. |
| - ASSERT_EQ((state = note.Wait()), CS_FLUSHED); |
| - |
| - EXPECT_EQ(client.num_decoded_frames(), 25 /* fps */ * 10 /* seconds */); |
| - // Hard-coded the number of NALUs in the stream. Icky. |
| - EXPECT_EQ(client.num_done_bitstream_buffers(), 258); |
| -} |
| + |
| + // 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, |
| + static_cast<int>(kFrameWidth), static_cast<int>(kFrameHeight), |
| + &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_NALs_per_decode, |
| + suppress_swap_to_display); |
| + clients[index] = client; |
| + OmxVideoDecodeAccelerator* decoder = |
| + new OmxVideoDecodeAccelerator(client, rendering_thread.message_loop()); |
| + decoders[index] = decoder; |
| + client->SetDecoder(decoder); |
| + decoder->SetEglState(client->egl_display(), client->egl_context()); |
| + ASSERT_EQ(note->Wait(), CS_DECODER_SET); |
| + |
| + // Configure the decoder. |
| + 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)); |
| + } |
| + // Then wait for all the decodes to finish. |
| + for (size_t i = 0; i < num_concurrent_decoders; ++i) { |
| + ClientStateNotification* note = notes[i]; |
| + ASSERT_EQ(note->Wait(), CS_INITIALIZED); |
| + // InitializeDone kicks off decoding inside the client, so we just need to |
| + // wait for Flush. |
| + ASSERT_EQ(note->Wait(), CS_FLUSHED); |
| + } |
| + // Finally assert that decoding went as expected. |
| + for (size_t i = 0; i < num_concurrent_decoders; ++i) { |
| + EglRenderingVDAClient* client = clients[i]; |
| + EXPECT_EQ(client->num_decoded_frames(), 25 /* fps */ * 10 /* seconds */); |
| + // Hard-coded the number of NALUs in the stream. Icky. |
| + EXPECT_EQ(client->num_done_bitstream_buffers(), |
| + ceil(258.0 / num_NALs_per_decode)); |
| + // These numbers are pulled out of a hat, but seem to be safe with current |
| + // hardware. |
| + double min_expected_fps = suppress_swap_to_display ? 175 : 50; |
| + min_expected_fps /= num_concurrent_decoders; |
| + LOG(INFO) << "Decoder " << i << " fps: " << client->frames_per_second(); |
| + EXPECT_GT(client->frames_per_second(), min_expected_fps); |
| + } |
| + STLDeleteElements(&decoders); |
| + STLDeleteElements(&clients); |
| + STLDeleteElements(¬es); |
| + |
| + rendering_thread.message_loop()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&RenderingHelper::UnInitialize, |
| + base::Unretained(&rendering_helper), |
| + &done)); |
| + done.Wait(); |
| + rendering_thread.Stop(); |
| +}; |
| + |
| +// TODO(fischman): using 16 and higher below 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. |
| +INSTANTIATE_TEST_CASE_P( |
| + DecodeVariations, OmxVideoDecodeAcceleratorTest, |
| + ::testing::Values( |
| + std::make_pair(1, 1), std::make_pair(1, 3), std::make_pair(2, 1), |
| + std::make_pair(3, 1), std::make_pair(5, 1), std::make_pair(8, 1), |
| + std::make_pair(15, 1))); |
| // TODO(fischman, vrk): add more tests! In particular: |
| -// - Test that chunking Decode() calls differently works. |
| +// - Test life-cycle: Seek/Stop/Pause/Play/RePlay for a single decoder. |
| // - Test for memory leaks (valgrind) |
| -// - Test decode speed. Ideally we can beat 60fps esp on simple test.mp4. |
| // - Test alternate configurations |
| // - Test failure conditions. |
| -// - Test multiple decodes; sequentially & concurrently. |
|
vrk (LEFT CHROMIUM)
2011/06/09 20:36:11
In what way did you want to test decodes sequentia
Ami GONE FROM CHROMIUM
2011/06/09 23:22:26
Play one stream, then play another, with the same
|
| // - Test frame size changes mid-stream |
| } // namespace |