#include "MediaAsset.h" #include #include #include #include // close(fd) 함수를 사용하기 위해 추가 #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(); // 새로 로드하기 전에 기존 자원 정리 if (fd < 0) { LOGE("Invalid file descriptor received: %d", fd); return false; } // NOTE: FFmpeg가 파일 디스크립터를 입력으로 받도록 "pipe:[fd]" 형식의 문자열을 생성합니다. char path_from_fd[32]; snprintf(path_from_fd, sizeof(path_from_fd), "pipe:%d", fd); // FFmpeg 로더 함수를 호출합니다. loadMediaWithFFmpeg(path_from_fd); // NOTE: 파일 디스크립터 사용이 끝났으므로 여기서 닫아줍니다. (누수 방지) close(fd); return isValid(); } // NOTE: 이 함수는 이제 "pipe:[fd]" 형식의 경로를 받아 비디오와 이미지를 모두 처리합니다. void MediaAsset::loadMediaWithFFmpeg(const std::string& path) { // 1. 미디어 파일 열기 if (avformat_open_input(&fmtCtx_, path.c_str(), nullptr, nullptr) != 0) { LOGE("Could not open media from path: %s", path.c_str()); return; } // 2. 스트림 정보 찾기 if (avformat_find_stream_info(fmtCtx_, nullptr) < 0) { LOGE("Could not find stream information."); release(); return; } // 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."); release(); return; } 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. 코덱 컨텍스트 준비 codecCtx_ = avcodec_alloc_context3(codec); if (!codecCtx_ || avcodec_parameters_to_context(codecCtx_, stream->codecpar) < 0) { LOGE("Failed to create codec context."); release(); return; } if (avcodec_open2(codecCtx_, codec, nullptr) < 0) { LOGE("Could not open codec."); release(); return; } // 6. 프레임 및 패킷 할당 frame_ = av_frame_alloc(); packet_ = av_packet_alloc(); if (!frame_ || !packet_) { LOGE("Could not allocate frame or packet."); release(); return; } // 7. RGBA 변환을 위한 SwsContext 준비 width_ = codecCtx_->width; height_ = codecCtx_->height; swsCtx_ = sws_getContext( width_, height_, codecCtx_->pix_fmt, width_, height_, AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr ); if (!swsCtx_) { LOGE("Could not create SwsContext."); release(); return; } // 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(); } void MediaAsset::release() { imageData_ = nullptr; // rgbBuffer_가 해제될 것이므로 포인터만 초기화 if (packet_) av_packet_free(&packet_); if (frame_) av_frame_free(&frame_); if (codecCtx_) avcodec_free_context(&codecCtx_); if (fmtCtx_) avformat_close_input(&fmtCtx_); if (swsCtx_) sws_freeContext(swsCtx_); packet_ = nullptr; frame_ = nullptr; codecCtx_ = nullptr; fmtCtx_ = nullptr; swsCtx_ = nullptr; type_ = Type::UNKNOWN; width_ = 0; height_ = 0; videoStreamIdx_ = -1; rgbBuffer_.clear(); } bool MediaAsset::isValid() const { if (type_ == Type::IMAGE) { return !rgbBuffer_.empty() && 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_)) { 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; } 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_; rgbBuffer_ = std::move(other.rgbBuffer_); 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; } return *this; }