import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,72 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(AudioSink_h__)
#define AudioSink_h__
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "nsISupportsImpl.h"
#include "MediaSink.h"
namespace mozilla {
class MediaData;
template <class T> class MediaQueue;
namespace media {
/*
* Define basic APIs for derived class instance to operate or obtain
* information from it.
*/
class AudioSink {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioSink)
AudioSink(MediaQueue<MediaData>& aAudioQueue)
: mAudioQueue(aAudioQueue)
{}
typedef MediaSink::PlaybackParams PlaybackParams;
// Return a promise which will be resolved when AudioSink finishes playing,
// or rejected if any error.
virtual RefPtr<GenericPromise> Init(const PlaybackParams& aParams) = 0;
virtual int64_t GetEndTime() const = 0;
virtual int64_t GetPosition() = 0;
// Check whether we've pushed more frames to the audio
// hardware than it has played.
virtual bool HasUnplayedFrames() = 0;
// Shut down the AudioSink's resources.
virtual void Shutdown() = 0;
// Change audio playback setting.
virtual void SetVolume(double aVolume) = 0;
virtual void SetPlaybackRate(double aPlaybackRate) = 0;
virtual void SetPreservesPitch(bool aPreservesPitch) = 0;
// Change audio playback status pause/resume.
virtual void SetPlaying(bool aPlaying) = 0;
protected:
virtual ~AudioSink() {}
virtual MediaQueue<MediaData>& AudioQueue() const {
return mAudioQueue;
}
// To queue audio data (no matter it's plain or encoded or encrypted, depends
// on the subclass)
MediaQueue<MediaData>& mAudioQueue;
};
} // namespace media
} // namespace mozilla
#endif

View file

@ -0,0 +1,248 @@
/* -*- 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 "AudioSink.h"
#include "AudioSinkWrapper.h"
namespace mozilla {
namespace media {
AudioSinkWrapper::~AudioSinkWrapper()
{
}
void
AudioSinkWrapper::Shutdown()
{
AssertOwnerThread();
MOZ_ASSERT(!mIsStarted, "Must be called after playback stopped.");
mCreator = nullptr;
}
const MediaSink::PlaybackParams&
AudioSinkWrapper::GetPlaybackParams() const
{
AssertOwnerThread();
return mParams;
}
void
AudioSinkWrapper::SetPlaybackParams(const PlaybackParams& aParams)
{
AssertOwnerThread();
if (mAudioSink) {
mAudioSink->SetVolume(aParams.mVolume);
mAudioSink->SetPlaybackRate(aParams.mPlaybackRate);
mAudioSink->SetPreservesPitch(aParams.mPreservesPitch);
}
mParams = aParams;
}
RefPtr<GenericPromise>
AudioSinkWrapper::OnEnded(TrackType aType)
{
AssertOwnerThread();
MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
if (aType == TrackInfo::kAudioTrack) {
return mEndPromise;
}
return nullptr;
}
int64_t
AudioSinkWrapper::GetEndTime(TrackType aType) const
{
AssertOwnerThread();
MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
if (aType == TrackInfo::kAudioTrack && mAudioSink) {
return mAudioSink->GetEndTime();
}
return -1;
}
int64_t
AudioSinkWrapper::GetVideoPosition(TimeStamp aNow) const
{
AssertOwnerThread();
MOZ_ASSERT(!mPlayStartTime.IsNull());
// Time elapsed since we started playing.
int64_t delta = (aNow - mPlayStartTime).ToMicroseconds();
// Take playback rate into account.
return mPlayDuration + delta * mParams.mPlaybackRate;
}
int64_t
AudioSinkWrapper::GetPosition(TimeStamp* aTimeStamp) const
{
AssertOwnerThread();
MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
int64_t pos = -1;
TimeStamp t = TimeStamp::Now();
if (!mAudioEnded) {
// Rely on the audio sink to report playback position when it is not ended.
pos = mAudioSink->GetPosition();
} else if (!mPlayStartTime.IsNull()) {
// Calculate playback position using system clock if we are still playing.
pos = GetVideoPosition(t);
} else {
// Return how long we've played if we are not playing.
pos = mPlayDuration;
}
if (aTimeStamp) {
*aTimeStamp = t;
}
return pos;
}
bool
AudioSinkWrapper::HasUnplayedFrames(TrackType aType) const
{
AssertOwnerThread();
return mAudioSink ? mAudioSink->HasUnplayedFrames() : false;
}
void
AudioSinkWrapper::SetVolume(double aVolume)
{
AssertOwnerThread();
mParams.mVolume = aVolume;
if (mAudioSink) {
mAudioSink->SetVolume(aVolume);
}
}
void
AudioSinkWrapper::SetPlaybackRate(double aPlaybackRate)
{
AssertOwnerThread();
if (!mAudioEnded) {
// 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 (!mPlayStartTime.IsNull()) {
// Adjust playback duration and start time when we are still playing.
TimeStamp now = TimeStamp::Now();
mPlayDuration = GetVideoPosition(now);
mPlayStartTime = now;
}
// mParams.mPlaybackRate affects GetVideoPosition(). It should be updated
// after the calls to GetVideoPosition();
mParams.mPlaybackRate = aPlaybackRate;
// Do nothing when not playing. Changes in playback rate will be taken into
// account by GetVideoPosition().
}
void
AudioSinkWrapper::SetPreservesPitch(bool aPreservesPitch)
{
AssertOwnerThread();
mParams.mPreservesPitch = aPreservesPitch;
if (mAudioSink) {
mAudioSink->SetPreservesPitch(aPreservesPitch);
}
}
void
AudioSinkWrapper::SetPlaying(bool aPlaying)
{
AssertOwnerThread();
// Resume/pause matters only when playback started.
if (!mIsStarted) {
return;
}
if (mAudioSink) {
mAudioSink->SetPlaying(aPlaying);
}
if (aPlaying) {
MOZ_ASSERT(mPlayStartTime.IsNull());
mPlayStartTime = TimeStamp::Now();
} else {
// Remember how long we've played.
mPlayDuration = GetPosition();
// mPlayStartTime must be updated later since GetPosition()
// depends on the value of mPlayStartTime.
mPlayStartTime = TimeStamp();
}
}
void
AudioSinkWrapper::Start(int64_t aStartTime, const MediaInfo& aInfo)
{
AssertOwnerThread();
MOZ_ASSERT(!mIsStarted, "playback already started.");
mIsStarted = true;
mPlayDuration = aStartTime;
mPlayStartTime = TimeStamp::Now();
// no audio is equivalent to audio ended before video starts.
mAudioEnded = !aInfo.HasAudio();
if (aInfo.HasAudio()) {
mAudioSink = mCreator->Create();
mEndPromise = mAudioSink->Init(mParams);
mAudioSinkPromise.Begin(mEndPromise->Then(
mOwnerThread.get(), __func__, this,
&AudioSinkWrapper::OnAudioEnded,
&AudioSinkWrapper::OnAudioEnded));
}
}
void
AudioSinkWrapper::Stop()
{
AssertOwnerThread();
MOZ_ASSERT(mIsStarted, "playback not started.");
mIsStarted = false;
mAudioEnded = true;
if (mAudioSink) {
mAudioSinkPromise.DisconnectIfExists();
mAudioSink->Shutdown();
mAudioSink = nullptr;
mEndPromise = nullptr;
}
}
bool
AudioSinkWrapper::IsStarted() const
{
AssertOwnerThread();
return mIsStarted;
}
bool
AudioSinkWrapper::IsPlaying() const
{
AssertOwnerThread();
return IsStarted() && !mPlayStartTime.IsNull();
}
void
AudioSinkWrapper::OnAudioEnded()
{
AssertOwnerThread();
mAudioSinkPromise.Complete();
mPlayDuration = GetPosition();
if (!mPlayStartTime.IsNull()) {
mPlayStartTime = TimeStamp::Now();
}
mAudioEnded = true;
}
} // namespace media
} // namespace mozilla

View file

@ -0,0 +1,108 @@
/* -*- 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/. */
#ifndef AudioSinkWrapper_h_
#define AudioSinkWrapper_h_
#include "mozilla/AbstractThread.h"
#include "mozilla/dom/AudioChannelBinding.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "MediaSink.h"
namespace mozilla {
class MediaData;
template <class T> class MediaQueue;
namespace media {
class AudioSink;
/**
* A wrapper around AudioSink to provide the interface of MediaSink.
*/
class AudioSinkWrapper : public MediaSink {
// An AudioSink factory.
class Creator {
public:
virtual ~Creator() {}
virtual AudioSink* Create() = 0;
};
// Wrap around a function object which creates AudioSinks.
template <typename Function>
class CreatorImpl : public Creator {
public:
explicit CreatorImpl(const Function& aFunc) : mFunction(aFunc) {}
AudioSink* Create() override { return mFunction(); }
private:
Function mFunction;
};
public:
template <typename Function>
AudioSinkWrapper(AbstractThread* aOwnerThread, const Function& aFunc)
: mOwnerThread(aOwnerThread)
, mCreator(new CreatorImpl<Function>(aFunc))
, mIsStarted(false)
// Give an insane value to facilitate debug if used before playback starts.
, mPlayDuration(INT64_MAX)
, mAudioEnded(true)
{}
const PlaybackParams& GetPlaybackParams() const override;
void SetPlaybackParams(const PlaybackParams& aParams) override;
RefPtr<GenericPromise> OnEnded(TrackType aType) override;
int64_t GetEndTime(TrackType aType) const override;
int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override;
bool HasUnplayedFrames(TrackType aType) const override;
void SetVolume(double aVolume) override;
void SetPlaybackRate(double aPlaybackRate) override;
void SetPreservesPitch(bool aPreservesPitch) override;
void SetPlaying(bool aPlaying) override;
void Start(int64_t aStartTime, const MediaInfo& aInfo) override;
void Stop() override;
bool IsStarted() const override;
bool IsPlaying() const override;
void Shutdown() override;
private:
virtual ~AudioSinkWrapper();
void AssertOwnerThread() const {
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
}
int64_t GetVideoPosition(TimeStamp aNow) const;
void OnAudioEnded();
const RefPtr<AbstractThread> mOwnerThread;
UniquePtr<Creator> mCreator;
RefPtr<AudioSink> mAudioSink;
RefPtr<GenericPromise> mEndPromise;
bool mIsStarted;
PlaybackParams mParams;
TimeStamp mPlayStartTime;
int64_t mPlayDuration;
bool mAudioEnded;
MozPromiseRequestHolder<GenericPromise> mAudioSinkPromise;
};
} // namespace media
} // namespace mozilla
#endif //AudioSinkWrapper_h_

View file

