diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.cpp b/rpcs3/Emu/Cell/Modules/cellVdec.cpp index c646ec42a1..d6471ab000 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVdec.cpp @@ -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::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(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 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 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(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{};