also looking at this
feat(video): add native AVC/HEVC and synchronized AAC MP4 streaming
#913
14 files
+1119
-0
| @@ -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. | |
| 4 | const std = ; | |
| 5 | const Handle = opaque {}; | |
| 6 | pub const Config = extern struct { codec: u32, width: u32, height: u32, fps_num: u32, fps_den: u32, bitrate: u32, quality: u32, gop: u32 }; | |
| 7 | const RawPacket = extern struct { data: [*]const u8, size: usize, pts: i64, key: c_int }; | |
| 8 | extern fn home_encoder_open(*const Config, *?*Handle) c_int; | |
| 9 | extern fn home_encoder_headers(*Handle, *RawPacket) c_int; | |
| 10 | extern fn home_encoder_encode(*Handle, *const [3][*]const u8, *const [3]c_int, i64, *RawPacket) c_int; | |
| 11 | extern fn home_encoder_request_keyframe(*Handle) c_int; | |
| 12 | extern fn home_encoder_flush(*Handle, *RawPacket) c_int; | |
| 13 | extern fn home_encoder_close(*Handle) void; | |
| 14 | pub const Packet = struct { data: []const u8, pts: i64, key: bool }; | |
| 15 | pub 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 | }; | |
| 69 | fn 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 | } | |
| 77 | fn 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 | } | |