234 lines
9.2 KiB
C++
Raw Normal View History

2025-08-27 15:09:05 +09:00
#include "Renderer.h"
#include "Preloader.h"
#include <android/log.h>
#include <algorithm>
#include <cmath>
#include <thread>
extern Preloader* preloader; // 전역 Preloader 객체
extern void callNextMediaCallback(); // 다음 미디어 요청 콜백
#define LOG_TAG "Renderer"
#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 constexpr long long displayDurationMs = 20000;
static constexpr long long fadeDurationMs = 3000;
Renderer::Renderer() {
mediaStartTime_ = std::chrono::steady_clock::now();
}
Renderer::~Renderer() {
release();
}
void Renderer::release() {
std::lock_guard<std::mutex> lock(renderMutex_);
currentMedia_.release();
nextMedia_.release();
}
void Renderer::setNextMedia(int fd) {
std::lock_guard<std::mutex> lock(renderMutex_);
currentMedia_.release();
if (currentMedia_.load(fd)) {
LOGI("New media loaded successfully.");
mediaStartTime_ = std::chrono::steady_clock::now();
isFading_ = false;
} else {
LOGE("Failed to load new media: %s. Requesting next media.", currentMediaPath_.c_str());
// NOTE: 초기 로딩 실패 시, 다음 미디어를 바로 요청하여 검은 화면 방지
callNextMediaCallback();
}
}
void Renderer::renderFrame(ANativeWindow* window) {
if (!window) return;
std::lock_guard<std::mutex> lock(renderMutex_);
if (!currentMedia_.isValid()) {
// NOTE: 렌더링할 미디어가 없으면 아무것도 하지 않음 (검은 화면)
// 로딩 실패 시 setNextMedia에서 다음 미디어를 요청하므로 일시적인 상태
return;
}
ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window, &buffer, nullptr) < 0) {
LOGE("Failed to lock window.");
return;
}
// 화면을 검은색으로 초기화
memset(buffer.bits, 0, buffer.stride * buffer.height * sizeof(uint32_t));
auto now = std::chrono::steady_clock::now();
long long elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - mediaStartTime_).count();
// 1. 현재 미디어 그리기 (페이드 아웃 효과 적용)
float currentAlpha = 1.0f;
if (isFading_) {
long long fadeElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - fadeStartTime_).count();
currentAlpha = std::clamp(1.0f - (float)fadeElapsed / fadeDurationMs, 0.0f, 1.0f);
}
drawMedia(buffer, currentMedia_, currentAlpha, elapsedMs);
// 2. 다음 미디어 그리기 (페이드 인 효과 적용)
if (isFading_) {
// Preloader로부터 미리 로드된 미디어를 가져오기 시도
if (!nextMedia_.isValid()) {
if (preloader && preloader->isPreloadedDataReady()) {
nextMedia_ = preloader->swapAndRelease();
if (nextMedia_.isValid()) {
LOGI("Renderer acquired preloaded media for fade-in.");
// 성공적으로 가져왔으면, 다음 미디어 예비 로딩 요청
callNextMediaCallback();
} else {
LOGE("Renderer failed to acquire a valid preloaded media.");
}
}
}
// 가져온 nextMedia_가 유효하다면 페이드 인 효과로 그리기
if (nextMedia_.isValid()) {
long long fadeElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - fadeStartTime_).count();
float nextAlpha = std::clamp((float)fadeElapsed / fadeDurationMs, 0.0f, 1.0f);
drawMedia(buffer, nextMedia_, nextAlpha, 0); // 새 미디어는 0ms부터 시작
}
}
// 3. 미디어 상태 업데이트
long long fadeTotalElapsed = isFading_ ? std::chrono::duration_cast<std::chrono::milliseconds>(now - fadeStartTime_).count() : 0;
if (isFading_ && fadeTotalElapsed >= fadeDurationMs) {
// 페이드가 끝났을 때
if (nextMedia_.isValid()) {
// NOTE: 다음 미디어가 유효할 때만 현재 미디어를 교체 (가장 중요)
currentMedia_ = std::move(nextMedia_);
LOGI("Renderer successfully swapped to new media.");
} else {
LOGE("Fade ended, but no valid next media. Retaining current media.");
}
isFading_ = false;
mediaStartTime_ = now;
} else if (!isFading_ && elapsedMs >= displayDurationMs) {
// 현재 미디어 재생 시간이 다 되어 페이드를 시작할 때
isFading_ = true;
fadeStartTime_ = now;
LOGI("Display duration ended. Starting fade transition.");
}
ANativeWindow_unlockAndPost(window);
}
// --- 나머지 함수들은 변경 사항 없음 ---
void Renderer::drawMedia(ANativeWindow_Buffer& buffer, const MediaAsset& media, float alpha, float offsetElapsedMs) {
if (!media.isValid() || alpha <= 0.0f) return;
float mediaW = static_cast<float>(media.getWidth());
float mediaH = static_cast<float>(media.getHeight());
float bufW = static_cast<float>(buffer.width);
float bufH = static_cast<float>(buffer.height);
float scale, overflowX, overflowY;
if ((mediaW / mediaH) > (bufW / bufH)) {
scale = bufH / mediaH;
overflowX = std::max(0.0f, mediaW * scale - bufW);
overflowY = 0.0f;
} else {
scale = bufW / mediaW;
overflowX = 0.0f;
overflowY = std::max(0.0f, mediaH * scale - bufH);
}
float offsetX = 0.0f;
float offsetY = 0.0f;
updateOffset(offsetX, offsetY, overflowX, overflowY, offsetElapsedMs);
if (media.getType() == MediaAsset::Type::IMAGE) {
renderImageFrame(media, buffer, scale, offsetX, offsetY, alpha);
} else {
renderVideoFrame(media, buffer, scale, offsetX, offsetY, alpha);
}
}
void Renderer::updateOffset(float& offsetX, float& offsetY, float overflowX, float overflowY, long long elapsedMs) {
if (overflowX > 0) {
float normalizedTime = fmod((float)elapsedMs / displayDurationMs, 2.0f);
normalizedTime = (normalizedTime > 1.0f) ? 2.0f - normalizedTime : normalizedTime;
offsetX = overflowX * normalizedTime;
}
if (overflowY > 0) {
float normalizedTime = fmod((float)elapsedMs / displayDurationMs, 2.0f);
normalizedTime = (normalizedTime > 1.0f) ? 2.0f - normalizedTime : normalizedTime;
offsetY = overflowY * normalizedTime;
}
}
void Renderer::renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha) {
uint32_t* dstPixels = (uint32_t*)buffer.bits;
int dstStride = buffer.stride;
const uint8_t* pixelData = media.getImageData();
int imgW = media.getWidth();
int imgH = media.getHeight();
uint8_t alphaByte = static_cast<uint8_t>(alpha * 255.0f);
for (int y = 0; y < buffer.height; ++y) {
int srcY = static_cast<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 = static_cast<int>((x + offsetX) / scale);
if (srcX < 0 || srcX >= imgW) continue;
const uint8_t* srcPixel = &pixelData[(srcY * imgW + srcX) * 4];
uint32_t dstPixel = dstRow[x];
uint8_t dstR = (dstPixel >> 16) & 0xFF;
uint8_t dstG = (dstPixel >> 8) & 0xFF;
uint8_t dstB = dstPixel & 0xFF;
uint8_t finalR = (srcPixel[0] * alphaByte + dstR * (255 - alphaByte)) / 255;
uint8_t finalG = (srcPixel[1] * alphaByte + dstG * (255 - alphaByte)) / 255;
uint8_t finalB = (srcPixel[2] * alphaByte + dstB * (255 - alphaByte)) / 255;
dstRow[x] = (0xFF << 24) | (finalR << 16) | (finalG << 8) | finalB;
}
}
}
void Renderer::renderVideoFrame(const MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha) {
AVFormatContext* fmt_ctx = media.getFormatContext();
AVCodecContext* codec_ctx = media.getCodecContext();
AVFrame* frame = media.getFrame();
AVPacket* pkt = media.getPacket();
SwsContext* sws_ctx = media.getSwsContext();
int video_stream_idx = media.getVideoStreamIndex();
if (!fmt_ctx || !codec_ctx || !frame || !pkt || !sws_ctx) return;
std::vector<uint8_t>& rgbBuf = const_cast<MediaAsset&>(media).getRgbBuffer();
int ret = av_read_frame(fmt_ctx, pkt);
if (ret >= 0) {
if (pkt->stream_index == video_stream_idx) {
if (avcodec_send_packet(codec_ctx, pkt) >= 0) {
if (avcodec_receive_frame(codec_ctx, frame) == 0) {
uint8_t* dst[4] = { rgbBuf.data(), nullptr, nullptr, nullptr };
int dstStride_arr[4] = { media.getWidth() * 4, 0, 0, 0 };
sws_scale(sws_ctx, frame->data, frame->linesize, 0, media.getHeight(), dst, dstStride_arr);
}
}
}
av_packet_unref(pkt);
} else if (ret == AVERROR_EOF) {
av_seek_frame(fmt_ctx, video_stream_idx, 0, AVSEEK_FLAG_BACKWARD);
}
// 디코딩된 프레임이 버퍼에 있으므로, renderImageFrame과 유사한 로직으로 화면에 그립니다.
renderImageFrame(media, buffer, scale, offsetX, offsetY, alpha);
}