Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  FFmpegVideoEncoder.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "FFmpegVideoEncoder.h"

#include <aom/aomcx.h>

#include <algorithm>

#include "AnnexB.h"
#include "BufferReader.h"
#include "EncoderConfig.h"
#include "FFmpegEncoderModule.h"
#include "FFmpegLog.h"
#include "FFmpegRuntimeLinker.h"
#include "FFmpegUtils.h"
#include "H264.h"
#include "ImageContainer.h"
#include "ImageConversion.h"
#include "libavutil/error.h"
#include "libavutil/pixfmt.h"
#include "libyuv.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/dom/ImageBitmapBinding.h"
#include "mozilla/dom/ImageUtils.h"
#include "mozilla/dom/VideoFrameBinding.h"
#ifdef MOZ_WIDGET_ANDROID
#  include "mozilla/gfx/gfxVars.h"
#endif
#include "nsPrintfCString.h"

// The ffmpeg namespace is introduced to avoid the PixelFormat's name conflicts
// with MediaDataEncoder::PixelFormat in MediaDataEncoder class scope.
namespace ffmpeg {

// TODO: WebCodecs' I420A should map to MediaDataEncoder::PixelFormat and then
// to AV_PIX_FMT_YUVA420P here.
#if LIBAVCODEC_VERSION_MAJOR < 54
using FFmpegPixelFormat = enum PixelFormat;
const FFmpegPixelFormat FFMPEG_PIX_FMT_NONE = FFmpegPixelFormat::PIX_FMT_NONE;
const FFmpegPixelFormat FFMPEG_PIX_FMT_RGBA = FFmpegPixelFormat::PIX_FMT_RGBA;
const FFmpegPixelFormat FFMPEG_PIX_FMT_BGRA = FFmpegPixelFormat::PIX_FMT_BGRA;
const FFmpegPixelFormat FFMPEG_PIX_FMT_RGB24 = FFmpegPixelFormat::PIX_FMT_RGB24;
const FFmpegPixelFormat FFMPEG_PIX_FMT_BGR24 = FFmpegPixelFormat::PIX_FMT_BGR24;
const FFmpegPixelFormat FFMPEG_PIX_FMT_YUV444P =
    FFmpegPixelFormat::PIX_FMT_YUV444P;
const FFmpegPixelFormat FFMPEG_PIX_FMT_YUV422P =
    FFmpegPixelFormat::PIX_FMT_YUV422P;
const FFmpegPixelFormat FFMPEG_PIX_FMT_YUV420P =
    FFmpegPixelFormat::PIX_FMT_YUV420P;
const FFmpegPixelFormat FFMPEG_PIX_FMT_NV12 = FFmpegPixelFormat::PIX_FMT_NV12;
const FFmpegPixelFormat FFMPEG_PIX_FMT_NV21 = FFmpegPixelFormat::PIX_FMT_NV21;
#else
using FFmpegPixelFormat = enum AVPixelFormat;
const FFmpegPixelFormat FFMPEG_PIX_FMT_NONE =
    FFmpegPixelFormat::AV_PIX_FMT_NONE;
const FFmpegPixelFormat FFMPEG_PIX_FMT_RGBA =
    FFmpegPixelFormat::AV_PIX_FMT_RGBA;
const FFmpegPixelFormat FFMPEG_PIX_FMT_BGRA =
    FFmpegPixelFormat::AV_PIX_FMT_BGRA;
const FFmpegPixelFormat FFMPEG_PIX_FMT_RGB24 =
    FFmpegPixelFormat::AV_PIX_FMT_RGB24;
const FFmpegPixelFormat FFMPEG_PIX_FMT_BGR24 =
    FFmpegPixelFormat::AV_PIX_FMT_BGR24;
const FFmpegPixelFormat FFMPEG_PIX_FMT_YUV444P =
    FFmpegPixelFormat::AV_PIX_FMT_YUV444P;
const FFmpegPixelFormat FFMPEG_PIX_FMT_YUV422P =
    FFmpegPixelFormat::AV_PIX_FMT_YUV422P;
const FFmpegPixelFormat FFMPEG_PIX_FMT_YUV420P =
    FFmpegPixelFormat::AV_PIX_FMT_YUV420P;
const FFmpegPixelFormat FFMPEG_PIX_FMT_NV12 =
    FFmpegPixelFormat::AV_PIX_FMT_NV12;
const FFmpegPixelFormat FFMPEG_PIX_FMT_NV21 =
    FFmpegPixelFormat::AV_PIX_FMT_NV21;
#endif

static const char* GetPixelFormatString(FFmpegPixelFormat aFormat) {
  switch (aFormat) {
    case FFMPEG_PIX_FMT_NONE:
      return "none";
    case FFMPEG_PIX_FMT_RGBA:
      return "packed RGBA 8:8:8:8 (32bpp, RGBARGBA...)";
    case FFMPEG_PIX_FMT_BGRA:
      return "packed BGRA 8:8:8:8 (32bpp, BGRABGRA...)";
    case FFMPEG_PIX_FMT_RGB24:
      return "packed RGB 8:8:8 (24bpp, RGBRGB...)";
    case FFMPEG_PIX_FMT_BGR24:
      return "packed RGB 8:8:8 (24bpp, BGRBGR...)";
    case FFMPEG_PIX_FMT_YUV444P:
      return "planar YUV 4:4:4 (24bpp, 1 Cr & Cb sample per 1x1 Y samples)";
    case FFMPEG_PIX_FMT_YUV422P:
      return "planar YUV 4:2:2 (16bpp, 1 Cr & Cb sample per 2x1 Y samples)";
    case FFMPEG_PIX_FMT_YUV420P:
      return "planar YUV 4:2:0 (12bpp, 1 Cr & Cb sample per 2x2 Y samples)";
    case FFMPEG_PIX_FMT_NV12:
      return "planar YUV 4:2:0 (12bpp, 1 interleaved UV components per 1x1 Y "
             "samples)";
    case FFMPEG_PIX_FMT_NV21:
      return "planar YUV 4:2:0 (12bpp, 1 interleaved VU components per 1x1 Y "
             "samples)";
    default:
      break;
  }
  MOZ_ASSERT_UNREACHABLE("Unsupported pixel format");
  return "unsupported";
}

};  // namespace ffmpeg

