diff options
author | bbudge@chromium.org <bbudge@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-30 22:45:56 +0000 |
---|---|---|
committer | bbudge@chromium.org <bbudge@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-30 22:45:56 +0000 |
commit | dfb0d06f30c661f3d3f51baa2db8e5caa0c9ad58 (patch) | |
tree | 968e3ad3ad61dc499cad6909ae9cd9a7259c078a /ppapi | |
parent | b0f8d6dce36cb22bc3fdf5cd592475493793cbc5 (diff) | |
download | chromium_src-dfb0d06f30c661f3d3f51baa2db8e5caa0c9ad58.zip chromium_src-dfb0d06f30c661f3d3f51baa2db8e5caa0c9ad58.tar.gz chromium_src-dfb0d06f30c661f3d3f51baa2db8e5caa0c9ad58.tar.bz2 |
Implement Pepper PPB_VideoDecoder interface.
Adds resource and host, unit test for the resource, and an example plugin.
Implements only the hardware accelerated case. Software fallback will be
in a follow-on CL.
Adds two new PP_Error codes:
PP_ERROR_UNREADABLE_INPUT
PP_ERROR_PLATFORM_FAILED
BUG=281689
R=dmichael@chromium.org, fischman@chromium.org, jar@chromium.org, piman@chromium.org, tsepez@chromium.org
Review URL: https://codereview.chromium.org/270213004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@273920 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ppapi')
-rw-r--r-- | ppapi/api/pp_errors.idl | 12 | ||||
-rw-r--r-- | ppapi/c/pp_errors.h | 13 | ||||
-rw-r--r-- | ppapi/examples/video_decode/video_decode.cc | 609 | ||||
-rw-r--r-- | ppapi/examples/video_decode/video_decode.html | 18 | ||||
-rw-r--r-- | ppapi/ppapi_proxy.gypi | 2 | ||||
-rw-r--r-- | ppapi/ppapi_shared.gypi | 1 | ||||
-rw-r--r-- | ppapi/ppapi_tests.gypi | 4 | ||||
-rw-r--r-- | ppapi/proxy/interface_list.cc | 1 | ||||
-rw-r--r-- | ppapi/proxy/ppapi_messages.h | 44 | ||||
-rw-r--r-- | ppapi/proxy/ppb_graphics_3d_proxy.h | 3 | ||||
-rw-r--r-- | ppapi/proxy/resource_creation_proxy.cc | 5 | ||||
-rw-r--r-- | ppapi/proxy/resource_creation_proxy.h | 1 | ||||
-rw-r--r-- | ppapi/proxy/video_decoder_constants.h | 28 | ||||
-rw-r--r-- | ppapi/proxy/video_decoder_resource.cc | 506 | ||||
-rw-r--r-- | ppapi/proxy/video_decoder_resource.h | 179 | ||||
-rw-r--r-- | ppapi/proxy/video_decoder_resource_unittest.cc | 580 | ||||
-rw-r--r-- | ppapi/shared_impl/resource.h | 1 | ||||
-rw-r--r-- | ppapi/thunk/interfaces_ppb_public_dev_channel.h | 1 | ||||
-rw-r--r-- | ppapi/thunk/resource_creation_api.h | 1 |
19 files changed, 2005 insertions, 4 deletions
diff --git a/ppapi/api/pp_errors.idl b/ppapi/api/pp_errors.idl index 1e4e870..2d958ec 100644 --- a/ppapi/api/pp_errors.idl +++ b/ppapi/api/pp_errors.idl @@ -85,6 +85,17 @@ * input events, and there are reentrancy and deadlock issues). */ PP_ERROR_BLOCKS_MAIN_THREAD = -13, + /** + * This value indicates that the plugin sent bad input data to a resource, + * leaving it in an invalid state. The resource can't be used after returning + * this error and should be released. + */ + PP_ERROR_MALFORMED_INPUT = -14, + /** + * This value indicates that a resource has failed. The resource can't be + * used after returning this error and should be released. + */ + PP_ERROR_RESOURCE_FAILED = -15, /** This value indicates failure due to a file that does not exist. */ PP_ERROR_FILENOTFOUND = -20, @@ -129,6 +140,7 @@ * thread. */ PP_ERROR_WRONG_THREAD = -52, + /** * This value indicates that the connection was closed. For TCP sockets, it * corresponds to a TCP FIN. diff --git a/ppapi/c/pp_errors.h b/ppapi/c/pp_errors.h index d0411c6..e65bb92 100644 --- a/ppapi/c/pp_errors.h +++ b/ppapi/c/pp_errors.h @@ -3,7 +3,7 @@ * found in the LICENSE file. */ -/* From pp_errors.idl modified Thu Jun 13 13:02:05 2013. */ +/* From pp_errors.idl modified Thu May 15 16:12:26 2014. */ #ifndef PPAPI_C_PP_ERRORS_H_ #define PPAPI_C_PP_ERRORS_H_ @@ -86,6 +86,17 @@ enum { * input events, and there are reentrancy and deadlock issues). */ PP_ERROR_BLOCKS_MAIN_THREAD = -13, + /** + * This value indicates that the plugin sent bad input data to a resource, + * leaving it in an invalid state. The resource can't be used after returning + * this error and should be released. + */ + PP_ERROR_MALFORMED_INPUT = -14, + /** + * This value indicates that a resource has failed. The resource can't be + * used after returning this error and should be released. + */ + PP_ERROR_RESOURCE_FAILED = -15, /** This value indicates failure due to a file that does not exist. */ PP_ERROR_FILENOTFOUND = -20, /** This value indicates failure due to a file that already exists. */ diff --git a/ppapi/examples/video_decode/video_decode.cc b/ppapi/examples/video_decode/video_decode.cc new file mode 100644 index 0000000..de9bb8d --- /dev/null +++ b/ppapi/examples/video_decode/video_decode.cc @@ -0,0 +1,609 @@ +// Copyright (c) 2014 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. + +#include <stdio.h> +#include <string.h> + +#include <iostream> +#include <queue> +#include <sstream> + +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/ppb_console.h" +#include "ppapi/c/ppb_opengles2.h" +#include "ppapi/cpp/graphics_3d.h" +#include "ppapi/cpp/graphics_3d_client.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/var.h" +#include "ppapi/cpp/video_decoder.h" +#include "ppapi/examples/video_decode/testdata.h" +#include "ppapi/lib/gl/include/GLES2/gl2.h" +#include "ppapi/lib/gl/include/GLES2/gl2ext.h" +#include "ppapi/utility/completion_callback_factory.h" + +// Use assert as a poor-man's CHECK, even in non-debug mode. +// Since <assert.h> redefines assert on every inclusion (it doesn't use +// include-guards), make sure this is the last file #include'd in this file. +#undef NDEBUG +#include <assert.h> + +// Assert |context_| isn't holding any GL Errors. Done as a macro instead of a +// function to preserve line number information in the failure message. +#define assertNoGLError() assert(!gles2_if_->GetError(context_->pp_resource())); + +namespace { + +struct Shader { + Shader() : program(0), texcoord_scale_location(0) {} + ~Shader() {} + + GLuint program; + GLint texcoord_scale_location; +}; + +class Decoder; +class MyInstance; + +class MyInstance : public pp::Instance, public pp::Graphics3DClient { + public: + MyInstance(PP_Instance instance, pp::Module* module); + virtual ~MyInstance(); + + // pp::Instance implementation. + virtual void DidChangeView(const pp::Rect& position, + const pp::Rect& clip_ignored); + + // pp::Graphics3DClient implementation. + virtual void Graphics3DContextLost() { + // TODO(vrk/fischman): Properly reset after a lost graphics context. In + // particular need to delete context_ and re-create textures. + // Probably have to recreate the decoder from scratch, because old textures + // can still be outstanding in the decoder! + assert(false && "Unexpectedly lost graphics context"); + } + + void PaintPicture(Decoder* decoder, const PP_VideoPicture& picture); + + private: + // Log an error to the developer console and stderr by creating a temporary + // object of this type and streaming to it. Example usage: + // LogError(this).s() << "Hello world: " << 42; + class LogError { + public: + LogError(MyInstance* instance) : instance_(instance) {} + ~LogError() { + const std::string& msg = stream_.str(); + instance_->console_if_->Log( + instance_->pp_instance(), PP_LOGLEVEL_ERROR, pp::Var(msg).pp_var()); + std::cerr << msg << std::endl; + } + // Impl note: it would have been nicer to have LogError derive from + // std::ostringstream so that it can be streamed to directly, but lookup + // rules turn streamed string literals to hex pointers on output. + std::ostringstream& s() { return stream_; } + + private: + MyInstance* instance_; + std::ostringstream stream_; + }; + + void InitializeDecoders(); + + // GL-related functions. + void InitGL(); + void CreateGLObjects(); + void Create2DProgramOnce(); + void CreateRectangleARBProgramOnce(); + Shader CreateProgram(const char* vertex_shader, const char* fragment_shader); + void CreateShader(GLuint program, GLenum type, const char* source, int size); + void PaintFinished(int32_t result, Decoder* decoder, PP_VideoPicture picture); + + pp::Size plugin_size_; + bool is_painting_; + // When decode outpaces render, we queue up decoded pictures for later + // painting. Elements are <decoder,picture>. + typedef std::queue<std::pair<Decoder*, PP_VideoPicture> > PictureQueue; + PictureQueue pictures_pending_paint_; + + int num_frames_rendered_; + PP_TimeTicks first_frame_delivered_ticks_; + PP_TimeTicks last_swap_request_ticks_; + PP_TimeTicks swap_ticks_; + pp::CompletionCallbackFactory<MyInstance> callback_factory_; + + // Unowned pointers. + const PPB_Console* console_if_; + const PPB_Core* core_if_; + const PPB_OpenGLES2* gles2_if_; + + // Owned data. + pp::Graphics3D* context_; + typedef std::vector<Decoder*> DecoderList; + DecoderList video_decoders_; + + // Shader program to draw GL_TEXTURE_2D target. + Shader shader_2d_; + // Shader program to draw GL_TEXTURE_RECTANGLE_ARB target. + Shader shader_rectangle_arb_; +}; + +class Decoder { + public: + Decoder(MyInstance* instance, int id, const pp::Graphics3D& graphics_3d); + ~Decoder(); + + int id() const { return id_; } + bool decoding() const { return !flushing_ && !resetting_; } + + void Seek(int frame); + void RecyclePicture(const PP_VideoPicture& picture); + + private: + void InitializeDone(int32_t result); + void Start(int frame); + void DecodeNextFrame(); + void DecodeDone(int32_t result); + void PictureReady(int32_t result, PP_VideoPicture picture); + void FlushDone(int32_t result); + void ResetDone(int32_t result); + + MyInstance* instance_; + int id_; + + pp::VideoDecoder* decoder_; + pp::CompletionCallbackFactory<Decoder> callback_factory_; + + size_t encoded_data_next_pos_to_decode_; + int next_picture_id_; + int seek_frame_; + bool flushing_; + bool resetting_; +}; + +// Returns true if the current position is at the start of a NAL unit. +static bool LookingAtNAL(const unsigned char* encoded, size_t pos) { + // H264 frames start with 0, 0, 0, 1 in our test data. + return pos + 3 < kDataLen && encoded[pos] == 0 && encoded[pos + 1] == 0 && + encoded[pos + 2] == 0 && encoded[pos + 3] == 1; +} + +// Find the start and end of the next frame. +static void GetNextFrame(size_t* start_pos, size_t* end_pos) { + assert(LookingAtNAL(kData, *start_pos)); + *end_pos = *start_pos; + *end_pos += 4; + while (*end_pos < kDataLen && !LookingAtNAL(kData, *end_pos)) { + ++*end_pos; + } +} + +Decoder::Decoder(MyInstance* instance, + int id, + const pp::Graphics3D& graphics_3d) + : instance_(instance), + id_(id), + decoder_(new pp::VideoDecoder(instance)), + callback_factory_(this), + encoded_data_next_pos_to_decode_(0), + next_picture_id_(0), + seek_frame_(0), + flushing_(false), + resetting_(false) { + assert(!decoder_->is_null()); + const PP_VideoProfile profile = PP_VIDEOPROFILE_H264MAIN; + decoder_->Initialize(graphics_3d, + profile, + PP_FALSE /* allow_software_fallback */, + callback_factory_.NewCallback(&Decoder::InitializeDone)); +} + +Decoder::~Decoder() { + delete decoder_; +} + +void Decoder::InitializeDone(int32_t result) { + assert(decoder_); + assert(result == PP_OK); + assert(decoding()); + Start(0); +} + +void Decoder::Start(int frame) { + assert(decoder_); + + // Skip to |frame|. + size_t start_pos = 0; + size_t end_pos = 0; + for (int i = 0; i < frame; i++) + GetNextFrame(&start_pos, &end_pos); + encoded_data_next_pos_to_decode_ = end_pos; + + // Register callback to get the first picture. We call GetPicture again in + // PictureReady to continuously receive pictures as they're decoded. + decoder_->GetPicture( + callback_factory_.NewCallbackWithOutput(&Decoder::PictureReady)); + + // Start the decode loop. + DecodeNextFrame(); +} + +void Decoder::Seek(int frame) { + assert(decoder_); + seek_frame_ = frame; + resetting_ = true; + decoder_->Reset(callback_factory_.NewCallback(&Decoder::ResetDone)); +} + +void Decoder::RecyclePicture(const PP_VideoPicture& picture) { + assert(decoder_); + decoder_->RecyclePicture(picture); +} + +void Decoder::DecodeNextFrame() { + assert(decoder_); + if (encoded_data_next_pos_to_decode_ <= kDataLen) { + // If we've just reached the end of the bitstream, flush and wait. + if (!flushing_ && encoded_data_next_pos_to_decode_ == kDataLen) { + flushing_ = true; + decoder_->Flush(callback_factory_.NewCallback(&Decoder::FlushDone)); + return; + } + + // Find the start of the next frame. + size_t start_pos = encoded_data_next_pos_to_decode_; + size_t end_pos; + GetNextFrame(&start_pos, &end_pos); + encoded_data_next_pos_to_decode_ = end_pos; + // Decode the frame. On completion, DecodeDone will call DecodeNextFrame + // to implement a decode loop. + uint32_t size = static_cast<uint32_t>(end_pos - start_pos); + decoder_->Decode(next_picture_id_++, + size, + kData + start_pos, + callback_factory_.NewCallback(&Decoder::DecodeDone)); + } +} + +void Decoder::DecodeDone(int32_t result) { + assert(decoder_); + // Break out of the decode loop on abort. + if (result == PP_ERROR_ABORTED) + return; + assert(result == PP_OK); + if (decoding()) + DecodeNextFrame(); +} + +void Decoder::PictureReady(int32_t result, PP_VideoPicture picture) { + assert(decoder_); + // Break out of the get picture loop on abort. + if (result == PP_ERROR_ABORTED) + return; + assert(result == PP_OK); + decoder_->GetPicture( + callback_factory_.NewCallbackWithOutput(&Decoder::PictureReady)); + instance_->PaintPicture(this, picture); +} + +void Decoder::FlushDone(int32_t result) { + assert(decoder_); + assert(result == PP_OK || result == PP_ERROR_ABORTED); + flushing_ = false; +} + +void Decoder::ResetDone(int32_t result) { + assert(decoder_); + assert(result == PP_OK); + resetting_ = false; +} + +MyInstance::MyInstance(PP_Instance instance, pp::Module* module) + : pp::Instance(instance), + pp::Graphics3DClient(this), + is_painting_(false), + num_frames_rendered_(0), + first_frame_delivered_ticks_(-1), + last_swap_request_ticks_(-1), + swap_ticks_(0), + callback_factory_(this), + context_(NULL) { + assert((console_if_ = static_cast<const PPB_Console*>( + module->GetBrowserInterface(PPB_CONSOLE_INTERFACE)))); + assert((core_if_ = static_cast<const PPB_Core*>( + module->GetBrowserInterface(PPB_CORE_INTERFACE)))); + assert((gles2_if_ = static_cast<const PPB_OpenGLES2*>( + module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE)))); + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); +} + +MyInstance::~MyInstance() { + if (!context_) + return; + + PP_Resource graphics_3d = context_->pp_resource(); + if (shader_2d_.program) + gles2_if_->DeleteProgram(graphics_3d, shader_2d_.program); + if (shader_rectangle_arb_.program) + gles2_if_->DeleteProgram(graphics_3d, shader_rectangle_arb_.program); + + for (DecoderList::iterator it = video_decoders_.begin(); + it != video_decoders_.end(); + ++it) + delete *it; + + delete context_; +} + +void MyInstance::DidChangeView(const pp::Rect& position, + const pp::Rect& clip_ignored) { + if (position.width() == 0 || position.height() == 0) + return; + if (plugin_size_.width()) { + assert(position.size() == plugin_size_); + return; + } + plugin_size_ = position.size(); + + // Initialize graphics. + InitGL(); + InitializeDecoders(); +} + +void MyInstance::InitializeDecoders() { + assert(video_decoders_.empty()); + // Create two decoders with ids 0 and 1. + video_decoders_.push_back(new Decoder(this, 0, *context_)); + video_decoders_.push_back(new Decoder(this, 1, *context_)); +} + +void MyInstance::PaintPicture(Decoder* decoder, + const PP_VideoPicture& picture) { + if (first_frame_delivered_ticks_ == -1) + assert((first_frame_delivered_ticks_ = core_if_->GetTimeTicks()) != -1); + if (is_painting_) { + pictures_pending_paint_.push(std::make_pair(decoder, picture)); + return; + } + + assert(!is_painting_); + is_painting_ = true; + int x = 0; + int y = 0; + int half_width = plugin_size_.width() / 2; + int half_height = plugin_size_.height() / 2; + if (decoder->id() != 0) { + x = half_width; + y = half_height; + } + + PP_Resource graphics_3d = context_->pp_resource(); + if (picture.texture_target == GL_TEXTURE_2D) { + Create2DProgramOnce(); + gles2_if_->UseProgram(graphics_3d, shader_2d_.program); + gles2_if_->Uniform2f( + graphics_3d, shader_2d_.texcoord_scale_location, 1.0, 1.0); + } else { + assert(picture.texture_target == GL_TEXTURE_RECTANGLE_ARB); + CreateRectangleARBProgramOnce(); + gles2_if_->UseProgram(graphics_3d, shader_rectangle_arb_.program); + gles2_if_->Uniform2f(graphics_3d, + shader_rectangle_arb_.texcoord_scale_location, + picture.texture_size.width, + picture.texture_size.height); + } + + gles2_if_->Viewport(graphics_3d, x, y, half_width, half_height); + gles2_if_->ActiveTexture(graphics_3d, GL_TEXTURE0); + gles2_if_->BindTexture( + graphics_3d, picture.texture_target, picture.texture_id); + gles2_if_->DrawArrays(graphics_3d, GL_TRIANGLE_STRIP, 0, 4); + + gles2_if_->UseProgram(graphics_3d, 0); + + last_swap_request_ticks_ = core_if_->GetTimeTicks(); + assert(PP_OK_COMPLETIONPENDING == + context_->SwapBuffers(callback_factory_.NewCallback( + &MyInstance::PaintFinished, decoder, picture))); +} + +void MyInstance::PaintFinished(int32_t result, + Decoder* decoder, + PP_VideoPicture picture) { + assert(result == PP_OK); + swap_ticks_ += core_if_->GetTimeTicks() - last_swap_request_ticks_; + is_painting_ = false; + ++num_frames_rendered_; + if (num_frames_rendered_ % 50 == 0) { + double elapsed = core_if_->GetTimeTicks() - first_frame_delivered_ticks_; + double fps = (elapsed > 0) ? num_frames_rendered_ / elapsed : 1000; + double ms_per_swap = (swap_ticks_ * 1e3) / num_frames_rendered_; + LogError(this).s() << "Rendered frames: " << num_frames_rendered_ + << ", fps: " << fps + << ", with average ms/swap of: " << ms_per_swap; + } + decoder->RecyclePicture(picture); + // Keep painting as long as we have pictures. + if (!pictures_pending_paint_.empty()) { + std::pair<Decoder*, PP_VideoPicture> pending = + pictures_pending_paint_.front(); + pictures_pending_paint_.pop(); + PaintPicture(pending.first, pending.second); + } +} + +void MyInstance::InitGL() { + assert(plugin_size_.width() && plugin_size_.height()); + is_painting_ = false; + + assert(!context_); + int32_t context_attributes[] = { + PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, + PP_GRAPHICS3DATTRIB_BLUE_SIZE, 8, + PP_GRAPHICS3DATTRIB_GREEN_SIZE, 8, + PP_GRAPHICS3DATTRIB_RED_SIZE, 8, + PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 0, + PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 0, + PP_GRAPHICS3DATTRIB_SAMPLES, 0, + PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0, + PP_GRAPHICS3DATTRIB_WIDTH, plugin_size_.width(), + PP_GRAPHICS3DATTRIB_HEIGHT, plugin_size_.height(), + PP_GRAPHICS3DATTRIB_NONE, + }; + context_ = new pp::Graphics3D(this, context_attributes); + assert(!context_->is_null()); + assert(BindGraphics(*context_)); + + // Clear color bit. + gles2_if_->ClearColor(context_->pp_resource(), 1, 0, 0, 1); + gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT); + + assertNoGLError(); + + CreateGLObjects(); +} + +void MyInstance::CreateGLObjects() { + // Assign vertex positions and texture coordinates to buffers for use in + // shader program. + static const float kVertices[] = { + -1, 1, -1, -1, 1, 1, 1, -1, // Position coordinates. + 0, 1, 0, 0, 1, 1, 1, 0, // Texture coordinates. + }; + + GLuint buffer; + gles2_if_->GenBuffers(context_->pp_resource(), 1, &buffer); + gles2_if_->BindBuffer(context_->pp_resource(), GL_ARRAY_BUFFER, buffer); + + gles2_if_->BufferData(context_->pp_resource(), + GL_ARRAY_BUFFER, + sizeof(kVertices), + kVertices, + GL_STATIC_DRAW); + assertNoGLError(); +} + +static const char kVertexShader[] = + "varying vec2 v_texCoord; \n" + "attribute vec4 a_position; \n" + "attribute vec2 a_texCoord; \n" + "uniform vec2 v_scale; \n" + "void main() \n" + "{ \n" + " v_texCoord = v_scale * a_texCoord; \n" + " gl_Position = a_position; \n" + "}"; + +void MyInstance::Create2DProgramOnce() { + if (shader_2d_.program) + return; + static const char kFragmentShader2D[] = + "precision mediump float; \n" + "varying vec2 v_texCoord; \n" + "uniform sampler2D s_texture; \n" + "void main() \n" + "{" + " gl_FragColor = texture2D(s_texture, v_texCoord); \n" + "}"; + shader_2d_ = CreateProgram(kVertexShader, kFragmentShader2D); + assertNoGLError(); +} + +void MyInstance::CreateRectangleARBProgramOnce() { + if (shader_rectangle_arb_.program) + return; + static const char kFragmentShaderRectangle[] = + "#extension GL_ARB_texture_rectangle : require\n" + "precision mediump float; \n" + "varying vec2 v_texCoord; \n" + "uniform sampler2DRect s_texture; \n" + "void main() \n" + "{" + " gl_FragColor = texture2DRect(s_texture, v_texCoord).rgba; \n" + "}"; + shader_rectangle_arb_ = + CreateProgram(kVertexShader, kFragmentShaderRectangle); +} + +Shader MyInstance::CreateProgram(const char* vertex_shader, + const char* fragment_shader) { + Shader shader; + + // Create shader program. + shader.program = gles2_if_->CreateProgram(context_->pp_resource()); + CreateShader( + shader.program, GL_VERTEX_SHADER, vertex_shader, strlen(vertex_shader)); + CreateShader(shader.program, + GL_FRAGMENT_SHADER, + fragment_shader, + strlen(fragment_shader)); + gles2_if_->LinkProgram(context_->pp_resource(), shader.program); + gles2_if_->UseProgram(context_->pp_resource(), shader.program); + gles2_if_->Uniform1i( + context_->pp_resource(), + gles2_if_->GetUniformLocation( + context_->pp_resource(), shader.program, "s_texture"), + 0); + assertNoGLError(); + + shader.texcoord_scale_location = gles2_if_->GetUniformLocation( + context_->pp_resource(), shader.program, "v_scale"); + + GLint pos_location = gles2_if_->GetAttribLocation( + context_->pp_resource(), shader.program, "a_position"); + GLint tc_location = gles2_if_->GetAttribLocation( + context_->pp_resource(), shader.program, "a_texCoord"); + assertNoGLError(); + + gles2_if_->EnableVertexAttribArray(context_->pp_resource(), pos_location); + gles2_if_->VertexAttribPointer( + context_->pp_resource(), pos_location, 2, GL_FLOAT, GL_FALSE, 0, 0); + gles2_if_->EnableVertexAttribArray(context_->pp_resource(), tc_location); + gles2_if_->VertexAttribPointer( + context_->pp_resource(), + tc_location, + 2, + GL_FLOAT, + GL_FALSE, + 0, + static_cast<float*>(0) + 8); // Skip position coordinates. + + gles2_if_->UseProgram(context_->pp_resource(), 0); + assertNoGLError(); + return shader; +} + +void MyInstance::CreateShader(GLuint program, + GLenum type, + const char* source, + int size) { + GLuint shader = gles2_if_->CreateShader(context_->pp_resource(), type); + gles2_if_->ShaderSource(context_->pp_resource(), shader, 1, &source, &size); + gles2_if_->CompileShader(context_->pp_resource(), shader); + gles2_if_->AttachShader(context_->pp_resource(), program, shader); + gles2_if_->DeleteShader(context_->pp_resource(), shader); +} + +// This object is the global object representing this plugin library as long +// as it is loaded. +class MyModule : public pp::Module { + public: + MyModule() : pp::Module() {} + virtual ~MyModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new MyInstance(instance, this); + } +}; + +} // anonymous namespace + +namespace pp { +// Factory function for your specialization of the Module object. +Module* CreateModule() { + return new MyModule(); +} +} // namespace pp diff --git a/ppapi/examples/video_decode/video_decode.html b/ppapi/examples/video_decode/video_decode.html new file mode 100644 index 0000000..f25f0db --- /dev/null +++ b/ppapi/examples/video_decode/video_decode.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2014 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. + --> +<head> + <title>Video Decode Example</title> +</head> + +<body> + +<embed id="plugin" type="application/x-ppapi-example-video-decode" + width="640" height="480"/> + +</body> +</html> diff --git a/ppapi/ppapi_proxy.gypi b/ppapi/ppapi_proxy.gypi index f8605db6..d816abf 100644 --- a/ppapi/ppapi_proxy.gypi +++ b/ppapi/ppapi_proxy.gypi @@ -226,6 +226,8 @@ 'proxy/var_serialization_rules.h', 'proxy/video_capture_resource.cc', 'proxy/video_capture_resource.h', + 'proxy/video_decoder_resource.cc', + 'proxy/video_decoder_resource.h', 'proxy/video_destination_resource.cc', 'proxy/video_destination_resource.h', 'proxy/video_frame_resource.cc', diff --git a/ppapi/ppapi_shared.gypi b/ppapi/ppapi_shared.gypi index f248ca9..ad4f685 100644 --- a/ppapi/ppapi_shared.gypi +++ b/ppapi/ppapi_shared.gypi @@ -272,6 +272,7 @@ 'thunk/ppb_video_decoder_api.h', 'thunk/ppb_video_decoder_dev_api.h', 'thunk/ppb_video_decoder_dev_thunk.cc', + 'thunk/ppb_video_decoder_thunk.cc', 'thunk/ppb_video_destination_private_api.h', 'thunk/ppb_video_destination_private_thunk.cc', 'thunk/ppb_video_frame_api.h', diff --git a/ppapi/ppapi_tests.gypi b/ppapi/ppapi_tests.gypi index 5802e57..cd70ef0 100644 --- a/ppapi/ppapi_tests.gypi +++ b/ppapi/ppapi_tests.gypi @@ -175,6 +175,7 @@ 'proxy/raw_var_data_unittest.cc', 'proxy/serialized_var_unittest.cc', 'proxy/talk_resource_unittest.cc', + 'proxy/video_decoder_resource_unittest.cc', 'proxy/websocket_resource_unittest.cc', 'shared_impl/media_stream_buffer_manager_unittest.cc', 'shared_impl/media_stream_video_track_shared_unittest.cc', @@ -450,8 +451,7 @@ 'lib/gl/include', ], 'sources': [ - # TODO(bbudge) Change to new example when implementation lands. - 'examples/video_decode/video_decode_dev.cc', + 'examples/video_decode/video_decode.cc', 'examples/video_decode/testdata.h', ], # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. diff --git a/ppapi/proxy/interface_list.cc b/ppapi/proxy/interface_list.cc index d9809f0..2ef6c50 100644 --- a/ppapi/proxy/interface_list.cc +++ b/ppapi/proxy/interface_list.cc @@ -65,6 +65,7 @@ #include "ppapi/c/ppb_var_array.h" #include "ppapi/c/ppb_var_array_buffer.h" #include "ppapi/c/ppb_var_dictionary.h" +#include "ppapi/c/ppb_video_decoder.h" #include "ppapi/c/ppb_video_frame.h" #include "ppapi/c/ppb_view.h" #include "ppapi/c/pp_errors.h" diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h index 6cd36df..4b51ab4 100644 --- a/ppapi/proxy/ppapi_messages.h +++ b/ppapi/proxy/ppapi_messages.h @@ -15,6 +15,7 @@ #include "base/sync_socket.h" #include "base/values.h" #include "gpu/command_buffer/common/command_buffer.h" +#include "gpu/command_buffer/common/mailbox.h" #include "gpu/ipc/gpu_command_buffer_traits.h" #include "ipc/ipc_channel_handle.h" #include "ipc/ipc_message_macros.h" @@ -26,6 +27,7 @@ #include "ppapi/c/dev/ppb_url_util_dev.h" #include "ppapi/c/dev/ppp_printing_dev.h" #include "ppapi/c/pp_bool.h" +#include "ppapi/c/pp_codecs.h" #include "ppapi/c/pp_file_info.h" #include "ppapi/c/pp_instance.h" #include "ppapi/c/pp_module.h" @@ -121,6 +123,7 @@ IPC_ENUM_TRAITS_MAX_VALUE(PP_UDPSocket_Option, IPC_ENUM_TRAITS(PP_VideoDecodeError_Dev) IPC_ENUM_TRAITS(PP_VideoDecoder_Profile) IPC_ENUM_TRAITS_MAX_VALUE(PP_VideoFrame_Format, PP_VIDEOFRAME_FORMAT_LAST) +IPC_ENUM_TRAITS_MAX_VALUE(PP_VideoProfile, PP_VIDEOPROFILE_MAX) IPC_STRUCT_TRAITS_BEGIN(PP_Point) IPC_STRUCT_TRAITS_MEMBER(x) @@ -1814,6 +1817,47 @@ IPC_MESSAGE_CONTROL2(PpapiPluginMsg_OutputProtection_QueryStatusReply, uint32_t /* link_mask */, uint32_t /* protection_mask */) +// VideoDecoder ------------------------------------------------------ + +IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoDecoder_Create) +IPC_MESSAGE_CONTROL3(PpapiHostMsg_VideoDecoder_Initialize, + ppapi::HostResource /* graphics_context */, + PP_VideoProfile /* profile */, + bool /* allow_software_fallback */) +IPC_MESSAGE_CONTROL0(PpapiPluginMsg_VideoDecoder_InitializeReply) +IPC_MESSAGE_CONTROL2(PpapiHostMsg_VideoDecoder_GetShm, + uint32_t /* shm_id */, + uint32_t /* shm_size */) +// On success, a shm handle is passed in the ReplyParams struct. +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoDecoder_GetShmReply, + uint32_t /* shm_size */) +IPC_MESSAGE_CONTROL3(PpapiHostMsg_VideoDecoder_Decode, + uint32_t /* shm_id */, + uint32_t /* size */, + int32_t /* decode_id */) +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoDecoder_DecodeReply, + uint32_t /* shm_id */) +IPC_MESSAGE_CONTROL3(PpapiPluginMsg_VideoDecoder_RequestTextures, + uint32_t /* num_textures */, + PP_Size /* size */, + uint32_t /* texture_target */) +IPC_MESSAGE_CONTROL2(PpapiHostMsg_VideoDecoder_AssignTextures, + PP_Size /* size */, + std::vector<uint32_t> /* texture_ids */) +IPC_MESSAGE_CONTROL2(PpapiPluginMsg_VideoDecoder_PictureReady, + int32_t /* decode_id */, + uint32_t /* texture_id */) +IPC_MESSAGE_CONTROL1(PpapiHostMsg_VideoDecoder_RecyclePicture, + uint32_t /* texture_id */) +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoDecoder_DismissPicture, + uint32_t /* texture_id */) +IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoDecoder_Flush) +IPC_MESSAGE_CONTROL0(PpapiPluginMsg_VideoDecoder_FlushReply) +IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoDecoder_Reset) +IPC_MESSAGE_CONTROL0(PpapiPluginMsg_VideoDecoder_ResetReply) +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoDecoder_NotifyError, + int32_t /* error */) + #if !defined(OS_NACL) && !defined(NACL_WIN64) // Audio input. diff --git a/ppapi/proxy/ppb_graphics_3d_proxy.h b/ppapi/proxy/ppb_graphics_3d_proxy.h index 931b84f..e66405c 100644 --- a/ppapi/proxy/ppb_graphics_3d_proxy.h +++ b/ppapi/proxy/ppb_graphics_3d_proxy.h @@ -12,6 +12,7 @@ #include "ppapi/c/pp_graphics_3d.h" #include "ppapi/c/pp_instance.h" #include "ppapi/proxy/interface_proxy.h" +#include "ppapi/proxy/ppapi_proxy_export.h" #include "ppapi/proxy/proxy_completion_callback_factory.h" #include "ppapi/shared_impl/ppb_graphics_3d_shared.h" #include "ppapi/shared_impl/resource.h" @@ -26,7 +27,7 @@ namespace proxy { class SerializedHandle; class PpapiCommandBufferProxy; -class Graphics3D : public PPB_Graphics3D_Shared { +class PPAPI_PROXY_EXPORT Graphics3D : public PPB_Graphics3D_Shared { public: explicit Graphics3D(const HostResource& resource); virtual ~Graphics3D(); diff --git a/ppapi/proxy/resource_creation_proxy.cc b/ppapi/proxy/resource_creation_proxy.cc index eda6308..aa5b8fd 100644 --- a/ppapi/proxy/resource_creation_proxy.cc +++ b/ppapi/proxy/resource_creation_proxy.cc @@ -47,6 +47,7 @@ #include "ppapi/proxy/url_request_info_resource.h" #include "ppapi/proxy/url_response_info_resource.h" #include "ppapi/proxy/video_capture_resource.h" +#include "ppapi/proxy/video_decoder_resource.h" #include "ppapi/proxy/video_destination_resource.h" #include "ppapi/proxy/video_source_resource.h" #include "ppapi/proxy/websocket_resource.h" @@ -369,6 +370,10 @@ PP_Resource ResourceCreationProxy::CreateUDPSocketPrivate( GetConnection(), instance))->GetReference(); } +PP_Resource ResourceCreationProxy::CreateVideoDecoder(PP_Instance instance) { + return (new VideoDecoderResource(GetConnection(), instance))->GetReference(); +} + PP_Resource ResourceCreationProxy::CreateVideoDestination( PP_Instance instance) { return (new VideoDestinationResource(GetConnection(), diff --git a/ppapi/proxy/resource_creation_proxy.h b/ppapi/proxy/resource_creation_proxy.h index c32bce3..7c1bc14 100644 --- a/ppapi/proxy/resource_creation_proxy.h +++ b/ppapi/proxy/resource_creation_proxy.h @@ -151,6 +151,7 @@ class ResourceCreationProxy : public InterfaceProxy, virtual PP_Resource CreateTCPSocketPrivate(PP_Instance instance) OVERRIDE; virtual PP_Resource CreateUDPSocket(PP_Instance instance) OVERRIDE; virtual PP_Resource CreateUDPSocketPrivate(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateVideoDecoder(PP_Instance instance) OVERRIDE; virtual PP_Resource CreateVideoDestination(PP_Instance instance) OVERRIDE; virtual PP_Resource CreateVideoSource(PP_Instance instance) OVERRIDE; virtual PP_Resource CreateWebSocket(PP_Instance instance) OVERRIDE; diff --git a/ppapi/proxy/video_decoder_constants.h b/ppapi/proxy/video_decoder_constants.h new file mode 100644 index 0000000..666ad46 --- /dev/null +++ b/ppapi/proxy/video_decoder_constants.h @@ -0,0 +1,28 @@ +// Copyright (c) 2014 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. + +#ifndef PPAPI_PROXY_VIDEO_DECODER_CONSTANTS_H_ +#define PPAPI_PROXY_VIDEO_DECODER_CONSTANTS_H_ + +namespace ppapi { +namespace proxy { + +// These constants are shared by the video decoder resource and host. +enum { + // Maximum number of concurrent decodes which can be pending. + kMaximumPendingDecodes = 8, + + // Minimum size of shared-memory buffers (100 KB). Make them large since we + // try to reuse them. + kMinimumBitstreamBufferSize = 100 << 10, + + // Maximum size of shared-memory buffers (4 MB). This should be enough even + // for 4K video at reasonable compression levels. + kMaximumBitstreamBufferSize = 4 << 20 +}; + +} // namespace proxy +} // namespace ppapi + +#endif // PPAPI_PROXY_VIDEO_DECODER_CONSTANTS_H_ diff --git a/ppapi/proxy/video_decoder_resource.cc b/ppapi/proxy/video_decoder_resource.cc new file mode 100644 index 0000000..95365e8 --- /dev/null +++ b/ppapi/proxy/video_decoder_resource.cc @@ -0,0 +1,506 @@ +// Copyright (c) 2012 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. + +#include "ppapi/proxy/video_decoder_resource.h" + +#include "base/bind.h" +#include "gpu/command_buffer/client/gles2_cmd_helper.h" +#include "gpu/command_buffer/client/gles2_implementation.h" +#include "ipc/ipc_message.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/ppb_opengles2.h" +#include "ppapi/proxy/plugin_dispatcher.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/ppb_graphics_3d_proxy.h" +#include "ppapi/proxy/serialized_handle.h" +#include "ppapi/proxy/video_decoder_constants.h" +#include "ppapi/shared_impl/ppapi_globals.h" +#include "ppapi/shared_impl/ppb_graphics_3d_shared.h" +#include "ppapi/shared_impl/proxy_lock.h" +#include "ppapi/shared_impl/resource_tracker.h" +#include "ppapi/thunk/enter.h" + +using ppapi::thunk::EnterResourceNoLock; +using ppapi::thunk::PPB_Graphics3D_API; +using ppapi::thunk::PPB_VideoDecoder_API; + +namespace ppapi { +namespace proxy { + +VideoDecoderResource::ShmBuffer::ShmBuffer( + scoped_ptr<base::SharedMemory> shm_ptr, + uint32_t size, + uint32_t shm_id) + : shm(shm_ptr.Pass()), addr(NULL), shm_id(shm_id) { + if (shm->Map(size)) + addr = shm->memory(); +} + +VideoDecoderResource::ShmBuffer::~ShmBuffer() { +} + +VideoDecoderResource::Texture::Texture(uint32_t texture_target, + const PP_Size& size) + : texture_target(texture_target), size(size) { +} + +VideoDecoderResource::Texture::~Texture() { +} + +VideoDecoderResource::Picture::Picture(int32_t decode_id, uint32_t texture_id) + : decode_id(decode_id), texture_id(texture_id) { +} + +VideoDecoderResource::Picture::~Picture() { +} + +VideoDecoderResource::VideoDecoderResource(Connection connection, + PP_Instance instance) + : PluginResource(connection, instance), + num_decodes_(0), + get_picture_(NULL), + gles2_impl_(NULL), + initialized_(false), + testing_(false), + // Set |decoder_last_error_| to PP_OK after successful initialization. + // This makes error checking a little more concise, since we can check + // that the decoder has been initialized and hasn't returned an error by + // just testing |decoder_last_error_|. + decoder_last_error_(PP_ERROR_FAILED) { + // Clear the decode_ids_ array. + memset(decode_ids_, 0, arraysize(decode_ids_)); + SendCreate(RENDERER, PpapiHostMsg_VideoDecoder_Create()); +} + +VideoDecoderResource::~VideoDecoderResource() { + // Destroy any textures which haven't been dismissed. + TextureMap::iterator it = textures_.begin(); + for (; it != textures_.end(); ++it) + DeleteGLTexture(it->first); +} + +PPB_VideoDecoder_API* VideoDecoderResource::AsPPB_VideoDecoder_API() { + return this; +} + +int32_t VideoDecoderResource::Initialize( + PP_Resource graphics_context, + PP_VideoProfile profile, + PP_Bool allow_software_fallback, + scoped_refptr<TrackedCallback> callback) { + if (initialized_) + return PP_ERROR_FAILED; + if (profile < 0 || profile > PP_VIDEOPROFILE_MAX) + return PP_ERROR_BADARGUMENT; + if (initialize_callback_) + return PP_ERROR_INPROGRESS; + if (!graphics_context) + return PP_ERROR_BADRESOURCE; + + HostResource host_resource; + if (!testing_) { + // Create a new Graphics3D resource that can create texture resources to + // share with the plugin. We can't use the plugin's Graphics3D, since we + // create textures on a proxy thread, and would interfere with the plugin. + thunk::EnterResourceCreationNoLock enter_create(pp_instance()); + if (enter_create.failed()) + return PP_ERROR_FAILED; + int32_t attrib_list[] = {PP_GRAPHICS3DATTRIB_NONE}; + graphics3d_ = + ScopedPPResource(ScopedPPResource::PassRef(), + enter_create.functions()->CreateGraphics3D( + pp_instance(), graphics_context, attrib_list)); + EnterResourceNoLock<PPB_Graphics3D_API> enter_graphics(graphics3d_.get(), + true); + if (enter_graphics.failed()) + return PP_ERROR_BADRESOURCE; + + PPB_Graphics3D_Shared* ppb_graphics3d_shared = + static_cast<PPB_Graphics3D_Shared*>(enter_graphics.object()); + gles2_impl_ = ppb_graphics3d_shared->gles2_impl(); + host_resource = ppb_graphics3d_shared->host_resource(); + } + + initialize_callback_ = callback; + + Call<PpapiPluginMsg_VideoDecoder_InitializeReply>( + RENDERER, + PpapiHostMsg_VideoDecoder_Initialize( + host_resource, profile, PP_ToBool(allow_software_fallback)), + base::Bind(&VideoDecoderResource::OnPluginMsgInitializeComplete, this)); + + return PP_OK_COMPLETIONPENDING; +} + +int32_t VideoDecoderResource::Decode(uint32_t decode_id, + uint32_t size, + const void* buffer, + scoped_refptr<TrackedCallback> callback) { + if (decoder_last_error_) + return decoder_last_error_; + if (flush_callback_ || reset_callback_) + return PP_ERROR_FAILED; + if (decode_callback_) + return PP_ERROR_INPROGRESS; + if (size > kMaximumBitstreamBufferSize) + return PP_ERROR_NOMEMORY; + + // If we allow the plugin to call Decode again, we must have somewhere to + // copy their buffer. + DCHECK(!available_shm_buffers_.empty() || + shm_buffers_.size() < kMaximumPendingDecodes); + + // Count up, wrapping back to 0 before overflowing. + int32_t uid = ++num_decodes_; + if (uid == std::numeric_limits<int32_t>::max()) + num_decodes_ = 0; + + // Save decode_id in a ring buffer. The ring buffer is sized to store + // decode_id for the maximum picture delay. + decode_ids_[uid % kMaximumPictureDelay] = decode_id; + + if (available_shm_buffers_.empty() || + available_shm_buffers_.back()->shm->mapped_size() < size) { + uint32_t shm_id; + if (shm_buffers_.size() < kMaximumPendingDecodes) { + // Signal the host to create a new shm buffer by passing an index outside + // the legal range. + shm_id = static_cast<uint32_t>(shm_buffers_.size()); + } else { + // Signal the host to grow a buffer by passing a legal index. Choose the + // last available shm buffer for simplicity. + shm_id = available_shm_buffers_.back()->shm_id; + available_shm_buffers_.pop_back(); + } + + // Synchronously get shared memory. Use GenericSyncCall so we can get the + // reply params, which contain the handle. + uint32_t shm_size = 0; + IPC::Message reply; + ResourceMessageReplyParams reply_params; + int32_t result = + GenericSyncCall(RENDERER, + PpapiHostMsg_VideoDecoder_GetShm(shm_id, size), + &reply, + &reply_params); + if (result != PP_OK) + return PP_ERROR_FAILED; + if (!UnpackMessage<PpapiPluginMsg_VideoDecoder_GetShmReply>(reply, + &shm_size)) + return PP_ERROR_FAILED; + base::SharedMemoryHandle shm_handle = base::SharedMemory::NULLHandle(); + if (!reply_params.TakeSharedMemoryHandleAtIndex(0, &shm_handle)) + return PP_ERROR_NOMEMORY; + scoped_ptr<base::SharedMemory> shm( + new base::SharedMemory(shm_handle, false /* read_only */)); + scoped_ptr<ShmBuffer> shm_buffer( + new ShmBuffer(shm.Pass(), shm_size, shm_id)); + if (!shm_buffer->addr) + return PP_ERROR_NOMEMORY; + + available_shm_buffers_.push_back(shm_buffer.get()); + if (shm_buffers_.size() < kMaximumPendingDecodes) { + shm_buffers_.push_back(shm_buffer.release()); + } else { + // Delete manually since ScopedVector won't delete the existing element if + // we just assign it. + delete shm_buffers_[shm_id]; + shm_buffers_[shm_id] = shm_buffer.release(); + } + } + + // At this point we should have shared memory to hold the plugin's buffer. + DCHECK(!available_shm_buffers_.empty() && + available_shm_buffers_.back()->shm->mapped_size() >= size); + + ShmBuffer* shm_buffer = available_shm_buffers_.back(); + available_shm_buffers_.pop_back(); + memcpy(shm_buffer->addr, buffer, size); + + Call<PpapiPluginMsg_VideoDecoder_DecodeReply>( + RENDERER, + PpapiHostMsg_VideoDecoder_Decode(shm_buffer->shm_id, size, uid), + base::Bind(&VideoDecoderResource::OnPluginMsgDecodeComplete, this)); + + // If we have another free buffer, or we can still create new buffers, let + // the plugin call Decode again. + if (!available_shm_buffers_.empty() || + shm_buffers_.size() < kMaximumPendingDecodes) + return PP_OK; + + // All buffers are busy and we can't create more. Delay completion until a + // buffer is available. + decode_callback_ = callback; + return PP_OK_COMPLETIONPENDING; +} + +int32_t VideoDecoderResource::GetPicture( + PP_VideoPicture* picture, + scoped_refptr<TrackedCallback> callback) { + if (decoder_last_error_) + return decoder_last_error_; + if (reset_callback_) + return PP_ERROR_FAILED; + if (get_picture_callback_) + return PP_ERROR_INPROGRESS; + + // If the next picture is ready, return it synchronously. + if (!received_pictures_.empty()) { + WriteNextPicture(picture); + return PP_OK; + } + + get_picture_callback_ = callback; + get_picture_ = picture; + return PP_OK_COMPLETIONPENDING; +} + +void VideoDecoderResource::RecyclePicture(const PP_VideoPicture* picture) { + if (decoder_last_error_) + return; + if (reset_callback_) + return; + + Post(RENDERER, PpapiHostMsg_VideoDecoder_RecyclePicture(picture->texture_id)); +} + +int32_t VideoDecoderResource::Flush(scoped_refptr<TrackedCallback> callback) { + if (decoder_last_error_) + return decoder_last_error_; + if (reset_callback_) + return PP_ERROR_FAILED; + if (flush_callback_) + return PP_ERROR_INPROGRESS; + flush_callback_ = callback; + + Call<PpapiPluginMsg_VideoDecoder_FlushReply>( + RENDERER, + PpapiHostMsg_VideoDecoder_Flush(), + base::Bind(&VideoDecoderResource::OnPluginMsgFlushComplete, this)); + + return PP_OK_COMPLETIONPENDING; +} + +int32_t VideoDecoderResource::Reset(scoped_refptr<TrackedCallback> callback) { + if (decoder_last_error_) + return decoder_last_error_; + if (flush_callback_) + return PP_ERROR_FAILED; + if (reset_callback_) + return PP_ERROR_INPROGRESS; + reset_callback_ = callback; + + // Cause any pending Decode or GetPicture callbacks to abort after we return, + // to avoid reentering the plugin. + if (TrackedCallback::IsPending(decode_callback_)) + decode_callback_->PostAbort(); + decode_callback_ = NULL; + if (TrackedCallback::IsPending(get_picture_callback_)) + get_picture_callback_->PostAbort(); + get_picture_callback_ = NULL; + Call<PpapiPluginMsg_VideoDecoder_ResetReply>( + RENDERER, + PpapiHostMsg_VideoDecoder_Reset(), + base::Bind(&VideoDecoderResource::OnPluginMsgResetComplete, this)); + + return PP_OK_COMPLETIONPENDING; +} + +void VideoDecoderResource::OnReplyReceived( + const ResourceMessageReplyParams& params, + const IPC::Message& msg) { + PPAPI_BEGIN_MESSAGE_MAP(VideoDecoderResource, msg) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( + PpapiPluginMsg_VideoDecoder_RequestTextures, OnPluginMsgRequestTextures) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( + PpapiPluginMsg_VideoDecoder_PictureReady, OnPluginMsgPictureReady) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( + PpapiPluginMsg_VideoDecoder_DismissPicture, OnPluginMsgDismissPicture) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( + PpapiPluginMsg_VideoDecoder_NotifyError, OnPluginMsgNotifyError) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED( + PluginResource::OnReplyReceived(params, msg)) + PPAPI_END_MESSAGE_MAP() +} + +void VideoDecoderResource::SetForTest() { + testing_ = true; +} + +void VideoDecoderResource::OnPluginMsgRequestTextures( + const ResourceMessageReplyParams& params, + uint32_t num_textures, + const PP_Size& size, + uint32_t texture_target) { + DCHECK(num_textures); + std::vector<uint32_t> texture_ids(num_textures); + if (gles2_impl_) { + gles2_impl_->GenTextures(num_textures, &texture_ids.front()); + for (uint32_t i = 0; i < num_textures; ++i) { + gles2_impl_->ActiveTexture(GL_TEXTURE0); + gles2_impl_->BindTexture(texture_target, texture_ids[i]); + gles2_impl_->TexParameteri( + texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gles2_impl_->TexParameteri( + texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gles2_impl_->TexParameterf( + texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gles2_impl_->TexParameterf( + texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (texture_target == GL_TEXTURE_2D) { + gles2_impl_->TexImage2D(texture_target, + 0, + GL_RGBA, + size.width, + size.height, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + NULL); + } + + textures_.insert( + std::make_pair(texture_ids[i], Texture(texture_target, size))); + } + gles2_impl_->Flush(); + } else { + DCHECK(testing_); + // Create some fake texture ids so we can test picture handling. + for (uint32_t i = 0; i < num_textures; ++i) { + texture_ids[i] = i + 1; + textures_.insert( + std::make_pair(texture_ids[i], Texture(texture_target, size))); + } + } + + Post(RENDERER, PpapiHostMsg_VideoDecoder_AssignTextures(size, texture_ids)); +} + +void VideoDecoderResource::OnPluginMsgPictureReady( + const ResourceMessageReplyParams& params, + int32_t decode_id, + uint32_t texture_id) { + received_pictures_.push(Picture(decode_id, texture_id)); + + if (TrackedCallback::IsPending(get_picture_callback_)) { + // The plugin may call GetPicture in its callback. + scoped_refptr<TrackedCallback> callback; + callback.swap(get_picture_callback_); + PP_VideoPicture* picture = get_picture_; + get_picture_ = NULL; + WriteNextPicture(picture); + callback->Run(PP_OK); + } +} + +void VideoDecoderResource::OnPluginMsgDismissPicture( + const ResourceMessageReplyParams& params, + uint32_t texture_id) { + DeleteGLTexture(texture_id); + textures_.erase(texture_id); +} + +void VideoDecoderResource::OnPluginMsgNotifyError( + const ResourceMessageReplyParams& params, + int32_t error) { + decoder_last_error_ = error; + // Cause any pending callbacks to run immediately. Reentrancy isn't a problem, + // since the plugin wasn't calling us. + RunCallbackWithError(&initialize_callback_); + RunCallbackWithError(&decode_callback_); + RunCallbackWithError(&get_picture_callback_); + RunCallbackWithError(&flush_callback_); + RunCallbackWithError(&reset_callback_); +} + +void VideoDecoderResource::OnPluginMsgInitializeComplete( + const ResourceMessageReplyParams& params) { + decoder_last_error_ = params.result(); + if (decoder_last_error_ == PP_OK) + initialized_ = true; + + // Let the plugin call Initialize again from its callback in case of failure. + scoped_refptr<TrackedCallback> callback; + callback.swap(initialize_callback_); + callback->Run(decoder_last_error_); +} + +void VideoDecoderResource::OnPluginMsgDecodeComplete( + const ResourceMessageReplyParams& params, + uint32_t shm_id) { + if (shm_id >= shm_buffers_.size()) { + NOTREACHED(); + return; + } + // Make the shm buffer available. + available_shm_buffers_.push_back(shm_buffers_[shm_id]); + // If the plugin is waiting, let it call Decode again. + if (decode_callback_) { + scoped_refptr<TrackedCallback> callback; + callback.swap(decode_callback_); + callback->Run(PP_OK); + } +} + +void VideoDecoderResource::OnPluginMsgFlushComplete( + const ResourceMessageReplyParams& params) { + // All shm buffers should have been made available by now. + DCHECK_EQ(shm_buffers_.size(), available_shm_buffers_.size()); + + if (get_picture_callback_) { + scoped_refptr<TrackedCallback> callback; + callback.swap(get_picture_callback_); + callback->Abort(); + } + + scoped_refptr<TrackedCallback> callback; + callback.swap(flush_callback_); + callback->Run(params.result()); +} + +void VideoDecoderResource::OnPluginMsgResetComplete( + const ResourceMessageReplyParams& params) { + // All shm buffers should have been made available by now. + DCHECK_EQ(shm_buffers_.size(), available_shm_buffers_.size()); + scoped_refptr<TrackedCallback> callback; + callback.swap(reset_callback_); + callback->Run(params.result()); +} + +void VideoDecoderResource::RunCallbackWithError( + scoped_refptr<TrackedCallback>* callback) { + if (TrackedCallback::IsPending(*callback)) { + scoped_refptr<TrackedCallback> temp; + callback->swap(temp); + temp->Run(decoder_last_error_); + } +} + +void VideoDecoderResource::DeleteGLTexture(uint32_t id) { + if (gles2_impl_) { + gles2_impl_->DeleteTextures(1, &id); + gles2_impl_->Flush(); + } +} + +void VideoDecoderResource::WriteNextPicture(PP_VideoPicture* pp_picture) { + DCHECK(!received_pictures_.empty()); + Picture& picture = received_pictures_.front(); + // Internally, we identify decodes by a unique id, which the host returns + // to us in the picture. Use this to get the plugin's decode_id. + pp_picture->decode_id = decode_ids_[picture.decode_id % kMaximumPictureDelay]; + pp_picture->texture_id = picture.texture_id; + TextureMap::iterator it = textures_.find(picture.texture_id); + if (it != textures_.end()) { + pp_picture->texture_target = it->second.texture_target; + pp_picture->texture_size = it->second.size; + } else { + NOTREACHED(); + } + received_pictures_.pop(); +} + +} // namespace proxy +} // namespace ppapi diff --git a/ppapi/proxy/video_decoder_resource.h b/ppapi/proxy/video_decoder_resource.h new file mode 100644 index 0000000..19ccffb --- /dev/null +++ b/ppapi/proxy/video_decoder_resource.h @@ -0,0 +1,179 @@ +// Copyright (c) 2014 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. + +#ifndef PPAPI_PROXY_VIDEO_DECODER_RESOURCE_H_ +#define PPAPI_PROXY_VIDEO_DECODER_RESOURCE_H_ + +#include <queue> + +#include "base/containers/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "ppapi/proxy/connection.h" +#include "ppapi/proxy/plugin_resource.h" +#include "ppapi/proxy/ppapi_proxy_export.h" +#include "ppapi/shared_impl/resource.h" +#include "ppapi/shared_impl/scoped_pp_resource.h" +#include "ppapi/thunk/ppb_video_decoder_api.h" + +namespace gpu { +namespace gles2 { +class GLES2Implementation; +} +} + +namespace ppapi { + +class PPB_Graphics3D_Shared; +class TrackedCallback; + +namespace proxy { + +class PPAPI_PROXY_EXPORT VideoDecoderResource + : public PluginResource, + public thunk::PPB_VideoDecoder_API { + public: + VideoDecoderResource(Connection connection, PP_Instance instance); + virtual ~VideoDecoderResource(); + + // Resource overrides. + virtual thunk::PPB_VideoDecoder_API* AsPPB_VideoDecoder_API() OVERRIDE; + + // PPB_VideoDecoder_API implementation. + virtual int32_t Initialize(PP_Resource graphics_context, + PP_VideoProfile profile, + PP_Bool allow_software_fallback, + scoped_refptr<TrackedCallback> callback) OVERRIDE; + virtual int32_t Decode(uint32_t decode_id, + uint32_t size, + const void* buffer, + scoped_refptr<TrackedCallback> callback) OVERRIDE; + virtual int32_t GetPicture(PP_VideoPicture* picture, + scoped_refptr<TrackedCallback> callback) OVERRIDE; + virtual void RecyclePicture(const PP_VideoPicture* picture) OVERRIDE; + virtual int32_t Flush(scoped_refptr<TrackedCallback> callback) OVERRIDE; + virtual int32_t Reset(scoped_refptr<TrackedCallback> callback) OVERRIDE; + + // PluginResource implementation. + virtual void OnReplyReceived(const ResourceMessageReplyParams& params, + const IPC::Message& msg) OVERRIDE; + + // Called only by unit tests. This bypasses Graphics3D setup, which doesn't + // work in ppapi::proxy::PluginProxyTest. + void SetForTest(); + + private: + // Struct to hold a shared memory buffer. + struct ShmBuffer { + ShmBuffer(scoped_ptr<base::SharedMemory> shm, + uint32_t size, + uint32_t shm_id); + ~ShmBuffer(); + + const scoped_ptr<base::SharedMemory> shm; + void* addr; + // Index into shm_buffers_ vector, used as an id. This should map 1:1 to + // the index on the host side of the proxy. + const uint32_t shm_id; + }; + + // Struct to hold texture information. + struct Texture { + Texture(uint32_t texture_target, const PP_Size& size); + ~Texture(); + + const uint32_t texture_target; + const PP_Size size; + }; + + // Struct to hold a picture received from the decoder. + struct Picture { + Picture(int32_t decode_id, uint32_t texture_id); + ~Picture(); + + int32_t decode_id; + uint32_t texture_id; + }; + + int32_t InitializeInternal(PP_Resource graphics_context, + PP_VideoProfile profile, + PP_Bool allow_software_fallback, + scoped_refptr<TrackedCallback> callback, + bool testing); + + // Unsolicited reply message handlers. + void OnPluginMsgRequestTextures(const ResourceMessageReplyParams& params, + uint32_t num_textures, + const PP_Size& size, + uint32_t texture_target); + void OnPluginMsgPictureReady(const ResourceMessageReplyParams& params, + int32_t decode_id, + uint32_t texture_id); + void OnPluginMsgDismissPicture(const ResourceMessageReplyParams& params, + uint32_t texture_id); + void OnPluginMsgNotifyError(const ResourceMessageReplyParams& params, + int32_t error); + + // Reply message handlers for operations that are done in the host. + void OnPluginMsgInitializeComplete(const ResourceMessageReplyParams& params); + void OnPluginMsgDecodeComplete(const ResourceMessageReplyParams& params, + uint32_t shm_id); + void OnPluginMsgFlushComplete(const ResourceMessageReplyParams& params); + void OnPluginMsgResetComplete(const ResourceMessageReplyParams& params); + + void RunCallbackWithError(scoped_refptr<TrackedCallback>* callback); + void DeleteGLTexture(uint32_t texture_id); + void WriteNextPicture(PP_VideoPicture* picture); + + // ScopedVector to own the shared memory buffers. + ScopedVector<ShmBuffer> shm_buffers_; + + // List of available shared memory buffers. + typedef std::vector<ShmBuffer*> ShmBufferList; + ShmBufferList available_shm_buffers_; + + // Map of GL texture id to texture info. + typedef base::hash_map<uint32_t, Texture> TextureMap; + TextureMap textures_; + + // Queue of received pictures. + typedef std::queue<Picture> PictureQueue; + PictureQueue received_pictures_; + + // Pending callbacks. + scoped_refptr<TrackedCallback> initialize_callback_; + scoped_refptr<TrackedCallback> decode_callback_; + scoped_refptr<TrackedCallback> get_picture_callback_; + scoped_refptr<TrackedCallback> flush_callback_; + scoped_refptr<TrackedCallback> reset_callback_; + + // Number of Decode calls made, mod 2^31, to serve as a uid for each decode. + int32_t num_decodes_; + // The maximum delay (in Decode calls) before we receive a picture. If we + // haven't received a picture from a Decode call after this many successive + // calls to Decode, then we will never receive a picture from the call. + // Note that this isn't guaranteed by H264 or other codecs. In practice, this + // number is less than 16. Make it much larger just to be safe. + // NOTE: because we count decodes mod 2^31, this value must be a power of 2. + static const int kMaximumPictureDelay = 128; + uint32_t decode_ids_[kMaximumPictureDelay]; + + // State for pending get_picture_callback_. + PP_VideoPicture* get_picture_; + + ScopedPPResource graphics3d_; + gpu::gles2::GLES2Implementation* gles2_impl_; + + bool initialized_; + bool testing_; + int32_t decoder_last_error_; + + DISALLOW_COPY_AND_ASSIGN(VideoDecoderResource); +}; + +} // namespace proxy +} // namespace ppapi + +#endif // PPAPI_PROXY_VIDEO_DECODER_RESOURCE_H_ diff --git a/ppapi/proxy/video_decoder_resource_unittest.cc b/ppapi/proxy/video_decoder_resource_unittest.cc new file mode 100644 index 0000000..228da92 --- /dev/null +++ b/ppapi/proxy/video_decoder_resource_unittest.cc @@ -0,0 +1,580 @@ +// Copyright 2014 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. + +#include <GLES2/gl2.h> + +#include "base/memory/shared_memory.h" +#include "base/message_loop/message_loop.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/ppb_video_decoder.h" +#include "ppapi/proxy/locking_resource_releaser.h" +#include "ppapi/proxy/plugin_message_filter.h" +#include "ppapi/proxy/ppapi_message_utils.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/ppapi_proxy_test.h" +#include "ppapi/proxy/ppb_graphics_3d_proxy.h" +#include "ppapi/proxy/video_decoder_constants.h" +#include "ppapi/proxy/video_decoder_resource.h" +#include "ppapi/shared_impl/proxy_lock.h" +#include "ppapi/thunk/thunk.h" + +using ppapi::proxy::ResourceMessageTestSink; + +namespace ppapi { +namespace proxy { + +namespace { + +const PP_Bool kAllowSoftwareFallback = PP_TRUE; +const PP_Resource kGraphics3D = 7; +const uint32_t kShmSize = 256; +const size_t kDecodeBufferSize = 16; +const uint32_t kDecodeId = 5; +const uint32_t kTextureId1 = 1; +const uint32_t kTextureId2 = 2; +const uint32_t kNumRequestedTextures = 2; + +class MockCompletionCallback { + public: + MockCompletionCallback() : called_(false) {} + + bool called() { return called_; } + int32_t result() { return result_; } + + void Reset() { called_ = false; } + + static void Callback(void* user_data, int32_t result) { + MockCompletionCallback* that = + reinterpret_cast<MockCompletionCallback*>(user_data); + that->called_ = true; + that->result_ = result; + } + + private: + bool called_; + int32_t result_; +}; + +class VideoDecoderResourceTest : public PluginProxyTest { + public: + VideoDecoderResourceTest() + : decoder_iface_(thunk::GetPPB_VideoDecoder_0_1_Thunk()) {} + + const PPB_VideoDecoder_0_1* decoder_iface() const { return decoder_iface_; } + + void SendReply(const ResourceMessageCallParams& params, + int32_t result, + const IPC::Message& nested_message) { + ResourceMessageReplyParams reply_params(params.pp_resource(), + params.sequence()); + reply_params.set_result(result); + PluginMessageFilter::DispatchResourceReplyForTest(reply_params, + nested_message); + } + + void SendReplyWithHandle(const ResourceMessageCallParams& params, + int32_t result, + const IPC::Message& nested_message, + const SerializedHandle& handle) { + ResourceMessageReplyParams reply_params(params.pp_resource(), + params.sequence()); + reply_params.set_result(result); + reply_params.AppendHandle(handle); + PluginMessageFilter::DispatchResourceReplyForTest(reply_params, + nested_message); + } + + PP_Resource CreateDecoder() { + PP_Resource result = decoder_iface()->Create(pp_instance()); + if (result) { + ProxyAutoLock lock; + ppapi::Resource* resource = + GetGlobals()->GetResourceTracker()->GetResource(result); + proxy::VideoDecoderResource* decoder = + static_cast<proxy::VideoDecoderResource*>(resource); + decoder->SetForTest(); + } + + return result; + } + + PP_Resource CreateGraphics3d() { + ProxyAutoLock lock; + + HostResource host_resource; + host_resource.SetHostResource(pp_instance(), kGraphics3D); + scoped_refptr<ppapi::proxy::Graphics3D> graphics_3d( + new ppapi::proxy::Graphics3D(host_resource)); + return graphics_3d->GetReference(); + } + + PP_Resource CreateAndInitializeDecoder() { + PP_Resource decoder = CreateDecoder(); + LockingResourceReleaser graphics3d(CreateGraphics3d()); + MockCompletionCallback cb; + int32_t result = decoder_iface()->Initialize( + decoder, + graphics3d.get(), + PP_VIDEOPROFILE_H264MAIN, + PP_TRUE /* allow_software_fallback */, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + if (result != PP_OK_COMPLETIONPENDING) + return 0; + ResourceMessageCallParams params; + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoDecoder_Initialize::ID, ¶ms, &msg)) + return 0; + sink().ClearMessages(); + SendReply(params, PP_OK, PpapiPluginMsg_VideoDecoder_InitializeReply()); + return decoder; + } + + int32_t CallDecode(PP_Resource pp_decoder, + MockCompletionCallback* cb, + const PpapiHostMsg_VideoDecoder_GetShm* expected_shm_msg) { + // Set up a handler in case the resource sends a sync message to create + // shared memory. + PpapiPluginMsg_VideoDecoder_GetShmReply shm_msg_reply(kShmSize); + ResourceSyncCallHandler shm_msg_handler( + &sink(), PpapiHostMsg_VideoDecoder_GetShm::ID, PP_OK, shm_msg_reply); + sink().AddFilter(&shm_msg_handler); + + base::SharedMemory shm; + if (expected_shm_msg) { + shm.CreateAnonymous(kShmSize); + base::SharedMemoryHandle shm_handle; + shm.ShareToProcess(base::GetCurrentProcessHandle(), &shm_handle); + SerializedHandle serialized_handle(shm_handle, kShmSize); + shm_msg_handler.set_serialized_handle(&serialized_handle); + } + + memset(decode_buffer_, 0x55, kDecodeBufferSize); + int32_t result = + decoder_iface()->Decode(pp_decoder, + kDecodeId, + kDecodeBufferSize, + decode_buffer_, + PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, cb)); + + if (expected_shm_msg) { + uint32_t shm_id, shm_size, expected_shm_id, expected_shm_size; + UnpackMessage<PpapiHostMsg_VideoDecoder_GetShm>( + *expected_shm_msg, &expected_shm_id, &expected_shm_size); + if (shm_msg_handler.last_handled_msg().type() == 0 || + !UnpackMessage<PpapiHostMsg_VideoDecoder_GetShm>( + shm_msg_handler.last_handled_msg(), &shm_id, &shm_size) || + shm_id != expected_shm_id || + shm_size != expected_shm_size) { + // Signal that the expected shm message wasn't sent by failing. + result = PP_ERROR_FAILED; + } + } + + sink().RemoveFilter(&shm_msg_handler); + return result; + } + + int32_t CallGetPicture(PP_Resource pp_decoder, + PP_VideoPicture* picture, + MockCompletionCallback* cb) { + int32_t result = + decoder_iface()->GetPicture(pp_decoder, + picture, + PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, cb)); + return result; + } + + void CallRecyclePicture(PP_Resource pp_decoder, + const PP_VideoPicture& picture) { + decoder_iface()->RecyclePicture(pp_decoder, &picture); + } + + int32_t CallFlush(PP_Resource pp_decoder, MockCompletionCallback* cb) { + int32_t result = + decoder_iface()->Flush(pp_decoder, + PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, cb)); + return result; + } + + int32_t CallReset(PP_Resource pp_decoder, MockCompletionCallback* cb) { + int32_t result = + decoder_iface()->Reset(pp_decoder, + PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, cb)); + return result; + } + + void SendDecodeReply(const ResourceMessageCallParams& params, + uint32_t shm_id) { + SendReply(params, PP_OK, PpapiPluginMsg_VideoDecoder_DecodeReply(shm_id)); + } + + void SendPictureReady(const ResourceMessageCallParams& params, + uint32_t decode_count, + uint32_t texture_id) { + SendReply( + params, + PP_OK, + PpapiPluginMsg_VideoDecoder_PictureReady(decode_count, texture_id)); + } + + void SendFlushReply(const ResourceMessageCallParams& params) { + SendReply(params, PP_OK, PpapiPluginMsg_VideoDecoder_FlushReply()); + } + + void SendResetReply(const ResourceMessageCallParams& params) { + SendReply(params, PP_OK, PpapiPluginMsg_VideoDecoder_ResetReply()); + } + + void SendRequestTextures(const ResourceMessageCallParams& params) { + SendReply(params, + PP_OK, + PpapiPluginMsg_VideoDecoder_RequestTextures( + kNumRequestedTextures, PP_MakeSize(320, 240), GL_TEXTURE_2D)); + } + + void SendNotifyError(const ResourceMessageCallParams& params, int32_t error) { + SendReply(params, PP_OK, PpapiPluginMsg_VideoDecoder_NotifyError(error)); + } + + bool CheckDecodeMsg(ResourceMessageCallParams* params, + uint32_t* shm_id, + uint32_t* size, + int32_t* decode_id) { + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoDecoder_Decode::ID, params, &msg)) + return false; + sink().ClearMessages(); + return UnpackMessage<PpapiHostMsg_VideoDecoder_Decode>( + msg, shm_id, size, decode_id); + } + + bool CheckRecyclePictureMsg(ResourceMessageCallParams* params, + uint32_t* texture_id) { + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoDecoder_RecyclePicture::ID, params, &msg)) + return false; + sink().ClearMessages(); + return UnpackMessage<PpapiHostMsg_VideoDecoder_RecyclePicture>(msg, + texture_id); + } + + bool CheckFlushMsg(ResourceMessageCallParams* params) { + return CheckMsg(params, PpapiHostMsg_VideoDecoder_Flush::ID); + } + + bool CheckResetMsg(ResourceMessageCallParams* params) { + return CheckMsg(params, PpapiHostMsg_VideoDecoder_Reset::ID); + } + + void ClearCallbacks(PP_Resource pp_decoder) { + ResourceMessageCallParams params; + MockCompletionCallback cb; + + // Reset to abort Decode and GetPicture callbacks. + CallReset(pp_decoder, &cb); + // Initialize params so we can reply to the Reset. + CheckResetMsg(¶ms); + // Run the Reset callback. + SendResetReply(params); + } + + private: + bool CheckMsg(ResourceMessageCallParams* params, int id) { + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching(id, params, &msg)) + return false; + sink().ClearMessages(); + return true; + } + + const PPB_VideoDecoder_0_1* decoder_iface_; + + char decode_buffer_[kDecodeBufferSize]; +}; + +} // namespace + +TEST_F(VideoDecoderResourceTest, Initialize) { + // Initialize with 0 graphics3d_context should fail. + { + LockingResourceReleaser decoder(CreateDecoder()); + MockCompletionCallback cb; + int32_t result = decoder_iface()->Initialize( + decoder.get(), + 0 /* invalid 3d graphics */, + PP_VIDEOPROFILE_H264MAIN, + kAllowSoftwareFallback, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_ERROR_BADRESOURCE, result); + } + // Initialize with bad profile value should fail. + { + LockingResourceReleaser decoder(CreateDecoder()); + MockCompletionCallback cb; + int32_t result = decoder_iface()->Initialize( + decoder.get(), + 1 /* non-zero resource */, + static_cast<PP_VideoProfile>(-1), + kAllowSoftwareFallback, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_ERROR_BADARGUMENT, result); + } + // Initialize with valid graphics3d_context and profile should succeed. + { + LockingResourceReleaser decoder(CreateDecoder()); + LockingResourceReleaser graphics3d(CreateGraphics3d()); + MockCompletionCallback cb; + int32_t result = decoder_iface()->Initialize( + decoder.get(), + graphics3d.get(), + PP_VIDEOPROFILE_H264MAIN, + kAllowSoftwareFallback, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + ASSERT_TRUE(decoder_iface()->IsVideoDecoder(decoder.get())); + + // Another attempt while pending should fail. + result = decoder_iface()->Initialize( + decoder.get(), + graphics3d.get(), + PP_VIDEOPROFILE_H264MAIN, + kAllowSoftwareFallback, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_ERROR_INPROGRESS, result); + + // Check for host message and send a reply to complete initialization. + ResourceMessageCallParams params; + IPC::Message msg; + ASSERT_TRUE(sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoDecoder_Initialize::ID, ¶ms, &msg)); + sink().ClearMessages(); + SendReply(params, PP_OK, PpapiPluginMsg_VideoDecoder_InitializeReply()); + ASSERT_TRUE(cb.called()); + ASSERT_EQ(PP_OK, cb.result()); + } +} + +TEST_F(VideoDecoderResourceTest, Uninitialized) { + // Operations on uninitialized decoders should fail. + LockingResourceReleaser decoder(CreateDecoder()); + MockCompletionCallback uncalled_cb; + + ASSERT_EQ(PP_ERROR_FAILED, CallDecode(decoder.get(), &uncalled_cb, NULL)); + ASSERT_FALSE(uncalled_cb.called()); + + ASSERT_EQ(PP_ERROR_FAILED, CallGetPicture(decoder.get(), NULL, &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + + ASSERT_EQ(PP_ERROR_FAILED, CallFlush(decoder.get(), &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + + ASSERT_EQ(PP_ERROR_FAILED, CallReset(decoder.get(), &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); +} + +// TODO(bbudge) Fix sync message testing on Windows 64 bit builds. The reply +// message for GetShm isn't received, causing Decode to fail. +// http://crbug.com/379260 +#if !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) +TEST_F(VideoDecoderResourceTest, DecodeAndGetPicture) { + LockingResourceReleaser decoder(CreateAndInitializeDecoder()); + ResourceMessageCallParams params, params2; + MockCompletionCallback decode_cb, get_picture_cb, uncalled_cb; + + uint32_t shm_id; + uint32_t decode_size; + int32_t decode_id; + // Call Decode until we have the maximum pending, minus one. + for (uint32_t i = 0; i < kMaximumPendingDecodes - 1; i++) { + PpapiHostMsg_VideoDecoder_GetShm shm_msg(i, kDecodeBufferSize); + ASSERT_EQ(PP_OK, CallDecode(decoder.get(), &uncalled_cb, &shm_msg)); + ASSERT_FALSE(uncalled_cb.called()); + CheckDecodeMsg(¶ms, &shm_id, &decode_size, &decode_id); + ASSERT_EQ(i, shm_id); + ASSERT_EQ(kDecodeBufferSize, decode_size); + // The resource generates uids internally, starting at 1. + int32_t uid = i + 1; + ASSERT_EQ(uid, decode_id); + } + // Once we've allocated the maximum number of buffers, we must wait. + PpapiHostMsg_VideoDecoder_GetShm shm_msg(7U, kDecodeBufferSize); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallDecode(decoder.get(), &decode_cb, &shm_msg)); + CheckDecodeMsg(¶ms, &shm_id, &decode_size, &decode_id); + ASSERT_EQ(7U, shm_id); + ASSERT_EQ(kDecodeBufferSize, decode_size); + + // Calling Decode when another Decode is pending should fail. + ASSERT_EQ(PP_ERROR_INPROGRESS, CallDecode(decoder.get(), &uncalled_cb, NULL)); + ASSERT_FALSE(uncalled_cb.called()); + // Free up the first decode buffer. + SendDecodeReply(params, 0U); + // The decoder should run the pending callback. + ASSERT_TRUE(decode_cb.called()); + ASSERT_EQ(PP_OK, decode_cb.result()); + decode_cb.Reset(); + + // Now try to get a picture. No picture ready message has been received yet. + PP_VideoPicture picture; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetPicture(decoder.get(), &picture, &get_picture_cb)); + ASSERT_FALSE(get_picture_cb.called()); + // Calling GetPicture when another GetPicture is pending should fail. + ASSERT_EQ(PP_ERROR_INPROGRESS, + CallGetPicture(decoder.get(), &picture, &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + // Send 'request textures' message to initialize textures. + SendRequestTextures(params); + // Send a picture ready message for Decode call 1. The GetPicture callback + // should complete. + SendPictureReady(params, 1U, kTextureId1); + ASSERT_TRUE(get_picture_cb.called()); + ASSERT_EQ(PP_OK, get_picture_cb.result()); + ASSERT_EQ(kDecodeId, picture.decode_id); + get_picture_cb.Reset(); + + // Send a picture ready message for Decode call 2. Since there is no pending + // GetPicture call, the picture should be queued. + SendPictureReady(params, 2U, kTextureId2); + // The next GetPicture should return synchronously. + ASSERT_EQ(PP_OK, CallGetPicture(decoder.get(), &picture, &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + ASSERT_EQ(kDecodeId, picture.decode_id); +} +#endif // !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) + +// TODO(bbudge) Fix sync message testing on Windows 64 bit builds. The reply +// message for GetShm isn't received, causing Decode to fail. +// http://crbug.com/379260 +#if !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) +TEST_F(VideoDecoderResourceTest, RecyclePicture) { + LockingResourceReleaser decoder(CreateAndInitializeDecoder()); + ResourceMessageCallParams params; + MockCompletionCallback decode_cb, get_picture_cb, uncalled_cb; + + // Get to a state where we have a picture to recycle. + PpapiHostMsg_VideoDecoder_GetShm shm_msg(0U, kDecodeBufferSize); + ASSERT_EQ(PP_OK, CallDecode(decoder.get(), &decode_cb, &shm_msg)); + uint32_t shm_id; + uint32_t decode_size; + int32_t decode_id; + CheckDecodeMsg(¶ms, &shm_id, &decode_size, &decode_id); + SendDecodeReply(params, 0U); + // Send 'request textures' message to initialize textures. + SendRequestTextures(params); + // Call GetPicture and send 'picture ready' message to get a picture to + // recycle. + PP_VideoPicture picture; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetPicture(decoder.get(), &picture, &get_picture_cb)); + SendPictureReady(params, 0U, kTextureId1); + ASSERT_EQ(kTextureId1, picture.texture_id); + + CallRecyclePicture(decoder.get(), picture); + uint32_t texture_id; + ASSERT_TRUE(CheckRecyclePictureMsg(¶ms, &texture_id)); + ASSERT_EQ(kTextureId1, texture_id); + + ClearCallbacks(decoder.get()); +} +#endif // !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) + +TEST_F(VideoDecoderResourceTest, Flush) { + LockingResourceReleaser decoder(CreateAndInitializeDecoder()); + ResourceMessageCallParams params, params2; + MockCompletionCallback flush_cb, get_picture_cb, uncalled_cb; + + ASSERT_EQ(PP_OK_COMPLETIONPENDING, CallFlush(decoder.get(), &flush_cb)); + ASSERT_FALSE(flush_cb.called()); + ASSERT_TRUE(CheckFlushMsg(¶ms)); + + ASSERT_EQ(PP_ERROR_FAILED, CallDecode(decoder.get(), &uncalled_cb, NULL)); + ASSERT_FALSE(uncalled_cb.called()); + + // Plugin can call GetPicture while Flush is pending. + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetPicture(decoder.get(), NULL, &get_picture_cb)); + ASSERT_FALSE(get_picture_cb.called()); + + ASSERT_EQ(PP_ERROR_INPROGRESS, CallFlush(decoder.get(), &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + + ASSERT_EQ(PP_ERROR_FAILED, CallReset(decoder.get(), &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + + // Plugin can call RecyclePicture while Flush is pending. + PP_VideoPicture picture; + picture.texture_id = kTextureId1; + CallRecyclePicture(decoder.get(), picture); + uint32_t texture_id; + ASSERT_TRUE(CheckRecyclePictureMsg(¶ms2, &texture_id)); + + SendFlushReply(params); + // Any pending GetPicture call is aborted. + ASSERT_TRUE(get_picture_cb.called()); + ASSERT_EQ(PP_ERROR_ABORTED, get_picture_cb.result()); + ASSERT_TRUE(flush_cb.called()); + ASSERT_EQ(PP_OK, flush_cb.result()); +} + +// TODO(bbudge) Test Reset when we can run the message loop to get aborted +// callbacks to run. + +// TODO(bbudge) Fix sync message testing on Windows 64 bit builds. The reply +// message for GetShm isn't received, causing Decode to fail. +// http://crbug.com/379260 +#if !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) +TEST_F(VideoDecoderResourceTest, NotifyError) { + LockingResourceReleaser decoder(CreateAndInitializeDecoder()); + ResourceMessageCallParams params; + MockCompletionCallback decode_cb, get_picture_cb, uncalled_cb; + + // Call Decode and GetPicture to have some pending requests. + PpapiHostMsg_VideoDecoder_GetShm shm_msg(0U, kDecodeBufferSize); + ASSERT_EQ(PP_OK, CallDecode(decoder.get(), &decode_cb, &shm_msg)); + ASSERT_FALSE(decode_cb.called()); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetPicture(decoder.get(), NULL, &get_picture_cb)); + ASSERT_FALSE(get_picture_cb.called()); + + // Send the decoder resource an unsolicited notify error message. We first + // need to initialize 'params' so the message is routed to the decoder. + uint32_t shm_id; + uint32_t decode_size; + int32_t decode_id; + CheckDecodeMsg(¶ms, &shm_id, &decode_size, &decode_id); + SendNotifyError(params, PP_ERROR_RESOURCE_FAILED); + + // Any pending message should be run with the reported error. + ASSERT_TRUE(get_picture_cb.called()); + ASSERT_EQ(PP_ERROR_RESOURCE_FAILED, get_picture_cb.result()); + + // All further calls return the reported error. + ASSERT_EQ(PP_ERROR_RESOURCE_FAILED, + CallDecode(decoder.get(), &uncalled_cb, NULL)); + ASSERT_FALSE(uncalled_cb.called()); + ASSERT_EQ(PP_ERROR_RESOURCE_FAILED, + CallGetPicture(decoder.get(), NULL, &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + ASSERT_EQ(PP_ERROR_RESOURCE_FAILED, CallFlush(decoder.get(), &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + ASSERT_EQ(PP_ERROR_RESOURCE_FAILED, CallReset(decoder.get(), &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); +} +#endif // !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) + +} // namespace proxy +} // namespace ppapi diff --git a/ppapi/shared_impl/resource.h b/ppapi/shared_impl/resource.h index 3fe3496..1dab07b 100644 --- a/ppapi/shared_impl/resource.h +++ b/ppapi/shared_impl/resource.h @@ -80,6 +80,7 @@ F(PPB_URLRequestInfo_API) \ F(PPB_URLResponseInfo_API) \ F(PPB_VideoCapture_API) \ + F(PPB_VideoDecoder_API) \ F(PPB_VideoDecoder_Dev_API) \ F(PPB_VideoDestination_Private_API) \ F(PPB_VideoFrame_API) \ diff --git a/ppapi/thunk/interfaces_ppb_public_dev_channel.h b/ppapi/thunk/interfaces_ppb_public_dev_channel.h index c2b6448..81600dd 100644 --- a/ppapi/thunk/interfaces_ppb_public_dev_channel.h +++ b/ppapi/thunk/interfaces_ppb_public_dev_channel.h @@ -9,5 +9,6 @@ // Interfaces go here. PROXIED_IFACE(PPB_FILEMAPPING_INTERFACE_0_1, PPB_FileMapping_0_1) +PROXIED_IFACE(PPB_VIDEODECODER_INTERFACE_0_1, PPB_VideoDecoder_0_1) #include "ppapi/thunk/interfaces_postamble.h" diff --git a/ppapi/thunk/resource_creation_api.h b/ppapi/thunk/resource_creation_api.h index 498d0d8..651de3d 100644 --- a/ppapi/thunk/resource_creation_api.h +++ b/ppapi/thunk/resource_creation_api.h @@ -160,6 +160,7 @@ class ResourceCreationAPI { virtual PP_Resource CreateTCPSocketPrivate(PP_Instance instace) = 0; virtual PP_Resource CreateUDPSocket(PP_Instance instace) = 0; virtual PP_Resource CreateUDPSocketPrivate(PP_Instance instace) = 0; + virtual PP_Resource CreateVideoDecoder(PP_Instance instance) = 0; virtual PP_Resource CreateVideoDestination(PP_Instance instance) = 0; virtual PP_Resource CreateVideoSource(PP_Instance instance) = 0; virtual PP_Resource CreateWebSocket(PP_Instance instance) = 0; |