aboutsummaryrefslogtreecommitdiff
path: root/src/video_core
diff options
context:
space:
mode:
authorrdenno <rhysdenno@gmail.com>2026-10-31 12:14:21 -0400
committerJonas <baguettegamerofficial2020@gmail.com>2026-11-03 05:55:03 +0600
commit701c91b402f47fdbb36d4f7c4b4bf1d0db08888b (patch)
tree038b9bcd39b7c62f77616a4277ab8c43deb90ce3 /src/video_core
parentbfd6430918ff4eda8c8c0e5fd0a927b3f64ff258 (diff)
nx2: vdecsvc picks H.264 when the configure carries no codec name and the stream is H.264 (Yoshi demo)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/host1x/nx2_video_decoder.cpp64
-rw-r--r--src/video_core/host1x/nx2_video_decoder.h3
2 files changed, 67 insertions, 0 deletions
diff --git a/src/video_core/host1x/nx2_video_decoder.cpp b/src/video_core/host1x/nx2_video_decoder.cpp
index 9b34bd84af..87265ee6f6 100644
--- a/src/video_core/host1x/nx2_video_decoder.cpp
+++ b/src/video_core/host1x/nx2_video_decoder.cpp
@@ -47,6 +47,45 @@ std::optional<NvdecCommon::VideoCodec> CodecFromMime(std::string_view mime) {
} // namespace
+bool IsH264Stream(std::span<const u8> data) {
+ if (data.size() >= 4) {
+ // Annex-B 3-byte start code: 00 00 01 <nal_header>
+ if (data[0] == 0 && data[1] == 0 && data[2] == 1) {
+ const u8 nal_type = data[3] & 0x1F;
+ if ((data[3] & 0x80) == 0 && nal_type >= 1 && nal_type <= 23) {
+ return true;
+ }
+ }
+ }
+ if (data.size() >= 5) {
+ // Annex-B 4-byte start code: 00 00 00 01 <nal_header>
+ if (data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1) {
+ const u8 nal_type = data[4] & 0x1F;
+ if ((data[4] & 0x80) == 0 && nal_type >= 1 && nal_type <= 23) {
+ return true;
+ }
+ }
+ // avcC / MP4 length-prefixed NAL unit: [u32 be length] <nal_header>
+ const u32 nalu_len = (static_cast<u32>(data[0]) << 24) |
+ (static_cast<u32>(data[1]) << 16) |
+ (static_cast<u32>(data[2]) << 8) |
+ static_cast<u32>(data[3]);
+ if (nalu_len > 0 && nalu_len <= data.size() - 4) {
+ const u8 nal_type = data[4] & 0x1F;
+ if ((data[4] & 0x80) == 0 && nal_type >= 1 && nal_type <= 23) {
+ return true;
+ }
+ }
+ }
+ if (data.size() >= 7) {
+ // avcC configuration record: version=1, profile, compat, level, lengthSizeMinusOne | 0xFC, numSPS | 0xE0
+ if (data[0] == 1 && (data[4] & 0xFC) == 0xFC && (data[5] & 0xE0) == 0xE0 && (data[5] & 0x1F) > 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
struct Nx2VideoDecoder::Impl {
std::string name;
std::optional<FFmpeg::Decoder> decoder;
@@ -123,6 +162,31 @@ void Nx2VideoDecoder::Decode(std::span<const u8> data, u64 pts, std::vector<Nx2P
}
impl->packet.assign(data.size() + AV_INPUT_BUFFER_PADDING_SIZE, 0);
std::memcpy(impl->packet.data(), data.data(), data.size());
+
+ // Convert MP4/avcC length-prefixed NALUs to Annex-B start codes for FFmpeg if needed.
+ if (impl->name.find("avc") != std::string::npos || impl->name.find("264") != std::string::npos) {
+ const bool is_annex_b = data.size() >= 4 &&
+ ((data[0] == 0 && data[1] == 0 && data[2] == 1) ||
+ (data.size() >= 5 && data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1));
+ if (!is_annex_b && data.size() >= 5) {
+ size_t offset = 0;
+ while (offset + 4 < data.size()) {
+ const u32 nalu_len = (static_cast<u32>(impl->packet[offset]) << 24) |
+ (static_cast<u32>(impl->packet[offset + 1]) << 16) |
+ (static_cast<u32>(impl->packet[offset + 2]) << 8) |
+ static_cast<u32>(impl->packet[offset + 3]);
+ if (nalu_len == 0 || offset + 4 + nalu_len > data.size()) {
+ break;
+ }
+ impl->packet[offset] = 0;
+ impl->packet[offset + 1] = 0;
+ impl->packet[offset + 2] = 0;
+ impl->packet[offset + 3] = 1;
+ offset += 4 + nalu_len;
+ }
+ }
+ }
+
FFmpeg::Packet packet{std::span<const u8>{impl->packet.data(), data.size()}};
packet.GetPacket()->pts = static_cast<s64>(pts);
// Hidden VP9 frames never come out: keep the set from growing over a long movie.
diff --git a/src/video_core/host1x/nx2_video_decoder.h b/src/video_core/host1x/nx2_video_decoder.h
index c979a2c801..347b0e6525 100644
--- a/src/video_core/host1x/nx2_video_decoder.h
+++ b/src/video_core/host1x/nx2_video_decoder.h
@@ -32,6 +32,9 @@ enum class Nx2PictureFormat : u32 {
Bgra8,
};
+/// Checks if stream data begins with an H.264 Annex-B start code or avcC/MP4 length-prefixed NALUs.
+bool IsH264Stream(std::span<const u8> data);
+
/// The decoder behind the Switch 2 video decoder service (nn::codec::video): FFmpeg decodes the
/// elementary stream the game demuxed, in software or on the host GPU (NVDEC emulation setting).
/// Used from one thread at a time.