namespace mozilla {

struct H264Setting {
  int mValue;
  nsCString mString;
};

struct H264LiteralSetting {
  int mValue;
  nsLiteralCString mString;
  H264Setting get() const { return {mValue, mString.AsString()}; }
};

#if LIBAVCODEC_VERSION_MAJOR < 62
static constexpr H264LiteralSetting H264Profiles[]{
    {FF_PROFILE_H264_BASELINE, "baseline"_ns},
    {FF_PROFILE_H264_MAIN, "main"_ns},
    {FF_PROFILE_H264_EXTENDED, ""_ns},
    {FF_PROFILE_H264_HIGH, "high"_ns}};
#else
static constexpr H264LiteralSetting H264Profiles[]{
    {AV_PROFILE_H264_BASELINE, "baseline"_ns},
    {AV_PROFILE_H264_MAIN, "main"_ns},
    {AV_PROFILE_H264_EXTENDED, ""_ns},
    {AV_PROFILE_H264_HIGH, "high"_ns}};
#endif

static Maybe<H264Setting> GetH264Profile(const H264_PROFILE& aProfile) {
  switch (aProfile) {
    case H264_PROFILE::H264_PROFILE_UNKNOWN:
      return Nothing();
    case H264_PROFILE::H264_PROFILE_BASE:
      return Some(H264Profiles[0].get());
    case H264_PROFILE::H264_PROFILE_MAIN:
      return Some(H264Profiles[1].get());
    case H264_PROFILE::H264_PROFILE_EXTENDED:
      return Some(H264Profiles[2].get());
    case H264_PROFILE::H264_PROFILE_HIGH:
      return Some(H264Profiles[3].get());
    default:
      break;
  }
  MOZ_ASSERT_UNREACHABLE("undefined profile");
  return Nothing();
}

static Maybe<H264Setting> GetH264Level(const H264_LEVEL& aLevel) {
  int val = static_cast<int>(aLevel);
  nsPrintfCString str("%d", val);
  str.Insert('.'1);
  return Some(H264Setting{val, std::move(str)});
}

struct VPXSVCAppendix {
  uint8_t mLayeringMode;
};

struct SVCLayerSettings {
  using CodecAppendix = Variant<VPXSVCAppendix, aom_svc_params_t>;
  size_t mNumberSpatialLayers;
  size_t mNumberTemporalLayers;
  uint8_t mPeriodicity;
  nsTArray<uint8_t> mLayerIds;
  // libvpx: ts_rate_decimator, libaom: framerate_factor
  nsTArray<uint8_t> mRateDecimators;
  nsTArray<uint32_t> mTargetBitrates;
  Maybe<CodecAppendix> mCodecAppendix;
};

static SVCLayerSettings GetSVCLayerSettings(CodecType aCodec,
                                            const ScalabilityMode& aMode,
                                            uint32_t aBitPerSec) {
  // TODO: Apply more sophisticated bitrate allocation, like SvcRateAllocator:
  // https://searchfox.org/mozilla-central/rev/3bd65516eb9b3a9568806d846ba8c81a9402a885/third_party/libwebrtc/modules/video_coding/svc/svc_rate_allocator.h#26

  size_t layers = 0;
  const uint32_t kbps = aBitPerSec / 1000;  // ts_target_bitrate requies kbps.

  uint8_t periodicity;
  nsTArray<uint8_t> layerIds;
  nsTArray<uint8_t> rateDecimators;
  nsTArray<uint32_t> bitrates;

  Maybe<SVCLayerSettings::CodecAppendix> appendix;

  if (aMode == ScalabilityMode::L1T2) {
    // Two temporal layers. 0-1...
    //
    // Frame pattern:
    // Layer 0: |0| |2| |4| |6| |8|
    // Layer 1: | |1| |3| |5| |7| |

    layers = 2;

    // 2 frames per period.
    periodicity = 2;

    // Assign layer ids.
    layerIds.AppendElement(0);
    layerIds.AppendElement(1);

    // Set rate decimators.
    rateDecimators.AppendElement(2);
    rateDecimators.AppendElement(1);

    // Bitrate allocation: L0 - 60%, L1 - 40%.
    bitrates.AppendElement(kbps * 3 / 5);
    bitrates.AppendElement(kbps);

    if (aCodec == CodecType::VP8 || aCodec == CodecType::VP9) {
      appendix.emplace(VPXSVCAppendix{
          .mLayeringMode = 2 /* VP9E_TEMPORAL_LAYERING_MODE_0101 */
      });
    }
  } else {
    MOZ_ASSERT(aMode == ScalabilityMode::L1T3);
    // Three temporal layers. 0-2-1-2...
    //
    // Frame pattern:
    // Layer 0: |0| | | |4| | | |8| |  |  |12|
    // Layer 1: | | |2| | | |6| | | |10|  |  |
    // Layer 2: | |1| |3| |5| |7| |9|  |11|  |

    layers = 3;

    // 4 frames per period
    periodicity = 4;

    // Assign layer ids.
    layerIds.AppendElement(0);
    layerIds.AppendElement(2);
    layerIds.AppendElement(1);
    layerIds.AppendElement(2);

    // Set rate decimators.
    rateDecimators.AppendElement(4);
    rateDecimators.AppendElement(2);
    rateDecimators.AppendElement(1);

    // Bitrate allocation: L0 - 50%, L1 - 20%, L2 - 30%.
    bitrates.AppendElement(kbps / 2);
    bitrates.AppendElement(kbps * 7 / 10);
    bitrates.AppendElement(kbps);

    if (aCodec == CodecType::VP8 || aCodec == CodecType::VP9) {
      appendix.emplace(VPXSVCAppendix{
          .mLayeringMode = 3 /* VP9E_TEMPORAL_LAYERING_MODE_0212 */
      });
    }
  }

  MOZ_ASSERT(layers == bitrates.Length(),
             "Bitrate must be assigned to each layer");
  return SVCLayerSettings{1,
                          layers,
                          periodicity,
                          std::move(layerIds),
                          std::move(rateDecimators),
                          std::move(bitrates),
                          appendix};
}

void FFmpegVideoEncoder<LIBAV_VER>::SVCInfo::UpdateTemporalLayerId() {
  MOZ_ASSERT(!mTemporalLayerIds.IsEmpty());
  mCurrentIndex = (mCurrentIndex + 1) % mTemporalLayerIds.Length();
}

uint8_t FFmpegVideoEncoder<LIBAV_VER>::SVCInfo::CurrentTemporalLayerId() {
  MOZ_ASSERT(!mTemporalLayerIds.IsEmpty());
  return mTemporalLayerIds[mCurrentIndex];
}

void FFmpegVideoEncoder<LIBAV_VER>::SVCInfo::ResetTemporalLayerId() {
  MOZ_ASSERT(!mTemporalLayerIds.IsEmpty());
  mCurrentIndex = 0;
}

FFmpegVideoEncoder<LIBAV_VER>::FFmpegVideoEncoder(
    const FFmpegLibWrapper* aLib, AVCodecID aCodecID,
    const RefPtr<TaskQueue>& aTaskQueue, const EncoderConfig& aConfig)
    : FFmpegDataEncoder(aLib, aCodecID, aTaskQueue, aConfig) {}

RefPtr<MediaDataEncoder::InitPromise> FFmpegVideoEncoder<LIBAV_VER>::Init() {
  FFMPEGV_LOG("Init");
  return InvokeAsync(mTaskQueue, __func__, [self = RefPtr(this)]() {
    MediaResult r = self->InitEncoder();
    if (NS_FAILED(r.Code())) {
      FFMPEGV_LOG("{}", r.Description().get());
      return InitPromise::CreateAndReject(r, __func__);
    }
    return InitPromise::CreateAndResolve(true, __func__);
  });
}

nsCString FFmpegVideoEncoder<LIBAV_VER>::GetDescriptionName() const {
#ifdef USING_MOZFFVPX
  return "ffvpx video encoder"_ns;
#else
  const char* lib =
#  if defined(MOZ_FFMPEG)
      FFmpegRuntimeLinker::LinkStatusLibraryName();
#  else
      "no library: ffmpeg disabled during build";
#  endif
  return nsPrintfCString("ffmpeg video encoder (%s)", lib);
#endif
}

bool FFmpegVideoEncoder<LIBAV_VER>::SvcEnabled() const {
  return mConfig.mScalabilityMode != ScalabilityMode::None;
}

bool FFmpegVideoEncoder<LIBAV_VER>::ShouldTryHardware() const {
#ifdef MOZ_WIDGET_ANDROID
  // On Android, the MediaCodec encoders are the only ones available to us,
  // which may be implemented in hardware or software.
  if (mCodecID == AV_CODEC_ID_H264 || mCodecID == AV_CODEC_ID_HEVC) {
    return StaticPrefs::media_ffvpx_hw_enabled();
  }

  if (StaticPrefs::media_ffvpx_hw_minimal()) {
    return false;
  }
#endif

  if (mConfig.mHardwarePreference == HardwarePreference::RequireSoftware) {
    return false;
  }

  RefPtr<PlatformEncoderModule> pem =
      FFmpegEncoderModule<LIBAV_VER>::Create(mLib);
  return pem->SupportsCodec(mConfig.mCodec)
      .contains(media::EncodeSupport::HardwareEncode);
}

MediaResult FFmpegVideoEncoder<LIBAV_VER>::InitEncoder() {
  MediaResult result(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
  if (ShouldTryHardware()) {
    result = InitEncoderInternal(/* aHardware */ true);
  }
  // TODO(aosmond): We should be checking here for RequireHardware, but we fail
  // encoding tests if we don't allow fallback to software on Linux in CI.
  if (NS_FAILED(result.Code())) {
    result = InitEncoderInternal(/* aHardware */ false);
  }
  return result;
}

MediaResult FFmpegVideoEncoder<LIBAV_VER>::InitEncoderInternal(bool aHardware) {
  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());

  FFMPEGV_LOG("FFmpegVideoEncoder::InitEncoder");

  // Initialize the common members of the encoder instance
  auto r = AllocateCodecContext(aHardware);
  if (r.isErr()) {
    return r.inspectErr();
  }
  mCodecContext = r.unwrap();
  mCodecName = mCodecContext->codec->name;

#ifdef MOZ_WIDGET_ANDROID
  // We need to create a MediaCodec encoder for H264/HEVC but it may or may not
  // be backed by actual hardware.
  switch (mCodecID) {
    case AV_CODEC_ID_H264:
      mIsHardwareAccelerated = aHardware && gfx::gfxVars::UseH264HwEncode();
      break;
    case AV_CODEC_ID_HEVC:
      mIsHardwareAccelerated = aHardware && gfx::gfxVars::UseHEVCHwEncode();
      break;
    default:
      mIsHardwareAccelerated = aHardware;
      break;
  }
  mUseDurationMap = aHardware;
#else
#  ifdef MOZ_FFMPEG_ENCODER_USE_DURATION_MAP
  mUseDurationMap = true;
#  endif
  mIsHardwareAccelerated = aHardware;
#endif

  // And now the video-specific part
#ifdef MOZ_WIDGET_ANDROID
  // COLOR_FormatYUV420SemiPlanar(NV12) is the most widely supported
  // format by the Android hardware encoders.
  mCodecContext->pix_fmt =
      aHardware ? ffmpeg::FFMPEG_PIX_FMT_NV12 : ffmpeg::FFMPEG_PIX_FMT_YUV420P;
#else
  mCodecContext->pix_fmt = ffmpeg::FFMPEG_PIX_FMT_YUV420P;
  // // TODO: do this properly, based on the colorspace of the frame. Setting
  // this like that crashes encoders. if (mConfig.mCodec != CodecType::AV1) {
  //     if (mConfig.mPixelFormat == dom::ImageBitmapFormat::RGBA32 ||
  //         mConfig.mPixelFormat == dom::ImageBitmapFormat::BGRA32) {
  //       mCodecContext->color_primaries = AVCOL_PRI_BT709;
  //       mCodecContext->colorspace = AVCOL_SPC_RGB;
  //   #ifdef FFVPX_VERSION
  //       mCodecContext->color_trc = AVCOL_TRC_IEC61966_2_1;
  //   #endif
  //     } else {
  //       mCodecContext->color_primaries = AVCOL_PRI_BT709;
  //       mCodecContext->colorspace = AVCOL_SPC_BT709;
  //       mCodecContext->color_trc = AVCOL_TRC_BT709;
  //     }
  // }
#endif

  mCodecContext->width = static_cast<int>(mConfig.mSize.width);
  mCodecContext->height = static_cast<int>(mConfig.mSize.height);
  // Reasonnable default for the quantization range.
  mCodecContext->qmin =
      AssertedCast<int>(StaticPrefs::media_ffmpeg_encoder_quantizer_min());
  mCodecContext->qmax =
      AssertedCast<int>(StaticPrefs::media_ffmpeg_encoder_quantizer_max());
  if (mConfig.mUsage == Usage::Realtime) {
    mCodecContext->thread_count = 1;
  } else {
    int64_t pixels = mCodecContext->width * mCodecContext->height;
    int threads = 1;
    // Select a thread count that depends on the frame size, and cap to the
    // number of available threads minus one
    if (pixels >= 3840 * 2160) {
      threads = 16;
    } else if (pixels >= 1920 * 1080) {
      threads = 8;
    } else if (pixels >= 1280 * 720) {
      threads = 4;
    } else if (pixels >= 640 * 480) {
      threads = 2;
    }
    mCodecContext->thread_count = std::clamp<int>(
        threads, 1, AssertedCast<int>(GetNumberOfProcessors()) - 1);
  }
  // TODO(bug 1869560): The recommended time_base is the reciprocal of the frame
  // rate, but we set it to microsecond for now.
  mCodecContext->time_base =
      AVRational{.num = 1, .den = AssertedCast<int>(USECS_PER_S)};
#if LIBAVCODEC_VERSION_MAJOR >= 57
  // Note that sometimes framerate can be zero (from webcodecs).
  mCodecContext->framerate =
      AVRational{.num = AssertedCast<int>(mConfig.mFramerate), .den = 1};
#endif

#if LIBAVCODEC_VERSION_MAJOR >= 60
  mCodecContext->flags |= AV_CODEC_FLAG_FRAME_DURATION;
#endif

  // Setting 0 here disable inter-frames: all frames are keyframes
  mCodecContext->gop_size = mConfig.mKeyframeInterval
                                ? AssertedCast<int>(mConfig.mKeyframeInterval)
                                : 10000;
  mCodecContext->keyint_min = 0;

  // When either real-time or SVC is enabled via config, the general settings of
  // the encoder are set to be more appropriate for real-time usage
  if (mConfig.mUsage == Usage::Realtime || SvcEnabled()) {
    if (mConfig.mUsage != Usage::Realtime) {
      FFMPEGV_LOG(
          "SVC enabled but low latency encoding mode not enabled, forcing low "
          "latency mode");
    }
    mLib->av_opt_set(mCodecContext->priv_data, "deadline""realtime"0);
    // Explicitly ask encoder do not keep in flight at any one time for
    // lookahead purposes.
    mLib->av_opt_set(mCodecContext->priv_data, "lag-in-frames""0"0);

    if (mConfig.mCodec == CodecType::VP8 || mConfig.mCodec == CodecType::VP9) {
      mLib->av_opt_set(mCodecContext->priv_data, "error-resilient""1"0);
    }
    if (mConfig.mCodec == CodecType::AV1) {
      mLib->av_opt_set(mCodecContext->priv_data, "error-resilience""1"0);
      // This sets usage to AOM_USAGE_REALTIME
      mLib->av_opt_set(mCodecContext->priv_data, "usage""1"0);
      // Allow the bitrate to swing 50% up and down the target
      mLib->av_opt_set(mCodecContext->priv_data, "rc_undershoot_percent""50",
                       0);
      mLib->av_opt_set(mCodecContext->priv_data, "rc_overshoot_percent""50",
                       0);
      // Row multithreading -- note that we do single threaded encoding for now,
      // so this doesn't do much
      mLib->av_opt_set(mCodecContext->priv_data, "row_mt""1"0);
      // Cyclic refresh adaptive quantization
      mLib->av_opt_set(mCodecContext->priv_data, "aq-mode""3"0);
      // optimized for real-time, 7 for regular, lower: more cpu use -> higher
      // compression ratio
      mLib->av_opt_set(mCodecContext->priv_data, "cpu-used""9"0);
      // disable, this is to handle camera motion, unlikely for our use case
      mLib->av_opt_set(mCodecContext->priv_data, "enable-global-motion""0",
                       0);
      mLib->av_opt_set(mCodecContext->priv_data, "enable-cfl-intra""0"0);
      // TODO: Set a number of tiles appropriate for the number of threads used
      // -- disable tiling if using a single thread.
      mLib->av_opt_set(mCodecContext->priv_data, "tile-columns""0"0);
      mLib->av_opt_set(mCodecContext->priv_data, "tile-rows""0"0);
    }
  } else {
    if (mConfig.mCodec == CodecType::AV1) {
      mLib->av_opt_set_int(
          mCodecContext->priv_data, "cpu-used",
          AssertedCast<int>(StaticPrefs::media_ffmpeg_encoder_cpu_used()), 0);
    }
  }

  if (SvcEnabled()) {
    if (Maybe<SVCSettings> settings = GetSVCSettings()) {
      if (mCodecName == "libaom-av1") {
        if (mConfig.mBitrateMode != BitrateMode::Constant) {
          return MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR,
                             "AV1 with SVC only supports constant bitrate"_ns);
        }
      }

      SVCSettings s = settings.extract();
      FFMPEGV_LOG("SVC options string: {}={}", s.mSettingKeyValue.first.get(),
                  s.mSettingKeyValue.second.get());
      mLib->av_opt_set(mCodecContext->priv_data, s.mSettingKeyValue.first.get(),
                       s.mSettingKeyValue.second.get(), 0);

      // FFmpegVideoEncoder is reset after Drain(), so mSVCInfo should be
      // reset() before emplace().
      mSVCInfo.reset();
      mSVCInfo.emplace(std::move(s.mTemporalLayerIds));

      // TODO: layer settings should be changed dynamically when the frame's
      // color space changed.
    }
  }

#ifdef MOZ_WIDGET_ANDROID
  // If we are using MediaCodec, we can set more options.
  if (aHardware) {
    if (mConfig.mBitrateMode == BitrateMode::Constant) {
      mLib->av_opt_set(mCodecContext->priv_data, "bitrate_mode""cbr"0);
    } else {
      mLib->av_opt_set(mCodecContext->priv_data, "bitrate_mode""vbr"0);
    }
  }
#endif

