// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h"

#include <utility>

#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "media/media_buildflags.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_codec_specifics_vp_8.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_video_frame_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_video_frame_metadata.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_video_frame_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_video_frame_type.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/modules/peerconnection/peer_connection_util.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/webrtc/api/frame_transformer_factory.h"
#include "third_party/webrtc/api/frame_transformer_interface.h"
#include "third_party/webrtc/api/units/timestamp.h"
#include "third_party/webrtc/api/video/video_frame_metadata.h"
#include "third_party/webrtc/api/video/video_frame_type.h"
#include "third_party/webrtc/api/video_codecs/video_codec.h"

namespace blink {

// Allow all fields to be set when calling RTCEncodedVideoFrame.setMetadata.
BASE_FEATURE(kAllowRTCEncodedVideoFrameSetMetadataAllFields,
             base::FEATURE_DISABLED_BY_DEFAULT);

namespace {
constexpr size_t kMaxNumDependencies = 8;

bool IsAllowedSetMetadataChange(
    const RTCEncodedVideoFrameMetadata* original_metadata,
    const RTCEncodedVideoFrameMetadata* metadata) {
  if (metadata->width() != original_metadata->width() ||
      metadata->height() != original_metadata->height() ||
      metadata->spatialIndex() != original_metadata->spatialIndex() ||
      metadata->temporalIndex() != original_metadata->temporalIndex()) {
    return false;
  }

  // It is possible to not have the RTP metadata values set. This condition
  // checks if the value exists and if it does, it should be the same.
  if ((metadata->hasSynchronizationSource() !=
           original_metadata->hasSynchronizationSource() ||
       (metadata->hasSynchronizationSource()
            ? metadata->synchronizationSource() !=
                  original_metadata->synchronizationSource()
            : false)) ||
      (metadata->hasContributingSources() !=
           original_metadata->hasContributingSources() ||
       (metadata->hasContributingSources()
            ? metadata->contributingSources() !=
                  original_metadata->contributingSources()
            : false))) {
    return false;
  }
  return true;
}

base::expected<void, String> ValidateMetadata(
    const RTCEncodedVideoFrameMetadata* metadata) {
  if (!metadata->hasWidth() || !metadata->hasHeight() ||
      !metadata->hasSpatialIndex() || !metadata->hasTemporalIndex() ||
      !metadata->hasRtpTimestamp()) {
    return base::unexpected("new metadata has member(s) missing.");
  }

  if (!metadata->hasFrameId() && metadata->hasDependencies() &&
      !metadata->dependencies().empty()) {
    return base::unexpected(
        "new metadata has frameID missing, but has dependencies");
  }
  if (!metadata->hasDependencies()) {
    return base::ok();
  }

  // Ensure there are at most 8 deps. Enforced in WebRTC's
  // RtpGenericFrameDescriptor::AddFrameDependencyDiff().
  if (metadata->dependencies().size() > kMaxNumDependencies) {
    return base::unexpected("new metadata has too many dependencies.");
  }
  // Require deps to all be before frame_id, but within 2^14 of it. Enforced in
  // WebRTC by a DCHECK in RtpGenericFrameDescriptor::AddFrameDependencyDiff().
  for (const int64_t dep : metadata->dependencies()) {
    if ((dep >= metadata->frameId()) ||
        ((metadata->frameId() - dep) >= (1 << 14))) {
      return base::unexpected("new metadata has invalid frame dependencies.");
    }
  }

  return base::ok();
}

}  // namespace

webrtc::VideoCodecType RTCEncodedVideoFrame::StringToVideoCodecType(
    const String& mime_type) {
  String lower_mime_type = mime_type.ToAsciiLower();
  if (lower_mime_type.contains("vp8")) {
    return webrtc::kVideoCodecVP8;
  }
  if (lower_mime_type.contains("vp9")) {
    return webrtc::kVideoCodecVP9;
  }
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
  if (lower_mime_type.contains("h264") || lower_mime_type.contains("avc1") ||
      lower_mime_type.contains("avc3")) {
    return webrtc::kVideoCodecH264;
  }
#endif
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
  if (lower_mime_type.contains("h265") || lower_mime_type.contains("hvc1") ||
      lower_mime_type.contains("hev1")) {
    return webrtc::kVideoCodecH265;
  }
#endif
  // TODO(crbug.com/40923648): Remove the wrong AV1 codecs string, "av1", once
  // we confirm nobody uses this in product.
  if (lower_mime_type.contains("av1") || lower_mime_type.contains("av01")) {
    return webrtc::kVideoCodecAV1;
  }
  return webrtc::kVideoCodecGeneric;
}

RTCEncodedVideoFrame* RTCEncodedVideoFrame::Create(
    ExecutionContext* context,
    RTCEncodedVideoFrame* original_frame,
    ExceptionState& exception_state) {
  return RTCEncodedVideoFrame::Create(context, original_frame, nullptr,
                                      exception_state);
}

RTCEncodedVideoFrame* RTCEncodedVideoFrame::Create(
    ExecutionContext* context,
    RTCEncodedVideoFrame* original_frame,
    const RTCEncodedVideoFrameOptions* options_dict,
    ExceptionState& exception_state) {
  RTCEncodedVideoFrame* new_frame;
  if (original_frame) {
    new_frame = MakeGarbageCollected<RTCEncodedVideoFrame>(
        original_frame->Delegate()->CloneWebRtcFrame());
  } else {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidAccessError,
        "Cannot create a new VideoFrame from an empty VideoFrame");
    return nullptr;
  }
  if (options_dict && options_dict->hasMetadata()) {
    base::expected<void, String> set_metadata =
        new_frame->SetMetadata(context, options_dict->metadata());
    if (!set_metadata.has_value()) {
      exception_state.ThrowDOMException(
          DOMExceptionCode::kInvalidModificationError,
          StrCat({"Cannot create a new VideoFrame: ", set_metadata.error()}));
      return nullptr;
    }
  }
  return new_frame;
}

