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


Quelle  MediaEncoder.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* 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 "MediaEncoder.h"

#include <algorithm>
#include "AudioNodeEngine.h"
#include "AudioNodeTrack.h"
#include "DriftCompensation.h"
#include "MediaDecoder.h"
#include "MediaTrackGraph.h"
#include "MediaTrackListener.h"
#include "mozilla/dom/AudioNode.h"
#include "mozilla/dom/AudioStreamTrack.h"
#include "mozilla/dom/Blob.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/MediaStreamTrack.h"
#include "mozilla/dom/MutableBlobStorage.h"
#include "mozilla/dom/VideoStreamTrack.h"
#include "mozilla/gfx/Point.h"  // IntSize
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TaskQueue.h"
#include "mozilla/Unused.h"
#include "Muxer.h"
#include "nsMimeTypes.h"
#include "nsThreadUtils.h"
#include "OggWriter.h"
#include "OpusTrackEncoder.h"
#include "TimeUnits.h"
#include "Tracing.h"

#include "VP8TrackEncoder.h"
#include "WebMWriter.h"

mozilla::LazyLogModule gMediaEncoderLog("MediaEncoder");
#define LOG(type, msg) MOZ_LOG(gMediaEncoderLog, type, msg)

namespace mozilla {

using namespace dom;
using namespace media;

namespace {
class BlobStorer : public MutableBlobStorageCallback {
  MozPromiseHolder<MediaEncoder::BlobPromise> mHolder;

  virtual ~BlobStorer() = default;

 public:
  BlobStorer() = default;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlobStorer, override)

  void BlobStoreCompleted(MutableBlobStorage*, BlobImpl* aBlobImpl,
                          nsresult aRv) override {
    MOZ_ASSERT(NS_IsMainThread());
    if (NS_FAILED(aRv)) {
      mHolder.Reject(aRv, __func__);
      return;
    }

    mHolder.Resolve(aBlobImpl, __func__);
  }

  RefPtr<MediaEncoder::BlobPromise> Promise() {
    return mHolder.Ensure(__func__);
  }
};
}  // namespace

class MediaEncoder::AudioTrackListener : public DirectMediaTrackListener {
 public:
  AudioTrackListener(RefPtr<DriftCompensator> aDriftCompensator,
                     RefPtr<MediaEncoder> aMediaEncoder)
      : mDirectConnected(false),
        mInitialized(false),
        mRemoved(false),
        mDriftCompensator(std::move(aDriftCompensator)),
        mMediaEncoder(std::move(aMediaEncoder)),
        mEncoderThread(mMediaEncoder->mEncoderThread),
        mShutdownPromise(mShutdownHolder.Ensure(__func__)) {
    MOZ_ASSERT(mMediaEncoder);
    MOZ_ASSERT(mMediaEncoder->mAudioEncoder);
    MOZ_ASSERT(mEncoderThread);
  }

  void NotifyDirectListenerInstalled(InstallationResult aResult) override {
    if (aResult == InstallationResult::SUCCESS) {
      LOG(LogLevel::Info, ("Audio track direct listener installed"));
      mDirectConnected = true;
    } else {
      LOG(LogLevel::Info, ("Audio track failed to install direct listener"));
      MOZ_ASSERT(!mDirectConnected);
    }
  }

  void NotifyDirectListenerUninstalled() override {
    mDirectConnected = false;

    if (mRemoved) {
      mMediaEncoder = nullptr;
      mEncoderThread = nullptr;
    }
  }

  void NotifyQueuedChanges(MediaTrackGraph* aGraph, TrackTime aTrackOffset,
                           const MediaSegment& aQueuedMedia) override {
    TRACE_COMMENT("MediaEncoder::NotifyQueuedChanges""%p",
                  mMediaEncoder.get());
    MOZ_ASSERT(mMediaEncoder);
    MOZ_ASSERT(mEncoderThread);

    if (!mInitialized) {
      mDriftCompensator->NotifyAudioStart(TimeStamp::Now());
      mInitialized = true;
    }

    mDriftCompensator->NotifyAudio(aQueuedMedia.GetDuration());

    const AudioSegment& audio = static_cast<const AudioSegment&>(aQueuedMedia);

    AudioSegment copy;
    copy.AppendSlice(audio, 0, audio.GetDuration());

    nsresult rv = mEncoderThread->Dispatch(NS_NewRunnableFunction(
        "mozilla::AudioTrackEncoder::AppendAudioSegment",
        [encoder = mMediaEncoder, copy = std::move(copy)]() mutable {
          encoder->mAudioEncoder->AppendAudioSegment(std::move(copy));
        }));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
  }

  void NotifyEnded(MediaTrackGraph* aGraph) override {
    MOZ_ASSERT(mMediaEncoder);
    MOZ_ASSERT(mMediaEncoder->mAudioEncoder);
    MOZ_ASSERT(mEncoderThread);

    nsresult rv = mEncoderThread->Dispatch(
        NS_NewRunnableFunction("mozilla::AudioTrackEncoder::NotifyEndOfStream",
                               [encoder = mMediaEncoder] {
                                 encoder->mAudioEncoder->NotifyEndOfStream();
                               }));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
  }