  nsAutoCString h264Log;
  if (mConfig.mCodecSpecific.is<H264Specific>()) {
    const H264Specific& h264Specific =
        mConfig.mCodecSpecific.as<H264Specific>();
    H264Settings s = GetH264Settings(h264Specific);
    mCodecContext->profile = s.mProfile;
    mCodecContext->level = s.mLevel;
    for (const auto& pair : s.mSettingKeyValuePairs) {
      mLib->av_opt_set(mCodecContext->priv_data, pair.first.get(),
                       pair.second.get(), 0);
    }

    // Log the settings.
    const char* formatStr =
        h264Specific.mFormat == H264BitStreamFormat::AVC ? "AVCC" : "AnnexB";
    if (mCodecName.Equals("libx264"_ns)) {
      // When using profile other than EXTENDED, the profile string is in the
      // first element of mSettingKeyValuePairs, while EXTENDED profile has no
      // profile string.

      MOZ_ASSERT_IF(
          s.mSettingKeyValuePairs.Length() != 3,
          h264Specific.mProfile == H264_PROFILE::H264_PROFILE_EXTENDED);
      const char* profileStr = s.mSettingKeyValuePairs.Length() == 3
                                   ? s.mSettingKeyValuePairs[0].second.get()
                                   : "extended";
      const char* levelStr = s.mSettingKeyValuePairs.Length() == 3
                                 ? s.mSettingKeyValuePairs[1].second.get()
                                 : s.mSettingKeyValuePairs[0].second.get();
      h264Log.AppendPrintf(", H264: profile - %d (%s), level %d (%s), %s",
                           mCodecContext->profile, profileStr,
                           mCodecContext->level, levelStr, formatStr);
    } else {
      h264Log.AppendPrintf(", H264: profile - %d, level %d, %s",
                           mCodecContext->profile, mCodecContext->level,
                           formatStr);
    }
  }

