Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 67 additions & 111 deletions dom/media/mediasink/DecodedStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "mozilla/CheckedInt.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/SyncRunnable.h"

#include "AudioSegment.h"
#include "DecodedStream.h"
Expand All @@ -18,6 +19,15 @@

namespace mozilla {

/*
* 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 {
typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent;
public:
Expand Down Expand Up @@ -116,7 +126,8 @@ UpdateStreamSuspended(MediaStream* aStream, bool aBlocking)
*/
class DecodedStreamData {
public:
DecodedStreamData(SourceMediaStream* aStream,
DecodedStreamData(OutputStreamManager aOutputStreamManager,
PlaybackInfoInit&& aInit,
MozPromiseHolder<GenericPromise>&& aPromise);
~DecodedStreamData();
bool IsFinished() const;
Expand All @@ -137,45 +148,56 @@ class DecodedStreamData {
// the image.
RefPtr<layers::Image> mLastVideoImage;
gfx::IntSize mLastVideoImageDisplaySize;
// This is set to true when the stream is initialized (audio and
// video tracks added).
bool mStreamInitialized;
bool mHaveSentFinish;
bool mHaveSentFinishAudio;
bool mHaveSentFinishVideo;

// The decoder is responsible for calling Destroy() on this stream.
const RefPtr<SourceMediaStream> mStream;
RefPtr<DecodedStreamGraphListener> mListener;
const RefPtr<DecodedStreamGraphListener> mListener;
bool mPlaying;
// True if we need to send a compensation video frame to ensure the
// StreamTime going forward.
bool mEOSVideoCompensation;

OutputStreamManager mOutputStreamManager;
};

DecodedStreamData::DecodedStreamData(SourceMediaStream* aStream,
DecodedStreamData::DecodedStreamData(OutputStreamManager aOutputStreamManager,
PlaybackInfoInit&& aInit,
MozPromiseHolder<GenericPromise>&& aPromise)
: mAudioFramesWritten(0)
, mNextVideoTime(-1)
, mNextAudioTime(-1)
, mStreamInitialized(false)
, mNextVideoTime(aInit.mStartTime)
, mNextAudioTime(aInit.mStartTime)
, mHaveSentFinish(false)
, mHaveSentFinishAudio(false)
, mHaveSentFinishVideo(false)
, mStream(aStream)
, mStream(aOutputStreamManager.Graph()->CreateSourceStream(nullptr))
// 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)
{
// DecodedStreamGraphListener will resolve this promise.
mListener = new DecodedStreamGraphListener(mStream, Move(aPromise));
mStream->AddListener(mListener);
mOutputStreamManager.Connect(mStream);

// mPlaying is initially true because MDSM won't start playback until playing
// becomes true. This is consistent with the settings of AudioSink.
// 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();
mListener->Forget();
mStream->Destroy();
}
Expand Down Expand Up @@ -377,28 +399,51 @@ DecodedStream::Start(int64_t aStartTime, const MediaInfo& aInfo)

class R : public nsRunnable {
typedef MozPromiseHolder<GenericPromise> Promise;
typedef void(DecodedStream::*Method)(Promise&&);
public:
R(DecodedStream* aThis, Method aMethod, Promise&& aPromise)
: mThis(aThis), mMethod(aMethod)
R(PlaybackInfoInit&& aInit, Promise&& aPromise, OutputStreamManager aManager)
: mInit(Move(aInit)), mOutputStreamManager(aManager)
{
mPromise = Move(aPromise);
}
NS_IMETHOD Run() override
{
(mThis->*mMethod)(Move(mPromise));
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:
RefPtr<DecodedStream> mThis;
Method mMethod;
PlaybackInfoInit mInit;
Promise mPromise;
OutputStreamManager mOutputStreamManager;
UniquePtr<DecodedStreamData> mData;
};

MozPromiseHolder<GenericPromise> promise;
mFinishPromise = promise.Ensure(__func__);
nsCOMPtr<nsIRunnable> r = new R(this, &DecodedStream::CreateData, Move(promise));
AbstractThread::MainThread()->Dispatch(r.forget());
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) {
mData->SetPlaying(mPlaying);
SendData();
}
}

void
Expand Down Expand Up @@ -440,78 +485,21 @@ DecodedStream::DestroyData(UniquePtr<DecodedStreamData> aData)
}

DecodedStreamData* data = aData.release();
RefPtr<DecodedStream> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
self->mOutputStreamManager.Disconnect();
delete data;
});
AbstractThread::MainThread()->Dispatch(r.forget());
}