RTCEncodedVideoFrame* RTCEncodedVideoFrame::Create(
    ExecutionContext* context,
    const RTCEncodedVideoFrameInit* init,
    ExceptionState& exception_state) {
  DOMArrayBuffer* payload_data_buffer = init->data();
  base::span<uint8_t> buffer_span = payload_data_buffer->ByteSpan();

  auto frame_type = webrtc::VideoFrameType::kEmptyFrame;
  if (init->type().AsEnum() == V8RTCEncodedVideoFrameType::Enum::kKey) {
    frame_type = webrtc::VideoFrameType::kVideoFrameKey;
  } else if (init->type().AsEnum() ==
             V8RTCEncodedVideoFrameType::Enum::kDelta) {
    frame_type = webrtc::VideoFrameType::kVideoFrameDelta;
  } else {
    NOTREACHED();
  }

  uint8_t payload_type = init->payloadType();
  uint32_t rtp_timestamp_without_offset = init->rtpTimestampWithoutOffset();

  std::optional<int64_t> absolute_capture_timestamp_ms;
  if (init->hasCaptureTime()) {
    base::TimeDelta capture_time = RTCEncodedFrameTimestampToCaptureTime(
        context, init->captureTime(), CaptureTimeInfo::ClockType::kTimeTicks);
    absolute_capture_timestamp_ms = capture_time.InMilliseconds();
  }

  std::vector<uint32_t> csrcs(init->contributingSources().begin(),
                              init->contributingSources().end());

  webrtc::VideoCodecType codec_type = StringToVideoCodecType(init->mimeType());

  std::optional<webrtc::Timestamp> presentation_timestamp;
  if (init->hasTimestamp()) {
    presentation_timestamp = webrtc::Timestamp::Micros(init->timestamp());
  }

  return MakeGarbageCollected<RTCEncodedVideoFrame>(
      webrtc::CreateOutgoingVideoFrame(
          frame_type, payload_type, rtp_timestamp_without_offset, buffer_span,
          absolute_capture_timestamp_ms, csrcs, codec_type,
          presentation_timestamp));
}

RTCEncodedVideoFrame::RTCEncodedVideoFrame(
    std::unique_ptr<webrtc::TransformableVideoFrameInterface> webrtc_frame)
    : RTCEncodedVideoFrame(std::move(webrtc_frame),
                           base::UnguessableToken::Null(),
                           0) {}

RTCEncodedVideoFrame::RTCEncodedVideoFrame(
    std::unique_ptr<webrtc::TransformableVideoFrameInterface> webrtc_frame,
    base::UnguessableToken owner_id,
    int64_t counter)
    : delegate_(base::MakeRefCounted<RTCEncodedVideoFrameDelegate>(
          std::move(webrtc_frame))),
      owner_id_(owner_id),
      counter_(counter) {}

RTCEncodedVideoFrame::RTCEncodedVideoFrame(
    scoped_refptr<RTCEncodedVideoFrameDelegate> delegate)
    : RTCEncodedVideoFrame(delegate->CloneWebRtcFrame()) {}

