ReviewOS

also looking at this

home-lang/home

feat(video): add native AVC/HEVC and synchronized AAC MP4 streaming

#913
Merged chris wants to merge feat/avc-hevc-mp4 into main
14 files +1119 -0
packages/video/src/codecs/video/native_encoder.zigadded+80-0
Changes to packages/video/src/codecs/video/native_encoder.zig
@@ -0,0 +1,80 @@
1//! Safe optional Home AVC/HEVC integration. Compile native_encoder.c and link
2//! x264/x265. Only Home's fixed scalar ABI crosses the boundary, never vendor
3//! structures; this module works before/after Zig's removal of @cImport.
4const std = ;
5const Handle = opaque {};
6pub const Config = extern struct { codec: u32, width: u32, height: u32, fps_num: u32, fps_den: u32, bitrate: u32, quality: u32, gop: u32 };
7const RawPacket = extern struct { data: [*]const u8, size: usize, pts: i64, key: c_int };
8extern fn home_encoder_open(*const Config, *?*Handle) c_int;
9extern fn home_encoder_headers(*Handle, *RawPacket) c_int;
10extern fn home_encoder_encode(*Handle, *const [3][*]const u8, *const [3]c_int, i64, *RawPacket) c_int;
11extern fn home_encoder_request_keyframe(*Handle) c_int;
12extern fn home_encoder_flush(*Handle, *RawPacket) c_int;
13extern fn home_encoder_close(*Handle) void;
14pub const Packet = struct { data: []const u8, pts: i64, key: bool };
15pub const Encoder = struct {
16 handle: *Handle,
17 width: u32,
18 height: u32,
19 last_pts: ?i64 = null,
20 draining: bool = false,
21 failed: bool = false,
22 pub fn init(config: Config) !Encoder {
23 var out: ?*Handle = null;
24 try check(home_encoder_open(&config, &out));
25 return .{ .handle = out.?, .width = config.width, .height = config.height };
26 }
27 pub fn deinit(self: *Encoder) void {
28 home_encoder_close(self.handle);
29 }
30 pub fn headers(self: *Encoder) ![]const u8 {
31 var packet: RawPacket = undefined;
32 try check(home_encoder_headers(self.handle, &packet));
33 return packet.data[0..packet.size];
34 }
35 pub fn requestKeyframe(self: *Encoder) !void {
36 if (self.failed or self.draining) return error.EncoderFailed;
37 try check(home_encoder_request_keyframe(self.handle));
38 }
39 pub fn flush(self: *Encoder) !?Packet {
40 if (self.failed) return error.EncoderFailed;
41 self.draining = true;
42 var packet: RawPacket = undefined;
43 errdefer self.failed = true;
44 try check(home_encoder_flush(self.handle, &packet));
45 return view(packet);
46 }
47 /// Borrowed output lasts until the next call; input may be released on return.
48 /// Null means buffered (AVC VFR retains one input); always drain flush().
49 pub fn encode(self: *Encoder, planes: [3][]const u8, strides: [3]u32, width: u32, height: u32, pts: i64) !?Packet {
50 if (self.failed or self.draining) return error.EncoderFailed;
51 if (pts < 0 or (self.last_pts != null and pts <= self.last_pts.?)) return error.InvalidTimestamp;
52 if (width != self.width or height != self.height) return error.InvalidFrame;
53 for (0..3) |i| {
54 const w = if (i == 0) width else width / 2;
55 const h = if (i == 0) height else height / 2;
56 if (strides[i] < w or strides[i] > std.math.maxInt(c_int) or planes[i].len < @as(u64, strides[i]) * (h - 1) + w) return error.InvalidFrame;
57 }
58 const pointers = [3][*]const u8{ planes[0].ptr, planes[1].ptr, planes[2].ptr };
59 const pitch = [3]c_int{ @intCast(strides[0]), @intCast(strides[1]), @intCast(strides[2]) };
60 var packet: RawPacket = undefined;
61 check(home_encoder_encode(self.handle, &pointers, &pitch, pts, &packet)) catch |err| {
62 self.failed = true;
63 return err;
64 };
65 self.last_pts = pts;
66 return view(packet);
67 }
68};
69fn check(status: c_int) !void {
70 return switch (status) {
71 0 => {},
72 -1 => error.InvalidConfiguration,
73 -2 => error.OutOfMemory,
74 else => error.EncodeFailed,
75 };
76}
77fn view(packet: RawPacket) ?Packet {
78 if (packet.size == 0) return null;
79 return .{ .data = packet.data[0..packet.size], .pts = packet.pts, .key = packet.key != 0 };
80}