From 0a58ead5f6bf9b0c98c4a017788903f5920bde9d Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:16:23 -0500 Subject: [PATCH] Add alternate code paths for handling legacy struct behavior in sceVideodec2GetPictureInfo (#3154) Older games aren't fond of how our sceVideodec2GetPictureInfo implementation outputs AVC picture info after the struct size increase. Adding the old struct, and additional code using it for these games works around this problem. --- src/core/libraries/videodec/videodec2.cpp | 37 ++++++++++--- src/core/libraries/videodec/videodec2_avc.h | 53 +++++++++++++++++++ .../libraries/videodec/videodec2_impl.cpp | 48 ++++++++++++----- src/core/libraries/videodec/videodec2_impl.h | 1 + 4 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/core/libraries/videodec/videodec2.cpp b/src/core/libraries/videodec/videodec2.cpp index 1c6044fe2..8c91e2bf1 100644 --- a/src/core/libraries/videodec/videodec2.cpp +++ b/src/core/libraries/videodec/videodec2.cpp @@ -171,19 +171,40 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp LOG_ERROR(Lib_Vdec2, "Invalid struct size"); return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; } - if (outputInfo->pictureCount == 0 || gPictureInfos.empty()) { + if (outputInfo->pictureCount == 0) { LOG_ERROR(Lib_Vdec2, "No picture info available"); return ORBIS_OK; } - if (p1stPictureInfoOut) { - OrbisVideodec2AvcPictureInfo* picInfo = - static_cast(p1stPictureInfoOut); - if ((picInfo->thisSize | 16) != sizeof(OrbisVideodec2AvcPictureInfo)) { - LOG_ERROR(Lib_Vdec2, "Invalid struct size"); - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + // If the game uses the older Videodec2 structs, we need to accomodate that. + if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { + if (gLegacyPictureInfos.empty()) { + LOG_ERROR(Lib_Vdec2, "No picture info available"); + return ORBIS_OK; + } + if (p1stPictureInfoOut) { + OrbisVideodec2LegacyAvcPictureInfo* picInfo = + static_cast(p1stPictureInfoOut); + if (picInfo->thisSize != sizeof(OrbisVideodec2LegacyAvcPictureInfo)) { + LOG_ERROR(Lib_Vdec2, "Invalid struct size"); + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + *picInfo = gLegacyPictureInfos.back(); + } + } else { + if (gPictureInfos.empty()) { + LOG_ERROR(Lib_Vdec2, "No picture info available"); + return ORBIS_OK; + } + if (p1stPictureInfoOut) { + OrbisVideodec2AvcPictureInfo* picInfo = + static_cast(p1stPictureInfoOut); + if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) { + LOG_ERROR(Lib_Vdec2, "Invalid struct size"); + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + *picInfo = gPictureInfos.back(); } - *picInfo = gPictureInfos.back(); } if (outputInfo->pictureCount > 1) { diff --git a/src/core/libraries/videodec/videodec2_avc.h b/src/core/libraries/videodec/videodec2_avc.h index 1975209cb..725d2335f 100644 --- a/src/core/libraries/videodec/videodec2_avc.h +++ b/src/core/libraries/videodec/videodec2_avc.h @@ -74,4 +74,57 @@ struct OrbisVideodec2AvcPictureInfo { }; static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78); +// An older version of the OrbisVideodec2AvcPictureInfo struct +// Keeping this is needed for compatiblity with older games. +struct OrbisVideodec2LegacyAvcPictureInfo { + u64 thisSize; + + bool isValid; + + u64 ptsData; + u64 dtsData; + u64 attachedData; + + u8 idrPictureflag; + + u8 profile_idc; + u8 level_idc; + u32 pic_width_in_mbs_minus1; + u32 pic_height_in_map_units_minus1; + u8 frame_mbs_only_flag; + + u8 frame_cropping_flag; + u32 frameCropLeftOffset; + u32 frameCropRightOffset; + u32 frameCropTopOffset; + u32 frameCropBottomOffset; + + u8 aspect_ratio_info_present_flag; + u8 aspect_ratio_idc; + u16 sar_width; + u16 sar_height; + + u8 video_signal_type_present_flag; + u8 video_format; + u8 video_full_range_flag; + u8 colour_description_present_flag; + u8 colour_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + + u8 timing_info_present_flag; + u32 num_units_in_tick; + u32 time_scale; + u8 fixed_frame_rate_flag; + + u8 bitstream_restriction_flag; + u8 max_dec_frame_buffering; + + u8 pic_struct_present_flag; + u8 pic_struct; + u8 field_pic_flag; + u8 bottom_field_flag; +}; +static_assert(sizeof(OrbisVideodec2LegacyAvcPictureInfo) == 0x68); + } // namespace Libraries::Vdec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_impl.cpp b/src/core/libraries/videodec/videodec2_impl.cpp index 373809c14..667fb79ac 100644 --- a/src/core/libraries/videodec/videodec2_impl.cpp +++ b/src/core/libraries/videodec/videodec2_impl.cpp @@ -12,6 +12,7 @@ namespace Libraries::Vdec2 { std::vector gPictureInfos; +std::vector gLegacyPictureInfos; static inline void CopyNV12Data(u8* dst, const AVFrame& src) { std::memcpy(dst, src.data[0], src.width * src.height); @@ -117,27 +118,46 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, outputInfo.isErrorFrame = false; outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video - // Only set framePitchInBytes if the game uses the newer struct version. + // For proper compatibility with older games, check the inputted OutputInfo struct size. if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) { + // framePitchInBytes only exists in the newer struct. outputInfo.framePitchInBytes = frame->linesize[0]; - } + if (outputInfo.isValid) { + OrbisVideodec2AvcPictureInfo pictureInfo = {}; - if (outputInfo.isValid) { - OrbisVideodec2AvcPictureInfo pictureInfo = {}; + pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo); + pictureInfo.isValid = true; - pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo); - pictureInfo.isValid = true; + pictureInfo.ptsData = inputData.ptsData; + pictureInfo.dtsData = inputData.dtsData; + pictureInfo.attachedData = inputData.attachedData; - pictureInfo.ptsData = inputData.ptsData; - pictureInfo.dtsData = inputData.dtsData; - pictureInfo.attachedData = inputData.attachedData; + pictureInfo.frameCropLeftOffset = frame->crop_left; + pictureInfo.frameCropRightOffset = frame->crop_right; + pictureInfo.frameCropTopOffset = frame->crop_top; + pictureInfo.frameCropBottomOffset = frame->crop_bottom; - pictureInfo.frameCropLeftOffset = frame->crop_left; - pictureInfo.frameCropRightOffset = frame->crop_right; - pictureInfo.frameCropTopOffset = frame->crop_top; - pictureInfo.frameCropBottomOffset = frame->crop_bottom; + gPictureInfos.push_back(pictureInfo); + } + } else { + if (outputInfo.isValid) { + // If the game uses the older struct versions, we need to use it too. + OrbisVideodec2LegacyAvcPictureInfo pictureInfo = {}; - gPictureInfos.push_back(pictureInfo); + pictureInfo.thisSize = sizeof(OrbisVideodec2LegacyAvcPictureInfo); + pictureInfo.isValid = true; + + pictureInfo.ptsData = inputData.ptsData; + pictureInfo.dtsData = inputData.dtsData; + pictureInfo.attachedData = inputData.attachedData; + + pictureInfo.frameCropLeftOffset = frame->crop_left; + pictureInfo.frameCropRightOffset = frame->crop_right; + pictureInfo.frameCropTopOffset = frame->crop_top; + pictureInfo.frameCropBottomOffset = frame->crop_bottom; + + gLegacyPictureInfos.push_back(pictureInfo); + } } } diff --git a/src/core/libraries/videodec/videodec2_impl.h b/src/core/libraries/videodec/videodec2_impl.h index c8e8ea253..7ee3339db 100644 --- a/src/core/libraries/videodec/videodec2_impl.h +++ b/src/core/libraries/videodec/videodec2_impl.h @@ -16,6 +16,7 @@ extern "C" { namespace Libraries::Vdec2 { extern std::vector gPictureInfos; +extern std::vector gLegacyPictureInfos; class VdecDecoder { public: