nsresult AudioSink::InitializeAudioStream() { // AudioStream initialization can block for extended periods in unusual // circumstances, so we take care to drop the decoder monitor while // initializing. RefPtr<AudioStream> audioStream(new AudioStream()); audioStream->Init(mInfo.mChannels, mInfo.mRate, mChannel, AudioStream::HighLatency); // TODO: Check Init's return value and bail on error. Unfortunately this // causes some tests to fail due to playback failing. ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mAudioStream = audioStream; UpdateStreamSettings(); return NS_OK; }
void MediaOmxCommonDecoder::PauseStateMachine() { MOZ_ASSERT(NS_IsMainThread()); GetReentrantMonitor().AssertCurrentThreadIn(); DECODER_LOG(PR_LOG_DEBUG, ("%s", __PRETTY_FUNCTION__)); if (!mDecoderStateMachine) { return; } // enter dormant state RefPtr<nsRunnable> event = NS_NewRunnableMethodWithArg<bool>( mDecoderStateMachine, &MediaDecoderStateMachine::SetDormant, true); mDecoderStateMachine->TaskQueue()->Dispatch(event); }
bool AudioSink::IsPlaybackContinuing() { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mPlaying && mAudioStream->IsPaused()) { mAudioStream->Resume(); } // 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 DASHRepDecoder::NotifyDownloadEnded(nsresult aStatus) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (!mMainDecoder) { LOG("Error! Main Decoder is reported as null: mMainDecoder [%p]", mMainDecoder.get()); DecodeError(); return; } if (NS_SUCCEEDED(aStatus)) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); // Decrement counter as metadata chunks are downloaded. // Note: Reader gets next chunk download via |ChannelMediaResource|:|Seek|. if (mMetadataChunkCount > 0) { LOG("Metadata chunk [%d] downloaded: range requested [%lld - %lld] " "subsegmentIdx [%d]", mMetadataChunkCount, mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx); mMetadataChunkCount--; } else { LOG("Byte range downloaded: status [%x] range requested [%lld - %lld] " "subsegmentIdx [%d]", aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx); if ((uint32_t)mSubsegmentIdx == mByteRanges.Length()-1) { mResource->NotifyLastByteRange(); } // Notify main decoder that a DATA byte range is downloaded. mMainDecoder->NotifyDownloadEnded(this, aStatus, mSubsegmentIdx); } } else if (aStatus == NS_BINDING_ABORTED) { LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus); if (mMainDecoder) { mMainDecoder->LoadAborted(); } return; } else if (aStatus != NS_BASE_STREAM_CLOSED) { LOG("Network error trying to download MPD: aStatus [%x].", aStatus); NetworkError(); } }
void MediaOmxDecoder::ResumeStateMachine() { MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); DECODER_LOG(PR_LOG_DEBUG, ("%s current time %f", __PRETTY_FUNCTION__, mCurrentTime)); if (!mDecoderStateMachine) { return; } mFallbackToStateMachine = true; mAudioOffloadPlayer = nullptr; mRequestedSeekTarget = SeekTarget(mCurrentTime, SeekTarget::Accurate); mNextState = mPlayState; ChangeState(PLAY_STATE_LOADING); mDecoderStateMachine->SetDormant(false); }
nsresult DecodedAudioDataSink::InitializeAudioStream() { // AudioStream initialization can block for extended periods in unusual // circumstances, so we take care to drop the decoder monitor while // initializing. RefPtr<AudioStream> audioStream(new AudioStream()); nsresult rv = audioStream->Init(mInfo.mChannels, mInfo.mRate, mChannel, AudioStream::HighLatency); if (NS_FAILED(rv)) { audioStream->Shutdown(); return rv; } ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mAudioStream = audioStream; return NS_OK; }
void MediaOmxDecoder::PlaybackPositionChanged() { MOZ_ASSERT(NS_IsMainThread()); if (!mAudioOffloadPlayer) { MediaDecoder::PlaybackPositionChanged(); return; } if (!mOwner || mShuttingDown) { return; } double lastTime = mCurrentTime; { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mCurrentTime = mAudioOffloadPlayer->GetMediaTimeSecs(); } if (mOwner && lastTime != mCurrentTime) { FireTimeUpdate(); } }
void MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction) { MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); double oldDuration = ExplicitDuration(); if (aDuration >= 0) { int64_t checkedDuration; if (NS_FAILED(SecondsToUsecs(aDuration, checkedDuration))) { // INT64_MAX is used as infinity by the state machine. // We want a very bigger number, but not infinity. checkedDuration = INT64_MAX - 1; } SetExplicitDuration(aDuration); } else { SetExplicitDuration(PositiveInfinity<double>()); } if (mMediaSource && aAction != MSRangeRemovalAction::SKIP) { mMediaSource->DurationChange(oldDuration, aDuration); } }
void MediaOmxCommonDecoder::ResumeStateMachine() { MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); DECODER_LOG(PR_LOG_DEBUG, ("%s current time %f", __PRETTY_FUNCTION__, mLogicalPosition)); if (mShuttingDown) { return; } if (!GetStateMachine()) { return; } mFallbackToStateMachine = true; mAudioOffloadPlayer = nullptr; SeekTarget target = SeekTarget(mLogicalPosition, SeekTarget::Accurate, MediaDecoderEventVisibility::Suppressed); // Call Seek of MediaDecoderStateMachine to suppress seek events. RefPtr<nsRunnable> event = NS_NewRunnableMethodWithArg<SeekTarget>( GetStateMachine(), &MediaDecoderStateMachine::Seek, target); GetStateMachine()->TaskQueue()->Dispatch(event.forget()); mNextState = mPlayState; ChangeState(PLAY_STATE_LOADING); // exit dormant state event = NS_NewRunnableMethodWithArg<bool>( GetStateMachine(), &MediaDecoderStateMachine::SetDormant, false); GetStateMachine()->TaskQueue()->Dispatch(event.forget()); UpdateLogicalPosition(); }
void MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); double oldDuration = mMediaSourceDuration; if (aDuration >= 0) { int64_t checkedDuration; if (NS_FAILED(SecondsToUsecs(aDuration, checkedDuration))) { // INT64_MAX is used as infinity by the state machine. // We want a very bigger number, but not infinity. checkedDuration = INT64_MAX - 1; } GetStateMachine()->SetDuration(checkedDuration); mMediaSourceDuration = aDuration; } else { GetStateMachine()->SetDuration(INT64_MAX); mMediaSourceDuration = PositiveInfinity<double>(); } if (mReader) { mReader->SetMediaSourceDuration(mMediaSourceDuration); } ScheduleDurationChange(oldDuration, aDuration, aAction); }
nsresult DASHRepDecoder::GetByteRangeForSeek(int64_t const aOffset, MediaByteRange& aByteRange) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); // Only check data ranges if they're available and if this decoder is active, // i.e. inactive rep decoders should only load metadata. ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); for (uint32_t i = 0; i < mByteRanges.Length(); i++) { NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED); // Check if |aOffset| lies within the current data range. if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) { if (mMainDecoder->IsDecoderAllowedToDownloadSubsegment(this, i)) { mCurrentByteRange = aByteRange = mByteRanges[i]; mSubsegmentIdx = i; // XXX Hack: should be setting subsegment outside this function, but // need to review seeking for multiple switches anyhow. mMainDecoder->SetSubsegmentIndex(this, i); LOG("Getting DATA range [%d] for seek offset [%lld]: " "bytes [%lld] to [%lld]", i, aOffset, aByteRange.mStart, aByteRange.mEnd); return NS_OK; } break; } } // Don't allow metadata downloads once they're loaded and byte ranges have // been populated. bool canDownloadMetadata = mByteRanges.IsEmpty(); if (canDownloadMetadata) { // Check metadata ranges; init range. if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) { mCurrentByteRange = aByteRange = mInitByteRange; mSubsegmentIdx = 0; LOG("Getting INIT range for seek offset [%lld]: bytes [%lld] to " "[%lld]", aOffset, aByteRange.mStart, aByteRange.mEnd); return NS_OK; } // ... index range. if (mIndexByteRange.mStart <= aOffset && aOffset <= mIndexByteRange.mEnd) { mCurrentByteRange = aByteRange = mIndexByteRange; mSubsegmentIdx = 0; LOG("Getting INDEXES range for seek offset [%lld]: bytes [%lld] to " "[%lld]", aOffset, aByteRange.mStart, aByteRange.mEnd); return NS_OK; } } else { LOG1("Metadata should be read; inhibiting further metadata downloads."); } // If no byte range is found by this stage, clear the parameter and return. aByteRange.Clear(); if (mByteRanges.IsEmpty() || !canDownloadMetadata) { // Assume mByteRanges will be populated after metadata is read. LOG("Data ranges not populated [%s]; metadata download restricted [%s]: " "offset[%lld].", (mByteRanges.IsEmpty() ? "yes" : "no"), (canDownloadMetadata ? "no" : "yes"), aOffset); return NS_ERROR_NOT_AVAILABLE; } else { // Cannot seek to an unknown offset. // XXX Revisit this for dynamic MPD profiles if MPD is regularly updated. LOG("Error! Offset [%lld] is in an unknown range!", aOffset); return NS_ERROR_ILLEGAL_VALUE; } }
DecodedStreamData* DecodedStream::GetData() const { GetReentrantMonitor().AssertCurrentThreadIn(); return mData.get(); }
void DASHRepDecoder::LoadNextByteRange() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); NS_ASSERTION(mResource, "Error: resource is reported as null!"); // Return silently if shutting down. if (mShuttingDown) { LOG1("Shutting down! Ignoring LoadNextByteRange()."); return; } ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); NS_ASSERTION(mMainDecoder, "Error: main decoder is null!"); NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this), "Should not be called on non-active decoders!"); // Cannot have empty byte ranges. if (mByteRanges.IsEmpty()) { LOG1("Error getting list of subsegment byte ranges."); DecodeError(); return; } // Get byte range for subsegment. int32_t subsegmentIdx = mMainDecoder->GetSubsegmentIndex(this); NS_ASSERTION(0 <= subsegmentIdx, "Subsegment index should be >= 0 for active decoders"); if (subsegmentIdx >= 0 && (uint32_t)subsegmentIdx < mByteRanges.Length()) { mCurrentByteRange = mByteRanges[subsegmentIdx]; mSubsegmentIdx = subsegmentIdx; } else { mCurrentByteRange.Clear(); mSubsegmentIdx = -1; LOG("End of subsegments: index [%d] out of range.", subsegmentIdx); return; } // Request a seek for the first reader. Required so that the reader is // primed to start here, and will block subsequent subsegment seeks unless // the subsegment has been read. if (subsegmentIdx == 0) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mReader->RequestSeekToSubsegment(0); } // Query resource for cached ranges; only download if it's not there. if (IsSubsegmentCached(mSubsegmentIdx)) { LOG("Subsegment [%d] bytes [%lld] to [%lld] already cached. No need to " "download.", mSubsegmentIdx, mCurrentByteRange.mStart, mCurrentByteRange.mEnd); nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &DASHRepDecoder::DoNotifyDownloadEnded); nsresult rv = NS_DispatchToMainThread(event); if (NS_FAILED(rv)) { LOG("Error notifying subsegment [%d] cached: rv[0x%x].", mSubsegmentIdx, rv); NetworkError(); } return; } // Open byte range corresponding to subsegment. nsresult rv = mResource->OpenByteRange(nullptr, mCurrentByteRange); if (NS_FAILED(rv)) { LOG("Error opening byte range [%lld - %lld]: subsegmentIdx [%d] rv [%x].", mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx, rv); NetworkError(); return; } }
nsTArray<OutputStreamData>& DecodedStream::OutputStreams() { GetReentrantMonitor().AssertCurrentThreadIn(); return mOutputStreams; }
void MediaOmxCommonDecoder::SetPlatformCanOffloadAudio(bool aCanOffloadAudio) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mCanOffloadAudio = aCanOffloadAudio; }
double MediaSourceDecoder::GetDuration() { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); return ExplicitDuration(); }
void MediaSourceDecoder::NotifyTimeRangesChanged() { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mon.NotifyAll(); }
bool MediaSourceDecoder::IsExpectingMoreData() { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); return !mReader->IsEnded(); }
double MediaSourceDecoder::GetMediaSourceDuration() { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); return mMediaSourceDuration; }
void AudioSink::AudioLoop() { AssertOnAudioThread(); SINK_LOG("AudioLoop started"); if (NS_FAILED(InitializeAudioStream())) { NS_WARNING("Initializing AudioStream failed."); mStateMachine->DispatchOnAudioSinkError(); 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(); } int64_t endTime = GetEndTime(); if (endTime != -1) { mOnAudioEndTimeUpdateTask->Dispatch(endTime); } } ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); MOZ_ASSERT(mStopAudioThread || AudioQueue().AtEndOfStream()); if (!mStopAudioThread && mPlaying) { Drain(); } SINK_LOG("AudioLoop complete"); Cleanup(); SINK_LOG("AudioLoop exit"); }