| /* |
| * Copyright (C) 2024 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 "VirtualCameraTestInstance" |
| |
| #include "VirtualCameraTestInstance.h" |
| |
| #include <atomic> |
| #include <chrono> |
| #include <memory> |
| #include <mutex> |
| #include <ratio> |
| #include <thread> |
| |
| #include "GLES/gl.h" |
| #include "android/binder_auto_utils.h" |
| #include "android/native_window.h" |
| #include "log/log.h" |
| #include "util/EglDisplayContext.h" |
| #include "util/EglProgram.h" |
| |
| namespace android { |
| namespace companion { |
| namespace virtualcamera { |
| |
| using ::aidl::android::companion::virtualcamera::Format; |
| using ::aidl::android::view::Surface; |
| using ::ndk::ScopedAStatus; |
| |
| namespace { |
| |
| std::shared_ptr<ANativeWindow> nativeWindowFromSurface(const Surface& surface) { |
| ANativeWindow* nativeWindow = surface.get(); |
| if (nativeWindow != nullptr) { |
| ANativeWindow_acquire(nativeWindow); |
| } |
| return std::shared_ptr<ANativeWindow>(nativeWindow, ANativeWindow_release); |
| } |
| |
| std::chrono::nanoseconds getCurrentTimestamp() { |
| return std::chrono::duration_cast<std::chrono::nanoseconds>( |
| std::chrono::steady_clock::now().time_since_epoch()); |
| } |
| |
| } // namespace |
| |
| TestPatternRenderer::TestPatternRenderer( |
| std::shared_ptr<ANativeWindow> nativeWindow, int fps) |
| : mFps(fps), mNativeWindow(nativeWindow) { |
| } |
| |
| void TestPatternRenderer::start() { |
| std::lock_guard<std::mutex> lock(mLock); |
| if (mRunning.exchange(true, std::memory_order_relaxed)) { |
| ALOGW("Render thread already started."); |
| return; |
| } |
| mThread = |
| std::thread(&TestPatternRenderer::renderThreadLoop, this, mNativeWindow); |
| } |
| |
| void TestPatternRenderer::stop() { |
| std::lock_guard<std::mutex> lock(mLock); |
| if (!mRunning.exchange(false, std::memory_order_relaxed)) { |
| ALOGW("Render thread already stopped."); |
| return; |
| } |
| mThread.detach(); |
| mRunning = false; |
| } |
| |
| void TestPatternRenderer::renderThreadLoop( |
| std::shared_ptr<ANativeWindow> nativeWindow) { |
| // Prevent destruction of this instance until the thread terminates. |
| std::shared_ptr<TestPatternRenderer> thiz = shared_from_this(); |
| |
| ALOGV("Starting test client render loop"); |
| |
| EglDisplayContext eglDisplayContext(nativeWindow); |
| EglTestPatternProgram testPatternProgram; |
| |
| const std::chrono::nanoseconds frameDuration( |
| static_cast<uint64_t>(1e9 / mFps)); |
| |
| std::chrono::nanoseconds lastFrameTs(0); |
| int frameNumber = 0; |
| while (mRunning) { |
| // Wait for appropriate amount of time to meet configured FPS. |
| std::chrono::nanoseconds ts = getCurrentTimestamp(); |
| std::chrono::nanoseconds currentDuration = ts - lastFrameTs; |
| if (currentDuration < frameDuration) { |
| std::this_thread::sleep_for(frameDuration - currentDuration); |
| } |
| |
| // Render the test pattern and update timestamp. |
| testPatternProgram.draw(ts); |
| eglDisplayContext.swapBuffers(); |
| lastFrameTs = getCurrentTimestamp(); |
| } |
| |
| ALOGV("Terminating test client render loop"); |
| } |
| |
| VirtualCameraTestInstance::VirtualCameraTestInstance(const int fps) |
| : mFps(fps) { |
| } |
| |
| ScopedAStatus VirtualCameraTestInstance::onStreamConfigured( |
| const int32_t streamId, const Surface& surface, const int32_t width, |
| const int32_t height, const Format pixelFormat) { |
| ALOGV("%s: streamId %d, %dx%d pixFmt=%s", __func__, streamId, width, height, |
| toString(pixelFormat).c_str()); |
| |
| auto renderer = std::make_shared<TestPatternRenderer>( |
| nativeWindowFromSurface(surface), mFps); |
| |
| std::lock_guard<std::mutex> lock(mLock); |
| if (mInputRenderers.try_emplace(streamId, renderer).second) { |
| renderer->start(); |
| } else { |
| ALOGE( |
| "%s: Input stream with id %d is already active, ignoring " |
| "onStreamConfigured call", |
| __func__, streamId); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus VirtualCameraTestInstance::onProcessCaptureRequest( |
| const int32_t /*in_streamId*/, const int32_t /*in_frameId*/) { |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus VirtualCameraTestInstance::onStreamClosed(const int32_t streamId) { |
| ALOGV("%s: streamId %d", __func__, streamId); |
| |
| std::shared_ptr<TestPatternRenderer> renderer; |
| { |
| std::lock_guard<std::mutex> lock(mLock); |
| auto it = mInputRenderers.find(streamId); |
| if (it != mInputRenderers.end()) { |
| renderer = std::move(it->second); |
| mInputRenderers.erase(it); |
| } |
| } |
| if (renderer != nullptr) { |
| renderer->stop(); |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| } // namespace virtualcamera |
| } // namespace companion |
| } // namespace android |