| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "starboard/shared/starboard/player/video_frame_internal.h" |
| |
| #include "starboard/log.h" |
| #include "starboard/memory.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace starboard { |
| namespace player { |
| |
| namespace { |
| |
| bool s_yuv_to_rgb_lookup_table_initialized = false; |
| int s_y_to_rgb[256]; |
| int s_v_to_r[256]; |
| int s_u_to_g[256]; |
| int s_v_to_g[256]; |
| int s_u_to_b[256]; |
| uint8_t s_clamp_table[256 * 5]; |
| |
| void EnsureYUVToRGBLookupTableInitialized() { |
| if (s_yuv_to_rgb_lookup_table_initialized) { |
| return; |
| } |
| |
| // The YUV to RGBA conversion is based on |
| // http://www.equasys.de/colorconversion.html. |
| // The formula is: |
| // r = 1.164f * y + 1.793f * (v - 128); |
| // g = 1.164f * y - 0.213f * (u - 128) - 0.533f * (v - 128); |
| // b = 1.164f * y + 2.112f * (u - 128); |
| // And r/g/b has to be clamped to [0, 255]. |
| // |
| // We optimize the conversion algorithm by creating two kinds of lookup |
| // tables. The color component table contains pre-calculated color component |
| // values. The clamp table contains a map between |v| + 512 to the clamped |
| // |v| to avoid conditional operation. |
| // The minimum value of |v| can be 2.112f * (-128) = -271, the maximum value |
| // of |v| can be 1.164f * 255 + 2.112f * 127 = 565. So we need 512 bytes at |
| // each side of the clamp buffer. |
| SbMemorySet(s_clamp_table, 0, 512); |
| SbMemorySet(s_clamp_table + 768, 0xff, 512); |
| |
| uint8_t i = 0; |
| while (true) { |
| s_y_to_rgb[i] = static_cast<int>((i - 16) * 1.164f); |
| s_v_to_r[i] = static_cast<int>((i - 128) * 1.793f); |
| s_u_to_g[i] = static_cast<int>((i - 128) * -0.213f); |
| s_v_to_g[i] = static_cast<int>((i - 128) * -0.533f); |
| s_u_to_b[i] = static_cast<int>((i - 128) * 2.112f); |
| s_clamp_table[512 + static_cast<std::size_t>(i)] = i; |
| if (i == 255) { |
| break; |
| } |
| ++i; |
| } |
| |
| s_yuv_to_rgb_lookup_table_initialized = true; |
| } |
| |
| uint8_t ClampColorComponent(int component) { |
| return s_clamp_table[component + 512]; |
| } |
| |
| } // namespace |
| |
| VideoFrame::VideoFrame() { |
| InitializeToInvalidFrame(); |
| } |
| |
| VideoFrame::VideoFrame(int width, |
| int height, |
| SbMediaTime pts, |
| void* native_texture, |
| void* native_texture_context, |
| FreeNativeTextureFunc free_native_texture_func) { |
| SB_DCHECK(native_texture != NULL); |
| SB_DCHECK(free_native_texture_func != NULL); |
| |
| InitializeToInvalidFrame(); |
| |
| format_ = kNativeTexture; |
| width_ = width; |
| height_ = height; |
| pts_ = pts; |
| native_texture_ = native_texture; |
| native_texture_context_ = native_texture_context; |
| free_native_texture_func_ = free_native_texture_func; |
| } |
| |
| VideoFrame::~VideoFrame() { |
| if (format_ == kNativeTexture) { |
| free_native_texture_func_(native_texture_context_, native_texture_); |
| } |
| } |
| |
| int VideoFrame::GetPlaneCount() const { |
| SB_DCHECK(format_ != kInvalid); |
| SB_DCHECK(format_ != kNativeTexture); |
| |
| return static_cast<int>(planes_.size()); |
| } |
| |
| const VideoFrame::Plane& VideoFrame::GetPlane(int index) const { |
| SB_DCHECK(format_ != kInvalid); |
| SB_DCHECK(format_ != kNativeTexture); |
| SB_DCHECK(index >= 0 && index < GetPlaneCount()) << "Invalid index: " |
| << index; |
| return planes_[index]; |
| } |
| |
| void* VideoFrame::native_texture() const { |
| SB_DCHECK(format_ == kNativeTexture); |
| return native_texture_; |
| } |
| |
| scoped_refptr<VideoFrame> VideoFrame::ConvertTo(Format target_format) const { |
| SB_DCHECK(format_ == kYV12); |
| SB_DCHECK(target_format == kBGRA32); |
| |
| EnsureYUVToRGBLookupTableInitialized(); |
| |
| scoped_refptr<VideoFrame> target_frame(new VideoFrame); |
| |
| target_frame->format_ = target_format; |
| target_frame->width_ = width(); |
| target_frame->height_ = height(); |
| target_frame->pts_ = pts_; |
| target_frame->pixel_buffer_.reset(new uint8_t[width() * height() * 4]); |
| target_frame->planes_.push_back( |
| Plane(width(), height(), width() * 4, target_frame->pixel_buffer_.get())); |
| |
| const uint8_t* y_data = GetPlane(0).data; |
| const uint8_t* u_data = GetPlane(1).data; |
| const uint8_t* v_data = GetPlane(2).data; |
| uint8_t* bgra_data = target_frame->pixel_buffer_.get(); |
| |
| int height = this->height(); |
| int width = this->width(); |
| |
| for (int row = 0; row < height; ++row) { |
| const uint8_t* y = &y_data[row * GetPlane(0).pitch_in_bytes]; |
| const uint8_t* u = &u_data[row / 2 * GetPlane(1).pitch_in_bytes]; |
| const uint8_t* v = &v_data[row / 2 * GetPlane(2).pitch_in_bytes]; |
| int v_to_r = 0; |
| int u_to_g = 0; |
| int v_to_g = 0; |
| int u_to_b = 0; |
| |
| for (int column = 0; column < width; ++column) { |
| if (column % 2 == 0) { |
| v_to_r = s_v_to_r[*v]; |
| u_to_g = s_u_to_g[*u]; |
| v_to_g = s_v_to_g[*v]; |
| u_to_b = s_u_to_b[*u]; |
| } else { |
| ++u, ++v; |
| } |
| |
| int y_to_rgb = s_y_to_rgb[*y]; |
| int r = y_to_rgb + v_to_r; |
| int g = y_to_rgb + u_to_g + v_to_g; |
| int b = y_to_rgb + u_to_b; |
| |
| *bgra_data++ = ClampColorComponent(b); |
| *bgra_data++ = ClampColorComponent(g); |
| *bgra_data++ = ClampColorComponent(r); |
| *bgra_data++ = 0xff; |
| |
| ++y; |
| } |
| } |
| |
| return target_frame; |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::CreateEOSFrame() { |
| return new VideoFrame; |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::CreateYV12Frame(int width, |
| int height, |
| int pitch_in_bytes, |
| SbMediaTime pts, |
| const uint8_t* y, |
| const uint8_t* u, |
| const uint8_t* v) { |
| scoped_refptr<VideoFrame> frame(new VideoFrame); |
| frame->format_ = kYV12; |
| frame->width_ = width; |
| frame->height_ = height; |
| frame->pts_ = pts; |
| |
| // U/V planes generally have half resolution of the Y plane. However, in the |
| // extreme case that any dimension of Y plane is odd, we want to have an |
| // extra pixel on U/V planes. |
| int uv_height = height / 2 + height % 2; |
| int uv_width = width / 2 + width % 2; |
| int uv_pitch_in_bytes = pitch_in_bytes / 2 + pitch_in_bytes % 2; |
| |
| int y_plane_size_in_bytes = height * pitch_in_bytes; |
| int uv_plane_size_in_bytes = uv_height * uv_pitch_in_bytes; |
| frame->pixel_buffer_.reset( |
| new uint8_t[y_plane_size_in_bytes + uv_plane_size_in_bytes * 2]); |
| SbMemoryCopy(frame->pixel_buffer_.get(), y, y_plane_size_in_bytes); |
| SbMemoryCopy(frame->pixel_buffer_.get() + y_plane_size_in_bytes, u, |
| uv_plane_size_in_bytes); |
| SbMemoryCopy(frame->pixel_buffer_.get() + y_plane_size_in_bytes + |
| uv_plane_size_in_bytes, |
| v, uv_plane_size_in_bytes); |
| |
| frame->planes_.push_back( |
| Plane(width, height, pitch_in_bytes, frame->pixel_buffer_.get())); |
| frame->planes_.push_back( |
| Plane(uv_width, uv_height, uv_pitch_in_bytes, |
| frame->pixel_buffer_.get() + y_plane_size_in_bytes)); |
| frame->planes_.push_back(Plane(uv_width, uv_height, uv_pitch_in_bytes, |
| frame->pixel_buffer_.get() + |
| y_plane_size_in_bytes + |
| uv_plane_size_in_bytes)); |
| return frame; |
| } |
| |
| // static |
| scoped_refptr<VideoFrame> VideoFrame::CreateEmptyFrame(SbMediaTime pts) { |
| VideoFrame* frame = new VideoFrame(); |
| frame->pts_ = pts; |
| return frame; |
| } |
| |
| void VideoFrame::InitializeToInvalidFrame() { |
| format_ = kInvalid; |
| width_ = 0; |
| height_ = 0; |
| |
| pts_ = 0; |
| native_texture_ = NULL; |
| native_texture_context_ = NULL; |
| free_native_texture_func_ = NULL; |
| } |
| |
| } // namespace player |
| } // namespace starboard |
| } // namespace shared |
| } // namespace starboard |