void
DecodedStream::CreateData(MozPromiseHolder<GenericPromise>&& aPromise)
{
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().
// Also we don't create a source stream when MDSM has begun shutdown.
if (!mOutputStreamManager.Graph() || mShuttingDown) {
// Resolve the promise to indicate the end of playback.
aPromise.Resolve(true, __func__);
return;
}

auto source = mOutputStreamManager.Graph()->CreateSourceStream(nullptr);
auto data = new DecodedStreamData(source, Move(aPromise));
mOutputStreamManager.Connect(data->mStream);

class R : public nsRunnable {
typedef void(DecodedStream::*Method)(UniquePtr<DecodedStreamData>);
public:
R(DecodedStream* aThis, Method aMethod, DecodedStreamData* aData)
: mThis(aThis), mMethod(aMethod), mData(aData) {}
NS_IMETHOD Run() override
{
(mThis->*mMethod)(Move(mData));
return NS_OK;
}
private:
RefPtr<DecodedStream> mThis;
Method mMethod;
UniquePtr<DecodedStreamData> mData;
};

// Post a message to ensure |mData| is only updated on the worker thread.
// Note this must be done before MDSM's shutdown since dispatch could fail
// when the worker thread is shut down.
nsCOMPtr<nsIRunnable> r = new R(this, &DecodedStream::OnDataCreated, data);
mOwnerThread->Dispatch(r.forget());
}

bool
DecodedStream::HasConsumers() const
{
return !mOutputStreamManager.IsEmpty();
}

void
DecodedStream::OnDataCreated(UniquePtr<DecodedStreamData> aData)
{
AssertOwnerThread();
MOZ_ASSERT(!mData, "Already created.");

// Start to send data to the stream immediately
if (mStartTime.isSome()) {
aData->SetPlaying(mPlaying);
mData = Move(aData);
SendData();
return;
}

// Playback has ended. Destroy aData which is not needed anymore.
DestroyData(Move(aData));
}

void
DecodedStream::AddOutput(ProcessedMediaStream* aStream, bool aFinishWhenEnded)
Expand Down Expand Up @@ -569,37 +557,6 @@ DecodedStream::SetSameOrigin(bool aSameOrigin)
mSameOrigin = aSameOrigin;
}

void
DecodedStream::InitTracks()
{
AssertOwnerThread();

if (mData->mStreamInitialized) {
return;
}

SourceMediaStream* sourceStream = mData->mStream;

if (mInfo.HasAudio()) {
TrackID audioTrackId = mInfo.mAudio.mTrackId;
AudioSegment* audio = new AudioSegment();
sourceStream->AddAudioTrack(audioTrackId, mInfo.mAudio.mRate, 0, audio,
SourceMediaStream::ADDTRACK_QUEUED);
mData->mNextAudioTime = mStartTime.ref();
}

if (mInfo.HasVideo()) {
TrackID videoTrackId = mInfo.mVideo.mTrackId;
VideoSegment* video = new VideoSegment();
sourceStream->AddTrack(videoTrackId, 0, video,
SourceMediaStream::ADDTRACK_QUEUED);
mData->mNextVideoTime = mStartTime.ref();
}

sourceStream->FinishAddTracks();
mData->mStreamInitialized = true;
}

static void
SendStreamAudio(DecodedStreamData* aStream, int64_t aStartTime,
MediaData* aData, AudioSegment* aOutput,
Expand Down Expand Up @@ -832,7 +789,6 @@ DecodedStream::SendData()
return;
}

InitTracks();
SendAudio(mParams.mVolume, mSameOrigin);
SendVideo(mSameOrigin);
AdvanceTracks();
Expand Down
4 changes: 1 addition & 3 deletions dom/media/mediasink/DecodedStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class MediaInputPort;
class MediaStream;
class MediaStreamGraph;
class OutputStreamManager;
struct PlaybackInfoInit;
class ProcessedMediaStream;
class TimeStamp;

Expand Down Expand Up @@ -131,10 +132,7 @@ class DecodedStream : public media::MediaSink {
virtual ~DecodedStream();

private:
void CreateData(MozPromiseHolder<GenericPromise>&& aPromise);
void DestroyData(UniquePtr<DecodedStreamData> aData);
void OnDataCreated(UniquePtr<DecodedStreamData> aData);
void InitTracks();
void AdvanceTracks();
void SendAudio(double aVolume, bool aIsSameOrigin);
void SendVideo(bool aIsSameOrigin);
Expand Down