  void NotifyRemoved(MediaTrackGraph* aGraph) override {
    nsresult rv = mEncoderThread->Dispatch(
        NS_NewRunnableFunction("mozilla::AudioTrackEncoder::NotifyEndOfStream",
                               [encoder = mMediaEncoder] {
                                 encoder->mAudioEncoder->NotifyEndOfStream();
                               }));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;

    mRemoved = true;

    if (!mDirectConnected) {
      mMediaEncoder = nullptr;
      mEncoderThread = nullptr;
    }

    mShutdownHolder.Resolve(true, __func__);
  }

  const RefPtr<GenericNonExclusivePromise>& OnShutdown() const {
    return mShutdownPromise;
  }

 private:
  bool mDirectConnected;
  bool mInitialized;
  bool mRemoved;
  const RefPtr<DriftCompensator> mDriftCompensator;
  RefPtr<MediaEncoder> mMediaEncoder;
  RefPtr<TaskQueue> mEncoderThread;
  MozPromiseHolder<GenericNonExclusivePromise> mShutdownHolder;
  const RefPtr<GenericNonExclusivePromise> mShutdownPromise;
};

class MediaEncoder::VideoTrackListener : public DirectMediaTrackListener {
 public:
  explicit VideoTrackListener(RefPtr<MediaEncoder> aMediaEncoder)
      : mDirectConnected(false),
        mInitialized(false),
        mRemoved(false),
        mPendingAdvanceCurrentTime(false),
        mMediaEncoder(std::move(aMediaEncoder)),
        mEncoderThread(mMediaEncoder->mEncoderThread),
        mShutdownPromise(mShutdownHolder.Ensure(__func__)) {
    MOZ_ASSERT(mMediaEncoder);
    MOZ_ASSERT(mEncoderThread);
  }

  void NotifyDirectListenerInstalled(InstallationResult aResult) override {
    if (aResult == InstallationResult::SUCCESS) {
      LOG(LogLevel::Info, ("Video track direct listener installed"));
      mDirectConnected = true;
    } else {
      LOG(LogLevel::Info, ("Video track failed to install direct listener"));
      MOZ_ASSERT(!mDirectConnected);
      return;
    }
  }

  void NotifyDirectListenerUninstalled() override {
    mDirectConnected = false;

    if (mRemoved) {
      mMediaEncoder = nullptr;
      mEncoderThread = nullptr;
    }
  }

  void NotifyQueuedChanges(MediaTrackGraph* aGraph, TrackTime aTrackOffset,
                           const MediaSegment& aQueuedMedia) override {
    TRACE_COMMENT("MediaEncoder::NotifyQueuedChanges""%p",
                  mMediaEncoder.get());
    MOZ_ASSERT(mMediaEncoder);
    MOZ_ASSERT(mMediaEncoder->mVideoEncoder);
    MOZ_ASSERT(mEncoderThread);

    mCurrentTime = TimeStamp::Now();
    if (!mInitialized) {
      nsresult rv = mEncoderThread->Dispatch(
          NS_NewRunnableFunction("mozilla::VideoTrackEncoder::SetStartOffset",
                                 [encoder = mMediaEncoder, now = mCurrentTime] {
                                   encoder->mVideoEncoder->SetStartOffset(now);
                                 }));
      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
      Unused << rv;
      mInitialized = true;
    }

    if (!mPendingAdvanceCurrentTime) {
      mPendingAdvanceCurrentTime = true;
      nsresult rv = mEncoderThread->Dispatch(NS_NewRunnableFunction(
          "mozilla::VideoTrackEncoder::AdvanceCurrentTime",
          [encoder = mMediaEncoder, now = mCurrentTime] {
            encoder->mVideoListener->mPendingAdvanceCurrentTime = false;
            encoder->mVideoEncoder->AdvanceCurrentTime(now);
          }));
      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
      Unused << rv;
    }
  }

  void NotifyRealtimeTrackData(MediaTrackGraph* aGraph, TrackTime aTrackOffset,
                               const MediaSegment& aMedia) override {
    TRACE_COMMENT("MediaEncoder::NotifyRealtimeTrackData""%p",
                  mMediaEncoder.get());
    MOZ_ASSERT(mMediaEncoder);
    MOZ_ASSERT(mMediaEncoder->mVideoEncoder);
    MOZ_ASSERT(mEncoderThread);
    MOZ_ASSERT(aMedia.GetType() == MediaSegment::VIDEO);

    const VideoSegment& video = static_cast<const VideoSegment&>(aMedia);
    VideoSegment copy;
    for (VideoSegment::ConstChunkIterator iter(video); !iter.IsEnded();
         iter.Next()) {
      copy.AppendFrame(do_AddRef(iter->mFrame.GetImage()),
                       iter->mFrame.GetIntrinsicSize(),
                       iter->mFrame.GetPrincipalHandle(),
                       iter->mFrame.GetForceBlack(), iter->mTimeStamp);
    }

    nsresult rv = mEncoderThread->Dispatch(NS_NewRunnableFunction(
        "mozilla::VideoTrackEncoder::AppendVideoSegment",
        [encoder = mMediaEncoder, copy = std::move(copy)]() mutable {
          encoder->mVideoEncoder->AppendVideoSegment(std::move(copy));
        }));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
  }

