| // Copyright 2019 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 "media/gpu/v4l2/v4l2_stateful_workaround.h" |
| |
| #include <string.h> |
| #include <memory> |
| #include <vector> |
| |
| #include <linux/videodev2.h> |
| |
| #include "base/containers/small_map.h" |
| #include "base/containers/span.h" |
| #include "base/memory/ptr_util.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/video_types.h" |
| #include "media/gpu/macros.h" |
| #include "media/parsers/vp8_parser.h" |
| #include "media/video/video_decode_accelerator.h" |
| |
| namespace media { |
| namespace { |
| // Creates superframe index from |frame_sizes|. The frame sizes is stored in the |
| // same bytes. For example, if the max frame size is two bytes, even if the |
| // smaller frame sizes are 1 byte, they are stored as two bytes. See the detail |
| // for VP9 Spec Annex B. |
| std::vector<uint8_t> CreateSuperFrameIndex( |
| const std::vector<uint32_t>& frame_sizes) { |
| if (frame_sizes.size() < 2) |
| return {}; |
| |
| // Computes the bytes of the maximum frame size. |
| const uint32_t max_frame_size = |
| *std::max_element(frame_sizes.begin(), frame_sizes.end()); |
| uint8_t bytes_per_framesize = 1; |
| for (uint32_t mask = 0xff; bytes_per_framesize <= 4; bytes_per_framesize++) { |
| if (max_frame_size < mask) |
| break; |
| mask <<= 8; |
| mask |= 0xff; |
| } |
| |
| uint8_t superframe_header = 0xc0; |
| superframe_header |= static_cast<uint8_t>(frame_sizes.size() - 1); |
| superframe_header |= (bytes_per_framesize - 1) << 3; |
| const size_t index_sz = 2 + bytes_per_framesize * frame_sizes.size(); |
| std::vector<uint8_t> superframe_index(index_sz); |
| size_t pos = 0; |
| superframe_index[pos++] = superframe_header; |
| for (uint32_t size : frame_sizes) { |
| for (int i = 0; i < bytes_per_framesize; i++) { |
| superframe_index[pos++] = size & 0xff; |
| size >>= 8; |
| } |
| } |
| superframe_index[pos++] = superframe_header; |
| |
| return superframe_index; |
| } |
| |
| // Overwrites show_frame of each frame. It is set to 1 for the top spatial layer |
| // or otherwise 0. |
| bool OverwriteShowFrame(base::span<uint8_t> frame_data, |
| const std::vector<uint32_t>& frame_sizes) { |
| size_t sum_frame_size = 0; |
| for (uint32_t frame_size : frame_sizes) |
| sum_frame_size += frame_size; |
| if (frame_data.size() != sum_frame_size) { |
| LOG(ERROR) << "frame data size=" << frame_data.size() |
| << " is different from the sum of frame sizes" |
| << " index size=" << sum_frame_size; |
| return false; |
| } |
| |
| size_t offset = 0; |
| for (size_t i = 0; i < frame_sizes.size(); ++i) { |
| uint8_t* header = frame_data.data() + offset; |
| |
| // See VP9 Spec Annex B. |
| const uint8_t frame_marker = (*header >> 6); |
| if (frame_marker != 0b10) { |
| LOG(ERROR) << "Invalid frame marker: " << static_cast<int>(frame_marker); |
| return false; |
| } |
| const uint8_t profile = (*header >> 4) & 0b11; |
| if (profile == 3) { |
| LOG(ERROR) << "Unsupported profile"; |
| return false; |
| } |
| |
| const bool show_existing_frame = (*header >> 3) & 1; |
| const bool show_frame = i == frame_sizes.size() - 1; |
| int bit = 0; |
| if (show_existing_frame) { |
| header++; |
| bit = 6; |
| } else { |
| bit = 1; |
| } |
| if (show_frame) { |
| *header |= (1u << bit); |
| } else { |
| *header &= ~(1u << bit); |
| } |
| |
| offset += frame_sizes[i]; |
| } |
| |
| return true; |
| } |
| } // namespace |
| // If the given resolution is not supported by the driver, some IOCTL must |
| // return some error code (e.g. EIO). However, there is a driver that doesn't |
| // follow this specification, for example go2001. This will be called before |
| // a bitstream to the driver in the driver. This parses the bitstream, gets |
| // its resolution and compares with the supported resolution. |
| // Returns true if the resolution is supported or this workaround is |
| // unnecessary. Otherwise return false. |
| // This class is currently created only on guado when codec is VP8. |
| // TODO(crbug.com/968945): Check this workaround is necessary for other codecs |
| // and other devices. |
| class SupportResolutionChecker : public V4L2StatefulWorkaround { |
| public: |
| static std::unique_ptr<V4L2StatefulWorkaround> CreateIfNeeded( |
| V4L2Device::Type device_type, |
| VideoCodecProfile profile); |
| ~SupportResolutionChecker() override = default; |
| |
| Result Apply(const uint8_t* data, size_t size) override; |
| |
| private: |
| using SupportedProfileMap = base::small_map< |
| std::map<VideoCodecProfile, VideoDecodeAccelerator::SupportedProfile>>; |
| |
| SupportResolutionChecker(SupportedProfileMap supported_profile_map) |
| : supported_profile_map_(std::move(supported_profile_map)), |
| vp8_parser_(std::make_unique<Vp8Parser>()) {} |
| |
| SupportedProfileMap supported_profile_map_; |
| const std::unique_ptr<Vp8Parser> vp8_parser_; |
| }; |
| |
| std::unique_ptr<V4L2StatefulWorkaround> |
| SupportResolutionChecker::CreateIfNeeded(V4L2Device::Type device_type, |
| VideoCodecProfile profile) { |
| if (device_type != V4L2Device::Type::kDecoder || profile < VP8PROFILE_MIN || |
| profile > VP8PROFILE_MAX) { |
| return nullptr; |
| } |
| |
| scoped_refptr<V4L2Device> device = V4L2Device::Create(); |
| if (!device->Open(V4L2Device::Type::kDecoder, V4L2_PIX_FMT_VP8)) { |
| VPLOGF(1) << "Failed to open device for profile: " << profile |
| << " fourcc: " << FourccToString(V4L2_PIX_FMT_VP8); |
| return nullptr; |
| } |
| |
| // Get the driver name. |
| struct v4l2_capability caps; |
| if (device->Ioctl(VIDIOC_QUERYCAP, &caps) != 0) { |
| VPLOGF(1) << "ioctl() failed: VIDIOC_QUERYCAP" |
| << ", caps check failed: 0x" << std::hex << caps.capabilities; |
| return nullptr; |
| } |
| constexpr char go2001[] = "go2001"; |
| if (strcmp(reinterpret_cast<const char*>(caps.driver), go2001)) |
| return nullptr; |
| |
| constexpr uint32_t supported_input_fourccs[] = { |
| V4L2_PIX_FMT_VP8, |
| }; |
| |
| // Recreate the V4L2 device in order to close the opened decoder, since |
| // we are about to query the supported decode profiles. |
| device = V4L2Device::Create(); |
| auto supported_profiles = device->GetSupportedDecodeProfiles( |
| base::size(supported_input_fourccs), supported_input_fourccs); |
| SupportedProfileMap supported_profile_map; |
| for (const auto& profile : supported_profiles) |
| supported_profile_map[profile.profile] = profile; |
| |
| VLOGF(2) << "Create SupportResolutionChecker workaround"; |
| return base::WrapUnique( |
| new SupportResolutionChecker(std::move(supported_profile_map))); |
| } |
| |
| V4L2StatefulWorkaround::Result SupportResolutionChecker::Apply( |
| const uint8_t* data, |
| size_t size) { |
| Vp8FrameHeader fhdr; |
| vp8_parser_->ParseFrame(data, size, &fhdr); |
| if (fhdr.IsKeyframe()) { |
| DCHECK(supported_profile_map_.find(VP8PROFILE_ANY) != |
| supported_profile_map_.end()); |
| const auto& supported_profile = supported_profile_map_[VP8PROFILE_ANY]; |
| const auto& min_resolution = supported_profile.min_resolution; |
| const auto& max_resolution = supported_profile.max_resolution; |
| const gfx::Rect current_resolution(fhdr.width, fhdr.height); |
| if (!gfx::Rect(max_resolution).Contains(current_resolution) || |
| !(current_resolution).Contains(gfx::Rect(min_resolution))) { |
| VLOGF(1) << "Resolution is unsupported: " |
| << current_resolution.size().ToString() |
| << ", min supported resolution: " << min_resolution.ToString() |
| << ", max supported resolution: " << max_resolution.ToString(); |
| return Result::NotifyError; |
| } |
| } |
| return Result::Success; |
| } |
| |
| std::vector<std::unique_ptr<V4L2StatefulWorkaround>> |
| CreateV4L2StatefulWorkarounds(V4L2Device::Type device_type, |
| VideoCodecProfile profile) { |
| using CreateWorkaroundFuncType = std::unique_ptr<V4L2StatefulWorkaround> (*)( |
| V4L2Device::Type device_type, VideoCodecProfile profile); |
| const CreateWorkaroundFuncType kWorkaroundFactoryFunction[] = { |
| &SupportResolutionChecker::CreateIfNeeded, |
| }; |
| |
| std::vector<std::unique_ptr<V4L2StatefulWorkaround>> workarounds; |
| for (const auto func : kWorkaroundFactoryFunction) { |
| auto vw = func(device_type, profile); |
| if (vw) |
| workarounds.push_back(std::move(vw)); |
| } |
| return workarounds; |
| } |
| |
| bool AppendVP9SuperFrameIndexIfNeeded(scoped_refptr<DecoderBuffer>& buffer) { |
| if (buffer->side_data_size() == 0) |
| return true; |
| |
| const size_t num_of_layers = buffer->side_data_size() / sizeof(uint32_t); |
| if (num_of_layers > 3u) { |
| LOG(ERROR) << "The maximum number of spatial layers in VP9 is three"; |
| return false; |
| } |
| |
| const uint32_t* cue_data = |
| reinterpret_cast<const uint32_t*>(buffer->side_data()); |
| std::vector<uint32_t> frame_sizes(cue_data, cue_data + num_of_layers); |
| std::vector<uint8_t> superframe_index = CreateSuperFrameIndex(frame_sizes); |
| const size_t vp9_superframe_size = |
| buffer->data_size() + superframe_index.size(); |
| auto vp9_superframe = std::make_unique<uint8_t[]>(vp9_superframe_size); |
| memcpy(vp9_superframe.get(), buffer->data(), buffer->data_size()); |
| memcpy(vp9_superframe.get() + buffer->data_size(), superframe_index.data(), |
| superframe_index.size()); |
| |
| if (!OverwriteShowFrame( |
| base::make_span(vp9_superframe.get(), buffer->data_size()), |
| frame_sizes)) { |
| return false; |
| } |
| |
| DVLOG(3) << "DecoderBuffer is overwritten"; |
| buffer = |
| DecoderBuffer::FromArray(std::move(vp9_superframe), vp9_superframe_size); |
| |
| return true; |
| } |
| } // namespace media |