  // - if mConfig.mDenoising is set: av_opt_set_int(mCodecContext->priv_data,
  // "noise_sensitivity", x, 0), where the x is from 0(disabled) to 6.
  // - if mConfig.mAdaptiveQp is set: av_opt_set_int(mCodecContext->priv_data,
  // "aq_mode", x, 0), where x is from 0 to 3: 0 - Disabled, 1 - Variance
  // AQ(default), 2 - Complexity AQ, 3 - Cycle AQ.

  // Our old version of libaom-av1 is considered experimental by the recent
  // ffmpeg we use. Allow experimental codecs for now until we decide on an AV1
  // encoder.
  mCodecContext->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;

  SetContextBitrate();

  AVDictionary* options = nullptr;
  if (int ret = OpenCodecContext(mCodecContext->codec, &options); ret < 0) {
    return MediaResult(
        NS_ERROR_DOM_MEDIA_FATAL_ERR,
        RESULT_DETAIL("failed to open %s avcodec: %s", mCodecName.get(),
                      MakeErrorString(mLib, ret).get()));
  }
  mLib->av_dict_free(&options);

  FFMPEGV_LOG(
      "{} has been initialized with format: {}, bitrate: {}, width: {}, "
      "height: {}, quantizer: [{}, {}], time_base: {}/{}{}",
      mCodecName.get(), ffmpeg::GetPixelFormatString(mCodecContext->pix_fmt),
      static_cast<int64_t>(mCodecContext->bit_rate), mCodecContext->width,
      mCodecContext->height, mCodecContext->qmin, mCodecContext->qmax,
      mCodecContext->time_base.num, mCodecContext->time_base.den,
      h264Log.IsEmpty() ? "" : h264Log.get());

