blob: d182f88f9c3077140f94eede55e06d470366fef1 [file] [log] [blame]
/*
* Copyright (C) 2022 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.
*/
package com.android.media.benchmark.library;
import android.media.MediaCodec;
import android.util.Log;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.CompletableFuture;
public class FrameReleaseQueue {
private static final String TAG = "FrameReleaseQueue";
private MediaCodec mCodec;
private LinkedBlockingQueue<FrameInfo> mFrameInfoQueue;
private ReleaseThread mReleaseThread;
private AtomicBoolean doFrameRelease = new AtomicBoolean(false);
private boolean mRender = false;
private int mWaitTime = 40; // milliseconds per frame
private int mWaitTimeCorrection = 0;
private int mCorrectionLoopCount;
private int firstReleaseTime = -1;
private int THRESHOLD_TIME = 5;
private static class FrameInfo {
private int number;
private int bufferId;
private int displayTime;
public FrameInfo(int frameNumber, int frameBufferId, int frameDisplayTime) {
this.number = frameNumber;
this.bufferId = frameBufferId;
this.displayTime = frameDisplayTime;
}
}
private class ReleaseThread extends Thread {
public void run() {
long nextReleaseTime = 0;
int loopCount = 0;
while (doFrameRelease.get() || mFrameInfoQueue.size() > 0) {
FrameInfo curFrameInfo = mFrameInfoQueue.peek();
if (curFrameInfo == null) {
nextReleaseTime += mWaitTime;
} else {
if (firstReleaseTime == -1 || curFrameInfo.displayTime <= 0) {
// first frame of loop
firstReleaseTime = getCurSysTime();
nextReleaseTime = firstReleaseTime + mWaitTime;
popAndRelease(true);
} else if (!doFrameRelease.get() && mFrameInfoQueue.size() == 1) {
// EOS
Log.i(TAG, "EOS");
popAndRelease(false);
} else {
nextReleaseTime += mWaitTime;
int curSysTime = getCurSysTime();
int curMediaTime = curSysTime - firstReleaseTime;
while (curFrameInfo != null && curFrameInfo.displayTime > 0 &&
curFrameInfo.displayTime <= curMediaTime) {
if (!((curMediaTime - curFrameInfo.displayTime) <= THRESHOLD_TIME)) {
Log.d(TAG, "Dropping expired frame " + curFrameInfo.number +
" display time " + curFrameInfo.displayTime +
" current time " + curMediaTime);
popAndRelease(false);
} else {
popAndRelease(true);
}
curFrameInfo = mFrameInfoQueue.peek();
}
if (curFrameInfo != null && curFrameInfo.displayTime > curMediaTime) {
if ((curFrameInfo.displayTime - curMediaTime) < THRESHOLD_TIME) {
// release the frame now as we are already there
popAndRelease(true);
}
}
}
}
long sleepTime = nextReleaseTime - getCurSysTime();
if (sleepTime > 0) {
try {
mReleaseThread.sleep(sleepTime);
} catch (InterruptedException e) {
Log.e(TAG, "Threw InterruptedException on sleep");
}
} else {
Log.d(TAG, "Thread sleep time less than 1");
}
if (loopCount % mCorrectionLoopCount == 0) {
nextReleaseTime += mWaitTimeCorrection;
}
loopCount += 1;
}
}
}
public FrameReleaseQueue(boolean render, int frameRate) {
this.mFrameInfoQueue = new LinkedBlockingQueue();
this.mReleaseThread = new ReleaseThread();
this.doFrameRelease.set(true);
this.mRender = render;
this.mWaitTime = 1000 / frameRate; // wait time in milliseconds per frame
int waitTimeRemainder = 1000 % frameRate;
int gcd = gcd(frameRate, waitTimeRemainder);
this.mCorrectionLoopCount = frameRate / gcd;
this.mWaitTimeCorrection = waitTimeRemainder / gcd;
Log.i(TAG, "Constructed FrameReleaseQueue with wait time " + this.mWaitTime + " ms");
}
private static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
public void setMediaCodec(MediaCodec mediaCodec) {
this.mCodec = mediaCodec;
}
public boolean pushFrame(int frameNumber, int frameBufferId, long frameDisplayTime) {
int frameDisplayTimeMs = (int)(frameDisplayTime/1000);
FrameInfo curFrameInfo = new FrameInfo(frameNumber, frameBufferId, frameDisplayTimeMs);
boolean pushSuccess = mFrameInfoQueue.offer(curFrameInfo);
if (!pushSuccess) {
Log.e(TAG, "Failed to push frame with buffer id " + curFrameInfo.bufferId);
return false;
}
if (!mReleaseThread.isAlive()) {
mReleaseThread.start();
Log.i(TAG, "Started frame release thread");
}
return true;
}
private int getCurSysTime() {
return (int)(System.nanoTime()/1000000);
}
private void popAndRelease(boolean renderThisFrame) {
final boolean actualRender = (renderThisFrame && mRender);
try {
final FrameInfo curFrameInfo = mFrameInfoQueue.take();
CompletableFuture future = CompletableFuture.runAsync(() -> {
try {
mCodec.releaseOutputBuffer(curFrameInfo.bufferId, actualRender);
} catch (IllegalStateException e) {
e.printStackTrace();
}
});
} catch (InterruptedException e) {
Log.e(TAG, "Threw InterruptedException on take");
}
}
public void stopFrameRelease() {
doFrameRelease.set(false);
try {
mReleaseThread.join();
Log.i(TAG, "Joined frame release thread");
} catch (InterruptedException e) {
Log.e(TAG, "Threw InterruptedException on thread join");
}
}
}