diff --git a/rpcs3/Emu/Cell/Modules/cellRec.cpp b/rpcs3/Emu/Cell/Modules/cellRec.cpp index 4701f621c3..d581628d55 100644 --- a/rpcs3/Emu/Cell/Modules/cellRec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellRec.cpp @@ -1,74 +1,461 @@ #include "stdafx.h" +#include "Emu/Cell/lv2/sys_memory.h" +#include "Emu/Cell/timers.hpp" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" +#include "cellRec.h" #include "cellSysutil.h" LOG_CHANNEL(cellRec); -enum +template<> +void fmt_class_string::format(std::string& out, u64 arg) { - CELL_REC_STATUS_UNLOAD = 0, - CELL_REC_STATUS_OPEN = 1, - CELL_REC_STATUS_START = 2, - CELL_REC_STATUS_STOP = 3, - CELL_REC_STATUS_CLOSE = 4, - CELL_REC_STATUS_ERR = 10 -}; - -struct CellRecSpursParam -{ - vm::bptr pSpurs; - be_t spu_usage_rate; - u8 priority[8]; -}; - -struct CellRecOption -{ - be_t option; - union + format_enum(out, arg, [](auto error) { - be_t ppu_thread_priority; - be_t spu_thread_priority; - be_t capture_priority; - be_t use_system_spu; - be_t fit_to_youtube; - be_t xmb_bgm; - be_t mpeg4_fast_encode; - be_t ring_sec; - be_t video_input; - be_t audio_input; - be_t audio_input_mix_vol; - be_t reduce_memsize; - be_t show_xmb; - vm::bptr metadata_filename; - vm::bptr pSpursParam; - be_t dummy; - } value; -}; + switch (error) + { + STR_CASE(CELL_REC_ERROR_OUT_OF_MEMORY); + STR_CASE(CELL_REC_ERROR_FATAL); + STR_CASE(CELL_REC_ERROR_INVALID_VALUE); + STR_CASE(CELL_REC_ERROR_FILE_OPEN); + STR_CASE(CELL_REC_ERROR_FILE_WRITE); + STR_CASE(CELL_REC_ERROR_INVALID_STATE); + STR_CASE(CELL_REC_ERROR_FILE_NO_DATA); + } -struct CellRecParam + return unknown; + }); +} + +// Helper to distinguish video type +enum : s32 { - be_t videoFmt; - be_t audioFmt; - be_t numOfOpt; - vm::bptr pOpt; + VIDEO_TYPE_MPEG4 = 0x0000, + VIDEO_TYPE_AVC_MP = 0x1000, + VIDEO_TYPE_AVC_BL = 0x2000, + VIDEO_TYPE_MJPEG = 0x3000, + VIDEO_TYPE_M4HD = 0x4000, }; -using CellRecCallback = void(s32 recStatus, s32 recError, vm::ptr userdata); +// Helper to distinguish video quality +enum : s32 +{ + VIDEO_QUALITY_0 = 0x000, // small + VIDEO_QUALITY_1 = 0x100, // middle + VIDEO_QUALITY_2 = 0x200, // large + VIDEO_QUALITY_3 = 0x300, + VIDEO_QUALITY_4 = 0x400, + VIDEO_QUALITY_5 = 0x500, + VIDEO_QUALITY_6 = 0x600, + VIDEO_QUALITY_7 = 0x700, +}; + +enum class rec_state : u32 +{ + closed = 0x2710, + open = 0x2711, +}; + +struct rec_param +{ + s32 ppu_thread_priority = CELL_REC_PARAM_PPU_THREAD_PRIORITY_DEFAULT; + s32 spu_thread_priority = CELL_REC_PARAM_SPU_THREAD_PRIORITY_DEFAULT; + s32 capture_priority = CELL_REC_PARAM_CAPTURE_PRIORITY_HIGHEST; + s32 use_system_spu = CELL_REC_PARAM_USE_SYSTEM_SPU_DISABLE; + s32 fit_to_youtube = 0; + s32 xmb_bgm = CELL_REC_PARAM_XMB_BGM_DISABLE; + s32 mpeg4_fast_encode = CELL_REC_PARAM_MPEG4_FAST_ENCODE_DISABLE; + u32 ring_sec = 10; // TODO + s32 video_input = CELL_REC_PARAM_VIDEO_INPUT_DISABLE; + s32 audio_input = CELL_REC_PARAM_AUDIO_INPUT_DISABLE; + s32 audio_input_mix_vol = CELL_REC_PARAM_AUDIO_INPUT_MIX_VOL_MIN; + s32 reduce_memsize = CELL_REC_PARAM_REDUCE_MEMSIZE_DISABLE; + s32 show_xmb = 0; + std::string filename; + std::string metadata_filename; + CellRecSpursParam spurs_param{}; + struct + { + std::string game_title; + std::string movie_title; + std::string description; + std::string userdata; + } movie_metadata{}; + struct + { + bool is_set = false; + u32 type; + u64 start_time; + u64 end_time; + std::string title; + std::vector tags; + } scene_metadata{}; +}; struct rec_info { vm::ptr cb{}; vm::ptr cbUserData{}; + atomic_t state = rec_state::closed; + rec_param param{}; + + vm::bptr video_input_buffer{}; + vm::bptr audio_input_buffer{}; + + u32 pitch = 4 * 1280; // TODO: remember to handle the pitch for CELL_REC_PARAM_VIDEO_INPUT_YUV420PLANAR_16_9 + u32 width = 1280; + u32 height = 720; + + u64 recording_time_start = 0; // TODO: proper time measurements + u64 recording_time_end = 0; // TODO: proper time measurements + u64 recording_time_total = 0; // TODO: proper time measurements shared_mutex mutex; }; +bool create_path(std::string& out, std::string dir_name, std::string file_name) +{ + out.clear(); + + if (dir_name.size() + file_name.size() > CELL_REC_MAX_PATH_LEN) + { + return false; + } + + out = dir_name; + + if (!out.empty() && out.back() != '/') + { + out += '/'; + } + + out += file_name; + + return true; +} + +u32 cellRecQueryMemSize(vm::cptr pParam); // Forward declaration + error_code cellRecOpen(vm::cptr pDirName, vm::cptr pFileName, vm::cptr pParam, u32 container, vm::ptr cb, vm::ptr cbUserData) { cellRec.todo("cellRecOpen(pDirName=%s, pFileName=%s, pParam=*0x%x, container=0x%x, cb=*0x%x, cbUserData=*0x%x)", pDirName, pFileName, pParam, container, cb, cbUserData); auto& rec = g_fxo->get(); + + if (rec.state != rec_state::closed) + { + return CELL_REC_ERROR_INVALID_STATE; + } + + if (!pParam || !pDirName || !pFileName) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + const u32 mem_size = cellRecQueryMemSize(pParam); + + if (container == SYS_MEMORY_CONTAINER_ID_INVALID) + { + if (mem_size != 0) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + } + else + { + // NOTE: Most likely tries to allocate a complete container. But we use g_fxo instead. + // TODO: how much data do we actually need ? + rec.video_input_buffer = vm::cast(vm::alloc(mem_size, vm::main)); + rec.audio_input_buffer = vm::cast(vm::alloc(mem_size, vm::main)); + + if (!rec.video_input_buffer || !rec.audio_input_buffer) + { + return CELL_REC_ERROR_OUT_OF_MEMORY; + } + } + + switch (pParam->videoFmt) + { + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_SMALL_512K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_SMALL_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_MIDDLE_512K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_MIDDLE_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_512K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_1024K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_1536K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_2048K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_MP_SMALL_512K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_MP_SMALL_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_MP_MIDDLE_512K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_MP_MIDDLE_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_MP_MIDDLE_1024K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_MP_MIDDLE_1536K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_BL_SMALL_512K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_BL_SMALL_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_BL_MIDDLE_512K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_BL_MIDDLE_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_BL_MIDDLE_1024K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_AVC_BL_MIDDLE_1536K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MJPEG_SMALL_5000K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MJPEG_MIDDLE_5000K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MJPEG_LARGE_11000K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MJPEG_HD720_11000K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MJPEG_HD720_20000K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_MJPEG_HD720_25000K_30FPS: + case 0x3791: + case 0x37a1: + case 0x3790: + case 0x37a0: + case CELL_REC_PARAM_VIDEO_FMT_M4HD_SMALL_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_M4HD_MIDDLE_768K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_M4HD_LARGE_1536K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_M4HD_LARGE_2048K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_M4HD_HD720_2048K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_M4HD_HD720_5000K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_M4HD_HD720_11000K_30FPS: + case CELL_REC_PARAM_VIDEO_FMT_YOUTUBE: + break; + default: + return CELL_REC_ERROR_INVALID_VALUE; + } + + const s32 video_type = pParam->videoFmt & 0xf000; + const s32 video_quality = pParam->videoFmt & 0xf00; + + switch (pParam->audioFmt) + { + case CELL_REC_PARAM_AUDIO_FMT_AAC_96K: + case CELL_REC_PARAM_AUDIO_FMT_AAC_128K: + case CELL_REC_PARAM_AUDIO_FMT_AAC_64K: + { + // Do not allow MJPEG or AVC_MP video format + if (video_type == VIDEO_TYPE_AVC_MP || video_type == VIDEO_TYPE_MJPEG) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + [[fallthrough]]; + } + case CELL_REC_PARAM_AUDIO_FMT_ULAW_384K: + case CELL_REC_PARAM_AUDIO_FMT_ULAW_768K: + { + // Do not allow AVC_MP video format + if (video_type == VIDEO_TYPE_AVC_MP) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + break; + } + case CELL_REC_PARAM_AUDIO_FMT_PCM_384K: + case CELL_REC_PARAM_AUDIO_FMT_PCM_768K: + case CELL_REC_PARAM_AUDIO_FMT_PCM_1536K: + { + // Only allow MJPEG video format + if (video_type != VIDEO_TYPE_MJPEG) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + break; + } + default: + return CELL_REC_ERROR_INVALID_VALUE; + } + + rec.param = {}; + + u32 spurs_param = 0; + bool abort_loop = false; + + for (s32 i = 0; i < pParam->numOfOpt; i++) + { + const auto& opt = pParam->pOpt[i]; + ensure(!!opt); + + switch (opt->option) + { + case CELL_REC_OPTION_PPU_THREAD_PRIORITY: + { + if (opt->value.ppu_thread_priority > 0xbff) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + rec.param.ppu_thread_priority = opt->value.ppu_thread_priority; + break; + } + case CELL_REC_OPTION_SPU_THREAD_PRIORITY: + { + if (opt->value.spu_thread_priority - 0x10U > 0xef) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + rec.param.spu_thread_priority = opt->value.spu_thread_priority; + break; + } + case CELL_REC_OPTION_CAPTURE_PRIORITY: + { + rec.param.capture_priority = opt->value.capture_priority; + break; + } + case CELL_REC_OPTION_USE_SYSTEM_SPU: + { + // TODO: Seems differ if video_quality is VIDEO_QUALITY_6 or VIDEO_QUALITY_7 + rec.param.use_system_spu = opt->value.use_system_spu; + break; + } + case CELL_REC_OPTION_FIT_TO_YOUTUBE: + { + rec.param.fit_to_youtube = opt->value.fit_to_youtube; + break; + } + case CELL_REC_OPTION_XMB_BGM: + { + rec.param.xmb_bgm = opt->value.xmb_bgm; + break; + } + case CELL_REC_OPTION_RING_SEC: + { + rec.param.ring_sec = opt->value.ring_sec; + break; + } + case CELL_REC_OPTION_MPEG4_FAST_ENCODE: + { + rec.param.mpeg4_fast_encode = opt->value.mpeg4_fast_encode; + break; + } + case CELL_REC_OPTION_VIDEO_INPUT: + { + const u32 v_input = (opt->value.video_input & 0xffU); + if (v_input > CELL_REC_PARAM_VIDEO_INPUT_YUV420PLANAR_16_9) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + rec.param.video_input = v_input; + break; + } + case CELL_REC_OPTION_AUDIO_INPUT: + { + if (opt->value.audio_input == CELL_REC_PARAM_AUDIO_INPUT_DISABLE) + { + rec.param.audio_input_mix_vol = 0; + } + else + { + rec.param.audio_input_mix_vol = 100; + } + break; + } + case CELL_REC_OPTION_AUDIO_INPUT_MIX_VOL: + { + rec.param.audio_input_mix_vol = opt->value.audio_input_mix_vol; + break; + } + case CELL_REC_OPTION_REDUCE_MEMSIZE: + { + rec.param.reduce_memsize = opt->value.reduce_memsize != CELL_REC_PARAM_REDUCE_MEMSIZE_DISABLE; + break; + } + case CELL_REC_OPTION_SHOW_XMB: + { + rec.param.show_xmb = (opt->value.show_xmb != 0); + break; + } + case CELL_REC_OPTION_METADATA_FILENAME: + { + if (opt->value.metadata_filename) + { + std::string path; + if (!create_path(path, pDirName.get_ptr(), opt->value.metadata_filename.get_ptr())) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + rec.param.metadata_filename = path; + } + else + { + abort_loop = true; // TODO: Apparently this doesn't return an error but goes to the callback section instead... + } + break; + } + case CELL_REC_OPTION_SPURS: + { + spurs_param = opt->value.pSpursParam.addr(); + + if (!opt->value.pSpursParam || !opt->value.pSpursParam->pSpurs) // TODO: check both or only pSpursParam ? + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + if (opt->value.pSpursParam->spu_usage_rate < 1 || opt->value.pSpursParam->spu_usage_rate > 100) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + rec.param.spurs_param.spu_usage_rate = opt->value.pSpursParam->spu_usage_rate; + [[fallthrough]]; + } + case 100: + { + if (spurs_param) + { + // TODO: + } + break; + } + default: + { + break; + } + } + + if (abort_loop) + { + break; + } + } + + // NOTE: The abort_loop variable I chose is actually a goto to the callback section at the end of the function. + if (!abort_loop) + { + if (rec.param.video_input != CELL_REC_PARAM_VIDEO_INPUT_DISABLE) + { + if (video_type == VIDEO_TYPE_MJPEG || video_type == VIDEO_TYPE_M4HD) + { + if (rec.param.video_input != CELL_REC_PARAM_VIDEO_INPUT_YUV420PLANAR_16_9) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + } + else if (rec.param.video_input == CELL_REC_PARAM_VIDEO_INPUT_YUV420PLANAR_16_9) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + } + + if (!create_path(rec.param.filename, pDirName.get_ptr(), pFileName.get_ptr())) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + if (!rec.param.filename.starts_with("/dev_hdd0") && !rec.param.filename.starts_with("/dev_hdd1")) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + //if (spurs_param) + //{ + // if (error_code error = cellSpurs_0xD9A9C592(); + // { + // return error; + // } + //} + } + + if (!cb) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + rec.cb = cb; rec.cbUserData = cbUserData; @@ -87,8 +474,30 @@ error_code cellRecClose(s32 isDiscard) auto& rec = g_fxo->get(); + if (rec.state != rec_state::open) + { + return CELL_REC_ERROR_INVALID_STATE; + } + sysutil_register_cb([=, &rec](ppu_thread& ppu) -> s32 { + if (isDiscard) + { + // TODO: remove recording + } + else + { + // TODO: save recording + } + + vm::dealloc(rec.video_input_buffer.addr(), vm::main); + vm::dealloc(rec.audio_input_buffer.addr(), vm::main); + + rec.param = {}; + + // TODO: Sets status CELL_REC_ERROR_FILE_NO_DATA if the start time AND end time are out of scope + + // Sets status CELL_REC_STATUS_ERR on error rec.cb(ppu, CELL_REC_STATUS_CLOSE, CELL_OK, rec.cbUserData); return CELL_OK; }); @@ -96,19 +505,24 @@ error_code cellRecClose(s32 isDiscard) return CELL_OK; } -void cellRecGetInfo(s32 info, vm::ptr pValue) -{ - cellRec.todo("cellRecGetInfo(info=0x%x, pValue=*0x%x)", info, pValue); -} - error_code cellRecStop() { cellRec.todo("cellRecStop()"); auto& rec = g_fxo->get(); + if (rec.state != rec_state::open) + { + return CELL_REC_ERROR_INVALID_STATE; + } + sysutil_register_cb([=, &rec](ppu_thread& ppu) -> s32 { + // TODO: stop recording + rec.recording_time_end = get_system_time(); + rec.recording_time_total += (rec.recording_time_end - rec.recording_time_start); + + // Sets status CELL_REC_STATUS_ERR on error rec.cb(ppu, CELL_REC_STATUS_STOP, CELL_OK, rec.cbUserData); return CELL_OK; }); @@ -122,8 +536,18 @@ error_code cellRecStart() auto& rec = g_fxo->get(); + if (rec.state != rec_state::open) + { + return CELL_REC_ERROR_INVALID_STATE; + } + sysutil_register_cb([=, &rec](ppu_thread& ppu) -> s32 { + // TODO: start recording + rec.recording_time_start = get_system_time(); + rec.recording_time_end = rec.recording_time_start; + + // Sets status CELL_REC_STATUS_ERR on error rec.cb(ppu, CELL_REC_STATUS_START, CELL_OK, rec.cbUserData); return CELL_OK; }); @@ -133,13 +557,347 @@ error_code cellRecStart() u32 cellRecQueryMemSize(vm::cptr pParam) { - cellRec.todo("cellRecQueryMemSize(pParam=*0x%x)", pParam); - return 1 * 1024 * 1024; // dummy memory size + cellRec.notice("cellRecQueryMemSize(pParam=*0x%x)", pParam); + + if (!pParam) + { + return 0x900000; + } + + u32 video_size = 0x600000; // 6 MB + u32 audio_size = 0x100000; // 1 MB + u32 external_input_size = 0; + u32 reduce_memsize = 0; + + s32 video_type = pParam->videoFmt & 0xf000; + s32 video_quality = pParam->videoFmt & 0xf00; + + switch (video_type) + { + case VIDEO_TYPE_MPEG4: + { + switch (video_quality) + { + case VIDEO_QUALITY_0: + case VIDEO_QUALITY_3: + video_size = 0x600000; // 6 MB + break; + case VIDEO_QUALITY_1: + video_size = 0x700000; // 7 MB + break; + case VIDEO_QUALITY_2: + video_size = 0x800000; // 8 MB + break; + default: + video_size = 0x900000; // 9 MB + break; + } + + audio_size = 0x100000; // 1 MB + break; + } + case VIDEO_TYPE_AVC_BL: + case VIDEO_TYPE_AVC_MP: + { + switch (video_quality) + { + case VIDEO_QUALITY_0: + case VIDEO_QUALITY_3: + video_size = 0x800000; // 8 MB + break; + default: + video_size = 0x900000; // 9 MB + break; + } + + audio_size = 0x100000; // 1 MB + break; + } + case VIDEO_TYPE_M4HD: + { + switch (video_quality) + { + case VIDEO_QUALITY_0: + case VIDEO_QUALITY_1: + video_size = 0x600000; // 6 MB + break; + case VIDEO_QUALITY_2: + video_size = 0x700000; // 7 MB + break; + default: + video_size = 0x1000000; // 16 MB + break; + } + + audio_size = 0x100000; // 1 MB + break; + } + default: + { + switch (video_quality) + { + case VIDEO_QUALITY_0: + video_size = 0x300000; // 3 MB + break; + case VIDEO_QUALITY_1: + case VIDEO_QUALITY_2: + video_size = 0x400000; // 4 MB + break; + case VIDEO_QUALITY_6: + video_size = 0x700000; // 7 MB + break; + default: + video_size = 0xd00000; // 14 MB + break; + } + + audio_size = 0; // 0 MB + break; + } + } + + for (s32 i = 0; i < pParam->numOfOpt; i++) + { + const auto& opt = pParam->pOpt[i]; + ensure(!!opt); + + switch (opt->option) + { + case CELL_REC_OPTION_REDUCE_MEMSIZE: + { + if (opt->value.reduce_memsize == CELL_REC_PARAM_REDUCE_MEMSIZE_DISABLE) + { + reduce_memsize = 0; // 0 MB + } + else + { + if (video_type == VIDEO_TYPE_M4HD && (video_quality == VIDEO_QUALITY_1 || video_quality == VIDEO_QUALITY_2)) + { + reduce_memsize = 0x200000; // 2 MB + } + else + { + reduce_memsize = 0x300000; // 3 MB + } + } + break; + } + case CELL_REC_OPTION_VIDEO_INPUT: + case CELL_REC_OPTION_AUDIO_INPUT: + { + if (opt->value.audio_input != CELL_REC_PARAM_AUDIO_INPUT_DISABLE) + { + if (video_type == VIDEO_TYPE_MJPEG || (video_type == VIDEO_TYPE_M4HD && video_quality != VIDEO_QUALITY_6)) + { + external_input_size = 0x100000; // 1MB + } + } + break; + } + case CELL_REC_OPTION_AUDIO_INPUT_MIX_VOL: + { + // NOTE: Doesn't seem to check opt->value.audio_input + if (video_type == VIDEO_TYPE_MJPEG || (video_type == VIDEO_TYPE_M4HD && video_quality != VIDEO_QUALITY_6)) + { + external_input_size = 0x100000; // 1MB + } + break; + } + default: + break; + } + } + + u32 size = video_size + audio_size + external_input_size; + + if (size > reduce_memsize) + { + size -= reduce_memsize; + } + else + { + size = 0; + } + + return size; +} + +void cellRecGetInfo(s32 info, vm::ptr pValue) +{ + cellRec.todo("cellRecGetInfo(info=0x%x, pValue=*0x%x)", info, pValue); + + if (!pValue) + { + return; + } + + *pValue = 0; + + auto& rec = g_fxo->get(); + + switch (info) + { + case CELL_REC_INFO_VIDEO_INPUT_ADDR: + { + *pValue = rec.video_input_buffer.addr(); + break; + } + case CELL_REC_INFO_VIDEO_INPUT_WIDTH: + { + *pValue = rec.width; + break; + } + case CELL_REC_INFO_VIDEO_INPUT_PITCH: + { + *pValue = rec.pitch; + break; + } + case CELL_REC_INFO_VIDEO_INPUT_HEIGHT: + { + *pValue = rec.height; + break; + } + case CELL_REC_INFO_AUDIO_INPUT_ADDR: + { + *pValue = rec.audio_input_buffer.addr(); + break; + } + case CELL_REC_INFO_MOVIE_TIME_MSEC: + { + if (rec.state == rec_state::open) + { + *pValue = rec.recording_time_total; + } + break; + } + case CELL_REC_INFO_SPURS_SYSTEMWORKLOAD_ID: + { + if (rec.param.spurs_param.pSpurs) + { + // TODO + //cellSpurs_0xE279681F(); + } + *pValue = 0xffffffff; + break; + } + default: + { + break; + } + } } error_code cellRecSetInfo(s32 setInfo, u64 value) { cellRec.todo("cellRecSetInfo(setInfo=0x%x, value=0x%x)", setInfo, value); + + auto& rec = g_fxo->get(); + + if (rec.state != rec_state::open) + { + return CELL_REC_ERROR_INVALID_STATE; + } + + switch (setInfo) + { + case CELL_REC_SETINFO_MOVIE_START_TIME_MSEC: + { + rec.param.scene_metadata.start_time = value; + break; + } + case CELL_REC_SETINFO_MOVIE_END_TIME_MSEC: + { + rec.param.scene_metadata.end_time = value; + break; + } + case CELL_REC_SETINFO_MOVIE_META: + { + if (!value) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + vm::ptr movie_metadata = vm::cast(value); + + if (!movie_metadata || !movie_metadata->gameTitle || !movie_metadata->movieTitle || + strnlen(movie_metadata->gameTitle.get_ptr(), CELL_REC_MOVIE_META_GAME_TITLE_LEN) >= CELL_REC_MOVIE_META_GAME_TITLE_LEN || + strnlen(movie_metadata->movieTitle.get_ptr(), CELL_REC_MOVIE_META_MOVIE_TITLE_LEN) >= CELL_REC_MOVIE_META_MOVIE_TITLE_LEN) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + if ((movie_metadata->description && strnlen(movie_metadata->description.get_ptr(), CELL_REC_MOVIE_META_DESCRIPTION_LEN) >= CELL_REC_MOVIE_META_DESCRIPTION_LEN) || + (movie_metadata->userdata && strnlen(movie_metadata->userdata.get_ptr(), CELL_REC_MOVIE_META_USERDATA_LEN) >= CELL_REC_MOVIE_META_USERDATA_LEN)) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + rec.param.movie_metadata = {}; + rec.param.movie_metadata.game_title = std::string{movie_metadata->gameTitle.get_ptr()}; + rec.param.movie_metadata.movie_title = std::string{movie_metadata->movieTitle.get_ptr()}; + + if (movie_metadata->description) + { + rec.param.movie_metadata.description = std::string{movie_metadata->description.get_ptr()}; + } + + if (movie_metadata->userdata) + { + rec.param.movie_metadata.userdata = std::string{movie_metadata->userdata.get_ptr()}; + } + + break; + } + case CELL_REC_SETINFO_SCEME_META: + { + if (!value) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + vm::ptr scene_metadata = vm::cast(value); + + if (!scene_metadata || !scene_metadata->title || + scene_metadata->type > CELL_REC_SCENE_META_TYPE_CLIP_USER || + scene_metadata->tagNum > CELL_REC_SCENE_META_TAG_NUM || + strnlen(scene_metadata->title.get_ptr(), CELL_REC_SCENE_META_TITLE_LEN) >= CELL_REC_SCENE_META_TITLE_LEN) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + + for (usz i = 0; i < scene_metadata->tagNum; i++) + { + if (!scene_metadata->tag[i] || + strnlen(scene_metadata->tag[i].get_ptr(), CELL_REC_SCENE_META_TAG_LEN) >= CELL_REC_SCENE_META_TAG_LEN) + { + return CELL_REC_ERROR_INVALID_VALUE; + } + } + + rec.param.scene_metadata = {}; + rec.param.scene_metadata.is_set = true; + rec.param.scene_metadata.type = scene_metadata->type; + rec.param.scene_metadata.start_time = scene_metadata->startTime; + rec.param.scene_metadata.end_time = scene_metadata->endTime; + rec.param.scene_metadata.title = std::string{scene_metadata->title.get_ptr()}; + rec.param.scene_metadata.tags.resize(scene_metadata->tagNum); + + for (usz i = 0; i < scene_metadata->tagNum; i++) + { + if (scene_metadata->tag[i]) + { + rec.param.scene_metadata.tags[i] = std::string{scene_metadata->tag[i].get_ptr()}; + } + } + + break; + } + default: + { + return CELL_REC_ERROR_INVALID_VALUE; + } + } + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/cellRec.h b/rpcs3/Emu/Cell/Modules/cellRec.h new file mode 100644 index 0000000000..53085c1057 --- /dev/null +++ b/rpcs3/Emu/Cell/Modules/cellRec.h @@ -0,0 +1,244 @@ +#pragma once + +enum CellRecError : u32 +{ + CELL_REC_ERROR_OUT_OF_MEMORY = 0x8002c501, + CELL_REC_ERROR_FATAL = 0x8002c502, + CELL_REC_ERROR_INVALID_VALUE = 0x8002c503, + CELL_REC_ERROR_FILE_OPEN = 0x8002c504, + CELL_REC_ERROR_FILE_WRITE = 0x8002c505, + CELL_REC_ERROR_INVALID_STATE = 0x8002c506, + CELL_REC_ERROR_FILE_NO_DATA = 0x8002c507 +}; + +enum +{ + CELL_REC_STATUS_UNLOAD = 0, + CELL_REC_STATUS_OPEN = 1, + CELL_REC_STATUS_START = 2, + CELL_REC_STATUS_STOP = 3, + CELL_REC_STATUS_CLOSE = 4, + CELL_REC_STATUS_ERR = 10 +}; + +enum +{ + CELL_REC_MIN_MEMORY_CONTAINER_SIZE = 0, + CELL_REC_MAX_MEMORY_CONTAINER_SIZE = 16 * 1024 * 1024, // 9 * 1024 * 1024 if SDK < 0x300000 + + CELL_REC_MAX_PATH_LEN = 1023, +}; + +enum +{ + CELL_REC_PARAM_PPU_THREAD_PRIORITY_DEFAULT = 400, + CELL_REC_PARAM_SPU_THREAD_PRIORITY_DEFAULT = 60, + + CELL_REC_PARAM_CAPTURE_PRIORITY_HIGHEST = 0, + CELL_REC_PARAM_CAPTURE_PRIORITY_EXCEPT_NOTIFICATION = 1, + CELL_REC_PARAM_CAPTURE_PRIORITY_GAME_SCREEN = 2, + + CELL_REC_PARAM_USE_SYSTEM_SPU_DISABLE = 0, + CELL_REC_PARAM_USE_SYSTEM_SPU_ENABLE = 1, + + CELL_REC_PARAM_XMB_BGM_DISABLE = 0, + CELL_REC_PARAM_XMB_BGM_ENABLE = 1, + + CELL_REC_PARAM_MPEG4_FAST_ENCODE_DISABLE = 0, + CELL_REC_PARAM_MPEG4_FAST_ENCODE_ENABLE = 1, + + CELL_REC_PARAM_VIDEO_INPUT_DISABLE = 0, + CELL_REC_PARAM_VIDEO_INPUT_ARGB_4_3 = 1, + CELL_REC_PARAM_VIDEO_INPUT_ARGB_16_9 = 2, + CELL_REC_PARAM_VIDEO_INPUT_RGBA_4_3 = 3, + CELL_REC_PARAM_VIDEO_INPUT_RGBA_16_9 = 4, + CELL_REC_PARAM_VIDEO_INPUT_YUV420PLANAR_16_9 = 5, + + CELL_REC_PARAM_AUDIO_INPUT_DISABLE = 0, + CELL_REC_PARAM_AUDIO_INPUT_ENABLE = 1, + + CELL_REC_PARAM_AUDIO_INPUT_MIX_VOL_MIN = 0, + CELL_REC_PARAM_AUDIO_INPUT_MIX_VOL_MAX = 100, + + CELL_REC_PARAM_REDUCE_MEMSIZE_DISABLE = 0, + CELL_REC_PARAM_REDUCE_MEMSIZE_ENABLE = 1, + + // SMALL = 320x240 (4:3) or 368x208 (16:9) + // MIDDLE = 368x272 (4:3) or 480x272 (16:9) + // LARGE = 480x368 (4:3) or 640x368 (16:9) + // HD720 = 1280x720 (16:9) + + // PS3 playable format; all + // PSP playable format: MPEG4 + SMALL, AVC + SMALL, AVC + MIDDLE + + CELL_REC_PARAM_VIDEO_FMT_MPEG4_SMALL_512K_30FPS = 0x0000, + CELL_REC_PARAM_VIDEO_FMT_MPEG4_SMALL_768K_30FPS = 0x0010, + CELL_REC_PARAM_VIDEO_FMT_MPEG4_MIDDLE_512K_30FPS = 0x0100, + CELL_REC_PARAM_VIDEO_FMT_MPEG4_MIDDLE_768K_30FPS = 0x0110, + CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_512K_30FPS = 0x0200, + CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_768K_30FPS = 0x0210, + CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_1024K_30FPS = 0x0220, + CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_1536K_30FPS = 0x0230, + CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_2048K_30FPS = 0x0240, + + CELL_REC_PARAM_VIDEO_FMT_AVC_MP_SMALL_512K_30FPS = 0x1000, + CELL_REC_PARAM_VIDEO_FMT_AVC_MP_SMALL_768K_30FPS = 0x1010, + CELL_REC_PARAM_VIDEO_FMT_AVC_MP_MIDDLE_512K_30FPS = 0x1100, + CELL_REC_PARAM_VIDEO_FMT_AVC_MP_MIDDLE_768K_30FPS = 0x1110, + CELL_REC_PARAM_VIDEO_FMT_AVC_MP_MIDDLE_1024K_30FPS = 0x1120, + CELL_REC_PARAM_VIDEO_FMT_AVC_MP_MIDDLE_1536K_30FPS = 0x1130, + + CELL_REC_PARAM_VIDEO_FMT_AVC_BL_SMALL_512K_30FPS = 0x2000, + CELL_REC_PARAM_VIDEO_FMT_AVC_BL_SMALL_768K_30FPS = 0x2010, + CELL_REC_PARAM_VIDEO_FMT_AVC_BL_MIDDLE_512K_30FPS = 0x2100, + CELL_REC_PARAM_VIDEO_FMT_AVC_BL_MIDDLE_768K_30FPS = 0x2110, + CELL_REC_PARAM_VIDEO_FMT_AVC_BL_MIDDLE_1024K_30FPS = 0x2120, + CELL_REC_PARAM_VIDEO_FMT_AVC_BL_MIDDLE_1536K_30FPS = 0x2130, + + CELL_REC_PARAM_VIDEO_FMT_MJPEG_SMALL_5000K_30FPS = 0x3060, + CELL_REC_PARAM_VIDEO_FMT_MJPEG_MIDDLE_5000K_30FPS = 0x3160, + CELL_REC_PARAM_VIDEO_FMT_MJPEG_LARGE_11000K_30FPS = 0x3270, + CELL_REC_PARAM_VIDEO_FMT_MJPEG_HD720_11000K_30FPS = 0x3670, + CELL_REC_PARAM_VIDEO_FMT_MJPEG_HD720_20000K_30FPS = 0x3680, + CELL_REC_PARAM_VIDEO_FMT_MJPEG_HD720_25000K_30FPS = 0x3690, + + CELL_REC_PARAM_VIDEO_FMT_M4HD_SMALL_768K_30FPS = 0x4010, + CELL_REC_PARAM_VIDEO_FMT_M4HD_MIDDLE_768K_30FPS = 0x4110, + CELL_REC_PARAM_VIDEO_FMT_M4HD_LARGE_1536K_30FPS = 0x4230, + CELL_REC_PARAM_VIDEO_FMT_M4HD_LARGE_2048K_30FPS = 0x4240, + CELL_REC_PARAM_VIDEO_FMT_M4HD_HD720_2048K_30FPS = 0x4640, + CELL_REC_PARAM_VIDEO_FMT_M4HD_HD720_5000K_30FPS = 0x4660, + CELL_REC_PARAM_VIDEO_FMT_M4HD_HD720_11000K_30FPS = 0x4670, + + CELL_REC_PARAM_AUDIO_FMT_AAC_96K = 0x0000, + CELL_REC_PARAM_AUDIO_FMT_AAC_128K = 0x0001, + CELL_REC_PARAM_AUDIO_FMT_AAC_64K = 0x0002, + + CELL_REC_PARAM_AUDIO_FMT_ULAW_384K = 0x1007, + CELL_REC_PARAM_AUDIO_FMT_ULAW_768K = 0x1008, + + CELL_REC_PARAM_AUDIO_FMT_PCM_384K = 0x2007, + CELL_REC_PARAM_AUDIO_FMT_PCM_768K = 0x2008, + CELL_REC_PARAM_AUDIO_FMT_PCM_1536K = 0x2009, + + CELL_REC_PARAM_VIDEO_FMT_YOUTUBE = 0x0310, + CELL_REC_PARAM_VIDEO_FMT_YOUTUBE_LARGE = CELL_REC_PARAM_VIDEO_FMT_MPEG4_LARGE_2048K_30FPS, + CELL_REC_PARAM_VIDEO_FMT_YOUTUBE_HD720 = CELL_REC_PARAM_VIDEO_FMT_M4HD_HD720_5000K_30FPS, + CELL_REC_PARAM_AUDIO_FMT_YOUTUBE = CELL_REC_PARAM_AUDIO_FMT_AAC_64K, + + CELL_REC_PARAM_VIDEO_FMT_YOUTUBE_MJPEG = CELL_REC_PARAM_VIDEO_FMT_MJPEG_HD720_11000K_30FPS, + CELL_REC_PARAM_AUDIO_FMT_YOUTUBE_MJPEG = CELL_REC_PARAM_AUDIO_FMT_PCM_768K, + + CELL_REC_AUDIO_BLOCK_SAMPLES = 256, +}; + +enum +{ + CELL_REC_INFO_VIDEO_INPUT_ADDR = 0, + CELL_REC_INFO_VIDEO_INPUT_WIDTH = 1, + CELL_REC_INFO_VIDEO_INPUT_PITCH = 2, + CELL_REC_INFO_VIDEO_INPUT_HEIGHT = 3, + CELL_REC_INFO_AUDIO_INPUT_ADDR = 4, + CELL_REC_INFO_MOVIE_TIME_MSEC = 5, + CELL_REC_INFO_SPURS_SYSTEMWORKLOAD_ID = 6, +}; + +enum +{ + CELL_REC_SETINFO_MOVIE_START_TIME_MSEC = 100, + CELL_REC_SETINFO_MOVIE_END_TIME_MSEC = 101, + CELL_REC_SETINFO_MOVIE_META = 200, + CELL_REC_SETINFO_SCEME_META = 201, + + CELL_REC_MOVIE_META_GAME_TITLE_LEN = 128, + CELL_REC_MOVIE_META_MOVIE_TITLE_LEN = 128, + CELL_REC_MOVIE_META_DESCRIPTION_LEN = 384, + CELL_REC_MOVIE_META_USERDATA_LEN = 64, + + CELL_REC_SCENE_META_TYPE_CHAPTER = 0, + CELL_REC_SCENE_META_TYPE_CLIP_HIGHLIGHT = 1, + CELL_REC_SCENE_META_TYPE_CLIP_USER = 2, + + CELL_REC_SCENE_META_TITLE_LEN = 128, + CELL_REC_SCENE_META_TAG_NUM = 6, + CELL_REC_SCENE_META_TAG_LEN = 64, +}; + +enum +{ + CELL_REC_OPTION_PPU_THREAD_PRIORITY = 1, + CELL_REC_OPTION_SPU_THREAD_PRIORITY = 2, + CELL_REC_OPTION_CAPTURE_PRIORITY = 3, + CELL_REC_OPTION_USE_SYSTEM_SPU = 4, + CELL_REC_OPTION_FIT_TO_YOUTUBE = 5, + CELL_REC_OPTION_XMB_BGM = 6, + CELL_REC_OPTION_RING_SEC = 7, + CELL_REC_OPTION_MPEG4_FAST_ENCODE = 8, + CELL_REC_OPTION_VIDEO_INPUT = 9, + CELL_REC_OPTION_AUDIO_INPUT = 10, + CELL_REC_OPTION_AUDIO_INPUT_MIX_VOL = 11, + CELL_REC_OPTION_REDUCE_MEMSIZE = 12, + CELL_REC_OPTION_SHOW_XMB = 13, + CELL_REC_OPTION_METADATA_FILENAME = 14, + CELL_REC_OPTION_SPURS = 15, +}; + +struct CellRecSpursParam +{ + vm::bptr pSpurs; + be_t spu_usage_rate; + u8 priority[8]; +}; + +struct CellRecOption +{ + be_t option; + union + { + be_t ppu_thread_priority; // CELL_REC_OPTION_PPU_THREAD_PRIORITY + be_t spu_thread_priority; // CELL_REC_OPTION_SPU_THREAD_PRIORITY + be_t capture_priority; // CELL_REC_OPTION_CAPTURE_PRIORITY + be_t use_system_spu; // CELL_REC_OPTION_USE_SYSTEM_SPU + be_t fit_to_youtube; // CELL_REC_OPTION_FIT_TO_YOUTUBE + be_t xmb_bgm; // CELL_REC_OPTION_XMB_BGM + be_t mpeg4_fast_encode; // CELL_REC_OPTION_MPEG4_FAST_ENCODE + be_t ring_sec; // CELL_REC_OPTION_RING_SEC + be_t video_input; // CELL_REC_OPTION_VIDEO_INPUT + be_t audio_input; // CELL_REC_OPTION_AUDIO_INPUT + be_t audio_input_mix_vol; // CELL_REC_OPTION_AUDIO_INPUT_MIX_VOL + be_t reduce_memsize; // CELL_REC_OPTION_REDUCE_MEMSIZE + be_t show_xmb; // CELL_REC_OPTION_SHOW_XMB + vm::bptr metadata_filename; // CELL_REC_OPTION_METADATA_FILENAME + vm::bptr pSpursParam; // CELL_REC_OPTION_SPURS + be_t dummy; // - + } value; +}; + +struct CellRecParam +{ + be_t videoFmt; + be_t audioFmt; + be_t numOfOpt; + vm::bpptr pOpt; +}; + +struct CellRecMovieMetadata +{ + vm::bptr gameTitle; + vm::bptr movieTitle; + vm::bptr description; + vm::bptr userdata; +}; + +struct CellRecSceneMetadata +{ + be_t type; + be_t startTime; + be_t endTime; + u8 unk[4]; // NOTE: there seems to be undocumented padding + vm::bptr title; + be_t tagNum; + vm::bpptr tag; +}; + +using CellRecCallback = void(s32 recStatus, s32 recError, vm::ptr userdata); diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index aa02725c52..cb5efa5e38 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -454,6 +454,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 55ff2ff007..1aae85bb56 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -2101,6 +2101,9 @@ Emu\GPU\RSX\Common + + Emu\Cell\Modules +