diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index b97c82e43f..a548f4ad1e 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -385,6 +385,7 @@ set(pcsx2USBSources USB/qemu-usb/input-keymap-qcode-to-qnum.cpp USB/qemu-usb/usb-ohci.cpp USB/shared/ringbuffer.cpp + USB/usb-eyetoy/cam-jpeg.cpp USB/usb-eyetoy/jo_mpeg.cpp USB/usb-eyetoy/usb-eyetoy-webcam.cpp USB/usb-hid/usb-hid.cpp @@ -414,6 +415,7 @@ set(pcsx2USBHeaders USB/qemu-usb/queue.h USB/qemu-usb/qusb.h USB/shared/ringbuffer.h + USB/usb-eyetoy/cam-jpeg.h USB/usb-eyetoy/jo_mpeg.h USB/usb-eyetoy/ov519.h USB/usb-eyetoy/usb-eyetoy-webcam.h diff --git a/pcsx2/USB/usb-eyetoy/cam-jpeg.cpp b/pcsx2/USB/usb-eyetoy/cam-jpeg.cpp new file mode 100644 index 0000000000..e9f8114ba9 --- /dev/null +++ b/pcsx2/USB/usb-eyetoy/cam-jpeg.cpp @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#include "cam-jpeg.h" + +#include "common/Console.h" + +#include + +#include + +namespace +{ + struct JPEGErrorHandler + { + jpeg_error_mgr err; + jmp_buf jbuf; + }; +} // namespace + +static bool HandleJPEGError(JPEGErrorHandler* eh) +{ + jpeg_std_error(&eh->err); + + eh->err.error_exit = [](j_common_ptr cinfo) { + JPEGErrorHandler* eh = (JPEGErrorHandler*)cinfo->err; + char msg[JMSG_LENGTH_MAX]; + eh->err.format_message(cinfo, msg); + Console.ErrorFmt("libjpeg fatal error: {}", msg); + longjmp(eh->jbuf, 1); + }; + + if (setjmp(eh->jbuf) == 0) + return true; + + return false; +} + +bool CompressCamJPEG(std::vector* buffer, const u8* image, u32 width, u32 height, int quality) +{ + struct MemCallback + { + jpeg_destination_mgr mgr; + std::vector* buffer; + size_t buffer_used; + }; + + JPEGErrorHandler err; + if (!HandleJPEGError(&err)) + return false; + + MemCallback cb; + cb.buffer = buffer; + cb.buffer_used = 0; + cb.mgr.next_output_byte = buffer->data(); + cb.mgr.free_in_buffer = buffer->size(); + cb.mgr.init_destination = [](j_compress_ptr cinfo) {}; + cb.mgr.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean { + MemCallback* cb = (MemCallback*)cinfo->dest; + + // double size + cb->buffer_used = cb->buffer->size(); + cb->buffer->resize(cb->buffer->size() * 2); + cb->mgr.next_output_byte = cb->buffer->data() + cb->buffer_used; + cb->mgr.free_in_buffer = cb->buffer->size() - cb->buffer_used; + return TRUE; + }; + cb.mgr.term_destination = [](j_compress_ptr cinfo) { + MemCallback* cb = (MemCallback*)cinfo->dest; + + // get final size + cb->buffer->resize(cb->buffer->size() - cb->mgr.free_in_buffer); + }; + + jpeg_compress_struct info; + info.err = &err.err; + jpeg_create_compress(&info); + info.dest = &cb.mgr; + + info.image_width = width; + info.image_height = height; + info.in_color_space = JCS_RGB; + info.input_components = 3; + + jpeg_set_defaults(&info); + jpeg_set_quality(&info, quality, TRUE); + + // H2V1 + info.comp_info[0].h_samp_factor = 2; + info.comp_info[0].v_samp_factor = 1; + info.comp_info[1].h_samp_factor = 1; + info.comp_info[1].v_samp_factor = 1; + info.comp_info[2].h_samp_factor = 1; + info.comp_info[2].v_samp_factor = 1; + + jpeg_start_compress(&info, TRUE); + + bool result = true; + for (u32 y = 0; y < info.image_height; y++) + { + u8* scanline_buffer[1] = { const_cast(image + (y * width * 3)) }; + if (jpeg_write_scanlines(&info, scanline_buffer, 1) != 1) + { + Console.ErrorFmt("jpeg_write_scanlines() failed at row {}", y); + result = false; + break; + } + } + + jpeg_finish_compress(&info); + jpeg_destroy_compress(&info); + return result; +} + +bool DecompressCamJPEG(std::vector* buffer, u32* width, u32* height, const u8* data, size_t data_size) +{ + JPEGErrorHandler err; + if (!HandleJPEGError(&err)) + return false; + + jpeg_decompress_struct info; + info.err = &err.err; + jpeg_create_decompress(&info); + jpeg_mem_src(&info, static_cast(data), data_size); + + const int herr = jpeg_read_header(&info, TRUE); + if (herr != JPEG_HEADER_OK) + { + Console.ErrorFmt("jpeg_read_header() returned {}", herr); + return false; + } + + if (info.image_width == 0 || info.image_height == 0 || info.num_components < 3) + { + Console.ErrorFmt("Invalid image dimensions: {}x{}x{}", info.image_width, info.image_height, info.num_components); + return false; + } + + info.out_color_space = JCS_RGB; + info.out_color_components = 3; + + if (!jpeg_start_decompress(&info)) + { + Console.ErrorFmt("jpeg_start_decompress() returned failure"); + return false; + } + + buffer->resize(info.image_width * info.image_height * 3); + + bool result = true; + for (u32 y = 0; y < info.image_height; y++) + { + u8* scanline_buffer[1] = { buffer->data() + (y * info.image_width * 3) }; + if (jpeg_read_scanlines(&info, scanline_buffer, 1) != 1) + { + Console.ErrorFmt("jpeg_read_scanlines() failed at row {}", y); + result = false; + break; + } + } + + jpeg_finish_decompress(&info); + jpeg_destroy_decompress(&info); + return result; +} diff --git a/pcsx2/USB/usb-eyetoy/cam-jpeg.h b/pcsx2/USB/usb-eyetoy/cam-jpeg.h new file mode 100644 index 0000000000..6653b81105 --- /dev/null +++ b/pcsx2/USB/usb-eyetoy/cam-jpeg.h @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#include "common/Pcsx2Defs.h" + +#include + +bool CompressCamJPEG(std::vector* buffer, const u8* image, u32 width, u32 height, int quality); +bool DecompressCamJPEG(std::vector* buffer, u32* width, u32* height, const u8* data, size_t data_size); \ No newline at end of file diff --git a/pcsx2/USB/usb-eyetoy/cam-linux.cpp b/pcsx2/USB/usb-eyetoy/cam-linux.cpp index d178000cd1..933ed250e0 100644 --- a/pcsx2/USB/usb-eyetoy/cam-linux.cpp +++ b/pcsx2/USB/usb-eyetoy/cam-linux.cpp @@ -1,10 +1,9 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #include "cam-linux.h" +#include "cam-jpeg.h" #include "usb-eyetoy-webcam.h" -#include "jpgd.h" -#include "jpge.h" #include "jo_mpeg.h" #include "common/Console.h" @@ -58,22 +57,23 @@ namespace usb_eyetoy static void store_mpeg_frame(const unsigned char* data, const unsigned int len) { mpeg_mutex.lock(); - memcpy(mpeg_buffer.start, data, len); + if (len > 0) + memcpy(mpeg_buffer.start, data, len); mpeg_buffer.length = len; mpeg_mutex.unlock(); } static void process_image(const unsigned char* data, int size) { - const int bytesPerPixel = 3; - int comprBufSize = frame_width * frame_height * bytesPerPixel; + constexpr int bytesPerPixel = 3; + const size_t comprBufSize = frame_width * frame_height * bytesPerPixel; if (pixelformat == V4L2_PIX_FMT_YUYV) { - unsigned char* comprBuf = (unsigned char*)calloc(1, comprBufSize); - int comprLen = 0; + std::vector comprBuf(comprBufSize); if (frame_format == format_mpeg) { - comprLen = jo_write_mpeg(comprBuf, data, frame_width, frame_height, JO_YUYV, mirroring_enabled ? JO_FLIP_X : JO_NONE, JO_NONE); + const size_t comprLen = jo_write_mpeg(comprBuf.data(), data, frame_width, frame_height, JO_YUYV, mirroring_enabled ? JO_FLIP_X : JO_NONE, JO_NONE); + comprBuf.resize(comprLen); } else if (frame_format == format_jpeg) { @@ -105,14 +105,10 @@ namespace usb_eyetoy dst[5] = (b > 255) ? 255 : ((b < 0) ? 0 : b); } } - jpge::params params; - params.m_quality = 80; - params.m_subsampling = jpge::H2V1; - comprLen = comprBufSize; - if (!jpge::compress_image_to_jpeg_file_in_memory(comprBuf, comprLen, frame_width, frame_height, 3, data2, params)) - { - comprLen = 0; - } + + if (!CompressCamJPEG(&comprBuf, data2, frame_width, frame_height, 80)) + comprBuf.clear(); + free(data2); } else if (frame_format == format_yuv400) @@ -135,22 +131,26 @@ namespace usb_eyetoy comprBuf[in_pos++] = src[0];//Y } } - comprLen = 80 * 64; + comprBuf.resize(80 * 64); } - store_mpeg_frame(comprBuf, comprLen); - free(comprBuf); + else + { + comprBuf.clear(); + } + store_mpeg_frame(comprBuf.data(), static_cast(comprBuf.size())); } else if (pixelformat == V4L2_PIX_FMT_JPEG) { if (frame_format == format_mpeg) { - int width, height, actual_comps; - unsigned char* rgbData = jpgd::decompress_jpeg_image_from_memory(data, size, &width, &height, &actual_comps, 3); - unsigned char* comprBuf = (unsigned char*)calloc(1, comprBufSize); - int comprLen = jo_write_mpeg(comprBuf, rgbData, frame_width, frame_height, JO_RGB24, mirroring_enabled ? JO_FLIP_X : JO_NONE, JO_NONE); - free(rgbData); - store_mpeg_frame(comprBuf, comprLen); - free(comprBuf); + std::vector rgbData; + u32 width, height; + if (DecompressCamJPEG(&rgbData, &width, &height, data, size)) + { + std::vector comprBuf(comprBufSize); + const size_t comprLen = jo_write_mpeg(comprBuf.data(), rgbData.data(), frame_width, frame_height, JO_RGB24, mirroring_enabled ? JO_FLIP_X : JO_NONE, JO_NONE); + store_mpeg_frame(comprBuf.data(), comprLen); + } } else if (frame_format == format_jpeg) { @@ -158,35 +158,35 @@ namespace usb_eyetoy } else if (frame_format == format_yuv400) { - int width, height, actual_comps; - unsigned char* rgbData = jpgd::decompress_jpeg_image_from_memory(data, size, &width, &height, &actual_comps, 3); - unsigned char* comprBuf = (unsigned char*)calloc(1, comprBufSize); - int comprLen = 0; - int in_pos = 0; - for (int my = 0; my < 8; my++) - for (int mx = 0; mx < 10; mx++) - for (int y = 0; y < 8; y++) - for (int x = 0; x < 8; x++) - { - int srcx = 4* (8*mx + x); - int srcy = 4* (8*my + y); - unsigned char* src = rgbData + (srcy * frame_width + srcx) * bytesPerPixel; - if (srcy >= frame_height) + std::vector rgbData; + u32 width, height; + if (DecompressCamJPEG(&rgbData, &width, &height, data, size)) + { + const size_t comprLen = 80 * 64; + std::vector comprBuf(comprLen); + int in_pos = 0; + for (int my = 0; my < 8; my++) + for (int mx = 0; mx < 10; mx++) + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) { - comprBuf[in_pos++] = 0x01; + int srcx = 4* (8*mx + x); + int srcy = 4* (8*my + y); + unsigned char* src = rgbData.data() + (srcy * frame_width + srcx) * bytesPerPixel; + if (srcy >= frame_height) + { + comprBuf[in_pos++] = 0x01; + } + else + { + float r = src[0]; + float g = src[1]; + float b = src[2]; + comprBuf[in_pos++] = 0.299f * r + 0.587f * g + 0.114f * b; + } } - else - { - float r = src[0]; - float g = src[1]; - float b = src[2]; - comprBuf[in_pos++] = 0.299f * r + 0.587f * g + 0.114f * b; - } - } - comprLen = 80 * 64; - free(rgbData); - store_mpeg_frame(comprBuf, comprLen); - free(comprBuf); + store_mpeg_frame(comprBuf.data(), comprLen); + } } } else @@ -536,8 +536,8 @@ namespace usb_eyetoy void create_dummy_frame_eyetoy() { - const int bytesPerPixel = 3; - int comprBufSize = frame_width * frame_height * bytesPerPixel; + constexpr int bytesPerPixel = 3; + const int comprBufSize = frame_width * frame_height * bytesPerPixel; unsigned char* rgbData = (unsigned char*)calloc(1, comprBufSize); for (int y = 0; y < frame_height; y++) { @@ -549,27 +549,25 @@ namespace usb_eyetoy ptr[2] = 255 * y / frame_height; } } - unsigned char* comprBuf = (unsigned char*)calloc(1, comprBufSize); - int comprLen = 0; + + std::vector comprBuf(comprBufSize); if (frame_format == format_mpeg) { - comprLen = jo_write_mpeg(comprBuf, rgbData, frame_width, frame_height, JO_RGB24, JO_NONE, JO_NONE); + const size_t comprLen = jo_write_mpeg(comprBuf.data(), rgbData, frame_width, frame_height, JO_RGB24, JO_NONE, JO_NONE); + comprBuf.resize(comprLen); } else if (frame_format == format_jpeg) { - jpge::params params; - params.m_quality = 80; - params.m_subsampling = jpge::H2V1; - comprLen = comprBufSize; - if (!jpge::compress_image_to_jpeg_file_in_memory(comprBuf, comprLen, frame_width, frame_height, 3, rgbData, params)) - { - comprLen = 0; - } + if (!CompressCamJPEG(&comprBuf, rgbData, frame_width, frame_height, 80)) + comprBuf.clear(); + } + else + { + comprBuf.clear(); } free(rgbData); - store_mpeg_frame(comprBuf, comprLen); - free(comprBuf); + store_mpeg_frame(comprBuf.data(), static_cast(comprBuf.size())); } void create_dummy_frame_ov511p() diff --git a/pcsx2/USB/usb-eyetoy/cam-windows.cpp b/pcsx2/USB/usb-eyetoy/cam-windows.cpp index 605cb30b19..dd0db0fddc 100644 --- a/pcsx2/USB/usb-eyetoy/cam-windows.cpp +++ b/pcsx2/USB/usb-eyetoy/cam-windows.cpp @@ -1,16 +1,18 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #include "common/Console.h" #include "common/StringUtil.h" -#include "jpge.h" #include "videodev.h" +#include "cam-jpeg.h" #include "cam-windows.h" #include "usb-eyetoy-webcam.h" #include "jo_mpeg.h" #include "USB/USB.h" +#include + #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmicrosoft-goto" @@ -373,7 +375,8 @@ namespace usb_eyetoy void store_mpeg_frame(const unsigned char* data, const unsigned int len) { mpeg_mutex.lock(); - memcpy(mpeg_buffer.start, data, len); + if (len > 0) + memcpy(mpeg_buffer.start, data, len); mpeg_buffer.length = len; mpeg_mutex.unlock(); } @@ -383,13 +386,13 @@ namespace usb_eyetoy if (bitsperpixel == 24) { const int bytesPerPixel = 3; - int comprBufSize = frame_width * frame_height * bytesPerPixel; - unsigned char* comprBuf = (unsigned char*)calloc(1, comprBufSize); - int comprLen = 0; + const size_t comprBufSize = frame_width * frame_height * bytesPerPixel; + std::vector comprBuf(comprBufSize); if (frame_format == format_mpeg) { - comprLen = jo_write_mpeg( - comprBuf, data, frame_width, frame_height, JO_BGR24, mirroring_enabled ? JO_FLIP_X : JO_NONE, JO_FLIP_Y); + const size_t comprLen = jo_write_mpeg( + comprBuf.data(), data, frame_width, frame_height, JO_BGR24, mirroring_enabled ? JO_FLIP_X : JO_NONE, JO_FLIP_Y); + comprBuf.resize(comprBufSize); } else if (frame_format == format_jpeg) { @@ -407,14 +410,9 @@ namespace usb_eyetoy } } - jpge::params params; - params.m_quality = 80; - params.m_subsampling = jpge::H2V1; - comprLen = comprBufSize; - if (!jpge::compress_image_to_jpeg_file_in_memory(comprBuf, comprLen, frame_width, frame_height, 3, data2, params)) - { - comprLen = 0; - } + if (!CompressCamJPEG(&comprBuf, data2, frame_width, frame_height, 80)) + comprBuf.clear(); + free(data2); } else if (frame_format == format_yuv400) @@ -440,10 +438,13 @@ namespace usb_eyetoy comprBuf[in_pos++] = 0.299f * r + 0.587f * g + 0.114f * b; } } - comprLen = 80 * 64; + comprBuf.resize(80 * 64); } - store_mpeg_frame(comprBuf, comprLen); - free(comprBuf); + else + { + comprBuf.clear(); + } + store_mpeg_frame(comprBuf.data(), static_cast(comprBuf.size())); } else { @@ -453,8 +454,8 @@ namespace usb_eyetoy void create_dummy_frame_eyetoy() { - const int bytesPerPixel = 3; - int comprBufSize = frame_width * frame_height * bytesPerPixel; + constexpr int bytesPerPixel = 3; + const int comprBufSize = frame_width * frame_height * bytesPerPixel; unsigned char* rgbData = (unsigned char*)calloc(1, comprBufSize); for (int y = 0; y < frame_height; y++) { @@ -466,27 +467,25 @@ namespace usb_eyetoy ptr[2] = 255 * y / frame_height; } } - unsigned char* comprBuf = (unsigned char*)calloc(1, comprBufSize); - int comprLen = 0; + + std::vector comprBuf(comprBufSize); if (frame_format == format_mpeg) { - comprLen = jo_write_mpeg(comprBuf, rgbData, frame_width, frame_height, JO_RGB24, JO_NONE, JO_NONE); + const size_t comprLen = jo_write_mpeg(comprBuf.data(), rgbData, frame_width, frame_height, JO_RGB24, JO_NONE, JO_NONE); + comprBuf.resize(comprBufSize); } else if (frame_format == format_jpeg) { - jpge::params params; - params.m_quality = 80; - params.m_subsampling = jpge::H2V1; - comprLen = comprBufSize; - if (!jpge::compress_image_to_jpeg_file_in_memory(comprBuf, comprLen, frame_width, frame_height, 3, rgbData, params)) - { - comprLen = 0; - } + if (!CompressCamJPEG(&comprBuf, rgbData, frame_width, frame_height, 80)) + comprBuf.clear(); + } + else + { + comprBuf.clear(); } free(rgbData); - store_mpeg_frame(comprBuf, comprLen); - free(comprBuf); + store_mpeg_frame(comprBuf.data(), static_cast(comprBuf.size())); } void create_dummy_frame_ov511p() diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index 26d833b3b9..582a75a339 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -327,6 +327,7 @@ + @@ -676,6 +677,7 @@ + diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index 1902395aa5..371f98fa46 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -1407,6 +1407,9 @@ System\Ps2\Iop\SIO\PAD + + System\Ps2\USB\usb-eyetoy + @@ -2324,6 +2327,9 @@ System\Ps2\Iop\SIO\PAD + + System\Ps2\USB\usb-eyetoy +