V8RTCEncodedVideoFrameType RTCEncodedVideoFrame::type() const {
  return V8RTCEncodedVideoFrameType(delegate_->Type());
}

uint32_t RTCEncodedVideoFrame::timestamp() const {
  return delegate_->RtpTimestamp().value_or(0);
}

DOMArrayBuffer* RTCEncodedVideoFrame::data(ExecutionContext* context) const {
  if (!frame_data_) {
    frame_data_ = delegate_->CreateDataBuffer(context->GetIsolate());
  }
  return frame_data_.Get();
}

RTCEncodedVideoFrameMetadata* RTCEncodedVideoFrame::getMetadata(
    ExecutionContext* context) const {
  RTCEncodedVideoFrameMetadata* metadata =
      RTCEncodedVideoFrameMetadata::Create();
  if (delegate_->PayloadType()) {
    metadata->setPayloadType(*delegate_->PayloadType());
  }
  if (delegate_->MimeType()) {
    metadata->setMimeType(String::FromUtf8(*delegate_->MimeType()));
  }

  if (RuntimeEnabledFeatures::RTCEncodedVideoFrameAdditionalMetadataEnabled()) {
    if (delegate_->PresentationTimestamp()) {
      metadata->setTimestamp(delegate_->PresentationTimestamp()->us());
    }
  }

  const std::optional<webrtc::VideoFrameMetadata> webrtc_metadata =
      delegate_->GetMetadata();
  if (!webrtc_metadata) {
    return metadata;
  }

  metadata->setSynchronizationSource(webrtc_metadata->GetSsrc());
  Vector<uint32_t> csrcs;
  for (uint32_t csrc : webrtc_metadata->GetCsrcs()) {
    csrcs.push_back(csrc);
  }
  metadata->setContributingSources(csrcs);

  if (webrtc_metadata->GetFrameId()) {
    metadata->setFrameId(*webrtc_metadata->GetFrameId());
  }

  if (auto webrtc_deps = webrtc_metadata->GetDependencies()) {
    Vector<int64_t> dependencies;
    dependencies.append_range(*webrtc_deps);
    metadata->setDependencies(std::move(dependencies));
  }
  metadata->setWidth(webrtc_metadata->GetWidth());
  metadata->setHeight(webrtc_metadata->GetHeight());
  metadata->setSpatialIndex(webrtc_metadata->GetSpatialIndex());
  metadata->setTemporalIndex(webrtc_metadata->GetTemporalIndex());
  if (delegate_->RtpTimestamp().has_value()) {
    metadata->setRtpTimestamp(*delegate_->RtpTimestamp());
  }

  if (RuntimeEnabledFeatures::RTCEncodedFrameTimestampsEnabled()) {
    if (std::optional<base::TimeTicks> receive_time =
            delegate_->ReceiveTime()) {
      metadata->setReceiveTime(
          RTCTimeStampFromTimeTicks(context, *receive_time));
    }
    if (std::optional<CaptureTimeInfo> capture_time_info =
            delegate_->CaptureTime()) {
      metadata->setCaptureTime(RTCEncodedFrameTimestampFromCaptureTimeInfo(
          context, *capture_time_info));
    }
    if (std::optional<base::TimeDelta> sender_capture_time_offset =
            delegate_->SenderCaptureTimeOffset()) {
      metadata->setSenderCaptureTimeOffset(CalculateRTCEncodedFrameTimeDelta(
          context, *sender_capture_time_offset));
    }
  }

  return metadata;
}

base::UnguessableToken RTCEncodedVideoFrame::OwnerId() {
  return owner_id_;
}
int64_t RTCEncodedVideoFrame::Counter() {
  return counter_;
}

