-
Notifications
You must be signed in to change notification settings - Fork 1
Add Rockchip MPP decoding #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0860320
1a9ec1d
071cd61
47393aa
052d6e1
2a9d052
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| [submodule "vendor/libdatachannel"] | ||
| path = vendor/libdatachannel | ||
| url = https://github.com/paullouisageneau/libdatachannel.git | ||
| [submodule "vendor/mpp"] | ||
| path = vendor/mpp | ||
| url = https://github.com/rockchip-linux/mpp.git |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,29 +1,78 @@ | ||||||
| const std = @import("std"); | ||||||
|
|
||||||
| // H700 target profile: both devices run vendor kernel 4.9.170 with glibc | ||||||
| // 2.38 (muOS) / 2.40 (Knulli). Targeting the 2.38 ceiling covers both. | ||||||
| const h700_query: std.Target.Query = .{ | ||||||
| // glibc 2.38 is the oldest userspace supported by the packaged release. | ||||||
| const aarch64_linux_query: std.Target.Query = .{ | ||||||
| .cpu_arch = .aarch64, | ||||||
| .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.cortex_a53 }, | ||||||
| .os_tag = .linux, | ||||||
| .abi = .gnu, | ||||||
| .glibc_version = .{ .major = 2, .minor = 38, .patch = 0 }, | ||||||
| }; | ||||||
|
|
||||||
| const c_test_flags = &.{ "-std=c11", "-Wall", "-Wextra", "-Werror" }; | ||||||
|
|
||||||
| fn addHostZigObject(b: *std.Build, name: []const u8, source: []const u8) *std.Build.Step.Compile { | ||||||
| const module = b.createModule(.{ | ||||||
| .root_source_file = b.path(source), | ||||||
| .target = b.graph.host, | ||||||
| .optimize = .Debug, | ||||||
| .link_libc = true, | ||||||
| }); | ||||||
| module.addIncludePath(b.path("src/media/video")); | ||||||
| return b.addObject(.{ .name = name, .root_module = module }); | ||||||
| } | ||||||
|
|
||||||
| fn addHostCExecutable( | ||||||
| b: *std.Build, | ||||||
| name: []const u8, | ||||||
| sources: []const []const u8, | ||||||
| objects: []const *std.Build.Step.Compile, | ||||||
| link_dl: bool, | ||||||
| ) *std.Build.Step.Compile { | ||||||
| const executable = b.addExecutable(.{ | ||||||
| .name = name, | ||||||
| .root_source_file = null, | ||||||
| .target = b.graph.host, | ||||||
| .optimize = .Debug, | ||||||
| }); | ||||||
| executable.addIncludePath(b.path("src/media/video")); | ||||||
| executable.addCSourceFiles(.{ .files = sources, .flags = c_test_flags }); | ||||||
| for (objects) |object| executable.addObject(object); | ||||||
| executable.linkLibC(); | ||||||
| if (link_dl) executable.linkSystemLibrary("dl"); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: On macOS hosts, this unconditional Prompt for AI agents
Suggested change
|
||||||
| return executable; | ||||||
| } | ||||||
|
|
||||||
| fn addHostCFakeLibrary( | ||||||
| b: *std.Build, | ||||||
| name: []const u8, | ||||||
| source: []const u8, | ||||||
| ) *std.Build.Step.Compile { | ||||||
| const library = b.addSharedLibrary(.{ | ||||||
| .name = name, | ||||||
| .root_source_file = null, | ||||||
| .target = b.graph.host, | ||||||
| .optimize = .Debug, | ||||||
| }); | ||||||
| library.addIncludePath(b.path("src/media/video")); | ||||||
| library.addCSourceFile(.{ .file = b.path(source), .flags = c_test_flags }); | ||||||
| library.linkLibC(); | ||||||
| return library; | ||||||
| } | ||||||
|
|
||||||
| pub fn build(b: *std.Build) void { | ||||||
| const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSafe }); | ||||||
| const h700 = b.resolveTargetQuery(h700_query); | ||||||
| const aarch64_linux = b.resolveTargetQuery(aarch64_linux_query); | ||||||
|
|
||||||
| const stat_compat = b.createModule(.{ | ||||||
| .root_source_file = b.path("src/platform/linux/stat_compat.zig"), | ||||||
| .target = h700, | ||||||
| .target = aarch64_linux, | ||||||
| .optimize = optimize, | ||||||
| }); | ||||||
|
|
||||||
| const smoke = b.addExecutable(.{ | ||||||
| .name = "greenovercast-smoke", | ||||||
| .root_source_file = b.path("src/smoke/abi_smoke.zig"), | ||||||
| .target = h700, | ||||||
| .target = aarch64_linux, | ||||||
| .optimize = optimize, | ||||||
| .link_libc = true, | ||||||
| }); | ||||||
|
|
@@ -46,6 +95,9 @@ pub fn build(b: *std.Build) void { | |||||
| .{ .name = "greenovercast-cloud", .path = "src/session/cloud_session.zig" }, | ||||||
| .{ .name = "greenovercast-controller", .path = "src/input/controller.zig" }, | ||||||
| .{ .name = "greenovercast-ui", .path = "src/ui/handheld_ui.zig" }, | ||||||
| .{ .name = "greenovercast-video-decoder", .path = "src/media/video/video_decoder.zig" }, | ||||||
| .{ .name = "greenovercast-video-decoder-selection", .path = "src/media/video/video_decoder_selection.zig" }, | ||||||
| .{ .name = "greenovercast-video-frame-copy", .path = "src/media/video/video_frame_copy.zig" }, | ||||||
| }; | ||||||
| const product_include_paths = [_][]const u8{ | ||||||
| "vendor/headers", | ||||||
|
|
@@ -63,7 +115,7 @@ pub fn build(b: *std.Build) void { | |||||
| for (product_roots) |root| { | ||||||
| const module = b.createModule(.{ | ||||||
| .root_source_file = b.path(root.path), | ||||||
| .target = h700, | ||||||
| .target = aarch64_linux, | ||||||
| .optimize = .ReleaseSafe, | ||||||
| .link_libc = true, | ||||||
| }); | ||||||
|
|
@@ -97,4 +149,95 @@ pub fn build(b: *std.Build) void { | |||||
| }); | ||||||
| test_step.dependOn(&b.addRunArtifact(unit_tests).step); | ||||||
| } | ||||||
|
|
||||||
| const video_decoder_object = | ||||||
| addHostZigObject(b, "video-decoder", "src/media/video/video_decoder.zig"); | ||||||
| const video_frame_copy_object = | ||||||
| addHostZigObject(b, "video-frame-copy", "src/media/video/video_frame_copy.zig"); | ||||||
| const video_decoder_selection_object = addHostZigObject( | ||||||
| b, | ||||||
| "video-decoder-selection", | ||||||
| "src/media/video/video_decoder_selection.zig", | ||||||
| ); | ||||||
|
|
||||||
| const decoder_contract_test = addHostCExecutable( | ||||||
| b, | ||||||
| "video-decoder-test", | ||||||
| &.{"tests/video_decoder_test.c"}, | ||||||
| &.{video_decoder_object}, | ||||||
| false, | ||||||
| ); | ||||||
| test_step.dependOn(&b.addRunArtifact(decoder_contract_test).step); | ||||||
|
|
||||||
| const decoder_selection_test = addHostCExecutable( | ||||||
| b, | ||||||
| "video-decoder-selection-test", | ||||||
| &.{"tests/video_decoder_selection_test.c"}, | ||||||
| &.{ video_decoder_object, video_decoder_selection_object }, | ||||||
| false, | ||||||
| ); | ||||||
| test_step.dependOn(&b.addRunArtifact(decoder_selection_test).step); | ||||||
|
|
||||||
| const frame_copy_test = addHostCExecutable( | ||||||
| b, | ||||||
| "video-frame-copy-test", | ||||||
| &.{"tests/video_frame_copy_test.c"}, | ||||||
| &.{video_frame_copy_object}, | ||||||
| false, | ||||||
| ); | ||||||
| test_step.dependOn(&b.addRunArtifact(frame_copy_test).step); | ||||||
|
|
||||||
| const fake_cedar_valid = | ||||||
| addHostCFakeLibrary(b, "fake-cedar-valid", "tests/cedar_fake_valid.c"); | ||||||
| const cedar_decoder_test = addHostCExecutable( | ||||||
| b, | ||||||
| "video-decoder-cedar-test", | ||||||
| &.{ | ||||||
| "src/media/video/cedar_loader.c", | ||||||
| "src/media/video/video_decoder_cedar.c", | ||||||
| "tests/video_decoder_cedar_test.c", | ||||||
| }, | ||||||
| &.{video_decoder_object}, | ||||||
| true, | ||||||
| ); | ||||||
| const run_cedar_decoder_test = b.addRunArtifact(cedar_decoder_test); | ||||||
| run_cedar_decoder_test.addArtifactArg(fake_cedar_valid); | ||||||
| test_step.dependOn(&run_cedar_decoder_test.step); | ||||||
|
|
||||||
| const fake_mpp_valid = addHostCFakeLibrary(b, "fake-mpp-valid", "tests/mpp_fake_valid.c"); | ||||||
| const fake_mpp_wrong_abi = | ||||||
| addHostCFakeLibrary(b, "fake-mpp-wrong-abi", "tests/mpp_fake_wrong_abi.c"); | ||||||
| const fake_mpp_missing_symbol = | ||||||
| addHostCFakeLibrary(b, "fake-mpp-missing-symbol", "tests/mpp_fake_missing_symbol.c"); | ||||||
|
|
||||||
| const mpp_loader_test = addHostCExecutable( | ||||||
| b, | ||||||
| "mpp-loader-test", | ||||||
| &.{ | ||||||
| "src/media/video/mpp_loader.c", | ||||||
| "tests/mpp_loader_test.c", | ||||||
| }, | ||||||
| &.{}, | ||||||
| true, | ||||||
| ); | ||||||
| const run_mpp_loader_test = b.addRunArtifact(mpp_loader_test); | ||||||
| run_mpp_loader_test.addArtifactArg(fake_mpp_valid); | ||||||
| run_mpp_loader_test.addArtifactArg(fake_mpp_wrong_abi); | ||||||
| run_mpp_loader_test.addArtifactArg(fake_mpp_missing_symbol); | ||||||
| test_step.dependOn(&run_mpp_loader_test.step); | ||||||
|
|
||||||
| const mpp_decoder_test = addHostCExecutable( | ||||||
| b, | ||||||
| "video-decoder-mpp-test", | ||||||
| &.{ | ||||||
| "src/media/video/mpp_loader.c", | ||||||
| "src/media/video/video_decoder_mpp.c", | ||||||
| "tests/video_decoder_mpp_test.c", | ||||||
| }, | ||||||
| &.{video_decoder_object}, | ||||||
| true, | ||||||
| ); | ||||||
| const run_mpp_decoder_test = b.addRunArtifact(mpp_decoder_test); | ||||||
| run_mpp_decoder_test.addArtifactArg(fake_mpp_valid); | ||||||
| test_step.dependOn(&run_mpp_decoder_test.step); | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,10 +66,23 @@ pub const Release = struct { | |
|
|
||
| fn initializeMedia(self: *Release) bool { | ||
| const bootstrap_path = std.posix.getenv("GREENOVERCAST_H264_BOOTSTRAP_FILE"); | ||
| self.video = c.go_video_pipeline_create( | ||
| c.go_sdl_platform_renderer(self.platform), | ||
| if (bootstrap_path) |path| path.ptr else null, | ||
| ); | ||
| const decoder_value = std.posix.getenv("GREENOVERCAST_VIDEO_DECODER"); | ||
| var decoder_preference: c.GoVideoDecoderPreference = c.GO_VIDEO_DECODER_PREFERENCE_AUTO; | ||
| if (c.go_video_decoder_preference_parse( | ||
| if (decoder_value) |value| value.ptr else null, | ||
| &decoder_preference, | ||
| ) != 0) { | ||
| std.debug.print("Invalid GREENOVERCAST_VIDEO_DECODER value\n", .{}); | ||
| return false; | ||
| } | ||
| const config = c.GoVideoPipelineConfig{ | ||
| .renderer = c.go_sdl_platform_renderer(self.platform), | ||
| .bootstrap_path = if (bootstrap_path) |path| path.ptr else null, | ||
| .max_width = 1280, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: When a decoded frame exceeds 1280×720, this configuration rejects it regardless of the negotiated stream size. The pipeline then treats the rejection as decoder failure, falls back to software, and reaches terminal failure; use negotiated dimensions or handle this case separately. Prompt for AI agents |
||
| .max_height = 720, | ||
| .decoder_preference = decoder_preference, | ||
| }; | ||
| self.video = c.go_video_pipeline_create(&config); | ||
| if (self.video == null or c.go_video_pipeline_start(self.video) < 0) { | ||
| std.debug.print("H.264 pipeline initialization failed\n", .{}); | ||
| _ = c.go_video_pipeline_destroy(self.video); | ||
|
|
@@ -379,6 +392,8 @@ pub const Release = struct { | |
| std.debug.print("Cloud game ended\n", .{}); | ||
| return .session_ended; | ||
| } | ||
| if (c.go_video_pipeline_failed(self.video) != 0) | ||
| return .failed; | ||
|
|
||
| c.go_webrtc_session_send_gamepad(self.webrtc); | ||
| c.go_video_pipeline_render(self.video); | ||
|
|
@@ -417,8 +432,8 @@ pub const Release = struct { | |
| std.debug.print( | ||
| "[{d}s] video_rtp={d} payload={d} rejected={d}/pt{d} aus={d} frames={d}/{d} " ++ | ||
| "nals={d}/{d}/{d}/{d} ts={d} synced={d} gaps={d} missing={d} late_rtp={d} " ++ | ||
| "decode_errors={d}/{d}/{d} keyframes={d} queue={d}/{d} audio_rtp={d} decoded={d} " ++ | ||
| "dropped={d} late={d} pending={d} queued_ms={d} resets={d} keepalive={d}/{d}\n", | ||
| "decoder={d} init_failures={d} fallbacks={d} backpressure={d} corrupt={d} " ++ | ||
| "info_changes={d} decode_errors={d}/{d}/{d} keyframes={d} queue={d}/{d}\n", | ||
| .{ | ||
| elapsed_ms / 1000, | ||
| video.rtp_packets, | ||
|
|
@@ -437,12 +452,25 @@ pub const Release = struct { | |
| video.discontinuities, | ||
| video.missing_packets, | ||
| video.late_packets, | ||
| video.decoder_backend, | ||
| video.decoder_init_failures, | ||
| video.decoder_runtime_fallbacks, | ||
| video.decoder_backpressure_events, | ||
| video.decoder_corrupt_frames, | ||
| video.decoder_info_changes, | ||
| video.decoder_send_errors, | ||
| video.decoder_receive_errors, | ||
| video.last_decoder_error, | ||
| video.keyframe_requests, | ||
| video.pending_packets, | ||
| video.dropped_packets, | ||
| }, | ||
| ); | ||
| std.debug.print( | ||
| "[{d}s] audio_rtp={d} decoded={d} dropped={d} late={d} pending={d} " ++ | ||
| "queued_ms={d} resets={d} keepalive={d}/{d}\n", | ||
| .{ | ||
| elapsed_ms / 1000, | ||
| audio.rtp_packets, | ||
| audio.decoded_packets, | ||
| audio.dropped_packets, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: The README now announces Rockchip/ROCKNIX MPP support, but the Supported devices table and the intro still list only H700 devices on muOS/Knulli, so no Rockchip device or ROCKNIX row exists for users to check against. The added claim also conflicts with the intro, which says the RG40XX-H was validated on Knulli even though the PR was tested on Rocknix. Add a Rockchip device/firmware row to the table (and align the intro's validation OS) so the new claim is reflected.
Prompt for AI agents