  void NotifyEnabledStateChanged(MediaTrackGraph* aGraph,
                                 bool aEnabled) override {
    MOZ_ASSERT(mMediaEncoder);
    MOZ_ASSERT(mMediaEncoder->mVideoEncoder);
    MOZ_ASSERT(mEncoderThread);

    nsresult rv;
    if (aEnabled) {
      rv = mEncoderThread->Dispatch(NS_NewRunnableFunction(
          "mozilla::VideoTrackEncoder::Enable",
          [encoder = mMediaEncoder, now = TimeStamp::Now()] {
            encoder->mVideoEncoder->Enable(now);
          }));
    } else {
      rv = mEncoderThread->Dispatch(NS_NewRunnableFunction(
          "mozilla::VideoTrackEncoder::Disable",
          [encoder = mMediaEncoder, now = TimeStamp::Now()] {
            encoder->mVideoEncoder->Disable(now);
          }));
    }
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
  }

  void NotifyEnded(MediaTrackGraph* aGraph) override {
    MOZ_ASSERT(mMediaEncoder);
    MOZ_ASSERT(mMediaEncoder->mVideoEncoder);
    MOZ_ASSERT(mEncoderThread);

    nsresult rv = mEncoderThread->Dispatch(NS_NewRunnableFunction(
        "mozilla::VideoTrackEncoder::NotifyEndOfStream",
        [encoder = mMediaEncoder, now = mCurrentTime] {
          if (!now.IsNull()) {
            encoder->mVideoEncoder->AdvanceCurrentTime(now);
          }
          encoder->mVideoEncoder->NotifyEndOfStream();
        }));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
  }

  void NotifyRemoved(MediaTrackGraph* aGraph) override {
    nsresult rv = mEncoderThread->Dispatch(NS_NewRunnableFunction(
        "mozilla::VideoTrackEncoder::NotifyEndOfStream",
        [encoder = mMediaEncoder, now = mCurrentTime] {
          if (!now.IsNull()) {
            encoder->mVideoEncoder->AdvanceCurrentTime(now);
          }
          encoder->mVideoEncoder->NotifyEndOfStream();
        }));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;

    mRemoved = true;

    if (!mDirectConnected) {
      mMediaEncoder = nullptr;
      mEncoderThread = nullptr;
    }

    mShutdownHolder.Resolve(true, __func__);
  }

  const RefPtr<GenericNonExclusivePromise>& OnShutdown() const {
    return mShutdownPromise;
  }

 private:
  bool mDirectConnected;
  bool mInitialized;
  bool mRemoved;
  TimeStamp mCurrentTime;
  Atomic<bool> mPendingAdvanceCurrentTime;
  RefPtr<MediaEncoder> mMediaEncoder;
  RefPtr<TaskQueue> mEncoderThread;
  MozPromiseHolder<GenericNonExclusivePromise> mShutdownHolder;
  const RefPtr<GenericNonExclusivePromise> mShutdownPromise;
};

class MediaEncoder::EncoderListener : public TrackEncoderListener {
 public:
  EncoderListener(TaskQueue* aEncoderThread, MediaEncoder* aEncoder)
      : mEncoderThread(aEncoderThread), mEncoder(aEncoder) {}

  void Forget() {
    MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
    mEncoder = nullptr;
  }

  void Initialized(TrackEncoder* aTrackEncoder) override {
    MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
    MOZ_ASSERT(aTrackEncoder->IsInitialized());

    if (!mEncoder) {
      return;
    }

    mEncoder->UpdateInitialized();
  }

  void Started(TrackEncoder* aTrackEncoder) override {
    MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
    MOZ_ASSERT(aTrackEncoder->IsStarted());

    if (!mEncoder) {
      return;
    }

    mEncoder->UpdateStarted();
  }

  void Error(TrackEncoder* aTrackEncoder) override {
    MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());

    if (!mEncoder) {
      return;
    }

    mEncoder->SetError();
  }

 protected:
  RefPtr<TaskQueue> mEncoderThread;
  RefPtr<MediaEncoder> mEncoder;
};

MediaEncoder::MediaEncoder(
    RefPtr<TaskQueue> aEncoderThread,
    RefPtr<DriftCompensator> aDriftCompensator,
    UniquePtr<ContainerWriter> aWriter,
    UniquePtr<AudioTrackEncoder> aAudioEncoder,
    UniquePtr<VideoTrackEncoder> aVideoEncoder,
    UniquePtr<MediaQueue<EncodedFrame>> aEncodedAudioQueue,
    UniquePtr<MediaQueue<EncodedFrame>> aEncodedVideoQueue,
    TrackRate aTrackRate, const nsAString& aMimeType, uint64_t aMaxMemory,
    TimeDuration aTimeslice)
    : mMainThread(GetMainThreadSerialEventTarget()),
      mEncoderThread(std::move(aEncoderThread)),
      mEncodedAudioQueue(std::move(aEncodedAudioQueue)),
      mEncodedVideoQueue(std::move(aEncodedVideoQueue)),
      mMuxer(MakeUnique<Muxer>(std::move(aWriter), *mEncodedAudioQueue,
                               *mEncodedVideoQueue)),
      mAudioEncoder(std::move(aAudioEncoder)),
      mAudioListener(mAudioEncoder ? MakeAndAddRef<AudioTrackListener>(
                                         std::move(aDriftCompensator), this)
                                   : nullptr),
      mVideoEncoder(std::move(aVideoEncoder)),
      mVideoListener(mVideoEncoder ? MakeAndAddRef<VideoTrackListener>(this)
                                   : nullptr),
      mEncoderListener(MakeAndAddRef<EncoderListener>(mEncoderThread, this)),
      mMimeType(aMimeType),
      mMaxMemory(aMaxMemory),
      mTimeslice(aTimeslice),
      mStartTime(TimeStamp::Now()),
      mInitialized(false),
      mStarted(false),
      mCompleted(false),
      mError(false) {
  if (!mAudioEncoder) {
    mMuxedAudioEndTime = TimeUnit::FromInfinity();
    mEncodedAudioQueue->Finish();
  }
  if (!mVideoEncoder) {
    mMuxedVideoEndTime = TimeUnit::FromInfinity();
    mEncodedVideoQueue->Finish();
  }
}