@ -0,0 +1,561 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "nsPrintfCString.h"
#include "MediaQueue.h"
#include "DecodedAudioDataSink.h"
#include "VideoUtils.h"
#include "AudioConverter.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "MediaPrefs.h"
namespace mozilla {
extern LazyLogModule gMediaDecoderLog;
#define SINK_LOG(msg, ...) \
MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, \
("DecodedAudioDataSink=%p " msg, this, ##__VA_ARGS__))
#define SINK_LOG_V(msg, ...) \
MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, \
("DecodedAudioDataSink=%p " msg, this, ##__VA_ARGS__))
namespace media {
// The amount of audio frames that is used to fuzz rounding errors.
static const int64_t AUDIO_FUZZ_FRAMES = 1;
// Amount of audio frames we will be processing ahead of use
static const int32_t LOW_AUDIO_USECS = 300000;
DecodedAudioDataSink::DecodedAudioDataSink(AbstractThread* aThread,
MediaQueue<MediaData>& aAudioQueue,
int64_t aStartTime,
const AudioInfo& aInfo,
dom::AudioChannel aChannel)
: AudioSink(aAudioQueue)
, mStartTime(aStartTime)
, mLastGoodPosition(0)
, mInfo(aInfo)
, mChannel(aChannel)
, mPlaying(true)
, mMonitor("DecodedAudioDataSink")
, mWritten(0)
, mErrored(false)
, mPlaybackComplete(false)
, mOwnerThread(aThread)
, mProcessedQueueLength(0)
, mFramesParsed(0)
, mLastEndTime(0)
, mIsAudioDataAudible(false)
{
bool resampling = MediaPrefs::AudioSinkResampling();
if (resampling) {
mOutputRate = MediaPrefs::AudioSinkResampleRate();
} else if (mInfo.mRate == 44100 || mInfo.mRate == 48000) {
// The original rate is of good quality and we want to minimize unecessary
// resampling. The common scenario being that the sampling rate is one or
// the other, this allows to minimize audio quality regression and hoping
// content provider want change from those rates mid-stream.
mOutputRate = mInfo.mRate;
} else {
// We will resample all data to match cubeb's preferred sampling rate.
mOutputRate = AudioStream::GetPreferredRate();
}
MOZ_DIAGNOSTIC_ASSERT(mOutputRate, "output rate can't be 0.");
bool monoAudioEnabled = MediaPrefs::MonoAudio();
mOutputChannels = monoAudioEnabled
? 1 : (MediaPrefs::AudioSinkForceStereo() ? 2 : mInfo.mChannels);
}
DecodedAudioDataSink::~DecodedAudioDataSink()
{
}
RefPtr<GenericPromise>
DecodedAudioDataSink::Init(const PlaybackParams& aParams)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
mAudioQueueListener = mAudioQueue.PushEvent().Connect(
mOwnerThread, this, &DecodedAudioDataSink::OnAudioPushed);
mAudioQueueFinishListener = mAudioQueue.FinishEvent().Connect(
mOwnerThread, this, &DecodedAudioDataSink::NotifyAudioNeeded);
mProcessedQueueListener = mProcessedQueue.PopEvent().Connect(
mOwnerThread, this, &DecodedAudioDataSink::OnAudioPopped);
// To ensure at least one audio packet will be popped from AudioQueue and
// ready to be played.
NotifyAudioNeeded();
RefPtr<GenericPromise> p = mEndPromise.Ensure(__func__);
nsresult rv = InitializeAudioStream(aParams);
if (NS_FAILED(rv)) {
mEndPromise.Reject(rv, __func__);
}
return p;
}
int64_t
DecodedAudioDataSink::GetPosition()
{
int64_t pos;
if (mAudioStream &&
(pos = mAudioStream->GetPosition()) >= 0) {
NS_ASSERTION(pos >= mLastGoodPosition,
"AudioStream position shouldn't go backward");
// Update the last good position when we got a good one.
if (pos >= mLastGoodPosition) {
mLastGoodPosition = pos;
}
}
return mStartTime + mLastGoodPosition;
}
bool
DecodedAudioDataSink::HasUnplayedFrames()
{
// Experimentation suggests that GetPositionInFrames() is zero-indexed,
// so we need to add 1 here before comparing it to mWritten.
int64_t total;
{
MonitorAutoLock mon(mMonitor);
total = mWritten + (mCursor.get() ? mCursor->Available() : 0);
}
return mProcessedQueue.GetSize() ||
(mAudioStream && mAudioStream->GetPositionInFrames() + 1 < total);
}
void
DecodedAudioDataSink::Shutdown()
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
mAudioQueueListener.Disconnect();
mAudioQueueFinishListener.Disconnect();
mProcessedQueueListener.Disconnect();
if (mAudioStream) {
mAudioStream->Shutdown();
mAudioStream = nullptr;
}
mProcessedQueue.Reset();
mProcessedQueue.Finish();
mEndPromise.ResolveIfExists(true, __func__);
}
void
DecodedAudioDataSink::SetVolume(double aVolume)
{
if (mAudioStream) {
mAudioStream->SetVolume(aVolume);
}
}
void
DecodedAudioDataSink::SetPlaybackRate(double aPlaybackRate)
{
MOZ_ASSERT(aPlaybackRate != 0, "Don't set the playbackRate to 0 on AudioStream");
if (mAudioStream) {
mAudioStream->SetPlaybackRate(aPlaybackRate);
}
}
void
DecodedAudioDataSink::SetPreservesPitch(bool aPreservesPitch)
{
if (mAudioStream) {
mAudioStream->SetPreservesPitch(aPreservesPitch);
}
}
void
DecodedAudioDataSink::SetPlaying(bool aPlaying)
{
if (!mAudioStream || mPlaying == aPlaying || mPlaybackComplete) {
return;
}
// pause/resume AudioStream as necessary.
if (!aPlaying) {
mAudioStream->Pause();
} else if (aPlaying) {
mAudioStream->Resume();
}
mPlaying = aPlaying;
}
nsresult
DecodedAudioDataSink::InitializeAudioStream(const PlaybackParams& aParams)
{
mAudioStream = new AudioStream(*this);
nsresult rv = mAudioStream->Init(mOutputChannels, mOutputRate, mChannel);
if (NS_FAILED(rv)) {
mAudioStream->Shutdown();
mAudioStream = nullptr;
return rv;
}
// Set playback params before calling Start() so they can take effect
// as soon as the 1st DataCallback of the AudioStream fires.
mAudioStream->SetVolume(aParams.mVolume);
mAudioStream->SetPlaybackRate(aParams.mPlaybackRate);
mAudioStream->SetPreservesPitch(aParams.mPreservesPitch);
mAudioStream->Start();
return NS_OK;
}
int64_t
DecodedAudioDataSink::GetEndTime() const
{
int64_t written;
{
MonitorAutoLock mon(mMonitor);
written = mWritten;
}
CheckedInt64 playedUsecs = FramesToUsecs(written, mOutputRate) + mStartTime;
if (!playedUsecs.isValid()) {
NS_WARNING("Int overflow calculating audio end time");
return -1;
}
// As we may be resampling, rounding errors may occur. Ensure we never get
// past the original end time.
return std::min<int64_t>(mLastEndTime, playedUsecs.value());
}
UniquePtr<AudioStream::Chunk>
DecodedAudioDataSink::PopFrames(uint32_t aFrames)
{
class Chunk : public AudioStream::Chunk {
public:
Chunk(AudioData* aBuffer, uint32_t aFrames, AudioDataValue* aData)
: mBuffer(aBuffer), mFrames(aFrames), mData(aData) {}
Chunk() : mFrames(0), mData(nullptr) {}
const AudioDataValue* Data() const { return mData; }
uint32_t Frames() const { return mFrames; }
uint32_t Channels() const { return mBuffer ? mBuffer->mChannels: 0; }
uint32_t Rate() const { return mBuffer ? mBuffer->mRate : 0; }
AudioDataValue* GetWritable() const { return mData; }
private:
const RefPtr<AudioData> mBuffer;
const uint32_t mFrames;
AudioDataValue* const mData;
};
class SilentChunk : public AudioStream::Chunk {
public:
SilentChunk(uint32_t aFrames, uint32_t aChannels, uint32_t aRate)
: mFrames(aFrames)
, mChannels(aChannels)
, mRate(aRate)
, mData(MakeUnique<AudioDataValue[]>(aChannels * aFrames)) {
memset(mData.get(), 0, aChannels * aFrames * sizeof(AudioDataValue));
}
const AudioDataValue* Data() const { return mData.get(); }
uint32_t Frames() const { return mFrames; }
uint32_t Channels() const { return mChannels; }
uint32_t Rate() const { return mRate; }
AudioDataValue* GetWritable() const { return mData.get(); }
private:
const uint32_t mFrames;
const uint32_t mChannels;
const uint32_t mRate;
UniquePtr<AudioDataValue[]> mData;
};
bool needPopping = false;
if (!mCurrentData) {
// No data in the queue. Return an empty chunk.
if (!mProcessedQueue.GetSize()) {
return MakeUnique<Chunk>();
}
// We need to update our values prior popping the processed queue in
// order to prevent the pop event to fire too early (prior
// mProcessedQueueLength being updated) or prevent HasUnplayedFrames
// to incorrectly return true during the time interval betweeen the
// when mProcessedQueue is read and mWritten is updated.
needPopping = true;
mCurrentData = mProcessedQueue.PeekFront();
{
MonitorAutoLock mon(mMonitor);
mCursor = MakeUnique<AudioBufferCursor>(mCurrentData->mAudioData.get(),
mCurrentData->mChannels,
mCurrentData->mFrames);
}
MOZ_ASSERT(mCurrentData->mFrames > 0);
mProcessedQueueLength -=
FramesToUsecs(mCurrentData->mFrames, mOutputRate).value();
}
auto framesToPop = std::min(aFrames, mCursor->Available());
SINK_LOG_V("playing audio at time=%lld offset=%u length=%u",
mCurrentData->mTime, mCurrentData->mFrames - mCursor->Available(), framesToPop);
UniquePtr<AudioStream::Chunk> chunk =
MakeUnique<Chunk>(mCurrentData, framesToPop, mCursor->Ptr());
{
MonitorAutoLock mon(mMonitor);
mWritten += framesToPop;
mCursor->Advance(framesToPop);
}
// All frames are popped. Reset mCurrentData so we can pop new elements from
// the audio queue in next calls to PopFrames().
if (!mCursor->Available()) {
mCurrentData = nullptr;
}
if (needPopping) {
// We can now safely pop the audio packet from the processed queue.
// This will fire the popped event, triggering a call to NotifyAudioNeeded.
RefPtr<AudioData> releaseMe = mProcessedQueue.PopFront();
CheckIsAudible(releaseMe);
}
return chunk;
}
bool
DecodedAudioDataSink::Ended() const
{
// Return true when error encountered so AudioStream can start draining.
return mProcessedQueue.IsFinished() || mErrored;
}
void
DecodedAudioDataSink::Drained()
{
SINK_LOG("Drained");
mPlaybackComplete = true;
mEndPromise.ResolveIfExists(true, __func__);
}
void
DecodedAudioDataSink::CheckIsAudible(const AudioData* aData)
{
MOZ_ASSERT(aData);
bool isAudible = aData->IsAudible();
if (isAudible != mIsAudioDataAudible) {
mIsAudioDataAudible = isAudible;
mAudibleEvent.Notify(mIsAudioDataAudible);
}
}
void
DecodedAudioDataSink::OnAudioPopped(const RefPtr<MediaData>& aSample)
{
SINK_LOG_V("AudioStream has used an audio packet.");
NotifyAudioNeeded();
}
void
DecodedAudioDataSink::OnAudioPushed(const RefPtr<MediaData>& aSample)
{
SINK_LOG_V("One new audio packet available.");
NotifyAudioNeeded();
}
void
DecodedAudioDataSink::NotifyAudioNeeded()
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn(),
"Not called from the owner's thread");
// Always ensure we have two processed frames pending to allow for processing
// latency.
while (AudioQueue().GetSize() && (AudioQueue().IsFinished() ||
mProcessedQueueLength < LOW_AUDIO_USECS ||
mProcessedQueue.GetSize() < 2)) {
RefPtr<AudioData> data =
dont_AddRef(AudioQueue().PopFront().take()->As<AudioData>());
// Ignore the element with 0 frames and try next.
if (!data->mFrames) {
continue;
}
if (!mConverter ||
(data->mRate != mConverter->InputConfig().Rate() ||
data->mChannels != mConverter->InputConfig().Channels())) {
SINK_LOG_V("Audio format changed from %u@%uHz to %u@%uHz",
mConverter? mConverter->InputConfig().Channels() : 0,
mConverter ? mConverter->InputConfig().Rate() : 0,
data->mChannels, data->mRate);
DrainConverter();
// mFramesParsed indicates the current playtime in frames at the current
// input sampling rate. Recalculate it per the new sampling rate.
if (mFramesParsed) {
// We minimize overflow.
uint32_t oldRate = mConverter->InputConfig().Rate();
uint32_t newRate = data->mRate;
CheckedInt64 result = SaferMultDiv(mFramesParsed, newRate, oldRate);
if (!result.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return;
}
mFramesParsed = result.value();
}
mConverter =
MakeUnique<AudioConverter>(
AudioConfig(data->mChannels, data->mRate),
AudioConfig(mOutputChannels, mOutputRate));
}
// See if there's a gap in the audio. If there is, push silence into the
// audio hardware, so we can play across the gap.
// Calculate the timestamp of the next chunk of audio in numbers of
// samples.
CheckedInt64 sampleTime = UsecsToFrames(data->mTime - mStartTime,
data->mRate);
// Calculate the number of frames that have been pushed onto the audio hardware.
CheckedInt64 missingFrames = sampleTime - mFramesParsed;
if (!missingFrames.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return;
}
if (missingFrames.value() > AUDIO_FUZZ_FRAMES) {
// The next audio packet begins some time after the end of the last packet
// we pushed to the audio hardware. We must push silence into the audio
// hardware so that the next audio packet begins playback at the correct
// time.
missingFrames = std::min<int64_t>(INT32_MAX, missingFrames.value());
mFramesParsed += missingFrames.value();
// We need to calculate how many frames are missing at the output rate.
missingFrames =
SaferMultDiv(missingFrames.value(), mOutputRate, data->mRate);
if (!missingFrames.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return;
}
// We need to insert silence, first use drained frames if any.
missingFrames -= DrainConverter(missingFrames.value());
// Insert silence if still needed.
if (missingFrames.value()) {
AlignedAudioBuffer silenceData(missingFrames.value() * mOutputChannels);
if (!silenceData) {
NS_WARNING("OOM in DecodedAudioDataSink");
mErrored = true;
return;
}
RefPtr<AudioData> silence = CreateAudioFromBuffer(Move(silenceData), data);
PushProcessedAudio(silence);
}
}
mLastEndTime = data->GetEndTime();
mFramesParsed += data->mFrames;
if (mConverter->InputConfig() != mConverter->OutputConfig()) {
// We must ensure that the size in the buffer contains exactly the number
// of frames, in case one of the audio producer over allocated the buffer.
AlignedAudioBuffer buffer(Move(data->mAudioData));
buffer.SetLength(size_t(data->mFrames) * data->mChannels);
AlignedAudioBuffer convertedData =
mConverter->Process(AudioSampleBuffer(Move(buffer))).Forget();
data = CreateAudioFromBuffer(Move(convertedData), data);
}
if (PushProcessedAudio(data)) {
mLastProcessedPacket = Some(data);
}
}
if (AudioQueue().IsFinished()) {
// We have reached the end of the data, drain the resampler.
DrainConverter();
mProcessedQueue.Finish();
}
}
uint32_t
DecodedAudioDataSink::PushProcessedAudio(AudioData* aData)
{
if (!aData || !aData->mFrames) {
return 0;
}
mProcessedQueue.Push(aData);
mProcessedQueueLength += FramesToUsecs(aData->mFrames, mOutputRate).value();
return aData->mFrames;
}
already_AddRefed<AudioData>
DecodedAudioDataSink::CreateAudioFromBuffer(AlignedAudioBuffer&& aBuffer,
AudioData* aReference)
{
uint32_t frames = aBuffer.Length() / mOutputChannels;
if (!frames) {
return nullptr;
}
CheckedInt64 duration = FramesToUsecs(frames, mOutputRate);
if (!duration.isValid()) {
NS_WARNING("Int overflow in DecodedAudioDataSink");
mErrored = true;
return nullptr;
}
RefPtr<AudioData> data =
new AudioData(aReference->mOffset,
aReference->mTime,
duration.value(),
frames,
Move(aBuffer),
mOutputChannels,
mOutputRate);
return data.forget();
}
uint32_t
DecodedAudioDataSink::DrainConverter(uint32_t aMaxFrames)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
if (!mConverter || !mLastProcessedPacket || !aMaxFrames) {
// nothing to drain.
return 0;
}
RefPtr<AudioData> lastPacket = mLastProcessedPacket.ref();
mLastProcessedPacket.reset();
// To drain we simply provide an empty packet to the audio converter.
AlignedAudioBuffer convertedData =
mConverter->Process(AudioSampleBuffer(AlignedAudioBuffer())).Forget();
uint32_t frames = convertedData.Length() / mOutputChannels;
if (!convertedData.SetLength(std::min(frames, aMaxFrames) * mOutputChannels)) {
// This can never happen as we were reducing the length of convertData.
mErrored = true;
return 0;
}
RefPtr<AudioData> data =
CreateAudioFromBuffer(Move(convertedData), lastPacket);
if (!data) {
return 0;
}
mProcessedQueue.Push(data);
return data->mFrames;
}
} // namespace media
} // namespace mozilla

