#include "Renderer.h" #include "Preloader.h" #include #include #include #include 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 lock(renderMutex_); currentMedia_.release(); nextMedia_.release(); } void Renderer::setNextMedia(int fd) { std::lock_guard 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 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(now - mediaStartTime_).count(); // 1. 현재 미디어 그리기 (페이드 아웃 효과 적용) float currentAlpha = 1.0f; if (isFading_) { long long fadeElapsed = std::chrono::duration_cast(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(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(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(media.getWidth()); float mediaH = static_cast(media.getHeight()); float bufW = static_cast(buffer.width); float bufH = static_cast(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(alpha * 255.0f); for (int y = 0; y < buffer.height; ++y) { int srcY = static_cast((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((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& rgbBuf = const_cast(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); }