void MediaEncoder::RegisterListeners() {
  if (mAudioEncoder) {
    mAudioPushListener = mEncodedAudioQueue->PushEvent().Connect(
        mEncoderThread, this, &MediaEncoder::OnEncodedAudioPushed);
    mAudioFinishListener = mEncodedAudioQueue->FinishEvent().Connect(
        mEncoderThread, this, &MediaEncoder::MaybeShutdown);
    MOZ_ALWAYS_SUCCEEDS(mEncoderThread->Dispatch(NS_NewRunnableFunction(
        "mozilla::AudioTrackEncoder::RegisterListener",
        [self = RefPtr<MediaEncoder>(this), this] {
          mAudioEncoder->RegisterListener(mEncoderListener);
        })));
  }
  if (mVideoEncoder) {
    mVideoPushListener = mEncodedVideoQueue->PushEvent().Connect(
        mEncoderThread, this, &MediaEncoder::OnEncodedVideoPushed);
    mVideoFinishListener = mEncodedVideoQueue->FinishEvent().Connect(
        mEncoderThread, this, &MediaEncoder::MaybeShutdown);
    MOZ_ALWAYS_SUCCEEDS(mEncoderThread->Dispatch(NS_NewRunnableFunction(
        "mozilla::VideoTrackEncoder::RegisterListener",
        [self = RefPtr<MediaEncoder>(this), this] {
          mVideoEncoder->RegisterListener(mEncoderListener);
        })));
  }
}

MediaEncoder::~MediaEncoder() {
  MOZ_ASSERT(!mAudioTrack);
  MOZ_ASSERT(!mVideoTrack);
  MOZ_ASSERT(!mAudioNode);
  MOZ_ASSERT(!mInputPort);
  MOZ_ASSERT(!mPipeTrack);
}

void MediaEncoder::EnsureGraphTrackFrom(MediaTrack* aTrack) {
  if (mGraphTrack) {
    return;
  }
  MOZ_DIAGNOSTIC_ASSERT(!aTrack->IsDestroyed());
  mGraphTrack = MakeAndAddRef<SharedDummyTrack>(
      aTrack->Graph()->CreateSourceTrack(MediaSegment::VIDEO));
}

void MediaEncoder::Suspend() {
  mGraphTrack->mTrack->QueueControlMessageWithNoShutdown(
      [self = RefPtr<MediaEncoder>(this), this] {
        TRACE("MediaEncoder::Suspend (graph)");
        if (NS_FAILED(mEncoderThread->Dispatch(
                NS_NewRunnableFunction("MediaEncoder::Suspend (encoder)",
                                       [self, this, now = TimeStamp::Now()] {
                                         if (mAudioEncoder) {
                                           mAudioEncoder->Suspend();
                                         }
                                         if (mVideoEncoder) {
                                           mVideoEncoder->Suspend(now);
                                         }
                                       })))) {
          // QueueControlMessageWithNoShutdown added an extra async step, and
          // now `thread` has shut down.
          return;
        }
      });
}

void MediaEncoder::Resume() {
  mGraphTrack->mTrack->QueueControlMessageWithNoShutdown(
      [self = RefPtr<MediaEncoder>(this), this] {
        TRACE("MediaEncoder::Resume (graph)");
        if (NS_FAILED(mEncoderThread->Dispatch(
                NS_NewRunnableFunction("MediaEncoder::Resume (encoder)",
                                       [self, this, now = TimeStamp::Now()] {
                                         if (mAudioEncoder) {
                                           mAudioEncoder->Resume();
                                         }
                                         if (mVideoEncoder) {
                                           mVideoEncoder->Resume(now);
                                         }
                                       })))) {
          // QueueControlMessageWithNoShutdown added an extra async step, and
          // now `thread` has shut down.
          return;
        }
      });
}

void MediaEncoder::ConnectAudioNode(AudioNode* aNode, uint32_t aOutput) {
  MOZ_ASSERT(NS_IsMainThread());

  if (mAudioNode) {
    MOZ_ASSERT(false"Only one audio node supported");
    return;
  }

  // Only AudioNodeTrack of kind EXTERNAL_OUTPUT stores output audio data in
  // the track (see AudioNodeTrack::AdvanceOutputSegment()). That means
  // forwarding input track in recorder session won't be able to copy data from
  // the track of non-destination node. Create a pipe track in this case.
  if (aNode->NumberOfOutputs() > 0) {
    AudioContext* ctx = aNode->Context();
    AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
    AudioNodeTrack::Flags flags = AudioNodeTrack::EXTERNAL_OUTPUT |
                                  AudioNodeTrack::NEED_MAIN_THREAD_ENDED;
    mPipeTrack = AudioNodeTrack::Create(ctx, engine, flags, ctx->Graph());
    AudioNodeTrack* ns = aNode->GetTrack();
    if (ns) {
      mInputPort = mPipeTrack->AllocateInputPort(aNode->GetTrack(), 0, aOutput);
    }
  }

  mAudioNode = aNode;

  if (mPipeTrack) {
    mPipeTrack->AddListener(mAudioListener);
    EnsureGraphTrackFrom(mPipeTrack);
  } else {
    mAudioNode->GetTrack()->AddListener(mAudioListener);
    EnsureGraphTrackFrom(mAudioNode->GetTrack());
  }
}

