| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "MediaClock" |
| #include <utils/Log.h> |
| #include <map> |
| |
| #include <media/stagefright/MediaClock.h> |
| |
| #include <media/stagefright/foundation/ADebug.h> |
| #include <media/stagefright/foundation/AMessage.h> |
| |
| namespace android { |
| |
| // Maximum allowed time backwards from anchor change. |
| // If larger than this threshold, it's treated as discontinuity. |
| static const int64_t kAnchorFluctuationAllowedUs = 10000LL; |
| |
| MediaClock::Timer::Timer(const sp<AMessage> ¬ify, int64_t mediaTimeUs, int64_t adjustRealUs) |
| : mNotify(notify), |
| mMediaTimeUs(mediaTimeUs), |
| mAdjustRealUs(adjustRealUs) { |
| } |
| |
| MediaClock::MediaClock() |
| : mAnchorTimeMediaUs(-1), |
| mAnchorTimeRealUs(-1), |
| mMaxTimeMediaUs(INT64_MAX), |
| mStartingTimeMediaUs(-1), |
| mPlaybackRate(1.0), |
| mGeneration(0) { |
| mLooper = new ALooper; |
| mLooper->setName("MediaClock"); |
| mLooper->start(false /* runOnCallingThread */, |
| false /* canCallJava */, |
| ANDROID_PRIORITY_AUDIO); |
| } |
| |
| void MediaClock::init() { |
| mLooper->registerHandler(this); |
| } |
| |
| MediaClock::~MediaClock() { |
| reset(); |
| if (mLooper != NULL) { |
| mLooper->unregisterHandler(id()); |
| mLooper->stop(); |
| } |
| } |
| |
| void MediaClock::reset() { |
| Mutex::Autolock autoLock(mLock); |
| auto it = mTimers.begin(); |
| while (it != mTimers.end()) { |
| it->mNotify->setInt32("reason", TIMER_REASON_RESET); |
| it->mNotify->post(); |
| it = mTimers.erase(it); |
| } |
| mMaxTimeMediaUs = INT64_MAX; |
| mStartingTimeMediaUs = -1; |
| updateAnchorTimesAndPlaybackRate_l(-1, -1, 1.0); |
| ++mGeneration; |
| } |
| |
| void MediaClock::setStartingTimeMedia(int64_t startingTimeMediaUs) { |
| Mutex::Autolock autoLock(mLock); |
| mStartingTimeMediaUs = startingTimeMediaUs; |
| } |
| |
| void MediaClock::clearAnchor() { |
| Mutex::Autolock autoLock(mLock); |
| updateAnchorTimesAndPlaybackRate_l(-1, -1, mPlaybackRate); |
| } |
| |
| void MediaClock::updateAnchor( |
| int64_t anchorTimeMediaUs, |
| int64_t anchorTimeRealUs, |
| int64_t maxTimeMediaUs) { |
| if (anchorTimeMediaUs < 0 || anchorTimeRealUs < 0) { |
| ALOGW("reject anchor time since it is negative."); |
| return; |
| } |
| |
| Mutex::Autolock autoLock(mLock); |
| int64_t nowUs = ALooper::GetNowUs(); |
| int64_t nowMediaUs = |
| anchorTimeMediaUs + (nowUs - anchorTimeRealUs) * (double)mPlaybackRate; |
| if (nowMediaUs < 0) { |
| ALOGW("reject anchor time since it leads to negative media time."); |
| return; |
| } |
| |
| if (maxTimeMediaUs != -1) { |
| mMaxTimeMediaUs = maxTimeMediaUs; |
| } |
| if (mAnchorTimeRealUs != -1) { |
| int64_t oldNowMediaUs = |
| mAnchorTimeMediaUs + (nowUs - mAnchorTimeRealUs) * (double)mPlaybackRate; |
| // earlier, we ensured that the anchor times are non-negative and the |
| // math to calculate the now/oldNow times stays non-negative. |
| // by casting into uint64_t, we gain headroom to avoid any overflows at the upper end |
| // when adding the fluctuation allowance. |
| if ((uint64_t)nowMediaUs < (uint64_t)oldNowMediaUs + kAnchorFluctuationAllowedUs |
| && (uint64_t)nowMediaUs + kAnchorFluctuationAllowedUs > (uint64_t)oldNowMediaUs) { |
| return; |
| } |
| } |
| updateAnchorTimesAndPlaybackRate_l(nowMediaUs, nowUs, mPlaybackRate); |
| |
| ++mGeneration; |
| processTimers_l(); |
| } |
| |
| void MediaClock::updateMaxTimeMedia(int64_t maxTimeMediaUs) { |
| Mutex::Autolock autoLock(mLock); |
| mMaxTimeMediaUs = maxTimeMediaUs; |
| } |
| |
| void MediaClock::setPlaybackRate(float rate) { |
| CHECK_GE(rate, 0.0); |
| Mutex::Autolock autoLock(mLock); |
| if (mAnchorTimeRealUs == -1) { |
| mPlaybackRate = rate; |
| return; |
| } |
| |
| int64_t nowUs = ALooper::GetNowUs(); |
| int64_t nowMediaUs = mAnchorTimeMediaUs + (nowUs - mAnchorTimeRealUs) * (double)mPlaybackRate; |
| if (nowMediaUs < 0) { |
| ALOGW("setRate: anchor time should not be negative, set to 0."); |
| nowMediaUs = 0; |
| } |
| updateAnchorTimesAndPlaybackRate_l(nowMediaUs, nowUs, rate); |
| |
| if (rate > 0.0) { |
| ++mGeneration; |
| processTimers_l(); |
| } |
| } |
| |
| float MediaClock::getPlaybackRate() const { |
| Mutex::Autolock autoLock(mLock); |
| return mPlaybackRate; |
| } |
| |
| status_t MediaClock::getMediaTime( |
| int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) const { |
| if (outMediaUs == NULL) { |
| return BAD_VALUE; |
| } |
| |
| Mutex::Autolock autoLock(mLock); |
| return getMediaTime_l(realUs, outMediaUs, allowPastMaxTime); |
| } |
| |
| status_t MediaClock::getMediaTime_l( |
| int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) const { |
| if (mAnchorTimeRealUs == -1) { |
| return NO_INIT; |
| } |
| |
| int64_t mediaUs = mAnchorTimeMediaUs |
| + (realUs - mAnchorTimeRealUs) * (double)mPlaybackRate; |
| if (mediaUs > mMaxTimeMediaUs && !allowPastMaxTime) { |
| mediaUs = mMaxTimeMediaUs; |
| } |
| if (mediaUs < mStartingTimeMediaUs) { |
| mediaUs = mStartingTimeMediaUs; |
| } |
| if (mediaUs < 0) { |
| mediaUs = 0; |
| } |
| *outMediaUs = mediaUs; |
| return OK; |
| } |
| |
| status_t MediaClock::getRealTimeFor( |
| int64_t targetMediaUs, int64_t *outRealUs) const { |
| if (outRealUs == NULL) { |
| return BAD_VALUE; |
| } |
| |
| Mutex::Autolock autoLock(mLock); |
| if (mPlaybackRate == 0.0) { |
| return NO_INIT; |
| } |
| |
| int64_t nowUs = ALooper::GetNowUs(); |
| int64_t nowMediaUs; |
| status_t status = |
| getMediaTime_l(nowUs, &nowMediaUs, true /* allowPastMaxTime */); |
| if (status != OK) { |
| return status; |
| } |
| *outRealUs = (targetMediaUs - nowMediaUs) / (double)mPlaybackRate + nowUs; |
| return OK; |
| } |
| |
| void MediaClock::addTimer(const sp<AMessage> ¬ify, int64_t mediaTimeUs, |
| int64_t adjustRealUs) { |
| Mutex::Autolock autoLock(mLock); |
| |
| bool updateTimer = (mPlaybackRate != 0.0); |
| if (updateTimer) { |
| auto it = mTimers.begin(); |
| while (it != mTimers.end()) { |
| if (((it->mAdjustRealUs - (double)adjustRealUs) * (double)mPlaybackRate |
| + (it->mMediaTimeUs - mediaTimeUs)) <= 0) { |
| updateTimer = false; |
| break; |
| } |
| ++it; |
| } |
| } |
| |
| mTimers.emplace_back(notify, mediaTimeUs, adjustRealUs); |
| |
| if (updateTimer) { |
| ++mGeneration; |
| processTimers_l(); |
| } |
| } |
| |
| void MediaClock::onMessageReceived(const sp<AMessage> &msg) { |
| switch (msg->what()) { |
| case kWhatTimeIsUp: |
| { |
| int32_t generation; |
| CHECK(msg->findInt32("generation", &generation)); |
| |
| Mutex::Autolock autoLock(mLock); |
| if (generation != mGeneration) { |
| break; |
| } |
| processTimers_l(); |
| break; |
| } |
| |
| default: |
| TRESPASS(); |
| break; |
| } |
| } |
| |
| void MediaClock::processTimers_l() { |
| int64_t nowMediaTimeUs; |
| status_t status = getMediaTime_l( |
| ALooper::GetNowUs(), &nowMediaTimeUs, false /* allowPastMaxTime */); |
| |
| if (status != OK) { |
| return; |
| } |
| |
| int64_t nextLapseRealUs = INT64_MAX; |
| std::multimap<int64_t, Timer> notifyList; |
| auto it = mTimers.begin(); |
| while (it != mTimers.end()) { |
| double diff = it->mAdjustRealUs * (double)mPlaybackRate |
| + it->mMediaTimeUs - nowMediaTimeUs; |
| int64_t diffMediaUs; |
| if (diff > (double)INT64_MAX) { |
| diffMediaUs = INT64_MAX; |
| } else if (diff < (double)INT64_MIN) { |
| diffMediaUs = INT64_MIN; |
| } else { |
| diffMediaUs = diff; |
| } |
| |
| if (diffMediaUs <= 0) { |
| notifyList.emplace(diffMediaUs, *it); |
| it = mTimers.erase(it); |
| } else { |
| if (mPlaybackRate != 0.0 |
| && (double)diffMediaUs < (double)INT64_MAX * (double)mPlaybackRate) { |
| int64_t targetRealUs = diffMediaUs / (double)mPlaybackRate; |
| if (targetRealUs < nextLapseRealUs) { |
| nextLapseRealUs = targetRealUs; |
| } |
| } |
| ++it; |
| } |
| } |
| |
| auto itNotify = notifyList.begin(); |
| while (itNotify != notifyList.end()) { |
| itNotify->second.mNotify->setInt32("reason", TIMER_REASON_REACHED); |
| itNotify->second.mNotify->post(); |
| itNotify = notifyList.erase(itNotify); |
| } |
| |
| if (mTimers.empty() || mPlaybackRate == 0.0 || mAnchorTimeMediaUs < 0 |
| || nextLapseRealUs == INT64_MAX) { |
| return; |
| } |
| |
| sp<AMessage> msg = new AMessage(kWhatTimeIsUp, this); |
| msg->setInt32("generation", mGeneration); |
| msg->post(nextLapseRealUs); |
| } |
| |
| void MediaClock::updateAnchorTimesAndPlaybackRate_l(int64_t anchorTimeMediaUs, |
| int64_t anchorTimeRealUs, float playbackRate) { |
| if (mAnchorTimeMediaUs != anchorTimeMediaUs |
| || mAnchorTimeRealUs != anchorTimeRealUs |
| || mPlaybackRate != playbackRate) { |
| mAnchorTimeMediaUs = anchorTimeMediaUs; |
| mAnchorTimeRealUs = anchorTimeRealUs; |
| mPlaybackRate = playbackRate; |
| notifyDiscontinuity_l(); |
| } |
| } |
| |
| void MediaClock::setNotificationMessage(const sp<AMessage> &msg) { |
| Mutex::Autolock autoLock(mLock); |
| mNotify = msg; |
| } |
| |
| void MediaClock::notifyDiscontinuity_l() { |
| if (mNotify != nullptr) { |
| sp<AMessage> msg = mNotify->dup(); |
| msg->setInt64("anchor-media-us", mAnchorTimeMediaUs); |
| msg->setInt64("anchor-real-us", mAnchorTimeRealUs); |
| msg->setFloat("playback-rate", mPlaybackRate); |
| msg->post(); |
| } |
| } |
| |
| } // namespace android |