View file

@ -0,0 +1,165 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(DecodedAudioDataSink_h__)
#define DecodedAudioDataSink_h__
#include "AudioSink.h"
#include "AudioStream.h"
#include "MediaEventSource.h"
#include "MediaQueue.h"
#include "MediaInfo.h"
#include "mozilla/RefPtr.h"
#include "nsISupportsImpl.h"
#include "mozilla/dom/AudioChannelBinding.h"
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Monitor.h"
namespace mozilla {
class AudioConverter;
namespace media {
class DecodedAudioDataSink : public AudioSink,
private AudioStream::DataSource {
public:
DecodedAudioDataSink(AbstractThread* aThread,
MediaQueue<MediaData>& aAudioQueue,
int64_t aStartTime,
const AudioInfo& aInfo,
dom::AudioChannel aChannel);
// Return a promise which will be resolved when DecodedAudioDataSink
// finishes playing, or rejected if any error.
RefPtr<GenericPromise> Init(const PlaybackParams& aParams) override;
/*
* All public functions are not thread-safe.
* Called on the task queue of MDSM only.
*/
int64_t GetPosition() override;
int64_t GetEndTime() const override;
// Check whether we've pushed more frames to the audio hardware than it has
// played.
bool HasUnplayedFrames() override;
// Shut down the DecodedAudioDataSink's resources.
void Shutdown() override;
void SetVolume(double aVolume) override;
void SetPlaybackRate(double aPlaybackRate) override;
void SetPreservesPitch(bool aPreservesPitch) override;
void SetPlaying(bool aPlaying) override;
MediaEventSource<bool>& AudibleEvent() {
return mAudibleEvent;
}
private:
virtual ~DecodedAudioDataSink();
// Allocate and initialize mAudioStream. Returns NS_OK on success.
nsresult InitializeAudioStream(const PlaybackParams& aParams);
// Interface of AudioStream::DataSource.
// Called on the callback thread of cubeb.
UniquePtr<AudioStream::Chunk> PopFrames(uint32_t aFrames) override;
bool Ended() const override;
void Drained() override;
void CheckIsAudible(const AudioData* aData);
// The audio stream resource. Used on the task queue of MDSM only.
RefPtr<AudioStream> mAudioStream;
// The presentation time of the first audio frame that was played in
// microseconds. We can add this to the audio stream position to determine
// the current audio time.
const int64_t mStartTime;
// Keep the last good position returned from the audio stream. Used to ensure
// position returned by GetPosition() is mono-increasing in spite of audio
// stream error. Used on the task queue of MDSM only.
int64_t mLastGoodPosition;
const AudioInfo mInfo;
const dom::AudioChannel mChannel;
// Used on the task queue of MDSM only.
bool mPlaying;
MozPromiseHolder<GenericPromise> mEndPromise;
/*
* Members to implement AudioStream::DataSource.
* Used on the callback thread of cubeb.
*/
// The AudioData at which AudioStream::DataSource is reading.
RefPtr<AudioData> mCurrentData;
// Monitor protecting access to mCursor and mWritten.
// mCursor is created/destroyed on the cubeb thread, while we must also
// ensure that mWritten and mCursor::Available() get modified simultaneously.
// (written on cubeb thread, and read on MDSM task queue).
mutable Monitor mMonitor;
// Keep track of the read position of mCurrentData.
UniquePtr<AudioBufferCursor> mCursor;
// PCM frames written to the stream so far.
int64_t mWritten;
// True if there is any error in processing audio data like overflow.
Atomic<bool> mErrored;
// Set on the callback thread of cubeb once the stream has drained.
Atomic<bool> mPlaybackComplete;
const RefPtr<AbstractThread> mOwnerThread;
// Audio Processing objects and methods
void OnAudioPopped(const RefPtr<MediaData>& aSample);
void OnAudioPushed(const RefPtr<MediaData>& aSample);
void NotifyAudioNeeded();
// Drain the converter and add the output to the processed audio queue.
// A maximum of aMaxFrames will be added.
uint32_t DrainConverter(uint32_t aMaxFrames = UINT32_MAX);
already_AddRefed<AudioData> CreateAudioFromBuffer(AlignedAudioBuffer&& aBuffer,
AudioData* aReference);
// Add data to the processsed queue, update mProcessedQueueLength and
// return the number of frames added.
uint32_t PushProcessedAudio(AudioData* aData);
UniquePtr<AudioConverter> mConverter;
MediaQueue<AudioData> mProcessedQueue;
// Length in microseconds of the ProcessedQueue
Atomic<int32_t> mProcessedQueueLength;
MediaEventListener mAudioQueueListener;
MediaEventListener mAudioQueueFinishListener;
MediaEventListener mProcessedQueueListener;
// Number of frames processed from AudioQueue(). Used to determine gaps in
// the input stream. It indicates the time in frames since playback started
// at the current input framerate.
int64_t mFramesParsed;
Maybe<RefPtr<AudioData>> mLastProcessedPacket;
int64_t mLastEndTime;
// Never modifed after construction.
uint32_t mOutputRate;
uint32_t mOutputChannels;
// True when audio is producing audible sound, false when audio is silent.
bool mIsAudioDataAudible;
MediaEventProducer<bool> mAudibleEvent;
};
} // namespace media
} // namespace mozilla
#endif

View file

