306 lines
11 KiB
C++
306 lines
11 KiB
C++
|
|
#include "pch.h"
|
||
|
|
#include "HeadlessDecoder.h"
|
||
|
|
#include <chrono>
|
||
|
|
#include <iomanip>
|
||
|
|
|
||
|
|
namespace Vav2Player {
|
||
|
|
|
||
|
|
HeadlessDecoder::HeadlessDecoder() = default;
|
||
|
|
|
||
|
|
HeadlessDecoder::~HeadlessDecoder() = default;
|
||
|
|
|
||
|
|
bool HeadlessDecoder::ProcessFile(const std::string& input_file_path) {
|
||
|
|
try {
|
||
|
|
m_input_file_path = input_file_path;
|
||
|
|
m_start_time = std::chrono::high_resolution_clock::now();
|
||
|
|
|
||
|
|
std::cout << "=== Vav2Player Headless Mode ===" << std::endl;
|
||
|
|
std::cout << "Input file: " << input_file_path << std::endl;
|
||
|
|
std::cout << std::endl;
|
||
|
|
|
||
|
|
// 1. 컴포넌트 초기화
|
||
|
|
std::cout << "Step 1: Initializing components..." << std::endl;
|
||
|
|
if (!InitializeComponents()) {
|
||
|
|
std::cerr << "Error: Failed to initialize components" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
std::cout << "Components initialized successfully" << std::endl;
|
||
|
|
|
||
|
|
// 2. WebM 파일 열기
|
||
|
|
std::cout << "Step 2: Opening WebM file..." << std::endl;
|
||
|
|
if (!OpenWebMFile(input_file_path)) {
|
||
|
|
std::cerr << "Error: Failed to open WebM file" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. 디코더 초기화
|
||
|
|
std::cout << "Step 3: Initializing decoder..." << std::endl;
|
||
|
|
if (!InitializeDecoder()) {
|
||
|
|
std::cerr << "Error: Failed to initialize decoder" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 4. 출력 초기화
|
||
|
|
std::cout << "Step 4: Initializing output..." << std::endl;
|
||
|
|
if (!InitializeOutput()) {
|
||
|
|
std::cerr << "Error: Failed to initialize output" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 5. 모든 프레임 처리
|
||
|
|
std::cout << "Step 5: Starting frame processing..." << std::endl;
|
||
|
|
if (!ProcessAllFrames()) {
|
||
|
|
std::cerr << "Error: Frame processing failed" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 6. 요약 출력
|
||
|
|
PrintSummary();
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
catch (const std::exception& e) {
|
||
|
|
std::cerr << "*** EXCEPTION in ProcessFile: " << e.what() << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
catch (...) {
|
||
|
|
std::cerr << "*** UNKNOWN EXCEPTION in ProcessFile" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool HeadlessDecoder::InitializeComponents() {
|
||
|
|
try {
|
||
|
|
std::cout << " Creating WebMFileReader..." << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
m_file_reader = std::make_unique<WebMFileReader>();
|
||
|
|
std::cout << " WebMFileReader created successfully" << std::endl;
|
||
|
|
|
||
|
|
std::cout << " Creating FileOutput..." << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
m_file_output = std::make_unique<FileOutput>();
|
||
|
|
std::cout << " FileOutput created successfully" << std::endl;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
} catch (const std::exception& e) {
|
||
|
|
std::cerr << "Exception during component initialization: " << e.what() << std::endl;
|
||
|
|
return false;
|
||
|
|
} catch (...) {
|
||
|
|
std::cerr << "Unknown exception during component initialization" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool HeadlessDecoder::OpenWebMFile(const std::string& file_path) {
|
||
|
|
std::cout << " Attempting to open file: " << file_path << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
|
||
|
|
if (!m_file_reader->OpenFile(file_path)) {
|
||
|
|
auto error_code = m_file_reader->GetLastError();
|
||
|
|
std::string error_msg = m_file_reader->GetLastErrorString();
|
||
|
|
std::cerr << "Failed to open WebM file: " << error_msg << std::endl;
|
||
|
|
std::cerr << "Error code: " << static_cast<int>(error_code) << std::endl;
|
||
|
|
|
||
|
|
// 디버깅: 발견된 트랙 정보 출력
|
||
|
|
auto tracks = m_file_reader->GetVideoTracks();
|
||
|
|
if (!tracks.empty()) {
|
||
|
|
std::cout << "Found " << tracks.size() << " video track(s):" << std::endl;
|
||
|
|
for (const auto& track : tracks) {
|
||
|
|
std::cout << " Track #" << track.track_number
|
||
|
|
<< ": " << track.codec_name
|
||
|
|
<< " (" << track.codec_id << ")"
|
||
|
|
<< " " << track.width << "x" << track.height << std::endl;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 메타데이터 얻기
|
||
|
|
m_metadata = m_file_reader->GetVideoMetadata();
|
||
|
|
|
||
|
|
std::cout << "WebM file opened successfully:" << std::endl;
|
||
|
|
std::cout << " Resolution: " << m_metadata.width << "x" << m_metadata.height << std::endl;
|
||
|
|
std::cout << " Codec: " << m_metadata.codec_name << std::endl;
|
||
|
|
std::cout << " Frame rate: " << m_metadata.frame_rate << " fps" << std::endl;
|
||
|
|
std::cout << " Total frames: " << m_metadata.total_frames << std::endl;
|
||
|
|
std::cout << " Duration: " << std::fixed << std::setprecision(2) << m_metadata.duration_seconds << " seconds" << std::endl;
|
||
|
|
std::cout << std::endl;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool HeadlessDecoder::InitializeDecoder() {
|
||
|
|
std::cout << " Creating decoder for codec: " << m_metadata.codec_name << " (type: " << static_cast<int>(m_metadata.codec_type) << ")" << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
|
||
|
|
m_decoder = VideoDecoderFactory::CreateDecoder(m_metadata.codec_type);
|
||
|
|
if (!m_decoder) {
|
||
|
|
std::cerr << "Failed to create decoder for codec: " << m_metadata.codec_name << std::endl;
|
||
|
|
std::cerr << "Codec type: " << static_cast<int>(m_metadata.codec_type) << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
std::cout << " Decoder created successfully" << std::endl;
|
||
|
|
|
||
|
|
std::cout << " Initializing decoder with metadata..." << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
|
||
|
|
if (!m_decoder->Initialize(m_metadata)) {
|
||
|
|
std::cerr << "Failed to initialize decoder" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::cout << "Decoder initialized successfully" << std::endl;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool HeadlessDecoder::InitializeOutput() {
|
||
|
|
std::cout << " Setting up output configuration..." << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
|
||
|
|
// 출력 디렉토리 설정
|
||
|
|
FileOutput::OutputConfig config;
|
||
|
|
config.format = FileOutput::OutputFormat::BMP;
|
||
|
|
config.output_directory = "output_frames";
|
||
|
|
config.filename_prefix = "frame";
|
||
|
|
config.create_subdirectories = true;
|
||
|
|
config.overwrite_existing = true;
|
||
|
|
|
||
|
|
std::cout << " Applying output config..." << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
|
||
|
|
m_file_output->SetConfig(config);
|
||
|
|
|
||
|
|
std::cout << " Creating output directory: " << config.output_directory << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
|
||
|
|
if (!m_file_output->CreateOutputDirectory()) {
|
||
|
|
std::cerr << "Failed to create output directory" << std::endl;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::cout << "Output configured: " << config.output_directory << "/" << config.filename_prefix << "_XXXXX.bmp" << std::endl;
|
||
|
|
std::cout << std::endl;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool HeadlessDecoder::ProcessAllFrames() {
|
||
|
|
m_processed_frames = 0;
|
||
|
|
m_successful_frames = 0;
|
||
|
|
m_failed_frames = 0;
|
||
|
|
|
||
|
|
uint64_t total_frames = m_metadata.total_frames;
|
||
|
|
if (total_frames == 0) total_frames = 1000; // 알 수 없는 경우 추정값
|
||
|
|
|
||
|
|
std::cout << " Expected total frames: " << total_frames << std::endl;
|
||
|
|
std::cout << " Starting frame processing loop..." << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
|
||
|
|
VideoPacket packet;
|
||
|
|
VideoFrame frame;
|
||
|
|
|
||
|
|
std::cout << " Reading first packet..." << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
|
||
|
|
while (m_file_reader->ReadNextPacket(packet)) {
|
||
|
|
m_processed_frames++;
|
||
|
|
|
||
|
|
if (m_processed_frames <= 5) { // 처음 5프레임만 상세 로그
|
||
|
|
std::cout << " Processing frame #" << m_processed_frames << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 디코딩 시도
|
||
|
|
if (m_decoder->DecodeFrame(packet, frame)) {
|
||
|
|
if (m_processed_frames <= 5) {
|
||
|
|
std::cout << " Decoding successful, saving frame..." << std::endl;
|
||
|
|
std::cout.flush();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 프레임 저장
|
||
|
|
auto save_result = m_file_output->SaveFrame(frame, m_processed_frames);
|
||
|
|
|
||
|
|
if (save_result.success) {
|
||
|
|
m_successful_frames++;
|
||
|
|
if (m_processed_frames <= 5) {
|
||
|
|
std::cout << " Frame saved successfully" << std::endl;
|
||
|
|
}
|
||
|
|
if (m_verbose && m_successful_frames % 10 == 0) {
|
||
|
|
PrintProgress(m_processed_frames, total_frames);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
m_failed_frames++;
|
||
|
|
std::cerr << "Failed to save frame #" << m_processed_frames << ": " << save_result.error_message << std::endl;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
m_failed_frames++;
|
||
|
|
if (m_processed_frames <= 5 || m_verbose) {
|
||
|
|
std::cerr << "Failed to decode frame #" << m_processed_frames << std::endl;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 진행률 출력 (매 30프레임마다)
|
||
|
|
if (m_processed_frames % 30 == 0) {
|
||
|
|
PrintProgress(m_processed_frames, total_frames);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 처음 몇 프레임만 처리하고 테스트 (디버그용)
|
||
|
|
if (m_processed_frames >= 10) {
|
||
|
|
std::cout << " Stopping after 10 frames for debug..." << std::endl;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::cout << " Frame processing completed" << std::endl;
|
||
|
|
std::cout << " Total read attempts: " << m_processed_frames << std::endl;
|
||
|
|
std::cout << " Successful decodes: " << m_successful_frames << std::endl;
|
||
|
|
std::cout << " Failed decodes: " << m_failed_frames << std::endl;
|
||
|
|
|
||
|
|
return m_successful_frames > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void HeadlessDecoder::PrintProgress(uint64_t current_frame, uint64_t total_frames) {
|
||
|
|
double percentage = 0.0;
|
||
|
|
if (total_frames > 0) {
|
||
|
|
percentage = (double)current_frame / total_frames * 100.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::cout << "\rProgress: " << current_frame << "/" << total_frames
|
||
|
|
<< " (" << std::fixed << std::setprecision(1) << percentage << "%) "
|
||
|
|
<< "Success: " << m_successful_frames
|
||
|
|
<< " Failed: " << m_failed_frames << std::flush;
|
||
|
|
}
|
||
|
|
|
||
|
|
void HeadlessDecoder::PrintSummary() {
|
||
|
|
auto end_time = std::chrono::high_resolution_clock::now();
|
||
|
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - m_start_time);
|
||
|
|
|
||
|
|
std::cout << std::endl << std::endl;
|
||
|
|
std::cout << "=== Processing Summary ===" << std::endl;
|
||
|
|
std::cout << "Total frames processed: " << m_processed_frames << std::endl;
|
||
|
|
std::cout << "Successfully decoded: " << m_successful_frames << std::endl;
|
||
|
|
std::cout << "Failed to decode: " << m_failed_frames << std::endl;
|
||
|
|
std::cout << "Processing time: " << duration.count() << " ms" << std::endl;
|
||
|
|
|
||
|
|
if (duration.count() > 0 && m_successful_frames > 0) {
|
||
|
|
double fps = (double)m_successful_frames / duration.count() * 1000.0;
|
||
|
|
std::cout << "Average FPS: " << std::fixed << std::setprecision(2) << fps << std::endl;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (m_successful_frames > 0) {
|
||
|
|
std::cout << "Output files saved to: output_frames/" << std::endl;
|
||
|
|
std::cout << "Success rate: " << std::fixed << std::setprecision(1)
|
||
|
|
<< (double)m_successful_frames / m_processed_frames * 100.0 << "%" << std::endl;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::cout << std::endl;
|
||
|
|
|
||
|
|
if (m_successful_frames == 0) {
|
||
|
|
std::cout << "No frames were successfully processed!" << std::endl;
|
||
|
|
} else {
|
||
|
|
std::cout << "Processing completed successfully!" << std::endl;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace Vav2Player
|