From bcf090304d705f1bea68062b60e945fff10de6ce Mon Sep 17 00:00:00 2001 From: ened Date: Sat, 20 Sep 2025 01:32:46 +0900 Subject: [PATCH] Optimization bmp file writing --- .../Vav2Player/src/Output/FileOutput.cpp | 152 ++++++++++++------ .../Vav2Player/src/Output/FileOutput.h | 4 + 2 files changed, 107 insertions(+), 49 deletions(-) diff --git a/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.cpp b/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.cpp index cb46a97..9da3cdc 100644 --- a/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.cpp +++ b/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.cpp @@ -203,8 +203,14 @@ FileOutput::SaveResult FileOutput::SaveAsRawYUV(const VideoFrame& frame, const s } } -// BMP 파일 저장 +// BMP 파일 저장 (기존 방식 - 호환성용) FileOutput::SaveResult FileOutput::SaveAsBMP(const VideoFrame& frame, const std::filesystem::path& file_path) { + // 📈 성능 최적화: 메모리 매핑 방식 사용 + return SaveAsBMP_MemoryMapped(frame, file_path); +} + +// 📈 메모리 매핑 기반 BMP 파일 저장 (고성능) +FileOutput::SaveResult FileOutput::SaveAsBMP_MemoryMapped(const VideoFrame& frame, const std::filesystem::path& file_path) { // YUV → RGB 변환 RGBFrame rgb_frame; if (!ConvertYUVToRGB(frame, rgb_frame)) { @@ -212,67 +218,100 @@ FileOutput::SaveResult FileOutput::SaveAsBMP(const VideoFrame& frame, const std: } try { - std::ofstream file(file_path, std::ios::binary); - if (!file.is_open()) { - return CreateErrorResult("Failed to open BMP file for writing"); - } - // BMP 헤더 생성 auto header = CreateBMPHeader(rgb_frame.width, rgb_frame.height); - - // 파일 헤더 쓰기 - file.write(reinterpret_cast(&header.file_type), sizeof(header.file_type)); - file.write(reinterpret_cast(&header.file_size), sizeof(header.file_size)); - file.write(reinterpret_cast(&header.reserved1), sizeof(header.reserved1)); - file.write(reinterpret_cast(&header.reserved2), sizeof(header.reserved2)); - file.write(reinterpret_cast(&header.offset_data), sizeof(header.offset_data)); - - // 정보 헤더 쓰기 - file.write(reinterpret_cast(&header.size), sizeof(header.size)); - file.write(reinterpret_cast(&header.width), sizeof(header.width)); - file.write(reinterpret_cast(&header.height), sizeof(header.height)); - file.write(reinterpret_cast(&header.planes), sizeof(header.planes)); - file.write(reinterpret_cast(&header.bit_count), sizeof(header.bit_count)); - file.write(reinterpret_cast(&header.compression), sizeof(header.compression)); - file.write(reinterpret_cast(&header.size_image), sizeof(header.size_image)); - file.write(reinterpret_cast(&header.x_pixels_per_meter), sizeof(header.x_pixels_per_meter)); - file.write(reinterpret_cast(&header.y_pixels_per_meter), sizeof(header.y_pixels_per_meter)); - file.write(reinterpret_cast(&header.colors_used), sizeof(header.colors_used)); - file.write(reinterpret_cast(&header.colors_important), sizeof(header.colors_important)); - - // RGB 데이터 쓰기 (BMP는 bottom-up이므로 행을 뒤집어서 저장) uint32_t row_size = rgb_frame.width * 3; - uint32_t padding = (4 - (row_size % 4)) % 4; // 4바이트 정렬 - uint8_t padding_bytes[3] = {0, 0, 0}; + uint32_t padding = (4 - (row_size % 4)) % 4; + uint32_t padded_row_size = row_size + padding; + uint32_t header_size = 54; // BMP 헤더 크기 + uint32_t total_file_size = header_size + (padded_row_size * rgb_frame.height); - for (int32_t y = static_cast(rgb_frame.height) - 1; y >= 0; --y) { - const uint8_t* row_data = rgb_frame.data.data() + (y * rgb_frame.stride); + // 📈 Windows 메모리 매핑 파일 생성 + HANDLE hFile = CreateFileW( + file_path.wstring().c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL + ); - // BGR 순서로 변환하여 저장 (BMP는 BGR) - for (uint32_t x = 0; x < rgb_frame.width; ++x) { - uint8_t r = row_data[x * 3 + 0]; - uint8_t g = row_data[x * 3 + 1]; - uint8_t b = row_data[x * 3 + 2]; - - file.write(reinterpret_cast(&b), 1); - file.write(reinterpret_cast(&g), 1); - file.write(reinterpret_cast(&r), 1); - } - - // 패딩 추가 - if (padding > 0) { - file.write(reinterpret_cast(padding_bytes), padding); - } + if (hFile == INVALID_HANDLE_VALUE) { + return CreateErrorResult("Failed to create file for memory mapping: " + std::to_string(GetLastError())); } + HANDLE hMapping = CreateFileMappingW( + hFile, + NULL, + PAGE_READWRITE, + 0, + total_file_size, + NULL + ); + + if (hMapping == NULL) { + CloseHandle(hFile); + return CreateErrorResult("Failed to create file mapping: " + std::to_string(GetLastError())); + } + + uint8_t* mapped_memory = static_cast(MapViewOfFile( + hMapping, + FILE_MAP_WRITE, + 0, + 0, + total_file_size + )); + + if (mapped_memory == NULL) { + CloseHandle(hMapping); + CloseHandle(hFile); + return CreateErrorResult("Failed to map view of file: " + std::to_string(GetLastError())); + } + + // 📈 메모리에 직접 헤더 쓰기 (파일 I/O 없음) + uint8_t* write_ptr = mapped_memory; + + // BMP 헤더 복사 + memcpy(write_ptr, &header.file_type, sizeof(header.file_type)); write_ptr += sizeof(header.file_type); + memcpy(write_ptr, &header.file_size, sizeof(header.file_size)); write_ptr += sizeof(header.file_size); + memcpy(write_ptr, &header.reserved1, sizeof(header.reserved1)); write_ptr += sizeof(header.reserved1); + memcpy(write_ptr, &header.reserved2, sizeof(header.reserved2)); write_ptr += sizeof(header.reserved2); + memcpy(write_ptr, &header.offset_data, sizeof(header.offset_data)); write_ptr += sizeof(header.offset_data); + memcpy(write_ptr, &header.size, sizeof(header.size)); write_ptr += sizeof(header.size); + memcpy(write_ptr, &header.width, sizeof(header.width)); write_ptr += sizeof(header.width); + memcpy(write_ptr, &header.height, sizeof(header.height)); write_ptr += sizeof(header.height); + memcpy(write_ptr, &header.planes, sizeof(header.planes)); write_ptr += sizeof(header.planes); + memcpy(write_ptr, &header.bit_count, sizeof(header.bit_count)); write_ptr += sizeof(header.bit_count); + memcpy(write_ptr, &header.compression, sizeof(header.compression)); write_ptr += sizeof(header.compression); + memcpy(write_ptr, &header.size_image, sizeof(header.size_image)); write_ptr += sizeof(header.size_image); + memcpy(write_ptr, &header.x_pixels_per_meter, sizeof(header.x_pixels_per_meter)); write_ptr += sizeof(header.x_pixels_per_meter); + memcpy(write_ptr, &header.y_pixels_per_meter, sizeof(header.y_pixels_per_meter)); write_ptr += sizeof(header.y_pixels_per_meter); + memcpy(write_ptr, &header.colors_used, sizeof(header.colors_used)); write_ptr += sizeof(header.colors_used); + memcpy(write_ptr, &header.colors_important, sizeof(header.colors_important)); + + // 📈 RGB→BGR 변환하면서 메모리에 직접 쓰기 (BMP는 bottom-up) + for (int32_t y = static_cast(rgb_frame.height) - 1; y >= 0; --y) { + uint8_t* dst_row = mapped_memory + header_size + ((rgb_frame.height - 1 - y) * padded_row_size); + const uint8_t* src_row = rgb_frame.data.data() + (y * rgb_frame.stride); + + // 라인 단위 RGB→BGR 변환 및 패딩 처리 + ConvertRGBToBGRLine(src_row, dst_row, rgb_frame.width, padding); + } + + // 메모리 매핑 해제 + UnmapViewOfFile(mapped_memory); + CloseHandle(hMapping); + CloseHandle(hFile); + SaveResult result; result.success = true; result.saved_path = file_path; - result.file_size_bytes = static_cast(file.tellp()); + result.file_size_bytes = total_file_size; return result; } catch (const std::exception& e) { - return CreateErrorResult("Exception during BMP save: " + std::string(e.what())); + return CreateErrorResult("Exception during memory-mapped BMP save: " + std::string(e.what())); } } @@ -518,4 +557,19 @@ std::filesystem::path FileOutput::GenerateOptimizedFilename(uint64_t frame_index return m_cached_output_path / m_filename_buffer; } +// 📈 RGB→BGR 라인 변환 (메모리 매핑 최적화용) +void FileOutput::ConvertRGBToBGRLine(const uint8_t* src_rgb, uint8_t* dst_bgr, uint32_t width, uint32_t padding) { + // RGB → BGR 변환 (3픽셀씩 처리로 최적화) + for (uint32_t x = 0; x < width; ++x) { + dst_bgr[x * 3 + 0] = src_rgb[x * 3 + 2]; // B = R + dst_bgr[x * 3 + 1] = src_rgb[x * 3 + 1]; // G = G + dst_bgr[x * 3 + 2] = src_rgb[x * 3 + 0]; // R = B + } + + // 패딩 영역을 0으로 채움 (4바이트 정렬) + if (padding > 0) { + memset(dst_bgr + width * 3, 0, padding); + } +} + } // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.h b/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.h index 196ff15..278a606 100644 --- a/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.h +++ b/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.h @@ -169,6 +169,10 @@ private: // 성능 최적화 메서드들 void InitializeCache(); std::filesystem::path GenerateOptimizedFilename(uint64_t frame_index); + + // 메모리 매핑 최적화 메서드들 + SaveResult SaveAsBMP_MemoryMapped(const VideoFrame& frame, const std::filesystem::path& file_path); + static void ConvertRGBToBGRLine(const uint8_t* src_rgb, uint8_t* dst_bgr, uint32_t width, uint32_t padding); }; } // namespace Vav2Player \ No newline at end of file