Files
android_multiviewwer/app/src/main/cpp/MediaAsset.cpp
T
2025-08-27 15:09:05 +09:00

198 lines
6.8 KiB
C++

#include "MediaAsset.h"
#include <android/log.h>
#include <algorithm>
#include <vector>
#include <unistd.h> // 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;
}