blob: 80f27ed547d48d6c7732b9a3b0506dd95ec9499d [file] [log] [blame]
/*
* Copyright (C) 2021 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_TAG "Camera3-PreviewFrameScheduler"
#define ATRACE_TAG ATRACE_TAG_CAMERA
//#define LOG_NDEBUG 0
#include <utils/Log.h>
#include <utils/Trace.h>
#include <android/looper.h>
#include "PreviewFrameScheduler.h"
#include "Camera3OutputStream.h"
namespace android {
namespace camera3 {
/**
* Internal Choreographer thread implementation for polling and handling callbacks
*/
// Callback function for Choreographer
static void frameCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
PreviewFrameScheduler* parent = static_cast<PreviewFrameScheduler*>(data);
if (parent == nullptr) {
ALOGE("%s: Invalid data for Choreographer callback!", __FUNCTION__);
return;
}
size_t length = AChoreographerFrameCallbackData_getFrameTimelinesLength(callbackData);
std::vector<nsecs_t> timeline(length);
for (size_t i = 0; i < length; i++) {
nsecs_t timestamp = AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
callbackData, i);
timeline[i] = timestamp;
}
parent->onNewPresentationTime(timeline);
AChoreographer_postVsyncCallback(AChoreographer_getInstance(), frameCallback, data);
}
struct ChoreographerThread : public Thread {
ChoreographerThread();
status_t start(PreviewFrameScheduler* parent);
virtual status_t readyToRun() override;
virtual bool threadLoop() override;
protected:
virtual ~ChoreographerThread() {}
private:
ChoreographerThread &operator=(const ChoreographerThread &);
// This only impacts the shutdown time. It won't impact the choreographer
// callback frequency.
static constexpr nsecs_t kPollingTimeoutMs = 5;
PreviewFrameScheduler* mParent = nullptr;
};
ChoreographerThread::ChoreographerThread() : Thread(false /*canCallJava*/) {
}
status_t ChoreographerThread::start(PreviewFrameScheduler* parent) {
mParent = parent;
return run("PreviewChoreographer");
}
status_t ChoreographerThread::readyToRun() {
ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
if (AChoreographer_getInstance() == NULL) {
return NO_INIT;
}
AChoreographer_postVsyncCallback(
AChoreographer_getInstance(), frameCallback, mParent);
return OK;
}
bool ChoreographerThread::threadLoop() {
if (exitPending()) {
return false;
}
ALooper_pollOnce(kPollingTimeoutMs, nullptr, nullptr, nullptr);
return true;
}
/**
* PreviewFrameScheduler implementation
*/
PreviewFrameScheduler::PreviewFrameScheduler(Camera3OutputStream& parent, sp<Surface> consumer) :
mParent(parent),
mConsumer(consumer),
mChoreographerThread(new ChoreographerThread()) {
}
PreviewFrameScheduler::~PreviewFrameScheduler() {
{
Mutex::Autolock l(mLock);
mChoreographerThread->requestExit();
}
mChoreographerThread->join();
}
status_t PreviewFrameScheduler::queuePreviewBuffer(nsecs_t timestamp, int32_t transform,
ANativeWindowBuffer* anwBuffer, int releaseFence) {
// Start choreographer thread if it's not already running.
if (!mChoreographerThread->isRunning()) {
status_t res = mChoreographerThread->start(this);
if (res != OK) {
ALOGE("%s: Failed to init choreographer thread!", __FUNCTION__);
return res;
}
}
{
Mutex::Autolock l(mLock);
mPendingBuffers.emplace(timestamp, transform, anwBuffer, releaseFence);
// Queue buffer to client right away if pending buffers are more than
// the queue depth watermark.
if (mPendingBuffers.size() > kQueueDepthWatermark) {
auto oldBuffer = mPendingBuffers.front();
mPendingBuffers.pop();
status_t res = queueBufferToClientLocked(oldBuffer, oldBuffer.timestamp);
if (res != OK) {
return res;
}
// Reset the last capture and presentation time
mLastCameraCaptureTime = 0;
mLastCameraPresentTime = 0;
} else {
ATRACE_INT(kPendingBufferTraceName, mPendingBuffers.size());
}
}
return OK;
}
void PreviewFrameScheduler::onNewPresentationTime(const std::vector<nsecs_t>& timeline) {
ATRACE_CALL();
Mutex::Autolock l(mLock);
if (mPendingBuffers.size() > 0) {
auto nextBuffer = mPendingBuffers.front();
mPendingBuffers.pop();
// Find the best presentation time by finding the element in the
// choreographer timeline that's closest to the ideal presentation time.
// The ideal presentation time is the last presentation time + frame
// interval.
nsecs_t cameraInterval = nextBuffer.timestamp - mLastCameraCaptureTime;
nsecs_t idealPresentTime = (cameraInterval < kSpacingResetIntervalNs) ?
(mLastCameraPresentTime + cameraInterval) : nextBuffer.timestamp;
nsecs_t presentTime = *std::min_element(timeline.begin(), timeline.end(),
[idealPresentTime](nsecs_t p1, nsecs_t p2) {
return std::abs(p1 - idealPresentTime) < std::abs(p2 - idealPresentTime);
});
status_t res = queueBufferToClientLocked(nextBuffer, presentTime);
ATRACE_INT(kPendingBufferTraceName, mPendingBuffers.size());
if (mParent.shouldLogError(res)) {
ALOGE("%s: Preview Stream: Error queueing buffer to native window:"
" %s (%d)", __FUNCTION__, strerror(-res), res);
}
mLastCameraCaptureTime = nextBuffer.timestamp;
mLastCameraPresentTime = presentTime;
}
}
status_t PreviewFrameScheduler::queueBufferToClientLocked(
const BufferHolder& bufferHolder, nsecs_t timestamp) {
mParent.setTransform(bufferHolder.transform, true/*mayChangeMirror*/);
status_t res = native_window_set_buffers_timestamp(mConsumer.get(), timestamp);
if (res != OK) {
ALOGE("%s: Preview Stream: Error setting timestamp: %s (%d)",
__FUNCTION__, strerror(-res), res);
return res;
}
Camera3Stream::queueHDRMetadata(bufferHolder.anwBuffer.get()->handle, mConsumer,
mParent.getDynamicRangeProfile());
res = mConsumer->queueBuffer(mConsumer.get(), bufferHolder.anwBuffer.get(),
bufferHolder.releaseFence);
if (res != OK) {
close(bufferHolder.releaseFence);
}
return res;
}
}; // namespace camera3
}; // namespace android