@ -0,0 +1,781 @@
/* -*- 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 "mozilla/CheckedInt.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/SyncRunnable.h"
#include "AudioSegment.h"
#include "DecodedStream.h"
#include "MediaData.h"
#include "MediaQueue.h"
#include "MediaStreamGraph.h"
#include "MediaStreamListener.h"
#include "OutputStreamManager.h"
#include "SharedBuffer.h"
#include "VideoSegment.h"
#include "VideoUtils.h"
namespace mozilla {
#undef DUMP_LOG
#define DUMP_LOG(x, ...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(x, ##__VA_ARGS__).get(), nullptr, nullptr, -1)
/*
* A container class to make it easier to pass the playback info all the
* way to DecodedStreamGraphListener from DecodedStream.
*/
struct PlaybackInfoInit {
int64_t mStartTime;
MediaInfo mInfo;
};
class DecodedStreamGraphListener : public MediaStreamListener {
public:
DecodedStreamGraphListener(MediaStream* aStream,
MozPromiseHolder<GenericPromise>&& aPromise)
: mMutex("DecodedStreamGraphListener::mMutex")
, mStream(aStream)
{
mFinishPromise = Move(aPromise);
}
void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) override
{
MutexAutoLock lock(mMutex);
if (mStream) {
int64_t t = mStream->StreamTimeToMicroseconds(
mStream->GraphTimeToStreamTime(aCurrentTime));
mOnOutput.Notify(t);
}
}
void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override
{
if (event == MediaStreamGraphEvent::EVENT_FINISHED) {
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
}
}
void DoNotifyFinished()
{
MOZ_ASSERT(NS_IsMainThread());
mFinishPromise.ResolveIfExists(true, __func__);
}
void Forget()
{
RefPtr<DecodedStreamGraphListener> self = this;
AbstractThread::MainThread()->Dispatch(NS_NewRunnableFunction([self] () {
MOZ_ASSERT(NS_IsMainThread());
self->mFinishPromise.ResolveIfExists(true, __func__);
}));
MutexAutoLock lock(mMutex);
mStream = nullptr;
}
MediaEventSource<int64_t>& OnOutput()
{
return mOnOutput;
}
private:
MediaEventProducer<int64_t> mOnOutput;
Mutex mMutex;
// Members below are protected by mMutex.
RefPtr<MediaStream> mStream;
// Main thread only.
MozPromiseHolder<GenericPromise> mFinishPromise;
};
static void
UpdateStreamSuspended(MediaStream* aStream, bool aBlocking)
{
if (NS_IsMainThread()) {
if (aBlocking) {
aStream->Suspend();
} else {
aStream->Resume();
}
} else {
nsCOMPtr<nsIRunnable> r;
if (aBlocking) {
r = NewRunnableMethod(aStream, &MediaStream::Suspend);
} else {
r = NewRunnableMethod(aStream, &MediaStream::Resume);
}
AbstractThread::MainThread()->Dispatch(r.forget());
}
}
/*
* All MediaStream-related data is protected by the decoder's monitor.
* We have at most one DecodedStreamDaata per MediaDecoder. Its stream
* is used as the input for each ProcessedMediaStream created by calls to
* captureStream(UntilEnded). Seeking creates a new source stream, as does
* replaying after the input as ended. In the latter case, the new source is
* not connected to streams created by captureStreamUntilEnded.
*/
class DecodedStreamData {
public:
DecodedStreamData(OutputStreamManager* aOutputStreamManager,
PlaybackInfoInit&& aInit,
MozPromiseHolder<GenericPromise>&& aPromise);
~DecodedStreamData();
void SetPlaying(bool aPlaying);
MediaEventSource<int64_t>& OnOutput();
void Forget();
void DumpDebugInfo();
/* The following group of fields are protected by the decoder's monitor
* and can be read or written on any thread.
*/
// Count of audio frames written to the stream
int64_t mAudioFramesWritten;
// mNextVideoTime is the end timestamp for the last packet sent to the stream.
// Therefore video packets starting at or after this time need to be copied
// to the output stream.
int64_t mNextVideoTime; // microseconds
int64_t mNextAudioTime; // microseconds
// The last video image sent to the stream. Useful if we need to replicate
// the image.
RefPtr<layers::Image> mLastVideoImage;
gfx::IntSize mLastVideoImageDisplaySize;
bool mHaveSentFinish;
bool mHaveSentFinishAudio;
bool mHaveSentFinishVideo;
// The decoder is responsible for calling Destroy() on this stream.
const RefPtr<SourceMediaStream> mStream;
const RefPtr<DecodedStreamGraphListener> mListener;
bool mPlaying;
// True if we need to send a compensation video frame to ensure the
// StreamTime going forward.
bool mEOSVideoCompensation;
const RefPtr<OutputStreamManager> mOutputStreamManager;
};
DecodedStreamData::DecodedStreamData(OutputStreamManager* aOutputStreamManager,
PlaybackInfoInit&& aInit,
MozPromiseHolder<GenericPromise>&& aPromise)
: mAudioFramesWritten(0)
, mNextVideoTime(aInit.mStartTime)
, mNextAudioTime(aInit.mStartTime)
, mHaveSentFinish(false)
, mHaveSentFinishAudio(false)
, mHaveSentFinishVideo(false)
, mStream(aOutputStreamManager->Graph()->CreateSourceStream())
// DecodedStreamGraphListener will resolve this promise.
, mListener(new DecodedStreamGraphListener(mStream, Move(aPromise)))
// mPlaying is initially true because MDSM won't start playback until playing
// becomes true. This is consistent with the settings of AudioSink.
, mPlaying(true)
, mEOSVideoCompensation(false)
, mOutputStreamManager(aOutputStreamManager)
{
mStream->AddListener(mListener);
mOutputStreamManager->Connect(mStream);
// Initialize tracks.
if (aInit.mInfo.HasAudio()) {
mStream->AddAudioTrack(aInit.mInfo.mAudio.mTrackId,
aInit.mInfo.mAudio.mRate,
0, new AudioSegment());
}
if (aInit.mInfo.HasVideo()) {
mStream->AddTrack(aInit.mInfo.mVideo.mTrackId, 0, new VideoSegment());
}
}
DecodedStreamData::~DecodedStreamData()
{
mOutputStreamManager->Disconnect();
mStream->Destroy();
}
MediaEventSource<int64_t>&
DecodedStreamData::OnOutput()
{
return mListener->OnOutput();
}
void
DecodedStreamData::SetPlaying(bool aPlaying)
{
if (mPlaying != aPlaying) {
mPlaying = aPlaying;
UpdateStreamSuspended(mStream, !mPlaying);
}
}
void
DecodedStreamData::Forget()
{
mListener->Forget();
}
void
DecodedStreamData::DumpDebugInfo()
{
DUMP_LOG(
"DecodedStreamData=%p mPlaying=%d mAudioFramesWritten=%lld"
"mNextAudioTime=%lld mNextVideoTime=%lld mHaveSentFinish=%d"
"mHaveSentFinishAudio=%d mHaveSentFinishVideo=%d",
this, mPlaying, mAudioFramesWritten, mNextAudioTime, mNextVideoTime,
mHaveSentFinish, mHaveSentFinishAudio, mHaveSentFinishVideo);
}
DecodedStream::DecodedStream(AbstractThread* aOwnerThread,
MediaQueue<MediaData>& aAudioQueue,
MediaQueue<MediaData>& aVideoQueue,
OutputStreamManager* aOutputStreamManager,
const bool& aSameOrigin,
const PrincipalHandle& aPrincipalHandle)
: mOwnerThread(aOwnerThread)
, mOutputStreamManager(aOutputStreamManager)
, mPlaying(false)
, mSameOrigin(aSameOrigin)
, mPrincipalHandle(aPrincipalHandle)
, mAudioQueue(aAudioQueue)
, mVideoQueue(aVideoQueue)
{
}
DecodedStream::~DecodedStream()
{
MOZ_ASSERT(mStartTime.isNothing(), "playback should've ended.");
}
const media::MediaSink::PlaybackParams&
DecodedStream::GetPlaybackParams() const
{
AssertOwnerThread();
return mParams;
}
void
DecodedStream::SetPlaybackParams(const PlaybackParams& aParams)
{
AssertOwnerThread();
mParams = aParams;
}
RefPtr<GenericPromise>
DecodedStream::OnEnded(TrackType aType)
{
AssertOwnerThread();
MOZ_ASSERT(mStartTime.isSome());
if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio()) {
// TODO: we should return a promise which is resolved when the audio track
// is finished. For now this promise is resolved when the whole stream is
// finished.
return mFinishPromise;
} else if (aType == TrackInfo::kVideoTrack && mInfo.HasVideo()) {
return mFinishPromise;
}
return nullptr;
}
void
DecodedStream::Start(int64_t aStartTime, const MediaInfo& aInfo)
{
AssertOwnerThread();
MOZ_ASSERT(mStartTime.isNothing(), "playback already started.");
mStartTime.emplace(aStartTime);
mLastOutputTime = 0;
mInfo = aInfo;
mPlaying = true;
ConnectListener();
class R : public Runnable {
typedef MozPromiseHolder<GenericPromise> Promise;
public:
R(PlaybackInfoInit&& aInit, Promise&& aPromise, OutputStreamManager* aManager)
: mInit(Move(aInit)), mOutputStreamManager(aManager)
{
mPromise = Move(aPromise);
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
// No need to create a source stream when there are no output streams. This
// happens when RemoveOutput() is called immediately after StartPlayback().
if (!mOutputStreamManager->Graph()) {
// Resolve the promise to indicate the end of playback.
mPromise.Resolve(true, __func__);
return NS_OK;
}
mData = MakeUnique<DecodedStreamData>(
mOutputStreamManager, Move(mInit), Move(mPromise));
return NS_OK;
}
UniquePtr<DecodedStreamData> ReleaseData()
{
return Move(mData);
}
private:
PlaybackInfoInit mInit;
Promise mPromise;
RefPtr<OutputStreamManager> mOutputStreamManager;
UniquePtr<DecodedStreamData> mData;
};
MozPromiseHolder<GenericPromise> promise;
mFinishPromise = promise.Ensure(__func__);
PlaybackInfoInit init {
aStartTime, aInfo
};
nsCOMPtr<nsIRunnable> r = new R(Move(init), Move(promise), mOutputStreamManager);
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
SyncRunnable::DispatchToThread(mainThread, r);
mData = static_cast<R*>(r.get())->ReleaseData();
if (mData) {
mOutputListener = mData->OnOutput().Connect(
mOwnerThread, this, &DecodedStream::NotifyOutput);
mData->SetPlaying(mPlaying);
SendData();
}
}
void
DecodedStream::Stop()
{
AssertOwnerThread();
MOZ_ASSERT(mStartTime.isSome(), "playback not started.");
mStartTime.reset();
DisconnectListener();
mFinishPromise = nullptr;
// Clear mData immediately when this playback session ends so we won't
// send data to the wrong stream in SendData() in next playback session.
DestroyData(Move(mData));
}
bool
DecodedStream::IsStarted() const
{
AssertOwnerThread();
return mStartTime.isSome();
}
bool
DecodedStream::IsPlaying() const
{
AssertOwnerThread();
return IsStarted() && mPlaying;
}
void
DecodedStream::DestroyData(UniquePtr<DecodedStreamData> aData)
{
AssertOwnerThread();
if (!aData) {
return;
}
mOutputListener.Disconnect();
DecodedStreamData* data = aData.release();
data->Forget();
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
delete data;
});
AbstractThread::MainThread()->Dispatch(r.forget());
}
void
DecodedStream::SetPlaying(bool aPlaying)
{
AssertOwnerThread();
// Resume/pause matters only when playback started.
if (mStartTime.isNothing()) {
return;
}
mPlaying = aPlaying;
if (mData) {
mData->SetPlaying(aPlaying);
}
}
void
DecodedStream::SetVolume(double aVolume)
{
AssertOwnerThread();
mParams.mVolume = aVolume;
}
void
DecodedStream::SetPlaybackRate(double aPlaybackRate)
{
AssertOwnerThread();
mParams.mPlaybackRate = aPlaybackRate;
}
void
DecodedStream::SetPreservesPitch(bool aPreservesPitch)
{
AssertOwnerThread();
mParams.mPreservesPitch = aPreservesPitch;
}
static void
SendStreamAudio(DecodedStreamData* aStream, int64_t aStartTime,
MediaData* aData, AudioSegment* aOutput, uint32_t aRate,
const PrincipalHandle& aPrincipalHandle)
{
// The amount of audio frames that is used to fuzz rounding errors.
static const int64_t AUDIO_FUZZ_FRAMES = 1;
MOZ_ASSERT(aData);
AudioData* audio = aData->As<AudioData>();
// This logic has to mimic AudioSink closely to make sure we write
// the exact same silences
CheckedInt64 audioWrittenOffset = aStream->mAudioFramesWritten +
UsecsToFrames(aStartTime, aRate);
CheckedInt64 frameOffset = UsecsToFrames(audio->mTime, aRate);
if (!audioWrittenOffset.isValid() ||
!frameOffset.isValid() ||
// ignore packet that we've already processed
audio->GetEndTime() <= aStream->mNextAudioTime) {
return;
}
if (audioWrittenOffset.value() + AUDIO_FUZZ_FRAMES < frameOffset.value()) {
int64_t silentFrames = frameOffset.value() - audioWrittenOffset.value();
// Write silence to catch up
AudioSegment silence;
silence.InsertNullDataAtStart(silentFrames);
aStream->mAudioFramesWritten += silentFrames;
audioWrittenOffset += silentFrames;
aOutput->AppendFrom(&silence);
}
// Always write the whole sample without truncation to be consistent with
// DecodedAudioDataSink::PlayFromAudioQueue()
audio->EnsureAudioBuffer();
RefPtr<SharedBuffer> buffer = audio->mAudioBuffer;
AudioDataValue* bufferData = static_cast<AudioDataValue*>(buffer->Data());
AutoTArray<const AudioDataValue*, 2> channels;
for (uint32_t i = 0; i < audio->mChannels; ++i) {
channels.AppendElement(bufferData + i * audio->mFrames);
}
aOutput->AppendFrames(buffer.forget(), channels, audio->mFrames, aPrincipalHandle);
aStream->mAudioFramesWritten += audio->mFrames;
aStream->mNextAudioTime = audio->GetEndTime();
}
void
DecodedStream::SendAudio(double aVolume, bool aIsSameOrigin,
const PrincipalHandle& aPrincipalHandle)
{
AssertOwnerThread();
if (!mInfo.HasAudio()) {
return;
}
AudioSegment output;
uint32_t rate = mInfo.mAudio.mRate;
AutoTArray<RefPtr<MediaData>,10> audio;
TrackID audioTrackId = mInfo.mAudio.mTrackId;
SourceMediaStream* sourceStream = mData->mStream;
// It's OK to hold references to the AudioData because AudioData
// is ref-counted.
mAudioQueue.GetElementsAfter(mData->mNextAudioTime, &audio);
for (uint32_t i = 0; i < audio.Length(); ++i) {
SendStreamAudio(mData.get(), mStartTime.ref(), audio[i], &output, rate,
aPrincipalHandle);
}
output.ApplyVolume(aVolume);
if (!aIsSameOrigin) {
output.ReplaceWithDisabled();
}
// |mNextAudioTime| is updated as we process each audio sample in
// SendStreamAudio(). This is consistent with how |mNextVideoTime|
// is updated for video samples.
if (output.GetDuration() > 0) {
sourceStream->AppendToTrack(audioTrackId, &output);
}
if (mAudioQueue.IsFinished() && !mData->mHaveSentFinishAudio) {
sourceStream->EndTrack(audioTrackId);
mData->mHaveSentFinishAudio = true;
}
}
static void
WriteVideoToMediaStream(MediaStream* aStream,
layers::Image* aImage,
int64_t aEndMicroseconds,
int64_t aStartMicroseconds,
const mozilla::gfx::IntSize& aIntrinsicSize,
const TimeStamp& aTimeStamp,
VideoSegment* aOutput,
const PrincipalHandle& aPrincipalHandle)
{
RefPtr<layers::Image> image = aImage;
StreamTime duration =
aStream->MicrosecondsToStreamTimeRoundDown(aEndMicroseconds) -
aStream->MicrosecondsToStreamTimeRoundDown(aStartMicroseconds);
aOutput->AppendFrame(image.forget(), duration, aIntrinsicSize,
aPrincipalHandle, false, aTimeStamp);
}
static bool
ZeroDurationAtLastChunk(VideoSegment& aInput)
{
// Get the last video frame's start time in VideoSegment aInput.
// If the start time is equal to the duration of aInput, means the last video
// frame's duration is zero.
StreamTime lastVideoStratTime;
aInput.GetLastFrame(&lastVideoStratTime);
return lastVideoStratTime == aInput.GetDuration();
}
void
DecodedStream::SendVideo(bool aIsSameOrigin, const PrincipalHandle& aPrincipalHandle)
{
AssertOwnerThread();
if (!mInfo.HasVideo()) {
return;
}
VideoSegment output;
TrackID videoTrackId = mInfo.mVideo.mTrackId;
AutoTArray<RefPtr<MediaData>, 10> video;
SourceMediaStream* sourceStream = mData->mStream;
// It's OK to hold references to the VideoData because VideoData
// is ref-counted.
mVideoQueue.GetElementsAfter(mData->mNextVideoTime, &video);
// tracksStartTimeStamp might be null when the SourceMediaStream not yet
// be added to MediaStreamGraph.
TimeStamp tracksStartTimeStamp = sourceStream->GetStreamTracksStrartTimeStamp();
if (tracksStartTimeStamp.IsNull()) {
tracksStartTimeStamp = TimeStamp::Now();
}
for (uint32_t i = 0; i < video.Length(); ++i) {
VideoData* v = video[i]->As<VideoData>();
if (mData->mNextVideoTime < v->mTime) {
// Write last video frame to catch up. mLastVideoImage can be null here
// which is fine, it just means there's no video.
// TODO: |mLastVideoImage| should come from the last image rendered
// by the state machine. This will avoid the black frame when capture
// happens in the middle of playback (especially in th middle of a
// video frame). E.g. if we have a video frame that is 30 sec long
// and capture happens at 15 sec, we'll have to append a black frame
// that is 15 sec long.
WriteVideoToMediaStream(sourceStream, mData->mLastVideoImage, v->mTime,
mData->mNextVideoTime, mData->mLastVideoImageDisplaySize,
tracksStartTimeStamp + TimeDuration::FromMicroseconds(v->mTime),
&output, aPrincipalHandle);
mData->mNextVideoTime = v->mTime;
}
if (mData->mNextVideoTime < v->GetEndTime()) {
WriteVideoToMediaStream(sourceStream, v->mImage, v->GetEndTime(),
mData->mNextVideoTime, v->mDisplay,
tracksStartTimeStamp + TimeDuration::FromMicroseconds(v->GetEndTime()),
&output, aPrincipalHandle);
mData->mNextVideoTime = v->GetEndTime();
mData->mLastVideoImage = v->mImage;
mData->mLastVideoImageDisplaySize = v->mDisplay;
}
}
// Check the output is not empty.
if (output.GetLastFrame()) {
mData->mEOSVideoCompensation = ZeroDurationAtLastChunk(output);
}
if (!aIsSameOrigin) {
output.ReplaceWithDisabled();
}
if (output.GetDuration() > 0) {
sourceStream->AppendToTrack(videoTrackId, &output);
}
if (mVideoQueue.IsFinished() && !mData->mHaveSentFinishVideo) {
if (mData->mEOSVideoCompensation) {
VideoSegment endSegment;
// Calculate the deviation clock time from DecodedStream.
int64_t deviation_usec = sourceStream->StreamTimeToMicroseconds(1);
WriteVideoToMediaStream(sourceStream, mData->mLastVideoImage,
mData->mNextVideoTime + deviation_usec, mData->mNextVideoTime,
mData->mLastVideoImageDisplaySize,
tracksStartTimeStamp + TimeDuration::FromMicroseconds(mData->mNextVideoTime + deviation_usec),
&endSegment, aPrincipalHandle);
mData->mNextVideoTime += deviation_usec;
MOZ_ASSERT(endSegment.GetDuration() > 0);
if (!aIsSameOrigin) {
endSegment.ReplaceWithDisabled();
}
sourceStream->AppendToTrack(videoTrackId, &endSegment);
}
sourceStream->EndTrack(videoTrackId);
mData->mHaveSentFinishVideo = true;
}
}
void
DecodedStream::AdvanceTracks()
{
AssertOwnerThread();
StreamTime endPosition = 0;
if (mInfo.HasAudio()) {
StreamTime audioEnd = mData->mStream->TicksToTimeRoundDown(
mInfo.mAudio.mRate, mData->mAudioFramesWritten);
endPosition = std::max(endPosition, audioEnd);
}
if (mInfo.HasVideo()) {
StreamTime videoEnd = mData->mStream->MicrosecondsToStreamTimeRoundDown(
mData->mNextVideoTime - mStartTime.ref());
endPosition = std::max(endPosition, videoEnd);
}
if (!mData->mHaveSentFinish) {
mData->mStream->AdvanceKnownTracksTime(endPosition);
}
}
void
DecodedStream::SendData()
{
AssertOwnerThread();
MOZ_ASSERT(mStartTime.isSome(), "Must be called after StartPlayback()");
// Not yet created on the main thread. MDSM will try again later.
if (!mData) {
return;
}
// Nothing to do when the stream is finished.
if (mData->mHaveSentFinish) {
return;
}
SendAudio(mParams.mVolume, mSameOrigin, mPrincipalHandle);
SendVideo(mSameOrigin, mPrincipalHandle);
AdvanceTracks();
bool finished = (!mInfo.HasAudio() || mAudioQueue.IsFinished()) &&
(!mInfo.HasVideo() || mVideoQueue.IsFinished());
if (finished && !mData->mHaveSentFinish) {
mData->mHaveSentFinish = true;
mData->mStream->Finish();
}
}
int64_t
DecodedStream::GetEndTime(TrackType aType) const
{
AssertOwnerThread();
if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
CheckedInt64 t = mStartTime.ref() +
FramesToUsecs(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
if (t.isValid()) {
return t.value();
}
} else if (aType == TrackInfo::kVideoTrack && mData) {
return mData->mNextVideoTime;
}
return -1;
}
int64_t
DecodedStream::GetPosition(TimeStamp* aTimeStamp) const
{
AssertOwnerThread();
// This is only called after MDSM starts playback. So mStartTime is
// guaranteed to be something.
MOZ_ASSERT(mStartTime.isSome());
if (aTimeStamp) {
*aTimeStamp = TimeStamp::Now();
}
return mStartTime.ref() + mLastOutputTime;
}
void
DecodedStream::NotifyOutput(int64_t aTime)
{
AssertOwnerThread();
mLastOutputTime = aTime;
int64_t currentTime = GetPosition();
// Remove audio samples that have been played by MSG from the queue.
RefPtr<MediaData> a = mAudioQueue.PeekFront();
for (; a && a->mTime < currentTime;) {
RefPtr<MediaData> releaseMe = mAudioQueue.PopFront();
a = mAudioQueue.PeekFront();
}
}
void
DecodedStream::ConnectListener()
{
AssertOwnerThread();
mAudioPushListener = mAudioQueue.PushEvent().Connect(
mOwnerThread, this, &DecodedStream::SendData);
mAudioFinishListener = mAudioQueue.FinishEvent().Connect(
mOwnerThread, this, &DecodedStream::SendData);
mVideoPushListener = mVideoQueue.PushEvent().Connect(
mOwnerThread, this, &DecodedStream::SendData);
mVideoFinishListener = mVideoQueue.FinishEvent().Connect(
mOwnerThread, this, &DecodedStream::SendData);
}
void
DecodedStream::DisconnectListener()
{
AssertOwnerThread();
mAudioPushListener.Disconnect();
mVideoPushListener.Disconnect();
mAudioFinishListener.Disconnect();
mVideoFinishListener.Disconnect();
}
void
DecodedStream::DumpDebugInfo()
{
AssertOwnerThread();
DUMP_LOG(
"DecodedStream=%p mStartTime=%lld mLastOutputTime=%lld mPlaying=%d mData=%p",
this, mStartTime.valueOr(-1), mLastOutputTime, mPlaying, mData.get());
if (mData) {
mData->DumpDebugInfo();
}
}
} // namespace mozilla

