tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AudioEncoder.cpp (17502B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/AudioEncoder.h"
      8 
      9 #include "EncoderConfig.h"
     10 #include "EncoderTraits.h"
     11 #include "EncoderTypes.h"
     12 #include "MediaData.h"
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/Logging.h"
     15 #include "mozilla/Maybe.h"
     16 #include "mozilla/dom/AudioDataBinding.h"
     17 #include "mozilla/dom/AudioEncoderBinding.h"
     18 #include "mozilla/dom/EncodedAudioChunk.h"
     19 #include "mozilla/dom/EncodedAudioChunkBinding.h"
     20 #include "mozilla/dom/Promise.h"
     21 #include "mozilla/dom/WebCodecsUtils.h"
     22 
     23 extern mozilla::LazyLogModule gWebCodecsLog;
     24 
     25 namespace mozilla::dom {
     26 
     27 #ifdef LOG_INTERNAL
     28 #  undef LOG_INTERNAL
     29 #endif  // LOG_INTERNAL
     30 #define LOG_INTERNAL(level, msg, ...) \
     31  MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__))
     32 
     33 #ifdef LOG
     34 #  undef LOG
     35 #endif  // LOG
     36 #define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__)
     37 
     38 #ifdef LOGW
     39 #  undef LOGW
     40 #endif  // LOGW
     41 #define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__)
     42 
     43 #ifdef LOGE
     44 #  undef LOGE
     45 #endif  // LOGE
     46 #define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__)
     47 
     48 #ifdef LOGV
     49 #  undef LOGV
     50 #endif  // LOGV
     51 #define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__)
     52 
     53 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioEncoder, DOMEventTargetHelper,
     54                                   mErrorCallback, mOutputCallback)
     55 NS_IMPL_ADDREF_INHERITED(AudioEncoder, DOMEventTargetHelper)
     56 NS_IMPL_RELEASE_INHERITED(AudioEncoder, DOMEventTargetHelper)
     57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioEncoder)
     58 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
     59 
     60 /*
     61 * Below are helper classes
     62 */
     63 AudioEncoderConfigInternal::AudioEncoderConfigInternal(
     64    const nsAString& aCodec, Maybe<uint32_t> aSampleRate,
     65    Maybe<uint32_t> aNumberOfChannels, Maybe<uint32_t> aBitrate,
     66    BitrateMode aBitrateMode)
     67    : mCodec(aCodec),
     68      mSampleRate(aSampleRate),
     69      mNumberOfChannels(aNumberOfChannels),
     70      mBitrate(aBitrate),
     71      mBitrateMode(aBitrateMode) {}
     72 
     73 AudioEncoderConfigInternal::AudioEncoderConfigInternal(
     74    const AudioEncoderConfig& aConfig)
     75    : AudioEncoderConfigInternal(
     76          aConfig.mCodec, OptionalToMaybe(aConfig.mSampleRate),
     77          OptionalToMaybe(aConfig.mNumberOfChannels),
     78          OptionalToMaybe(aConfig.mBitrate), aConfig.mBitrateMode) {
     79  DebugOnly<nsCString> errorMessage;
     80  if (aConfig.mCodec.EqualsLiteral("opus") && aConfig.mOpus.WasPassed()) {
     81    // All values are in range at this point, the config is known valid.
     82    OpusSpecific specific;
     83    if (aConfig.mOpus.Value().mComplexity.WasPassed()) {
     84      specific.mComplexity = aConfig.mOpus.Value().mComplexity.Value();
     85    } else {
     86      // https://w3c.github.io/webcodecs/opus_codec_registration.html#dom-opusencoderconfig-complexity
     87      // If no value is specificied, the default value is platform-specific:
     88      // User Agents SHOULD set a default of 5 for mobile platforms, and a
     89      // default of 9 for all other platforms.
     90      if (IsOnAndroid()) {
     91        specific.mComplexity = 5;
     92      } else {
     93        specific.mComplexity = 9;
     94      }
     95    }
     96    specific.mApplication = OpusSpecific::Application::Unspecified;
     97    specific.mFrameDuration = aConfig.mOpus.Value().mFrameDuration;
     98    specific.mPacketLossPerc = aConfig.mOpus.Value().mPacketlossperc;
     99    specific.mUseDTX = aConfig.mOpus.Value().mUsedtx;
    100    specific.mUseInBandFEC = aConfig.mOpus.Value().mUseinbandfec;
    101    mSpecific = AsVariant(specific);
    102  }
    103  MOZ_ASSERT(AudioEncoderTraits::Validate(aConfig, errorMessage));
    104 }
    105 
    106 AudioEncoderConfigInternal::AudioEncoderConfigInternal(
    107    const AudioEncoderConfigInternal& aConfig)
    108    : AudioEncoderConfigInternal(aConfig.mCodec, aConfig.mSampleRate,
    109                                 aConfig.mNumberOfChannels, aConfig.mBitrate,
    110                                 aConfig.mBitrateMode) {}
    111 
    112 void AudioEncoderConfigInternal::SetSpecific(
    113    const EncoderConfig::CodecSpecific& aSpecific) {
    114  mSpecific = aSpecific;
    115 }
    116 
    117 /*
    118 * The followings are helpers for AudioEncoder methods
    119 */
    120 
    121 static void CloneConfiguration(RootedDictionary<AudioEncoderConfig>& aDest,
    122                               JSContext* aCx,
    123                               const AudioEncoderConfig& aConfig) {
    124  aDest.mCodec = aConfig.mCodec;
    125 
    126  if (aConfig.mNumberOfChannels.WasPassed()) {
    127    aDest.mNumberOfChannels.Construct(aConfig.mNumberOfChannels.Value());
    128  }
    129  if (aConfig.mSampleRate.WasPassed()) {
    130    aDest.mSampleRate.Construct(aConfig.mSampleRate.Value());
    131  }
    132  if (aConfig.mBitrate.WasPassed()) {
    133    aDest.mBitrate.Construct(aConfig.mBitrate.Value());
    134  }
    135  if (aConfig.mOpus.WasPassed()) {
    136    aDest.mOpus.Construct(aConfig.mOpus.Value());
    137    // Handle the default value manually since it's different on mobile
    138    if (!aConfig.mOpus.Value().mComplexity.WasPassed()) {
    139      if (IsOnAndroid()) {
    140        aDest.mOpus.Value().mComplexity.Construct(5);
    141      } else {
    142        aDest.mOpus.Value().mComplexity.Construct(9);
    143      }
    144    }
    145  }
    146  aDest.mBitrateMode = aConfig.mBitrateMode;
    147 }
    148 
    149 static bool IsAudioEncodeSupported(const nsAString& aCodec) {
    150  LOG("IsEncodeSupported: %s", NS_ConvertUTF16toUTF8(aCodec).get());
    151 
    152  return aCodec.EqualsLiteral("opus") || aCodec.EqualsLiteral("vorbis");
    153 }
    154 
    155 static bool CanEncode(const RefPtr<AudioEncoderConfigInternal>& aConfig,
    156                      nsCString& aErrorMessage) {
    157  // TODO: Enable WebCodecs on Android (Bug 1840508)
    158  if (IsOnAndroid()) {
    159    return false;
    160  }
    161  if (!IsAudioEncodeSupported(aConfig->mCodec)) {
    162    return false;
    163  }
    164  if (!IsSupportedAudioCodec(aConfig->mCodec)) {
    165    aErrorMessage.AppendPrintf("%s is not supported",
    166                               NS_ConvertUTF16toUTF8(aConfig->mCodec).get());
    167    return false;
    168  }
    169 
    170  if (aConfig->mNumberOfChannels.value() > 256) {
    171    aErrorMessage.AppendPrintf(
    172        "Invalid number of channels, supported range is between 1 and 256");
    173    return false;
    174  }
    175 
    176  // Somewhat arbitrarily chosen, but reflects real-life and what the rest of
    177  // Gecko does.
    178  if (aConfig->mSampleRate.value() < 3000 ||
    179      aConfig->mSampleRate.value() > 384000) {
    180    aErrorMessage.AppendPrintf(
    181        "Invalid sample-rate of %d, supported range is 3000Hz to 384000Hz",
    182        aConfig->mSampleRate.value());
    183    return false;
    184  }
    185 
    186  return EncoderSupport::Supports(aConfig);
    187 }
    188 
    189 nsCString AudioEncoderConfigInternal::ToString() const {
    190  nsCString rv;
    191 
    192  rv.AppendPrintf("AudioEncoderConfigInternal: %s",
    193                  NS_ConvertUTF16toUTF8(mCodec).get());
    194  if (mSampleRate) {
    195    rv.AppendPrintf(" %" PRIu32 "Hz", mSampleRate.value());
    196  }
    197  if (mNumberOfChannels) {
    198    rv.AppendPrintf(" %" PRIu32 "ch", mNumberOfChannels.value());
    199  }
    200  if (mBitrate) {
    201    rv.AppendPrintf(" %" PRIu32 "bps", mBitrate.value());
    202  }
    203  rv.AppendPrintf(" (%s)", mBitrateMode == mozilla::dom::BitrateMode::Constant
    204                               ? "CRB"
    205                               : "VBR");
    206 
    207  return rv;
    208 }
    209 
    210 EncoderConfig AudioEncoderConfigInternal::ToEncoderConfig() const {
    211  const mozilla::BitrateMode bitrateMode =
    212      mBitrateMode == mozilla::dom::BitrateMode::Constant
    213          ? mozilla::BitrateMode::Constant
    214          : mozilla::BitrateMode::Variable;
    215 
    216  CodecType type = CodecType::Opus;
    217  EncoderConfig::CodecSpecific specific{void_t{}};
    218  if (mCodec.EqualsLiteral("opus")) {
    219    type = CodecType::Opus;
    220    MOZ_ASSERT(mSpecific.is<void_t>() || mSpecific.is<OpusSpecific>());
    221    specific = mSpecific;
    222  } else if (mCodec.EqualsLiteral("vorbis")) {
    223    type = CodecType::Vorbis;
    224  } else if (mCodec.EqualsLiteral("flac")) {
    225    type = CodecType::Flac;
    226  } else if (StringBeginsWith(mCodec, u"pcm-"_ns)) {
    227    type = CodecType::PCM;
    228  } else if (mCodec.EqualsLiteral("ulaw")) {
    229    type = CodecType::PCM;
    230  } else if (mCodec.EqualsLiteral("alaw")) {
    231    type = CodecType::PCM;
    232  } else if (StringBeginsWith(mCodec, u"mp4a."_ns)) {
    233    type = CodecType::AAC;
    234  }
    235 
    236  // This should have been checked ahead of time -- we can't encode without
    237  // knowing the sample-rate or the channel count at the very least.
    238  MOZ_ASSERT(mSampleRate.value());
    239  MOZ_ASSERT(mNumberOfChannels.value());
    240 
    241  return EncoderConfig(type, mNumberOfChannels.value(), bitrateMode,
    242                       AssertedCast<uint32_t>(mSampleRate.value()),
    243                       mBitrate.valueOr(0), specific);
    244 }
    245 
    246 bool AudioEncoderConfigInternal::Equals(
    247    const AudioEncoderConfigInternal& aOther) const {
    248  return false;
    249 }
    250 
    251 bool AudioEncoderConfigInternal::CanReconfigure(
    252    const AudioEncoderConfigInternal& aOther) const {
    253  return false;
    254 }
    255 
    256 already_AddRefed<WebCodecsConfigurationChangeList>
    257 AudioEncoderConfigInternal::Diff(
    258    const AudioEncoderConfigInternal& aOther) const {
    259  return MakeRefPtr<WebCodecsConfigurationChangeList>().forget();
    260 }
    261 
    262 /* static */
    263 bool AudioEncoderTraits::IsSupported(
    264    const AudioEncoderConfigInternal& aConfig) {
    265  nsCString errorMessage;
    266  bool canEncode =
    267      CanEncode(MakeRefPtr<AudioEncoderConfigInternal>(aConfig), errorMessage);
    268  if (!canEncode) {
    269    LOGE("Can't encode configuration %s: %s", aConfig.ToString().get(),
    270         errorMessage.get());
    271  }
    272  return canEncode;
    273 }
    274 
    275 // https://w3c.github.io/webcodecs/#valid-audioencoderconfig
    276 /* static */
    277 bool AudioEncoderTraits::Validate(const AudioEncoderConfig& aConfig,
    278                                  nsCString& aErrorMessage) {
    279  Maybe<nsString> codec = ParseCodecString(aConfig.mCodec);
    280  if (!codec || codec->IsEmpty()) {
    281    LOGE("Validating AudioEncoderConfig: invalid codec string");
    282    return false;
    283  }
    284 
    285  if (!aConfig.mNumberOfChannels.WasPassed()) {
    286    aErrorMessage.AppendPrintf("Channel count required");
    287    return false;
    288  }
    289  if (aConfig.mNumberOfChannels.Value() == 0) {
    290    aErrorMessage.AppendPrintf(
    291        "Invalid number of channels, supported range is between 1 and 256");
    292    return false;
    293  }
    294  if (!aConfig.mSampleRate.WasPassed()) {
    295    aErrorMessage.AppendPrintf("Sample-rate required");
    296    return false;
    297  }
    298  if (aConfig.mSampleRate.Value() == 0) {
    299    aErrorMessage.AppendPrintf("Invalid sample-rate of 0");
    300    return false;
    301  }
    302 
    303  if (aConfig.mBitrate.WasPassed() &&
    304      aConfig.mBitrate.Value() > std::numeric_limits<int>::max()) {
    305    aErrorMessage.AppendPrintf("Invalid config: bitrate value too large");
    306    return false;
    307  }
    308 
    309  // https://github.com/w3c/webcodecs/issues/816
    310  if ((aConfig.mBitrate.WasPassed() && aConfig.mBitrate.Value() == 0)) {
    311    aErrorMessage.AssignLiteral(
    312        "Invalid AudioEncoderConfig: bitrate equal to 0");
    313    LOGE("%s", aErrorMessage.get());
    314    return false;
    315  }
    316 
    317  if (codec->EqualsLiteral("opus")) {
    318    // This comes from
    319    // https://w3c.github.io/webcodecs/opus_codec_registration.html#opus-encoder-config
    320    if (aConfig.mBitrate.WasPassed() && (aConfig.mBitrate.Value() < 6000 ||
    321                                         aConfig.mBitrate.Value() > 510000)) {
    322      aErrorMessage.AppendPrintf(
    323          "Invalid config: bitrate value outside of [6k, 510k] for opus");
    324      return false;
    325    }
    326    if (aConfig.mOpus.WasPassed()) {
    327      // Verify value ranges
    328      const std::array validFrameDurationUs = {2500,  5000,  10000,
    329                                               20000, 40000, 60000};
    330      if (std::find(validFrameDurationUs.begin(), validFrameDurationUs.end(),
    331                    aConfig.mOpus.Value().mFrameDuration) ==
    332          validFrameDurationUs.end()) {
    333        aErrorMessage.AppendPrintf("Invalid config: invalid frame duration");
    334        return false;
    335      }
    336      if (aConfig.mOpus.Value().mComplexity.WasPassed() &&
    337          aConfig.mOpus.Value().mComplexity.Value() > 10) {
    338        aErrorMessage.AppendPrintf(
    339            "Invalid config: Opus complexity greater than 10");
    340        return false;
    341      }
    342      if (aConfig.mOpus.Value().mPacketlossperc > 100) {
    343        aErrorMessage.AppendPrintf(
    344            "Invalid config: Opus packet loss percentage greater than 100");
    345        return false;
    346      }
    347    }
    348  }
    349 
    350  return true;
    351 }
    352 
    353 /* static */
    354 RefPtr<AudioEncoderConfigInternal> AudioEncoderTraits::CreateConfigInternal(
    355    const AudioEncoderConfig& aConfig) {
    356  nsCString errorMessage;
    357  if (!AudioEncoderTraits::Validate(aConfig, errorMessage)) {
    358    return nullptr;
    359  }
    360  return MakeRefPtr<AudioEncoderConfigInternal>(aConfig);
    361 }
    362 
    363 /* static */
    364 RefPtr<mozilla::AudioData> AudioEncoderTraits::CreateInputInternal(
    365    const dom::AudioData& aInput,
    366    const dom::VideoEncoderEncodeOptions& /* unused */) {
    367  return aInput.ToAudioData();
    368 }
    369 
    370 /*
    371 * Below are AudioEncoder implementation
    372 */
    373 
    374 AudioEncoder::AudioEncoder(
    375    nsIGlobalObject* aParent, RefPtr<WebCodecsErrorCallback>&& aErrorCallback,
    376    RefPtr<EncodedAudioChunkOutputCallback>&& aOutputCallback)
    377    : EncoderTemplate(aParent, std::move(aErrorCallback),
    378                      std::move(aOutputCallback)) {
    379  MOZ_ASSERT(mErrorCallback);
    380  MOZ_ASSERT(mOutputCallback);
    381  LOG("AudioEncoder %p ctor", this);
    382 }
    383 
    384 AudioEncoder::~AudioEncoder() {
    385  LOG("AudioEncoder %p dtor", this);
    386  (void)ResetInternal(NS_ERROR_DOM_ABORT_ERR);
    387 }
    388 
    389 JSObject* AudioEncoder::WrapObject(JSContext* aCx,
    390                                   JS::Handle<JSObject*> aGivenProto) {
    391  AssertIsOnOwningThread();
    392 
    393  return AudioEncoder_Binding::Wrap(aCx, this, aGivenProto);
    394 }
    395 
    396 // https://w3c.github.io/webcodecs/#dom-audioencoder-audioencoder
    397 /* static */
    398 already_AddRefed<AudioEncoder> AudioEncoder::Constructor(
    399    const GlobalObject& aGlobal, const AudioEncoderInit& aInit,
    400    ErrorResult& aRv) {
    401  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    402  if (!global) {
    403    aRv.Throw(NS_ERROR_FAILURE);
    404    return nullptr;
    405  }
    406 
    407  return MakeAndAddRef<AudioEncoder>(
    408      global.get(), RefPtr<WebCodecsErrorCallback>(aInit.mError),
    409      RefPtr<EncodedAudioChunkOutputCallback>(aInit.mOutput));
    410 }
    411 
    412 // https://w3c.github.io/webcodecs/#dom-audioencoder-isconfigsupported
    413 /* static */
    414 already_AddRefed<Promise> AudioEncoder::IsConfigSupported(
    415    const GlobalObject& aGlobal, const AudioEncoderConfig& aConfig,
    416    ErrorResult& aRv) {
    417  LOG("AudioEncoder::IsConfigSupported, config: %s",
    418      NS_ConvertUTF16toUTF8(aConfig.mCodec).get());
    419 
    420  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    421  if (!global) {
    422    aRv.Throw(NS_ERROR_FAILURE);
    423    return nullptr;
    424  }
    425 
    426  RefPtr<Promise> p = Promise::Create(global.get(), aRv);
    427  if (NS_WARN_IF(aRv.Failed())) {
    428    return p.forget();
    429  }
    430 
    431  nsCString errorMessage;
    432  if (!AudioEncoderTraits::Validate(aConfig, errorMessage)) {
    433    p->MaybeRejectWithTypeError(errorMessage);
    434    return p.forget();
    435  }
    436 
    437  // TODO: Move the following works to another thread to unblock the current
    438  // thread, as what spec suggests.
    439 
    440  RootedDictionary<AudioEncoderConfig> config(aGlobal.Context());
    441  CloneConfiguration(config, aGlobal.Context(), aConfig);
    442 
    443  auto configInternal = MakeRefPtr<AudioEncoderConfigInternal>(aConfig);
    444  bool canEncode = CanEncode(configInternal, errorMessage);
    445  if (!canEncode) {
    446    LOG("CanEncode failed: %s", errorMessage.get());
    447  }
    448  RootedDictionary<AudioEncoderSupport> s(aGlobal.Context());
    449  s.mConfig.Construct(std::move(config));
    450  s.mSupported.Construct(canEncode);
    451 
    452  p->MaybeResolve(s);
    453  return p.forget();
    454 }
    455 
    456 RefPtr<EncodedAudioChunk> AudioEncoder::EncodedDataToOutputType(
    457    nsIGlobalObject* aGlobalObject, const RefPtr<MediaRawData>& aData) {
    458  AssertIsOnOwningThread();
    459 
    460  // Package into an EncodedAudioChunk
    461  auto buffer =
    462      MakeRefPtr<MediaAlignedByteBuffer>(aData->Data(), aData->Size());
    463  auto encoded = MakeRefPtr<EncodedAudioChunk>(
    464      aGlobalObject, buffer.forget(), EncodedAudioChunkType::Key,
    465      aData->mTime.ToMicroseconds(),
    466      aData->mDuration.IsZero() ? Nothing()
    467                                : Some(aData->mDuration.ToMicroseconds()));
    468  return encoded;
    469 }
    470 
    471 void AudioEncoder::EncoderConfigToDecoderConfig(
    472    JSContext* aCx, const RefPtr<MediaRawData>& aRawData,
    473    const AudioEncoderConfigInternal& aSrcConfig,
    474    AudioDecoderConfig& aDestConfig) const {
    475  MOZ_ASSERT(aCx);
    476  MOZ_ASSERT(aSrcConfig.mSampleRate.isSome());
    477  MOZ_ASSERT(aSrcConfig.mNumberOfChannels.isSome());
    478 
    479  uint32_t sampleRate = aSrcConfig.mSampleRate.value();
    480  uint32_t channelCount = aSrcConfig.mNumberOfChannels.value();
    481  // Check if the encoder had to modify the settings because of codec
    482  // constraints. e.g. FFmpegAudioEncoder can encode any sample-rate, but if the
    483  // codec is Opus, then it will resample the audio one of the specific rates
    484  // supported by the encoder.
    485  if (aRawData->mConfig) {
    486    sampleRate = aRawData->mConfig->mSampleRate;
    487    channelCount = aRawData->mConfig->mNumberOfChannels;
    488  }
    489 
    490  aDestConfig.mCodec = aSrcConfig.mCodec;
    491  aDestConfig.mNumberOfChannels = channelCount;
    492  aDestConfig.mSampleRate = sampleRate;
    493 
    494  if (aRawData->mExtraData && !aRawData->mExtraData->IsEmpty()) {
    495    Span<const uint8_t> description(aRawData->mExtraData->Elements(),
    496                                    aRawData->mExtraData->Length());
    497    if (!CopyExtradataToDescription(aCx, description,
    498                                    aDestConfig.mDescription.Construct())) {
    499      LOGE("Failed to copy extra data");
    500    }
    501  }
    502 }
    503 
    504 #undef LOG
    505 #undef LOGW
    506 #undef LOGE
    507 #undef LOGV
    508 #undef LOG_INTERNAL
    509 
    510 }  // namespace mozilla::dom