Optimization bmp file writing
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user