View file

@ -0,0 +1,122 @@
/* -*- 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/. */
#ifndef DecodedStream_h_
#define DecodedStream_h_
#include "MediaEventSource.h"
#include "MediaInfo.h"
#include "MediaSink.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
class DecodedStreamData;
class MediaData;
class MediaStream;
class OutputStreamManager;
struct PlaybackInfoInit;
class ProcessedMediaStream;
class TimeStamp;
template <class T> class MediaQueue;
class DecodedStream : public media::MediaSink {
using media::MediaSink::PlaybackParams;
public:
DecodedStream(AbstractThread* aOwnerThread,
MediaQueue<MediaData>& aAudioQueue,
MediaQueue<MediaData>& aVideoQueue,
OutputStreamManager* aOutputStreamManager,
const bool& aSameOrigin,
const PrincipalHandle& aPrincipalHandle);
// MediaSink functions.
const PlaybackParams& GetPlaybackParams() const override;
void SetPlaybackParams(const PlaybackParams& aParams) override;
RefPtr<GenericPromise> OnEnded(TrackType aType) override;
int64_t GetEndTime(TrackType aType) const override;
int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override;
bool HasUnplayedFrames(TrackType aType) const override
{
// TODO: implement this.
return false;
}
void SetVolume(double aVolume) override;
void SetPlaybackRate(double aPlaybackRate) override;
void SetPreservesPitch(bool aPreservesPitch) override;
void SetPlaying(bool aPlaying) override;
void Start(int64_t aStartTime, const MediaInfo& aInfo) override;
void Stop() override;
bool IsStarted() const override;
bool IsPlaying() const override;
void DumpDebugInfo() override;
protected:
virtual ~DecodedStream();
private:
void DestroyData(UniquePtr<DecodedStreamData> aData);
void AdvanceTracks();
void SendAudio(double aVolume, bool aIsSameOrigin, const PrincipalHandle& aPrincipalHandle);
void SendVideo(bool aIsSameOrigin, const PrincipalHandle& aPrincipalHandle);
void SendData();
void NotifyOutput(int64_t aTime);
void AssertOwnerThread() const {
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
}
void ConnectListener();
void DisconnectListener();
const RefPtr<AbstractThread> mOwnerThread;
/*
* Main thread only members.
*/
// Data about MediaStreams that are being fed by the decoder.
const RefPtr<OutputStreamManager> mOutputStreamManager;
/*
* Worker thread only members.
*/
UniquePtr<DecodedStreamData> mData;
RefPtr<GenericPromise> mFinishPromise;
bool mPlaying;
const bool& mSameOrigin; // valid until Shutdown() is called.
const PrincipalHandle& mPrincipalHandle; // valid until Shutdown() is called.
PlaybackParams mParams;
Maybe<int64_t> mStartTime;
int64_t mLastOutputTime = 0; // microseconds
MediaInfo mInfo;
MediaQueue<MediaData>& mAudioQueue;
MediaQueue<MediaData>& mVideoQueue;
MediaEventListener mAudioPushListener;
MediaEventListener mVideoPushListener;
MediaEventListener mAudioFinishListener;
MediaEventListener mVideoFinishListener;
MediaEventListener mOutputListener;
};
} // namespace mozilla
#endif // DecodedStream_h_