  return NS_OK;
}

// avcodec_send_frame and avcodec_receive_packet were introduced in version 58.
#if LIBAVCODEC_VERSION_MAJOR >= 58
Result<MediaDataEncoder::EncodedData, MediaResult> FFmpegVideoEncoder<
    LIBAV_VER>::EncodeInputWithModernAPIs(RefPtr<const MediaData> aSample) {
  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
  MOZ_ASSERT(mCodecContext);
  MOZ_ASSERT(aSample);

  RefPtr<const VideoData> sample(aSample->As<VideoData>());

  // Validate input.
  if (!sample->mImage) {
    return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "No image"_ns));
  }
  if (sample->mImage->GetSize().IsEmpty()) {
    return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                           "image width or height is invalid"_ns));
  }

  // Allocate AVFrame.
  if (!PrepareFrame()) {
    return Err(
        MediaResult(NS_ERROR_OUT_OF_MEMORY, "failed to allocate frame"_ns));
  }

  // Set AVFrame properties for its internal data allocation. For now, we always
  // convert into ffmpeg's buffer.
  mFrame->format = mCodecContext->pix_fmt;
  mFrame->width = static_cast<int>(mConfig.mSize.width);
  mFrame->height = static_cast<int>(mConfig.mSize.height);
  mFrame->pict_type =
      sample->mKeyframe ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE;

  // Allocate AVFrame data.
  if (int ret = mLib->av_frame_get_buffer(mFrame, 0); ret < 0) {
    return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                           RESULT_DETAIL("failed to allocate frame data: %s",
                                         MakeErrorString(mLib, ret).get())));
  }

  // Make sure AVFrame is writable.
  if (int ret = mLib->av_frame_make_writable(mFrame); ret < 0) {
    return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                           RESULT_DETAIL("failed to make frame writable: %s",
                                         MakeErrorString(mLib, ret).get())));
  }

  nsresult rv;
  switch (mFrame->format) {
    case ffmpeg::FFMPEG_PIX_FMT_YUV420P:
      rv = ConvertToI420(sample->mImage, mFrame->data[0], mFrame->linesize[0],
                         mFrame->data[1], mFrame->linesize[1], mFrame->data[2],
                         mFrame->linesize[2], mConfig.mSize);
      break;
    case ffmpeg::FFMPEG_PIX_FMT_NV12:
      rv = ConvertToNV12(sample->mImage, mFrame->data[0], mFrame->linesize[0],
                         mFrame->data[1], mFrame->linesize[1], mConfig.mSize);
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("unhandled ffmpeg format!");
      rv = NS_ERROR_DOM_MEDIA_FATAL_ERR;
      break;
  }
  if (NS_FAILED(rv)) {
    return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                           "failed to convert format to ffmpeg format"_ns));
  }

  // Set presentation timestamp and duration of the AVFrame. The unit of pts is
  // time_base.
  // TODO(bug 1869560): The recommended time_base is the reciprocal of the frame
  // rate, but we set it to microsecond for now.
