This commit is contained in:
2025-08-27 18:19:26 +09:00
parent a13f66d917
commit 6e64d739ac
10444 changed files with 178 additions and 2319538 deletions
+23 -26
View File
@@ -1,47 +1,38 @@
# CMake 최소 버전 및 프로젝트 이름 설정
cmake_minimum_required(VERSION 3.18.1)
project("native_renderer")
# --- 1. FFmpeg 빌드 스크립트 실행 ---
# NOTE: 이 명령은 CMake 설정 단계에서 딱 한 번 실행되어 FFmpeg 라이브러리를 빌드합니다.
# ffmpeg 폴더에 있는 build_ffmpeg.sh 스크립트를 실행합니다.
execute_process(
COMMAND sh ../../../../ffmpeg/build_ffmpeg.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE FFMPEG_BUILD_RESULT
)
if(NOT FFMPEG_BUILD_RESULT EQUAL 0)
message(FATAL_ERROR "FFmpeg build script failed with exit code: ${FFMPEG_BUILD_RESULT}")
endif()
# --- 실행 끝 ---
# --- 1. 경로 변수 설정 ---
# NOTE: .so 파일들이 있는 lib 폴더와 include 폴더의 경로를 각각 명확히 지정합니다.
set(JNI_LIBS_ROOT ${CMAKE_SOURCE_DIR}/../../jniLibs/${ANDROID_ABI})
set(JNI_SO_DIR ${JNI_LIBS_ROOT}/lib)
set(JNI_INCLUDE_DIR ${JNI_LIBS_ROOT}/include)
# --- 2. 빌드된 라이브러리 참조 ---
# NOTE: 이제 FFmpeg은 미리 빌드된 라이브러리(prebuilt)처럼 취급됩니다.
# jniLibs 폴더 경로 설정
set(JNI_LIBS_DIR ${CMAKE_SOURCE_DIR}/../../jniLibs/${ANDROID_ABI})
# --- 2. 헤더 파일 경로 추가 ---
include_directories(${JNI_INCLUDE_DIR})
# 헤더 파일 경로 추가
include_directories(${JNI_LIBS_DIR}/include)
# 각 .so 파일을 IMPORTED 라이브러리로 추가
# --- 3. 미리 빌드된 FFmpeg .so 라이브러리들을 IMPORTED 타겟으로 선언 ---
# NOTE: IMPORTED_LOCATION 경로가 lib 폴더를 포함하도록 수정되었습니다.
add_library(avformat SHARED IMPORTED)
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libavformat.so)
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION ${JNI_SO_DIR}/libavformat.so)
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libavcodec.so)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${JNI_SO_DIR}/libavcodec.so)
add_library(avutil SHARED IMPORTED)
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libavutil.so)
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION ${JNI_SO_DIR}/libavutil.so)
add_library(swscale SHARED IMPORTED)
set_target_properties(swscale PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libswscale.so)
set_target_properties(swscale PROPERTIES IMPORTED_LOCATION ${JNI_SO_DIR}/libswscale.so)
add_library(swresample SHARED IMPORTED)
set_target_properties(swresample PROPERTIES IMPORTED_LOCATION ${JNI_LIBS_DIR}/libswresample.so)
set_target_properties(swresample PROPERTIES IMPORTED_LOCATION ${JNI_SO_DIR}/libswresample.so)
# --- 3. 네이티브 렌더러 라이브러리 빌드 및 링크 ---
# --- 4. 네이티브 렌더러 라이브러리 빌드 설정 ---
# NOTE: 앱의 C++ 소스 파일들을 여기에 모두 나열합니다.
add_library(native_renderer SHARED
native_renderer.cpp
Renderer.cpp
@@ -49,16 +40,22 @@ add_library(native_renderer SHARED
MediaAsset.cpp
)
# --- 5. 최종 라이브러리 링크 ---
# NOTE: 안드로이드 기본 라이브러리와 위에서 선언한 FFmpeg 타겟들을 링크합니다.
find_library(log-lib log)
find_library(android-lib android)
find_library(nativewindow-lib nativewindow)
target_link_libraries(native_renderer
# FFmpeg 라이브러리 타겟들
avformat
avcodec
avutil
swscale
swresample
# 안드로이드 기본 라이브러리들
${log-lib}
${android-lib}
${nativewindow-lib}
+115 -92
View File
@@ -1,89 +1,117 @@
#include "MediaAsset.h"
#include <android/log.h>
#include <algorithm>
#include <vector>
#include <unistd.h> // close(fd) 함수를 사용하기 위해 추가
#include <algorithm>
// stb_image.h의 구현부를 여기에 단 한 번만 포함시킵니다.
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define LOG_TAG "MediaAsset"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// NOTE: C++ JNI 단에서 파일 디스크립터(fd)의 소유권을 가지므로, 사용 후 반드시 닫아야 합니다.
bool MediaAsset::load(int fd) {
release(); // 새로 로드하기 전에 기존 자원 정리
MediaAsset::MediaAsset() = default;
if (fd < 0) {
LOGE("Invalid file descriptor received: %d", fd);
MediaAsset::~MediaAsset() {
release();
}
// 파일 확장자를 확인하여 적절한 로더를 호출하는 분기 함수
bool MediaAsset::load(const std::string& path) {
release();
if (path.empty()) {
LOGE("Load failed: Path is empty.");
return false;
}
// NOTE: FFmpeg가 파일 디스크립터를 입력으로 받도록 "pipe:[fd]" 형식의 문자열을 생성합니다.
char path_from_fd[32];
snprintf(path_from_fd, sizeof(path_from_fd), "pipe:%d", fd);
size_t dotPos = path.find_last_of('.');
if (dotPos == std::string::npos) {
LOGE("Load failed: Could not find file extension in path: %s", path.c_str());
return false;
}
// FFmpeg 로더 함수를 호출합니다.
loadMediaWithFFmpeg(path_from_fd);
std::string extension = path.substr(dotPos + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
// NOTE: 파일 디스크립터 사용이 끝났으므로 여기서 닫아줍니다. (누수 방지)
close(fd);
const std::vector<std::string> videoExts = {"mp4", "mkv", "avi", "mov", "webm"};
const std::vector<std::string> imageExts = {"jpg", "jpeg", "png", "bmp", "webp"};
return isValid();
bool isVideo = std::find(videoExts.begin(), videoExts.end(), extension) != videoExts.end();
bool isImage = std::find(imageExts.begin(), imageExts.end(), extension) != imageExts.end();
if (isImage) {
return loadImageWithStb(path);
} else if (isVideo) {
return loadVideoWithFFmpeg(path);
}
LOGE("Load failed: Unsupported file format for path: %s", path.c_str());
return false;
}
// NOTE: 이 함수는 이제 "pipe:[fd]" 형식의 경로를 받아 비디오와 이미지를 모두 처리합니다.
void MediaAsset::loadMediaWithFFmpeg(const std::string& path) {
// 1. 미디어 파일 열기
// stb_image.h를 사용하여 이미지를 로드하는 함수
bool MediaAsset::loadImageWithStb(const std::string& path) {
type_ = Type::IMAGE;
// 4 채널(RGBA)로 강제 변환하여 로드합니다.
imageData_ = stbi_load(path.c_str(), &width_, &height_, nullptr, 4);
if (!imageData_) {
LOGE("Failed to load image with stb_image: %s", stbi_failure_reason());
return false;
}
LOGI("Successfully loaded image with stb_image: %s (%dx%d)", path.c_str(), width_, height_);
return true;
}
// FFmpeg을 사용하여 비디오를 로드하는 함수
bool MediaAsset::loadVideoWithFFmpeg(const std::string& path) {
type_ = Type::VIDEO;
// 1. 비디오 파일 열기
if (avformat_open_input(&fmtCtx_, path.c_str(), nullptr, nullptr) != 0) {
LOGE("Could not open media from path: %s", path.c_str());
return;
LOGE("Could not open video file: %s", path.c_str());
release();
return false;
}
// 2. 스트림 정보 찾기
if (avformat_find_stream_info(fmtCtx_, nullptr) < 0) {
LOGE("Could not find stream information.");
LOGE("Could not find stream information for %s", path.c_str());
release();
return;
return false;
}
// 3. 최적의 비디오 스트림 찾기
const AVCodec* codec = nullptr;
videoStreamIdx_ = av_find_best_stream(fmtCtx_, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
if (videoStreamIdx_ < 0) {
LOGE("Could not find a video stream in media.");
LOGE("Could not find a video stream in %s", path.c_str());
release();
return;
return false;
}
AVStream* stream = fmtCtx_->streams[videoStreamIdx_];
// 4. 미디어 타입 결정 (비디오 vs 이미지)
if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC || stream->duration <= 0) {
type_ = Type::IMAGE;
} else {
type_ = Type::VIDEO;
}
// 5. 코덱 컨텍스트 준비
// 4. 코덱 컨텍스트 준비
codecCtx_ = avcodec_alloc_context3(codec);
if (!codecCtx_ || avcodec_parameters_to_context(codecCtx_, stream->codecpar) < 0) {
LOGE("Failed to create codec context.");
if (!codecCtx_ || avcodec_parameters_to_context(codecCtx_, fmtCtx_->streams[videoStreamIdx_]->codecpar) < 0) {
LOGE("Failed to create codec context for %s", path.c_str());
release();
return;
return false;
}
// 5. 코덱 열기
if (avcodec_open2(codecCtx_, codec, nullptr) < 0) {
LOGE("Could not open codec.");
LOGE("Could not open codec for %s", path.c_str());
release();
return;
return false;
}
// 6. 프레임 및 패킷 할당
frame_ = av_frame_alloc();
packet_ = av_packet_alloc();
if (!frame_ || !packet_) {
LOGE("Could not allocate frame or packet.");
LOGE("Could not allocate frame or packet for %s", path.c_str());
release();
return;
return false;
}
// 7. RGBA 변환을 위한 SwsContext 준비
@@ -94,48 +122,30 @@ void MediaAsset::loadMediaWithFFmpeg(const std::string& path) {
width_, height_, AV_PIX_FMT_RGBA,
SWS_BILINEAR, nullptr, nullptr, nullptr
);
if (!swsCtx_) {
LOGE("Could not create SwsContext.");
LOGE("Could not create SwsContext for %s", path.c_str());
release();
return;
return false;
}
// 8. RGBA 픽셀 데이터를 담을 버퍼 할당
rgbBuffer_.resize(width_ * height_ * 4);
// 9. 이미지일 경우, 첫 프레임을 미리 디코딩하여 버퍼에 저장
if (type_ == Type::IMAGE) {
if (av_read_frame(fmtCtx_, packet_) >= 0 && packet_->stream_index == videoStreamIdx_) {
if (avcodec_send_packet(codecCtx_, packet_) == 0) {
if (avcodec_receive_frame(codecCtx_, frame_) == 0) {
uint8_t* dst[4] = { rgbBuffer_.data(), nullptr, nullptr, nullptr };
int dstStride_arr[4] = { width_ * 4, 0, 0, 0 };
sws_scale(swsCtx_, frame_->data, frame_->linesize, 0, height_, dst, dstStride_arr);
imageData_ = rgbBuffer_.data(); // imageData_가 버퍼를 가리키도록 설정
}
}
}
av_packet_unref(packet_);
// 이미지는 첫 프레임만 필요하므로 컨텍스트를 미리 닫아 자원 절약
avformat_close_input(&fmtCtx_);
fmtCtx_ = nullptr;
}
LOGI("Successfully loaded media (Type: %s, W: %d, H: %d)",
(type_ == Type::IMAGE ? "Image" : "Video"), width_, height_);
}
MediaAsset::~MediaAsset() {
release();
LOGI("Successfully loaded video: %s (W: %d, H: %d)", path.c_str(), width_, height_);
return true;
}
void MediaAsset::release() {
imageData_ = nullptr; // rgbBuffer_가 해제될 것이므로 포인터만 초기화
// 이미지 데이터 해제
if (type_ == Type::IMAGE && imageData_) {
stbi_image_free(imageData_);
}
imageData_ = nullptr;
// FFmpeg 비디오 자원 해제
if (packet_) av_packet_free(&packet_);
if (frame_) av_frame_free(&frame_);
if (codecCtx_) avcodec_close(codecCtx_); // avcodec_free_context 전에 호출
if (codecCtx_) avcodec_free_context(&codecCtx_);
if (fmtCtx_) avformat_close_input(&fmtCtx_);
if (swsCtx_) sws_freeContext(swsCtx_);
@@ -146,6 +156,7 @@ void MediaAsset::release() {
fmtCtx_ = nullptr;
swsCtx_ = nullptr;
// 공통 변수 초기화
type_ = Type::UNKNOWN;
width_ = 0;
height_ = 0;
@@ -155,44 +166,56 @@ void MediaAsset::release() {
bool MediaAsset::isValid() const {
if (type_ == Type::IMAGE) {
return !rgbBuffer_.empty() && width_ > 0 && height_ > 0;
return imageData_ != nullptr && width_ > 0 && height_ > 0;
} else if (type_ == Type::VIDEO) {
return fmtCtx_ != nullptr && codecCtx_ != nullptr && frame_ != nullptr && swsCtx_ != nullptr;
}
return false;
}
// --- Move Constructor and Assignment Operator ---
// --- 이동 생성자 및 이동 할당 연산자 (자원 소유권 이전) ---
MediaAsset::MediaAsset(MediaAsset&& other) noexcept
: type_(other.type_), width_(other.width_), height_(other.height_),
imageData_(nullptr), fmtCtx_(other.fmtCtx_), codecCtx_(other.codecCtx_),
frame_(other.frame_), packet_(other.packet_), swsCtx_(other.swsCtx_),
videoStreamIdx_(other.videoStreamIdx_), rgbBuffer_(std::move(other.rgbBuffer_)) {
imageData_(other.imageData_), rgbBuffer_(std::move(other.rgbBuffer_)),
fmtCtx_(other.fmtCtx_), codecCtx_(other.codecCtx_), frame_(other.frame_),
packet_(other.packet_), swsCtx_(other.swsCtx_), videoStreamIdx_(other.videoStreamIdx_) {
if (!rgbBuffer_.empty()) {
imageData_ = rgbBuffer_.data();
}
other.imageData_ = nullptr; other.fmtCtx_ = nullptr; other.codecCtx_ = nullptr;
other.frame_ = nullptr; other.packet_ = nullptr; other.swsCtx_ = nullptr;
other.type_ = Type::UNKNOWN; other.videoStreamIdx_ = -1;
// 소유권을 이전했으므로, 원본 객체의 포인터들은 초기화하여 이중 해제를 방지
other.type_ = Type::UNKNOWN;
other.imageData_ = nullptr;
other.fmtCtx_ = nullptr;
other.codecCtx_ = nullptr;
other.frame_ = nullptr;
other.packet_ = nullptr;
other.swsCtx_ = nullptr;
}
MediaAsset& MediaAsset::operator=(MediaAsset&& other) noexcept {
if (this != &other) {
release();
type_ = other.type_; width_ = other.width_; height_ = other.height_;
fmtCtx_ = other.fmtCtx_; codecCtx_ = other.codecCtx_; frame_ = other.frame_;
packet_ = other.packet_; swsCtx_ = other.swsCtx_; videoStreamIdx_ = other.videoStreamIdx_;
release(); // 기존 자원 정리
// 자원 소유권 이전
type_ = other.type_;
width_ = other.width_;
height_ = other.height_;
imageData_ = other.imageData_;
rgbBuffer_ = std::move(other.rgbBuffer_);
fmtCtx_ = other.fmtCtx_;
codecCtx_ = other.codecCtx_;
frame_ = other.frame_;
packet_ = other.packet_;
swsCtx_ = other.swsCtx_;
videoStreamIdx_ = other.videoStreamIdx_;
if (!rgbBuffer_.empty()) {
imageData_ = rgbBuffer_.data();
}
other.imageData_ = nullptr; other.fmtCtx_ = nullptr; other.codecCtx_ = nullptr;
other.frame_ = nullptr; other.packet_ = nullptr; other.swsCtx_ = nullptr;
other.type_ = Type::UNKNOWN; other.videoStreamIdx_ = -1;
// 원본 객체 초기화
other.type_ = Type::UNKNOWN;
other.imageData_ = nullptr;
other.fmtCtx_ = nullptr;
other.codecCtx_ = nullptr;
other.frame_ = nullptr;
other.packet_ = nullptr;
other.swsCtx_ = nullptr;
}
return *this;
}
+27 -9
View File
@@ -3,8 +3,8 @@
#include <string>
#include <vector>
#include <cstdint>
#include <fstream>
// FFmpeg 헤더 파일 포함
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
@@ -13,46 +13,64 @@ extern "C" {
class MediaAsset {
public:
// 미디어 타입을 구분하기 위한 enum
enum class Type { UNKNOWN, IMAGE, VIDEO };
MediaAsset() = default;
MediaAsset();
~MediaAsset();
// 복사를 방지하고 이동만 허용 (효율적인 자원 관리)
MediaAsset(const MediaAsset&) = delete;
MediaAsset& operator=(const MediaAsset&) = delete;
MediaAsset(MediaAsset&& other) noexcept;
MediaAsset& operator=(MediaAsset&& other) noexcept;
bool load(int fd); // 수정
// bool load(const std::string& path);
// 파일 경로를 받아 이미지 또는 비디오를 로드하는 메인 함수
bool load(const std::string& path);
// 모든 자원을 해제하는 함수
void release();
// 현재 MediaAsset이 유효한 데이터를 가지고 있는지 확인
bool isValid() const;
// Getter 함수들
Type getType() const { return type_; }
int getWidth() const { return width_; }
int getHeight() const { return height_; }
// 이미지 데이터 포인터를 반환 (이미지 타입일 때만 유효)
uint8_t* getImageData() const { return imageData_; }
// 비디오 프레임의 RGB 데이터 버퍼를 반환 (비디오 타입일 때만 유효)
std::vector<uint8_t>& getRgbBuffer() { return rgbBuffer_; }
// 비디오 처리에 필요한 FFmpeg 컨텍스트들을 반환하는 Getter 함수들
AVFormatContext* getFormatContext() const { return fmtCtx_; }
AVCodecContext* getCodecContext() const { return codecCtx_; }
AVFrame* getFrame() const { return frame_; }
AVPacket* getPacket() const { return packet_; }
SwsContext* getSwsContext() const { return swsCtx_; }
int getVideoStreamIndex() const { return videoStreamIdx_; }
std::vector<uint8_t>& getRgbBuffer() { return rgbBuffer_; }
private:
void loadVideo(const std::string& path);
void loadImage(const std::string& path);
void loadMediaWithFFmpeg(const std::string& path);
// 내부 헬퍼 함수
bool loadVideoWithFFmpeg(const std::string& path);
bool loadImageWithStb(const std::string& path);
// 공통 멤버 변수
Type type_ = Type::UNKNOWN;
int width_ = 0;
int height_ = 0;
// 이미지 전용 멤버 변수
uint8_t* imageData_ = nullptr;
// 비디오 전용 멤버 변수 (FFmpeg)
std::vector<uint8_t> rgbBuffer_;
AVFormatContext* fmtCtx_ = nullptr;
AVCodecContext* codecCtx_ = nullptr;
AVFrame* frame_ = nullptr;
AVPacket* packet_ = nullptr;
SwsContext* swsCtx_ = nullptr;
int videoStreamIdx_ = -1;
std::vector<uint8_t> rgbBuffer_;
};