mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
089cacb631
- Bug 1184867: [vp9] P1. VPXDecoder pass DTS to VideoData::Create. r=jya (18c5dfa2f)
- Bug 1184867: [MSE] P2. Update WebMContainerParser to be compatible with new MSE. r=kinetik (8ad865692)
- Bug 1184867: [MSE] P3. Use WebMDemuxer in TrackBuffersManager. r=jya (957b2f1bf)
- Bug 1185792: [webm] P1. Make MediaInfo.mFrame nsIntRect for visible area. r=jya (af461bb88)
- Bug 1185792: [webm] P2. AndroidDecoderModule requires API 16 or later. r=snorp (a0710615f)
- Bug 1185792: [webm] P3. Enable WebMDemuxer/MediaFormatReader by default. r=kinetik (9c9dbe96d)
- Bug 1180535 - Dispatch the media-playback notification when navigating away from a page that has a media element playing; r=baku (8dfc42c1f)
- Bug 1177259 - Improve the names of the methods of nsIAudioChannelAgent, r=alwu (760512d4f)
- Bug 1156472 - Part 1 - Allow to capture all HTMLMediaElements and AudioContexts for a document. r=baku,padenot (da382bae7)
- Bug 1156472 - Part 2 - Rename MediaEngineWebRTCAudioSource to MediaEngineWebRTCMicrophoneSource. r=jesup (91622741c)
- Bug 1156472 - Part 3 - Implement AudioCaptureStream. r=roc (857eb8ede)
- Bug 1156472 - Part 4 - Add a new MediaStreamGraph API to connect a MediaStream to a capture stream. r=jesup,roc (a3f1a5d9d)
- Bug 1181428 - Add persist option to OriginKey to skip saving of temporal keys. r=mt (9af4b9425)
- Bug 1181428 - Defer persisting until gUM succeeds or persistent permission. r=mt (db55b5e98)
- Bug 1156472 - Part 5 - Add MediaEngineWebRTCAudioCaptureSource as a new audio source, and "audioCapture" as a new MediaSource. r=jesup,bz (bee21be1d)
- Bug 1181428 - Move code to reuse test for active gum or persistent permission. r=mt (be4009601)
- Bug 1181428 - Plumb origin through to success path. r=mt (7f1dba49d)
- bug 1116382 reuse some UTF8toUTF16 code r=bholley (4f2411821)
- bug 1116382 accept abstract strings in NewURIFromString() r=bholley (3f54ab677)
- bug 1116382 resolve MediaSource urls on Source element src attributes when set r=bholley (6a804840e)
- bug 1116382 resolve MediaSource urls on media element src attributes when set r=bholley (44d6e9cd3)
- no need to RemoveMediaElementFromURITable, it is done already in ShutdownDecoder (7159dc04c)
- bug 1116382 always ShutdownDecoder() so that MediaSourceDecoder alone can manage MediaSource::Detach() r=bholley (d675be4c5)
- no need to RemoveMediaElementFromURITable (dcbb55480)
- Bug 1162639 - Make consistent how to handle MediaStreamURI r=roc (572c17214)
- no need to RemoveMediaElementFromURITable (647db2f47)
- bug 1116382 use MediaSource from resolution of MediaSource urls when set r=bholley (b6055f2f8)
- bug 1116382 no need to check IsMediaSourceURI(mLoadingSrc) once mMediaSource has been set r=bholley (ec34fb8b5)
- bug 1116382 auto revoke MediaSource object URLs r=bholley (b4f6e1847)
- bug 1116382 adjust explict revoke test to fail if revocation fails r=bholley (d2185e611)
- bug 1116382 test opening referenced MediaSource after URL.revokeObjectURL() r=bholley (6d7b80343)
- bug 1116382 test auto-revoking behavior with URL.createObjectURL(MediaSource) r=bholley (70fda515d)
- fix patch error (c56103f58)
- bit of Bug 659285 - Extend media.autoplay.enabled to provide a way to disable untrusted play() invocations. (d1265c47a)
- Bug 1135013 - Remove unused media-eme-metadataloaded notification. r=gerald (248717ff3)
- Bug 1183700 - Don't prevent playback of media elements in a muted tab on desktop, r=ehsan (fffeb70b4)
- Bug 1185303 - HTMLMediaElement should not be paused when visibility changed and when muted by the AudioChannel policy, r=ehsan (954f10541)
- Bug 1156472 - Part 6 - Connect HTMLMediaElement and AudioContext to the capture stream when capturing is needed. r=roc (0a845b985)
- Bug 1156472 - Part 7 - Allow to un-capture an HTMLMediaElement. r=pehrsons,jwwang (e07f287e5)
- Bug 1156472 - Part 8 - Use fatal asserts in AudioChannelUpmix, because it would have crashed anyways. r=roc (9c57a5b32)
- Bug 1157701 - Properly remove usage of Array.prototype.includes(). r=jib (7040e053c)
- Bug 1169338 - Part 3: Do a GC before each webrtc mochitest, so there isn't too much buildup. r=mt (b7f812bff)
- Bug 1156472 - Part 10 - Test AudioCaptureStream. r=pehrsons (f084533a2)
- Bug 1156472 - Part 11 - Unbitrot MediaManager.cpp over jib's changes. r=jib (4b5552125)
- Bug 1156472 - Part 12 - Allow to pipe the AudioCaptureStream into an AudioContext. r=mt,roc (8067c2512)
- Bug 1156472 - Part 13 - Make necessary adjustments for integer audio. r=jesup (deaf8e09e)
- Bug 1156472 - Part 14 - Null check the window, because it can be different during the window's shutdown. r=baku (65351205f)
- missing comment of 1153344 (83297ee81)
- Bug 1183518. Part 1 - move calls to mResource->SetReadMode out of Mediadecoder::{Start,Stop}ProgressUpdates since they are not related to progress update. r=roc. (b78e2f712)
- Bug 1183518. Part 2 - Move update of mIgnoreProgressData to main thread. r=cpearce. (33f73d117)
- Bug 1183518. Part 3 - early bailout from MediaDecoder::NotifyBytesConsumed when shutting down. r=jya. (f4b8234fc)
- Bug 1177452: Ensure start time is always calculated on first frame decoding. r=jwwang (cb4d66300)
- Bug 1185407. Part 1 - have AudioSink::Init() return a promise. r=kinetik. (dca5aee79)
- Bug 1185407. Part 2 - remove unused code. r=kinetik. (aeca11114)
- Bug 1185407. Part 3 - don't shutdown AudioStream prematurely. r=kinetik. (be11cfe89)
- Bug 1190496 - Remove dependency on VideoUtils.h for stack size. r=cpearce (d2b9a9c7c)
- Bug 1186358. Part 1 - Remove dependency on MediaDecoderStateMachine from AudioSink. r=kinetik. (96015551d)
- Bug 1186358. Part 2 - remove unnecessary/insufficient includes. r=kinetik. (6498e2f6e)
- Bug 1186299 - Consolidate AudioSink::PrepareToShutdown and AudioSink::Shutdown into one function. r=kinetik. (e6f755f9e)
- fix include (14742ce82)
- Bug 1184874. Part 1 - fix indentation. r=cpearce. (6f82906b3)
- Bug 1184874. Part 2 - add the ability to know which item is popped from the MediaQueue. r=cpearce. (54a65afc7)
- Bug 1184874. Part 3 - call mDecoder->UpdatePlaybackOffset in On{Audio,Video}Popped and remove MediaDecoderStateMachine::DispatchOnPlaybackOffsetUpdate. r=cpearce. (d280e80ce)
- Bug 1186801 - Remove decoder monitor from AudioSink. r=kinetik. (0fb442870)
668 lines
19 KiB
C++
668 lines
19 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "DecodedStream.h"
|
|
#include "MediaStreamGraph.h"
|
|
#include "AudioSegment.h"
|
|
#include "VideoSegment.h"
|
|
#include "MediaQueue.h"
|
|
#include "MediaData.h"
|
|
#include "SharedBuffer.h"
|
|
#include "VideoUtils.h"
|
|
|
|
namespace mozilla {
|
|
|
|
class DecodedStreamGraphListener : public MediaStreamListener {
|
|
typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent;
|
|
public:
|
|
explicit DecodedStreamGraphListener(MediaStream* aStream)
|
|
: mMutex("DecodedStreamGraphListener::mMutex")
|
|
, mStream(aStream)
|
|
, mLastOutputTime(aStream->StreamTimeToMicroseconds(aStream->GetCurrentTime()))
|
|
, mStreamFinishedOnMainThread(false) {}
|
|
|
|
void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) override
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
if (mStream) {
|
|
mLastOutputTime = mStream->StreamTimeToMicroseconds(mStream->GraphTimeToStreamTime(aCurrentTime));
|
|
}
|
|
}
|
|
|
|
void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override
|
|
{
|
|
if (event == EVENT_FINISHED) {
|
|
nsCOMPtr<nsIRunnable> event =
|
|
NS_NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
|
|
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
|
|
}
|
|
}
|
|
|
|
void DoNotifyFinished()
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
mStreamFinishedOnMainThread = true;
|
|
}
|
|
|
|
int64_t GetLastOutputTime()
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
return mLastOutputTime;
|
|
}
|
|
|
|
void Forget()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MutexAutoLock lock(mMutex);
|
|
mStream = nullptr;
|
|
}
|
|
|
|
bool IsFinishedOnMainThread()
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
return mStreamFinishedOnMainThread;
|
|
}
|
|
|
|
private:
|
|
Mutex mMutex;
|
|
// Members below are protected by mMutex.
|
|
nsRefPtr<MediaStream> mStream;
|
|
int64_t mLastOutputTime; // microseconds
|
|
bool mStreamFinishedOnMainThread;
|
|
};
|
|
|
|
static void
|
|
UpdateStreamBlocking(MediaStream* aStream, bool aBlocking)
|
|
{
|
|
int32_t delta = aBlocking ? 1 : -1;
|
|
if (NS_IsMainThread()) {
|
|
aStream->ChangeExplicitBlockerCount(delta);
|
|
} else {
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<int32_t>(
|
|
aStream, &MediaStream::ChangeExplicitBlockerCount, delta);
|
|
AbstractThread::MainThread()->Dispatch(r.forget());
|
|
}
|
|
}
|
|
|
|
DecodedStreamData::DecodedStreamData(SourceMediaStream* aStream, bool aPlaying)
|
|
: mAudioFramesWritten(0)
|
|
, mNextVideoTime(-1)
|
|
, mNextAudioTime(-1)
|
|
, mStreamInitialized(false)
|
|
, mHaveSentFinish(false)
|
|
, mHaveSentFinishAudio(false)
|
|
, mHaveSentFinishVideo(false)
|
|
, mStream(aStream)
|
|
, mPlaying(aPlaying)
|
|
, mEOSVideoCompensation(false)
|
|
{
|
|
mListener = new DecodedStreamGraphListener(mStream);
|
|
mStream->AddListener(mListener);
|
|
|
|
// Block the stream if we are not playing.
|
|
if (!aPlaying) {
|
|
UpdateStreamBlocking(mStream, true);
|
|
}
|
|
}
|
|
|
|
DecodedStreamData::~DecodedStreamData()
|
|
{
|
|
mListener->Forget();
|
|
mStream->Destroy();
|
|
}
|
|
|
|
bool
|
|
DecodedStreamData::IsFinished() const
|
|
{
|
|
return mListener->IsFinishedOnMainThread();
|
|
}
|
|
|
|
int64_t
|
|
DecodedStreamData::GetPosition() const
|
|
{
|
|
return mListener->GetLastOutputTime();
|
|
}
|
|
|
|
void
|
|
DecodedStreamData::SetPlaying(bool aPlaying)
|
|
{
|
|
if (mPlaying != aPlaying) {
|
|
mPlaying = aPlaying;
|
|
UpdateStreamBlocking(mStream, !mPlaying);
|
|
}
|
|
}
|
|
|
|
class OutputStreamListener : public MediaStreamListener {
|
|
typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent;
|
|
public:
|
|
OutputStreamListener(DecodedStream* aDecodedStream, MediaStream* aStream)
|
|
: mDecodedStream(aDecodedStream), mStream(aStream) {}
|
|
|
|
void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override
|
|
{
|
|
if (event == EVENT_FINISHED) {
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
|
|
this, &OutputStreamListener::DoNotifyFinished);
|
|
aGraph->DispatchToMainThreadAfterStreamStateUpdate(r.forget());
|
|
}
|
|
}
|
|
|
|
void Forget()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mDecodedStream = nullptr;
|
|
}
|
|
|
|
private:
|
|
void DoNotifyFinished()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (mDecodedStream) {
|
|
// Remove the finished stream so it won't block the decoded stream.
|
|
mDecodedStream->Remove(mStream);
|
|
}
|
|
}
|
|
|
|
// Main thread only
|
|
DecodedStream* mDecodedStream;
|
|
nsRefPtr<MediaStream> mStream;
|
|
};
|
|
|
|
OutputStreamData::~OutputStreamData()
|
|
{
|
|
mListener->Forget();
|
|
}
|
|
|
|
void
|
|
OutputStreamData::Init(DecodedStream* aDecodedStream, ProcessedMediaStream* aStream)
|
|
{
|
|
mStream = aStream;
|
|
mListener = new OutputStreamListener(aDecodedStream, aStream);
|
|
aStream->AddListener(mListener);
|
|
}
|
|
|
|
DecodedStream::DecodedStream(MediaQueue<AudioData>& aAudioQueue,
|
|
MediaQueue<VideoData>& aVideoQueue)
|
|
: mMonitor("DecodedStream::mMonitor")
|
|
, mPlaying(false)
|
|
, mAudioQueue(aAudioQueue)
|
|
, mVideoQueue(aVideoQueue)
|
|
{
|
|
//
|
|
}
|
|
|
|
void
|
|
DecodedStream::StartPlayback(int64_t aStartTime, const MediaInfo& aInfo)
|
|
{
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
if (mStartTime.isNothing()) {
|
|
mStartTime.emplace(aStartTime);
|
|
mInfo = aInfo;
|
|
}
|
|
}
|
|
|
|
void DecodedStream::StopPlayback()
|
|
{
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
mStartTime.reset();
|
|
}
|
|
|
|
void
|
|
DecodedStream::DestroyData()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
|
|
// Avoid the redundant blocking to output stream.
|
|
if (!mData) {
|
|
return;
|
|
}
|
|
|
|
// All streams are having their SourceMediaStream disconnected, so they
|
|
// need to be explicitly blocked again.
|
|
auto& outputStreams = OutputStreams();
|
|
for (int32_t i = outputStreams.Length() - 1; i >= 0; --i) {
|
|
OutputStreamData& os = outputStreams[i];
|
|
// Explicitly remove all existing ports.
|
|
// This is not strictly necessary but it's good form.
|
|
MOZ_ASSERT(os.mPort, "Double-delete of the ports!");
|
|
os.mPort->Destroy();
|
|
os.mPort = nullptr;
|
|
// During cycle collection, nsDOMMediaStream 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 (os.mStream->IsDestroyed()) {
|
|
// Probably the DOM MediaStream was GCed. Clean up.
|
|
outputStreams.RemoveElementAt(i);
|
|
} else {
|
|
os.mStream->ChangeExplicitBlockerCount(1);
|
|
}
|
|
}
|
|
|
|
mData = nullptr;
|
|
}
|
|
|
|
void
|
|
DecodedStream::RecreateData()
|
|
{
|
|
nsRefPtr<DecodedStream> self = this;
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void {
|
|
self->RecreateData(nullptr);
|
|
});
|
|
AbstractThread::MainThread()->Dispatch(r.forget());
|
|
}
|
|
|
|
void
|
|
DecodedStream::RecreateData(MediaStreamGraph* aGraph)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
MOZ_ASSERT((aGraph && !mData && OutputStreams().IsEmpty()) || // first time
|
|
(!aGraph && mData)); // 2nd time and later
|
|
|
|
auto source = aGraph->CreateSourceStream(nullptr);
|
|
DestroyData();
|
|
mData.reset(new DecodedStreamData(source, mPlaying));
|
|
|
|
// Note that the delay between removing ports in DestroyDecodedStream
|
|
// and adding new ones won't cause a glitch since all graph operations
|
|
// between main-thread stable states take effect atomically.
|
|
auto& outputStreams = OutputStreams();
|
|
for (int32_t i = outputStreams.Length() - 1; i >= 0; --i) {
|
|
OutputStreamData& os = outputStreams[i];
|
|
MOZ_ASSERT(!os.mStream->IsDestroyed(), "Should've been removed in DestroyData()");
|
|
Connect(&os);
|
|
}
|
|
}
|
|
|
|
nsTArray<OutputStreamData>&
|
|
DecodedStream::OutputStreams()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
GetReentrantMonitor().AssertCurrentThreadIn();
|
|
return mOutputStreams;
|
|
}
|
|
|
|
bool
|
|
DecodedStream::HasConsumers() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
return mOutputStreams.IsEmpty();
|
|
}
|
|
|
|
ReentrantMonitor&
|
|
DecodedStream::GetReentrantMonitor() const
|
|
{
|
|
return mMonitor;
|
|
}
|
|
|
|
void
|
|
DecodedStream::Connect(OutputStreamData* aStream)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
GetReentrantMonitor().AssertCurrentThreadIn();
|
|
NS_ASSERTION(!aStream->mPort, "Already connected?");
|
|
|
|
// The output stream must stay in sync with the decoded stream, so if
|
|
// either stream is blocked, we block the other.
|
|
aStream->mPort = aStream->mStream->AllocateInputPort(mData->mStream,
|
|
MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT);
|
|
// Unblock the output stream now. While it's connected to DecodedStream,
|
|
// DecodedStream is responsible for controlling blocking.
|
|
aStream->mStream->ChangeExplicitBlockerCount(-1);
|
|
}
|
|
|
|
void
|
|
DecodedStream::Connect(ProcessedMediaStream* aStream, bool aFinishWhenEnded)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
|
|
if (!mData) {
|
|
RecreateData(aStream->Graph());
|
|
}
|
|
|
|
OutputStreamData* os = OutputStreams().AppendElement();
|
|
os->Init(this, aStream);
|
|
Connect(os);
|
|
if (aFinishWhenEnded) {
|
|
// Ensure that aStream finishes the moment mDecodedStream does.
|
|
aStream->SetAutofinish(true);
|
|
}
|
|
}
|
|
|
|
void
|
|
DecodedStream::Remove(MediaStream* aStream)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
|
|
auto& streams = OutputStreams();
|
|
for (int32_t i = streams.Length() - 1; i >= 0; --i) {
|
|
auto& os = streams[i];
|
|
MediaStream* p = os.mStream.get();
|
|
if (p == aStream) {
|
|
if (os.mPort) {
|
|
os.mPort->Destroy();
|
|
os.mPort = nullptr;
|
|
}
|
|
streams.RemoveElementAt(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
DecodedStream::SetPlaying(bool aPlaying)
|
|
{
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
mPlaying = aPlaying;
|
|
if (mData) {
|
|
mData->SetPlaying(aPlaying);
|
|
}
|
|
}
|
|
|
|
void
|
|
DecodedStream::InitTracks()
|
|
{
|
|
GetReentrantMonitor().AssertCurrentThreadIn();
|
|
|
|
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,
|
|
AudioData* aAudio, AudioSegment* aOutput,
|
|
uint32_t aRate, double aVolume)
|
|
{
|
|
// 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(aAudio->mTime, aRate);
|
|
|
|
if (!audioWrittenOffset.isValid() ||
|
|
!frameOffset.isValid() ||
|
|
// ignore packet that we've already processed
|
|
frameOffset.value() + aAudio->mFrames <= audioWrittenOffset.value()) {
|
|
return;
|
|
}
|
|
|
|
if (audioWrittenOffset.value() < 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);
|
|
}
|
|
|
|
MOZ_ASSERT(audioWrittenOffset.value() >= frameOffset.value());
|
|
|
|
int64_t offset = audioWrittenOffset.value() - frameOffset.value();
|
|
size_t framesToWrite = aAudio->mFrames - offset;
|
|
|
|
aAudio->EnsureAudioBuffer();
|
|
nsRefPtr<SharedBuffer> buffer = aAudio->mAudioBuffer;
|
|
AudioDataValue* bufferData = static_cast<AudioDataValue*>(buffer->Data());
|
|
nsAutoTArray<const AudioDataValue*, 2> channels;
|
|
for (uint32_t i = 0; i < aAudio->mChannels; ++i) {
|
|
channels.AppendElement(bufferData + i * aAudio->mFrames + offset);
|
|
}
|
|
aOutput->AppendFrames(buffer.forget(), channels, framesToWrite);
|
|
aStream->mAudioFramesWritten += framesToWrite;
|
|
aOutput->ApplyVolume(aVolume);
|
|
|
|
aStream->mNextAudioTime = aAudio->GetEndTime();
|
|
}
|
|
|
|
void
|
|
DecodedStream::SendAudio(double aVolume, bool aIsSameOrigin)
|
|
{
|
|
GetReentrantMonitor().AssertCurrentThreadIn();
|
|
|
|
if (!mInfo.HasAudio()) {
|
|
return;
|
|
}
|
|
|
|
AudioSegment output;
|
|
uint32_t rate = mInfo.mAudio.mRate;
|
|
nsAutoTArray<nsRefPtr<AudioData>,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, 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,
|
|
VideoSegment* aOutput)
|
|
{
|
|
nsRefPtr<layers::Image> image = aImage;
|
|
StreamTime duration =
|
|
aStream->MicrosecondsToStreamTimeRoundDown(aEndMicroseconds) -
|
|
aStream->MicrosecondsToStreamTimeRoundDown(aStartMicroseconds);
|
|
aOutput->AppendFrame(image.forget(), duration, aIntrinsicSize);
|
|
}
|
|
|
|
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)
|
|
{
|
|
GetReentrantMonitor().AssertCurrentThreadIn();
|
|
|
|
if (!mInfo.HasVideo()) {
|
|
return;
|
|
}
|
|
|
|
VideoSegment output;
|
|
TrackID videoTrackId = mInfo.mVideo.mTrackId;
|
|
nsAutoTArray<nsRefPtr<VideoData>, 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);
|
|
|
|
for (uint32_t i = 0; i < video.Length(); ++i) {
|
|
VideoData* v = video[i];
|
|
|
|
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, &output);
|
|
mData->mNextVideoTime = v->mTime;
|
|
}
|
|
|
|
if (mData->mNextVideoTime < v->GetEndTime()) {
|
|
WriteVideoToMediaStream(sourceStream, v->mImage,
|
|
v->GetEndTime(), mData->mNextVideoTime, v->mDisplay, &output);
|
|
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, &endSegment);
|
|
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()
|
|
{
|
|
GetReentrantMonitor().AssertCurrentThreadIn();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
bool
|
|
DecodedStream::SendData(double aVolume, bool aIsSameOrigin)
|
|
{
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
MOZ_ASSERT(mStartTime.isSome(), "Must be called after StartPlayback()");
|
|
|
|
InitTracks();
|
|
SendAudio(aVolume, aIsSameOrigin);
|
|
SendVideo(aIsSameOrigin);
|
|
AdvanceTracks();
|
|
|
|
bool finished = (!mInfo.HasAudio() || mAudioQueue.IsFinished()) &&
|
|
(!mInfo.HasVideo() || mVideoQueue.IsFinished());
|
|
|
|
if (finished && !mData->mHaveSentFinish) {
|
|
mData->mHaveSentFinish = true;
|
|
mData->mStream->Finish();
|
|
}
|
|
|
|
return finished;
|
|
}
|
|
|
|
int64_t
|
|
DecodedStream::AudioEndTime() const
|
|
{
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
if (mStartTime.isSome() && mInfo.HasAudio()) {
|
|
CheckedInt64 t = mStartTime.ref() +
|
|
FramesToUsecs(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
|
|
if (t.isValid()) {
|
|
return t.value();
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int64_t
|
|
DecodedStream::GetPosition() const
|
|
{
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
// This is only called after MDSM starts playback. So mStartTime is
|
|
// guaranteed to be something.
|
|
MOZ_ASSERT(mStartTime.isSome());
|
|
return mStartTime.ref() + mData->GetPosition();
|
|
}
|
|
|
|
bool
|
|
DecodedStream::IsFinished() const
|
|
{
|
|
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
|
return mData->IsFinished();
|
|
}
|
|
|
|
} // namespace mozilla
|