View file

@ -0,0 +1,133 @@
/* -*- 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/. */
#ifndef MediaSink_h_
#define MediaSink_h_
#include "mozilla/RefPtr.h"
#include "mozilla/MozPromise.h"
#include "nsISupportsImpl.h"
#include "MediaInfo.h"
namespace mozilla {
class TimeStamp;
namespace media {
/**
* A consumer of audio/video data which plays audio and video tracks and
* manages A/V sync between them.
*
* A typical sink sends audio/video outputs to the speaker and screen.
* However, there are also sinks which capture the output of an media element
* and send the output to a MediaStream.
*
* This class is used to move A/V sync management and audio/video rendering
* out of MDSM so it is possible for subclasses to do external rendering using
* specific hardware which is required by TV projects and CDM.
*
* Note this class is not thread-safe and should be called from the state
* machine thread only.
*/
class MediaSink {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSink);
typedef mozilla::TrackInfo::TrackType TrackType;
struct PlaybackParams {
PlaybackParams()
: mVolume(1.0) , mPlaybackRate(1.0) , mPreservesPitch(true) {}
double mVolume;
double mPlaybackRate;
bool mPreservesPitch;
};
// Return the playback parameters of this sink.
// Can be called in any state.
virtual const PlaybackParams& GetPlaybackParams() const = 0;
// Set the playback parameters of this sink.
// Can be called in any state.
virtual void SetPlaybackParams(const PlaybackParams& aParams) = 0;
// Return a promise which is resolved when the track finishes
// or null if no such track.
// Must be called after playback starts.
virtual RefPtr<GenericPromise> OnEnded(TrackType aType) = 0;
// Return the end time of the audio/video data that has been consumed
// or -1 if no such track.
// Must be called after playback starts.
virtual int64_t GetEndTime(TrackType aType) const = 0;
// Return playback position of the media.
// Since A/V sync is always maintained by this sink, there is no need to
// specify whether we want to get audio or video position.
// aTimeStamp returns the timeStamp corresponding to the returned position
// which is used by the compositor to derive the render time of video frames.
// Must be called after playback starts.
virtual int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const = 0;
// Return true if there are data consumed but not played yet.
// Can be called in any state.
virtual bool HasUnplayedFrames(TrackType aType) const = 0;
// Set volume of the audio track.
// Do nothing if this sink has no audio track.
// Can be called in any state.
virtual void SetVolume(double aVolume) {}
// Set the playback rate.
// Can be called in any state.
virtual void SetPlaybackRate(double aPlaybackRate) {}
// Whether to preserve pitch of the audio track.
// Do nothing if this sink has no audio track.
// Can be called in any state.
virtual void SetPreservesPitch(bool aPreservesPitch) {}
// Pause/resume the playback. Only work after playback starts.
virtual void SetPlaying(bool aPlaying) = 0;
// Single frame rendering operation may need to be done before playback
// started (1st frame) or right after seek completed or playback stopped.
// Do nothing if this sink has no video track. Can be called in any state.
virtual void Redraw(const VideoInfo& aInfo) {};
// Begin a playback session with the provided start time and media info.
// Must be called when playback is stopped.
virtual void Start(int64_t aStartTime, const MediaInfo& aInfo) = 0;
// Finish a playback session.
// Must be called after playback starts.
virtual void Stop() = 0;
// Return true if playback has started.
// Can be called in any state.
virtual bool IsStarted() const = 0;
// Return true if playback is started and not paused otherwise false.
// Can be called in any state.
virtual bool IsPlaying() const = 0;
// Called on the state machine thread to shut down the sink. All resources
// allocated by this sink should be released.
// Must be called after playback stopped.
virtual void Shutdown() {}
// Dump debugging information to the logs.
// Can be called in any phase.
virtual void DumpDebugInfo() {}
protected:
virtual ~MediaSink() {}
};
} // namespace media
} // namespace mozilla
#endif //MediaSink_h_

View file

@ -0,0 +1,134 @@
/* -*- 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 "MediaStreamGraph.h"
#include "OutputStreamManager.h"
namespace mozilla {
OutputStreamData::~OutputStreamData()
{
MOZ_ASSERT(NS_IsMainThread());
// Break the connection to the input stream if necessary.
if (mPort) {
mPort->Destroy();
}
}
void
OutputStreamData::Init(OutputStreamManager* aOwner, ProcessedMediaStream* aStream)
{
mOwner = aOwner;
mStream = aStream;
}
bool
OutputStreamData::Connect(MediaStream* aStream)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mPort, "Already connected?");
if (mStream->IsDestroyed()) {
return false;
}
mPort = mStream->AllocateInputPort(aStream);
return true;
}
bool
OutputStreamData::Disconnect()
{
MOZ_ASSERT(NS_IsMainThread());
// During cycle collection, DOMMediaStream can be destroyed and send
// its Destroy message before this decoder is destroyed. So we have to
// be careful not to send any messages after the Destroy().
if (mStream->IsDestroyed()) {
return false;
}
// Disconnect the existing port if necessary.
if (mPort) {
mPort->Destroy();
mPort = nullptr;
}
return true;
}
bool
OutputStreamData::Equals(MediaStream* aStream) const
{
return mStream == aStream;
}
MediaStreamGraph*
OutputStreamData::Graph() const
{
return mStream->Graph();
}
void
OutputStreamManager::Add(ProcessedMediaStream* aStream, bool aFinishWhenEnded)
{
MOZ_ASSERT(NS_IsMainThread());
// All streams must belong to the same graph.
MOZ_ASSERT(!Graph() || Graph() == aStream->Graph());
// Ensure that aStream finishes the moment mDecodedStream does.
if (aFinishWhenEnded) {
aStream->SetAutofinish(true);
}
OutputStreamData* p = mStreams.AppendElement();
p->Init(this, aStream);
// Connect to the input stream if we have one. Otherwise the output stream
// will be connected in Connect().
if (mInputStream) {
p->Connect(mInputStream);
}
}
void
OutputStreamManager::Remove(MediaStream* aStream)
{
MOZ_ASSERT(NS_IsMainThread());
for (int32_t i = mStreams.Length() - 1; i >= 0; --i) {
if (mStreams[i].Equals(aStream)) {
mStreams.RemoveElementAt(i);
break;
}
}
}
void
OutputStreamManager::Connect(MediaStream* aStream)
{
MOZ_ASSERT(NS_IsMainThread());
mInputStream = aStream;
for (int32_t i = mStreams.Length() - 1; i >= 0; --i) {
if (!mStreams[i].Connect(aStream)) {
// Probably the DOMMediaStream was GCed. Clean up.
mStreams.RemoveElementAt(i);
}
}
}
void
OutputStreamManager::Disconnect()
{
MOZ_ASSERT(NS_IsMainThread());
mInputStream = nullptr;
for (int32_t i = mStreams.Length() - 1; i >= 0; --i) {
if (!mStreams[i].Disconnect()) {
// Probably the DOMMediaStream was GCed. Clean up.
mStreams.RemoveElementAt(i);
}
}
}
} // namespace mozilla

View file

@ -0,0 +1,80 @@
/* -*- 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/. */
#ifndef OutputStreamManager_h
#define OutputStreamManager_h
#include "mozilla/RefPtr.h"
#include "nsTArray.h"
namespace mozilla {
class MediaInputPort;
class MediaStream;
class MediaStreamGraph;
class OutputStreamManager;
class ProcessedMediaStream;
class OutputStreamData {
public:
~OutputStreamData();
void Init(OutputStreamManager* aOwner, ProcessedMediaStream* aStream);
// Connect mStream to the input stream.
// Return false is mStream is already destroyed, otherwise true.
bool Connect(MediaStream* aStream);
// Disconnect mStream from its input stream.
// Return false is mStream is already destroyed, otherwise true.
bool Disconnect();
// Return true if aStream points to the same object as mStream.
// Used by OutputStreamManager to remove an output stream.
bool Equals(MediaStream* aStream) const;
// Return the graph mStream belongs to.
MediaStreamGraph* Graph() const;
private:
OutputStreamManager* mOwner;
RefPtr<ProcessedMediaStream> mStream;
// mPort connects our mStream to an input stream.
RefPtr<MediaInputPort> mPort;
};
class OutputStreamManager {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OutputStreamManager);
public:
// Add the output stream to the collection.
void Add(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
// Remove the output stream from the collection.
void Remove(MediaStream* aStream);
// Return true if the collection empty.
bool IsEmpty() const
{
MOZ_ASSERT(NS_IsMainThread());
return mStreams.IsEmpty();
}
// Connect all output streams in the collection to the input stream.
void Connect(MediaStream* aStream);
// Disconnect all output streams from the input stream.
void Disconnect();
// Return the graph these streams belong to or null if empty.
MediaStreamGraph* Graph() const
{
MOZ_ASSERT(NS_IsMainThread());
return !IsEmpty() ? mStreams[0].Graph() : nullptr;
}
private:
~OutputStreamManager() {}
// Keep the input stream so we can connect the output streams that
// are added after Connect().
RefPtr<MediaStream> mInputStream;
nsTArray<OutputStreamData> mStreams;
};
} // namespace mozilla
#endif // OutputStreamManager_h

