|
| 1 | +#include "stdafx.h" |
| 2 | +#include "camera_video_sink.h" |
| 3 | + |
| 4 | +#include "Emu/Cell/Modules/cellCamera.h" |
| 5 | +#include "Emu/system_config.h" |
| 6 | + |
| 7 | +LOG_CHANNEL(camera_log, "Camera"); |
| 8 | + |
| 9 | +camera_video_sink::camera_video_sink(bool front_facing) |
| 10 | + : m_front_facing(front_facing) |
| 11 | +{ |
| 12 | +} |
| 13 | + |
| 14 | +camera_video_sink::~camera_video_sink() |
| 15 | +{ |
| 16 | +} |
| 17 | + |
| 18 | +bool camera_video_sink::present(u32 src_width, u32 src_height, u32 src_pitch, u32 src_bytes_per_pixel, std::function<const u8*(u32)> src_line_ptr) |
| 19 | +{ |
| 20 | + ensure(!!src_line_ptr); |
| 21 | + |
| 22 | + const u64 new_size = m_bytesize; |
| 23 | + image_buffer& image_buffer = m_image_buffer[m_write_index]; |
| 24 | + |
| 25 | + // Reset buffer if necessary |
| 26 | + if (image_buffer.data.size() != new_size) |
| 27 | + { |
| 28 | + image_buffer.data.clear(); |
| 29 | + } |
| 30 | + |
| 31 | + // Create buffer if necessary |
| 32 | + if (image_buffer.data.empty() && new_size > 0) |
| 33 | + { |
| 34 | + image_buffer.data.resize(new_size); |
| 35 | + image_buffer.width = m_width; |
| 36 | + image_buffer.height = m_height; |
| 37 | + } |
| 38 | + |
| 39 | + if (!image_buffer.data.empty() && src_width && src_height) |
| 40 | + { |
| 41 | + // Convert image to proper layout |
| 42 | + // TODO: check if pixel format and bytes per pixel match and convert if necessary |
| 43 | + // TODO: implement or improve more conversions |
| 44 | + |
| 45 | + const u32 width = std::min<u32>(image_buffer.width, src_width); |
| 46 | + const u32 height = std::min<u32>(image_buffer.height, src_height); |
| 47 | + |
| 48 | + switch (m_format) |
| 49 | + { |
| 50 | + case CELL_CAMERA_RAW8: // The game seems to expect BGGR |
| 51 | + { |
| 52 | + // Let's use a very simple algorithm to convert the image to raw BGGR |
| 53 | + u8* dst = image_buffer.data.data(); |
| 54 | + |
| 55 | + for (u32 y = 0; y < height; y++) |
| 56 | + { |
| 57 | + const u8* src = src_line_ptr(y); |
| 58 | + const bool is_top_pixel = (y % 2) == 0; |
| 59 | + |
| 60 | + // Split loops (roughly twice the performance by removing one condition) |
| 61 | + if (is_top_pixel) |
| 62 | + { |
| 63 | + for (u32 x = 0; x < width; x++, dst++, src += 4) |
| 64 | + { |
| 65 | + const bool is_left_pixel = (x % 2) == 0; |
| 66 | + |
| 67 | + if (is_left_pixel) |
| 68 | + { |
| 69 | + *dst = src[2]; // Blue |
| 70 | + } |
| 71 | + else |
| 72 | + { |
| 73 | + *dst = src[1]; // Green |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + else |
| 78 | + { |
| 79 | + for (u32 x = 0; x < width; x++, dst++, src += 4) |
| 80 | + { |
| 81 | + const bool is_left_pixel = (x % 2) == 0; |
| 82 | + |
| 83 | + if (is_left_pixel) |
| 84 | + { |
| 85 | + *dst = src[1]; // Green |
| 86 | + } |
| 87 | + else |
| 88 | + { |
| 89 | + *dst = src[0]; // Red |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + } |
| 94 | + break; |
| 95 | + } |
| 96 | + //case CELL_CAMERA_YUV422: |
| 97 | + case CELL_CAMERA_Y0_U_Y1_V: |
| 98 | + case CELL_CAMERA_V_Y1_U_Y0: |
| 99 | + { |
| 100 | + // Simple RGB to Y0_U_Y1_V conversion from stackoverflow. |
| 101 | + constexpr int yuv_bytes_per_pixel = 2; |
| 102 | + const int yuv_pitch = image_buffer.width * yuv_bytes_per_pixel; |
| 103 | + |
| 104 | + const int y0_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3; |
| 105 | + const int u_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2; |
| 106 | + const int y1_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1; |
| 107 | + const int v_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0; |
| 108 | + |
| 109 | + for (u32 y = 0; y < height; y++) |
| 110 | + { |
| 111 | + const u8* src = src_line_ptr(y); |
| 112 | + u8* yuv_row_ptr = &image_buffer.data[y * yuv_pitch]; |
| 113 | + |
| 114 | + for (u32 x = 0; x < width - 1; x += 2, src += 8) |
| 115 | + { |
| 116 | + const f32 r1 = src[0]; |
| 117 | + const f32 g1 = src[1]; |
| 118 | + const f32 b1 = src[2]; |
| 119 | + const f32 r2 = src[4]; |
| 120 | + const f32 g2 = src[5]; |
| 121 | + const f32 b2 = src[6]; |
| 122 | + |
| 123 | + const f32 y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f; |
| 124 | + const f32 u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f; |
| 125 | + const f32 v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f; |
| 126 | + const f32 y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f; |
| 127 | + |
| 128 | + const int yuv_index = x * yuv_bytes_per_pixel; |
| 129 | + yuv_row_ptr[yuv_index + y0_offset] = static_cast<u8>(std::clamp(y0, 0.0f, 255.0f)); |
| 130 | + yuv_row_ptr[yuv_index + u_offset] = static_cast<u8>(std::clamp( u, 0.0f, 255.0f)); |
| 131 | + yuv_row_ptr[yuv_index + y1_offset] = static_cast<u8>(std::clamp(y1, 0.0f, 255.0f)); |
| 132 | + yuv_row_ptr[yuv_index + v_offset] = static_cast<u8>(std::clamp( v, 0.0f, 255.0f)); |
| 133 | + } |
| 134 | + } |
| 135 | + break; |
| 136 | + } |
| 137 | + case CELL_CAMERA_JPG: |
| 138 | + case CELL_CAMERA_RGBA: |
| 139 | + case CELL_CAMERA_RAW10: |
| 140 | + case CELL_CAMERA_YUV420: |
| 141 | + case CELL_CAMERA_FORMAT_UNKNOWN: |
| 142 | + default: |
| 143 | + const u32 bytes_per_line = src_bytes_per_pixel * src_width; |
| 144 | + if (src_pitch == bytes_per_line) |
| 145 | + { |
| 146 | + std::memcpy(image_buffer.data.data(), src_line_ptr(0), std::min<usz>(image_buffer.data.size(), src_height * bytes_per_line)); |
| 147 | + } |
| 148 | + else |
| 149 | + { |
| 150 | + for (u32 y = 0, pos = 0; y < src_height && pos < image_buffer.data.size(); y++, pos += bytes_per_line) |
| 151 | + { |
| 152 | + std::memcpy(&image_buffer.data[pos], src_line_ptr(y), std::min<usz>(image_buffer.data.size() - pos, bytes_per_line)); |
| 153 | + } |
| 154 | + } |
| 155 | + break; |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + camera_log.trace("Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesize=%d", |
| 160 | + m_write_index, m_frame_number.load(), m_width, m_height, m_bytesize); |
| 161 | + |
| 162 | + // Toggle write/read index |
| 163 | + std::lock_guard lock(m_mutex); |
| 164 | + image_buffer.frame_number = m_frame_number++; |
| 165 | + m_write_index = read_index(); |
| 166 | + |
| 167 | + return true; |
| 168 | +} |
| 169 | + |
| 170 | +void camera_video_sink::set_format(s32 format, u32 bytesize) |
| 171 | +{ |
| 172 | + camera_log.notice("Setting format: format=%d, bytesize=%d", format, bytesize); |
| 173 | + |
| 174 | + m_format = format; |
| 175 | + m_bytesize = bytesize; |
| 176 | +} |
| 177 | + |
| 178 | +void camera_video_sink::set_resolution(u32 width, u32 height) |
| 179 | +{ |
| 180 | + camera_log.notice("Setting resolution: width=%d, height=%d", width, height); |
| 181 | + |
| 182 | + m_width = width; |
| 183 | + m_height = height; |
| 184 | +} |
| 185 | + |
| 186 | +void camera_video_sink::set_mirrored(bool mirrored) |
| 187 | +{ |
| 188 | + camera_log.notice("Setting mirrored: mirrored=%d", mirrored); |
| 189 | + |
| 190 | + m_mirrored = mirrored; |
| 191 | +} |
| 192 | + |
| 193 | +u64 camera_video_sink::frame_number() const |
| 194 | +{ |
| 195 | + return m_frame_number.load(); |
| 196 | +} |
| 197 | + |
| 198 | +void camera_video_sink::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read) |
| 199 | +{ |
| 200 | + // Lock read buffer |
| 201 | + std::lock_guard lock(m_mutex); |
| 202 | + const image_buffer& image_buffer = m_image_buffer[read_index()]; |
| 203 | + |
| 204 | + width = image_buffer.width; |
| 205 | + height = image_buffer.height; |
| 206 | + frame_number = image_buffer.frame_number; |
| 207 | + |
| 208 | + // Copy to out buffer |
| 209 | + if (buf && !image_buffer.data.empty()) |
| 210 | + { |
| 211 | + bytes_read = std::min<u64>(image_buffer.data.size(), size); |
| 212 | + std::memcpy(buf, image_buffer.data.data(), bytes_read); |
| 213 | + |
| 214 | + if (image_buffer.data.size() != size) |
| 215 | + { |
| 216 | + camera_log.error("Buffer size mismatch: in=%d, out=%d. Cropping to incoming size. Please contact a developer.", size, image_buffer.data.size()); |
| 217 | + } |
| 218 | + } |
| 219 | + else |
| 220 | + { |
| 221 | + bytes_read = 0; |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +u32 camera_video_sink::read_index() const |
| 226 | +{ |
| 227 | + // The read buffer index cannot be the same as the write index |
| 228 | + return (m_write_index + 1u) % ::narrow<u32>(m_image_buffer.size()); |
| 229 | +} |
| 230 | + |
0 commit comments