#  if LIBAVCODEC_VERSION_MAJOR >= 59
  mFrame->time_base =
      AVRational{.num = 1, .den = AssertedCast<int>(USECS_PER_S)};
#  endif
  // Provide fake pts, see header file.
  if (mConfig.mCodec == CodecType::AV1) {
    mFrame->pts = mFakePts;
    mPtsMap.Insert(mFakePts, aSample->mTime.ToMicroseconds());
    mFakePts += aSample->mDuration.ToMicroseconds();
    mCurrentFramePts = aSample->mTime.ToMicroseconds();
  } else {
    mFrame->pts = aSample->mTime.ToMicroseconds();
  }
#  ifdef MOZ_FFMPEG_ENCODER_USE_DURATION_MAP
  if (mUseDurationMap) {
    mDurationMap.Insert(mFrame->pts, aSample->mDuration.ToMicroseconds());
  }
#  else
  mFrame->duration = aSample->mDuration.ToMicroseconds();
#  endif
  Duration(mFrame) = aSample->mDuration.ToMicroseconds();

  AVDictionary* dict = nullptr;
  // VP8/VP9 use a mode that handles the temporal layer id sequence internally,
  // and don't require setting explicitly setting the metadata. Other codecs
  // such as AV1 via libaom however requires manual frame tagging.
  if (SvcEnabled() && mConfig.mCodec != CodecType::VP8 &&
      mConfig.mCodec != CodecType::VP9) {
    if (aSample->mKeyframe) {
      FFMPEGV_LOG("Key frame requested, reseting temporal layer id");
      mSVCInfo->ResetTemporalLayerId();
    }
    nsPrintfCString str("%d", mSVCInfo->CurrentTemporalLayerId());
    mLib->av_dict_set(&dict, "temporal_id", str.get(), 0);
    mFrame->metadata = dict;
  }

  // Now send the AVFrame to ffmpeg for encoding, same code for audio and video.
  return FFmpegDataEncoder<LIBAV_VER>::EncodeWithModernAPIs();
}
#endif  // if LIBAVCODEC_VERSION_MAJOR >= 58

Result<RefPtr<MediaRawData>, MediaResult>
FFmpegVideoEncoder<LIBAV_VER>::ToMediaRawData(AVPacket* aPacket) {
  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
  MOZ_ASSERT(aPacket);

  auto extradataResult = GetExtraData(aPacket);
  if (extradataResult.isOk()) {
    mLastExtraData = extradataResult.unwrap();
  } else if (extradataResult.isErr()) {
    MediaResult e = extradataResult.unwrapErr();
    if (e.Code() != NS_ERROR_NOT_AVAILABLE &&
        e.Code() != NS_ERROR_NOT_IMPLEMENTED) {
      return Err(e);
    }
    FFMPEGV_LOG("GetExtraData failed with {}, but we can ignore it for now",
                e.Description().get());
  }

  auto data = MakeRefPtr<MediaRawData>();
  data->mKeyframe = (aPacket->flags & AV_PKT_FLAG_KEY) != 0;
  UniquePtr<MediaRawDataWriter> writer(data->CreateWriter());

  const bool isH264 =
      mCodecID == AV_CODEC_ID_H264 && mConfig.mCodecSpecific.is<H264Specific>();

#ifdef MOZ_WIDGET_ANDROID
  // If we have an AnnexB keyframe, and we failed to extract extradata from the
  // packet, then we know that the SPS/PPS data is missing from the data. In
  // that case we need to prepend our cached extradata to supply it in-band.
  //
  // When our minimum supported Android version is >= 9, then we can just use
  // the prepend-sps-pps-to-idr-frames parameter with AMediaFormat.
  if (data->mKeyframe && !extradataResult.isOk() && mLastExtraData && isH264 &&
      mConfig.mCodecSpecific.as<H264Specific>().mFormat ==
          H264BitStreamFormat::ANNEXB) {
    if (!writer->Append(mLastExtraData->Elements(), mLastExtraData->Length())) {
      return Err(
          MediaResult(NS_ERROR_OUT_OF_MEMORY,
                      "fail to append extradata to MediaRawData buffer"_ns));
    }
  }
#endif

  if (!writer->Append(aPacket->data, aPacket->size)) {
    return Err(MediaResult(NS_ERROR_OUT_OF_MEMORY,
                           "fail to append packet to MediaRawData buffer"_ns));
  }

  // With AVC, we are expected to provide the extradata out-of-band. This will
  // either be done as part of our conversion from AnnexB, or we will attach the
  // extradata we cached/extracted from the packet earlier.
  if (isH264 && mConfig.mCodecSpecific.as<H264Specific>().mFormat ==
                    H264BitStreamFormat::AVC) {
    if (!mCodecName.Equals("libx264"_ns) && AnnexB::IsAnnexB(*data)) {
      if (!AnnexB::ConvertSampleToAVCC(data, mLastExtraData)) {
        return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                               "Failed to convert to AVCC"_ns));
      }
    } else {
      data->mExtraData = mLastExtraData;
    }
  }

  // TODO(bug 1869560): The unit of pts, dts, and duration is time_base, which
  // is recommended to be the reciprocal of the frame rate, but we set it to
  // microsecond for now.
  data->mTime = media::TimeUnit::FromMicroseconds(aPacket->pts);