base::expected<void, String> RTCEncodedVideoFrame::SetMetadata(
    ExecutionContext* context,
    const RTCEncodedVideoFrameMetadata* metadata) {
  const std::optional<webrtc::VideoFrameMetadata> original_webrtc_metadata =
      delegate_->GetMetadata();
  if (!original_webrtc_metadata) {
    return base::unexpected("underlying webrtc frame is an empty frame.");
  }

  base::expected<void, String> validate_metadata = ValidateMetadata(metadata);
  if (!validate_metadata.has_value()) {
    return validate_metadata;
  }

  RTCEncodedVideoFrameMetadata* original_metadata = getMetadata(context);
  if (!original_metadata) {
    return base::unexpected("internal error when calling getMetadata().");
  }
  if (!IsAllowedSetMetadataChange(original_metadata, metadata) &&
      !base::FeatureList::IsEnabled(
          kAllowRTCEncodedVideoFrameSetMetadataAllFields)) {
    return base::unexpected(
        "invalid modification of RTCEncodedVideoFrameMetadata.");
  }

  if ((metadata->hasPayloadType() != original_metadata->hasPayloadType()) ||
      (metadata->hasPayloadType() &&
       metadata->payloadType() != original_metadata->payloadType())) {
    return base::unexpected(
        "invalid modification of payloadType in RTCEncodedVideoFrameMetadata.");
  }

  // Initialize the new metadata from original_metadata to account for fields
  // not part of RTCEncodedVideoFrameMetadata.
  webrtc::VideoFrameMetadata webrtc_metadata = *original_webrtc_metadata;
  if (metadata->hasFrameId()) {
    webrtc_metadata.SetFrameId(metadata->frameId());
  }
  if (metadata->hasDependencies()) {
    webrtc_metadata.SetDependencies(metadata->dependencies());
  }
  webrtc_metadata.SetWidth(metadata->width());
  webrtc_metadata.SetHeight(metadata->height());
  webrtc_metadata.SetSpatialIndex(metadata->spatialIndex());
  webrtc_metadata.SetTemporalIndex(metadata->temporalIndex());
  webrtc_metadata.SetSsrc(metadata->synchronizationSource());

  if (metadata->hasContributingSources()) {
    std::vector<uint32_t> csrcs;
    for (uint32_t csrc : metadata->contributingSources()) {
      csrcs.push_back(csrc);
    }
    webrtc_metadata.SetCsrcs(csrcs);
  }

  return delegate_->SetMetadata(webrtc_metadata, metadata->rtpTimestamp());
}

void RTCEncodedVideoFrame::setMetadata(ExecutionContext* context,
                                       RTCEncodedVideoFrameMetadata* metadata,
                                       ExceptionState& exception_state) {
  base::expected<void, String> set_metadata = SetMetadata(context, metadata);
  if (!set_metadata.has_value()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidModificationError,
        StrCat({"Cannot setMetadata: ", set_metadata.error()}));
  }
}

void RTCEncodedVideoFrame::setData(ExecutionContext*, DOMArrayBuffer* data) {
  frame_data_ = data;
}

String RTCEncodedVideoFrame::toString(ExecutionContext* context) const {
  if (!delegate_) {
    return "empty";
  }

  StringBuilder sb;
  sb.Append("RTCEncodedVideoFrame{");
  if (const std::optional<uint32_t> rtp_timestamp = delegate_->RtpTimestamp()) {
    sb.Append("rtpTimestamp: ");
    sb.AppendNumber(*rtp_timestamp);
    sb.Append(", ");
  }
  sb.Append("size: ");
  sb.AppendNumber(data(context)->ByteLength());
  sb.Append(" bytes, type: ");
  sb.Append(type().AsCStr());
  sb.Append("}");
  return sb.ToString();
}

void RTCEncodedVideoFrame::SyncDelegate() const {
  delegate_->SetData(frame_data_);
}

scoped_refptr<RTCEncodedVideoFrameDelegate> RTCEncodedVideoFrame::Delegate()
    const {
  SyncDelegate();
  return delegate_;
}

std::unique_ptr<webrtc::TransformableVideoFrameInterface>
RTCEncodedVideoFrame::PassWebRtcFrame(v8::Isolate* isolate,
                                      bool detach_frame_data) {
  SyncDelegate();
  auto transformable_video_frame = delegate_->PassWebRtcFrame();
  // Detach the `frame_data_` ArrayBuffer if it's been created, as described in
  // the transfer on step 5 of the encoded transform spec write steps
  // (https://www.w3.org/TR/webrtc-encoded-transform/#stream-processing)
  if (detach_frame_data && frame_data_ && !frame_data_->IsDetached()) {
    CHECK(isolate);
    ArrayBufferContents contents_to_drop;
    NonThrowableExceptionState exception_state;
    CHECK(frame_data_->Transfer(isolate, v8::Local<v8::Value>(),
                                contents_to_drop, exception_state));
  }
  return transformable_video_frame;
}

void RTCEncodedVideoFrame::Trace(Visitor* visitor) const {
  ScriptWrappable::Trace(visitor);
  visitor->Trace(frame_data_);
}

}  // namespace blink
