summaryrefslogtreecommitdiffstats
path: root/ppapi
diff options
context:
space:
mode:
authorbbudge@chromium.org <bbudge@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-30 22:45:56 +0000
committerbbudge@chromium.org <bbudge@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-30 22:45:56 +0000
commitdfb0d06f30c661f3d3f51baa2db8e5caa0c9ad58 (patch)
tree968e3ad3ad61dc499cad6909ae9cd9a7259c078a /ppapi
parentb0f8d6dce36cb22bc3fdf5cd592475493793cbc5 (diff)
downloadchromium_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.idl12
-rw-r--r--ppapi/c/pp_errors.h13
-rw-r--r--ppapi/examples/video_decode/video_decode.cc609
-rw-r--r--ppapi/examples/video_decode/video_decode.html18
-rw-r--r--ppapi/ppapi_proxy.gypi2
-rw-r--r--ppapi/ppapi_shared.gypi1
-rw-r--r--ppapi/ppapi_tests.gypi4
-rw-r--r--ppapi/proxy/interface_list.cc1
-rw-r--r--ppapi/proxy/ppapi_messages.h44
-rw-r--r--ppapi/proxy/ppb_graphics_3d_proxy.h3
-rw-r--r--ppapi/proxy/resource_creation_proxy.cc5
-rw-r--r--ppapi/proxy/resource_creation_proxy.h1
-rw-r--r--ppapi/proxy/video_decoder_constants.h28
-rw-r--r--ppapi/proxy/video_decoder_resource.cc506
-rw-r--r--ppapi/proxy/video_decoder_resource.h179
-rw-r--r--ppapi/proxy/video_decoder_resource_unittest.cc580
-rw-r--r--ppapi/shared_impl/resource.h1
-rw-r--r--ppapi/thunk/interfaces_ppb_public_dev_channel.h1
-rw-r--r--ppapi/thunk/resource_creation_api.h1
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, &params, &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(&params);
+ // 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, &params, &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(&params, &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(&params, &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(&params, &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(&params, &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(&params));
+
+ 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(&params2, &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(&params, &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;