| // 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 "content/renderer/pepper/video_decoder_shim.h" |
| |
| #include <GLES2/gl2.h> |
| #include <GLES2/gl2ext.h> |
| #include <GLES2/gl2extchromium.h> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "cc/blink/context_provider_web_context.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "content/renderer/pepper/pepper_video_decoder_host.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "gpu/command_buffer/client/gles2_interface.h" |
| #include "media/base/cdm_context.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/limits.h" |
| #include "media/base/media_util.h" |
| #include "media/base/video_decoder.h" |
| #include "media/filters/ffmpeg_video_decoder.h" |
| #include "media/filters/vpx_video_decoder.h" |
| #include "media/renderers/skcanvas_video_renderer.h" |
| #include "media/video/picture.h" |
| #include "media/video/video_decode_accelerator.h" |
| #include "ppapi/c/pp_errors.h" |
| #include "third_party/skia/include/gpu/GrTypes.h" |
| |
| namespace content { |
| |
| static const uint32_t kGrInvalidateState = |
| kRenderTarget_GrGLBackendState | kTextureBinding_GrGLBackendState | |
| kView_GrGLBackendState | kVertex_GrGLBackendState | |
| kProgram_GrGLBackendState | kPixelStore_GrGLBackendState; |
| |
| namespace { |
| |
| bool IsCodecSupported(media::VideoCodec codec) { |
| #if !defined(MEDIA_DISABLE_LIBVPX) |
| if (codec == media::kCodecVP9) |
| return true; |
| #endif |
| |
| #if !defined(MEDIA_DISABLE_FFMPEG) && !defined(DISABLE_FFMPEG_VIDEO_DECODERS) |
| return media::FFmpegVideoDecoder::IsCodecSupported(codec); |
| #else |
| return false; |
| #endif |
| } |
| |
| } // namespace |
| |
| // YUV->RGB converter class using a shader and FBO. |
| class VideoDecoderShim::YUVConverter { |
| public: |
| YUVConverter(const scoped_refptr<cc_blink::ContextProviderWebContext>&); |
| ~YUVConverter(); |
| bool Initialize(); |
| void Convert(const scoped_refptr<media::VideoFrame>& frame, GLuint tex_out); |
| |
| private: |
| GLuint CreateShader(); |
| GLuint CompileShader(const char* name, GLuint type, const char* code); |
| GLuint CreateProgram(const char* name, GLuint vshader, GLuint fshader); |
| GLuint CreateTexture(); |
| |
| scoped_refptr<cc_blink::ContextProviderWebContext> context_provider_; |
| gpu::gles2::GLES2Interface* gl_; |
| GLuint frame_buffer_; |
| GLuint vertex_buffer_; |
| GLuint program_; |
| |
| GLuint y_texture_; |
| GLuint u_texture_; |
| GLuint v_texture_; |
| GLuint a_texture_; |
| |
| GLuint internal_format_; |
| GLuint format_; |
| media::VideoPixelFormat video_format_; |
| |
| GLuint y_width_; |
| GLuint y_height_; |
| |
| GLuint uv_width_; |
| GLuint uv_height_; |
| uint32_t uv_height_divisor_; |
| uint32_t uv_width_divisor_; |
| |
| GLint yuv_matrix_loc_; |
| GLint yuv_adjust_loc_; |
| |
| DISALLOW_COPY_AND_ASSIGN(YUVConverter); |
| }; |
| |
| VideoDecoderShim::YUVConverter::YUVConverter( |
| const scoped_refptr<cc_blink::ContextProviderWebContext>& context_provider) |
| : context_provider_(context_provider), |
| gl_(context_provider_->ContextGL()), |
| frame_buffer_(0), |
| vertex_buffer_(0), |
| program_(0), |
| y_texture_(0), |
| u_texture_(0), |
| v_texture_(0), |
| a_texture_(0), |
| internal_format_(0), |
| format_(0), |
| video_format_(media::PIXEL_FORMAT_UNKNOWN), |
| y_width_(2), |
| y_height_(2), |
| uv_width_(2), |
| uv_height_(2), |
| uv_height_divisor_(1), |
| uv_width_divisor_(1), |
| yuv_matrix_loc_(0), |
| yuv_adjust_loc_(0) { |
| DCHECK(gl_); |
| } |
| |
| VideoDecoderShim::YUVConverter::~YUVConverter() { |
| if (y_texture_) |
| gl_->DeleteTextures(1, &y_texture_); |
| |
| if (u_texture_) |
| gl_->DeleteTextures(1, &u_texture_); |
| |
| if (v_texture_) |
| gl_->DeleteTextures(1, &v_texture_); |
| |
| if (a_texture_) |
| gl_->DeleteTextures(1, &a_texture_); |
| |
| if (frame_buffer_) |
| gl_->DeleteFramebuffers(1, &frame_buffer_); |
| |
| if (vertex_buffer_) |
| gl_->DeleteBuffers(1, &vertex_buffer_); |
| |
| if (program_) |
| gl_->DeleteProgram(program_); |
| } |
| |
| GLuint VideoDecoderShim::YUVConverter::CreateTexture() { |
| GLuint tex = 0; |
| |
| gl_->GenTextures(1, &tex); |
| gl_->BindTexture(GL_TEXTURE_2D, tex); |
| |
| // Create texture with default size - will be resized upon first frame. |
| gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, 2, 2, 0, format_, |
| GL_UNSIGNED_BYTE, NULL); |
| |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| |
| gl_->BindTexture(GL_TEXTURE_2D, 0); |
| |
| return tex; |
| } |
| |
| GLuint VideoDecoderShim::YUVConverter::CompileShader(const char* name, |
| GLuint type, |
| const char* code) { |
| GLuint shader = gl_->CreateShader(type); |
| |
| gl_->ShaderSource(shader, 1, (const GLchar**)&code, NULL); |
| gl_->CompileShader(shader); |
| |
| #ifndef NDEBUG |
| GLint status = 0; |
| |
| gl_->GetShaderiv(shader, GL_COMPILE_STATUS, &status); |
| if (status != GL_TRUE) { |
| GLint max_length = 0; |
| GLint actual_length = 0; |
| gl_->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length); |
| |
| // The max_length includes the NULL character. |
| std::string error_log(max_length, 0); |
| gl_->GetShaderInfoLog(shader, max_length, &actual_length, &error_log[0]); |
| |
| LOG(ERROR) << name << " shader compilation failed: " << error_log.c_str(); |
| gl_->DeleteShader(shader); |
| return 0; |
| } |
| #endif |
| |
| return shader; |
| } |
| |
| GLuint VideoDecoderShim::YUVConverter::CreateProgram(const char* name, |
| GLuint vshader, |
| GLuint fshader) { |
| GLuint program = gl_->CreateProgram(); |
| gl_->AttachShader(program, vshader); |
| gl_->AttachShader(program, fshader); |
| |
| gl_->BindAttribLocation(program, 0, "position"); |
| |
| gl_->LinkProgram(program); |
| |
| #ifndef NDEBUG |
| GLint status = 0; |
| |
| gl_->GetProgramiv(program, GL_LINK_STATUS, &status); |
| if (status != GL_TRUE) { |
| GLint max_length = 0; |
| GLint actual_length = 0; |
| gl_->GetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length); |
| |
| // The max_length includes the NULL character. |
| std::string error_log(max_length, 0); |
| gl_->GetProgramInfoLog(program, max_length, &actual_length, &error_log[0]); |
| |
| LOG(ERROR) << name << " program linking failed: " << error_log.c_str(); |
| return 0; |
| } |
| #endif |
| |
| return program; |
| } |
| |
| GLuint VideoDecoderShim::YUVConverter::CreateShader() { |
| const char* vert_shader = |
| "precision mediump float;\n" |
| "attribute vec2 position;\n" |
| "varying vec2 texcoord;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4( position.xy, 0, 1 );\n" |
| " texcoord = position*0.5+0.5;\n" |
| "}"; |
| |
| const char* frag_shader = |
| "precision mediump float;\n" |
| "varying vec2 texcoord;\n" |
| "uniform sampler2D y_sampler;\n" |
| "uniform sampler2D u_sampler;\n" |
| "uniform sampler2D v_sampler;\n" |
| "uniform sampler2D a_sampler;\n" |
| "uniform mat3 yuv_matrix;\n" |
| "uniform vec3 yuv_adjust;\n" |
| "void main()\n" |
| "{\n" |
| " vec3 yuv = vec3(texture2D(y_sampler, texcoord).x,\n" |
| " texture2D(u_sampler, texcoord).x,\n" |
| " texture2D(v_sampler, texcoord).x) +\n" |
| " yuv_adjust;\n" |
| " gl_FragColor = vec4(yuv_matrix * yuv, texture2D(a_sampler, " |
| "texcoord).x);\n" |
| "}"; |
| |
| GLuint vertex_shader = |
| CompileShader("Vertex Shader", GL_VERTEX_SHADER, vert_shader); |
| if (!vertex_shader) { |
| return 0; |
| } |
| |
| GLuint fragment_shader = |
| CompileShader("Fragment Shader", GL_FRAGMENT_SHADER, frag_shader); |
| if (!fragment_shader) { |
| gl_->DeleteShader(vertex_shader); |
| return 0; |
| } |
| |
| GLuint program = |
| CreateProgram("YUVConverter Program", vertex_shader, fragment_shader); |
| |
| gl_->DeleteShader(vertex_shader); |
| gl_->DeleteShader(fragment_shader); |
| |
| if (!program) { |
| return 0; |
| } |
| |
| gl_->UseProgram(program); |
| |
| GLint uniform_location; |
| uniform_location = gl_->GetUniformLocation(program, "y_sampler"); |
| DCHECK(uniform_location != -1); |
| gl_->Uniform1i(uniform_location, 0); |
| |
| uniform_location = gl_->GetUniformLocation(program, "u_sampler"); |
| DCHECK(uniform_location != -1); |
| gl_->Uniform1i(uniform_location, 1); |
| |
| uniform_location = gl_->GetUniformLocation(program, "v_sampler"); |
| DCHECK(uniform_location != -1); |
| gl_->Uniform1i(uniform_location, 2); |
| |
| uniform_location = gl_->GetUniformLocation(program, "a_sampler"); |
| DCHECK(uniform_location != -1); |
| gl_->Uniform1i(uniform_location, 3); |
| |
| gl_->UseProgram(0); |
| |
| yuv_matrix_loc_ = gl_->GetUniformLocation(program, "yuv_matrix"); |
| DCHECK(yuv_matrix_loc_ != -1); |
| |
| yuv_adjust_loc_ = gl_->GetUniformLocation(program, "yuv_adjust"); |
| DCHECK(yuv_adjust_loc_ != -1); |
| |
| return program; |
| } |
| |
| bool VideoDecoderShim::YUVConverter::Initialize() { |
| // If texture_rg extension is not available, use slower GL_LUMINANCE. |
| if (context_provider_->ContextCapabilities().gpu.texture_rg) { |
| internal_format_ = GL_RED_EXT; |
| format_ = GL_RED_EXT; |
| } else { |
| internal_format_ = GL_LUMINANCE; |
| format_ = GL_LUMINANCE; |
| } |
| |
| if (context_provider_->ContextCapabilities().gpu.max_texture_image_units < |
| 4) { |
| // We support YUVA textures and require 4 texture units in the fragment |
| // stage. |
| return false; |
| } |
| |
| gl_->TraceBeginCHROMIUM("YUVConverter", "YUVConverterContext"); |
| gl_->GenFramebuffers(1, &frame_buffer_); |
| |
| y_texture_ = CreateTexture(); |
| u_texture_ = CreateTexture(); |
| v_texture_ = CreateTexture(); |
| a_texture_ = CreateTexture(); |
| |
| // Vertex positions. Also converted to texcoords in vertex shader. |
| GLfloat vertex_positions[] = {-1.f, -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f}; |
| |
| gl_->GenBuffers(1, &vertex_buffer_); |
| gl_->BindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); |
| gl_->BufferData(GL_ARRAY_BUFFER, 2 * sizeof(GLfloat) * 4, vertex_positions, |
| GL_STATIC_DRAW); |
| gl_->BindBuffer(GL_ARRAY_BUFFER, 0); |
| |
| program_ = CreateShader(); |
| |
| gl_->TraceEndCHROMIUM(); |
| |
| context_provider_->InvalidateGrContext(kGrInvalidateState); |
| |
| return (program_ != 0); |
| } |
| |
| void VideoDecoderShim::YUVConverter::Convert( |
| const scoped_refptr<media::VideoFrame>& frame, |
| GLuint tex_out) { |
| const float* yuv_matrix = 0; |
| const float* yuv_adjust = 0; |
| |
| if (video_format_ != frame->format()) { |
| // The constants below were taken from cc/output/gl_renderer.cc. |
| // These values are magic numbers that are used in the transformation from |
| // YUV to RGB color values. They are taken from the following webpage: |
| // http://www.fourcc.org/fccyvrgb.php |
| const float yuv_to_rgb_rec601[9] = { |
| 1.164f, 1.164f, 1.164f, 0.0f, -.391f, 2.018f, 1.596f, -.813f, 0.0f, |
| }; |
| const float yuv_to_rgb_jpeg[9] = { |
| 1.f, 1.f, 1.f, 0.0f, -.34414f, 1.772f, 1.402f, -.71414f, 0.0f, |
| }; |
| const float yuv_to_rgb_rec709[9] = { |
| 1.164f, 1.164f, 1.164f, 0.0f, -0.213f, 2.112f, 1.793f, -0.533f, 0.0f, |
| }; |
| |
| // These values map to 16, 128, and 128 respectively, and are computed |
| // as a fraction over 256 (e.g. 16 / 256 = 0.0625). |
| // They are used in the YUV to RGBA conversion formula: |
| // Y - 16 : Gives 16 values of head and footroom for overshooting |
| // U - 128 : Turns unsigned U into signed U [-128,127] |
| // V - 128 : Turns unsigned V into signed V [-128,127] |
| const float yuv_adjust_constrained[3] = { |
| -0.0625f, -0.5f, -0.5f, |
| }; |
| // Same as above, but without the head and footroom. |
| const float yuv_adjust_full[3] = { |
| 0.0f, -0.5f, -0.5f, |
| }; |
| |
| yuv_adjust = yuv_adjust_constrained; |
| yuv_matrix = yuv_to_rgb_rec601; |
| |
| int result; |
| if (frame->metadata()->GetInteger(media::VideoFrameMetadata::COLOR_SPACE, |
| &result)) { |
| if (result == media::COLOR_SPACE_JPEG) { |
| yuv_matrix = yuv_to_rgb_jpeg; |
| yuv_adjust = yuv_adjust_full; |
| } else if (result == media::COLOR_SPACE_HD_REC709) { |
| yuv_matrix = yuv_to_rgb_rec709; |
| } |
| } |
| |
| switch (frame->format()) { |
| case media::PIXEL_FORMAT_YV12: // 420 |
| case media::PIXEL_FORMAT_YV12A: |
| case media::PIXEL_FORMAT_I420: |
| uv_height_divisor_ = 2; |
| uv_width_divisor_ = 2; |
| break; |
| case media::PIXEL_FORMAT_YV16: // 422 |
| uv_width_divisor_ = 2; |
| uv_height_divisor_ = 1; |
| break; |
| case media::PIXEL_FORMAT_YV24: // 444 |
| uv_width_divisor_ = 1; |
| uv_height_divisor_ = 1; |
| break; |
| |
| default: |
| NOTREACHED(); |
| } |
| |
| video_format_ = frame->format(); |
| |
| // Zero these so everything is reset below. |
| y_width_ = y_height_ = 0; |
| } |
| |
| gl_->TraceBeginCHROMIUM("YUVConverter", "YUVConverterContext"); |
| |
| uint32_t ywidth = frame->coded_size().width(); |
| uint32_t yheight = frame->coded_size().height(); |
| |
| DCHECK_EQ(frame->stride(media::VideoFrame::kUPlane), |
| frame->stride(media::VideoFrame::kVPlane)); |
| |
| uint32_t ystride = frame->stride(media::VideoFrame::kYPlane); |
| uint32_t uvstride = frame->stride(media::VideoFrame::kUPlane); |
| |
| // The following code assumes that extended GLES 2.0 state like |
| // UNPACK_SKIP* (if available) are set to defaults. |
| gl_->PixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| |
| if (ywidth != y_width_ || yheight != y_height_) { |
| y_width_ = ywidth; |
| y_height_ = yheight; |
| |
| uv_width_ = y_width_ / uv_width_divisor_; |
| uv_height_ = y_height_ / uv_height_divisor_; |
| |
| // Re-create to resize the textures and upload data. |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, ystride); |
| gl_->ActiveTexture(GL_TEXTURE0); |
| gl_->BindTexture(GL_TEXTURE_2D, y_texture_); |
| gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, y_width_, y_height_, 0, |
| format_, GL_UNSIGNED_BYTE, |
| frame->data(media::VideoFrame::kYPlane)); |
| |
| if (video_format_ == media::PIXEL_FORMAT_YV12A) { |
| DCHECK_EQ(frame->stride(media::VideoFrame::kYPlane), |
| frame->stride(media::VideoFrame::kAPlane)); |
| gl_->ActiveTexture(GL_TEXTURE3); |
| gl_->BindTexture(GL_TEXTURE_2D, a_texture_); |
| gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, y_width_, y_height_, |
| 0, format_, GL_UNSIGNED_BYTE, |
| frame->data(media::VideoFrame::kAPlane)); |
| } else { |
| // if there is no alpha channel, then create a 2x2 texture with full |
| // alpha. |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); |
| const uint8_t alpha[4] = {0xff, 0xff, 0xff, 0xff}; |
| gl_->ActiveTexture(GL_TEXTURE3); |
| gl_->BindTexture(GL_TEXTURE_2D, a_texture_); |
| gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, 2, 2, 0, format_, |
| GL_UNSIGNED_BYTE, alpha); |
| } |
| |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, uvstride); |
| gl_->ActiveTexture(GL_TEXTURE1); |
| gl_->BindTexture(GL_TEXTURE_2D, u_texture_); |
| gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, uv_width_, uv_height_, |
| 0, format_, GL_UNSIGNED_BYTE, |
| frame->data(media::VideoFrame::kUPlane)); |
| |
| gl_->ActiveTexture(GL_TEXTURE2); |
| gl_->BindTexture(GL_TEXTURE_2D, v_texture_); |
| gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, uv_width_, uv_height_, |
| 0, format_, GL_UNSIGNED_BYTE, |
| frame->data(media::VideoFrame::kVPlane)); |
| } else { |
| // Bind textures and upload texture data |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, ystride); |
| gl_->ActiveTexture(GL_TEXTURE0); |
| gl_->BindTexture(GL_TEXTURE_2D, y_texture_); |
| gl_->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, y_width_, y_height_, format_, |
| GL_UNSIGNED_BYTE, |
| frame->data(media::VideoFrame::kYPlane)); |
| |
| if (video_format_ == media::PIXEL_FORMAT_YV12A) { |
| DCHECK_EQ(frame->stride(media::VideoFrame::kYPlane), |
| frame->stride(media::VideoFrame::kAPlane)); |
| gl_->ActiveTexture(GL_TEXTURE3); |
| gl_->BindTexture(GL_TEXTURE_2D, a_texture_); |
| gl_->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, y_width_, y_height_, format_, |
| GL_UNSIGNED_BYTE, |
| frame->data(media::VideoFrame::kAPlane)); |
| } else { |
| gl_->ActiveTexture(GL_TEXTURE3); |
| gl_->BindTexture(GL_TEXTURE_2D, a_texture_); |
| } |
| |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, uvstride); |
| gl_->ActiveTexture(GL_TEXTURE1); |
| gl_->BindTexture(GL_TEXTURE_2D, u_texture_); |
| gl_->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, uv_width_, uv_height_, format_, |
| GL_UNSIGNED_BYTE, |
| frame->data(media::VideoFrame::kUPlane)); |
| |
| gl_->ActiveTexture(GL_TEXTURE2); |
| gl_->BindTexture(GL_TEXTURE_2D, v_texture_); |
| gl_->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, uv_width_, uv_height_, format_, |
| GL_UNSIGNED_BYTE, |
| frame->data(media::VideoFrame::kVPlane)); |
| } |
| |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, frame_buffer_); |
| gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| tex_out, 0); |
| |
| #ifndef NDEBUG |
| // We should probably check for framebuffer complete here, but that |
| // will slow this method down so check only in debug mode. |
| GLint status = gl_->CheckFramebufferStatus(GL_FRAMEBUFFER); |
| if (status != GL_FRAMEBUFFER_COMPLETE) { |
| return; |
| } |
| #endif |
| |
| gl_->Viewport(0, 0, ywidth, yheight); |
| |
| gl_->UseProgram(program_); |
| |
| if (yuv_matrix) { |
| gl_->UniformMatrix3fv(yuv_matrix_loc_, 1, 0, yuv_matrix); |
| gl_->Uniform3fv(yuv_adjust_loc_, 1, yuv_adjust); |
| } |
| |
| gl_->BindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); |
| gl_->EnableVertexAttribArray(0); |
| gl_->VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), |
| static_cast<const void*>(0)); |
| |
| gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| |
| // The YUVConverter shares the context with Skia and possibly other modules |
| // that may make OpenGL calls. To be a "good OpenGL citizen" for other |
| // (non-Skia) modules that may share this context we restore |
| // buffer/texture/state bindings to OpenGL defaults here. If we were only |
| // sharing the context with Skia this may not be necessary as we also |
| // Invalidate the GrContext below so that Skia is aware that its state |
| // caches need to be reset. |
| |
| gl_->BindBuffer(GL_ARRAY_BUFFER, 0); |
| gl_->DisableVertexAttribArray(0); |
| gl_->UseProgram(0); |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, 0); |
| |
| gl_->BindTexture(GL_TEXTURE_2D, 0); |
| |
| gl_->ActiveTexture(GL_TEXTURE2); |
| gl_->BindTexture(GL_TEXTURE_2D, 0); |
| |
| gl_->ActiveTexture(GL_TEXTURE1); |
| gl_->BindTexture(GL_TEXTURE_2D, 0); |
| |
| gl_->ActiveTexture(GL_TEXTURE0); |
| gl_->BindTexture(GL_TEXTURE_2D, 0); |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); |
| |
| gl_->TraceEndCHROMIUM(); |
| |
| context_provider_->InvalidateGrContext(kGrInvalidateState); |
| } |
| |
| struct VideoDecoderShim::PendingDecode { |
| PendingDecode(uint32_t decode_id, |
| const scoped_refptr<media::DecoderBuffer>& buffer); |
| ~PendingDecode(); |
| |
| const uint32_t decode_id; |
| const scoped_refptr<media::DecoderBuffer> buffer; |
| }; |
| |
| VideoDecoderShim::PendingDecode::PendingDecode( |
| uint32_t decode_id, |
| const scoped_refptr<media::DecoderBuffer>& buffer) |
| : decode_id(decode_id), buffer(buffer) { |
| } |
| |
| VideoDecoderShim::PendingDecode::~PendingDecode() { |
| } |
| |
| struct VideoDecoderShim::PendingFrame { |
| explicit PendingFrame(uint32_t decode_id); |
| PendingFrame(uint32_t decode_id, |
| const scoped_refptr<media::VideoFrame>& frame); |
| ~PendingFrame(); |
| |
| const uint32_t decode_id; |
| scoped_refptr<media::VideoFrame> video_frame; |
| |
| private: |
| // This could be expensive to copy, so guard against that. |
| DISALLOW_COPY_AND_ASSIGN(PendingFrame); |
| }; |
| |
| VideoDecoderShim::PendingFrame::PendingFrame(uint32_t decode_id) |
| : decode_id(decode_id) { |
| } |
| |
| VideoDecoderShim::PendingFrame::PendingFrame( |
| uint32_t decode_id, |
| const scoped_refptr<media::VideoFrame>& frame) |
| : decode_id(decode_id), video_frame(frame) { |
| } |
| |
| VideoDecoderShim::PendingFrame::~PendingFrame() { |
| } |
| |
| // DecoderImpl runs the underlying VideoDecoder on the media thread, receiving |
| // calls from the VideoDecodeShim on the main thread and sending results back. |
| // This class is constructed on the main thread, but used and destructed on the |
| // media thread. |
| class VideoDecoderShim::DecoderImpl { |
| public: |
| explicit DecoderImpl(const base::WeakPtr<VideoDecoderShim>& proxy); |
| ~DecoderImpl(); |
| |
| void Initialize(media::VideoDecoderConfig config); |
| void Decode(uint32_t decode_id, scoped_refptr<media::DecoderBuffer> buffer); |
| void Reset(); |
| void Stop(); |
| |
| private: |
| void OnInitDone(bool success); |
| void DoDecode(); |
| void OnDecodeComplete(media::DecodeStatus status); |
| void OnOutputComplete(const scoped_refptr<media::VideoFrame>& frame); |
| void OnResetComplete(); |
| |
| // WeakPtr is bound to main_message_loop_. Use only in shim callbacks. |
| base::WeakPtr<VideoDecoderShim> shim_; |
| scoped_ptr<media::VideoDecoder> decoder_; |
| bool initialized_ = false; |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| // Queue of decodes waiting for the decoder. |
| typedef std::queue<PendingDecode> PendingDecodeQueue; |
| PendingDecodeQueue pending_decodes_; |
| bool awaiting_decoder_ = false; |
| // VideoDecoder returns pictures without information about the decode buffer |
| // that generated it, but VideoDecoder implementations used in this class |
| // (media::FFmpegVideoDecoder and media::VpxVideoDecoder) always generate |
| // corresponding frames before decode is finished. |decode_id_| is used to |
| // store id of the current buffer while Decode() call is pending. |
| uint32_t decode_id_ = 0; |
| |
| base::WeakPtrFactory<DecoderImpl> weak_ptr_factory_; |
| }; |
| |
| VideoDecoderShim::DecoderImpl::DecoderImpl( |
| const base::WeakPtr<VideoDecoderShim>& proxy) |
| : shim_(proxy), |
| main_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| weak_ptr_factory_(this) { |
| } |
| |
| VideoDecoderShim::DecoderImpl::~DecoderImpl() { |
| DCHECK(pending_decodes_.empty()); |
| } |
| |
| void VideoDecoderShim::DecoderImpl::Initialize( |
| media::VideoDecoderConfig config) { |
| DCHECK(!decoder_); |
| #if !defined(MEDIA_DISABLE_LIBVPX) |
| if (config.codec() == media::kCodecVP9) { |
| decoder_.reset(new media::VpxVideoDecoder()); |
| } else |
| #endif |
| |
| #if !defined(MEDIA_DISABLE_FFMPEG) && !defined(DISABLE_FFMPEG_VIDEO_DECODERS) |
| { |
| scoped_ptr<media::FFmpegVideoDecoder> ffmpeg_video_decoder( |
| new media::FFmpegVideoDecoder()); |
| ffmpeg_video_decoder->set_decode_nalus(true); |
| decoder_ = std::move(ffmpeg_video_decoder); |
| } |
| #elif defined(MEDIA_DISABLE_LIBVPX) |
| OnInitDone(false); |
| return; |
| #endif |
| |
| // VpxVideoDecoder and FFmpegVideoDecoder support only one pending Decode() |
| // request. |
| DCHECK_EQ(decoder_->GetMaxDecodeRequests(), 1); |
| |
| decoder_->Initialize( |
| config, true /* low_delay */, nullptr, |
| base::Bind(&VideoDecoderShim::DecoderImpl::OnInitDone, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&VideoDecoderShim::DecoderImpl::OnOutputComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void VideoDecoderShim::DecoderImpl::Decode( |
| uint32_t decode_id, |
| scoped_refptr<media::DecoderBuffer> buffer) { |
| DCHECK(decoder_); |
| pending_decodes_.push(PendingDecode(decode_id, buffer)); |
| DoDecode(); |
| } |
| |
| void VideoDecoderShim::DecoderImpl::Reset() { |
| DCHECK(decoder_); |
| // Abort all pending decodes. |
| while (!pending_decodes_.empty()) { |
| const PendingDecode& decode = pending_decodes_.front(); |
| scoped_ptr<PendingFrame> pending_frame(new PendingFrame(decode.decode_id)); |
| main_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&VideoDecoderShim::OnDecodeComplete, shim_, PP_OK, |
| decode.decode_id)); |
| pending_decodes_.pop(); |
| } |
| // Don't need to call Reset() if the |decoder_| hasn't been initialized. |
| if (!initialized_) { |
| OnResetComplete(); |
| return; |
| } |
| |
| decoder_->Reset(base::Bind(&VideoDecoderShim::DecoderImpl::OnResetComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void VideoDecoderShim::DecoderImpl::Stop() { |
| DCHECK(decoder_); |
| // Clear pending decodes now. We don't want OnDecodeComplete to call DoDecode |
| // again. |
| while (!pending_decodes_.empty()) |
| pending_decodes_.pop(); |
| decoder_.reset(); |
| // This instance is deleted once we exit this scope. |
| } |
| |
| void VideoDecoderShim::DecoderImpl::OnInitDone(bool success) { |
| if (!success) { |
| main_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&VideoDecoderShim::OnInitializeFailed, shim_)); |
| return; |
| } |
| |
| initialized_ = true; |
| DoDecode(); |
| } |
| |
| void VideoDecoderShim::DecoderImpl::DoDecode() { |
| if (!initialized_ || pending_decodes_.empty() || awaiting_decoder_) |
| return; |
| |
| awaiting_decoder_ = true; |
| const PendingDecode& decode = pending_decodes_.front(); |
| decode_id_ = decode.decode_id; |
| decoder_->Decode(decode.buffer, |
| base::Bind(&VideoDecoderShim::DecoderImpl::OnDecodeComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| pending_decodes_.pop(); |
| } |
| |
| void VideoDecoderShim::DecoderImpl::OnDecodeComplete( |
| media::DecodeStatus status) { |
| DCHECK(awaiting_decoder_); |
| awaiting_decoder_ = false; |
| |
| int32_t result; |
| switch (status) { |
| case media::DecodeStatus::OK: |
| case media::DecodeStatus::ABORTED: |
| result = PP_OK; |
| break; |
| case media::DecodeStatus::DECODE_ERROR: |
| result = PP_ERROR_RESOURCE_FAILED; |
| break; |
| } |
| |
| main_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&VideoDecoderShim::OnDecodeComplete, shim_, result, |
| decode_id_)); |
| |
| DoDecode(); |
| } |
| |
| void VideoDecoderShim::DecoderImpl::OnOutputComplete( |
| const scoped_refptr<media::VideoFrame>& frame) { |
| // Software decoders are expected to generated frames only when a Decode() |
| // call is pending. |
| DCHECK(awaiting_decoder_); |
| |
| scoped_ptr<PendingFrame> pending_frame; |
| if (!frame->metadata()->IsTrue(media::VideoFrameMetadata::END_OF_STREAM)) |
| pending_frame.reset(new PendingFrame(decode_id_, frame)); |
| else |
| pending_frame.reset(new PendingFrame(decode_id_)); |
| |
| main_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&VideoDecoderShim::OnOutputComplete, shim_, |
| base::Passed(&pending_frame))); |
| } |
| |
| void VideoDecoderShim::DecoderImpl::OnResetComplete() { |
| main_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&VideoDecoderShim::OnResetComplete, shim_)); |
| } |
| |
| VideoDecoderShim::VideoDecoderShim( |
| PepperVideoDecoderHost* host, uint32_t texture_pool_size) |
| : state_(UNINITIALIZED), |
| host_(host), |
| media_task_runner_( |
| RenderThreadImpl::current()->GetMediaThreadTaskRunner()), |
| context_provider_( |
| RenderThreadImpl::current()->SharedMainThreadContextProvider()), |
| texture_pool_size_(texture_pool_size), |
| num_pending_decodes_(0), |
| yuv_converter_(new YUVConverter(context_provider_)), |
| weak_ptr_factory_(this) { |
| DCHECK(host_); |
| DCHECK(media_task_runner_.get()); |
| DCHECK(context_provider_.get()); |
| decoder_impl_.reset(new DecoderImpl(weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| VideoDecoderShim::~VideoDecoderShim() { |
| DCHECK(RenderThreadImpl::current()); |
| // Delete any remaining textures. |
| TextureIdMap::iterator it = texture_id_map_.begin(); |
| for (; it != texture_id_map_.end(); ++it) |
| DeleteTexture(it->second); |
| texture_id_map_.clear(); |
| |
| FlushCommandBuffer(); |
| |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| // No more callbacks from the delegate will be received now. |
| |
| // The callback now holds the only reference to the DecoderImpl, which will be |
| // deleted when Stop completes. |
| media_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&VideoDecoderShim::DecoderImpl::Stop, |
| base::Owned(decoder_impl_.release()))); |
| } |
| |
| bool VideoDecoderShim::Initialize(const Config& vda_config, Client* client) { |
| DCHECK_EQ(client, host_); |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK_EQ(state_, UNINITIALIZED); |
| |
| if (vda_config.is_encrypted) { |
| NOTREACHED() << "Encrypted streams are not supported"; |
| return false; |
| } |
| |
| media::VideoCodec codec = media::kUnknownVideoCodec; |
| if (vda_config.profile <= media::H264PROFILE_MAX) |
| codec = media::kCodecH264; |
| else if (vda_config.profile <= media::VP8PROFILE_MAX) |
| codec = media::kCodecVP8; |
| else if (vda_config.profile <= media::VP9PROFILE_MAX) |
| codec = media::kCodecVP9; |
| DCHECK_NE(codec, media::kUnknownVideoCodec); |
| |
| if (!IsCodecSupported(codec)) |
| return false; |
| |
| if (!yuv_converter_->Initialize()) |
| return false; |
| |
| media::VideoDecoderConfig video_decoder_config( |
| codec, vda_config.profile, media::PIXEL_FORMAT_YV12, |
| media::COLOR_SPACE_UNSPECIFIED, |
| gfx::Size(32, 24), // Small sizes that won't fail. |
| gfx::Rect(32, 24), gfx::Size(32, 24), |
| // TODO(bbudge): Verify extra data isn't needed. |
| media::EmptyExtraData(), media::Unencrypted()); |
| |
| media_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&VideoDecoderShim::DecoderImpl::Initialize, |
| base::Unretained(decoder_impl_.get()), video_decoder_config)); |
| |
| state_ = DECODING; |
| |
| // Return success, even though we are asynchronous, to mimic |
| // media::VideoDecodeAccelerator. |
| return true; |
| } |
| |
| void VideoDecoderShim::Decode(const media::BitstreamBuffer& bitstream_buffer) { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK_EQ(state_, DECODING); |
| |
| // We need the address of the shared memory, so we can copy the buffer. |
| const uint8_t* buffer = host_->DecodeIdToAddress(bitstream_buffer.id()); |
| DCHECK(buffer); |
| |
| media_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &VideoDecoderShim::DecoderImpl::Decode, |
| base::Unretained(decoder_impl_.get()), |
| bitstream_buffer.id(), |
| media::DecoderBuffer::CopyFrom(buffer, bitstream_buffer.size()))); |
| num_pending_decodes_++; |
| } |
| |
| void VideoDecoderShim::AssignPictureBuffers( |
| const std::vector<media::PictureBuffer>& buffers) { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK_NE(state_, UNINITIALIZED); |
| if (buffers.empty()) { |
| NOTREACHED(); |
| return; |
| } |
| DCHECK_EQ(buffers.size(), pending_texture_mailboxes_.size()); |
| GLuint num_textures = base::checked_cast<GLuint>(buffers.size()); |
| std::vector<uint32_t> local_texture_ids(num_textures); |
| gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL(); |
| for (uint32_t i = 0; i < num_textures; i++) { |
| local_texture_ids[i] = gles2->CreateAndConsumeTextureCHROMIUM( |
| GL_TEXTURE_2D, pending_texture_mailboxes_[i].name); |
| // Map the plugin texture id to the local texture id. |
| uint32_t plugin_texture_id = buffers[i].texture_id(); |
| texture_id_map_[plugin_texture_id] = local_texture_ids[i]; |
| available_textures_.insert(plugin_texture_id); |
| } |
| pending_texture_mailboxes_.clear(); |
| SendPictures(); |
| } |
| |
| void VideoDecoderShim::ReusePictureBuffer(int32_t picture_buffer_id) { |
| DCHECK(RenderThreadImpl::current()); |
| uint32_t texture_id = static_cast<uint32_t>(picture_buffer_id); |
| if (textures_to_dismiss_.find(texture_id) != textures_to_dismiss_.end()) { |
| DismissTexture(texture_id); |
| } else if (texture_id_map_.find(texture_id) != texture_id_map_.end()) { |
| available_textures_.insert(texture_id); |
| SendPictures(); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void VideoDecoderShim::Flush() { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK_EQ(state_, DECODING); |
| state_ = FLUSHING; |
| } |
| |
| void VideoDecoderShim::Reset() { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK_EQ(state_, DECODING); |
| state_ = RESETTING; |
| media_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&VideoDecoderShim::DecoderImpl::Reset, |
| base::Unretained(decoder_impl_.get()))); |
| } |
| |
| void VideoDecoderShim::Destroy() { |
| delete this; |
| } |
| |
| void VideoDecoderShim::OnInitializeFailed() { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK(host_); |
| |
| host_->NotifyError(media::VideoDecodeAccelerator::PLATFORM_FAILURE); |
| } |
| |
| void VideoDecoderShim::OnDecodeComplete(int32_t result, uint32_t decode_id) { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK(host_); |
| |
| if (result == PP_ERROR_RESOURCE_FAILED) { |
| host_->NotifyError(media::VideoDecodeAccelerator::PLATFORM_FAILURE); |
| return; |
| } |
| |
| num_pending_decodes_--; |
| completed_decodes_.push(decode_id); |
| |
| // If frames are being queued because we're out of textures, don't notify |
| // the host that decode has completed. This exerts "back pressure" to keep |
| // the host from sending buffers that will cause pending_frames_ to grow. |
| if (pending_frames_.empty()) |
| NotifyCompletedDecodes(); |
| } |
| |
| void VideoDecoderShim::OnOutputComplete(scoped_ptr<PendingFrame> frame) { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK(host_); |
| |
| if (frame->video_frame) { |
| if (texture_size_ != frame->video_frame->coded_size()) { |
| // If the size has changed, all current textures must be dismissed. Add |
| // all textures to |textures_to_dismiss_| and dismiss any that aren't in |
| // use by the plugin. We will dismiss the rest as they are recycled. |
| for (TextureIdMap::const_iterator it = texture_id_map_.begin(); |
| it != texture_id_map_.end(); |
| ++it) { |
| textures_to_dismiss_.insert(it->first); |
| } |
| for (TextureIdSet::const_iterator it = available_textures_.begin(); |
| it != available_textures_.end(); |
| ++it) { |
| DismissTexture(*it); |
| } |
| available_textures_.clear(); |
| FlushCommandBuffer(); |
| |
| DCHECK(pending_texture_mailboxes_.empty()); |
| for (uint32_t i = 0; i < texture_pool_size_; i++) |
| pending_texture_mailboxes_.push_back(gpu::Mailbox::Generate()); |
| |
| host_->RequestTextures(texture_pool_size_, |
| frame->video_frame->coded_size(), GL_TEXTURE_2D, |
| pending_texture_mailboxes_); |
| texture_size_ = frame->video_frame->coded_size(); |
| } |
| |
| pending_frames_.push(std::move(frame)); |
| SendPictures(); |
| } |
| } |
| |
| void VideoDecoderShim::SendPictures() { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK(host_); |
| while (!pending_frames_.empty() && !available_textures_.empty()) { |
| const scoped_ptr<PendingFrame>& frame = pending_frames_.front(); |
| |
| TextureIdSet::iterator it = available_textures_.begin(); |
| uint32_t texture_id = *it; |
| available_textures_.erase(it); |
| |
| uint32_t local_texture_id = texture_id_map_[texture_id]; |
| |
| yuv_converter_->Convert(frame->video_frame, local_texture_id); |
| |
| host_->PictureReady(media::Picture(texture_id, frame->decode_id, |
| frame->video_frame->visible_rect(), |
| false)); |
| pending_frames_.pop(); |
| } |
| |
| FlushCommandBuffer(); |
| |
| if (pending_frames_.empty()) { |
| // If frames aren't backing up, notify the host of any completed decodes so |
| // it can send more buffers. |
| NotifyCompletedDecodes(); |
| |
| if (state_ == FLUSHING && !num_pending_decodes_) { |
| state_ = DECODING; |
| host_->NotifyFlushDone(); |
| } |
| } |
| } |
| |
| void VideoDecoderShim::OnResetComplete() { |
| DCHECK(RenderThreadImpl::current()); |
| DCHECK(host_); |
| |
| while (!pending_frames_.empty()) |
| pending_frames_.pop(); |
| NotifyCompletedDecodes(); |
| |
| // Dismiss any old textures now. |
| while (!textures_to_dismiss_.empty()) |
| DismissTexture(*textures_to_dismiss_.begin()); |
| |
| state_ = DECODING; |
| host_->NotifyResetDone(); |
| } |
| |
| void VideoDecoderShim::NotifyCompletedDecodes() { |
| while (!completed_decodes_.empty()) { |
| host_->NotifyEndOfBitstreamBuffer(completed_decodes_.front()); |
| completed_decodes_.pop(); |
| } |
| } |
| |
| void VideoDecoderShim::DismissTexture(uint32_t texture_id) { |
| DCHECK(host_); |
| textures_to_dismiss_.erase(texture_id); |
| DCHECK(texture_id_map_.find(texture_id) != texture_id_map_.end()); |
| DeleteTexture(texture_id_map_[texture_id]); |
| texture_id_map_.erase(texture_id); |
| host_->DismissPictureBuffer(texture_id); |
| } |
| |
| void VideoDecoderShim::DeleteTexture(uint32_t texture_id) { |
| gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL(); |
| gles2->DeleteTextures(1, &texture_id); |
| } |
| |
| void VideoDecoderShim::FlushCommandBuffer() { |
| context_provider_->ContextGL()->Flush(); |
| } |
| |
| } // namespace content |