void MediaEncoder::ConnectMediaStreamTrack(MediaStreamTrack* aTrack) {
  MOZ_ASSERT(NS_IsMainThread());

  if (aTrack->Ended()) {
    MOZ_ASSERT_UNREACHABLE("Cannot connect ended track");
    return;
  }

  EnsureGraphTrackFrom(aTrack->GetTrack());

  if (AudioStreamTrack* audio = aTrack->AsAudioStreamTrack()) {
    if (!mAudioEncoder) {
      // No audio encoder for this audio track. It could be disabled.
      LOG(LogLevel::Warning, ("Cannot connect to audio track - no encoder"));
      return;
    }

    MOZ_ASSERT(!mAudioTrack, "Only one audio track supported.");
    MOZ_ASSERT(mAudioListener, "No audio listener for this audio track");

    LOG(LogLevel::Info, ("Connected to audio track %p", aTrack));

    mAudioTrack = audio;
    audio->AddListener(mAudioListener);
  } else if (VideoStreamTrack* video = aTrack->AsVideoStreamTrack()) {
    if (!mVideoEncoder) {
      // No video encoder for this video track. It could be disabled.
      LOG(LogLevel::Warning, ("Cannot connect to video track - no encoder"));
      return;
    }

    MOZ_ASSERT(!mVideoTrack, "Only one video track supported.");
    MOZ_ASSERT(mVideoListener, "No video listener for this video track");

    LOG(LogLevel::Info, ("Connected to video track %p", aTrack));

    mVideoTrack = video;
    video->AddDirectListener(mVideoListener);
    video->AddListener(mVideoListener);
  } else {
    MOZ_ASSERT(false"Unknown track type");
  }
}

void MediaEncoder::RemoveMediaStreamTrack(MediaStreamTrack* aTrack) {
  if (!aTrack) {
    MOZ_ASSERT(false);
    return;
  }

  if (AudioStreamTrack* audio = aTrack->AsAudioStreamTrack()) {
    if (audio != mAudioTrack) {
      MOZ_ASSERT(false"Not connected to this audio track");
      return;
    }

    if (mAudioListener) {
      audio->RemoveDirectListener(mAudioListener);
      audio->RemoveListener(mAudioListener);
    }
    mAudioTrack = nullptr;
  } else if (VideoStreamTrack* video = aTrack->AsVideoStreamTrack()) {
    if (video != mVideoTrack) {
      MOZ_ASSERT(false"Not connected to this video track");
      return;
    }

    if (mVideoListener) {
      video->RemoveDirectListener(mVideoListener);
      video->RemoveListener(mVideoListener);
    }
    mVideoTrack = nullptr;
  }
}

/* static */
already_AddRefed<MediaEncoder> MediaEncoder::CreateEncoder(
    RefPtr<TaskQueue> aEncoderThread, const nsAString& aMimeType,
    uint32_t aAudioBitrate, uint32_t aVideoBitrate, uint8_t aTrackTypes,
    TrackRate aTrackRate, uint64_t aMaxMemory, TimeDuration aTimeslice) {
  AUTO_PROFILER_LABEL("MediaEncoder::CreateEncoder", OTHER);

  UniquePtr<ContainerWriter> writer;
  UniquePtr<AudioTrackEncoder> audioEncoder;
  UniquePtr<VideoTrackEncoder> videoEncoder;
  auto encodedAudioQueue = MakeUnique<MediaQueue<EncodedFrame>>();
  auto encodedVideoQueue = MakeUnique<MediaQueue<EncodedFrame>>();
  auto driftCompensator =
      MakeRefPtr<DriftCompensator>(aEncoderThread, aTrackRate);

  Maybe<MediaContainerType> mimeType = MakeMediaContainerType(aMimeType);
  if (!mimeType) {
    return nullptr;
  }

  for (const auto& codec : mimeType->ExtendedType().Codecs().Range()) {
    if (codec.EqualsLiteral("opus")) {
      MOZ_ASSERT(!audioEncoder);
      audioEncoder =
          MakeUnique<OpusTrackEncoder>(aTrackRate, *encodedAudioQueue);
    } else if (codec.EqualsLiteral("vp8") || codec.EqualsLiteral("vp8.0")) {
      MOZ_ASSERT(!videoEncoder);
      if (Preferences::GetBool("media.recorder.video.frame_drops"true)) {
        videoEncoder = MakeUnique<VP8TrackEncoder>(driftCompensator, aTrackRate,
                                                   *encodedVideoQueue,
                                                   FrameDroppingMode::ALLOW);
      } else {
        videoEncoder = MakeUnique<VP8TrackEncoder>(driftCompensator, aTrackRate,
                                                   *encodedVideoQueue,
                                                   FrameDroppingMode::DISALLOW);
      }
    } else {
      MOZ_CRASH("Unknown codec");
    }
  }

  if (mimeType->Type() == MEDIAMIMETYPE(VIDEO_WEBM) ||
      mimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM)) {
    MOZ_ASSERT_IF(mimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM), !videoEncoder);
    writer = MakeUnique<WebMWriter>();
  } else if (mimeType->Type() == MEDIAMIMETYPE(AUDIO_OGG)) {
    MOZ_ASSERT(audioEncoder);
    MOZ_ASSERT(!videoEncoder);
    writer = MakeUnique<OggWriter>();
  }
  NS_ENSURE_TRUE(writer, nullptr);

  LOG(LogLevel::Info,
      ("Create encoder result:a[%p](%u bps) v[%p](%u bps) w[%p] mimeType = "
       "%s.",
       audioEncoder.get(), aAudioBitrate, videoEncoder.get(), aVideoBitrate,
       writer.get(), NS_ConvertUTF16toUTF8(aMimeType).get()));

  if (audioEncoder) {
    audioEncoder->SetWorkerThread(aEncoderThread);
    if (aAudioBitrate != 0) {
      audioEncoder->SetBitrate(aAudioBitrate);
    }
  }
  if (videoEncoder) {
    videoEncoder->SetWorkerThread(aEncoderThread);
    if (aVideoBitrate != 0) {
      videoEncoder->SetBitrate(aVideoBitrate);
    }
  }
  RefPtr<MediaEncoder> encoder = new MediaEncoder(
      std::move(aEncoderThread), std::move(driftCompensator), std::move(writer),
      std::move(audioEncoder), std::move(videoEncoder),
      std::move(encodedAudioQueue), std::move(encodedVideoQueue), aTrackRate,
      aMimeType, aMaxMemory, aTimeslice);

  encoder->RegisterListeners();

  return encoder.forget();
}

