2021-10-13 20:18:18 +02:00
# include "stdafx.h"
# include "qt_camera_video_surface.h"
# include "Emu/Cell/Modules/cellCamera.h"
# include "Emu/system_config.h"
2021-10-19 02:59:03 +02:00
# include <QtConcurrent>
2021-10-13 20:18:18 +02:00
LOG_CHANNEL ( camera_log , " Camera " ) ;
qt_camera_video_surface : : qt_camera_video_surface ( bool front_facing , QObject * parent )
: QAbstractVideoSurface ( parent ) , m_front_facing ( front_facing )
{
}
qt_camera_video_surface : : ~ qt_camera_video_surface ( )
{
std : : lock_guard lock ( m_mutex ) ;
// Free memory
for ( auto & image_buffer : m_image_buffer )
{
if ( image_buffer . data )
{
delete [ ] image_buffer . data ;
image_buffer . data = nullptr ;
}
}
}
QList < QVideoFrame : : PixelFormat > qt_camera_video_surface : : supportedPixelFormats ( QAbstractVideoBuffer : : HandleType type ) const
{
Q_UNUSED ( type )
2021-10-22 00:48:31 +02:00
// Support all cameras
2021-10-13 20:18:18 +02:00
QList < QVideoFrame : : PixelFormat > result ;
result
2021-10-22 00:48:31 +02:00
< < QVideoFrame : : Format_ARGB32
< < QVideoFrame : : Format_ARGB32_Premultiplied
2021-10-13 20:18:18 +02:00
< < QVideoFrame : : Format_RGB32
2021-10-22 00:48:31 +02:00
< < QVideoFrame : : Format_RGB24
< < QVideoFrame : : Format_RGB565
< < QVideoFrame : : Format_RGB555
< < QVideoFrame : : Format_ARGB8565_Premultiplied
< < QVideoFrame : : Format_BGRA32
< < QVideoFrame : : Format_BGRA32_Premultiplied
< < QVideoFrame : : Format_BGR32
< < QVideoFrame : : Format_BGR24
< < QVideoFrame : : Format_BGR565
< < QVideoFrame : : Format_BGR555
< < QVideoFrame : : Format_BGRA5658_Premultiplied
< < QVideoFrame : : Format_AYUV444
< < QVideoFrame : : Format_AYUV444_Premultiplied
< < QVideoFrame : : Format_YUV444
< < QVideoFrame : : Format_YUV420P
< < QVideoFrame : : Format_YV12
< < QVideoFrame : : Format_UYVY
< < QVideoFrame : : Format_YUYV
< < QVideoFrame : : Format_NV12
< < QVideoFrame : : Format_NV21
< < QVideoFrame : : Format_IMC1
< < QVideoFrame : : Format_IMC2
< < QVideoFrame : : Format_IMC3
< < QVideoFrame : : Format_IMC4
< < QVideoFrame : : Format_Y8
< < QVideoFrame : : Format_Y16
< < QVideoFrame : : Format_Jpeg
< < QVideoFrame : : Format_CameraRaw
< < QVideoFrame : : Format_AdobeDng
< < QVideoFrame : : Format_ABGR32
< < QVideoFrame : : Format_YUV422P ;
2021-10-13 20:18:18 +02:00
return result ;
}
bool qt_camera_video_surface : : present ( const QVideoFrame & frame )
{
if ( ! frame . isValid ( ) )
{
2021-10-19 02:59:03 +02:00
camera_log . error ( " Received invalid video frame " ) ;
2021-10-13 20:18:18 +02:00
return false ;
}
2021-10-19 02:59:03 +02:00
// Get video image. Map frame for faster read operations.
QVideoFrame tmp ( frame ) ;
if ( ! tmp . map ( QAbstractVideoBuffer : : ReadOnly ) )
{
camera_log . error ( " Failed to map video frame " ) ;
return false ;
}
2021-10-22 00:48:31 +02:00
// Get image. This usually also converts the image to ARGB32.
QImage image = frame . image ( ) ;
2021-10-13 20:18:18 +02:00
2021-10-22 00:48:31 +02:00
if ( image . isNull ( ) )
{
2021-10-22 16:58:55 +02:00
camera_log . warning ( " Image is invalid: pixel_format=%s, format=%d " , tmp . pixelFormat ( ) , static_cast < int > ( QVideoFrame : : imageFormatFromPixelFormat ( tmp . pixelFormat ( ) ) ) ) ;
2021-10-22 00:48:31 +02:00
}
else
2021-10-13 20:18:18 +02:00
{
// Scale image if necessary
2021-10-22 01:56:03 +02:00
if ( m_width > 0 & & m_height > 0 & & m_width ! = static_cast < u32 > ( image . width ( ) ) & & m_height ! = static_cast < u32 > ( image . height ( ) ) )
2021-10-13 20:18:18 +02:00
{
2021-10-19 02:59:03 +02:00
image = image . scaled ( m_width , m_height , Qt : : AspectRatioMode : : IgnoreAspectRatio , Qt : : SmoothTransformation ) ;
2021-10-13 20:18:18 +02:00
}
// Determine image flip
2021-10-22 01:56:03 +02:00
const camera_flip flip_setting = g_cfg . io . camera_flip_option ;
2021-10-13 20:18:18 +02:00
bool flip_horizontally = m_front_facing ; // Front facing cameras are flipped already
if ( flip_setting = = camera_flip : : horizontal | | flip_setting = = camera_flip : : both )
{
flip_horizontally = ! flip_horizontally ;
}
if ( m_mirrored ) // Set by the game
{
flip_horizontally = ! flip_horizontally ;
}
2021-10-22 14:51:22 +02:00
bool flip_vertically = false ;
2021-10-13 20:18:18 +02:00
if ( flip_setting = = camera_flip : : vertical | | flip_setting = = camera_flip : : both )
{
flip_vertically = ! flip_vertically ;
}
// Flip image if necessary
if ( flip_horizontally | | flip_vertically )
{
image = image . mirrored ( flip_horizontally , flip_vertically ) ;
}
}
2021-10-19 19:18:19 +02:00
const u64 new_size = m_bytesize ;
2021-10-13 20:18:18 +02:00
image_buffer & image_buffer = m_image_buffer [ m_write_index ] ;
// Reset buffer if necessary
if ( image_buffer . size ! = new_size )
{
image_buffer . size = 0 ;
if ( image_buffer . data )
{
delete [ ] image_buffer . data ;
image_buffer . data = nullptr ;
}
}
// Create buffer if necessary
if ( ! image_buffer . data & & new_size > 0 )
{
image_buffer . data = new u8 [ new_size ] ;
image_buffer . size = new_size ;
image_buffer . width = m_width ;
image_buffer . height = m_height ;
}
if ( image_buffer . size > 0 & & ! image . isNull ( ) )
{
// Convert image to proper layout
// TODO: check if pixel format and bytes per pixel match and convert if necessary
// TODO: implement or improve more conversions
switch ( m_format )
{
case CELL_CAMERA_JPG :
break ;
case CELL_CAMERA_RGBA :
break ;
case CELL_CAMERA_RAW8 : // The game seems to expect BGGR
{
// Let's use a very simple algorithm to convert the image to raw BGGR
2021-10-21 03:28:01 +02:00
const auto convert_to_bggr = [ & image_buffer , & image ] ( u32 y_begin , u32 y_end )
2021-10-13 20:18:18 +02:00
{
2021-10-21 03:28:01 +02:00
for ( u32 y = y_begin ; y < std : : min < u32 > ( image_buffer . height , image . height ( ) ) & & y < y_end ; y + + )
2021-10-13 20:18:18 +02:00
{
2021-10-21 03:28:01 +02:00
for ( u32 x = 0 ; x < std : : min < u32 > ( image_buffer . width , image . width ( ) ) ; x + + )
2021-10-13 20:18:18 +02:00
{
2021-10-19 02:59:03 +02:00
u8 & pixel = image_buffer . data [ image_buffer . width * y + x ] ;
const bool is_left_pixel = ( x % 2 ) = = 0 ;
const bool is_top_pixel = ( y % 2 ) = = 0 ;
if ( is_left_pixel & & is_top_pixel )
{
pixel = qBlue ( image . pixel ( x , y ) ) ;
}
else if ( is_left_pixel | | is_top_pixel )
{
pixel = qGreen ( image . pixel ( x , y ) ) ;
}
else
{
pixel = qRed ( image . pixel ( x , y ) ) ;
}
2021-10-13 20:18:18 +02:00
}
}
2021-10-19 02:59:03 +02:00
} ;
// Use a multithreaded workload. The faster we get this done, the better.
constexpr u32 thread_count = 4 ;
2021-10-21 03:28:01 +02:00
const u32 lines_per_thread = std : : ceil ( image_buffer . height / static_cast < double > ( thread_count ) ) ;
u32 y_begin = 0 ;
u32 y_end = lines_per_thread ;
2021-10-19 02:59:03 +02:00
QFutureSynchronizer < void > synchronizer ;
for ( u32 i = 0 ; i < thread_count ; i + + )
{
synchronizer . addFuture ( QtConcurrent : : run ( convert_to_bggr , y_begin , y_end ) ) ;
y_begin = y_end ;
y_end + = lines_per_thread ;
2021-10-13 20:18:18 +02:00
}
2021-10-19 02:59:03 +02:00
synchronizer . waitForFinished ( ) ;
2021-10-13 20:18:18 +02:00
break ;
}
2021-10-19 19:22:33 +02:00
//case CELL_CAMERA_YUV422:
case CELL_CAMERA_Y0_U_Y1_V :
case CELL_CAMERA_V_Y1_U_Y0 :
2021-10-13 20:18:18 +02:00
{
2021-10-19 19:22:33 +02:00
// Simple RGB to Y0_U_Y1_V conversion from stackoverflow.
2021-10-21 03:28:01 +02:00
const auto convert_to_yuv422 = [ & image_buffer , & image , format = m_format ] ( u32 y_begin , u32 y_end )
2021-10-13 20:18:18 +02:00
{
2021-10-19 19:22:33 +02:00
constexpr int yuv_bytes_per_pixel = 2 ;
const int yuv_pitch = image_buffer . width * yuv_bytes_per_pixel ;
const int y0_offset = ( format = = CELL_CAMERA_Y0_U_Y1_V ) ? 0 : 3 ;
const int u_offset = ( format = = CELL_CAMERA_Y0_U_Y1_V ) ? 1 : 2 ;
const int y1_offset = ( format = = CELL_CAMERA_Y0_U_Y1_V ) ? 2 : 1 ;
const int v_offset = ( format = = CELL_CAMERA_Y0_U_Y1_V ) ? 3 : 0 ;
2021-10-13 20:18:18 +02:00
2021-10-21 03:28:01 +02:00
for ( u32 y = y_begin ; y < std : : min < u32 > ( image_buffer . height , image . height ( ) ) & & y < y_end ; y + + )
2021-10-13 20:18:18 +02:00
{
2021-10-19 19:22:33 +02:00
uint8_t * yuv_row_ptr = & image_buffer . data [ y * yuv_pitch ] ;
2021-10-21 03:28:01 +02:00
for ( u32 x = 0 ; x < std : : min < u32 > ( image_buffer . width , image . width ( ) ) - 1 ; x + = 2 )
2021-10-19 19:22:33 +02:00
{
const QRgb pixel_1 = image . pixel ( x , y ) ;
const QRgb pixel_2 = image . pixel ( x + 1 , y ) ;
const double r1 = qRed ( pixel_1 ) ;
const double g1 = qGreen ( pixel_1 ) ;
const double b1 = qBlue ( pixel_1 ) ;
const double r2 = qRed ( pixel_2 ) ;
const double g2 = qGreen ( pixel_2 ) ;
const double b2 = qBlue ( pixel_2 ) ;
const int y0 = ( 0.257 * r1 ) + ( 0.504 * g1 ) + ( 0.098 * b1 ) + 16.0 ;
const int u = - ( 0.148 * r1 ) - ( 0.291 * g1 ) + ( 0.439 * b1 ) + 128.0 ;
const int v = ( 0.439 * r1 ) - ( 0.368 * g1 ) - ( 0.071 * b1 ) + 128.0 ;
const int y1 = ( 0.257 * r2 ) + ( 0.504 * g2 ) + ( 0.098 * b2 ) + 16.0 ;
const int yuv_index = x * yuv_bytes_per_pixel ;
yuv_row_ptr [ yuv_index + y0_offset ] = std : : max < u8 > ( 0 , std : : min < u8 > ( y0 , 255 ) ) ;
yuv_row_ptr [ yuv_index + u_offset ] = std : : max < u8 > ( 0 , std : : min < u8 > ( u , 255 ) ) ;
yuv_row_ptr [ yuv_index + y1_offset ] = std : : max < u8 > ( 0 , std : : min < u8 > ( y1 , 255 ) ) ;
yuv_row_ptr [ yuv_index + v_offset ] = std : : max < u8 > ( 0 , std : : min < u8 > ( v , 255 ) ) ;
}
2021-10-13 20:18:18 +02:00
}
2021-10-19 19:22:33 +02:00
} ;
// Use a multithreaded workload. The faster we get this done, the better.
constexpr u32 thread_count = 4 ;
2021-10-21 03:28:01 +02:00
const u32 lines_per_thread = std : : ceil ( image_buffer . height / static_cast < double > ( thread_count ) ) ;
u32 y_begin = 0 ;
u32 y_end = lines_per_thread ;
2021-10-19 19:22:33 +02:00
QFutureSynchronizer < void > synchronizer ;
for ( u32 i = 0 ; i < thread_count ; i + + )
{
synchronizer . addFuture ( QtConcurrent : : run ( convert_to_yuv422 , y_begin , y_end ) ) ;
y_begin = y_end ;
y_end + = lines_per_thread ;
2021-10-13 20:18:18 +02:00
}
2021-10-19 19:22:33 +02:00
synchronizer . waitForFinished ( ) ;
2021-10-13 20:18:18 +02:00
break ;
}
case CELL_CAMERA_RAW10 :
case CELL_CAMERA_YUV420 :
case CELL_CAMERA_FORMAT_UNKNOWN :
default :
std : : memcpy ( image_buffer . data , image . constBits ( ) , std : : min < usz > ( image_buffer . size , image . height ( ) * image . bytesPerLine ( ) ) ) ;
break ;
}
}
2021-10-19 02:59:03 +02:00
// Unmap frame memory
tmp . unmap ( ) ;
2021-10-19 19:18:19 +02:00
camera_log . trace ( " Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesize=%d " ,
m_write_index , m_frame_number . load ( ) , m_width , m_height , m_bytesize ) ;
2021-10-13 20:18:18 +02:00
// Toggle write/read index
std : : lock_guard lock ( m_mutex ) ;
image_buffer . frame_number = m_frame_number + + ;
m_write_index = read_index ( ) ;
return true ;
}
2021-10-19 19:18:19 +02:00
void qt_camera_video_surface : : set_format ( s32 format , u32 bytesize )
2021-10-13 20:18:18 +02:00
{
2021-10-19 19:18:19 +02:00
camera_log . notice ( " Setting format: format=%d, bytesize=%d " , format , bytesize ) ;
2021-10-13 20:18:18 +02:00
m_format = format ;
2021-10-19 19:18:19 +02:00
m_bytesize = bytesize ;
2021-10-13 20:18:18 +02:00
}
void qt_camera_video_surface : : set_resolution ( u32 width , u32 height )
{
camera_log . notice ( " Setting resolution: width=%d, height=%d " , width , height ) ;
m_width = width ;
m_height = height ;
}
void qt_camera_video_surface : : set_mirrored ( bool mirrored )
{
camera_log . notice ( " Setting mirrored: mirrored=%d " , mirrored ) ;
m_mirrored = mirrored ;
}
u64 qt_camera_video_surface : : frame_number ( ) const
{
return m_frame_number . load ( ) ;
}
void qt_camera_video_surface : : get_image ( u8 * buf , u64 size , u32 & width , u32 & height , u64 & frame_number , u64 & bytes_read )
{
// Lock read buffer
std : : lock_guard lock ( m_mutex ) ;
const image_buffer & image_buffer = m_image_buffer [ read_index ( ) ] ;
width = image_buffer . width ;
height = image_buffer . height ;
frame_number = image_buffer . frame_number ;
// Copy to out buffer
if ( buf & & image_buffer . data )
{
bytes_read = std : : min < u64 > ( image_buffer . size , size ) ;
std : : memcpy ( buf , image_buffer . data , bytes_read ) ;
2021-10-17 12:38:09 +02:00
if ( image_buffer . size ! = size )
{
camera_log . error ( " Buffer size mismatch: in=%d, out=%d. Cropping to incoming size. Please contact a developer. " , size , image_buffer . size ) ;
}
2021-10-13 20:18:18 +02:00
}
else
{
bytes_read = 0 ;
}
}
u32 qt_camera_video_surface : : read_index ( ) const
{
// The read buffer index cannot be the same as the write index
return ( m_write_index + 1u ) % : : narrow < u32 > ( m_image_buffer . size ( ) ) ;
}