mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-10-12 02:09:17 +00:00
cellVdec: Fix decoding of streams without a leading sequence header and of reused AU buffers- #19658
This commit is contained in:
@@ -53,6 +53,31 @@ constexpr int averror_eof = AVERROR_EOF; // workaround for old-style-cast error
|
||||
|
||||
LOG_CHANNEL(cellVdec);
|
||||
|
||||
// Checks whether the AU holds a header that tells the decoder the picture size:
|
||||
// MPEG-2: sequence header; MPEG-4 Part 2: Video Object Layer header, or a short video header (H.263 picture start code) at the start of the AU
|
||||
static bool au_has_sequence_header(AVCodecID codec_id, const u8* data, u32 size)
|
||||
{
|
||||
if (codec_id == AV_CODEC_ID_MPEG4 && size >= 3 && data[0] == 0 && data[1] == 0 && (data[2] & 0xfc) == 0x80)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i + 3 < size; i++)
|
||||
{
|
||||
if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1)
|
||||
{
|
||||
const u8 code = data[i + 3];
|
||||
|
||||
if (codec_id == AV_CODEC_ID_MPEG2VIDEO ? code == 0xb3 : (code & 0xf0) == 0x20)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<CellVdecError>::format(std::string& out, u64 arg)
|
||||
{
|
||||
@@ -137,6 +162,10 @@ struct vdec_cmd
|
||||
: type(_type), seq_id(_seq_id), id(_id), mode(_mode), au(std::move(_au))
|
||||
{
|
||||
ensure(_type == vdec_cmd_type::au_decode);
|
||||
|
||||
// Copy the AU now: the game may refill its buffer before the decoder thread gets to this AU
|
||||
const u8* data = vm::_ptr<const u8>(au.startAddr);
|
||||
au_data.assign(data, data + au.size);
|
||||
}
|
||||
|
||||
explicit vdec_cmd(vdec_cmd_type _type, u64 _seq_id, u64 _id, s32 _framerate)
|
||||
@@ -151,6 +180,7 @@ struct vdec_cmd
|
||||
s32 mode{};
|
||||
s32 framerate{};
|
||||
CellVdecAuInfo au{};
|
||||
std::vector<u8> au_data;
|
||||
};
|
||||
|
||||
struct vdec_frame
|
||||
@@ -224,6 +254,8 @@ struct vdec_context final
|
||||
|
||||
AVRational log_time_base{}; // Used to reduce log spam
|
||||
AVRational log_framerate{}; // Used to reduce log spam
|
||||
u32 skipped_au_count{}; // Used to reduce log spam
|
||||
bool sequence_header_found = false;
|
||||
|
||||
vdec_context(s32 type, u32 /*profile*/, u32 addr, u32 size, vm::ptr<CellVdecCbMsg> func, u32 arg)
|
||||
: type(type)
|
||||
@@ -268,6 +300,9 @@ struct vdec_context final
|
||||
fmt::throw_exception("avcodec_descriptor_get() failed (type=0x%x)", type);
|
||||
}
|
||||
|
||||
// Only these decoders are checked for a sequence header before an AU is passed to them
|
||||
sequence_header_found = codec->id != AV_CODEC_ID_MPEG2VIDEO && codec->id != AV_CODEC_ID_MPEG4;
|
||||
|
||||
ctx = avcodec_alloc_context3(codec);
|
||||
|
||||
if (!ctx)
|
||||
@@ -413,13 +448,12 @@ struct vdec_context final
|
||||
const bool drain = cmd->type == vdec_cmd_type::drain;
|
||||
|
||||
const u32 au_mode = cmd->mode;
|
||||
const u32 au_addr = cmd->au.startAddr;
|
||||
const u32 au_size = cmd->au.size;
|
||||
const u64 au_pts = u64{cmd->au.pts.upper} << 32 | cmd->au.pts.lower;
|
||||
const u64 au_dts = u64{cmd->au.dts.upper} << 32 | cmd->au.dts.lower;
|
||||
au_usrd = cmd->au.userData;
|
||||
|
||||
packet.data = vm::_ptr<u8>(au_addr);
|
||||
packet.data = cmd->au_data.data();
|
||||
packet.size = au_size;
|
||||
packet.pts = au_pts != umax ? au_pts : s64{smin};
|
||||
packet.dts = au_dts != umax ? au_dts : s64{smin};
|
||||
@@ -447,10 +481,23 @@ struct vdec_context final
|
||||
{
|
||||
cellVdec.trace("AU decoding: handle=0x%x, seq_id=%d, cmd_id=%d, size=0x%x, pts=0x%llx, dts=0x%llx, userdata=0x%llx", handle, cmd->seq_id, cmd->id, au_size, au_pts, au_dts, au_usrd);
|
||||
|
||||
if (int ret = avcodec_send_packet(ctx, drain ? nullptr : &packet); ret < 0)
|
||||
if (!drain && !sequence_header_found && !au_has_sequence_header(codec->id, packet.data, au_size))
|
||||
{
|
||||
// The decoder cannot use an AU before a sequence header (e.g. a stream continued on a new decoder,
|
||||
// or one that sends its sequence header after its first pictures): skip it
|
||||
if (skipped_au_count++ == 0)
|
||||
{
|
||||
cellVdec.warning("No sequence header yet, skipping AUs until one arrives (handle=0x%x, seq_id=%d, cmd_id=%d)", handle, cmd->seq_id, cmd->id);
|
||||
}
|
||||
}
|
||||
else if (int ret = avcodec_send_packet(ctx, drain ? nullptr : &packet); ret < 0)
|
||||
{
|
||||
fmt::throw_exception("AU queuing error (handle=0x%x, seq_id=%d, cmd_id=%d, error=0x%x): %s", handle, cmd->seq_id, cmd->id, ret, utils::av_error_to_string(ret));
|
||||
}
|
||||
else
|
||||
{
|
||||
sequence_header_found = true;
|
||||
}
|
||||
|
||||
while (!abort_decode && seq_id == cmd->seq_id)
|
||||
{
|
||||
@@ -577,6 +624,12 @@ struct vdec_context final
|
||||
|
||||
decoded_frames.push_back(std::move(frame));
|
||||
}
|
||||
|
||||
if (skipped_au_count && !decoded_frames.empty())
|
||||
{
|
||||
cellVdec.warning("Decoder output resumed after %d skipped AUs (handle=0x%x, seq_id=%d, cmd_id=%d)", skipped_au_count, handle, cmd->seq_id, cmd->id);
|
||||
skipped_au_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (thread_ctrl::state() != thread_state::aborting)
|
||||
@@ -610,10 +663,10 @@ struct vdec_context final
|
||||
|
||||
thread_ctrl::wait_for(10000);
|
||||
|
||||
if (elapsed++ >= 500) // 5 seconds
|
||||
// Report a wait once: a game may keep the pictures of a decoder it does not display for a long time
|
||||
if (++elapsed == 500) // 5 seconds
|
||||
{
|
||||
cellVdec.error("Video au decode has been waiting for a consumer for 5 seconds. (handle=0x%x, seq_id=%d, cmd_id=%d, queue_size=%d)", handle, cmd->seq_id, cmd->id, out_queue.size());
|
||||
elapsed = 0;
|
||||
cellVdec.warning("Video au decode has been waiting for a consumer for 5 seconds. (handle=0x%x, seq_id=%d, cmd_id=%d, queue_size=%d)", handle, cmd->seq_id, cmd->id, out_queue.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,6 +675,11 @@ struct vdec_context final
|
||||
break;
|
||||
}
|
||||
|
||||
if (elapsed >= 500)
|
||||
{
|
||||
cellVdec.notice("Video au decode resumed after waiting %d seconds for a consumer. (handle=0x%x, seq_id=%d, cmd_id=%d)", elapsed / 100, handle, cmd->seq_id, cmd->id);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
out_queue.push_back(std::move(decoded_frames.front()));
|
||||
@@ -1685,7 +1743,8 @@ error_code cellVdecDecodeAu(ppu_thread& ppu, u32 handle, CellVdecDecodeMode mode
|
||||
|
||||
if (!vdec->au_count.try_inc(4))
|
||||
{
|
||||
return CELL_VDEC_ERROR_BUSY;
|
||||
// The AU queue is full: BUSY asks the caller to retry, it is not an error
|
||||
return not_an_error(CELL_VDEC_ERROR_BUSY);
|
||||
}
|
||||
|
||||
const u64 seq_id = vdec->seq_id;
|
||||
@@ -1732,7 +1791,8 @@ error_code cellVdecDecodeAuEx2(ppu_thread& ppu, u32 handle, CellVdecDecodeMode m
|
||||
|
||||
if (!vdec->au_count.try_inc(4))
|
||||
{
|
||||
return CELL_VDEC_ERROR_BUSY;
|
||||
// The AU queue is full: BUSY asks the caller to retry, it is not an error
|
||||
return not_an_error(CELL_VDEC_ERROR_BUSY);
|
||||
}
|
||||
|
||||
CellVdecAuInfo au_info{};
|
||||
|
||||
Reference in New Issue
Block a user