...
This commit is contained in:
@@ -1,8 +1,59 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.10.2)
|
||||
project("native_renderer")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(native-renderer SHARED native_renderer.cpp)
|
||||
project(native_renderer)
|
||||
|
||||
# 기본 로그 및 안드로이드 라이브러리
|
||||
find_library(log-lib log)
|
||||
find_library(android-lib android)
|
||||
|
||||
target_link_libraries(native-renderer ${log-lib} android)
|
||||
# ABI에 따라 jniLibs 경로 지정 (예: arm64-v8a, armeabi-v7a 등)
|
||||
set(FFMPEG_LIB_DIR ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
|
||||
message(STATUS "FFMPEG_LIB_DIR: ${FFMPEG_LIB_DIR}")
|
||||
|
||||
|
||||
# FFmpeg 헤더 경로 추가
|
||||
include_directories(${FFMPEG_LIB_DIR}/include)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/include) # stb_image.h가 이 경로에 있다면
|
||||
|
||||
# FFmpeg 라이브러리들 임포트 선언 및 위치 지정
|
||||
add_library(avformat SHARED IMPORTED)
|
||||
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION
|
||||
${FFMPEG_LIB_DIR}/libavformat.so)
|
||||
|
||||
add_library(avcodec SHARED IMPORTED)
|
||||
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION
|
||||
${FFMPEG_LIB_DIR}/libavcodec.so)
|
||||
|
||||
add_library(avutil SHARED IMPORTED)
|
||||
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION
|
||||
${FFMPEG_LIB_DIR}/libavutil.so)
|
||||
|
||||
add_library(swscale SHARED IMPORTED)
|
||||
set_target_properties(swscale PROPERTIES IMPORTED_LOCATION
|
||||
${FFMPEG_LIB_DIR}/libswscale.so)
|
||||
|
||||
add_library(swresample SHARED IMPORTED)
|
||||
set_target_properties(swresample PROPERTIES IMPORTED_LOCATION
|
||||
${FFMPEG_LIB_DIR}/libswresample.so)
|
||||
|
||||
add_library(avfilter SHARED IMPORTED)
|
||||
set_target_properties(avfilter PROPERTIES IMPORTED_LOCATION
|
||||
${FFMPEG_LIB_DIR}/libavfilter.so)
|
||||
|
||||
# 네이티브 렌더러 라이브러리 빌드
|
||||
add_library(native_renderer SHARED native_renderer.cpp) # 실제 소스파일명 입력
|
||||
|
||||
# 링크할 라이브러리 지정
|
||||
target_link_libraries(native_renderer
|
||||
avformat
|
||||
avcodec
|
||||
avutil
|
||||
swscale
|
||||
swresample
|
||||
avfilter
|
||||
${log-lib}
|
||||
${android-lib}
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,82 +1,697 @@
|
||||
#include <jni.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
#define LOG_TAG "NativeRenderer"
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
static ANativeWindow* window = nullptr;
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
// 비디오 변수
|
||||
static AVFormatContext* fmt_ctx = nullptr;
|
||||
static AVCodecContext* codec_ctx = nullptr;
|
||||
static AVFrame* frame = nullptr;
|
||||
static AVPacket* pkt = nullptr;
|
||||
static SwsContext* sws_ctx = nullptr;
|
||||
static int video_stream_idx = -1;
|
||||
static int videoWidth = 0;
|
||||
static int videoHeight = 0;
|
||||
static std::vector<uint8_t> rgbBuffer;
|
||||
|
||||
// 이미지 변수
|
||||
static uint8_t* imageData = nullptr;
|
||||
static int imageWidth = 0;
|
||||
static int imageHeight = 0;
|
||||
static int imageChannels = 0;
|
||||
static bool isImage = false;
|
||||
|
||||
// 다음 미디어 변수
|
||||
static AVFormatContext* next_fmt_ctx = nullptr;
|
||||
static AVCodecContext* next_codec_ctx = nullptr;
|
||||
static AVFrame* next_frame = nullptr;
|
||||
static AVPacket* next_pkt = nullptr;
|
||||
static SwsContext* next_sws_ctx = nullptr;
|
||||
static int next_video_stream_idx = -1;
|
||||
static int next_videoWidth = 0;
|
||||
static int next_videoHeight = 0;
|
||||
static std::vector<uint8_t> next_rgbBuffer;
|
||||
|
||||
static uint8_t* next_imageData = nullptr;
|
||||
static int next_imageWidth = 0;
|
||||
static int next_imageHeight = 0;
|
||||
static int next_imageChannels = 0;
|
||||
static bool nextIsImage = false;
|
||||
|
||||
static bool nextMediaReady = false;
|
||||
|
||||
static std::mutex renderMutex;
|
||||
|
||||
static constexpr float frameDurationMs = 16.0f;
|
||||
static constexpr long long displayDurationMs = 20000;
|
||||
static constexpr long long fadeDurationMs = 3000;
|
||||
|
||||
static std::vector<std::string> mediaPaths;
|
||||
static int currentMediaIndex = 0;
|
||||
static int nextMediaIndex = 1;
|
||||
|
||||
// 애니메이션 변수
|
||||
static float offsetX = 0.f;
|
||||
static float offsetY = 0.f;
|
||||
static bool movingForwardLocX = true;
|
||||
static bool movingDownLocY = true;
|
||||
|
||||
// 페이드 및 미디어 전환 시간 상태
|
||||
static std::chrono::steady_clock::time_point mediaStartTime;
|
||||
static std::chrono::steady_clock::time_point fadeStartTime;
|
||||
static bool isFading = false;
|
||||
|
||||
// 페이드 알파값
|
||||
static float fadeOutAlpha = 1.f;
|
||||
static float fadeInAlpha = 0.f;
|
||||
|
||||
|
||||
// ==================== 메모리 해제: 이미지 ====================
|
||||
static void releaseImageData(uint8_t** data) {
|
||||
if (*data) {
|
||||
stbi_image_free(*data);
|
||||
*data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 메모리 해제: FFmpeg 컨텍스트 ====================
|
||||
static void releaseFFmpegContext(
|
||||
AVFormatContext** fctx, AVCodecContext** cctx,
|
||||
AVFrame** frm, AVPacket** pck,
|
||||
SwsContext** sws, std::vector<uint8_t>* buffer) {
|
||||
if (*cctx) avcodec_free_context(cctx);
|
||||
if (*fctx) avformat_close_input(fctx);
|
||||
if (*frm) av_frame_free(frm);
|
||||
if (*pck) av_packet_free(pck);
|
||||
if (*sws) sws_freeContext(*sws);
|
||||
if (buffer) buffer->clear();
|
||||
|
||||
*cctx = nullptr;
|
||||
*fctx = nullptr;
|
||||
*frm = nullptr;
|
||||
*pck = nullptr;
|
||||
*sws = nullptr;
|
||||
}
|
||||
|
||||
// ==================== 미디어 데이터 해제 ====================
|
||||
static void releaseMediaData(bool loadIsImage,
|
||||
uint8_t** imgData,
|
||||
AVFormatContext** fmtCtx, AVCodecContext** codecCtx,
|
||||
AVFrame** frm, AVPacket** pck, SwsContext** sws,
|
||||
std::vector<uint8_t>* rgbBuf) {
|
||||
if (loadIsImage) {
|
||||
releaseImageData(imgData);
|
||||
} else {
|
||||
releaseFFmpegContext(fmtCtx, codecCtx, frm, pck, sws, rgbBuf);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 스케일 계산 구조체 및 함수 ====================
|
||||
struct ScaleResult {
|
||||
float scale;
|
||||
float scaledW;
|
||||
float scaledH;
|
||||
float overflowX;
|
||||
float overflowY;
|
||||
};
|
||||
|
||||
static ScaleResult calculateScale(float mediaW, float mediaH, float bufW, float bufH) {
|
||||
ScaleResult res{};
|
||||
if ((mediaW / mediaH) > (bufW / bufH)) {
|
||||
res.scale = bufH / mediaH;
|
||||
res.scaledW = mediaW * res.scale;
|
||||
res.scaledH = bufH;
|
||||
} else {
|
||||
res.scale = bufW / mediaW;
|
||||
res.scaledW = bufW;
|
||||
res.scaledH = mediaH * res.scale;
|
||||
}
|
||||
res.overflowX = std::max(0.f, res.scaledW - bufW);
|
||||
res.overflowY = std::max(0.f, res.scaledH - bufH);
|
||||
return res;
|
||||
}
|
||||
|
||||
// ==================== 오프셋 애니메이션 업데이트 ====================
|
||||
static void updateOffset(float& offsetX, float& offsetY,
|
||||
bool& movingX, bool& movingY,
|
||||
float overflowX, float overflowY) {
|
||||
if (overflowX > 0) {
|
||||
float speedX = overflowX / displayDurationMs;
|
||||
float deltaX = speedX * frameDurationMs;
|
||||
if (movingX) {
|
||||
offsetX += deltaX;
|
||||
if (offsetX >= overflowX) {
|
||||
offsetX = overflowX;
|
||||
movingX = false;
|
||||
}
|
||||
} else {
|
||||
offsetX -= deltaX;
|
||||
if (offsetX <= 0) {
|
||||
offsetX = 0.f;
|
||||
movingX = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
offsetX = 0.f;
|
||||
}
|
||||
|
||||
if (overflowY > 0) {
|
||||
float speedY = overflowY / displayDurationMs;
|
||||
float deltaY = speedY * frameDurationMs;
|
||||
if (movingY) {
|
||||
offsetY += deltaY;
|
||||
if (offsetY >= overflowY) {
|
||||
offsetY = overflowY;
|
||||
movingY = false;
|
||||
}
|
||||
} else {
|
||||
offsetY -= deltaY;
|
||||
if (offsetY <= 0) {
|
||||
offsetY = 0.f;
|
||||
movingY = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
offsetY = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 버퍼 클리어 함수 ====================
|
||||
static void clearBufferIfNeeded(ANativeWindow_Buffer& buffer, bool shouldClear) {
|
||||
if (!shouldClear) return;
|
||||
uint32_t* dstPixels = (uint32_t*)buffer.bits;
|
||||
int dstStride = buffer.stride;
|
||||
for (int y = 0; y < buffer.height; ++y) {
|
||||
uint32_t* dstRow = dstPixels + y * dstStride;
|
||||
for (int x = 0; x < buffer.width; ++x) {
|
||||
dstRow[x] = 0x00000000; // 완전 투명 또는 검은색
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 픽셀 그리기: 이미지 및 비디오 프레임 ====================
|
||||
static void drawToBuffer(ANativeWindow_Buffer& buffer,
|
||||
uint8_t* pixelData, int imgW, int imgH,
|
||||
float scale, float offsetX, float offsetY, float alpha) {
|
||||
if (alpha <= 0.f) return;
|
||||
alpha = std::clamp(alpha, 0.f, 1.f);
|
||||
|
||||
uint32_t* dstPixels = (uint32_t*)buffer.bits;
|
||||
int dstStride = buffer.stride;
|
||||
|
||||
for (int y = 0; y < buffer.height; ++y) {
|
||||
int srcY = (int)((y + offsetY) / scale);
|
||||
if (srcY < 0 || srcY >= imgH) continue;
|
||||
|
||||
uint32_t* dstRow = dstPixels + y * dstStride;
|
||||
for (int x = 0; x < buffer.width; ++x) {
|
||||
int srcX = (int)((x + offsetX) / scale);
|
||||
if (srcX < 0 || srcX >= imgW) continue;
|
||||
|
||||
uint8_t* px = &pixelData[(srcY * imgW + srcX) * 4];
|
||||
uint8_t r = (uint8_t)(px[0] * alpha);
|
||||
uint8_t g = (uint8_t)(px[1] * alpha);
|
||||
uint8_t b = (uint8_t)(px[2] * alpha);
|
||||
uint8_t a = (uint8_t)(px[3] * alpha);
|
||||
|
||||
dstRow[x] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 미디어 로딩 함수(이미지/비디오) ====================
|
||||
static bool loadMedia(const std::string& path, bool loadIsImage,
|
||||
uint8_t** imgData, int* imgW, int* imgH, int* imgCh,
|
||||
AVFormatContext** fmtCtx, AVCodecContext** codecCtx, AVFrame** frm, AVPacket** pck,
|
||||
SwsContext** sws, int* videoIdx, int* vidW, int* vidH,
|
||||
std::vector<uint8_t>* rgbBuf) {
|
||||
try {
|
||||
if (!loadIsImage) {
|
||||
*fmtCtx = avformat_alloc_context();
|
||||
if (avformat_open_input(fmtCtx, path.c_str(), nullptr, nullptr) != 0) {
|
||||
LOGE("Failed to open video: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
if (avformat_find_stream_info(*fmtCtx, nullptr) < 0) {
|
||||
LOGE("Failed to get stream info: %s", path.c_str());
|
||||
avformat_close_input(fmtCtx);
|
||||
return false;
|
||||
}
|
||||
|
||||
*videoIdx = -1;
|
||||
for (unsigned int i = 0; i < (*fmtCtx)->nb_streams; ++i) {
|
||||
if ((*fmtCtx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
*videoIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*videoIdx == -1) {
|
||||
LOGE("No video stream found: %s", path.c_str());
|
||||
avformat_close_input(fmtCtx);
|
||||
return false;
|
||||
}
|
||||
|
||||
AVCodecParameters* codecpar = (*fmtCtx)->streams[*videoIdx]->codecpar;
|
||||
const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);
|
||||
if (!codec) {
|
||||
LOGE("Decoder not found");
|
||||
avformat_close_input(fmtCtx);
|
||||
return false;
|
||||
}
|
||||
|
||||
*codecCtx = avcodec_alloc_context3(codec);
|
||||
if (!*codecCtx) {
|
||||
LOGE("Failed to alloc codec context");
|
||||
avformat_close_input(fmtCtx);
|
||||
return false;
|
||||
}
|
||||
if (avcodec_parameters_to_context(*codecCtx, codecpar) < 0) {
|
||||
LOGE("Failed to copy codec params");
|
||||
avcodec_free_context(codecCtx);
|
||||
avformat_close_input(fmtCtx);
|
||||
return false;
|
||||
}
|
||||
if (avcodec_open2(*codecCtx, codec, nullptr) < 0) {
|
||||
LOGE("Failed to open codec");
|
||||
avcodec_free_context(codecCtx);
|
||||
avformat_close_input(fmtCtx);
|
||||
return false;
|
||||
}
|
||||
*vidW = (*codecCtx)->width;
|
||||
*vidH = (*codecCtx)->height;
|
||||
|
||||
*frm = av_frame_alloc();
|
||||
*pck = av_packet_alloc();
|
||||
*sws = sws_getContext(*vidW, *vidH, (*codecCtx)->pix_fmt, *vidW, *vidH,
|
||||
AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr);
|
||||
|
||||
if (!*sws) {
|
||||
LOGE("Failed to create sws context");
|
||||
av_frame_free(frm);
|
||||
av_packet_free(pck);
|
||||
avcodec_free_context(codecCtx);
|
||||
avformat_close_input(fmtCtx);
|
||||
return false;
|
||||
}
|
||||
|
||||
rgbBuf->resize((*vidW) * (*vidH) * 4);
|
||||
*imgData = nullptr;
|
||||
*imgW = 0;
|
||||
*imgH = 0;
|
||||
*imgCh = 0;
|
||||
} else {
|
||||
*imgData = stbi_load(path.c_str(), imgW, imgH, imgCh, 4);
|
||||
if (!*imgData) {
|
||||
LOGE("Failed to load image: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
*fmtCtx = nullptr;
|
||||
*codecCtx = nullptr;
|
||||
*frm = nullptr;
|
||||
*pck = nullptr;
|
||||
*sws = nullptr;
|
||||
*videoIdx = -1;
|
||||
*vidW = 0;
|
||||
*vidH = 0;
|
||||
rgbBuf->clear();
|
||||
}
|
||||
LOGI("Successfully loaded media: %s", path.c_str());
|
||||
return true;
|
||||
} catch (...) {
|
||||
LOGE("Exception occurred during media loading: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 다음 미디어 비동기 로드 ====================
|
||||
static bool loadNextMedia() {
|
||||
LOGI("loadNextMedia: Trying to load media index %d", nextMediaIndex);
|
||||
|
||||
releaseMediaData(nextIsImage, &next_imageData, &next_fmt_ctx, &next_codec_ctx,
|
||||
&next_frame, &next_pkt, &next_sws_ctx, &next_rgbBuffer);
|
||||
|
||||
if (mediaPaths.empty()) {
|
||||
LOGE("loadNextMedia: mediaPaths is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& nextPath = mediaPaths[nextMediaIndex];
|
||||
LOGI("loadNextMedia: nextPath=%s", nextPath.c_str());
|
||||
|
||||
nextIsImage = (nextPath.find(".mp4") == std::string::npos &&
|
||||
nextPath.find(".mkv") == std::string::npos);
|
||||
|
||||
bool ok = loadMedia(nextPath, nextIsImage,
|
||||
&next_imageData, &next_imageWidth, &next_imageHeight, &next_imageChannels,
|
||||
&next_fmt_ctx, &next_codec_ctx, &next_frame, &next_pkt, &next_sws_ctx,
|
||||
&next_video_stream_idx, &next_videoWidth, &next_videoHeight, &next_rgbBuffer);
|
||||
if (!ok) {
|
||||
LOGE("loadNextMedia: Failed to load media %s", nextPath.c_str());
|
||||
} else {
|
||||
LOGI("loadNextMedia: Successfully loaded media");
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ==================== 비디오/이미지 렌더링 ====================
|
||||
static void renderMedia(ANativeWindow_Buffer& buffer,
|
||||
uint8_t* imgData, int imgW, int imgH, int imgCh,
|
||||
AVFormatContext* fctx, AVCodecContext* cctx,
|
||||
AVFrame* frm, AVPacket* pck, SwsContext* sws,
|
||||
int vidStreamIdx, int vidW, int vidH,
|
||||
std::vector<uint8_t>& rgbBuf,
|
||||
bool isImageLocal,
|
||||
float scale,
|
||||
float offsetXLocal,
|
||||
float offsetYLocal,
|
||||
float alpha) {
|
||||
|
||||
if (isImageLocal) {
|
||||
drawToBuffer(buffer, imgData, imgW, imgH, scale, offsetXLocal, offsetYLocal, alpha);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fctx || !cctx) return;
|
||||
|
||||
int ret = av_read_frame(fctx, pck);
|
||||
bool gotFrame = false;
|
||||
while (ret >= 0) {
|
||||
if (pck->stream_index == vidStreamIdx) {
|
||||
ret = avcodec_send_packet(cctx, pck);
|
||||
if (ret < 0) break;
|
||||
ret = avcodec_receive_frame(cctx, frm);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
av_packet_unref(pck);
|
||||
ret = av_read_frame(fctx, pck);
|
||||
continue;
|
||||
} else if (ret < 0) break;
|
||||
|
||||
uint8_t* dst[4] = { rgbBuf.data(), nullptr, nullptr, nullptr };
|
||||
int dstStride_arr[4] = { vidW * 4, 0, 0, 0 };
|
||||
sws_scale(sws, frm->data, frm->linesize, 0, vidH, dst, dstStride_arr);
|
||||
|
||||
gotFrame = true;
|
||||
break;
|
||||
}
|
||||
av_packet_unref(pck);
|
||||
ret = av_read_frame(fctx, pck);
|
||||
}
|
||||
av_packet_unref(pck);
|
||||
|
||||
if (!gotFrame) {
|
||||
av_seek_frame(fctx, vidStreamIdx, 0, AVSEEK_FLAG_BACKWARD);
|
||||
return;
|
||||
}
|
||||
|
||||
drawToBuffer(buffer, rgbBuf.data(), vidW, vidH, scale, offsetXLocal, offsetYLocal, alpha);
|
||||
}
|
||||
|
||||
// ==================== 페이드 인/아웃 크로스렌더링 ====================
|
||||
static void renderWithFade(ANativeWindow_Buffer& buffer,
|
||||
float bufW, float bufH,
|
||||
uint8_t* curImgData, int curImgW, int curImgH, int curImgCh,
|
||||
AVFormatContext* curFmtCtx, AVCodecContext* curCodecCtx,
|
||||
AVFrame* curFrame, AVPacket* curPkt, SwsContext* curSwsCtx,
|
||||
int curVidStreamIdx, int curVidW, int curVidH,
|
||||
std::vector<uint8_t>& curRgbBuf,
|
||||
bool curIsImage,
|
||||
uint8_t* nextImgData, int nextImgW, int nextImgH, int nextImgCh,
|
||||
AVFormatContext* nextFmtCtx, AVCodecContext* nextCodecCtx,
|
||||
AVFrame* nextFrame, AVPacket* nextPkt, SwsContext* nextSwsCtx,
|
||||
int nextVidStreamIdx, int nextVidW, int nextVidH,
|
||||
std::vector<uint8_t>& nextRgbBuf,
|
||||
bool nextIsImage,
|
||||
float fadeOutAlpha, float fadeInAlpha,
|
||||
float& curOffsetX, float& curOffsetY,
|
||||
bool& curMovingX, bool& curMovingY,
|
||||
float& nextOffsetX, float& nextOffsetY,
|
||||
bool& nextMovingX, bool& nextMovingY) {
|
||||
|
||||
auto curScaleRes = calculateScale(
|
||||
curIsImage ? (float)curImgW : (float)curVidW,
|
||||
curIsImage ? (float)curImgH : (float)curVidH,
|
||||
bufW, bufH);
|
||||
|
||||
auto nextScaleRes = calculateScale(
|
||||
nextIsImage ? (float)nextImgW : (float)nextVidW,
|
||||
nextIsImage ? (float)nextImgH : (float)nextVidH,
|
||||
bufW, bufH);
|
||||
|
||||
updateOffset(curOffsetX, curOffsetY, curMovingX, curMovingY, curScaleRes.overflowX, curScaleRes.overflowY);
|
||||
updateOffset(nextOffsetX, nextOffsetY, nextMovingX, nextMovingY, nextScaleRes.overflowX, nextScaleRes.overflowY);
|
||||
|
||||
if (curIsImage) {
|
||||
drawToBuffer(buffer, curImgData, curImgW, curImgH,
|
||||
curScaleRes.scale, curOffsetX, curOffsetY, fadeOutAlpha);
|
||||
} else {
|
||||
renderMedia(buffer, curImgData, curImgW, curImgH, curImgCh,
|
||||
curFmtCtx, curCodecCtx, curFrame, curPkt, curSwsCtx,
|
||||
curVidStreamIdx, curVidW, curVidH, curRgbBuf,
|
||||
false,
|
||||
curScaleRes.scale, curOffsetX, curOffsetY,
|
||||
fadeOutAlpha);
|
||||
}
|
||||
|
||||
if (nextIsImage) {
|
||||
drawToBuffer(buffer, nextImgData, nextImgW, nextImgH,
|
||||
nextScaleRes.scale, nextOffsetX, nextOffsetY, fadeInAlpha);
|
||||
} else {
|
||||
renderMedia(buffer, nextImgData, nextImgW, nextImgH, nextImgCh,
|
||||
nextFmtCtx, nextCodecCtx, nextFrame, nextPkt, nextSwsCtx,
|
||||
nextVidStreamIdx, nextVidW, nextVidH, nextRgbBuf,
|
||||
false,
|
||||
nextScaleRes.scale, nextOffsetX, nextOffsetY,
|
||||
fadeInAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== JNI 함수: 미디어 리스트 세팅 ====================
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeSetMediaList(JNIEnv* env, jobject, jobjectArray paths) {
|
||||
std::lock_guard<std::mutex> lock(renderMutex);
|
||||
mediaPaths.clear();
|
||||
jsize len = env->GetArrayLength(paths);
|
||||
for (jsize i = 0; i < len; ++i) {
|
||||
jstring pathStr = (jstring) env->GetObjectArrayElement(paths, i);
|
||||
const char* pathCStr = env->GetStringUTFChars(pathStr, nullptr);
|
||||
mediaPaths.push_back(std::string(pathCStr));
|
||||
env->ReleaseStringUTFChars(pathStr, pathCStr);
|
||||
env->DeleteLocalRef(pathStr);
|
||||
}
|
||||
currentMediaIndex = 0;
|
||||
nextMediaIndex = (len > 1) ? 1 : 0;
|
||||
|
||||
releaseMediaData(isImage, &imageData, &fmt_ctx, &codec_ctx, &frame, &pkt, &sws_ctx, &rgbBuffer);
|
||||
isImage = false;
|
||||
|
||||
if (!mediaPaths.empty()) {
|
||||
const bool loadIsImage = (mediaPaths[0].find(".mp4") == std::string::npos &&
|
||||
mediaPaths[0].find(".mkv") == std::string::npos);
|
||||
isImage = loadIsImage;
|
||||
|
||||
if (!loadMedia(mediaPaths[0], loadIsImage,
|
||||
&imageData, &imageWidth, &imageHeight, &imageChannels,
|
||||
&fmt_ctx, &codec_ctx, &frame, &pkt, &sws_ctx,
|
||||
&video_stream_idx, &videoWidth, &videoHeight, &rgbBuffer)) {
|
||||
LOGE("Failed to load the first media");
|
||||
return;
|
||||
}
|
||||
|
||||
offsetX = 0.f; offsetY = 0.f;
|
||||
movingForwardLocX = true; movingDownLocY = true;
|
||||
mediaStartTime = std::chrono::steady_clock::now();
|
||||
isFading = false;
|
||||
nextMediaReady = false;
|
||||
|
||||
releaseMediaData(nextIsImage, &next_imageData, &next_fmt_ctx, &next_codec_ctx,
|
||||
&next_frame, &next_pkt, &next_sws_ctx, &next_rgbBuffer);
|
||||
|
||||
std::thread([](){
|
||||
std::lock_guard<std::mutex> preloadLock(renderMutex);
|
||||
nextMediaReady = loadNextMedia();
|
||||
if (nextMediaReady) {
|
||||
LOGI("Preloaded next media ready");
|
||||
} else {
|
||||
LOGE("Preload failed");
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeRender(JNIEnv* env, jobject) {
|
||||
std::lock_guard<std::mutex> lock(renderMutex);
|
||||
if (!window || mediaPaths.empty()) {
|
||||
LOGI("nativeRender: no window or empty media");
|
||||
return;
|
||||
}
|
||||
|
||||
ANativeWindow_Buffer buffer;
|
||||
if (ANativeWindow_lock(window, &buffer, nullptr) < 0) {
|
||||
LOGE("nativeRender: Failed to lock window");
|
||||
return;
|
||||
}
|
||||
|
||||
clearBufferIfNeeded(buffer, !isFading);
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - mediaStartTime).count();
|
||||
elapsedMs = std::max(elapsedMs, 0LL);
|
||||
|
||||
if (!isFading && elapsedMs > (displayDurationMs - fadeDurationMs - 10) && nextMediaReady) {
|
||||
isFading = true;
|
||||
fadeStartTime = now;
|
||||
LOGI("Fade started");
|
||||
}
|
||||
|
||||
if (isFading) {
|
||||
auto fadeElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - fadeStartTime).count();
|
||||
fadeOutAlpha = std::clamp(1.f - (float)fadeElapsed / fadeDurationMs, 0.f, 1.f);
|
||||
fadeInAlpha = std::clamp((float)fadeElapsed / fadeDurationMs, 0.f, 1.f);
|
||||
|
||||
static float nextOffsetX = 0.f;
|
||||
static float nextOffsetY = 0.f;
|
||||
static bool nextMovingForwardX = true;
|
||||
static bool nextMovingDownY = true;
|
||||
|
||||
renderWithFade(buffer,
|
||||
(float)buffer.width, (float)buffer.height,
|
||||
imageData, imageWidth, imageHeight, imageChannels,
|
||||
fmt_ctx, codec_ctx, frame, pkt, sws_ctx,
|
||||
video_stream_idx, videoWidth, videoHeight,
|
||||
rgbBuffer,
|
||||
isImage,
|
||||
next_imageData, next_imageWidth, next_imageHeight, next_imageChannels,
|
||||
next_fmt_ctx, next_codec_ctx, next_frame, next_pkt, next_sws_ctx,
|
||||
next_video_stream_idx, next_videoWidth, next_videoHeight,
|
||||
next_rgbBuffer,
|
||||
nextIsImage,
|
||||
fadeOutAlpha, fadeInAlpha,
|
||||
offsetX, offsetY,
|
||||
movingForwardLocX, movingDownLocY,
|
||||
nextOffsetX, nextOffsetY,
|
||||
nextMovingForwardX, nextMovingDownY);
|
||||
|
||||
if (fadeElapsed >= fadeDurationMs) {
|
||||
LOGI("Fade ended, switching media");
|
||||
|
||||
releaseMediaData(isImage, &imageData, &fmt_ctx, &codec_ctx, &frame, &pkt, &sws_ctx, &rgbBuffer);
|
||||
|
||||
imageData = next_imageData;
|
||||
imageWidth = next_imageWidth;
|
||||
imageHeight = next_imageHeight;
|
||||
imageChannels = next_imageChannels;
|
||||
|
||||
fmt_ctx = next_fmt_ctx;
|
||||
codec_ctx = next_codec_ctx;
|
||||
frame = next_frame;
|
||||
pkt = next_pkt;
|
||||
sws_ctx = next_sws_ctx;
|
||||
|
||||
video_stream_idx = next_video_stream_idx;
|
||||
videoWidth = next_videoWidth;
|
||||
videoHeight = next_videoHeight;
|
||||
rgbBuffer = std::move(next_rgbBuffer);
|
||||
|
||||
isImage = nextIsImage;
|
||||
|
||||
currentMediaIndex = nextMediaIndex;
|
||||
nextMediaIndex = (nextMediaIndex + 1) % mediaPaths.size();
|
||||
|
||||
offsetX = 0.f; offsetY = 0.f;
|
||||
movingForwardLocX = true; movingDownLocY = true;
|
||||
|
||||
nextOffsetX = 0.f; nextOffsetY = 0.f;
|
||||
nextMovingForwardX = true; nextMovingDownY = true;
|
||||
|
||||
mediaStartTime = std::chrono::steady_clock::now();
|
||||
|
||||
isFading = false;
|
||||
nextMediaReady = false;
|
||||
|
||||
std::thread([](){
|
||||
std::lock_guard<std::mutex> preloadLock(renderMutex);
|
||||
nextMediaReady = loadNextMedia();
|
||||
if (nextMediaReady) {
|
||||
LOGI("Preloaded next media ready");
|
||||
} else {
|
||||
LOGE("Preload failed");
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
} else {
|
||||
auto curScaleRes = calculateScale(
|
||||
isImage ? (float)imageWidth : (float)videoWidth,
|
||||
isImage ? (float)imageHeight : (float)videoHeight,
|
||||
(float)buffer.width, (float)buffer.height);
|
||||
|
||||
updateOffset(offsetX, offsetY,
|
||||
movingForwardLocX, movingDownLocY,
|
||||
curScaleRes.overflowX, curScaleRes.overflowY);
|
||||
|
||||
if (isImage) {
|
||||
drawToBuffer(buffer, imageData, imageWidth, imageHeight,
|
||||
curScaleRes.scale, offsetX, offsetY, 1.f);
|
||||
} else {
|
||||
renderMedia(buffer, imageData, imageWidth, imageHeight, imageChannels,
|
||||
fmt_ctx, codec_ctx, frame, pkt, sws_ctx,
|
||||
video_stream_idx, videoWidth, videoHeight, rgbBuffer,
|
||||
false,
|
||||
curScaleRes.scale, offsetX, offsetY,
|
||||
1.f);
|
||||
}
|
||||
}
|
||||
|
||||
ANativeWindow_unlockAndPost(window);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeInit(JNIEnv* env, jobject, jobject surface) {
|
||||
if (window != nullptr) {
|
||||
std::lock_guard<std::mutex> lock(renderMutex);
|
||||
if (window) {
|
||||
ANativeWindow_release(window);
|
||||
window = nullptr;
|
||||
}
|
||||
window = ANativeWindow_fromSurface(env, surface);
|
||||
LOGI("Native window initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeRender(
|
||||
JNIEnv* env, jobject, jintArray pixels, jint width, jint height,
|
||||
jfloat offsetX, jfloat offsetY, jfloat fadeAlpha) {
|
||||
JNIEXPORT void JNICALL
|
||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeDestroy(JNIEnv* env, jobject) {
|
||||
std::lock_guard<std::mutex> lock(renderMutex);
|
||||
releaseMediaData(isImage, &imageData, &fmt_ctx, &codec_ctx, &frame, &pkt, &sws_ctx, &rgbBuffer);
|
||||
releaseMediaData(nextIsImage, &next_imageData, &next_fmt_ctx, &next_codec_ctx, &next_frame, &next_pkt, &next_sws_ctx, &next_rgbBuffer);
|
||||
|
||||
if (!window) return;
|
||||
|
||||
ANativeWindow_Buffer buffer;
|
||||
if (ANativeWindow_lock(window, &buffer, nullptr) < 0) {
|
||||
LOGE("Failed to lock native window");
|
||||
return;
|
||||
}
|
||||
|
||||
jint* srcPixels = env->GetIntArrayElements(pixels, nullptr);
|
||||
if (!srcPixels) {
|
||||
ANativeWindow_unlockAndPost(window);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t* dstPixels = (uint32_t*)buffer.bits;
|
||||
int stride = buffer.stride;
|
||||
|
||||
int offsetXInt = static_cast<int>(offsetX);
|
||||
int offsetYInt = static_cast<int>(offsetY);
|
||||
|
||||
for (int y = 0; y < buffer.height; y++) {
|
||||
int srcY = y - offsetYInt;
|
||||
if (srcY < 0 || srcY >= height) continue;
|
||||
|
||||
for (int x = 0; x < buffer.width; x++) {
|
||||
int srcX = x - offsetXInt;
|
||||
if (srcX < 0 || srcX >= width) continue;
|
||||
|
||||
uint32_t pixel = srcPixels[srcY * width + srcX];
|
||||
|
||||
uint8_t alpha = static_cast<uint8_t>(fadeAlpha * 255);
|
||||
uint8_t r = (pixel >> 16) & 0xFF;
|
||||
uint8_t g = (pixel >> 8) & 0xFF;
|
||||
uint8_t b = pixel & 0xFF;
|
||||
|
||||
r = (r * alpha) / 255;
|
||||
g = (g * alpha) / 255;
|
||||
b = (b * alpha) / 255;
|
||||
|
||||
dstPixels[y * stride + x] = (0xFF << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
|
||||
env->ReleaseIntArrayElements(pixels, srcPixels, JNI_ABORT);
|
||||
|
||||
ANativeWindow_unlockAndPost(window);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeDestroy(JNIEnv*, jobject) {
|
||||
if (window) {
|
||||
ANativeWindow_release(window);
|
||||
window = nullptr;
|
||||
LOGI("Native window released");
|
||||
}
|
||||
LOGI("Native window released");
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
Reference in New Issue
Block a user