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


Quelle  AudioSinkWrapper.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "AudioSinkWrapper.h"
#include "AudioDeviceInfo.h"
#include "AudioSink.h"
#include "VideoUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/Result.h"
#include "mozilla/StaticPrefs_media.h"
#include "nsPrintfCString.h"
#include "nsThreadManager.h"

mozilla::LazyLogModule gAudioSinkWrapperLog("AudioSinkWrapper");
#define LOG(...) \
  MOZ_LOG(gAudioSinkWrapperLog, mozilla::LogLevel::Debug, (__VA_ARGS__));
#define LOGV(...) \
  MOZ_LOG(gAudioSinkWrapperLog, mozilla::LogLevel::Verbose, (__VA_ARGS__));

namespace mozilla {

using media::TimeUnit;

AudioSinkWrapper::~AudioSinkWrapper() = default;

void AudioSinkWrapper::Shutdown() {
  AssertOwnerThread();
  MOZ_ASSERT(!mIsStarted, "Must be called after playback stopped.");
  mSinkCreator = nullptr;
}

/* static */
already_AddRefed<TaskQueue> AudioSinkWrapper::CreateAsyncInitTaskQueue() {
  return nsThreadManager::get().CreateBackgroundTaskQueue("AsyncAudioSinkInit");
}

RefPtr<MediaSink::EndedPromise> AudioSinkWrapper::OnEnded(TrackType aType) {
  AssertOwnerThread();
  MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
  if (aType == TrackInfo::kAudioTrack) {
    return mEndedPromise;
  }
  return nullptr;
}

TimeUnit AudioSinkWrapper::GetEndTime(TrackType aType) const {
  AssertOwnerThread();
  MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
  if (aType != TrackInfo::kAudioTrack) {
    return TimeUnit::Zero();
  }

  if (mAudioSink && mAudioSink->AudioStreamCallbackStarted()) {
    auto time = mAudioSink->GetEndTime();
    LOGV("%p: GetEndTime return %lf from sink"this, time.ToSeconds());
    return time;
  }

  RefPtr<const AudioData> audio = mAudioQueue.PeekBack();
  if (audio) {
    LOGV("%p: GetEndTime return %lf from queue"this,
         audio->GetEndTime().ToSeconds());
    return audio->GetEndTime();
  }

  LOGV("%p: GetEndTime return %lf from last packet"this,
       mLastPacketEndTime.ToSeconds());
  return mLastPacketEndTime;
}

TimeUnit AudioSinkWrapper::GetSystemClockPosition(TimeStamp aNow) const {
  AssertOwnerThread();
  MOZ_ASSERT(!mClockStartTime.IsNull());
  // Time elapsed since we started playing.
  double delta = (aNow - mClockStartTime).ToSeconds();
  // Take playback rate into account.
  return mPositionAtClockStart +
         TimeUnit::FromSeconds(delta * mParams.mPlaybackRate);
}

bool AudioSinkWrapper::IsMuted() const {
  AssertOwnerThread();
  return mParams.mVolume == 0.0;
}

TimeUnit AudioSinkWrapper::GetPosition(TimeStamp* aTimeStamp) {
  AssertOwnerThread();
  MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");

  TimeUnit pos;
  TimeStamp t = TimeStamp::Now();

  if (mAudioSink) {
    if (mLastClockSource == ClockSource::SystemClock) {
      TimeUnit switchTime = GetSystemClockPosition(t);
      // Update the _actual_ start time of the audio stream now that it has
      // started, preventing any clock discontinuity.
      mAudioSink->UpdateStartTime(switchTime);
      LOGV("%p: switching to audio clock at media time %lf"this,
           switchTime.ToSeconds());
    }
    // Rely on the audio sink to report playback position when it is not ended.
    pos = mAudioSink->GetPosition();
    LOGV("%p: Getting position from the Audio Sink %lf"this, pos.ToSeconds());
    mLastClockSource = ClockSource::AudioStream;
  } else if (!mClockStartTime.IsNull()) {
    // Calculate playback position using system clock if we are still playing,
    // but not rendering the audio, because this audio sink is muted.
    pos = GetSystemClockPosition(t);
    LOGV("%p: Getting position from the system clock %lf"this,
         pos.ToSeconds());
    if (mAudioQueue.GetSize() > 0) {
      // Audio track, but it won't be dequeued.  Discard packets
      // that are behind the current media time, to keep the queue size under
      // control.
      DropAudioPacketsIfNeeded(pos);
    }
    // Without an AudioSink, it's necessary to manually check if the audio has
    // "ended", meaning that all the audio packets have been consumed,
    // to resolve the ended promise.
    if (CheckIfEnded()) {
      MOZ_ASSERT(!mAudioSink);
      mEndedPromiseHolder.ResolveIfExists(true, __func__);
    }
    mLastClockSource = ClockSource::SystemClock;

    if (!mAudioSink && mAsyncCreateCount == 0 && NeedAudioSink() &&
        t > mRetrySinkTime) {
      MaybeAsyncCreateAudioSink(mAudioDevice);
    }
  } else {
    // Return how long we've played if we are not playing.
    pos = mPositionAtClockStart;
    LOGV("%p: Getting static position, not playing %lf"this, pos.ToSeconds());
    mLastClockSource = ClockSource::Paused;
  }

  if (aTimeStamp) {
    *aTimeStamp = t;
  }

  return pos;
}

bool AudioSinkWrapper::CheckIfEnded() const {
  return mAudioQueue.IsFinished() && mAudioQueue.GetSize() == 0u;
}

bool AudioSinkWrapper::HasUnplayedFrames(TrackType aType) const {
  AssertOwnerThread();
  return mAudioSink ? mAudioSink->HasUnplayedFrames() : false;
}

media::TimeUnit AudioSinkWrapper::UnplayedDuration(TrackType aType) const {
  AssertOwnerThread();
  return mAudioSink ? mAudioSink->UnplayedDuration() : media::TimeUnit::Zero();
}

void AudioSinkWrapper::DropAudioPacketsIfNeeded(
    const TimeUnit& aMediaPosition) {
  RefPtr<AudioData> audio = mAudioQueue.PeekFront();
  uint32_t dropped = 0;
  while (audio && audio->GetEndTime() < aMediaPosition) {
    // drop this packet, try the next one
    audio = mAudioQueue.PopFront();
    dropped++;
    if (audio) {
      mLastPacketEndTime = audio->GetEndTime();
      LOGV(
          "Dropping audio packets: media position: %lf, "
          "packet dropped: [%lf, %lf] (%u so far).\n",
          aMediaPosition.ToSeconds(), audio->mTime.ToSeconds(),
          (audio->GetEndTime()).ToSeconds(), dropped);
    }
    audio = mAudioQueue.PeekFront();
  }
}

void AudioSinkWrapper::OnMuted(bool aMuted) {
  AssertOwnerThread();
  LOG("%p: AudioSinkWrapper::OnMuted(%s)"this, aMuted ? "true" : "false");
  // Nothing to do
  if (mAudioEnded) {
    LOG("%p: AudioSinkWrapper::OnMuted, but no audio track"this);
    return;
  }
  if (aMuted) {
    if (mAudioSink) {
      LOG("AudioSinkWrapper muted, shutting down AudioStream.");
      ShutDownAudioSink();
    }
  } else {
    LOG("%p: AudioSinkWrapper unmuted, maybe re-creating an AudioStream.",
        this);
    MaybeAsyncCreateAudioSink(mAudioDevice);
  }
}

void AudioSinkWrapper::SetVolume(double aVolume) {
  AssertOwnerThread();

  bool wasMuted = mParams.mVolume == 0;
  bool nowMuted = aVolume == 0.;
  mParams.mVolume = aVolume;

  if (!wasMuted && nowMuted) {
    OnMuted(true);
  } else if (wasMuted && !nowMuted) {
    OnMuted(false);
  }

  if (mAudioSink) {
    mAudioSink->SetVolume(aVolume);
  }
}

void AudioSinkWrapper::SetStreamName(const nsAString& aStreamName) {
  AssertOwnerThread();
  if (mAudioSink) {
    mAudioSink->SetStreamName(aStreamName);
  }
}

void AudioSinkWrapper::SetPlaybackRate(double aPlaybackRate) {
  AssertOwnerThread();
  if (mAudioSink) {
    // Pass the playback rate to the audio sink. The underlying AudioStream
    // will handle playback rate changes and report correct audio position.
    mAudioSink->SetPlaybackRate(aPlaybackRate);
  } else if (!mClockStartTime.IsNull()) {
    // Adjust playback duration and start time when we are still playing.
    TimeStamp now = TimeStamp::Now();
    mPositionAtClockStart = GetSystemClockPosition(now);
    mClockStartTime = now;
  }
  // mParams.mPlaybackRate affects GetSystemClockPosition(). It should be
  // updated after the calls to GetSystemClockPosition();
  mParams.mPlaybackRate = aPlaybackRate;

  // Do nothing when not playing. Changes in playback rate will be taken into
  // account by GetSystemClockPosition().
}

void AudioSinkWrapper::SetPreservesPitch(bool aPreservesPitch) {
  AssertOwnerThread();
  mParams.mPreservesPitch = aPreservesPitch;
  if (mAudioSink) {
    mAudioSink->SetPreservesPitch(aPreservesPitch);
  }
}

void AudioSinkWrapper::SetPlaying(bool aPlaying) {
  AssertOwnerThread();
  LOG("%p: AudioSinkWrapper::SetPlaying %s"this, aPlaying ? "true" : "false");

  // Resume/pause matters only when playback started.
  if (!mIsStarted) {
    return;
  }

  if (mAudioSink) {
    mAudioSink->SetPlaying(aPlaying);
  }

  if (aPlaying) {
    MOZ_ASSERT(mClockStartTime.IsNull());
    TimeUnit switchTime = GetPosition();
    mClockStartTime = TimeStamp::Now();
    if (!mAudioSink && NeedAudioSink()) {
      LOG("%p: AudioSinkWrapper::SetPlaying : starting an AudioSink"this);
      DropAudioPacketsIfNeeded(switchTime);
      SyncCreateAudioSink(switchTime);
    }
  } else {
    // Remember how long we've played.
    mPositionAtClockStart = GetPosition();
    // mClockStartTime must be updated later since GetPosition()
    // depends on the value of mClockStartTime.
    mClockStartTime = TimeStamp();
  }
}

RefPtr<GenericPromise> AudioSinkWrapper::SetAudioDevice(
    RefPtr<AudioDeviceInfo> aDevice) {
  return MaybeAsyncCreateAudioSink(std::move(aDevice));
}

double AudioSinkWrapper::PlaybackRate() const {
  AssertOwnerThread();
  return mParams.mPlaybackRate;
}

nsresult AudioSinkWrapper::Start(const TimeUnit& aStartTime,
                                 const MediaInfo& aInfo) {
  LOG("%p AudioSinkWrapper::Start"this);
  AssertOwnerThread();
  MOZ_ASSERT(!mIsStarted, "playback already started.");

  mIsStarted = true;
  mPositionAtClockStart = aStartTime;
  mClockStartTime = TimeStamp::Now();
  mAudioEnded = IsAudioSourceEnded(aInfo);
  mLastPacketEndTime = TimeUnit::Zero();

  if (mAudioEnded) {
    // Resolve promise if we start playback at the end position of the audio.
    mEndedPromise =
        aInfo.HasAudio()
            ? MediaSink::EndedPromise::CreateAndResolve(true, __func__)
            : nullptr;
    return NS_OK;
  }

  mEndedPromise = mEndedPromiseHolder.Ensure(__func__);
  if (!NeedAudioSink()) {
    return NS_OK;
  }
  return SyncCreateAudioSink(aStartTime);
}

bool AudioSinkWrapper::NeedAudioSink() {
  // An AudioSink is needed if unmuted, playing, and not ended.  The not-ended
  // check also avoids creating an AudioSink when there is no audio track.
  return !IsMuted() && IsPlaying() && !mEndedPromiseHolder.IsEmpty();
}

void AudioSinkWrapper::StartAudioSink(UniquePtr<AudioSink> aAudioSink,
                                      const TimeUnit& aStartTime) {
  AssertOwnerThread();
  MOZ_ASSERT(!mAudioSink);
  mAudioSink = std::move(aAudioSink);
  mAudioSink->Start(mParams, aStartTime)
      ->Then(mOwnerThread.GetEventTarget(), __func__, this,
             &AudioSinkWrapper::OnAudioEnded)
      ->Track(mAudioSinkEndedRequest);
}

void AudioSinkWrapper::ShutDownAudioSink() {
  AssertOwnerThread();
  mAudioSinkEndedRequest.DisconnectIfExists();
  if (IsPlaying()) {
    mPositionAtClockStart = mAudioSink->GetPosition();
    mClockStartTime = TimeStamp::Now();
  }
  mAudioSink->ShutDown();
  mLastPacketEndTime = mAudioSink->GetEndTime();
  mAudioSink = nullptr;
}

RefPtr<GenericPromise> AudioSinkWrapper::MaybeAsyncCreateAudioSink(
    RefPtr<AudioDeviceInfo> aDevice) {
  AssertOwnerThread();
  UniquePtr<AudioSink> audioSink;
  if (NeedAudioSink() && (!mAudioSink || aDevice != mAudioDevice)) {
    LOG("%p: AudioSinkWrapper::MaybeAsyncCreateAudioSink: AudioSink needed",
        this);
    if (mAudioSink) {
      ShutDownAudioSink();
    }
    audioSink = mSinkCreator();
  } else {
    LOG("%p: AudioSinkWrapper::MaybeAsyncCreateAudioSink: no AudioSink change",
        this);
    // Bounce off the background thread to keep promise resolution in order.
  }
  mAudioDevice = std::move(aDevice);
  ++mAsyncCreateCount;
  using Promise =
      MozPromise<UniquePtr<AudioSink>, nsresult, /* IsExclusive = */ true>;
  return InvokeAsync(
             mAsyncInitTaskQueue,
             "MaybeAsyncCreateAudioSink (Async part: initialization)",
             [self = RefPtr<AudioSinkWrapper>(this),
              audioSink{std::move(audioSink)}, audioDevice = mAudioDevice,
              this]() mutable {
               if (!audioSink || !mAsyncInitTaskQueue->IsEmpty()) {
                 // Either an AudioSink is not required or there's a
                 // pending task to init an AudioSink with a possibly
                 // different device.
                 return Promise::CreateAndResolve(nullptr, __func__);
               }

               LOG("AudioSink initialization on background thread");
               // This can take about 200ms, e.g. on Windows, we don't
               // want to do it on the MDSM thread, because it would
               // make the clock not update for that amount of time, and
               // the video would therefore not update. The Start() call
               // is very cheap on the other hand, we can do it from the
               // MDSM thread.
               nsresult rv = audioSink->InitializeAudioStream(
                   audioDevice, AudioSink::InitializationType::UNMUTING);
               if (NS_FAILED(rv)) {
                 LOG("Async AudioSink initialization failed");
                 return Promise::CreateAndReject(rv, __func__);
               }
               return Promise::CreateAndResolve(std::move(audioSink), __func__);
             })
      ->Then(
          mOwnerThread.GetEventTarget(),
          "MaybeAsyncCreateAudioSink (Async part: start from MDSM thread)",
          [self = RefPtr<AudioSinkWrapper>(this), audioDevice = mAudioDevice,
           this](Promise::ResolveOrRejectValue&& aValue) mutable {
            LOG("AudioSink async init done, back on MDSM thread");
            --mAsyncCreateCount;
            UniquePtr<AudioSink> audioSink;
            if (aValue.IsResolve()) {
              audioSink = std::move(aValue.ResolveValue());
            }
            // It's possible that the newly created AudioSink isn't needed at
            // this point, in some cases:
            // 1. An AudioSink was created synchronously while this
            // AudioSink was initialized asynchronously, bail out here. This
            // happens when seeking (which does a synchronous initialization)
            // right after unmuting.  mEndedPromiseHolder is managed by the
            // other AudioSink, so don't touch it here.
            // 2. The media element was muted while the async initialization
            // was happening.
            // 3. The AudioSinkWrapper was paused or stopped during
            // asynchronous initialization.
            // 4. The audio has ended during asynchronous initialization.
            // 5. A change to a potentially different sink device is pending.
            if (mAudioSink || !NeedAudioSink() || audioDevice != mAudioDevice) {
              LOG("AudioSink async initialization isn't needed.");
              if (audioSink) {
                LOG("Shutting down unneeded AudioSink.");
                audioSink->ShutDown();
              }
              return GenericPromise::CreateAndResolve(true, __func__);
            }

            if (aValue.IsReject()) {
              if (audioDevice) {
                // Device will be started when available again.
                ScheduleRetrySink();
              } else {
                // Default device not available.  Report error.
                MOZ_ASSERT(!mAudioSink);
                mEndedPromiseHolder.RejectIfExists(aValue.RejectValue(),
                                                   __func__);
              }
              return GenericPromise::CreateAndResolve(true, __func__);
            }

            if (!audioSink) {
              // No-op because either an existing AudioSink was suitable or no
              // AudioSink was needed when MaybeAsyncCreateAudioSink() set up
              // this task.  We now need a new AudioSink, but that will be
              // handled by another task, either already pending or a delayed
              // retry task yet to be created by GetPosition().
              return GenericPromise::CreateAndResolve(true, __func__);
            }

            MOZ_ASSERT(!mAudioSink);
            // Avoiding the side effects of GetPosition() creating another
            // sink another AudioSink and resolving mEndedPromiseHolder, which
            // the new audioSink will now manage.
            TimeUnit switchTime = GetSystemClockPosition(TimeStamp::Now());
            DropAudioPacketsIfNeeded(switchTime);
            mLastClockSource = ClockSource::SystemClock;

            LOG("AudioSink async, start");
            StartAudioSink(std::move(audioSink), switchTime);
            return GenericPromise::CreateAndResolve(true, __func__);
          });
}

nsresult AudioSinkWrapper::SyncCreateAudioSink(const TimeUnit& aStartTime) {
  AssertOwnerThread();
  MOZ_ASSERT(!mAudioSink);
  MOZ_ASSERT(!mAudioSinkEndedRequest.Exists());

  LOG("%p: AudioSinkWrapper::SyncCreateAudioSink(%lf)"this,
      aStartTime.ToSeconds());

  UniquePtr<AudioSink> audioSink = mSinkCreator();
  nsresult rv = audioSink->InitializeAudioStream(
      mAudioDevice, AudioSink::InitializationType::INITIAL);
  if (NS_FAILED(rv)) {
    LOG("Sync AudioSinkWrapper initialization failed");
    // If a specific device has been specified through setSinkId()
    // the sink is started after the device becomes available again.
    if (mAudioDevice) {
      ScheduleRetrySink();
      return NS_OK;
    }
    // If a default output device is not available, the system may not support
    // audio output.  Report an error so that playback can be aborted if there
    // is no video.
    mEndedPromiseHolder.RejectIfExists(rv, __func__);
    return rv;
  }
  StartAudioSink(std::move(audioSink), aStartTime);

  return NS_OK;
}

void AudioSinkWrapper::ScheduleRetrySink() {
  mRetrySinkTime =
      TimeStamp::Now() + TimeDuration::FromMilliseconds(
                             StaticPrefs::media_audio_device_retry_ms());
}

bool AudioSinkWrapper::IsAudioSourceEnded(const MediaInfo& aInfo) const {
  // no audio or empty audio queue which won't get data anymore is equivalent to
  // audio ended
  return !aInfo.HasAudio() ||
         (mAudioQueue.IsFinished() && mAudioQueue.GetSize() == 0u);
}

void AudioSinkWrapper::Stop() {
  AssertOwnerThread();
  MOZ_ASSERT(mIsStarted, "playback not started.");

  LOG("%p: AudioSinkWrapper::Stop"this);

  mIsStarted = false;
  mClockStartTime = TimeStamp();
  mPositionAtClockStart = TimeUnit::Invalid();
  mAudioEnded = true;
  if (mAudioSink) {
    ShutDownAudioSink();
  }

  mEndedPromiseHolder.ResolveIfExists(true, __func__);
  mEndedPromise = nullptr;
}

bool AudioSinkWrapper::IsStarted() const {
  AssertOwnerThread();
  return mIsStarted;
}

bool AudioSinkWrapper::IsPlaying() const {
  AssertOwnerThread();
  MOZ_ASSERT(mClockStartTime.IsNull() || IsStarted());
  return !mClockStartTime.IsNull();
}

void AudioSinkWrapper::OnAudioEnded(
    const EndedPromise::ResolveOrRejectValue& aValue) {
  AssertOwnerThread();
  // This callback on mAudioSinkEndedRequest should have been disconnected if
  // mEndedPromiseHolder has been settled.
  MOZ_ASSERT(!mEndedPromiseHolder.IsEmpty());
  LOG("%p: AudioSinkWrapper::OnAudioEnded %i"this, aValue.IsResolve());
  mAudioSinkEndedRequest.Complete();
  ShutDownAudioSink();
  // System time is now used for the clock as video may not have ended.
  if (aValue.IsResolve()) {
    mAudioEnded = true;
    mEndedPromiseHolder.Resolve(aValue.ResolveValue(), __func__);
    return;
  }
  if (mAudioDevice) {
    ScheduleRetrySink();  // Device will be restarted when available again.
    return;
  }
  // Default device not available.  Report error.
  mEndedPromiseHolder.Reject(aValue.RejectValue(), __func__);
}

void AudioSinkWrapper::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) {
  AssertOwnerThread();
  aInfo.mAudioSinkWrapper.mIsPlaying = IsPlaying();
  aInfo.mAudioSinkWrapper.mIsStarted = IsStarted();
  aInfo.mAudioSinkWrapper.mAudioEnded = mAudioEnded;
  if (mAudioSink) {
    mAudioSink->GetDebugInfo(aInfo);
  }
}

}  // namespace mozilla

#undef LOG
#undef LOGV

Messung V0.5
C=87 H=99 G=93

¤ Dauer der Verarbeitung: 0.9 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

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

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