nsresult MediaEncoder::GetEncodedData(
    nsTArray<nsTArray<uint8_t>>* aOutputBufs) {
  AUTO_PROFILER_LABEL("MediaEncoder::GetEncodedData", OTHER);

  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());

  LOG(LogLevel::Verbose,
      ("GetEncodedData TimeStamp = %f", GetEncodeTimeStamp()));

  if (!mInitialized) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  nsresult rv = mMuxer->GetData(aOutputBufs);
  if (mMuxer->IsFinished()) {
    mCompleted = true;
  }

  LOG(LogLevel::Verbose,
      ("END GetEncodedData TimeStamp=%f "
       "mCompleted=%d, aComplete=%d, vComplete=%d",
       GetEncodeTimeStamp(), mCompleted,
       !mAudioEncoder || mAudioEncoder->IsEncodingComplete(),
       !mVideoEncoder || mVideoEncoder->IsEncodingComplete()));

  return rv;
}

void MediaEncoder::MaybeShutdown() {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
  if (!mEncodedAudioQueue->IsFinished()) {
    LOG(LogLevel::Debug,
        ("MediaEncoder %p not shutting down, audio is still live"this));
    return;
  }

  if (!mEncodedVideoQueue->IsFinished()) {
    LOG(LogLevel::Debug,
        ("MediaEncoder %p not shutting down, video is still live"this));
    return;
  }

  mShutdownEvent.Notify();

  // Stop will Shutdown() gracefully.
  Unused << InvokeAsync(mMainThread, this, __func__, &MediaEncoder::Stop);
}

RefPtr<GenericNonExclusivePromise> MediaEncoder::Shutdown() {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
  if (mShutdownPromise) {
    return mShutdownPromise;
  }

  LOG(LogLevel::Info, ("MediaEncoder is shutting down."));

  AutoTArray<RefPtr<GenericNonExclusivePromise>, 2> shutdownPromises;
  if (mAudioListener) {
    shutdownPromises.AppendElement(mAudioListener->OnShutdown());
  }
  if (mVideoListener) {
    shutdownPromises.AppendElement(mVideoListener->OnShutdown());
  }

  mShutdownPromise =
      GenericNonExclusivePromise::All(mEncoderThread, shutdownPromises)
          ->Then(mEncoderThread, __func__,
                 [](const GenericNonExclusivePromise::AllPromiseType::
                        ResolveOrRejectValue& aValue) {
                   if (aValue.IsResolve()) {
                     return GenericNonExclusivePromise::CreateAndResolve(
                         true, __func__);
                   }
                   return GenericNonExclusivePromise::CreateAndReject(
                       aValue.RejectValue(), __func__);
                 });

  mShutdownPromise->Then(
      mEncoderThread, __func__, [self = RefPtr<MediaEncoder>(this), this] {
        if (mAudioEncoder) {
          mAudioEncoder->UnregisterListener(mEncoderListener);
        }
        if (mVideoEncoder) {
          mVideoEncoder->UnregisterListener(mEncoderListener);
        }
        mEncoderListener->Forget();
        mMuxer->Disconnect();
        mAudioPushListener.DisconnectIfExists();
        mAudioFinishListener.DisconnectIfExists();
        mVideoPushListener.DisconnectIfExists();
        mVideoFinishListener.DisconnectIfExists();
      });

  return mShutdownPromise;
}

RefPtr<GenericNonExclusivePromise> MediaEncoder::Stop() {
  MOZ_ASSERT(NS_IsMainThread());

  LOG(LogLevel::Info, ("MediaEncoder %p Stop"this));

  DisconnectTracks();

  return InvokeAsync(mEncoderThread, this, __func__, &MediaEncoder::Shutdown);
}

