Optimization bmp file writing

This commit is contained in:
2025-09-20 01:32:46 +09:00
parent 8a53de4035
commit bcf090304d
2 changed files with 107 additions and 49 deletions

View File

@@ -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<const char*>(&header.file_type), sizeof(header.file_type));
file.write(reinterpret_cast<const char*>(&header.file_size), sizeof(header.file_size));
file.write(reinterpret_cast<const char*>(&header.reserved1), sizeof(header.reserved1));
file.write(reinterpret_cast<const char*>(&header.reserved2), sizeof(header.reserved2));
file.write(reinterpret_cast<const char*>(&header.offset_data), sizeof(header.offset_data));
// 정보 헤더 쓰기
file.write(reinterpret_cast<const char*>(&header.size), sizeof(header.size));
file.write(reinterpret_cast<const char*>(&header.width), sizeof(header.width));
file.write(reinterpret_cast<const char*>(&header.height), sizeof(header.height));
file.write(reinterpret_cast<const char*>(&header.planes), sizeof(header.planes));
file.write(reinterpret_cast<const char*>(&header.bit_count), sizeof(header.bit_count));
file.write(reinterpret_cast<const char*>(&header.compression), sizeof(header.compression));
file.write(reinterpret_cast<const char*>(&header.size_image), sizeof(header.size_image));
file.write(reinterpret_cast<const char*>(&header.x_pixels_per_meter), sizeof(header.x_pixels_per_meter));
file.write(reinterpret_cast<const char*>(&header.y_pixels_per_meter), sizeof(header.y_pixels_per_meter));
file.write(reinterpret_cast<const char*>(&header.colors_used), sizeof(header.colors_used));
file.write(reinterpret_cast<const char*>(&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<int32_t>(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<const char*>(&b), 1);
file.write(reinterpret_cast<const char*>(&g), 1);
file.write(reinterpret_cast<const char*>(&r), 1);
}
// 패딩 추가
if (padding > 0) {
file.write(reinterpret_cast<const char*>(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<uint8_t*>(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<int32_t>(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<size_t>(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

View File

@@ -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