[TTVD] Perform manual H.26X bitstream conversion in TTVD 87/318587/9 submit/tizen/20241002.160013
authorMichal Jurkiewicz <m.jurkiewicz@samsung.com>
Mon, 16 Sep 2024 12:53:40 +0000 (14:53 +0200)
committerBot Blink <blinkbot@samsung.com>
Wed, 2 Oct 2024 14:17:21 +0000 (14:17 +0000)
[Problem]
Fallback from TTVD HW decoder to SW decoder for H.265 results
in failure during SW decoder initialization.

[Cause]
TTVD requests builtin AVCC -> AnnexB bitstream conversion
for H264 and H265 codecs as platform HW decoder needs data
in AnnexB format.

In case of failure of HW decoder initialization, SW fallback
should be performed.
The problem is, that FFmpeg SW decoder requests data
in AVCC format and FFmpeg fails during H265 AnnexB stream parsing
(it treats AnnexB start code as NAL unit size).

[Solution]
Stop requesting builtin AVCC -> AnnexB bitstream conversion
in TTVD and perform it manually inside the TTVD implementation.

In case of need of SW fallback, data in AVCC format
will be delivered to FFmpeg SW decoder.

Bug: https://jira-eu.sec.samsung.net/browse/VDGAME-572
Change-Id: I937ffa0e075783b39974002fcb76d8dfb70a0983
Signed-off-by: Michal Jurkiewicz <m.jurkiewicz@samsung.com>
media/filters/BUILD.gn
media/filters/tizen/h26x_bitstream_converter.cc [new file with mode: 0644]
media/filters/tizen/h26x_bitstream_converter.h [new file with mode: 0644]
media/filters/tizen/ttvd_video_decoder.h
media/filters/tizen/ttvd_video_decoder_impl.cc
media/filters/tizen/ttvd_video_decoder_impl.h