RefPtr<GenericNonExclusivePromise> MediaEncoder::Cancel() {
  MOZ_ASSERT(NS_IsMainThread());

  LOG(LogLevel::Info, ("MediaEncoder %p Cancel"this));

  DisconnectTracks();

  return InvokeAsync(mEncoderThread, __func__,
                     [self = RefPtr<MediaEncoder>(this), this]() {
                       if (mAudioEncoder) {
                         mAudioEncoder->Cancel();
                       }
                       if (mVideoEncoder) {
                         mVideoEncoder->Cancel();
                       }
                       return Shutdown();
                     });
}

bool MediaEncoder::HasError() {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
  return mError;
}

void MediaEncoder::SetError() {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());

  if (mError) {
    return;
  }

  mError = true;
  mErrorEvent.Notify();
}

auto MediaEncoder::RequestData() -> RefPtr<BlobPromise> {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
  TimeUnit muxedEndTime = std::min(mMuxedAudioEndTime, mMuxedVideoEndTime);
  mLastBlobTime = muxedEndTime;
  mLastExtractTime = muxedEndTime;
  return Extract()->Then(
      mMainThread, __func__,
      [this, self = RefPtr<MediaEncoder>(this)](
          const GenericPromise::ResolveOrRejectValue& aValue) {
        // Even if rejected, we want to gather what has already been
        // extracted into the current blob and expose that.
        Unused << NS_WARN_IF(aValue.IsReject());
        return GatherBlob();
      });
}

void MediaEncoder::MaybeCreateMutableBlobStorage() {
  MOZ_ASSERT(NS_IsMainThread());
  if (!mMutableBlobStorage) {
    mMutableBlobStorage = new MutableBlobStorage(
        MutableBlobStorage::eCouldBeInTemporaryFile, nullptr, mMaxMemory);
  }
}

void MediaEncoder::OnEncodedAudioPushed(const RefPtr<EncodedFrame>& aFrame) {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
  mMuxedAudioEndTime = aFrame->GetEndTime();
  MaybeExtractOrGatherBlob();
}

void MediaEncoder::OnEncodedVideoPushed(const RefPtr<EncodedFrame>& aFrame) {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
  mMuxedVideoEndTime = aFrame->GetEndTime();
  MaybeExtractOrGatherBlob();
}

void MediaEncoder::MaybeExtractOrGatherBlob() {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());

  TimeUnit muxedEndTime = std::min(mMuxedAudioEndTime, mMuxedVideoEndTime);
  if ((muxedEndTime - mLastBlobTime).ToTimeDuration() >= mTimeslice) {
    LOG(LogLevel::Verbose, ("MediaEncoder %p Muxed %.2fs of data since last "
                            "blob. Issuing new blob.",
                            this, (muxedEndTime - mLastBlobTime).ToSeconds()));
    RequestData()->Then(mEncoderThread, __func__,
                        [this, self = RefPtr<MediaEncoder>(this)](
                            const BlobPromise::ResolveOrRejectValue& aValue) {
                          if (aValue.IsReject()) {
                            SetError();
                            return;
                          }
                          RefPtr<BlobImpl> blob = aValue.ResolveValue();
                          mDataAvailableEvent.Notify(std::move(blob));
                        });
  }

  if (muxedEndTime - mLastExtractTime > TimeUnit::FromSeconds(1)) {
    // Extract data from the muxer at least every second.
    LOG(LogLevel::Verbose,
        ("MediaEncoder %p Muxed %.2fs of data since last "
         "extract. Extracting more data into blob.",
         this, (muxedEndTime - mLastExtractTime).ToSeconds()));
    mLastExtractTime = muxedEndTime;
    Unused << Extract();
  }
}

// Pull encoded media data from MediaEncoder and put into MutableBlobStorage.
RefPtr<GenericPromise> MediaEncoder::Extract() {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());

  LOG(LogLevel::Debug, ("MediaEncoder %p Extract"this));

  AUTO_PROFILER_LABEL("MediaEncoder::Extract", OTHER);

  // Pull encoded media data from MediaEncoder
  nsTArray<nsTArray<uint8_t>> buffer;
  nsresult rv = GetEncodedData(&buffer);
  MOZ_ASSERT(rv != NS_ERROR_INVALID_ARG, "Invalid args can be prevented.");
  if (NS_FAILED(rv)) {
    MOZ_RELEASE_ASSERT(buffer.IsEmpty());
    // Even if we failed to encode more data, it might be time to push a blob
    // with already encoded data.
  }

  // To ensure Extract() promises are resolved in calling order, we always
  // invoke the main thread. Even when the encoded buffer is empty.
  return InvokeAsync(
      mMainThread, __func__,
      [self = RefPtr<MediaEncoder>(this), this, buffer = std::move(buffer)] {
        MaybeCreateMutableBlobStorage();
        for (const auto& part : buffer) {
          if (part.IsEmpty()) {
            continue;
          }

          nsresult rv =
              mMutableBlobStorage->Append(part.Elements(), part.Length());
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return GenericPromise::CreateAndReject(rv, __func__);
          }
        }
        return GenericPromise::CreateAndResolve(true, __func__);
      });
}