#ifdef MOZ_FFMPEG_ENCODER_USE_DURATION_MAP
  Maybe<int64_t> duration;
  if (mUseDurationMap && (duration = mDurationMap.Take(aPacket->pts))) {
    data->mDuration = media::TimeUnit::FromMicroseconds(*duration);
  } else
#endif
  {
    data->mDuration = media::TimeUnit::FromMicroseconds(aPacket->duration);
  }
  data->mTimecode = media::TimeUnit::FromMicroseconds(aPacket->dts);

  if (mConfig.mCodec == CodecType::AV1) {
    auto found = mPtsMap.Take(aPacket->pts);
    data->mTime = media::TimeUnit::FromMicroseconds(found.value());
  }

  if (mSVCInfo) {
    if (data->mKeyframe) {
      FFMPEGV_LOG(
          "Encoded packet is key frame, reseting temporal layer id sequence");
      mSVCInfo->ResetTemporalLayerId();
    }
    uint8_t temporalLayerId = mSVCInfo->CurrentTemporalLayerId();
    data->mTemporalLayerId.emplace(temporalLayerId);
    mSVCInfo->UpdateTemporalLayerId();
  }

  return data;
}

Result<already_AddRefed<MediaByteBuffer>, MediaResult>
FFmpegVideoEncoder<LIBAV_VER>::GetExtraData(AVPacket* aPacket) {
  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
  MOZ_ASSERT(aPacket);

  // We only extract the extra data when encoding into AVCC format.
  if (mCodecID != AV_CODEC_ID_H264 ||
      !mConfig.mCodecSpecific.is<H264Specific>()) {
    return Err(
        MediaResult(NS_ERROR_NOT_AVAILABLE, "Extra data unnecessary"_ns));
  }

  const bool wantAVCC = mConfig.mCodecSpecific.as<H264Specific>().mFormat ==
                        H264BitStreamFormat::AVC;

  Span<const uint8_t> packetBuf(aPacket->data,
                                AssertedCast<size_t>(aPacket->size));
  if (!mCodecName.Equals("libx264"_ns) && AnnexB::IsAnnexB(packetBuf)) {
    auto extraData = wantAVCC ? AnnexB::ExtractExtraDataForAVCC(packetBuf)
                              : AnnexB::ExtractExtraData(packetBuf);
    if (!extraData) {
      return Err(MediaResult(NS_ERROR_NOT_AVAILABLE,
                             "Extra data missing from packet"_ns));
    }
    return extraData.forget();
  }

  if (!(aPacket->flags & AV_PKT_FLAG_KEY)) {
    return Err(MediaResult(NS_ERROR_NOT_AVAILABLE,
                           "Extra data only comes with key frame"_ns));
  }

  if (!mCodecName.Equals("libx264"_ns)) {
    return Err(MediaResult(
        NS_ERROR_NOT_IMPLEMENTED,
        RESULT_DETAIL(
            "Get extra data from codec %s has not been implemented yet",
            mCodecName.get())));
  }

  if (!wantAVCC) {
    return Err(
        MediaResult(NS_ERROR_NOT_AVAILABLE, "Extra data unnecessary"_ns));
  }

  bool useGlobalHeader =
#if LIBAVCODEC_VERSION_MAJOR >= 57
      mCodecContext->flags & AV_CODEC_FLAG_GLOBAL_HEADER;
#else
      false;
#endif

  Span<const uint8_t> buf;
  if (useGlobalHeader) {
    buf = Span<const uint8_t>(
        mCodecContext->extradata,
        AssertedCast<size_t>(mCodecContext->extradata_size));
  } else {
    buf = packetBuf;
  }
  if (buf.empty()) {
    return Err(MediaResult(NS_ERROR_UNEXPECTED,
                           "fail to get H264 AVCC header in key frame!"_ns));
  }

  BufferReader reader(buf);

  // The first part is sps.
  uint32_t spsSize = MOZ_TRY(reader.ReadU32());
  Span<const uint8_t> spsData =
      MOZ_TRY(reader.ReadSpan<const uint8_t>(static_cast<size_t>(spsSize)));

  // The second part is pps.
  uint32_t ppsSize = MOZ_TRY(reader.ReadU32());
  Span<const uint8_t> ppsData =
      MOZ_TRY(reader.ReadSpan<const uint8_t>(static_cast<size_t>(ppsSize)));

  // Ensure we have profile, constraints and level needed to create the extra
  // data.
  if (spsData.Length() < 4) {
    return Err(MediaResult(NS_ERROR_UNEXPECTED, "spsData is too short"_ns));
  }

  FFMPEGV_LOG(
      "Generate extra data: profile - {}, constraints: {}, level: {} for pts @ "
      "{}",
      spsData[1], spsData[2], spsData[3], aPacket->pts);

  // Create extra data.
  auto extraData = MakeRefPtr<MediaByteBuffer>();
  H264::WriteExtraData(extraData, spsData[1], spsData[2], spsData[3], spsData,
                       ppsData);
  MOZ_ASSERT(extraData);
  return extraData.forget();
}

