...
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");
|
LOGE("Could not find a video stream");
|
||||||
release(); return false;
|
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);
|
codecCtx_ = avcodec_alloc_context3(codec);
|
||||||
if (!codecCtx_ || avcodec_parameters_to_context(codecCtx_, fmtCtx_->streams[videoStreamIdx_]->codecpar) < 0) {
|
if (!codecCtx_ || avcodec_parameters_to_context(codecCtx_, fmtCtx_->streams[videoStreamIdx_]->codecpar) < 0) {
|
||||||
LOGE("Failed to create codec context");
|
LOGE("Failed to create codec context");
|
||||||
@ -244,6 +251,14 @@ bool MediaAsset::loadVideoWithFFmpeg(const std::string& path) {
|
|||||||
const AVCodec* codec = nullptr;
|
const AVCodec* codec = nullptr;
|
||||||
videoStreamIdx_ = av_find_best_stream(fmtCtx_, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
videoStreamIdx_ = av_find_best_stream(fmtCtx_, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
||||||
if (videoStreamIdx_ < 0) { release(); return false; }
|
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);
|
codecCtx_ = avcodec_alloc_context3(codec);
|
||||||
if (!codecCtx_ || avcodec_parameters_to_context(codecCtx_, fmtCtx_->streams[videoStreamIdx_]->codecpar) < 0) { release(); return false; }
|
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; }
|
if (avcodec_open2(codecCtx_, codec, nullptr) < 0) { release(); return false; }
|
||||||
|
|||||||
@ -51,6 +51,7 @@ public:
|
|||||||
AVPacket* getPacket() const { return packet_; }
|
AVPacket* getPacket() const { return packet_; }
|
||||||
SwsContext* getSwsContext() const { return swsCtx_; }
|
SwsContext* getSwsContext() const { return swsCtx_; }
|
||||||
int getVideoStreamIndex() const { return videoStreamIdx_; }
|
int getVideoStreamIndex() const { return videoStreamIdx_; }
|
||||||
|
double getFps() const { return fps_; } // <-- FPS getter 추가
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool loadInternal(const std::string& path);
|
bool loadInternal(const std::string& path);
|
||||||
@ -72,4 +73,5 @@ private:
|
|||||||
AVPacket* packet_ = nullptr;
|
AVPacket* packet_ = nullptr;
|
||||||
SwsContext* swsCtx_ = nullptr;
|
SwsContext* swsCtx_ = nullptr;
|
||||||
int videoStreamIdx_ = -1;
|
int videoStreamIdx_ = -1;
|
||||||
|
double fps_ = 0.0; // <-- FPS 저장 변수 추가
|
||||||
};
|
};
|
||||||
@ -22,7 +22,10 @@ Renderer::Renderer() {
|
|||||||
setAnimationMode(static_cast<int>(AnimationMode::PAN));
|
setAnimationMode(static_cast<int>(AnimationMode::PAN));
|
||||||
setTransitionMode(static_cast<int>(TransitionMode::FADE));
|
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::release() { std::lock_guard<std::mutex> lock(renderMutex_); currentMedia_.release(); nextMedia_.release(); }
|
||||||
void Renderer::setNextMedia(int fd) { preloader_.startNextPreload(fd); }
|
void Renderer::setNextMedia(int fd) { preloader_.startNextPreload(fd); }
|
||||||
void Renderer::setAnimationSpeed(float speed) { animationSpeed_ = speed > 0 ? speed : 1.0f; }
|
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;
|
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() {
|
void Renderer::determineActiveAnimationMode() {
|
||||||
AnimationMode modeToSetActive;
|
AnimationMode modeToSetActive;
|
||||||
if (configuredAnimationMode_ == AnimationMode::RANDOM) {
|
if (configuredAnimationMode_ == AnimationMode::RANDOM) {
|
||||||
@ -202,6 +247,14 @@ void Renderer::renderFrame(ANativeWindow* window) {
|
|||||||
if (!currentMedia_.isValid() && preloader_.isPreloadedDataReady()) {
|
if (!currentMedia_.isValid() && preloader_.isPreloadedDataReady()) {
|
||||||
currentMedia_ = preloader_.swapAndRelease();
|
currentMedia_ = preloader_.swapAndRelease();
|
||||||
if (currentMedia_.isValid()) {
|
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;
|
currentState_ = RenderState::ANIMATING;
|
||||||
determineActiveAnimationMode();
|
determineActiveAnimationMode();
|
||||||
if(animationStrategy_) animationStrategy_->reset();
|
if(animationStrategy_) animationStrategy_->reset();
|
||||||
|
|||||||
@ -31,6 +31,9 @@ public:
|
|||||||
void release();
|
void release();
|
||||||
std::string getDebugInfo() const;
|
std::string getDebugInfo() const;
|
||||||
|
|
||||||
|
void startRenderLoop(ANativeWindow* window);
|
||||||
|
void stopRenderLoop();
|
||||||
|
|
||||||
void setAnimationSpeed(float speed);
|
void setAnimationSpeed(float speed);
|
||||||
void setAnimationMode(int mode);
|
void setAnimationMode(int mode);
|
||||||
void setFadeDuration(int durationMs);
|
void setFadeDuration(int durationMs);
|
||||||
@ -44,6 +47,11 @@ public:
|
|||||||
float& outScale, float& outOffsetX, float& outOffsetY) const;
|
float& outScale, float& outOffsetX, float& outOffsetY) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
std::thread renderThread_;
|
||||||
|
std::atomic<bool> isRunning_{false};
|
||||||
|
ANativeWindow* nativeWindow_ = nullptr;
|
||||||
|
|
||||||
enum class RenderState { ANIMATING, TRANSITIONING };
|
enum class RenderState { ANIMATING, TRANSITIONING };
|
||||||
RenderState currentState_ = RenderState::ANIMATING;
|
RenderState currentState_ = RenderState::ANIMATING;
|
||||||
|
|
||||||
@ -66,6 +74,8 @@ private:
|
|||||||
long long fadeDurationMs_ = 3000;
|
long long fadeDurationMs_ = 3000;
|
||||||
long long pageTurnDelayMs_ = 5000;
|
long long pageTurnDelayMs_ = 5000;
|
||||||
float animationSpeed_ = 1.0f;
|
float animationSpeed_ = 1.0f;
|
||||||
|
// --- ⬇️ 목표 프레임 딜레이 변수 추가 ⬇️ ---
|
||||||
|
std::chrono::milliseconds currentFrameDelay_{33}; // 기본값 33ms (약 30fps)
|
||||||
|
|
||||||
AnimationState getStartStateForMode(AnimationMode mode, int surfaceWidth, int surfaceHeight);
|
AnimationState getStartStateForMode(AnimationMode mode, int surfaceWidth, int surfaceHeight);
|
||||||
AnimationMode predictNextAnimationMode();
|
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을 첫 파라미터로 받도록 변경
|
// [수정] 모든 함수가 nativeHandle을 첫 파라미터로 받도록 변경
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeRender(JNIEnv* env, jobject, jlong nativeHandle, jobject surface) {
|
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 val syncDelayMs = 250L // 상태 변경을 처리하기 전의 지연 시간
|
||||||
private var frameCount = 0
|
private var frameCount = 0
|
||||||
private val debugLogCheck = 240
|
private val debugLogCheck = 240
|
||||||
private val renderRunnable = object : Runnable {
|
// private val renderRunnable = object : Runnable {
|
||||||
override fun run() {
|
// override fun run() {
|
||||||
if (!isVisible || !isSurfaceValid) return
|
// if (!isVisible || !isSurfaceValid) return
|
||||||
|
//
|
||||||
// [수정] nativeRender -> render
|
// // [수정] nativeRender -> render
|
||||||
nativeRenderer?.render(holder.surface)
|
// nativeRenderer?.render(holder.surface)
|
||||||
|
//
|
||||||
frameCount++
|
// frameCount++
|
||||||
if (frameCount % debugLogCheck == 0) {
|
// if (frameCount % debugLogCheck == 0) {
|
||||||
frameCount = 0
|
// frameCount = 0
|
||||||
// [수정] nativeGetDebugInfo -> getDebugInfo
|
// // [수정] nativeGetDebugInfo -> getDebugInfo
|
||||||
val debugInfo = nativeRenderer?.getDebugInfo() ?: "Kotlin nativeRenderer is null"
|
// val debugInfo = nativeRenderer?.getDebugInfo() ?: "Kotlin nativeRenderer is null"
|
||||||
Log.d(TAG, debugInfo)
|
// Log.d(TAG, debugInfo)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
handler.postDelayed(this, frameDelayMs)
|
// handler.postDelayed(this, frameDelayMs)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
private val destroyRenderer = Runnable {
|
private val destroyRenderer = Runnable {
|
||||||
nativeRenderer?.destroy()
|
nativeRenderer?.destroy()
|
||||||
nativeRenderer = null
|
nativeRenderer = null
|
||||||
@ -161,8 +161,9 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
private fun startRendering() {
|
private fun startRendering() {
|
||||||
if (isVisible && isSurfaceValid) {
|
if (isVisible && isSurfaceValid) {
|
||||||
Log.d(TAG, "startRendering: Conditions met. Starting loop. ${handler}")
|
Log.d(TAG, "startRendering: Conditions met. Starting loop. ${handler}")
|
||||||
handler.removeCallbacks(renderRunnable)
|
// handler.removeCallbacks(renderRunnable)
|
||||||
handler.post(renderRunnable)
|
// handler.post(renderRunnable)
|
||||||
|
nativeRenderer?.startRenderLoop(holder.surface)
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "startRendering: Conditions not met. Starting loop fail.")
|
Log.d(TAG, "startRendering: Conditions not met. Starting loop fail.")
|
||||||
}
|
}
|
||||||
@ -172,7 +173,7 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
Log.w(TAG, "stopRendering: Stopping loop.")
|
Log.w(TAG, "stopRendering: Stopping loop.")
|
||||||
// 핸들러가 초기화된 경우에만 콜백 제거 시도
|
// 핸들러가 초기화된 경우에만 콜백 제거 시도
|
||||||
if (::handler.isInitialized) {
|
if (::handler.isInitialized) {
|
||||||
handler.removeCallbacks(renderRunnable)
|
nativeRenderer?.stopRenderLoop()
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "stopRendering: Stopping loop. ${::handler.isInitialized}")
|
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 nativeInit(): Long
|
||||||
private external fun nativeDestroy(nativeHandle: Long)
|
private external fun nativeDestroy(nativeHandle: Long)
|
||||||
private external fun nativeRender(nativeHandle: Long, surface: Surface)
|
private external fun nativeRender(nativeHandle: Long, surface: Surface)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user