...
This commit is contained in:
parent
03086981e5
commit
32e38fda59
@ -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; }
|
||||
|
||||
@ -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 저장 변수 추가
|
||||
};
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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}")
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user