blob: 23671f28a6a318eb2dfd7ec39b1216f1ec9100ba [file] [log] [blame]
/*
* Copyright (C) 2018 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 "VideoFrameScheduler2"
#include <utils/Log.h>
#define ATRACE_TAG ATRACE_TAG_VIDEO
#include <utils/Mutex.h>
#include <utils/Thread.h>
#include <utils/Trace.h>
#include <algorithm>
#include <jni.h>
#include <math.h>
#include <android/choreographer.h>
#include <android/looper.h>
#include <media/stagefright/VideoFrameScheduler2.h>
#include <mediaplayer2/JavaVMHelper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AUtils.h>
namespace android {
static void getVsyncOffset(nsecs_t* appVsyncOffsetPtr, nsecs_t* sfVsyncOffsetPtr);
/* ======================================================================= */
/* VsyncTracker */
/* ======================================================================= */
class VsyncTracker : public RefBase{
public:
VsyncTracker();
~VsyncTracker() {}
nsecs_t getVsyncPeriod();
nsecs_t getVsyncTime(nsecs_t periodOffset);
void addSample(nsecs_t timestamp);
private:
static const int kMaxSamples = 32;
static const int kMinSamplesForUpdate = 6;
int mNumSamples;
int mFirstSample;
nsecs_t mReferenceTime;
nsecs_t mPhase;
nsecs_t mPeriod;
nsecs_t mTimestampSamples[kMaxSamples];
Mutex mLock;
void updateModelLocked();
};
VsyncTracker::VsyncTracker()
: mNumSamples(0),
mFirstSample(0),
mReferenceTime(0),
mPhase(0),
mPeriod(0) {
for (int i = 0; i < kMaxSamples; i++) {
mTimestampSamples[i] = 0;
}
}
nsecs_t VsyncTracker::getVsyncPeriod() {
Mutex::Autolock dataLock(mLock);
return mPeriod;
}
nsecs_t VsyncTracker::getVsyncTime(nsecs_t periodOffset) {
Mutex::Autolock dataLock(mLock);
const nsecs_t now = systemTime();
nsecs_t phase = mReferenceTime + mPhase;
// result = (((now - phase) / mPeriod) + periodOffset + 1) * mPeriod + phase
// prevent overflow
nsecs_t result = (now - phase) / mPeriod;
if (result > LONG_LONG_MAX - periodOffset - 1) {
return LONG_LONG_MAX;
} else {
result += periodOffset + 1;
}
if (result > LONG_LONG_MAX / mPeriod) {
return LONG_LONG_MAX;
} else {
result *= mPeriod;
}
if (result > LONG_LONG_MAX - phase) {
return LONG_LONG_MAX;
} else {
result += phase;
}
return result;
}
void VsyncTracker::addSample(nsecs_t timestamp) {
Mutex::Autolock dataLock(mLock);
if (mNumSamples == 0) {
mPhase = 0;
mReferenceTime = timestamp;
}
int idx = (mFirstSample + mNumSamples) % kMaxSamples;
mTimestampSamples[idx] = timestamp;
if (mNumSamples < kMaxSamples) {
mNumSamples++;
} else {
mFirstSample = (mFirstSample + 1) % kMaxSamples;
}
updateModelLocked();
}
void VsyncTracker::updateModelLocked() {
if (mNumSamples < kMinSamplesForUpdate) {
return;
}
nsecs_t durationSum = 0;
nsecs_t minDuration = LONG_MAX;
nsecs_t maxDuration = 0;
for (int i = 1; i < mNumSamples; i++) {
int idx = (mFirstSample + i) % kMaxSamples;
int prev = (idx + kMaxSamples - 1) % kMaxSamples;
long duration = mTimestampSamples[idx] - mTimestampSamples[prev];
durationSum += duration;
if (minDuration > duration) { minDuration = duration; }
if (maxDuration < duration) { maxDuration = duration; }
}
durationSum -= (minDuration + maxDuration);
mPeriod = durationSum / (mNumSamples - 3);
double sampleAvgX = 0.0;
double sampleAvgY = 0.0;
double scale = 2.0 * M_PI / (double) mPeriod;
for (int i = 1; i < mNumSamples; i++) {
int idx = (mFirstSample + i) % kMaxSamples;
long sample = mTimestampSamples[idx] - mReferenceTime;
double samplePhase = (double) (sample % mPeriod) * scale;
sampleAvgX += cos(samplePhase);
sampleAvgY += sin(samplePhase);
}
sampleAvgX /= (double) mNumSamples - 1.0;
sampleAvgY /= (double) mNumSamples - 1.0;
mPhase = (long) (atan2(sampleAvgY, sampleAvgX) / scale);
}
static void frameCallback(int64_t frameTimeNanos, void* data) {
if (data == NULL) {
return;
}
sp<VsyncTracker> vsyncTracker(static_cast<VsyncTracker*>(data));
vsyncTracker->addSample(frameTimeNanos);
AChoreographer_postFrameCallback64(AChoreographer_getInstance(),
frameCallback, static_cast<void*>(vsyncTracker.get()));
}
/* ======================================================================= */
/* JNI */
/* ======================================================================= */
static void getVsyncOffset(nsecs_t* appVsyncOffsetPtr, nsecs_t* sfVsyncOffsetPtr) {
static const nsecs_t kOneMillisecInNanosec = 1000000;
static const nsecs_t kOneSecInNanosec = kOneMillisecInNanosec * 1000;
JNIEnv *env = JavaVMHelper::getJNIEnv();
jclass jDisplayManagerGlobalCls = env->FindClass(
"android/hardware/display/DisplayManagerGlobal");
jclass jDisplayCls = env->FindClass("android/view/Display");
jmethodID jGetInstance = env->GetStaticMethodID(jDisplayManagerGlobalCls,
"getInstance", "()Landroid/hardware/display/DisplayManagerGlobal;");
jobject javaDisplayManagerGlobalObj = env->CallStaticObjectMethod(
jDisplayManagerGlobalCls, jGetInstance);
jfieldID jDEFAULT_DISPLAY = env->GetStaticFieldID(jDisplayCls, "DEFAULT_DISPLAY", "I");
jint DEFAULT_DISPLAY = env->GetStaticIntField(jDisplayCls, jDEFAULT_DISPLAY);
jmethodID jgetRealDisplay = env->GetMethodID(jDisplayManagerGlobalCls,
"getRealDisplay", "(I)Landroid/view/Display;");
jobject javaDisplayObj = env->CallObjectMethod(
javaDisplayManagerGlobalObj, jgetRealDisplay, DEFAULT_DISPLAY);
jmethodID jGetRefreshRate = env->GetMethodID(jDisplayCls, "getRefreshRate", "()F");
jfloat javaRefreshRate = env->CallFloatMethod(javaDisplayObj, jGetRefreshRate);
nsecs_t vsyncPeriod = (nsecs_t) (kOneSecInNanosec / (float) javaRefreshRate);
jmethodID jGetAppVsyncOffsetNanos = env->GetMethodID(
jDisplayCls, "getAppVsyncOffsetNanos", "()J");
jlong javaAppVsyncOffset = env->CallLongMethod(javaDisplayObj, jGetAppVsyncOffsetNanos);
*appVsyncOffsetPtr = (nsecs_t) javaAppVsyncOffset;
jmethodID jGetPresentationDeadlineNanos = env->GetMethodID(
jDisplayCls, "getPresentationDeadlineNanos", "()J");
jlong javaPresentationDeadline = env->CallLongMethod(
javaDisplayObj, jGetPresentationDeadlineNanos);
*sfVsyncOffsetPtr = vsyncPeriod - ((nsecs_t) javaPresentationDeadline - kOneMillisecInNanosec);
}
/* ======================================================================= */
/* Choreographer Thread */
/* ======================================================================= */
struct ChoreographerThread : public Thread {
ChoreographerThread(bool canCallJava);
status_t init(void* data);
virtual status_t readyToRun() override;
virtual bool threadLoop() override;
protected:
virtual ~ChoreographerThread() {}
private:
DISALLOW_EVIL_CONSTRUCTORS(ChoreographerThread);
void* mData;
};
ChoreographerThread::ChoreographerThread(bool canCallJava) : Thread(canCallJava) {
}
status_t ChoreographerThread::init(void* data) {
if (data == NULL) {
return NO_INIT;
}
mData = data;
return OK;
}
status_t ChoreographerThread::readyToRun() {
ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
if (AChoreographer_getInstance() == NULL) {
return NO_INIT;
}
AChoreographer_postFrameCallback64(AChoreographer_getInstance(), frameCallback, mData);
return OK;
}
bool ChoreographerThread::threadLoop() {
ALooper_pollOnce(-1, nullptr, nullptr, nullptr);
return true;
}
/* ======================================================================= */
/* Frame Scheduler */
/* ======================================================================= */
VideoFrameScheduler2::VideoFrameScheduler2() : VideoFrameSchedulerBase() {
getVsyncOffset(&mAppVsyncOffset, &mSfVsyncOffset);
Mutex::Autolock threadLock(mLock);
mChoreographerThread = new ChoreographerThread(true);
mVsyncTracker = new VsyncTracker();
if (mChoreographerThread->init(static_cast<void*>(mVsyncTracker.get())) != OK) {
mChoreographerThread.clear();
}
if (mChoreographerThread != NULL && mChoreographerThread->run("Choreographer") != OK) {
mChoreographerThread.clear();
}
}
void VideoFrameScheduler2::updateVsync() {
mVsyncTime = 0;
mVsyncPeriod = 0;
if (mVsyncTracker != NULL) {
mVsyncPeriod = mVsyncTracker->getVsyncPeriod();
mVsyncTime = mVsyncTracker->getVsyncTime(mSfVsyncOffset - mAppVsyncOffset);
}
mVsyncRefreshAt = systemTime(SYSTEM_TIME_MONOTONIC) + kVsyncRefreshPeriod;
}
void VideoFrameScheduler2::release() {
// Do not change order
{
Mutex::Autolock threadLock(mLock);
mChoreographerThread->requestExitAndWait();
mChoreographerThread.clear();
}
mVsyncTracker.clear();
}
VideoFrameScheduler2::~VideoFrameScheduler2() {
release();
}
} // namespace android