Maybe<FFmpegVideoEncoder<LIBAV_VER>::SVCSettings>
FFmpegVideoEncoder<LIBAV_VER>::GetSVCSettings() {
  MOZ_ASSERT(!mCodecName.IsEmpty());
  MOZ_ASSERT(SvcEnabled());

  CodecType codecType = CodecType::Unknown;
  if (mCodecName == "libvpx") {
    codecType = CodecType::VP8;
  } else if (mCodecName == "libvpx-vp9") {
    codecType = CodecType::VP9;
  } else if (mCodecName == "libaom-av1") {
    codecType = CodecType::AV1;
  }

  if (codecType == CodecType::Unknown) {
    FFMPEGV_LOG("SVC setting is not implemented for {} codec",
                mCodecName.get());
    return Nothing();
  }

  SVCLayerSettings svc = GetSVCLayerSettings(
      codecType, mConfig.mScalabilityMode, mConfig.mBitrate);

  nsAutoCString name;
  nsAutoCString parameters;

  if (codecType == CodecType::VP8 || codecType == CodecType::VP9) {
    // Check if the number of temporal layers in codec specific settings
    // matches
    // the number of layers for the given scalability mode.
    if (mConfig.mCodecSpecific.is<VP8Specific>()) {
      MOZ_ASSERT(mConfig.mCodecSpecific.as<VP8Specific>().mNumTemporalLayers ==
                 svc.mNumberTemporalLayers);
    } else if (mConfig.mCodecSpecific.is<VP9Specific>()) {
      MOZ_ASSERT(mConfig.mCodecSpecific.as<VP9Specific>().mNumTemporalLayers ==
                 svc.mNumberTemporalLayers);
    }

    // Form an SVC setting string for libvpx.
    name = "ts-parameters"_ns;
    parameters.Append("ts_target_bitrate=");
    for (size_t i = 0; i < svc.mTargetBitrates.Length(); ++i) {
      if (i > 0) {
        parameters.Append(",");
      }
      parameters.AppendPrintf("%d", svc.mTargetBitrates[i]);
    }
    parameters.AppendPrintf(
        ":ts_layering_mode=%u",
        svc.mCodecAppendix->as<VPXSVCAppendix>().mLayeringMode);
  }

  if (codecType == CodecType::AV1) {
    // Form an SVC setting string for libaom.
    name = "svc-parameters"_ns;
    parameters.AppendPrintf("number_spatial_layers=%zu",
                            svc.mNumberSpatialLayers);
    parameters.AppendPrintf(":number_temporal_layers=%zu",
                            svc.mNumberTemporalLayers);
    parameters.Append(":framerate_factor=");
    for (size_t i = 0; i < svc.mRateDecimators.Length(); ++i) {
      if (i > 0) {
        parameters.Append(",");
      }
      parameters.AppendPrintf("%d", svc.mRateDecimators[i]);
    }
    parameters.Append(":layer_target_bitrate=");
    for (size_t i = 0; i < svc.mTargetBitrates.Length(); ++i) {
      if (i > 0) {
        parameters.Append(",");
      }
      parameters.AppendPrintf("%d", svc.mTargetBitrates[i]);
    }
  }

  return Some(
      SVCSettings{std::move(svc.mLayerIds),
                  std::make_pair(std::move(name), std::move(parameters))});
}

FFmpegVideoEncoder<LIBAV_VER>::H264Settings FFmpegVideoEncoder<
    LIBAV_VER>::GetH264Settings(const H264Specific& aH264Specific) {
  nsTArray<std::pair<nsCString, nsCString>> keyValuePairs;

  Maybe<H264Setting> profile = GetH264Profile(aH264Specific.mProfile);
  MOZ_RELEASE_ASSERT(profile.isSome());
  MOZ_RELEASE_ASSERT(!profile->mString.IsEmpty() ||
                     aH264Specific.mProfile ==
                         H264_PROFILE::H264_PROFILE_EXTENDED);

  Maybe<H264Setting> level = GetH264Level(aH264Specific.mLevel);
  MOZ_RELEASE_ASSERT(level.isSome());
  MOZ_RELEASE_ASSERT(!level->mString.IsEmpty());

  if (!profile->mString.IsEmpty()) {
    keyValuePairs.AppendElement(std::make_pair("profile"_ns, profile->mString));
  }
  keyValuePairs.AppendElement(std::make_pair("level"_ns, level->mString));

  if (mCodecName.Equals("libx264"_ns)) {
    // Set format: libx264's default format is annexb.
    if (aH264Specific.mFormat == H264BitStreamFormat::AVC) {
      keyValuePairs.AppendElement(std::make_pair("x264-params"_ns, "annexb=0"));
      // mCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER
      // if we don't want to append SPS/PPS data in all keyframe
      // (LIBAVCODEC_VERSION_MAJOR >= 57 only).
    } else {
      // Set annexb explicitly even if it's default format.
      keyValuePairs.AppendElement(std::make_pair("x264-params"_ns, "annexb=1"));
    }
  }

  return H264Settings{.mProfile = profile->mValue,
                      .mLevel = level->mValue,
                      .mSettingKeyValuePairs = std::move(keyValuePairs)};
}

}  // namespace mozilla

Messung V0.5 in Prozent
C=92 H=95 G=93

¤ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002