aboutsummaryrefslogtreecommitdiff
path: root/src/video_core
diff options
context:
space:
mode:
authorJonas <104419015+jojo-chaechae@users.noreply.github.com>2026-11-03 05:58:07 +0600
committerGitHub <noreply@github.com>2026-11-03 05:58:07 +0600
commitaae778edce69f18513e952d3d267518b0f7d3f72 (patch)
treebe8c37b01c91ba58937c87791532ea1019c603b9 /src/video_core
parent2d81fd5072c060737c18ed4cfc46a04fb8cbb94c (diff)
parent873cae5569e9f0069a73f24ab5e4ffd0799b02e7 (diff)
Merge pull request #356 from Djihads80/fix/nx2-video-decode
nx2: video decoding fixes (green movies, end of stream, H.264 detection)
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/host1x/codec_types.h8
-rw-r--r--src/video_core/host1x/codecs/vp9.cpp43
-rw-r--r--src/video_core/host1x/nx2_video_decoder.cpp78
-rw-r--r--src/video_core/host1x/nx2_video_decoder.h3
4 files changed, 113 insertions, 19 deletions
diff --git a/src/video_core/host1x/codec_types.h b/src/video_core/host1x/codec_types.h
index b2f80c822a..da9b713b26 100644
--- a/src/video_core/host1x/codec_types.h
+++ b/src/video_core/host1x/codec_types.h
@@ -312,6 +312,10 @@ struct Vp9PictureInfo {
bool allow_high_precision_mv;
bool segment_enabled;
bool mode_ref_delta_enabled;
+
+ Segmentation segmentation;
+ std::array<u8, 7> seg_tree_probs;
+ std::array<u8, 3> seg_pred_probs;
};
struct Vp9FrameContainer {
@@ -377,6 +381,10 @@ struct PictureInfo {
.allow_high_precision_mv = allow_high_precision_mv != 0,
.segment_enabled = segmentation.enabled != 0,
.mode_ref_delta_enabled = loop_filter.mode_ref_delta_enabled != 0,
+
+ .segmentation = segmentation,
+ .seg_tree_probs = {},
+ .seg_pred_probs = {},
};
}
};
diff --git a/src/video_core/host1x/codecs/vp9.cpp b/src/video_core/host1x/codecs/vp9.cpp
index dcc6261e5c..dd91f7f1b7 100644
--- a/src/video_core/host1x/codecs/vp9.cpp
+++ b/src/video_core/host1x/codecs/vp9.cpp
@@ -331,19 +331,16 @@ void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_
}
void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
- bool enabled = current_picture_info.segmentation.enabled != 0;
+ bool enabled = current_frame_info.segmentation.enabled != 0;
writer.WriteBit(enabled);
if (!enabled) {
return;
}
- auto update_map = current_picture_info.segmentation.update_map != 0;
+ auto update_map = current_frame_info.segmentation.update_map != 0;
writer.WriteBit(update_map);
if (update_map) {
- EntropyProbs entropy_probs{};
- host1x.gmmu_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs, sizeof(entropy_probs));
-
auto WriteProb = [&](u8 prob) {
bool coded = prob != 255;
writer.WriteBit(coded);
@@ -352,55 +349,55 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
}
};
- for (size_t i = 0; i < entropy_probs.mb_segment_tree_probs.size(); i++) {
- WriteProb(entropy_probs.mb_segment_tree_probs[i]);
+ for (size_t i = 0; i < current_frame_info.seg_tree_probs.size(); i++) {
+ WriteProb(current_frame_info.seg_tree_probs[i]);
}
- auto temporal_update = current_picture_info.segmentation.temporal_update != 0;
+ auto temporal_update = current_frame_info.segmentation.temporal_update != 0;
writer.WriteBit(temporal_update);
if (temporal_update) {
for (s32 i = 0; i < 3; i++) {
- WriteProb(entropy_probs.segment_pred_probs[i]);
+ WriteProb(current_frame_info.seg_pred_probs[i]);
}
}
}
- if (last_segmentation == current_picture_info.segmentation) {
+ if (last_segmentation == current_frame_info.segmentation) {
writer.WriteBit(false);
return;
}
- last_segmentation = current_picture_info.segmentation;
+ last_segmentation = current_frame_info.segmentation;
writer.WriteBit(true);
- writer.WriteBit(current_picture_info.segmentation.abs_delta != 0);
+ writer.WriteBit(current_frame_info.segmentation.abs_delta != 0);
constexpr s32 MAX_SEGMENTS = 8;
constexpr std::array SegmentationFeatureBits = {8, 6, 2, 0};
for (s32 i = 0; i < MAX_SEGMENTS; i++) {
- auto q_enabled = current_picture_info.segmentation.feature_enabled[i][0] != 0;
+ auto q_enabled = current_frame_info.segmentation.feature_enabled[i][0] != 0;
writer.WriteBit(q_enabled);
if (q_enabled) {
- writer.WriteS(current_picture_info.segmentation.feature_data[i][0],
+ writer.WriteS(current_frame_info.segmentation.feature_data[i][0],
SegmentationFeatureBits[0]);
}
- auto lf_enabled = current_picture_info.segmentation.feature_enabled[i][1] != 0;
+ auto lf_enabled = current_frame_info.segmentation.feature_enabled[i][1] != 0;
writer.WriteBit(lf_enabled);
if (lf_enabled) {
- writer.WriteS(current_picture_info.segmentation.feature_data[i][1],
+ writer.WriteS(current_frame_info.segmentation.feature_data[i][1],
SegmentationFeatureBits[1]);
}
- auto ref_enabled = current_picture_info.segmentation.feature_enabled[i][2] != 0;
+ auto ref_enabled = current_frame_info.segmentation.feature_enabled[i][2] != 0;
writer.WriteBit(ref_enabled);
if (ref_enabled) {
- writer.WriteU(current_picture_info.segmentation.feature_data[i][2],
+ writer.WriteU(current_frame_info.segmentation.feature_data[i][2],
SegmentationFeatureBits[2]);
}
- auto skip_enabled = current_picture_info.segmentation.feature_enabled[i][3] != 0;
+ auto skip_enabled = current_frame_info.segmentation.feature_enabled[i][3] != 0;
writer.WriteBit(skip_enabled);
}
}
@@ -411,6 +408,14 @@ Vp9PictureInfo VP9::GetVp9PictureInfo() {
InsertEntropy(regs.vp9_prob_tab_buffer_offset.Address(), vp9_info.entropy);
+ EntropyProbs entropy_probs{};
+ host1x.gmmu_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(),
+ &entropy_probs, sizeof(entropy_probs));
+
+ vp9_info.seg_tree_probs = entropy_probs.mb_segment_tree_probs;
+ vp9_info.seg_pred_probs = entropy_probs.segment_pred_probs;
+
+
// surface_luma_offset[0:3] contains the address of the reference frame offsets in the following
// order: last, golden, altref, current.
for (size_t i = 0; i < 4; i++) {
diff --git a/src/video_core/host1x/nx2_video_decoder.cpp b/src/video_core/host1x/nx2_video_decoder.cpp
index 9b34bd84af..86869ad5e2 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.
@@ -162,6 +226,20 @@ void Nx2VideoDecoder::Render(const Nx2Picture& picture, Nx2PictureFormat format,
const AVPixelFormat target = format == Nx2PictureFormat::Nv12 ? AV_PIX_FMT_NV12
: format == Nx2PictureFormat::Rgba8 ? AV_PIX_FMT_RGBA
: AV_PIX_FMT_BGRA;
+ // Frames copied back from a host GPU decoder are already NV12. Copy them as they are:
+ // FFmpeg 6.1's swscale leaves the chroma plane of an NV12 to NV12 copy at zero, which shows
+ // the movies green.
+ if (target == AV_PIX_FMT_NV12 && f->format == AV_PIX_FMT_NV12 &&
+ static_cast<u32>(f->width) == width && static_cast<u32>(f->height) == height) {
+ for (u32 y = 0; y < height; ++y) {
+ std::memcpy(out.data() + y * pitch, f->data[0] + y * f->linesize[0], width);
+ }
+ for (u32 y = 0; y < (height + 1) / 2; ++y) {
+ std::memcpy(out.data() + chroma_offset + y * pitch, f->data[1] + y * f->linesize[1],
+ width);
+ }
+ return;
+ }
impl->scaler = sws_getCachedContext(impl->scaler, f->width, f->height,
static_cast<AVPixelFormat>(f->format),
static_cast<int>(width), static_cast<int>(height), target,
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.