From 32e38fda59878819ad731734bc55b919a78fd3cf Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Fri, 29 Aug 2025 18:01:53 +0900 Subject: [PATCH] ... --- app/src/main/cpp/MediaAsset.cpp | 15 +++++ app/src/main/cpp/MediaAsset.h | 2 + app/src/main/cpp/Renderer.cpp | 55 ++++++++++++++++++- app/src/main/cpp/Renderer.h | 10 ++++ app/src/main/cpp/native_renderer.cpp | 24 ++++++++ .../launcher/wall/MyWallpaperService.kt | 43 ++++++++------- .../lunatic/launcher/wall/NativeRenderer.kt | 19 ++++++- 7 files changed, 145 insertions(+), 23 deletions(-) diff --git a/app/src/main/cpp/MediaAsset.cpp b/app/src/main/cpp/MediaAsset.cpp index c03e9a6e..0c744989 100644 --- a/app/src/main/cpp/MediaAsset.cpp +++ b/app/src/main/cpp/MediaAsset.cpp @@ -203,6 +203,13 @@ bool MediaAsset::loadVideoWithFFmpeg(int fd) { LOGE("Could not find a video stream"); release(); return false; } + AVStream* stream = fmtCtx_->streams[videoStreamIdx_]; + if (stream->avg_frame_rate.den != 0) { + fps_ = av_q2d(stream->avg_frame_rate); + } else { + fps_ = 30.0; // FPS 정보가 없는 경우 기본값 30 + } + LOGI("Video FPS detected: %f", fps_); codecCtx_ = avcodec_alloc_context3(codec); if (!codecCtx_ || avcodec_parameters_to_context(codecCtx_, fmtCtx_->streams[videoStreamIdx_]->codecpar) < 0) { LOGE("Failed to create codec context"); @@ -244,6 +251,14 @@ bool MediaAsset::loadVideoWithFFmpeg(const std::string& path) { const AVCodec* codec = nullptr; videoStreamIdx_ = av_find_best_stream(fmtCtx_, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); if (videoStreamIdx_ < 0) { release(); return false; } + // --- ⬇️ FPS 추출 코드 추가 (동일한 코드) ⬇️ --- + AVStream* stream = fmtCtx_->streams[videoStreamIdx_]; + if (stream->avg_frame_rate.den != 0) { + fps_ = av_q2d(stream->avg_frame_rate); + } else { + fps_ = 30.0; + } + LOGI("Video FPS detected: %f", fps_); codecCtx_ = avcodec_alloc_context3(codec); if (!codecCtx_ || avcodec_parameters_to_context(codecCtx_, fmtCtx_->streams[videoStreamIdx_]->codecpar) < 0) { release(); return false; } if (avcodec_open2(codecCtx_, codec, nullptr) < 0) { release(); return false; } diff --git a/app/src/main/cpp/MediaAsset.h b/app/src/main/cpp/MediaAsset.h index 27ab51ef..9efb1d17 100644 --- a/app/src/main/cpp/MediaAsset.h +++ b/app/src/main/cpp/MediaAsset.h @@ -51,6 +51,7 @@ public: AVPacket* getPacket() const { return packet_; } SwsContext* getSwsContext() const { return swsCtx_; } int getVideoStreamIndex() const { return videoStreamIdx_; } + double getFps() const { return fps_; } // <-- FPS getter 추가 private: bool loadInternal(const std::string& path); @@ -72,4 +73,5 @@ private: AVPacket* packet_ = nullptr; SwsContext* swsCtx_ = nullptr; int videoStreamIdx_ = -1; + double fps_ = 0.0; // <-- FPS 저장 변수 추가 }; \ No newline at end of file diff --git a/app/src/main/cpp/Renderer.cpp b/app/src/main/cpp/Renderer.cpp index ff6f3f2d..9552cff8 100644 --- a/app/src/main/cpp/Renderer.cpp +++ b/app/src/main/cpp/Renderer.cpp @@ -22,7 +22,10 @@ Renderer::Renderer() { setAnimationMode(static_cast(AnimationMode::PAN)); setTransitionMode(static_cast(TransitionMode::FADE)); } -Renderer::~Renderer() { release(); } +Renderer::~Renderer() { + stopRenderLoop(); + release(); +} void Renderer::release() { std::lock_guard lock(renderMutex_); currentMedia_.release(); nextMedia_.release(); } void Renderer::setNextMedia(int fd) { preloader_.startNextPreload(fd); } void Renderer::setAnimationSpeed(float speed) { animationSpeed_ = speed > 0 ? speed : 1.0f; } @@ -37,6 +40,48 @@ void Renderer::setTransitionMode(int mode) { configuredTransitionMode_ = (mode >= 0 && mode <= static_cast(TransitionMode::MOSAIC)) ? static_cast(mode) : TransitionMode::FADE; } +void Renderer::startRenderLoop(ANativeWindow* window) { + if (isRunning_) return; // 이미 실행 중이면 무시 + + nativeWindow_ = window; + if (nativeWindow_) { + ANativeWindow_acquire(nativeWindow_); + isRunning_ = true; + renderThread_ = std::thread([this]() { + LOGI("C++ Render thread started."); + while (isRunning_) { + auto frameStartTime = std::chrono::steady_clock::now(); + + renderFrame(nativeWindow_); // 기존의 복잡한 renderFrame 함수를 그대로 호출 + + auto frameEndTime = std::chrono::steady_clock::now(); + auto frameDuration = std::chrono::duration_cast(frameEndTime - frameStartTime); + + // --- ⬇️ 수정: 고정 딜레이 -> 가변 딜레이 ⬇️ --- + // 현재 설정된 목표 딜레이(currentFrameDelay_)에 맞춰 대기 + auto sleepTime = currentFrameDelay_ - frameDuration; + if (sleepTime > std::chrono::milliseconds(0)) { + std::this_thread::sleep_for(sleepTime); + } + } + LOGI("C++ Render thread finished."); + }); + } +} + +void Renderer::stopRenderLoop() { + if (!isRunning_) return; + + isRunning_ = false; + if (renderThread_.joinable()) { + renderThread_.join(); + } + if (nativeWindow_) { + ANativeWindow_release(nativeWindow_); + nativeWindow_ = nullptr; + } +} + void Renderer::determineActiveAnimationMode() { AnimationMode modeToSetActive; if (configuredAnimationMode_ == AnimationMode::RANDOM) { @@ -202,6 +247,14 @@ void Renderer::renderFrame(ANativeWindow* window) { if (!currentMedia_.isValid() && preloader_.isPreloadedDataReady()) { currentMedia_ = preloader_.swapAndRelease(); if (currentMedia_.isValid()) { + // --- ⬇️ 프레임 딜레이 설정 로직 추가 ⬇️ --- + if (currentMedia_.getType() == MediaAsset::Type::VIDEO && currentMedia_.getFps() > 0) { + currentFrameDelay_ = std::chrono::milliseconds(static_cast(1000.0 / currentMedia_.getFps())); + LOGI("Media type: Video. Frame delay set to %lldms for %.2f FPS", currentFrameDelay_.count(), currentMedia_.getFps()); + } else { + currentFrameDelay_ = std::chrono::milliseconds(33); // 이미지일 경우 30fps + LOGI("Media type: Image. Frame delay set to 33ms (~30 FPS)"); + } currentState_ = RenderState::ANIMATING; determineActiveAnimationMode(); if(animationStrategy_) animationStrategy_->reset(); diff --git a/app/src/main/cpp/Renderer.h b/app/src/main/cpp/Renderer.h index a55b8232..9c4a328d 100644 --- a/app/src/main/cpp/Renderer.h +++ b/app/src/main/cpp/Renderer.h @@ -31,6 +31,9 @@ public: void release(); std::string getDebugInfo() const; + void startRenderLoop(ANativeWindow* window); + void stopRenderLoop(); + void setAnimationSpeed(float speed); void setAnimationMode(int mode); void setFadeDuration(int durationMs); @@ -44,6 +47,11 @@ public: float& outScale, float& outOffsetX, float& outOffsetY) const; private: + + std::thread renderThread_; + std::atomic isRunning_{false}; + ANativeWindow* nativeWindow_ = nullptr; + enum class RenderState { ANIMATING, TRANSITIONING }; RenderState currentState_ = RenderState::ANIMATING; @@ -66,6 +74,8 @@ private: long long fadeDurationMs_ = 3000; long long pageTurnDelayMs_ = 5000; float animationSpeed_ = 1.0f; + // --- ⬇️ 목표 프레임 딜레이 변수 추가 ⬇️ --- + std::chrono::milliseconds currentFrameDelay_{33}; // 기본값 33ms (약 30fps) AnimationState getStartStateForMode(AnimationMode mode, int surfaceWidth, int surfaceHeight); AnimationMode predictNextAnimationMode(); diff --git a/app/src/main/cpp/native_renderer.cpp b/app/src/main/cpp/native_renderer.cpp index f9c54805..b9aefaeb 100644 --- a/app/src/main/cpp/native_renderer.cpp +++ b/app/src/main/cpp/native_renderer.cpp @@ -74,6 +74,30 @@ Java_bums_lunatic_launcher_wall_NativeRenderer_nativeDestroy(JNIEnv* env, jobjec } } + +JNIEXPORT void JNICALL +Java_bums_lunatic_launcher_wall_NativeRenderer_nativeStopRenderLoop(JNIEnv* env, jobject, jlong nativeHandle) { + Renderer* renderer = toNative(nativeHandle); + if (renderer) { + renderer->stopRenderLoop(); + } +} + +JNIEXPORT void JNICALL +Java_bums_lunatic_launcher_wall_NativeRenderer_nativeStartRenderLoop(JNIEnv* env, jobject, jlong nativeHandle, jobject surface) { + Renderer* renderer = toNative(nativeHandle); + if (!renderer || !surface) return; + + ANativeWindow* window = ANativeWindow_fromSurface(env, surface); + if (!window) { + LOGE("Could not get ANativeWindow from Surface."); + return; + } + renderer->startRenderLoop(window); + ANativeWindow_release(window); +} + + // [수정] 모든 함수가 nativeHandle을 첫 파라미터로 받도록 변경 JNIEXPORT void JNICALL Java_bums_lunatic_launcher_wall_NativeRenderer_nativeRender(JNIEnv* env, jobject, jlong nativeHandle, jobject surface) { diff --git a/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt index 4cb14fcc..be94bd10 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt @@ -33,24 +33,24 @@ class MyWallpaperService : WallpaperService() { private val syncDelayMs = 250L // 상태 변경을 처리하기 전의 지연 시간 private var frameCount = 0 private val debugLogCheck = 240 - private val renderRunnable = object : Runnable { - override fun run() { - if (!isVisible || !isSurfaceValid) return - - // [수정] nativeRender -> render - nativeRenderer?.render(holder.surface) - - frameCount++ - if (frameCount % debugLogCheck == 0) { - frameCount = 0 - // [수정] nativeGetDebugInfo -> getDebugInfo - val debugInfo = nativeRenderer?.getDebugInfo() ?: "Kotlin nativeRenderer is null" - Log.d(TAG, debugInfo) - } - - handler.postDelayed(this, frameDelayMs) - } - } +// private val renderRunnable = object : Runnable { +// override fun run() { +// if (!isVisible || !isSurfaceValid) return +// +// // [수정] nativeRender -> render +// nativeRenderer?.render(holder.surface) +// +// frameCount++ +// if (frameCount % debugLogCheck == 0) { +// frameCount = 0 +// // [수정] nativeGetDebugInfo -> getDebugInfo +// val debugInfo = nativeRenderer?.getDebugInfo() ?: "Kotlin nativeRenderer is null" +// Log.d(TAG, debugInfo) +// } +// +// handler.postDelayed(this, frameDelayMs) +// } +// } private val destroyRenderer = Runnable { nativeRenderer?.destroy() nativeRenderer = null @@ -161,8 +161,9 @@ class MyWallpaperService : WallpaperService() { private fun startRendering() { if (isVisible && isSurfaceValid) { Log.d(TAG, "startRendering: Conditions met. Starting loop. ${handler}") - handler.removeCallbacks(renderRunnable) - handler.post(renderRunnable) +// handler.removeCallbacks(renderRunnable) +// handler.post(renderRunnable) + nativeRenderer?.startRenderLoop(holder.surface) } else { Log.d(TAG, "startRendering: Conditions not met. Starting loop fail.") } @@ -172,7 +173,7 @@ class MyWallpaperService : WallpaperService() { Log.w(TAG, "stopRendering: Stopping loop.") // 핸들러가 초기화된 경우에만 콜백 제거 시도 if (::handler.isInitialized) { - handler.removeCallbacks(renderRunnable) + nativeRenderer?.stopRenderLoop() } else { Log.w(TAG, "stopRendering: Stopping loop. ${::handler.isInitialized}") } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/wall/NativeRenderer.kt b/app/src/main/kotlin/bums/lunatic/launcher/wall/NativeRenderer.kt index cb8eda51..57e5aa78 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/wall/NativeRenderer.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/wall/NativeRenderer.kt @@ -80,8 +80,25 @@ class NativeRenderer { } } - // --- Private JNI declarations --- + fun startRenderLoop(surface: Surface) { + if (nativeHandle != 0L) { + nativeStartRenderLoop(nativeHandle, surface) + } else { + "Kotlin Wrapper: Native handle is null." + } + } + fun stopRenderLoop() { + if (nativeHandle != 0L) { + nativeStopRenderLoop(nativeHandle) + } else { + "Kotlin Wrapper: Native handle is null." + } + } + + // --- Private JNI declarations --- + private external fun nativeStartRenderLoop(nativeHandle: Long, surface: Surface) + private external fun nativeStopRenderLoop(nativeHandle: Long) private external fun nativeInit(): Long private external fun nativeDestroy(nativeHandle: Long) private external fun nativeRender(nativeHandle: Long, surface: Surface)