View file

@ -0,0 +1,486 @@
/* -*- 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 "MediaQueue.h"
#include "VideoSink.h"
#include "MediaPrefs.h"
namespace mozilla {
extern LazyLogModule gMediaDecoderLog;
#undef FMT
#undef DUMP_LOG
#define FMT(x, ...) "VideoSink=%p " x, this, ##__VA_ARGS__
#define VSINK_LOG(x, ...) MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, (FMT(x, ##__VA_ARGS__)))
#define VSINK_LOG_V(x, ...) MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, (FMT(x, ##__VA_ARGS__)))
#define DUMP_LOG(x, ...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(FMT(x, ##__VA_ARGS__)).get(), nullptr, nullptr, -1)
using namespace mozilla::layers;
namespace media {
// Minimum update frequency is 1/120th of a second, i.e. half the
// duration of a 60-fps frame.
static const int64_t MIN_UPDATE_INTERVAL_US = 1000000 / (60 * 2);
VideoSink::VideoSink(AbstractThread* aThread,
MediaSink* aAudioSink,
MediaQueue<MediaData>& aVideoQueue,
VideoFrameContainer* aContainer,
FrameStatistics& aFrameStats,
uint32_t aVQueueSentToCompositerSize)
: mOwnerThread(aThread)
, mAudioSink(aAudioSink)
, mVideoQueue(aVideoQueue)
, mContainer(aContainer)
, mProducerID(ImageContainer::AllocateProducerID())
, mFrameStats(aFrameStats)
, mVideoFrameEndTime(-1)
, mHasVideo(false)
, mUpdateScheduler(aThread)
, mVideoQueueSendToCompositorSize(aVQueueSentToCompositerSize)
, mMinVideoQueueSize(MediaPrefs::RuinAvSync() ? 1 : 0)
{
MOZ_ASSERT(mAudioSink, "AudioSink should exist.");
}
VideoSink::~VideoSink()
{
}
const MediaSink::PlaybackParams&
VideoSink::GetPlaybackParams() const
{
AssertOwnerThread();
return mAudioSink->GetPlaybackParams();
}
void
VideoSink::SetPlaybackParams(const PlaybackParams& aParams)
{
AssertOwnerThread();
mAudioSink->SetPlaybackParams(aParams);
}
RefPtr<GenericPromise>
VideoSink::OnEnded(TrackType aType)
{
AssertOwnerThread();
MOZ_ASSERT(mAudioSink->IsStarted(), "Must be called after playback starts.");
if (aType == TrackInfo::kAudioTrack) {
return mAudioSink->OnEnded(aType);
} else if (aType == TrackInfo::kVideoTrack) {
return mEndPromise;
}
return nullptr;
}
int64_t
VideoSink::GetEndTime(TrackType aType) const
{
AssertOwnerThread();
MOZ_ASSERT(mAudioSink->IsStarted(), "Must be called after playback starts.");
if (aType == TrackInfo::kVideoTrack) {
return mVideoFrameEndTime;
} else if (aType == TrackInfo::kAudioTrack) {
return mAudioSink->GetEndTime(aType);
}
return -1;
}
int64_t
VideoSink::GetPosition(TimeStamp* aTimeStamp) const
{
AssertOwnerThread();
return mAudioSink->GetPosition(aTimeStamp);
}
bool
VideoSink::HasUnplayedFrames(TrackType aType) const
{
AssertOwnerThread();
MOZ_ASSERT(aType == TrackInfo::kAudioTrack, "Not implemented for non audio tracks.");
return mAudioSink->HasUnplayedFrames(aType);
}
void
VideoSink::SetPlaybackRate(double aPlaybackRate)
{
AssertOwnerThread();
mAudioSink->SetPlaybackRate(aPlaybackRate);
}
void
VideoSink::SetVolume(double aVolume)
{
AssertOwnerThread();
mAudioSink->SetVolume(aVolume);
}
void
VideoSink::SetPreservesPitch(bool aPreservesPitch)
{
AssertOwnerThread();
mAudioSink->SetPreservesPitch(aPreservesPitch);
}
void
VideoSink::SetPlaying(bool aPlaying)
{
AssertOwnerThread();
VSINK_LOG_V(" playing (%d) -> (%d)", mAudioSink->IsPlaying(), aPlaying);
if (!aPlaying) {
// Reset any update timer if paused.
mUpdateScheduler.Reset();
// Since playback is paused, tell compositor to render only current frame.
RenderVideoFrames(1);
if (mContainer) {
mContainer->ClearCachedResources();
}
}
mAudioSink->SetPlaying(aPlaying);
if (mHasVideo && aPlaying) {
// There's no thread in VideoSink for pulling video frames, need to trigger
// rendering while becoming playing status. because the VideoQueue may be
// full already.
TryUpdateRenderedVideoFrames();
}
}
void
VideoSink::Start(int64_t aStartTime, const MediaInfo& aInfo)
{
AssertOwnerThread();
VSINK_LOG("[%s]", __func__);
mAudioSink->Start(aStartTime, aInfo);
mHasVideo = aInfo.HasVideo();
if (mHasVideo) {
mEndPromise = mEndPromiseHolder.Ensure(__func__);
// If the underlying MediaSink has an end promise for the video track (which
// happens when mAudioSink refers to a DecodedStream), we must wait for it
// to complete before resolving our own end promise. Otherwise, MDSM might
// stop playback before DecodedStream plays to the end and cause
// test_streams_element_capture.html to time out.
RefPtr<GenericPromise> p = mAudioSink->OnEnded(TrackInfo::kVideoTrack);
if (p) {
RefPtr<VideoSink> self = this;
mVideoSinkEndRequest.Begin(p->Then(mOwnerThread, __func__,
[self] () {
self->mVideoSinkEndRequest.Complete();
self->TryUpdateRenderedVideoFrames();
// It is possible the video queue size is 0 and we have no frames to
// render. However, we need to call MaybeResolveEndPromise() to ensure
// mEndPromiseHolder is resolved.
self->MaybeResolveEndPromise();
}, [self] () {
self->mVideoSinkEndRequest.Complete();
self->TryUpdateRenderedVideoFrames();
self->MaybeResolveEndPromise();
}));
}
ConnectListener();
// Run the render loop at least once so we can resolve the end promise
// when video duration is 0.
UpdateRenderedVideoFrames();
}
}
void
VideoSink::Stop()
{
AssertOwnerThread();
MOZ_ASSERT(mAudioSink->IsStarted(), "playback not started.");
VSINK_LOG("[%s]", __func__);
mAudioSink->Stop();
mUpdateScheduler.Reset();
if (mHasVideo) {
DisconnectListener();
mVideoSinkEndRequest.DisconnectIfExists();
mEndPromiseHolder.ResolveIfExists(true, __func__);
mEndPromise = nullptr;
}
mVideoFrameEndTime = -1;
}
bool
VideoSink::IsStarted() const
{
AssertOwnerThread();
return mAudioSink->IsStarted();
}
bool
VideoSink::IsPlaying() const
{
AssertOwnerThread();
return mAudioSink->IsPlaying();
}
void
VideoSink::Shutdown()
{
AssertOwnerThread();
MOZ_ASSERT(!mAudioSink->IsStarted(), "must be called after playback stops.");
VSINK_LOG("[%s]", __func__);
mAudioSink->Shutdown();
}
void
VideoSink::OnVideoQueuePushed(RefPtr<MediaData>&& aSample)
{
AssertOwnerThread();
// Listen to push event, VideoSink should try rendering ASAP if first frame
// arrives but update scheduler is not triggered yet.
VideoData* v = aSample->As<VideoData>();
if (!v->mSentToCompositor) {
// Since we push rendered frames back to the queue, we will receive
// push events for them. We only need to trigger render loop
// when this frame is not rendered yet.
TryUpdateRenderedVideoFrames();
}
}
void
VideoSink::OnVideoQueueFinished()
{
AssertOwnerThread();
// Run render loop if the end promise is not resolved yet.
if (!mUpdateScheduler.IsScheduled() &&
mAudioSink->IsPlaying() &&
!mEndPromiseHolder.IsEmpty()) {
UpdateRenderedVideoFrames();
}
}
void
VideoSink::Redraw(const VideoInfo& aInfo)
{
AssertOwnerThread();
// No video track, nothing to draw.
if (!aInfo.IsValid() || !mContainer) {
return;
}
if (VideoQueue().GetSize() > 0) {
RenderVideoFrames(1);
return;
}
// When we reach here, it means there are no frames in this video track.
// Draw a blank frame to ensure there is something in the image container
// to fire 'loadeddata'.
RefPtr<Image> blank =
mContainer->GetImageContainer()->CreatePlanarYCbCrImage();
mContainer->SetCurrentFrame(aInfo.mDisplay, blank, TimeStamp::Now());
}
void
VideoSink::TryUpdateRenderedVideoFrames()
{
AssertOwnerThread();
if (!mUpdateScheduler.IsScheduled() && VideoQueue().GetSize() >= 1 &&
mAudioSink->IsPlaying()) {
UpdateRenderedVideoFrames();
}
}
void
VideoSink::UpdateRenderedVideoFramesByTimer()
{
AssertOwnerThread();
mUpdateScheduler.CompleteRequest();
UpdateRenderedVideoFrames();
}
void
VideoSink::ConnectListener()
{
AssertOwnerThread();
mPushListener = VideoQueue().PushEvent().Connect(
mOwnerThread, this, &VideoSink::OnVideoQueuePushed);
mFinishListener = VideoQueue().FinishEvent().Connect(
mOwnerThread, this, &VideoSink::OnVideoQueueFinished);
}
void
VideoSink::DisconnectListener()
{
AssertOwnerThread();
mPushListener.Disconnect();
mFinishListener.Disconnect();
}
void
VideoSink::RenderVideoFrames(int32_t aMaxFrames,
int64_t aClockTime,
const TimeStamp& aClockTimeStamp)
{
AssertOwnerThread();
AutoTArray<RefPtr<MediaData>,16> frames;
VideoQueue().GetFirstElements(aMaxFrames, &frames);
if (frames.IsEmpty() || !mContainer) {
return;
}
AutoTArray<ImageContainer::NonOwningImage,16> images;
TimeStamp lastFrameTime;
MediaSink::PlaybackParams params = mAudioSink->GetPlaybackParams();
for (uint32_t i = 0; i < frames.Length(); ++i) {
VideoData* frame = frames[i]->As<VideoData>();
frame->mSentToCompositor = true;
if (!frame->mImage || !frame->mImage->IsValid() ||
!frame->mImage->GetSize().width || !frame->mImage->GetSize().height) {
continue;
}
int64_t frameTime = frame->mTime;
if (frameTime < 0) {
// Frame times before the start time are invalid; drop such frames
continue;
}
TimeStamp t;
if (aMaxFrames > 1) {
MOZ_ASSERT(!aClockTimeStamp.IsNull());
int64_t delta = frame->mTime - aClockTime;
t = aClockTimeStamp +
TimeDuration::FromMicroseconds(delta / params.mPlaybackRate);
if (!lastFrameTime.IsNull() && t <= lastFrameTime) {
// Timestamps out of order; drop the new frame. In theory we should
// probably replace the previous frame with the new frame if the
// timestamps are equal, but this is a corrupt video file already so
// never mind.
continue;
}
lastFrameTime = t;
}
ImageContainer::NonOwningImage* img = images.AppendElement();
img->mTimeStamp = t;
img->mImage = frame->mImage;
img->mFrameID = frame->mFrameID;
img->mProducerID = mProducerID;
VSINK_LOG_V("playing video frame %lld (id=%x) (vq-queued=%i)",
frame->mTime, frame->mFrameID, VideoQueue().GetSize());
}
mContainer->SetCurrentFrames(frames[0]->As<VideoData>()->mDisplay, images);
}
void
VideoSink::UpdateRenderedVideoFrames()
{
AssertOwnerThread();
MOZ_ASSERT(mAudioSink->IsPlaying(), "should be called while playing.");
// Get the current playback position.
TimeStamp nowTime;
const int64_t clockTime = mAudioSink->GetPosition(&nowTime);
NS_ASSERTION(clockTime >= 0, "Should have positive clock time.");
// Skip frames up to the playback position.
int64_t lastFrameEndTime = 0;
while (VideoQueue().GetSize() > mMinVideoQueueSize &&
clockTime >= VideoQueue().PeekFront()->GetEndTime()) {
RefPtr<MediaData> frame = VideoQueue().PopFront();
lastFrameEndTime = frame->GetEndTime();
if (frame->As<VideoData>()->mSentToCompositor) {
mFrameStats.NotifyPresentedFrame();
} else {
mFrameStats.NotifyDecodedFrames({ 0, 0, 1 });
VSINK_LOG_V("discarding video frame mTime=%lld clock_time=%lld",
frame->mTime, clockTime);
}
}
// The presentation end time of the last video frame displayed is either
// the end time of the current frame, or if we dropped all frames in the
// queue, the end time of the last frame we removed from the queue.
RefPtr<MediaData> currentFrame = VideoQueue().PeekFront();
mVideoFrameEndTime = std::max(mVideoFrameEndTime,
currentFrame ? currentFrame->GetEndTime() : lastFrameEndTime);
MaybeResolveEndPromise();
RenderVideoFrames(mVideoQueueSendToCompositorSize, clockTime, nowTime);
// Get the timestamp of the next frame. Schedule the next update at
// the start time of the next frame. If we don't have a next frame,
// we will run render loops again upon incoming frames.
nsTArray<RefPtr<MediaData>> frames;
VideoQueue().GetFirstElements(2, &frames);
if (frames.Length() < 2) {
return;
}
int64_t nextFrameTime = frames[1]->mTime;
int64_t delta = std::max<int64_t>((nextFrameTime - clockTime), MIN_UPDATE_INTERVAL_US);
TimeStamp target = nowTime + TimeDuration::FromMicroseconds(
delta / mAudioSink->GetPlaybackParams().mPlaybackRate);
RefPtr<VideoSink> self = this;
mUpdateScheduler.Ensure(target, [self] () {
self->UpdateRenderedVideoFramesByTimer();
}, [self] () {
self->UpdateRenderedVideoFramesByTimer();
});
}
void
VideoSink::MaybeResolveEndPromise()
{
AssertOwnerThread();
// All frames are rendered, Let's resolve the promise.
if (VideoQueue().IsFinished() &&
VideoQueue().GetSize() <= 1 &&
!mVideoSinkEndRequest.Exists()) {
mEndPromiseHolder.ResolveIfExists(true, __func__);
}
}
void
VideoSink::DumpDebugInfo()
{
AssertOwnerThread();
DUMP_LOG(
"IsStarted=%d IsPlaying=%d, VideoQueue: finished=%d size=%d, "
"mVideoFrameEndTime=%lld mHasVideo=%d mVideoSinkEndRequest.Exists()=%d "
"mEndPromiseHolder.IsEmpty()=%d",
IsStarted(), IsPlaying(), VideoQueue().IsFinished(), VideoQueue().GetSize(),
mVideoFrameEndTime, mHasVideo, mVideoSinkEndRequest.Exists(), mEndPromiseHolder.IsEmpty());
mAudioSink->DumpDebugInfo();
}
} // namespace media
} // namespace mozilla

View file

@ -0,0 +1,160 @@
/* -*- 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/. */
#ifndef VideoSink_h_
#define VideoSink_h_
#include "FrameStatistics.h"
#include "ImageContainer.h"
#include "MediaEventSource.h"
#include "MediaSink.h"
#include "MediaTimer.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "VideoFrameContainer.h"
namespace mozilla {
class VideoFrameContainer;
template <class T> class MediaQueue;
namespace media {
class VideoSink : public MediaSink
{
typedef mozilla::layers::ImageContainer::ProducerID ProducerID;
public:
VideoSink(AbstractThread* aThread,
MediaSink* aAudioSink,
MediaQueue<MediaData>& aVideoQueue,
VideoFrameContainer* aContainer,
FrameStatistics& aFrameStats,
uint32_t aVQueueSentToCompositerSize);
const PlaybackParams& GetPlaybackParams() const override;
void SetPlaybackParams(const PlaybackParams& aParams) override;
RefPtr<GenericPromise> OnEnded(TrackType aType) override;
int64_t GetEndTime(TrackType aType) const override;
int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override;
bool HasUnplayedFrames(TrackType aType) const override;
void SetPlaybackRate(double aPlaybackRate) override;
void SetVolume(double aVolume) override;
void SetPreservesPitch(bool aPreservesPitch) override;
void SetPlaying(bool aPlaying) override;
void Redraw(const VideoInfo& aInfo) override;
void Start(int64_t aStartTime, const MediaInfo& aInfo) override;
void Stop() override;
bool IsStarted() const override;
bool IsPlaying() const override;
void Shutdown() override;
void DumpDebugInfo() override;
private:
virtual ~VideoSink();
// VideoQueue listener related.
void OnVideoQueuePushed(RefPtr<MediaData>&& aSample);
void OnVideoQueueFinished();
void ConnectListener();
void DisconnectListener();
// Sets VideoQueue images into the VideoFrameContainer. Called on the shared
// state machine thread. The first aMaxFrames (at most) are set.
// aClockTime and aClockTimeStamp are used as the baseline for deriving
// timestamps for the frames; when omitted, aMaxFrames must be 1 and
// a null timestamp is passed to the VideoFrameContainer.
// If the VideoQueue is empty, this does nothing.
void RenderVideoFrames(int32_t aMaxFrames, int64_t aClockTime = 0,
const TimeStamp& aClickTimeStamp = TimeStamp());
// Triggered while videosink is started, videosink becomes "playing" status,
// or VideoQueue event arrived.
void TryUpdateRenderedVideoFrames();
// If we have video, display a video frame if it's time for display has
// arrived, otherwise sleep until it's time for the next frame. Update the
// current frame time as appropriate, and trigger ready state update.
// Called on the shared state machine thread.
void UpdateRenderedVideoFrames();
void UpdateRenderedVideoFramesByTimer();
void MaybeResolveEndPromise();
void AssertOwnerThread() const
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
}
MediaQueue<MediaData>& VideoQueue() const {
return mVideoQueue;
}
const RefPtr<AbstractThread> mOwnerThread;
RefPtr<MediaSink> mAudioSink;
MediaQueue<MediaData>& mVideoQueue;
VideoFrameContainer* mContainer;
// Producer ID to help ImageContainer distinguish different streams of
// FrameIDs. A unique and immutable value per VideoSink.
const ProducerID mProducerID;
// Used to notify MediaDecoder's frame statistics
FrameStatistics& mFrameStats;
RefPtr<GenericPromise> mEndPromise;
MozPromiseHolder<GenericPromise> mEndPromiseHolder;
MozPromiseRequestHolder<GenericPromise> mVideoSinkEndRequest;
// The presentation end time of the last video frame which has been displayed
// in microseconds.
int64_t mVideoFrameEndTime;
// Event listeners for VideoQueue
MediaEventListener mPushListener;
MediaEventListener mFinishListener;
// True if this sink is going to handle video track.
bool mHasVideo;
// Used to trigger another update of rendered frames in next round.
DelayedScheduler mUpdateScheduler;
// Max frame number sent to compositor at a time.
// Based on the pref value obtained in MDSM.
const uint32_t mVideoQueueSendToCompositorSize;
// Talos tests for the compositor require at least one frame in the
// video queue so that the compositor has something to composit during
// the talos test when the decode is stressed. We have a minimum size
// on the video queue in order to facilitate this talos test.
// Note: Normal playback should not have a queue size of more than 0,
// otherwise A/V sync will be ruined! *Only* make this non-zero for
// testing purposes.
const uint32_t mMinVideoQueueSize;
};
} // namespace media
} // namespace mozilla
#endif

View file

@ -0,0 +1,18 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES += [
'AudioSinkWrapper.cpp',
'DecodedAudioDataSink.cpp',
'DecodedStream.cpp',
'OutputStreamManager.cpp',
'VideoSink.cpp',
]
FINAL_LIBRARY = 'xul'
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']