This commit is contained in:
lunaticbum 2025-08-29 18:01:53 +09:00
parent 03086981e5
commit 32e38fda59
7 changed files with 145 additions and 23 deletions

View File

@ -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; }

View File

@ -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 저장 변수 추가
};

View File

@ -22,7 +22,10 @@ Renderer::Renderer() {
setAnimationMode(static_cast<int>(AnimationMode::PAN));
setTransitionMode(static_cast<int>(TransitionMode::FADE));
}
Renderer::~Renderer() { release(); }
Renderer::~Renderer() {
stopRenderLoop();
release();
}
void Renderer::release() { std::lock_guard<std::mutex> 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<int>(TransitionMode::MOSAIC)) ? static_cast<TransitionMode>(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<std::chrono::milliseconds>(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<long long>(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();

View File

@ -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<bool> 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();

View File

@ -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<Renderer>(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<Renderer>(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) {

View File

@ -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}")
}

View File

@ -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)