auto MediaEncoder::GatherBlob() -> RefPtr<BlobPromise> {
  MOZ_ASSERT(NS_IsMainThread());
  if (!mBlobPromise) {
    return mBlobPromise = GatherBlobImpl();
  }
  return mBlobPromise = mBlobPromise->Then(mMainThread, __func__,
                                           [self = RefPtr<MediaEncoder>(this)] {
                                             return self->GatherBlobImpl();
                                           });
}

auto MediaEncoder::GatherBlobImpl() -> RefPtr<BlobPromise> {
  RefPtr<BlobStorer> storer = MakeAndAddRef<BlobStorer>();
  MaybeCreateMutableBlobStorage();
  mMutableBlobStorage->GetBlobImplWhenReady(NS_ConvertUTF16toUTF8(mMimeType),
                                            storer);
  mMutableBlobStorage = nullptr;

  storer->Promise()->Then(
      mMainThread, __func__,
      [self = RefPtr<MediaEncoder>(this), p = storer->Promise()] {
        if (self->mBlobPromise == p) {
          // Reset BlobPromise.
          self->mBlobPromise = nullptr;
        }
      });

  return storer->Promise();
}

void MediaEncoder::DisconnectTracks() {
  MOZ_ASSERT(NS_IsMainThread());

  if (mAudioNode) {
    mAudioNode->GetTrack()->RemoveListener(mAudioListener);
    if (mInputPort) {
      mInputPort->Destroy();
      mInputPort = nullptr;
    }
    if (mPipeTrack) {
      mPipeTrack->RemoveListener(mAudioListener);
      mPipeTrack->Destroy();
      mPipeTrack = nullptr;
    }
    mAudioNode = nullptr;
  }

  if (mAudioTrack) {
    RemoveMediaStreamTrack(mAudioTrack);
  }

  if (mVideoTrack) {
    RemoveMediaStreamTrack(mVideoTrack);
  }
}

bool MediaEncoder::IsWebMEncoderEnabled() {
  return StaticPrefs::media_encoder_webm_enabled();
}

void MediaEncoder::UpdateInitialized() {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());

  if (mInitialized) {
    // This could happen if an encoder re-inits due to a resolution change.
    return;
  }

  if (mAudioEncoder && !mAudioEncoder->IsInitialized()) {
    LOG(LogLevel::Debug,
        ("MediaEncoder %p UpdateInitialized waiting for audio"this));
    return;
  }

  if (mVideoEncoder && !mVideoEncoder->IsInitialized()) {
    LOG(LogLevel::Debug,
        ("MediaEncoder %p UpdateInitialized waiting for video"this));
    return;
  }

  MOZ_ASSERT(mMuxer->NeedsMetadata());
  nsTArray<RefPtr<TrackMetadataBase>> meta;
  if (mAudioEncoder && !*meta.AppendElement(mAudioEncoder->GetMetadata())) {
    LOG(LogLevel::Error, ("Audio metadata is null"));
    SetError();
    return;
  }
  if (mVideoEncoder && !*meta.AppendElement(mVideoEncoder->GetMetadata())) {
    LOG(LogLevel::Error, ("Video metadata is null"));
    SetError();
    return;
  }

  if (NS_FAILED(mMuxer->SetMetadata(meta))) {
    LOG(LogLevel::Error, ("SetMetadata failed"));
    SetError();
    return;
  }

  LOG(LogLevel::Info,
      ("MediaEncoder %p UpdateInitialized set metadata in muxer"this));

  mInitialized = true;
}

void MediaEncoder::UpdateStarted() {
  MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());

  if (mStarted) {
    return;
  }

  if (mAudioEncoder && !mAudioEncoder->IsStarted()) {
    return;
  }

  if (mVideoEncoder && !mVideoEncoder->IsStarted()) {
    return;
  }

  mStarted = true;

  // Start issuing timeslice-based blobs.
  MOZ_ASSERT(mLastBlobTime == TimeUnit::Zero());

  mStartedEvent.Notify();
}

/*
 * SizeOfExcludingThis measures memory being used by the Media Encoder.
 * Currently it measures the size of the Encoder buffer and memory occupied
 * by mAudioEncoder, mVideoEncoder, and any current blob storage.
 */

auto MediaEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
    -> RefPtr<SizeOfPromise> {
  MOZ_ASSERT(NS_IsMainThread());
  size_t blobStorageSize =
      mMutableBlobStorage ? mMutableBlobStorage->SizeOfCurrentMemoryBuffer()
                          : 0;

  return InvokeAsync(
      mEncoderThread, __func__,
      [self = RefPtr<MediaEncoder>(this), this, blobStorageSize,
       aMallocSizeOf]() {
        size_t size = 0;
        if (mAudioEncoder) {
          size += mAudioEncoder->SizeOfExcludingThis(aMallocSizeOf);
        }
        if (mVideoEncoder) {
          size += mVideoEncoder->SizeOfExcludingThis(aMallocSizeOf);
        }
        return SizeOfPromise::CreateAndResolve(blobStorageSize + size,
                                               __func__);
      });
}

}  // namespace mozilla

#undef LOG

Messung V0.5
C=98 H=99 G=98

¤ Dauer der Verarbeitung: 0.4 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Entwurf

Ziele

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Ergonomie der
Schnittstellen

Diese beiden folgenden Angebotsgruppen bietet das Unternehmen

Angebot

Hier finden Sie eine Liste der Produkte des Unternehmens






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge