michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* michael@0: * Copyright (c) 2014 The Linux Foundation. All rights reserved. michael@0: * Copyright (C) 2009 The Android Open Source Project michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include "AudioOffloadPlayer.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsITimer.h" michael@0: #include "mozilla/dom/HTMLMediaElement.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: using namespace android; michael@0: michael@0: namespace mozilla { michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gAudioOffloadPlayerLog; michael@0: #define AUDIO_OFFLOAD_LOG(type, msg) \ michael@0: PR_LOG(gAudioOffloadPlayerLog, type, msg) michael@0: #else michael@0: #define AUDIO_OFFLOAD_LOG(type, msg) michael@0: #endif michael@0: michael@0: // maximum time in paused state when offloading audio decompression. michael@0: // When elapsed, the AudioSink is destroyed to allow the audio DSP to power down. michael@0: static const uint64_t OFFLOAD_PAUSE_MAX_MSECS = 60000ll; michael@0: michael@0: AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxDecoder* aObserver) : michael@0: mObserver(aObserver), michael@0: mInputBuffer(nullptr), michael@0: mSampleRate(0), michael@0: mSeeking(false), michael@0: mSeekDuringPause(false), michael@0: mReachedEOS(false), michael@0: mSeekTimeUs(0), michael@0: mStartPosUs(0), michael@0: mPositionTimeMediaUs(-1), michael@0: mStarted(false), michael@0: mPlaying(false), michael@0: mIsElementVisible(true) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (!gAudioOffloadPlayerLog) { michael@0: gAudioOffloadPlayerLog = PR_NewLogModule("AudioOffloadPlayer"); michael@0: } michael@0: #endif michael@0: michael@0: CHECK(aObserver); michael@0: mSessionId = AudioSystem::newAudioSessionId(); michael@0: AudioSystem::acquireAudioSessionId(mSessionId); michael@0: mAudioSink = new AudioOutput(mSessionId, michael@0: IPCThreadState::self()->getCallingUid()); michael@0: } michael@0: michael@0: AudioOffloadPlayer::~AudioOffloadPlayer() michael@0: { michael@0: Reset(); michael@0: AudioSystem::releaseAudioSessionId(mSessionId); michael@0: } michael@0: michael@0: void AudioOffloadPlayer::SetSource(const sp &aSource) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: CHECK(!mSource.get()); michael@0: michael@0: mSource = aSource; michael@0: } michael@0: michael@0: status_t AudioOffloadPlayer::Start(bool aSourceAlreadyStarted) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: CHECK(!mStarted); michael@0: CHECK(mSource.get()); michael@0: michael@0: status_t err; michael@0: CHECK(mAudioSink.get()); michael@0: michael@0: if (!aSourceAlreadyStarted) { michael@0: err = mSource->start(); michael@0: michael@0: if (err != OK) { michael@0: return err; michael@0: } michael@0: } michael@0: michael@0: sp format = mSource->getFormat(); michael@0: const char* mime; michael@0: int avgBitRate = -1; michael@0: int32_t channelMask; michael@0: int32_t numChannels; michael@0: int64_t durationUs = -1; michael@0: audio_format_t audioFormat = AUDIO_FORMAT_PCM_16_BIT; michael@0: uint32_t flags = AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD; michael@0: audio_offload_info_t offloadInfo = AUDIO_INFO_INITIALIZER; michael@0: michael@0: CHECK(format->findCString(kKeyMIMEType, &mime)); michael@0: CHECK(format->findInt32(kKeySampleRate, &mSampleRate)); michael@0: CHECK(format->findInt32(kKeyChannelCount, &numChannels)); michael@0: format->findInt32(kKeyBitRate, &avgBitRate); michael@0: format->findInt64(kKeyDuration, &durationUs); michael@0: michael@0: if(!format->findInt32(kKeyChannelMask, &channelMask)) { michael@0: channelMask = CHANNEL_MASK_USE_CHANNEL_ORDER; michael@0: } michael@0: michael@0: if (mapMimeToAudioFormat(audioFormat, mime) != OK) { michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Couldn't map mime type \"%s\" to a valid " michael@0: "AudioSystem::audio_format", mime)); michael@0: audioFormat = AUDIO_FORMAT_INVALID; michael@0: } michael@0: michael@0: offloadInfo.duration_us = durationUs; michael@0: offloadInfo.sample_rate = mSampleRate; michael@0: offloadInfo.channel_mask = channelMask; michael@0: offloadInfo.format = audioFormat; michael@0: offloadInfo.stream_type = AUDIO_STREAM_MUSIC; michael@0: offloadInfo.bit_rate = avgBitRate; michael@0: offloadInfo.has_video = false; michael@0: offloadInfo.is_streaming = false; michael@0: michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("isOffloadSupported: SR=%u, CM=0x%x, " michael@0: "Format=0x%x, StreamType=%d, BitRate=%u, duration=%lld us, has_video=%d", michael@0: offloadInfo.sample_rate, offloadInfo.channel_mask, offloadInfo.format, michael@0: offloadInfo.stream_type, offloadInfo.bit_rate, offloadInfo.duration_us, michael@0: offloadInfo.has_video)); michael@0: michael@0: err = mAudioSink->Open(mSampleRate, michael@0: numChannels, michael@0: channelMask, michael@0: audioFormat, michael@0: &AudioOffloadPlayer::AudioSinkCallback, michael@0: this, michael@0: (audio_output_flags_t) flags, michael@0: &offloadInfo); michael@0: if (err == OK) { michael@0: // If the playback is offloaded to h/w we pass the michael@0: // HAL some metadata information michael@0: // We don't want to do this for PCM because it will be going michael@0: // through the AudioFlinger mixer before reaching the hardware michael@0: SendMetaDataToHal(mAudioSink, format); michael@0: } michael@0: mStarted = true; michael@0: mPlaying = false; michael@0: michael@0: return err; michael@0: } michael@0: michael@0: status_t AudioOffloadPlayer::ChangeState(MediaDecoder::PlayState aState) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mPlayState = aState; michael@0: michael@0: switch (mPlayState) { michael@0: case MediaDecoder::PLAY_STATE_PLAYING: { michael@0: status_t err = Play(); michael@0: if (err != OK) { michael@0: return err; michael@0: } michael@0: StartTimeUpdate(); michael@0: } break; michael@0: michael@0: case MediaDecoder::PLAY_STATE_SEEKING: { michael@0: int64_t seekTimeUs michael@0: = mObserver->GetSeekTime(); michael@0: SeekTo(seekTimeUs, true); michael@0: mObserver->ResetSeekTime(); michael@0: } break; michael@0: michael@0: case MediaDecoder::PLAY_STATE_PAUSED: michael@0: case MediaDecoder::PLAY_STATE_SHUTDOWN: michael@0: // Just pause here during play state shutdown as well to stop playing michael@0: // offload track immediately. Resources will be freed by MediaOmxDecoder michael@0: Pause(); michael@0: break; michael@0: michael@0: case MediaDecoder::PLAY_STATE_ENDED: michael@0: Pause(true); michael@0: break; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: return OK; michael@0: } michael@0: michael@0: static void ResetCallback(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: AudioOffloadPlayer* player = static_cast(aClosure); michael@0: if (player) { michael@0: player->Reset(); michael@0: } michael@0: } michael@0: michael@0: void AudioOffloadPlayer::Pause(bool aPlayPendingSamples) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mStarted) { michael@0: CHECK(mAudioSink.get()); michael@0: if (aPlayPendingSamples) { michael@0: mAudioSink->Stop(); michael@0: } else { michael@0: mAudioSink->Pause(); michael@0: } michael@0: mPlaying = false; michael@0: } michael@0: michael@0: if (mResetTimer) { michael@0: return; michael@0: } michael@0: mResetTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mResetTimer->InitWithFuncCallback(ResetCallback, michael@0: this, michael@0: OFFLOAD_PAUSE_MAX_MSECS, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: status_t AudioOffloadPlayer::Play() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mResetTimer) { michael@0: mResetTimer->Cancel(); michael@0: mResetTimer = nullptr; michael@0: } michael@0: michael@0: status_t err = OK; michael@0: michael@0: if (!mStarted) { michael@0: // Last pause timed out and offloaded audio sink was reset. Start it again michael@0: err = Start(false); michael@0: if (err != OK) { michael@0: return err; michael@0: } michael@0: // Seek to last play position only when there was no seek during last pause michael@0: if (!mSeeking) { michael@0: SeekTo(mPositionTimeMediaUs); michael@0: } michael@0: } michael@0: michael@0: if (!mPlaying) { michael@0: CHECK(mAudioSink.get()); michael@0: err = mAudioSink->Start(); michael@0: if (err == OK) { michael@0: mPlaying = true; michael@0: } michael@0: } michael@0: michael@0: return err; michael@0: } michael@0: michael@0: void AudioOffloadPlayer::Reset() michael@0: { michael@0: if (!mStarted) { michael@0: return; michael@0: } michael@0: michael@0: CHECK(mAudioSink.get()); michael@0: michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("reset: mPlaying=%d mReachedEOS=%d", michael@0: mPlaying, mReachedEOS)); michael@0: michael@0: mAudioSink->Stop(); michael@0: // If we're closing and have reached EOS, we don't want to flush michael@0: // the track because if it is offloaded there could be a small michael@0: // amount of residual data in the hardware buffer which we must michael@0: // play to give gapless playback. michael@0: // But if we're resetting when paused or before we've reached EOS michael@0: // we can't be doing a gapless playback and there could be a large michael@0: // amount of data queued in the hardware if the track is offloaded, michael@0: // so we must flush to prevent a track switch being delayed playing michael@0: // the buffered data that we don't want now michael@0: if (!mPlaying || !mReachedEOS) { michael@0: mAudioSink->Flush(); michael@0: } michael@0: michael@0: mAudioSink->Close(); michael@0: // Make sure to release any buffer we hold onto so that the michael@0: // source is able to stop(). michael@0: michael@0: if (mInputBuffer) { michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Releasing input buffer")); michael@0: michael@0: mInputBuffer->release(); michael@0: mInputBuffer = nullptr; michael@0: } michael@0: mSource->stop(); michael@0: michael@0: IPCThreadState::self()->flushCommands(); michael@0: StopTimeUpdate(); michael@0: michael@0: mReachedEOS = false; michael@0: mStarted = false; michael@0: mPlaying = false; michael@0: mStartPosUs = 0; michael@0: } michael@0: michael@0: status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: CHECK(mAudioSink.get()); michael@0: michael@0: android::Mutex::Autolock autoLock(mLock); michael@0: michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("SeekTo ( %lld )", aTimeUs)); michael@0: michael@0: mSeeking = true; michael@0: mReachedEOS = false; michael@0: mPositionTimeMediaUs = -1; michael@0: mSeekTimeUs = aTimeUs; michael@0: mStartPosUs = aTimeUs; michael@0: mDispatchSeekEvents = aDispatchSeekEvents; michael@0: michael@0: if (mDispatchSeekEvents) { michael@0: nsCOMPtr nsEvent = NS_NewRunnableMethod(mObserver, michael@0: &MediaDecoder::SeekingStarted); michael@0: NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: if (mPlaying) { michael@0: mAudioSink->Pause(); michael@0: mAudioSink->Flush(); michael@0: mAudioSink->Start(); michael@0: michael@0: } else { michael@0: mSeekDuringPause = true; michael@0: michael@0: if (mStarted) { michael@0: mAudioSink->Flush(); michael@0: } michael@0: michael@0: if (mDispatchSeekEvents) { michael@0: mDispatchSeekEvents = false; michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Fake seek complete during pause")); michael@0: nsCOMPtr nsEvent = NS_NewRunnableMethod(mObserver, michael@0: &MediaDecoder::SeekingStopped); michael@0: NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: double AudioOffloadPlayer::GetMediaTimeSecs() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return (static_cast(GetMediaTimeUs()) / michael@0: static_cast(USECS_PER_S)); michael@0: } michael@0: michael@0: int64_t AudioOffloadPlayer::GetMediaTimeUs() michael@0: { michael@0: android::Mutex::Autolock autoLock(mLock); michael@0: michael@0: int64_t playPosition = 0; michael@0: if (mSeeking) { michael@0: return mSeekTimeUs; michael@0: } michael@0: if (!mStarted) { michael@0: return mPositionTimeMediaUs; michael@0: } michael@0: michael@0: playPosition = GetOutputPlayPositionUs_l(); michael@0: if (!mReachedEOS) { michael@0: mPositionTimeMediaUs = playPosition; michael@0: } michael@0: michael@0: return mPositionTimeMediaUs; michael@0: } michael@0: michael@0: int64_t AudioOffloadPlayer::GetOutputPlayPositionUs_l() const michael@0: { michael@0: CHECK(mAudioSink.get()); michael@0: uint32_t playedSamples = 0; michael@0: michael@0: mAudioSink->GetPosition(&playedSamples); michael@0: michael@0: const int64_t playedUs = (static_cast(playedSamples) * 1000000 ) / michael@0: mSampleRate; michael@0: michael@0: // HAL position is relative to the first buffer we sent at mStartPosUs michael@0: const int64_t renderedDuration = mStartPosUs + playedUs; michael@0: return renderedDuration; michael@0: } michael@0: michael@0: void AudioOffloadPlayer::NotifyAudioEOS() michael@0: { michael@0: nsCOMPtr nsEvent = NS_NewRunnableMethod(mObserver, michael@0: &MediaDecoder::PlaybackEnded); michael@0: NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void AudioOffloadPlayer::NotifyPositionChanged() michael@0: { michael@0: nsCOMPtr nsEvent = NS_NewRunnableMethod(mObserver, michael@0: &MediaOmxDecoder::PlaybackPositionChanged); michael@0: NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void AudioOffloadPlayer::NotifyAudioTearDown() michael@0: { michael@0: nsCOMPtr nsEvent = NS_NewRunnableMethod(mObserver, michael@0: &MediaOmxDecoder::AudioOffloadTearDown); michael@0: NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: // static michael@0: size_t AudioOffloadPlayer::AudioSinkCallback(AudioSink* aAudioSink, michael@0: void* aBuffer, michael@0: size_t aSize, michael@0: void* aCookie, michael@0: AudioSink::cb_event_t aEvent) michael@0: { michael@0: AudioOffloadPlayer* me = (AudioOffloadPlayer*) aCookie; michael@0: michael@0: switch (aEvent) { michael@0: michael@0: case AudioSink::CB_EVENT_FILL_BUFFER: michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Notify Audio position changed")); michael@0: me->NotifyPositionChanged(); michael@0: return me->FillBuffer(aBuffer, aSize); michael@0: michael@0: case AudioSink::CB_EVENT_STREAM_END: michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Notify Audio EOS")); michael@0: me->mReachedEOS = true; michael@0: me->NotifyAudioEOS(); michael@0: break; michael@0: michael@0: case AudioSink::CB_EVENT_TEAR_DOWN: michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Notify Tear down event")); michael@0: me->NotifyAudioTearDown(); michael@0: break; michael@0: michael@0: default: michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Unknown event %d from audio sink", michael@0: aEvent)); michael@0: break; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize) michael@0: { michael@0: CHECK(mAudioSink.get()); michael@0: michael@0: if (mReachedEOS) { michael@0: return 0; michael@0: } michael@0: michael@0: size_t sizeDone = 0; michael@0: size_t sizeRemaining = aSize; michael@0: while (sizeRemaining > 0) { michael@0: MediaSource::ReadOptions options; michael@0: bool refreshSeekTime = false; michael@0: michael@0: { michael@0: android::Mutex::Autolock autoLock(mLock); michael@0: michael@0: if (mSeeking) { michael@0: options.setSeekTo(mSeekTimeUs); michael@0: refreshSeekTime = true; michael@0: michael@0: if (mInputBuffer) { michael@0: mInputBuffer->release(); michael@0: mInputBuffer = nullptr; michael@0: } michael@0: mSeeking = false; michael@0: } michael@0: } michael@0: michael@0: if (!mInputBuffer) { michael@0: michael@0: status_t err; michael@0: err = mSource->read(&mInputBuffer, &options); michael@0: michael@0: CHECK((!err && mInputBuffer) || (err && !mInputBuffer)); michael@0: michael@0: android::Mutex::Autolock autoLock(mLock); michael@0: michael@0: if (err != OK) { michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Error while reading media source %d " michael@0: "Ok to receive EOS error at end", err)); michael@0: if (!mReachedEOS) { michael@0: // After seek there is a possible race condition if michael@0: // OffloadThread is observing state_stopping_1 before michael@0: // framesReady() > 0. Ensure sink stop is called michael@0: // after last buffer is released. This ensures the michael@0: // partial buffer is written to the driver before michael@0: // stopping one is observed.The drawback is that michael@0: // there will be an unnecessary call to the parser michael@0: // after parser signalled EOS. michael@0: if (sizeDone > 0) { michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("send Partial buffer down")); michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("skip calling stop till next" michael@0: " fillBuffer")); michael@0: break; michael@0: } michael@0: // no more buffers to push - stop() and wait for STREAM_END michael@0: // don't set mReachedEOS until stream end received michael@0: mAudioSink->Stop(); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: if(mInputBuffer->range_length() != 0) { michael@0: CHECK(mInputBuffer->meta_data()->findInt64( michael@0: kKeyTime, &mPositionTimeMediaUs)); michael@0: } michael@0: michael@0: if (refreshSeekTime) { michael@0: michael@0: if (mDispatchSeekEvents && !mSeekDuringPause) { michael@0: mDispatchSeekEvents = false; michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("FillBuffer posting SEEK_COMPLETE")); michael@0: nsCOMPtr nsEvent = NS_NewRunnableMethod(mObserver, michael@0: &MediaDecoder::SeekingStopped); michael@0: NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); michael@0: michael@0: } else if (mSeekDuringPause) { michael@0: // Callback is already called for seek during pause. Just reset the michael@0: // flag michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Not posting seek complete as its" michael@0: " already faked")); michael@0: mSeekDuringPause = false; michael@0: } michael@0: michael@0: NotifyPositionChanged(); michael@0: michael@0: // need to adjust the mStartPosUs for offload decoding since parser michael@0: // might not be able to get the exact seek time requested. michael@0: mStartPosUs = mPositionTimeMediaUs; michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Adjust seek time to: %.2f", michael@0: mStartPosUs / 1E6)); michael@0: michael@0: // clear seek time with mLock locked and once we have valid michael@0: // mPositionTimeMediaUs michael@0: // before clearing mSeekTimeUs check if a new seek request has been michael@0: // received while we were reading from the source with mLock released. michael@0: if (!mSeeking) { michael@0: mSeekTimeUs = 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mInputBuffer->range_length() == 0) { michael@0: mInputBuffer->release(); michael@0: mInputBuffer = nullptr; michael@0: continue; michael@0: } michael@0: michael@0: size_t copy = sizeRemaining; michael@0: if (copy > mInputBuffer->range_length()) { michael@0: copy = mInputBuffer->range_length(); michael@0: } michael@0: michael@0: memcpy((char *)aData + sizeDone, michael@0: (const char *)mInputBuffer->data() + mInputBuffer->range_offset(), michael@0: copy); michael@0: michael@0: mInputBuffer->set_range(mInputBuffer->range_offset() + copy, michael@0: mInputBuffer->range_length() - copy); michael@0: michael@0: sizeDone += copy; michael@0: sizeRemaining -= copy; michael@0: } michael@0: return sizeDone; michael@0: } michael@0: michael@0: void AudioOffloadPlayer::SetElementVisibility(bool aIsVisible) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mIsElementVisible = aIsVisible; michael@0: if (mIsElementVisible) { michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Element is visible. Start time update")); michael@0: StartTimeUpdate(); michael@0: } michael@0: } michael@0: michael@0: static void TimeUpdateCallback(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: AudioOffloadPlayer* player = static_cast(aClosure); michael@0: player->TimeUpdate(); michael@0: } michael@0: michael@0: void AudioOffloadPlayer::TimeUpdate() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: TimeStamp now = TimeStamp::Now(); michael@0: michael@0: // If TIMEUPDATE_MS has passed since the last fire update event fired, fire michael@0: // another timeupdate event. michael@0: if ((mLastFireUpdateTime.IsNull() || michael@0: now - mLastFireUpdateTime >= michael@0: TimeDuration::FromMilliseconds(TIMEUPDATE_MS))) { michael@0: mLastFireUpdateTime = now; michael@0: NotifyPositionChanged(); michael@0: } michael@0: michael@0: if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING || !mIsElementVisible) { michael@0: StopTimeUpdate(); michael@0: } michael@0: } michael@0: michael@0: nsresult AudioOffloadPlayer::StartTimeUpdate() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mTimeUpdateTimer) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mTimeUpdateTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: return mTimeUpdateTimer->InitWithFuncCallback(TimeUpdateCallback, michael@0: this, michael@0: TIMEUPDATE_MS, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: michael@0: nsresult AudioOffloadPlayer::StopTimeUpdate() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!mTimeUpdateTimer) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = mTimeUpdateTimer->Cancel(); michael@0: mTimeUpdateTimer = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: MediaDecoderOwner::NextFrameStatus AudioOffloadPlayer::GetNextFrameStatus() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mPlayState == MediaDecoder::PLAY_STATE_SEEKING) { michael@0: return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING; michael@0: } else if (mPlayState == MediaDecoder::PLAY_STATE_ENDED) { michael@0: return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; michael@0: } else { michael@0: return MediaDecoderOwner::NEXT_FRAME_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: void AudioOffloadPlayer::SendMetaDataToHal(sp& aSink, michael@0: const sp& aMeta) michael@0: { michael@0: int32_t sampleRate = 0; michael@0: int32_t bitRate = 0; michael@0: int32_t channelMask = 0; michael@0: int32_t delaySamples = 0; michael@0: int32_t paddingSamples = 0; michael@0: CHECK(aSink.get()); michael@0: michael@0: AudioParameter param = AudioParameter(); michael@0: michael@0: if (aMeta->findInt32(kKeySampleRate, &sampleRate)) { michael@0: param.addInt(String8(AUDIO_OFFLOAD_CODEC_SAMPLE_RATE), sampleRate); michael@0: } michael@0: if (aMeta->findInt32(kKeyChannelMask, &channelMask)) { michael@0: param.addInt(String8(AUDIO_OFFLOAD_CODEC_NUM_CHANNEL), channelMask); michael@0: } michael@0: if (aMeta->findInt32(kKeyBitRate, &bitRate)) { michael@0: param.addInt(String8(AUDIO_OFFLOAD_CODEC_AVG_BIT_RATE), bitRate); michael@0: } michael@0: if (aMeta->findInt32(kKeyEncoderDelay, &delaySamples)) { michael@0: param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), delaySamples); michael@0: } michael@0: if (aMeta->findInt32(kKeyEncoderPadding, &paddingSamples)) { michael@0: param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), paddingSamples); michael@0: } michael@0: michael@0: AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("SendMetaDataToHal: bitRate %d," michael@0: " sampleRate %d, chanMask %d, delaySample %d, paddingSample %d", bitRate, michael@0: sampleRate, channelMask, delaySamples, paddingSamples)); michael@0: michael@0: aSink->SetParameters(param.toString()); michael@0: return; michael@0: } michael@0: michael@0: void AudioOffloadPlayer::SetVolume(double aVolume) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: CHECK(mAudioSink.get()); michael@0: mAudioSink->SetVolume((float) aVolume); michael@0: } michael@0: michael@0: } // namespace mozilla