198 lines
6.8 KiB
C++
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;
|
|
} |