mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1187817. Part 1 - Move Set{Volume,PlaybackRate,PreservesPitch} to the audio thread. r=kinetik. (a234a7080)
- Bug 1187817. Part 2 - remove unused code. r=kinetik. (836c52a19)
- Bug 1187763. Part 1 - move while loop out of WaitingForAudioToPlay(). r=kinetik. (a80d70f87)
- Bug 1187763. Part 2 - extract some code of AudioLoop() into its own function. r=kinetik. (275b8bfb6)
- Bug 1187763. Part 3 - refactor AudioSink::AudioLoop into a series of events. r=kinetik (7eb3f506f)
- Bug 1187817. Part 3 - move SetPlaying to the audio thread. r=kinetik. (0fef85968)
- Bug 1187817. Part 4 - move some code in Shutdown to the audio thread. r=kinetik. (8c73fbe6d)
- Bug 1187817. Part 5 - assert some code in the audio thread and don't enter the monitor. r=kinetik. (64de4616f)
- move include (f842b4b1e)
- Bug 1163486 - Update test to use new MP4Demuxer. r=bholley (b853be477)
- Bug 1190496 - Namespace the SharedThreadPool.h include. r=cpearce (196d25d42)
- Bug 1190496 - Hoist SharedThreadPool into xpcom. r=froydnj (3dad8176f)
- Bug 1190492 - Hoist AbstractThread and TaskDispatcher to xpcom. r=froydnj (c3329fa29)
- bug 1166107 documentation of mWaitForInternalDrain thread access r=gerald (f762764b1)
- correct comment (b623b2959)
- revert demuxer check from promise reject to assert like in 1156708 (f540b270c)
- more chekcs back to asserts (7e82a0f99)
- space (733bd85a3)
- Bug 1188220: Allow disabling HW acceleration even when SharedDecoderManager isn't used. r=cpearce (909a86682)
- bug 1161903 ensure pending DrainComplete is not run after Flush() r=cpearce (648cabbb7)
- Bug 1158089 - Fall back to d3d9 DXVA if d3d11 initialization fails. r=cpearce (84b3a8e6d)
This commit is contained in:
+237
-150
@@ -10,6 +10,7 @@
|
||||
#include "VideoUtils.h"
|
||||
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -28,22 +29,60 @@ AudioSink::AudioSink(MediaQueue<AudioData>& aAudioQueue,
|
||||
dom::AudioChannel aChannel)
|
||||
: mAudioQueue(aAudioQueue)
|
||||
, mMonitor("AudioSink::mMonitor")
|
||||
, mState(AUDIOSINK_STATE_INIT)
|
||||
, mAudioLoopScheduled(false)
|
||||
, mStartTime(aStartTime)
|
||||
, mWritten(0)
|
||||
, mLastGoodPosition(0)
|
||||
, mInfo(aInfo)
|
||||
, mChannel(aChannel)
|
||||
, mVolume(1.0)
|
||||
, mPlaybackRate(1.0)
|
||||
, mPreservesPitch(false)
|
||||
, mStopAudioThread(false)
|
||||
, mSetVolume(false)
|
||||
, mSetPlaybackRate(false)
|
||||
, mSetPreservesPitch(false)
|
||||
, mPlaying(true)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::SetState(State aState)
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
mPendingState = Some(aState);
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::DispatchTask(already_AddRefed<nsIRunnable>&& event)
|
||||
{
|
||||
DebugOnly<nsresult> rv = mThread->Dispatch(Move(event), NS_DISPATCH_NORMAL);
|
||||
// There isn't much we can do if Dispatch() fails.
|
||||
// Just assert it to keep things simple.
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::ScheduleNextLoop()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
if (mAudioLoopScheduled) {
|
||||
return;
|
||||
}
|
||||
mAudioLoopScheduled = true;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &AudioSink::AudioLoop);
|
||||
DispatchTask(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::ScheduleNextLoopCrossThread()
|
||||
{
|
||||
AssertNotOnAudioThread();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () {
|
||||
// Do nothing if there is already a pending task waiting for its turn.
|
||||
if (!self->mAudioLoopScheduled) {
|
||||
self->AudioLoop();
|
||||
}
|
||||
});
|
||||
DispatchTask(r.forget());
|
||||
}
|
||||
|
||||
nsRefPtr<GenericPromise>
|
||||
AudioSink::Init()
|
||||
{
|
||||
@@ -57,13 +96,7 @@ AudioSink::Init()
|
||||
return p;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &AudioSink::AudioLoop);
|
||||
rv = mThread->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
mEndPromise.Reject(rv, __func__);
|
||||
return p;
|
||||
}
|
||||
|
||||
ScheduleNextLoopCrossThread();
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -94,120 +127,104 @@ AudioSink::HasUnplayedFrames()
|
||||
void
|
||||
AudioSink::Shutdown()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mStopAudioThread = true;
|
||||
if (mAudioStream) {
|
||||
mAudioStream->Cancel();
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
if (mAudioStream) {
|
||||
mAudioStream->Cancel();
|
||||
}
|
||||
}
|
||||
GetReentrantMonitor().NotifyAll();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
self->mStopAudioThread = true;
|
||||
if (!self->mAudioLoopScheduled) {
|
||||
self->AudioLoop();
|
||||
}
|
||||
});
|
||||
DispatchTask(r.forget());
|
||||
|
||||
// Exit the monitor so audio loop can enter the monitor and finish its job.
|
||||
ReentrantMonitorAutoExit exit(GetReentrantMonitor());
|
||||
mThread->Shutdown();
|
||||
mThread = nullptr;
|
||||
if (mAudioStream) {
|
||||
mAudioStream->Shutdown();
|
||||
mAudioStream = nullptr;
|
||||
}
|
||||
|
||||
// Should've reached the final state after shutdown.
|
||||
MOZ_ASSERT(mState == AUDIOSINK_STATE_SHUTDOWN ||
|
||||
mState == AUDIOSINK_STATE_ERROR);
|
||||
// Should have no pending state change.
|
||||
MOZ_ASSERT(mPendingState.isNothing());
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::SetVolume(double aVolume)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mVolume = aVolume;
|
||||
mSetVolume = true;
|
||||
AssertNotOnAudioThread();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
if (self->mState == AUDIOSINK_STATE_PLAYING) {
|
||||
self->mAudioStream->SetVolume(aVolume);
|
||||
}
|
||||
});
|
||||
DispatchTask(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::SetPlaybackRate(double aPlaybackRate)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
NS_ASSERTION(mPlaybackRate != 0, "Don't set the playbackRate to 0 on AudioStream");
|
||||
mPlaybackRate = aPlaybackRate;
|
||||
mSetPlaybackRate = true;
|
||||
AssertNotOnAudioThread();
|
||||
MOZ_ASSERT(aPlaybackRate != 0, "Don't set the playbackRate to 0 on AudioStream");
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
if (self->mState == AUDIOSINK_STATE_PLAYING) {
|
||||
self->mAudioStream->SetPlaybackRate(aPlaybackRate);
|
||||
}
|
||||
});
|
||||
DispatchTask(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::SetPreservesPitch(bool aPreservesPitch)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mPreservesPitch = aPreservesPitch;
|
||||
mSetPreservesPitch = true;
|
||||
AssertNotOnAudioThread();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
if (self->mState == AUDIOSINK_STATE_PLAYING) {
|
||||
self->mAudioStream->SetPreservesPitch(aPreservesPitch);
|
||||
}
|
||||
});
|
||||
DispatchTask(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::SetPlaying(bool aPlaying)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mPlaying = aPlaying;
|
||||
GetReentrantMonitor().NotifyAll();
|
||||
AssertNotOnAudioThread();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
if (self->mState != AUDIOSINK_STATE_PLAYING ||
|
||||
self->mPlaying == aPlaying) {
|
||||
return;
|
||||
}
|
||||
self->mPlaying = aPlaying;
|
||||
// pause/resume AudioStream as necessary.
|
||||
if (!aPlaying && !self->mAudioStream->IsPaused()) {
|
||||
self->mAudioStream->Pause();
|
||||
} else if (aPlaying && self->mAudioStream->IsPaused()) {
|
||||
self->mAudioStream->Resume();
|
||||
}
|
||||
// Wake up the audio loop to play next sample.
|
||||
if (aPlaying && !self->mAudioLoopScheduled) {
|
||||
self->AudioLoop();
|
||||
}
|
||||
});
|
||||
DispatchTask(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::NotifyData()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
GetReentrantMonitor().NotifyAll();
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::AudioLoop()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
SINK_LOG("AudioLoop started");
|
||||
|
||||
nsresult rv = InitializeAudioStream();
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Initializing AudioStream failed.");
|
||||
mEndPromise.Reject(rv, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
WaitForAudioToPlay();
|
||||
if (!IsPlaybackContinuing()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
NS_ASSERTION(AudioQueue().GetSize() > 0, "Should have data to play");
|
||||
CheckedInt64 sampleTime = UsecsToFrames(AudioQueue().PeekFront()->mTime, mInfo.mRate);
|
||||
|
||||
// Calculate the number of frames that have been pushed onto the audio hardware.
|
||||
CheckedInt64 playedFrames = UsecsToFrames(mStartTime, mInfo.mRate) +
|
||||
static_cast<int64_t>(mWritten);
|
||||
|
||||
CheckedInt64 missingFrames = sampleTime - playedFrames;
|
||||
if (!missingFrames.isValid() || !sampleTime.isValid()) {
|
||||
NS_WARNING("Int overflow adding in AudioLoop");
|
||||
break;
|
||||
}
|
||||
|
||||
if (missingFrames.value() > AUDIO_FUZZ_FRAMES) {
|
||||
// The next audio chunk begins some time after the end of the last chunk
|
||||
// we pushed to the audio hardware. We must push silence into the audio
|
||||
// hardware so that the next audio chunk begins playback at the correct
|
||||
// time.
|
||||
missingFrames = std::min<int64_t>(UINT32_MAX, missingFrames.value());
|
||||
mWritten += PlaySilence(static_cast<uint32_t>(missingFrames.value()));
|
||||
} else {
|
||||
mWritten += PlayFromAudioQueue();
|
||||
}
|
||||
}
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
MOZ_ASSERT(mStopAudioThread || AudioQueue().AtEndOfStream());
|
||||
if (!mStopAudioThread && mPlaying) {
|
||||
Drain();
|
||||
}
|
||||
SINK_LOG("AudioLoop complete");
|
||||
Cleanup();
|
||||
SINK_LOG("AudioLoop exit");
|
||||
ScheduleNextLoopCrossThread();
|
||||
}
|
||||
|
||||
nsresult
|
||||
@@ -226,7 +243,6 @@ AudioSink::InitializeAudioStream()
|
||||
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mAudioStream = audioStream;
|
||||
UpdateStreamSettings();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@@ -234,21 +250,18 @@ AudioSink::InitializeAudioStream()
|
||||
void
|
||||
AudioSink::Drain()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
MOZ_ASSERT(mPlaying && !mAudioStream->IsPaused());
|
||||
AssertCurrentThreadInMonitor();
|
||||
// If the media was too short to trigger the start of the audio stream,
|
||||
// start it now.
|
||||
mAudioStream->Start();
|
||||
{
|
||||
ReentrantMonitorAutoExit exit(GetReentrantMonitor());
|
||||
mAudioStream->Drain();
|
||||
}
|
||||
mAudioStream->Drain();
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::Cleanup()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
AssertOnAudioThread();
|
||||
mEndPromise.Resolve(true, __func__);
|
||||
// Since the promise if resolved asynchronously, we don't shutdown
|
||||
// AudioStream here so MDSM::ResyncAudioClock can get the correct
|
||||
@@ -261,39 +274,141 @@ AudioSink::ExpectMoreAudioData()
|
||||
return AudioQueue().GetSize() == 0 && !AudioQueue().IsFinished();
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::WaitForAudioToPlay()
|
||||
bool
|
||||
AudioSink::WaitingForAudioToPlay()
|
||||
{
|
||||
// Wait while we're not playing, and we're not shutting down, or we're
|
||||
AssertOnAudioThread();
|
||||
// Return true if we're not playing, and we're not shutting down, or we're
|
||||
// playing and we've got no audio to play.
|
||||
AssertCurrentThreadInMonitor();
|
||||
while (!mStopAudioThread && (!mPlaying || ExpectMoreAudioData())) {
|
||||
if (!mPlaying && !mAudioStream->IsPaused()) {
|
||||
mAudioStream->Pause();
|
||||
}
|
||||
GetReentrantMonitor().Wait();
|
||||
if (!mStopAudioThread && (!mPlaying || ExpectMoreAudioData())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioSink::IsPlaybackContinuing()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
if (mPlaying && mAudioStream->IsPaused()) {
|
||||
mAudioStream->Resume();
|
||||
}
|
||||
|
||||
AssertOnAudioThread();
|
||||
// If we're shutting down, captured, or at EOS, break out and exit the audio
|
||||
// thread.
|
||||
if (mStopAudioThread || AudioQueue().AtEndOfStream()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateStreamSettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::AudioLoop()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
mAudioLoopScheduled = false;
|
||||
|
||||
switch (mState) {
|
||||
case AUDIOSINK_STATE_INIT: {
|
||||
SINK_LOG("AudioLoop started");
|
||||
nsresult rv = InitializeAudioStream();
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Initializing AudioStream failed.");
|
||||
mEndPromise.Reject(rv, __func__);
|
||||
SetState(AUDIOSINK_STATE_ERROR);
|
||||
break;
|
||||
}
|
||||
SetState(AUDIOSINK_STATE_PLAYING);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDIOSINK_STATE_PLAYING: {
|
||||
if (WaitingForAudioToPlay()) {
|
||||
// NotifyData() will schedule next loop.
|
||||
break;
|
||||
}
|
||||
if (!IsPlaybackContinuing()) {
|
||||
SetState(AUDIOSINK_STATE_COMPLETE);
|
||||
break;
|
||||
}
|
||||
if (!PlayAudio()) {
|
||||
SetState(AUDIOSINK_STATE_COMPLETE);
|
||||
break;
|
||||
}
|
||||
// Schedule next loop to play next sample.
|
||||
ScheduleNextLoop();
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDIOSINK_STATE_COMPLETE: {
|
||||
FinishAudioLoop();
|
||||
SetState(AUDIOSINK_STATE_SHUTDOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDIOSINK_STATE_SHUTDOWN:
|
||||
break;
|
||||
|
||||
case AUDIOSINK_STATE_ERROR:
|
||||
break;
|
||||
} // end of switch
|
||||
|
||||
// We want mState to stay stable during AudioLoop to keep things simple.
|
||||
// Therefore, we only do state transition at the end of AudioLoop.
|
||||
if (mPendingState.isSome()) {
|
||||
MOZ_ASSERT(mState != mPendingState.ref());
|
||||
SINK_LOG("change mState, %d -> %d", mState, mPendingState.ref());
|
||||
mState = mPendingState.ref();
|
||||
mPendingState.reset();
|
||||
// Schedule next loop when state changes.
|
||||
ScheduleNextLoop();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
AudioSink::PlayAudio()
|
||||
{
|
||||
// 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.
|
||||
NS_ASSERTION(AudioQueue().GetSize() > 0, "Should have data to play");
|
||||
CheckedInt64 sampleTime = UsecsToFrames(AudioQueue().PeekFront()->mTime, mInfo.mRate);
|
||||
|
||||
// Calculate the number of frames that have been pushed onto the audio hardware.
|
||||
CheckedInt64 playedFrames = UsecsToFrames(mStartTime, mInfo.mRate) +
|
||||
static_cast<int64_t>(mWritten);
|
||||
|
||||
CheckedInt64 missingFrames = sampleTime - playedFrames;
|
||||
if (!missingFrames.isValid() || !sampleTime.isValid()) {
|
||||
NS_WARNING("Int overflow adding in AudioLoop");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (missingFrames.value() > AUDIO_FUZZ_FRAMES) {
|
||||
// The next audio chunk begins some time after the end of the last chunk
|
||||
// we pushed to the audio hardware. We must push silence into the audio
|
||||
// hardware so that the next audio chunk begins playback at the correct
|
||||
// time.
|
||||
missingFrames = std::min<int64_t>(UINT32_MAX, missingFrames.value());
|
||||
mWritten += PlaySilence(static_cast<uint32_t>(missingFrames.value()));
|
||||
} else {
|
||||
mWritten += PlayFromAudioQueue();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::FinishAudioLoop()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
MOZ_ASSERT(mStopAudioThread || AudioQueue().AtEndOfStream());
|
||||
if (!mStopAudioThread && mPlaying) {
|
||||
Drain();
|
||||
}
|
||||
SINK_LOG("AudioLoop complete");
|
||||
Cleanup();
|
||||
SINK_LOG("AudioLoop exit");
|
||||
}
|
||||
|
||||
uint32_t
|
||||
AudioSink::PlaySilence(uint32_t aFrames)
|
||||
{
|
||||
@@ -335,40 +450,6 @@ AudioSink::PlayFromAudioQueue()
|
||||
return audio->mFrames;
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::UpdateStreamSettings()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
||||
bool setVolume = mSetVolume;
|
||||
bool setPlaybackRate = mSetPlaybackRate;
|
||||
bool setPreservesPitch = mSetPreservesPitch;
|
||||
double volume = mVolume;
|
||||
double playbackRate = mPlaybackRate;
|
||||
bool preservesPitch = mPreservesPitch;
|
||||
|
||||
mSetVolume = false;
|
||||
mSetPlaybackRate = false;
|
||||
mSetPreservesPitch = false;
|
||||
|
||||
{
|
||||
ReentrantMonitorAutoExit exit(GetReentrantMonitor());
|
||||
if (setVolume) {
|
||||
mAudioStream->SetVolume(volume);
|
||||
}
|
||||
|
||||
if (setPlaybackRate &&
|
||||
NS_FAILED(mAudioStream->SetPlaybackRate(playbackRate))) {
|
||||
NS_WARNING("Setting the playback rate failed in AudioSink.");
|
||||
}
|
||||
|
||||
if (setPreservesPitch &&
|
||||
NS_FAILED(mAudioStream->SetPreservesPitch(preservesPitch))) {
|
||||
NS_WARNING("Setting the pitch preservation failed in AudioSink.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::StartAudioStreamPlaybackIfNeeded()
|
||||
{
|
||||
@@ -411,4 +492,10 @@ AudioSink::AssertOnAudioThread()
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mThread);
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::AssertNotOnAudioThread()
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() != mThread);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
+28
-12
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "mozilla/dom/AudioChannelBinding.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
|
||||
@@ -57,8 +58,21 @@ public:
|
||||
void NotifyData();
|
||||
|
||||
private:
|
||||
enum State {
|
||||
AUDIOSINK_STATE_INIT,
|
||||
AUDIOSINK_STATE_PLAYING,
|
||||
AUDIOSINK_STATE_COMPLETE,
|
||||
AUDIOSINK_STATE_SHUTDOWN,
|
||||
AUDIOSINK_STATE_ERROR
|
||||
};
|
||||
|
||||
~AudioSink() {}
|
||||
|
||||
void DispatchTask(already_AddRefed<nsIRunnable>&& event);
|
||||
void SetState(State aState);
|
||||
void ScheduleNextLoop();
|
||||
void ScheduleNextLoopCrossThread();
|
||||
|
||||
// The main loop for the audio thread. Sent to the thread as
|
||||
// an nsRunnableMethod. This continually does blocking writes to
|
||||
// to audio stream to play audio data.
|
||||
@@ -73,14 +87,20 @@ private:
|
||||
|
||||
bool ExpectMoreAudioData();
|
||||
|
||||
// Wait on the decoder monitor until playback is ready or the sink is told to shut down.
|
||||
void WaitForAudioToPlay();
|
||||
// Return true if playback is not ready and the sink is not told to shut down.
|
||||
bool WaitingForAudioToPlay();
|
||||
|
||||
// Check if the sink has been told to shut down, resuming mAudioStream if
|
||||
// not. Returns true if processing should continue, false if AudioLoop
|
||||
// should shutdown.
|
||||
bool IsPlaybackContinuing();
|
||||
|
||||
// Write audio samples or silence to the audio hardware.
|
||||
// Return false if any error. Called on the audio thread.
|
||||
bool PlayAudio();
|
||||
|
||||
void FinishAudioLoop();
|
||||
|
||||
// Write aFrames of audio frames of silence to the audio hardware. Returns
|
||||
// the number of frames actually written. The write size is capped at
|
||||
// SILENCE_BYTES_CHUNK (32kB), so must be called in a loop to write the
|
||||
@@ -94,8 +114,6 @@ private:
|
||||
// audio data to the audio hardware. Called on the audio thread.
|
||||
uint32_t PlayFromAudioQueue();
|
||||
|
||||
void UpdateStreamSettings();
|
||||
|
||||
// If we have already written enough frames to the AudioStream, start the
|
||||
// playback.
|
||||
void StartAudioStreamPlaybackIfNeeded();
|
||||
@@ -114,10 +132,16 @@ private:
|
||||
}
|
||||
|
||||
void AssertOnAudioThread();
|
||||
void AssertNotOnAudioThread();
|
||||
|
||||
MediaQueue<AudioData>& mAudioQueue;
|
||||
mutable ReentrantMonitor mMonitor;
|
||||
|
||||
// There members are accessed on the audio thread only.
|
||||
State mState;
|
||||
Maybe<State> mPendingState;
|
||||
bool mAudioLoopScheduled;
|
||||
|
||||
// Thread for pushing audio onto the audio hardware.
|
||||
// The "audio push thread".
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
@@ -146,16 +170,8 @@ private:
|
||||
|
||||
dom::AudioChannel mChannel;
|
||||
|
||||
double mVolume;
|
||||
double mPlaybackRate;
|
||||
bool mPreservesPitch;
|
||||
|
||||
bool mStopAudioThread;
|
||||
|
||||
bool mSetVolume;
|
||||
bool mSetPlaybackRate;
|
||||
bool mSetPreservesPitch;
|
||||
|
||||
bool mPlaying;
|
||||
|
||||
MozPromiseHolder<GenericPromise> mEndPromise;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "MediaCache.h"
|
||||
#include "nsDeque.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
|
||||
struct PRFileDesc;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "nsITimer.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "MediaShutdownManager.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
#include "nsIEventTarget.h"
|
||||
#include "prenv.h"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "MediaFormatReader.h"
|
||||
#include "MediaResource.h"
|
||||
#include "SharedDecoderManager.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -257,10 +257,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
|
||||
if (videoActive) {
|
||||
// We currently only handle the first video track.
|
||||
mVideo.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
|
||||
if (!mVideo.mTrackDemuxer) {
|
||||
mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mVideo.mTrackDemuxer);
|
||||
mInfo.mVideo = *mVideo.mTrackDemuxer->GetInfo()->GetAsVideoInfo();
|
||||
mVideo.mCallback = new DecoderCallback(this, TrackInfo::kVideoTrack);
|
||||
mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
|
||||
@@ -270,10 +267,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
|
||||
bool audioActive = !!mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
|
||||
if (audioActive) {
|
||||
mAudio.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
|
||||
if (!mAudio.mTrackDemuxer) {
|
||||
mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mAudio.mTrackDemuxer);
|
||||
mInfo.mAudio = *mAudio.mTrackDemuxer->GetInfo()->GetAsAudioInfo();
|
||||
mAudio.mCallback = new DecoderCallback(this, TrackInfo::kAudioTrack);
|
||||
mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
|
||||
@@ -317,18 +311,12 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
|
||||
if (videoActive) {
|
||||
mVideoTrackDemuxer =
|
||||
mMainThreadDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
|
||||
if (!mVideoTrackDemuxer) {
|
||||
mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mVideoTrackDemuxer);
|
||||
}
|
||||
if (audioActive) {
|
||||
mAudioTrackDemuxer =
|
||||
mMainThreadDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
|
||||
if (!mAudioTrackDemuxer) {
|
||||
mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mAudioTrackDemuxer);
|
||||
}
|
||||
|
||||
mInitDone = true;
|
||||
@@ -374,7 +362,7 @@ MediaFormatReader::EnsureDecodersSetup()
|
||||
|
||||
if (HasAudio() && !mAudio.mDecoder) {
|
||||
NS_ENSURE_TRUE(IsSupportedAudioMimeType(mInfo.mAudio.mMimeType),
|
||||
false);
|
||||
false);
|
||||
|
||||
mAudio.mDecoder =
|
||||
mPlatform->CreateDecoder(mAudio.mInfo ?
|
||||
@@ -441,11 +429,15 @@ void
|
||||
MediaFormatReader::DisableHardwareAcceleration()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
if (HasVideo() && mSharedDecoderManager) {
|
||||
mSharedDecoderManager->DisableHardwareAcceleration();
|
||||
|
||||
if (!mSharedDecoderManager->Recreate(mInfo.mVideo)) {
|
||||
mVideo.mError = true;
|
||||
if (HasVideo()) {
|
||||
mPlatform->DisableHardwareAcceleration();
|
||||
Flush(TrackInfo::kVideoTrack);
|
||||
mVideo.mDecoder->Shutdown();
|
||||
mVideo.mDecoder = nullptr;
|
||||
if (!EnsureDecodersSetup()) {
|
||||
LOG("Unable to re-create decoder, aborting");
|
||||
NotifyError(TrackInfo::kVideoTrack);
|
||||
return;
|
||||
}
|
||||
ScheduleUpdate(TrackInfo::kVideoTrack);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "nsContentUtils.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "MediaDecoder.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "mozilla/TaskQueue.h"
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
class nsIRunnable;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/Function.h"
|
||||
|
||||
#include "MediaResource.h"
|
||||
#include "TimeUnits.h"
|
||||
@@ -15,8 +16,7 @@
|
||||
#include "nsSize.h"
|
||||
#include "VorbisUtils.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/Function.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "nsIRandomGenerator.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsCharSeparatedTokenizer.h"
|
||||
|
||||
@@ -4,32 +4,151 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mp4_demuxer/mp4_demuxer.h"
|
||||
#include "MP4Demuxer.h"
|
||||
#include "MP4Stream.h"
|
||||
#include "MozPromise.h"
|
||||
#include "MediaDataDemuxer.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "TaskQueue.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "MockMediaResource.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mp4_demuxer;
|
||||
|
||||
class AutoTaskQueue;
|
||||
|
||||
#define DO_FAIL []()->void { EXPECT_TRUE(false); }
|
||||
|
||||
class MP4DemuxerBinding
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MP4DemuxerBinding);
|
||||
|
||||
nsRefPtr<MockMediaResource> resource;
|
||||
Monitor mMonitor;
|
||||
nsRefPtr<MP4Demuxer> demuxer;
|
||||
nsRefPtr<MP4Demuxer> mDemuxer;
|
||||
nsRefPtr<TaskQueue> mTaskQueue;
|
||||
nsRefPtr<MediaTrackDemuxer> mAudioTrack;
|
||||
nsRefPtr<MediaTrackDemuxer> mVideoTrack;
|
||||
uint32_t mIndex;
|
||||
nsTArray<nsRefPtr<MediaRawData>> mSamples;
|
||||
nsTArray<int64_t> mKeyFrameTimecodes;
|
||||
MozPromiseHolder<GenericPromise> mCheckTrackKeyFramePromise;
|
||||
MozPromiseHolder<GenericPromise> mCheckTrackSamples;
|
||||
|
||||
explicit MP4DemuxerBinding(const char* aFileName = "dash_dashinit.mp4")
|
||||
: resource(new MockMediaResource(aFileName))
|
||||
, mMonitor("TestMP4Demuxer monitor")
|
||||
, demuxer(new MP4Demuxer(new MP4Stream(resource), &mMonitor))
|
||||
, mDemuxer(new MP4Demuxer(resource))
|
||||
, mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK)))
|
||||
, mIndex(0)
|
||||
{
|
||||
EXPECT_EQ(NS_OK, resource->Open(nullptr));
|
||||
}
|
||||
|
||||
template<typename Function>
|
||||
void RunTestAndWait(const Function& aFunction)
|
||||
{
|
||||
Function func(aFunction);
|
||||
mDemuxer->Init()->Then(mTaskQueue, __func__, Move(func), DO_FAIL);
|
||||
mTaskQueue->AwaitShutdownAndIdle();
|
||||
}
|
||||
|
||||
nsRefPtr<GenericPromise>
|
||||
CheckTrackKeyFrame(MediaTrackDemuxer* aTrackDemuxer)
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||
|
||||
nsRefPtr<MediaTrackDemuxer> track = aTrackDemuxer;
|
||||
nsRefPtr<MP4DemuxerBinding> self = this;
|
||||
|
||||
int64_t time = -1;
|
||||
while (mIndex < mSamples.Length()) {
|
||||
uint32_t i = mIndex++;
|
||||
if (mSamples[i]->mKeyframe) {
|
||||
time = mSamples[i]->mTime;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nsRefPtr<GenericPromise> p = mCheckTrackKeyFramePromise.Ensure(__func__);
|
||||
|
||||
if (time == -1) {
|
||||
mCheckTrackKeyFramePromise.Resolve(true, __func__);
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
DispatchTask(
|
||||
[track, time, self] () {
|
||||
track->Seek(media::TimeUnit::FromMicroseconds(time))->Then(self->mTaskQueue, __func__,
|
||||
[track, time, self] () {
|
||||
track->GetSamples()->Then(self->mTaskQueue, __func__,
|
||||
[track, time, self] (nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
|
||||
EXPECT_EQ(time, aSamples->mSamples[0]->mTime);
|
||||
self->CheckTrackKeyFrame(track);
|
||||
},
|
||||
DO_FAIL
|
||||
);
|
||||
},
|
||||
DO_FAIL
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
nsRefPtr<GenericPromise>
|
||||
CheckTrackSamples(MediaTrackDemuxer* aTrackDemuxer)
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||
|
||||
nsRefPtr<MediaTrackDemuxer> track = aTrackDemuxer;
|
||||
nsRefPtr<MP4DemuxerBinding> self = this;
|
||||
|
||||
nsRefPtr<GenericPromise> p = mCheckTrackSamples.Ensure(__func__);
|
||||
|
||||
DispatchTask(
|
||||
[track, self] () {
|
||||
track->GetSamples()->Then(self->mTaskQueue, __func__,
|
||||
[track, self] (nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
|
||||
if (aSamples->mSamples.Length()) {
|
||||
self->mSamples.AppendElements(aSamples->mSamples);
|
||||
self->CheckTrackSamples(track);
|
||||
}
|
||||
},
|
||||
[self] (DemuxerFailureReason aReason) {
|
||||
if (aReason == DemuxerFailureReason::DEMUXER_ERROR) {
|
||||
EXPECT_TRUE(false);
|
||||
self->mCheckTrackSamples.Reject(NS_ERROR_FAILURE, __func__);
|
||||
} else if (aReason == DemuxerFailureReason::END_OF_STREAM) {
|
||||
EXPECT_TRUE(self->mSamples.Length() > 1);
|
||||
for (uint32_t i = 0; i < (self->mSamples.Length() - 1); i++) {
|
||||
EXPECT_LT(self->mSamples[i]->mTimecode, self->mSamples[i + 1]->mTimecode);
|
||||
if (self->mSamples[i]->mKeyframe) {
|
||||
self->mKeyFrameTimecodes.AppendElement(self->mSamples[i]->mTimecode);
|
||||
}
|
||||
}
|
||||
self->mCheckTrackSamples.Resolve(true, __func__);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename FunctionType>
|
||||
void
|
||||
DispatchTask(FunctionType aFun)
|
||||
{
|
||||
nsRefPtr<nsRunnable> r = NS_NewRunnableFunction(aFun);
|
||||
mTaskQueue->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
virtual ~MP4DemuxerBinding()
|
||||
{
|
||||
}
|
||||
@@ -37,30 +156,20 @@ private:
|
||||
|
||||
TEST(MP4Demuxer, Seek)
|
||||
{
|
||||
nsRefPtr<MP4DemuxerBinding> b = new MP4DemuxerBinding();
|
||||
MonitorAutoLock mon(b->mMonitor);
|
||||
MP4Demuxer* d = b->demuxer;
|
||||
nsRefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding();
|
||||
|
||||
EXPECT_TRUE(d->Init());
|
||||
|
||||
nsTArray<nsRefPtr<MediaRawData>> samples;
|
||||
nsRefPtr<MediaRawData> sample;
|
||||
while (!!(sample = d->DemuxVideoSample())) {
|
||||
samples.AppendElement(sample);
|
||||
if (samples.Length() >= 2) {
|
||||
EXPECT_LT(samples[samples.Length() - 2]->mTimecode,
|
||||
samples[samples.Length() - 1]->mTimecode);
|
||||
}
|
||||
}
|
||||
Microseconds keyFrame = 0;
|
||||
for (size_t i = 0; i < samples.Length(); i++) {
|
||||
if (samples[i]->mKeyframe) {
|
||||
keyFrame = samples[i]->mTimecode;
|
||||
}
|
||||
d->SeekVideo(samples[i]->mTime);
|
||||
sample = d->DemuxVideoSample();
|
||||
EXPECT_EQ(keyFrame, sample->mTimecode);
|
||||
}
|
||||
binding->RunTestAndWait([binding] () {
|
||||
binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
|
||||
binding->CheckTrackSamples(binding->mVideoTrack)
|
||||
->Then(binding->mTaskQueue, __func__,
|
||||
[binding] () {
|
||||
binding->CheckTrackKeyFrame(binding->mVideoTrack)
|
||||
->Then(binding->mTaskQueue, __func__,
|
||||
[binding] () {
|
||||
binding->mTaskQueue->BeginShutdown();
|
||||
}, DO_FAIL);
|
||||
}, DO_FAIL);
|
||||
});
|
||||
}
|
||||
|
||||
static nsCString
|
||||
@@ -87,14 +196,10 @@ ToCryptoString(const CryptoSample& aCrypto)
|
||||
return res;
|
||||
}
|
||||
|
||||
#ifndef XP_WIN // VC2013 doesn't support C++11 array initialization.
|
||||
|
||||
TEST(MP4Demuxer, CENCFrag)
|
||||
{
|
||||
nsRefPtr<MP4DemuxerBinding> b = new MP4DemuxerBinding("gizmo-frag.mp4");
|
||||
MonitorAutoLock mon(b->mMonitor);
|
||||
MP4Demuxer* d = b->demuxer;
|
||||
|
||||
EXPECT_TRUE(d->Init());
|
||||
|
||||
const char* video[] = {
|
||||
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000000 5,684 5,16980",
|
||||
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000450 5,1826",
|
||||
@@ -158,13 +263,22 @@ TEST(MP4Demuxer, CENCFrag)
|
||||
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000019cd 5,2392",
|
||||
};
|
||||
|
||||
nsRefPtr<MediaRawData> sample;
|
||||
size_t i = 0;
|
||||
while (!!(sample = d->DemuxVideoSample())) {
|
||||
nsCString text = ToCryptoString(sample->mCrypto);
|
||||
EXPECT_STREQ(video[i++], text.get());
|
||||
}
|
||||
EXPECT_EQ(ArrayLength(video), i);
|
||||
nsRefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("gizmo-frag.mp4");
|
||||
|
||||
binding->RunTestAndWait([binding, video] () {
|
||||
// grab all video samples.
|
||||
binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
|
||||
binding->CheckTrackSamples(binding->mVideoTrack)
|
||||
->Then(binding->mTaskQueue, __func__,
|
||||
[binding, video] () {
|
||||
for (uint32_t i = 0; i < binding->mSamples.Length(); i++) {
|
||||
nsCString text = ToCryptoString(binding->mSamples[i]->mCrypto);
|
||||
EXPECT_STREQ(video[i++], text.get());
|
||||
}
|
||||
EXPECT_EQ(ArrayLength(video), binding->mSamples.Length());
|
||||
binding->mTaskQueue->BeginShutdown();
|
||||
}, DO_FAIL);
|
||||
});
|
||||
|
||||
const char* audio[] = {
|
||||
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000000 0,281",
|
||||
@@ -262,42 +376,52 @@ TEST(MP4Demuxer, CENCFrag)
|
||||
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008cd 0,433",
|
||||
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008e9 0,481",
|
||||
};
|
||||
nsRefPtr<MP4DemuxerBinding> audiobinding = new MP4DemuxerBinding("gizmo-frag.mp4");
|
||||
|
||||
i = 0;
|
||||
while (!!(sample = d->DemuxAudioSample())) {
|
||||
nsCString text = ToCryptoString(sample->mCrypto);
|
||||
EXPECT_STREQ(audio[i++], text.get());
|
||||
}
|
||||
EXPECT_EQ(ArrayLength(audio), i);
|
||||
audiobinding->RunTestAndWait([audiobinding, audio] () {
|
||||
// grab all audio samples.
|
||||
audiobinding->mAudioTrack = audiobinding->mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
|
||||
audiobinding->CheckTrackSamples(audiobinding->mAudioTrack)
|
||||
->Then(audiobinding->mTaskQueue, __func__,
|
||||
[audiobinding, audio] () {
|
||||
EXPECT_TRUE(audiobinding->mSamples.Length() > 1);
|
||||
for (uint32_t i = 0; i < audiobinding->mSamples.Length(); i++) {
|
||||
nsCString text = ToCryptoString(audiobinding->mSamples[i]->mCrypto);
|
||||
EXPECT_STREQ(audio[i++], text.get());
|
||||
}
|
||||
EXPECT_EQ(ArrayLength(audio), audiobinding->mSamples.Length());
|
||||
audiobinding->mTaskQueue->BeginShutdown();
|
||||
}, DO_FAIL);
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(MP4Demuxer, GetNextKeyframe)
|
||||
{
|
||||
nsRefPtr<MP4DemuxerBinding> b = new MP4DemuxerBinding("gizmo-frag.mp4");
|
||||
MonitorAutoLock mon(b->mMonitor);
|
||||
MP4Demuxer* d = b->demuxer;
|
||||
nsRefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("gizmo-frag.mp4");
|
||||
|
||||
EXPECT_TRUE(d->Init());
|
||||
binding->RunTestAndWait([binding] () {
|
||||
// Insert a [0,end] buffered range, to simulate Moof's being buffered
|
||||
// via MSE.
|
||||
auto len = binding->resource->GetLength();
|
||||
binding->resource->MockAddBufferedRange(0, len);
|
||||
|
||||
// Insert a [0,end] buffered range, to simulate Moof's being buffered
|
||||
// via MSE.
|
||||
auto len = b->resource->GetLength();
|
||||
b->resource->MockAddBufferedRange(0, len);
|
||||
|
||||
// Rebuild the index so that it can be used to find the keyframes.
|
||||
nsTArray<MediaByteRange> ranges;
|
||||
EXPECT_TRUE(NS_SUCCEEDED(b->resource->GetCachedRanges(ranges)));
|
||||
d->UpdateIndex(ranges);
|
||||
|
||||
// gizmp-frag has two keyframes; one at dts=cts=0, and another at
|
||||
// dts=cts=1000000. Verify we get expected results.
|
||||
|
||||
nsRefPtr<MediaRawData> sample;
|
||||
size_t i = 0;
|
||||
const int64_t keyframe = 1000000;
|
||||
while (!!(sample = d->DemuxVideoSample())) {
|
||||
int64_t expected = (sample->mTimecode < keyframe) ? keyframe : -1;
|
||||
EXPECT_EQ(d->GetNextKeyframeTime(), expected);
|
||||
i++;
|
||||
}
|
||||
// gizmp-frag has two keyframes; one at dts=cts=0, and another at
|
||||
// dts=cts=1000000. Verify we get expected results.
|
||||
media::TimeUnit time;
|
||||
binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
|
||||
binding->mVideoTrack->Reset();
|
||||
binding->mVideoTrack->GetNextRandomAccessPoint(&time);
|
||||
EXPECT_EQ(time.ToMicroseconds(), 0);
|
||||
binding->mVideoTrack->GetSamples()->Then(binding->mTaskQueue, __func__,
|
||||
[binding] () {
|
||||
media::TimeUnit time;
|
||||
binding->mVideoTrack->GetNextRandomAccessPoint(&time);
|
||||
EXPECT_EQ(time.ToMicroseconds(), 1000000);
|
||||
binding->mTaskQueue->BeginShutdown();
|
||||
},
|
||||
DO_FAIL
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "MP4Reader.h"
|
||||
#include "MP4Decoder.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "MockMediaResource.h"
|
||||
#include "MockMediaDecoderOwner.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "mozilla/MozPromise.h"
|
||||
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
@@ -11,6 +11,7 @@ SOURCES += [
|
||||
'TestIntervalSet.cpp',
|
||||
'TestMozPromise.cpp',
|
||||
'TestMP3Demuxer.cpp',
|
||||
'TestMP4Demuxer.cpp',
|
||||
'TestMP4Reader.cpp',
|
||||
'TestTrackEncoder.cpp',
|
||||
'TestVideoSegment.cpp',
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "ContainerParser.h"
|
||||
#include "MediaData.h"
|
||||
#include "MediaSourceDecoder.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
#include "SourceBufferDecoder.h"
|
||||
#include "SourceBufferResource.h"
|
||||
|
||||
@@ -133,7 +133,6 @@ EXPORTS += [
|
||||
'RtspMediaResource.h',
|
||||
'SelfRef.h',
|
||||
'SharedBuffer.h',
|
||||
'SharedThreadPool.h',
|
||||
'StreamBuffer.h',
|
||||
'ThreadPoolCOMListener.h',
|
||||
'TimeUnits.h',
|
||||
@@ -146,12 +145,10 @@ EXPORTS += [
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'AbstractThread.h',
|
||||
'MediaManager.h',
|
||||
'MozPromise.h',
|
||||
'StateMirroring.h',
|
||||
'StateWatching.h',
|
||||
'TaskDispatcher.h',
|
||||
'TaskQueue.h',
|
||||
]
|
||||
|
||||
@@ -190,7 +187,6 @@ EXPORTS.mozilla.dom += [
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AbstractThread.cpp',
|
||||
'ADTSDecoder.cpp',
|
||||
'ADTSDemuxer.cpp',
|
||||
'AudioCaptureStream.cpp',
|
||||
@@ -233,7 +229,6 @@ UNIFIED_SOURCES += [
|
||||
'MP3Demuxer.cpp',
|
||||
'MP3FrameParser.cpp',
|
||||
'RtspMediaResource.cpp',
|
||||
'SharedThreadPool.cpp',
|
||||
'StreamBuffer.cpp',
|
||||
'TaskQueue.cpp',
|
||||
'TextTrack.cpp',
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
#include "nsMimeTypes.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "VideoFrameContainer.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "mozilla/TaskQueue.h"
|
||||
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
|
||||
#include "MediaInfo.h"
|
||||
#include "H264Converter.h"
|
||||
|
||||
@@ -55,7 +55,7 @@ SharedDecoderManager::SharedDecoderManager()
|
||||
, mActiveProxy(nullptr)
|
||||
, mActiveCallback(nullptr)
|
||||
, mWaitForInternalDrain(false)
|
||||
, mMonitor("SharedDecoderProxy")
|
||||
, mMonitor("SharedDecoderManager")
|
||||
, mDecoderReleasedResources(false)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread()); // taskqueue must be created on main thread.
|
||||
|
||||
@@ -245,6 +245,8 @@ public:
|
||||
|
||||
virtual HRESULT ConfigureForSize(uint32_t aWidth, uint32_t aHeight) override;
|
||||
|
||||
virtual bool IsD3D11() override { return true; }
|
||||
|
||||
private:
|
||||
HRESULT CreateFormatConverter();
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
|
||||
virtual HRESULT ConfigureForSize(uint32_t aWidth, uint32_t aHeight) { return S_OK; }
|
||||
|
||||
virtual bool IsD3D11() { return false; }
|
||||
|
||||
virtual ~DXVA2Manager();
|
||||
|
||||
protected:
|
||||
|
||||
@@ -23,7 +23,6 @@ WMFMediaDataDecoder::WMFMediaDataDecoder(MFTManager* aMFTManager,
|
||||
, mCallback(aCallback)
|
||||
, mMFTManager(aMFTManager)
|
||||
, mMonitor("WMFMediaDataDecoder")
|
||||
, mIsDecodeTaskDispatched(false)
|
||||
, mIsFlushing(false)
|
||||
, mIsShutDown(false)
|
||||
{
|
||||
@@ -71,18 +70,6 @@ WMFMediaDataDecoder::ProcessShutdown()
|
||||
mDecoder = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
WMFMediaDataDecoder::EnsureDecodeTaskDispatched()
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
if (!mIsDecodeTaskDispatched) {
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, &WMFMediaDataDecoder::Decode);
|
||||
mTaskQueue->Dispatch(runnable.forget());
|
||||
mIsDecodeTaskDispatched = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts data into the decoder's pipeline.
|
||||
nsresult
|
||||
WMFMediaDataDecoder::Input(MediaRawData* aSample)
|
||||
@@ -90,50 +77,36 @@ WMFMediaDataDecoder::Input(MediaRawData* aSample)
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
|
||||
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mInput.push(aSample);
|
||||
EnsureDecodeTaskDispatched();
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethodWithArg<nsRefPtr<MediaRawData>>(
|
||||
this,
|
||||
&WMFMediaDataDecoder::ProcessDecode,
|
||||
nsRefPtr<MediaRawData>(aSample));
|
||||
mTaskQueue->Dispatch(runnable.forget());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
WMFMediaDataDecoder::Decode()
|
||||
WMFMediaDataDecoder::ProcessDecode(MediaRawData* aSample)
|
||||
{
|
||||
while (true) {
|
||||
nsRefPtr<MediaRawData> input;
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MOZ_ASSERT(mIsDecodeTaskDispatched);
|
||||
if (mInput.empty()) {
|
||||
if (mIsFlushing) {
|
||||
if (mDecoder) {
|
||||
mDecoder->Flush();
|
||||
}
|
||||
mIsFlushing = false;
|
||||
}
|
||||
mIsDecodeTaskDispatched = false;
|
||||
mon.NotifyAll();
|
||||
return;
|
||||
}
|
||||
input = mInput.front();
|
||||
mInput.pop();
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (mIsFlushing) {
|
||||
// Skip sample, to be released by runnable.
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT hr = mMFTManager->Input(input);
|
||||
if (FAILED(hr)) {
|
||||
NS_WARNING("MFTManager rejected sample");
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
PurgeInputQueue();
|
||||
}
|
||||
mCallback->Error();
|
||||
continue; // complete flush if flushing
|
||||
}
|
||||
|
||||
mLastStreamOffset = input->mOffset;
|
||||
|
||||
ProcessOutput();
|
||||
}
|
||||
|
||||
HRESULT hr = mMFTManager->Input(aSample);
|
||||
if (FAILED(hr)) {
|
||||
NS_WARNING("MFTManager rejected sample");
|
||||
mCallback->Error();
|
||||
return;
|
||||
}
|
||||
|
||||
mLastStreamOffset = aSample->mOffset;
|
||||
|
||||
ProcessOutput();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -151,21 +124,19 @@ WMFMediaDataDecoder::ProcessOutput()
|
||||
}
|
||||
} else if (FAILED(hr)) {
|
||||
NS_WARNING("WMFMediaDataDecoder failed to output data");
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
PurgeInputQueue();
|
||||
}
|
||||
mCallback->Error();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WMFMediaDataDecoder::PurgeInputQueue()
|
||||
WMFMediaDataDecoder::ProcessFlush()
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
while (!mInput.empty()) {
|
||||
mInput.pop();
|
||||
if (mDecoder) {
|
||||
mDecoder->Flush();
|
||||
}
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mIsFlushing = false;
|
||||
mon.NotifyAll();
|
||||
}
|
||||
|
||||
nsresult
|
||||
@@ -174,11 +145,12 @@ WMFMediaDataDecoder::Flush()
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, &WMFMediaDataDecoder::ProcessFlush);
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
PurgeInputQueue();
|
||||
mIsFlushing = true;
|
||||
EnsureDecodeTaskDispatched();
|
||||
while (mIsDecodeTaskDispatched || mIsFlushing) {
|
||||
mTaskQueue->Dispatch(runnable.forget());
|
||||
while (mIsFlushing) {
|
||||
mon.Wait();
|
||||
}
|
||||
return NS_OK;
|
||||
@@ -187,7 +159,12 @@ WMFMediaDataDecoder::Flush()
|
||||
void
|
||||
WMFMediaDataDecoder::ProcessDrain()
|
||||
{
|
||||
if (mDecoder) {
|
||||
bool isFlushing;
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
isFlushing = mIsFlushing;
|
||||
}
|
||||
if (!isFlushing && mDecoder) {
|
||||
// Order the decoder to drain...
|
||||
if (FAILED(mDecoder->SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0))) {
|
||||
NS_WARNING("Failed to send DRAIN command to MFT");
|
||||
|
||||
@@ -75,14 +75,16 @@ private:
|
||||
|
||||
// Called on the task queue. Inserts the sample into the decoder, and
|
||||
// extracts output if available.
|
||||
void Decode();
|
||||
void EnsureDecodeTaskDispatched();
|
||||
void PurgeInputQueue();
|
||||
void ProcessDecode(MediaRawData* aSample);
|
||||
|
||||
// Called on the task queue. Extracts output if available, and delivers
|
||||
// it to the reader. Called after ProcessDecode() and ProcessDrain().
|
||||
void ProcessOutput();
|
||||
|
||||
// Called on the task queue. Orders the MFT to flush. There is no output to
|
||||
// extract.
|
||||
void ProcessFlush();
|
||||
|
||||
// Called on the task queue. Orders the MFT to drain, and then extracts
|
||||
// all available output.
|
||||
void ProcessDrain();
|
||||
@@ -99,10 +101,13 @@ private:
|
||||
// This is used to approximate the decoder's position in the media resource.
|
||||
int64_t mLastStreamOffset;
|
||||
|
||||
// For access to and waiting on mIsFlushing
|
||||
Monitor mMonitor;
|
||||
std::queue<nsRefPtr<MediaRawData>> mInput;
|
||||
bool mIsDecodeTaskDispatched;
|
||||
// Set on reader/decode thread calling Flush() to indicate that output is
|
||||
// not required and so input samples on mTaskQueue need not be processed.
|
||||
// Cleared on mTaskQueue.
|
||||
bool mIsFlushing;
|
||||
|
||||
bool mIsShutDown;
|
||||
};
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ public:
|
||||
};
|
||||
|
||||
bool
|
||||
WMFVideoMFTManager::InitializeDXVA()
|
||||
WMFVideoMFTManager::InitializeDXVA(bool aForceD3D9)
|
||||
{
|
||||
MOZ_ASSERT(!mDXVA2Manager);
|
||||
|
||||
@@ -159,7 +159,8 @@ WMFVideoMFTManager::InitializeDXVA()
|
||||
}
|
||||
|
||||
// The DXVA manager must be created on the main thread.
|
||||
nsRefPtr<CreateDXVAManagerEvent> event(new CreateDXVAManagerEvent(mLayersBackend));
|
||||
nsRefPtr<CreateDXVAManagerEvent> event =
|
||||
new CreateDXVAManagerEvent(aForceD3D9 ? LayersBackend::LAYERS_D3D9 : mLayersBackend);
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
event->Run();
|
||||
@@ -173,9 +174,24 @@ WMFVideoMFTManager::InitializeDXVA()
|
||||
|
||||
already_AddRefed<MFTDecoder>
|
||||
WMFVideoMFTManager::Init()
|
||||
{
|
||||
RefPtr<MFTDecoder> decoder = InitInternal(/* aForceD3D9 = */ false);
|
||||
|
||||
// If initialization failed with d3d11 DXVA then try falling back
|
||||
// to d3d9.
|
||||
if (!decoder && mDXVA2Manager && mDXVA2Manager->IsD3D11()) {
|
||||
mDXVA2Manager = nullptr;
|
||||
decoder = InitInternal(true);
|
||||
}
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<MFTDecoder>
|
||||
WMFVideoMFTManager::InitInternal(bool aForceD3D9)
|
||||
{
|
||||
mUseHwAccel = false; // default value; changed if D3D setup succeeds.
|
||||
bool useDxva = InitializeDXVA();
|
||||
bool useDxva = InitializeDXVA(aForceD3D9);
|
||||
|
||||
RefPtr<MFTDecoder> decoder(new MFTDecoder());
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
bool InitializeDXVA();
|
||||
bool InitializeDXVA(bool aForceD3D9);
|
||||
|
||||
already_AddRefed<MFTDecoder> InitInternal(bool aForceD3D9);
|
||||
|
||||
HRESULT ConfigureVideoFrameGeometry();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "MediaResource.h"
|
||||
#include "mozilla/dom/HTMLMediaElement.h"
|
||||
#include "nsError.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "VorbisUtils.h"
|
||||
#include "nestegg/nestegg.h"
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "WebMBufferedParser.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "MediaDataDemuxer.h"
|
||||
#include "nsAutoRef.h"
|
||||
#include "NesteggPacketHolder.h"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "gfx2DGlue.h"
|
||||
#include "Layers.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -280,7 +280,8 @@ nsresult WebMReader::ReadMetadata(MediaInfo* aInfo,
|
||||
MetadataTags** aTags)
|
||||
{
|
||||
// We can't use OnTaskQueue() here because of the wacky initialization task
|
||||
// queue that TrackBuffer uses.
|
||||
// queue that TrackBuffer uses. We should be able to fix this when we do
|
||||
// bug 1148234.
|
||||
MOZ_ASSERT(mDecoder->OnDecodeTaskQueue());
|
||||
|
||||
nestegg_io io;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 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 "SharedThreadPool.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
@@ -28,14 +28,18 @@ EXPORTS += [
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'AbstractThread.h',
|
||||
'BackgroundHangMonitor.h',
|
||||
'HangAnnotations.h',
|
||||
'HangMonitor.h',
|
||||
'LazyIdleThread.h',
|
||||
'SharedThreadPool.h',
|
||||
'SyncRunnable.h',
|
||||
'TaskDispatcher.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AbstractThread.cpp',
|
||||
'BackgroundHangMonitor.cpp',
|
||||
'HangAnnotations.cpp',
|
||||
'HangMonitor.cpp',
|
||||
@@ -48,6 +52,7 @@ UNIFIED_SOURCES += [
|
||||
'nsThreadManager.cpp',
|
||||
'nsThreadPool.cpp',
|
||||
'nsTimerImpl.cpp',
|
||||
'SharedThreadPool.cpp',
|
||||
'ThreadStackHelper.cpp',
|
||||
'TimerThread.cpp',
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user