index 99b04553eb5171e3d67f16fbf657cbf52cebf98d..d9e68a719514bdeac5a5b7a5c1cc8995a6a45b44 100644 (file)
@@ -307,6 +307,8 @@ source_set("filters") {
       "tizen/dummy_drm.cc",
       "tizen/dummy_drm.h",
       "tizen/extended_video_decoder_config.h",
+      "tizen/h26x_bitstream_converter.cc",
+      "tizen/h26x_bitstream_converter.h",
       "tizen/latency_mode.h",
       "tizen/lazy_frame_ranges.cc",
       "tizen/lazy_frame_ranges.h",
diff --git a/media/filters/tizen/h26x_bitstream_converter.cc b/media/filters/tizen/h26x_bitstream_converter.cc
new file mode 100644 (file)
index 0000000..7f5a24c
--- /dev/null
@@ -0,0 +1,171 @@
+// Copyright 2024 Samsung Electronics Inc. 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/filters/tizen/h26x_bitstream_converter.h"
+
+#include <memory>
+#include <variant>
+
+#include "base/notreached.h"
+#include "media/base/tizen/logger/media_logger.h"
+#include "media/filters/h264_to_annex_b_bitstream_converter.h"
+#include "media/filters/h265_to_annex_b_bitstream_converter.h"
+#include "media/filters/tizen/media_video_codec.h"
+#include "media/formats/mp4/box_definitions.h"
+#include "media/formats/mp4/hevc.h"
+
+namespace media {
+
+class H26XBitstreamConverter::Impl {
+ public:
+  Impl() = default;
+
+  template <typename ConfigurationType, typename ConverterType>
+  void ParseConfiguration(const std::vector<uint8_t>& extradata);
+
+  template <typename ConfigurationType, typename ConverterType>
+  scoped_refptr<DecoderBuffer> ConvertBufferInternal(
+      scoped_refptr<DecoderBuffer> input_buffer);
+
+ private:
+  std::variant<std::monostate,
+               mp4::AVCDecoderConfigurationRecord,
+               mp4::HEVCDecoderConfigurationRecord>
+      h26x_configuration_;
+
+  std::variant<std::monostate,
+               H264ToAnnexBBitstreamConverter,
+               H265ToAnnexBBitstreamConverter>
+      h26x_bitstream_converter_;
+
+  bool h26x_configuration_parsed_ = false;
+};
+
+template <typename ConfigurationType, typename ConverterType>
+void H26XBitstreamConverter::Impl::ParseConfiguration(
+    const std::vector<uint8_t>& extradata) {
+  if (extradata.empty()) {
+    return;
+  }
+
+  auto& configuration = h26x_configuration_.emplace<ConfigurationType>();
+  auto& converter = h26x_bitstream_converter_.emplace<ConverterType>();
+
+  if (!converter.ParseConfiguration(extradata.data(), extradata.size(),
+                                    &configuration)) {
+    LOG(WARNING)
+        << "Failed to parse H264 extradata. Could not obtain configuration.";
+    return;
+  }
+
+  h26x_configuration_parsed_ = true;
+}
+
+template <typename ConfigurationType, typename ConverterType>
+scoped_refptr<DecoderBuffer>
+H26XBitstreamConverter::Impl::ConvertBufferInternal(
+    scoped_refptr<DecoderBuffer> input_buffer) {
+  if (!h26x_configuration_parsed_) {
+    // No extradata was provided for codec (so it is already in AnnexB format)
+    // or extradata parsing failed.
+    return input_buffer;
+  }
+
+  if (input_buffer->end_of_stream() || !input_buffer->data() ||
+      !input_buffer->data_size()) {
+    // Buffer contains no data. Cannot convert to AnnexB format.
+    return input_buffer;
+  }
+
+  LOG_ASSERT(std::holds_alternative<ConfigurationType>(h26x_configuration_));
+  LOG_ASSERT(std::holds_alternative<ConverterType>(h26x_bitstream_converter_));
+
+  const auto& configuration = std::get<ConfigurationType>(h26x_configuration_);
+  auto& converter = std::get<ConverterType>(h26x_bitstream_converter_);
+
+  const uint32_t output_packet_size = converter.CalculateNeededOutputBufferSize(
+      input_buffer->data(), input_buffer->data_size(), &configuration);
+
+  if (output_packet_size == 0) {
+    LOG(WARNING)
+        << "Could not convert packet. Wrong calculated output buffer size";
+    return input_buffer;
+  }
+
+  auto converted_data = std::make_unique<uint8_t[]>(output_packet_size);
+
+  auto bytes_written = output_packet_size;
+  if (!converter.ConvertNalUnitStreamToByteStream(
+          input_buffer->data(), input_buffer->data_size(), &configuration,
+          converted_data.get(), &bytes_written)) {
+    LOG(WARNING) << "Failed to convert AVCC buffer to AnnexB format";
+    return input_buffer;
+  }
+
+  auto converted_buffer =
+      DecoderBuffer::FromArray(std::move(converted_data), output_packet_size);
+
+  converted_buffer->set_is_key_frame(input_buffer->is_key_frame());
+  converted_buffer->set_side_data(input_buffer->side_data());
+  converted_buffer->set_timestamp(input_buffer->timestamp());
+  converted_buffer->set_dts(input_buffer->dts());
+  converted_buffer->set_duration(input_buffer->duration());
+  converted_buffer->set_is_key_frame(input_buffer->is_key_frame());
+  if (input_buffer->decrypt_config()) {
+    converted_buffer->set_decrypt_config(
+        input_buffer->decrypt_config()->Clone());
+  }
+
+  return converted_buffer;
+}
+
+H26XBitstreamConverter::H26XBitstreamConverter(
+    MediaVideoCodec codec,
+    const std::vector<uint8_t>& extradata)
+    : impl_(std::make_unique<Impl>()), codec_(codec) {
+  TIZEN_MEDIA_LOG_ASSERT(codec_ == MediaVideoCodec::kCodecH264 ||
+                         codec_ == MediaVideoCodec::kCodecHEVC);
+
+  switch (codec_) {
+    case media::MediaVideoCodec::kCodecH264: {
+      impl_->ParseConfiguration<mp4::AVCDecoderConfigurationRecord,
+                                H264ToAnnexBBitstreamConverter>(extradata);
+      break;
+    }
+    case media::MediaVideoCodec::kCodecHEVC: {
+      impl_->ParseConfiguration<mp4::HEVCDecoderConfigurationRecord,
+                                H265ToAnnexBBitstreamConverter>(extradata);
+      break;
+    }
+    default:
+      NOTREACHED();
+  }
+}
+
+H26XBitstreamConverter::~H26XBitstreamConverter() = default;
+
+scoped_refptr<DecoderBuffer> H26XBitstreamConverter::ConvertBuffer(
+    scoped_refptr<DecoderBuffer> input_buffer) {
+  TIZEN_MEDIA_LOG_ASSERT(codec_ == MediaVideoCodec::kCodecH264 ||
+                         codec_ == MediaVideoCodec::kCodecHEVC);
+
+  switch (codec_) {
+    case media::MediaVideoCodec::kCodecH264: {
+      return impl_->ConvertBufferInternal<mp4::AVCDecoderConfigurationRecord,
+                                          H264ToAnnexBBitstreamConverter>(
+          std::move(input_buffer));
+    }
+    case media::MediaVideoCodec::kCodecHEVC: {
+      return impl_->ConvertBufferInternal<mp4::HEVCDecoderConfigurationRecord,
+                                          H265ToAnnexBBitstreamConverter>(
+          std::move(input_buffer));
+    }
+    default:
+      NOTREACHED();
+  }
+
+  return nullptr;
+}
+
+}  // namespace media
diff --git a/media/filters/tizen/h26x_bitstream_converter.h b/media/filters/tizen/h26x_bitstream_converter.h
new file mode 100644 (file)
index 0000000..7de0633
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright 2024 Samsung Electronics Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_FILTERS_TIZEN_H26X_BITSTREAM_CONVERTER_H_
+#define MEDIA_FILTERS_TIZEN_H26X_BITSTREAM_CONVERTER_H_
+
+#include <cstdint>
+#include <vector>
+#include "base/memory/scoped_refptr.h"
+#include "media/base/decoder_buffer.h"
+#include "media/filters/tizen/media_video_codec.h"
+
+namespace media {
+
+// H.264 and H.265 AVCC -> AnnexB Bitstream Converter.
+class H26XBitstreamConverter {
+ public:
+  H26XBitstreamConverter(MediaVideoCodec codec,
+                         const std::vector<uint8_t>& extradata);
+
+  scoped_refptr<DecoderBuffer> ConvertBuffer(
+      scoped_refptr<DecoderBuffer> input_buffer);
+
+  ~H26XBitstreamConverter();
+
+ private:
+  class Impl;
+
+  const std::unique_ptr<Impl> impl_;
+  const MediaVideoCodec codec_;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FILTERS_TIZEN_H26X_BITSTREAM_CONVERTER_H_
index 39e48af1875c14a0a5166ae8c08f8c330189d4bd..e485664fd464818e2dbcb13ba41b4f35b87a5eaa 100644 (file)
@@ -52,7 +52,7 @@ class MEDIA_EXPORT TTvdVideoDecoder
                   const WaitingCB& waiting_cb) override;
   void Decode(scoped_refptr<DecoderBuffer> buffer, DecodeCB decode_cb) override;
   void Reset(base::OnceClosure closure) override;
-  bool NeedsBitstreamConversion() const override { return true; }
+  bool NeedsBitstreamConversion() const override { return false; }
   bool CanReadWithoutStalling() const override { return false; }
   int GetMaxDecodeRequests() const override { return 4; }
   VideoDecoderType GetDecoderType() const override {
index 6e75da74b5dbb8ee246dd40038d13ef4332f0036..96102a76aebbfcdc55aba678501d4cd68e737dcf 100644 (file)
@@ -37,6 +37,7 @@
 #include "media/base/waiting.h"
 #include "media/filters/tizen/cdm_utils.h"
 #include "media/filters/tizen/decoder_promotion.h"
+#include "media/filters/tizen/h26x_bitstream_converter.h"
 #include "media/filters/tizen/media_video_codec.h"
 #include "media/filters/tizen/tizen_cdm_bridge.h"
 #include "media/filters/tizen/video_decoder_config_updater.h"
@@ -420,6 +421,10 @@ void TTvdVideoDecoderImpl::Decode(scoped_refptr<DecoderBuffer> buffer,
   CHECK(decoder_task_runner_->RunsTasksInCurrentSequence())
       << "Not on decoder thread";
 
+  if (h26x_bitstream_converter_) {
+    buffer = h26x_bitstream_converter_->ConvertBuffer(std::move(buffer));
+  }
+
   if (buffer->timestamp() == kNoTimestamp) {
     TIZEN_MEDIA_LOG(ERROR) << "Buffer with invalid timestamp provided.";
     std::move(decode_cb).Run(DecoderStatus::Codes::kMissingTimestamp);
@@ -718,6 +723,23 @@ void TTvdVideoDecoderImpl::Initialize(VideoDecoderConfig config,
                                       VideoDecoder::InitCB init_cb,
                                       VideoDecoder::OutputCB output_cb,
                                       WaitingCB waiting_cb) {
+  codec_ = VideoCodecToMediaVideoCodec(config.codec());
+  if (codec_ == MediaVideoCodec::kCodecUnknown) {
+    TIZEN_MEDIA_LOG(ERROR) << "Not supported video codec: " << config.codec();
+  }
+
+  if (codec_ == MediaVideoCodec::kCodecH264 ||
+      codec_ == MediaVideoCodec::kCodecHEVC) {
+    // In case of H264 and H265 codecs it is possible that data will be
+    // provided in AVCC format. In such case, we need to convert input data
+    // from AVCC to AnnexB format that is supported by platform HW decoder.
+    // To make it possible, we need to initialize H.26X bitstream converter.
+    h26x_bitstream_converter_ =
+        std::make_unique<H26XBitstreamConverter>(codec_, config.extra_data());
+  } else {
+    h26x_bitstream_converter_.reset();
+  }
+
   initialized_ = true;
   waiting_for_key_ = false;
   first_frame_done_ = false;
@@ -1092,11 +1114,6 @@ bool TTvdVideoDecoderImpl::AllocateDecoder() {
   callbacks.switch_cb = base::BindPostTaskToCurrentDefault(base::BindRepeating(
       &TTvdVideoDecoderImpl::SwitchDecoder, weak_factory_.GetWeakPtr()));
 
-  codec_ = VideoCodecToMediaVideoCodec(config_.codec());
-  if (codec_ == MediaVideoCodec::kCodecUnknown) {
-    TIZEN_MEDIA_LOG(ERROR) << "Not supported video codec: " << config_.codec();
-  }
-
   // Use picture size instead of |coded_size| as resource manager might
   // return that valid resolutions (e.g 3840x2176) are not supported on
   // several boards.
index 08542aac9a369528b96dde2b75881710dddf1d7f..dc36e7d92d429e210e6f874ac629fa0d2c40ea4e 100644 (file)
@@ -36,6 +36,7 @@
 
 namespace media {
 
+class H26XBitstreamConverter;
 class TizenCdmBridge;
 
 class DecodedCollectionOnGpu : public gfx::TizenOverlayPlaneCollection {
@@ -273,7 +274,7 @@ class MEDIA_EXPORT TTvdVideoDecoderImpl {
   // It might be also changed be calls to |Initialize|.
   ExtendedVideoDecoderConfig config_;
 
-  MediaVideoCodec codec_;
+  MediaVideoCodec codec_ = MediaVideoCodec::kCodecUnknown;
 
   // Tracks if |Initialize| was ever called for this instance. It exists
   /// because it's hard to track using |decoder_state_|.
@@ -459,6 +460,17 @@ class MEDIA_EXPORT TTvdVideoDecoderImpl {
 
   bool request_keyframe_after_initialization_ = false;
 
+  // H.264 (AVC) and H.265 (HEVC) data can be provided to decoder in AVCC
+  // format. HW decoder requests data to be provided in AnnexB format, so
+  // in case of receiving data in AVCC format, AVCC -> AnnexB conversion needs
+  // to be performed.
+  //
+  // We cannot use |VideoDecoder::NeedsBitstreamConversion| for automatic
+  // conversion, because it is possible, that HW decoder initialization may fail
+  // (eg. due to lack of available HW resources) and SW fallback (which requires
+  // AVCC format) will be performed.
+  std::unique_ptr<H26XBitstreamConverter> h26x_bitstream_converter_;
+
   // Token from GPU collection, needed to prepare surface to render on.
   base::UnguessableToken collection_token_;