From c1f3af359179fb23df5ecc2ac8d2d6903977e0dc Mon Sep 17 00:00:00 2001 From: Billal GHILAS Date: Wed, 22 Apr 2026 20:31:37 +0100 Subject: [PATCH 1/5] fix: support zig version 0.16.0 --- build.zig.zon | 2 +- src/io.zig | 2 +- src/root.zig | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 249d156..a264f37 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .name = .media, .version = "0.1.0", .fingerprint = 0x6a2ca10c089dcfa9, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", .dependencies = .{}, .paths = .{ "build.zig", diff --git a/src/io.zig b/src/io.zig index c19f28c..01a926e 100644 --- a/src/io.zig +++ b/src/io.zig @@ -207,5 +207,5 @@ pub const BitReader = struct { }; test { - std.testing.refAllDeclsRecursive(@This()); + std.testing.refAllDecls(@This()); } diff --git a/src/root.zig b/src/root.zig index 26cfe49..a1fea64 100644 --- a/src/root.zig +++ b/src/root.zig @@ -278,8 +278,5 @@ test "Packet.mutableData: writes are visible through data slice" { } test { - std.testing.refAllDeclsRecursive(@This()); - _ = @import("h264.zig"); - _ = @import("io.zig"); - _ = @import("buffer_pool_allocator.zig"); + std.testing.refAllDecls(@This()); } From 23998a2640c5745d9ea44796a6da34eae8c64734 Mon Sep 17 00:00:00 2001 From: Billal GHILAS Date: Wed, 22 Apr 2026 22:38:11 +0100 Subject: [PATCH 2/5] fix: benchmarks --- bench/h264_sps.zig | 71 ++++++++++++++-------------------------------- build.zig | 9 +++++- build.zig.zon | 7 ++++- src/h264.zig | 6 +--- 4 files changed, 37 insertions(+), 56 deletions(-) diff --git a/bench/h264_sps.zig b/bench/h264_sps.zig index b71d12a..fc8a24d 100644 --- a/bench/h264_sps.zig +++ b/bench/h264_sps.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const zbench = @import("zbench"); const h264 = @import("media").h264; const sps_nal = [_]u8{ @@ -23,59 +24,31 @@ const sps_with_frame_cropping = [_]u8{ 0x20, }; -const iterations = 1_000_000; - -pub fn main() !void { - var buffer: [1024]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buffer); - - try stdout.interface.writeAll("\x1b[1;36m┌─────────────────────────┐\x1b[0m\n"); - try stdout.interface.writeAll("\x1b[1;36m│ H264 SPS Benchmarks │\x1b[0m\n"); - try stdout.interface.writeAll("\x1b[1;36m└─────────────────────────┘\x1b[0m\n\n"); - - // Warm-up: one pass to bring code/data into cache. - for (0..iterations) |_| { - const sps = try h264.Sps.parse(sps_nal[1..]); - std.mem.doNotOptimizeAway(sps); - } - - const fixtures = [_]struct { - name: []const u8, - data: []const u8, - }{ - .{ .name = "Basic SPS", .data = sps_nal[1..] }, - .{ .name = "SPS with scaling list", .data = sps_with_scaling_list[1..] }, - .{ .name = "SPS with frame cropping", .data = sps_with_frame_cropping[1..] }, - }; - - for (fixtures) |fixture| { - try benchMark(fixture.name, fixture.data, &stdout.interface); - } +fn benchBasicSps(allocator: std.mem.Allocator) void { + _ = allocator; + const sps = h264.Sps.parse(sps_nal[1..]) catch unreachable; + std.mem.doNotOptimizeAway(sps); +} - try stdout.interface.flush(); +fn benchScalingList(allocator: std.mem.Allocator) void { + _ = allocator; + const sps = h264.Sps.parse(sps_with_scaling_list[1..]) catch unreachable; + std.mem.doNotOptimizeAway(sps); } -fn benchMark(name: []const u8, data: []const u8, writer: *std.Io.Writer) !void { - var timer = try std.time.Timer.start(); +fn benchFrameCropping(allocator: std.mem.Allocator) void { + _ = allocator; + const sps = h264.Sps.parse(sps_with_frame_cropping[1..]) catch unreachable; + std.mem.doNotOptimizeAway(sps); +} - for (0..iterations) |_| { - const sps = try h264.Sps.parse(data); - std.mem.doNotOptimizeAway(sps); - } +pub fn main(init: std.process.Init) !void { + var bench = zbench.Benchmark.init(init.gpa, .{}); + defer bench.deinit(); - const elapsed_ns = timer.read(); - const ns_per_op = elapsed_ns / iterations; - const ops_per_sec = @as(u64, std.time.ns_per_s) / @max(ns_per_op, 1); + try bench.add("H264 Basic SPS", benchBasicSps, .{}); + try bench.add("H264 SPS with scaling list", benchScalingList, .{}); + try bench.add("H264 SPS with frame cropping", benchFrameCropping, .{}); - try writer.print("\x1b[1;33mH264 {s}\x1b[0m\n" ++ - " iterations : {d}\n" ++ - " total time : {d} ms\n" ++ - " ns/op : {d}\n" ++ - " ops/sec : {d}\n\n", .{ - name, - iterations, - elapsed_ns / std.time.ns_per_ms, - ns_per_op, - ops_per_sec, - }); + try bench.run(init.io, std.Io.File.stdout()); } diff --git a/build.zig b/build.zig index 510f769..fdcc329 100644 --- a/build.zig +++ b/build.zig @@ -17,10 +17,16 @@ pub fn build(b: *std.Build) void { test_step.dependOn(&run_media_tests.step); { + const zbench_dep = b.dependency("zbench", .{ + .target = target, + .optimize = .ReleaseFast, + }); + const zbench_mod = zbench_dep.module("zbench"); + const bench_step = b.step("bench", "Run all benchmarks"); const benches = .{ - .{ .name = "h264_sps", .src = "bench/core/h264_sps.zig" }, + .{ .name = "h264_sps", .src = "bench/h264_sps.zig" }, }; inline for (benches) |bench| { @@ -32,6 +38,7 @@ pub fn build(b: *std.Build) void { .optimize = .ReleaseFast, .imports = &.{ .{ .name = "media", .module = mod }, + .{ .name = "zbench", .module = zbench_mod }, }, }), }); diff --git a/build.zig.zon b/build.zig.zon index a264f37..ce1da8e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,7 +3,12 @@ .version = "0.1.0", .fingerprint = 0x6a2ca10c089dcfa9, // Changing this has security and trust implications. .minimum_zig_version = "0.16.0", - .dependencies = .{}, + .dependencies = .{ + .zbench = .{ + .url = "https://github.com/hendriknielaender/zBench/archive/zig-0.16.0.tar.gz", + .hash = "zbench-0.11.2-YTdc76Q_AQAxonIKZ2-H1PdcESJGwyuYylw6RkPiBqyx", + }, + }, .paths = .{ "build.zig", "build.zig.zon", diff --git a/src/h264.zig b/src/h264.zig index f7d2161..870a456 100644 --- a/src/h264.zig +++ b/src/h264.zig @@ -163,11 +163,7 @@ pub const Sps = struct { const entries: usize = if (sps.chroma_format_idc != 3) 8 else 12; for (0..entries) |i| { if (try bit_reader.takeBit() == 0) continue; - if (i < 6) { - try parseScalingList(&bit_reader, 16); - } else { - try parseScalingList(&bit_reader, 64); - } + try parseScalingList(&bit_reader, if (i < 6) 16 else 64); } } }, From 9c33b76fe9d0787d17b4df75647aad0145d08b74 Mon Sep 17 00:00:00 2001 From: Billal GHILAS Date: Sun, 26 Apr 2026 06:46:11 +0100 Subject: [PATCH 3/5] fix: buffer pool allocator --- src/buffer_pool_allocator.zig | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/buffer_pool_allocator.zig b/src/buffer_pool_allocator.zig index 2e6ba58..3a6b03c 100644 --- a/src/buffer_pool_allocator.zig +++ b/src/buffer_pool_allocator.zig @@ -78,12 +78,7 @@ pub fn BufferPoolAllocator(comptime config: Config) type { return struct { const have_mutex = config.thread_safe; - const mutex_init = if (have_mutex) std.Thread.Mutex{} else DummyMutex{}; - - const DummyMutex = struct { - inline fn lock(_: DummyMutex) void {} - inline fn unlock(_: DummyMutex) void {} - }; + const mutex_init = if (have_mutex) std.Io.Mutex.init else void; buckets: [config.bucket_sizes.len]Bucket, backing_allocator: std.mem.Allocator, @@ -94,7 +89,7 @@ pub fn BufferPoolAllocator(comptime config: Config) type { var self = @This(){ .backing_allocator = backing_allocator, .buckets = undefined, - .buffer_ref_allocator = BufferRefAllocator.init(backing_allocator), + .buffer_ref_allocator = BufferRefAllocator.empty, }; var initialized: usize = 0; errdefer { @@ -115,7 +110,7 @@ pub fn BufferPoolAllocator(comptime config: Config) type { for (0..config.bucket_sizes.len) |idx| { self.buckets[idx].deinit(self.backing_allocator); } - self.buffer_ref_allocator.deinit(); + self.buffer_ref_allocator.deinit(self.backing_allocator); } pub fn allocator(self: *@This()) std.mem.Allocator { @@ -133,9 +128,9 @@ pub fn BufferPoolAllocator(comptime config: Config) type { fn alloc(context: *anyopaque, len: usize, _: std.mem.Alignment, _: usize) ?[*]u8 { const self: *@This() = @ptrCast(@alignCast(context)); if (len == buffer_ref_size) { - if (have_mutex) std.Thread.Mutex.lock(&self.mutex); - defer if (have_mutex) std.Thread.Mutex.unlock(&self.mutex); - const buf_ref = self.buffer_ref_allocator.create() catch { + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); + const buf_ref = self.buffer_ref_allocator.create(self.backing_allocator) catch { return null; }; return @ptrCast(@alignCast(buf_ref)); @@ -143,8 +138,8 @@ pub fn BufferPoolAllocator(comptime config: Config) type { for (&self.buckets) |*bucket| { if (len <= bucket.block_size) { - if (have_mutex) std.Thread.Mutex.lock(&self.mutex); - defer if (have_mutex) std.Thread.Mutex.unlock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); if (bucket.acquire()) |b| { return b.ptr; @@ -157,8 +152,8 @@ pub fn BufferPoolAllocator(comptime config: Config) type { fn free(context: *anyopaque, memory: []u8, _: std.mem.Alignment, _: usize) void { const self: *@This() = @ptrCast(@alignCast(context)); if (memory.len == buffer_ref_size) { - if (have_mutex) std.Thread.Mutex.lock(&self.mutex); - defer if (have_mutex) std.Thread.Mutex.unlock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); self.buffer_ref_allocator.destroy(@ptrCast(@alignCast(memory.ptr))); return; } @@ -166,8 +161,9 @@ pub fn BufferPoolAllocator(comptime config: Config) type { for (&self.buckets) |*bucket| { const start = @intFromPtr(bucket.buffer.ptr); if (ptr >= start and ptr < start + bucket.buffer.len) { - if (have_mutex) std.Thread.Mutex.lock(&self.mutex); - defer if (have_mutex) std.Thread.Mutex.unlock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); + bucket.release(memory); return; } From 3687b690509a8218e54e35d6e7b06cdc7895d863 Mon Sep 17 00:00:00 2001 From: Billal GHILAS Date: Sun, 26 Apr 2026 07:16:04 +0100 Subject: [PATCH 4/5] fix: buffer pool allocator --- src/buffer_pool_allocator.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buffer_pool_allocator.zig b/src/buffer_pool_allocator.zig index 3a6b03c..cc3f5d5 100644 --- a/src/buffer_pool_allocator.zig +++ b/src/buffer_pool_allocator.zig @@ -78,12 +78,12 @@ pub fn BufferPoolAllocator(comptime config: Config) type { return struct { const have_mutex = config.thread_safe; - const mutex_init = if (have_mutex) std.Io.Mutex.init else void; + const Mutex = if (have_mutex) std.Io.Mutex else void; buckets: [config.bucket_sizes.len]Bucket, backing_allocator: std.mem.Allocator, buffer_ref_allocator: BufferRefAllocator, - mutex: @TypeOf(mutex_init) = mutex_init, + mutex: Mutex = if (have_mutex) std.Io.Mutex.init else {}, pub fn init(backing_allocator: std.mem.Allocator) !@This() { var self = @This(){ From de058e61bcbe763fe48ff503a4e193cb6d10d812 Mon Sep 17 00:00:00 2001 From: Billal GHILAS Date: Wed, 29 Apr 2026 18:56:52 +0100 Subject: [PATCH 5/5] Bump ci zig version --- .github/workflows/{zig.yml => ci.yml} | 2 +- src/buffer_pool_allocator.zig | 2 +- src/root.zig | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename .github/workflows/{zig.yml => ci.yml} (95%) diff --git a/.github/workflows/zig.yml b/.github/workflows/ci.yml similarity index 95% rename from .github/workflows/zig.yml rename to .github/workflows/ci.yml index 7bfc26e..be16a42 100644 --- a/.github/workflows/zig.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v6 - uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - run: zig build test --summary all lint: runs-on: ubuntu-latest diff --git a/src/buffer_pool_allocator.zig b/src/buffer_pool_allocator.zig index cc3f5d5..e0d8b70 100644 --- a/src/buffer_pool_allocator.zig +++ b/src/buffer_pool_allocator.zig @@ -11,7 +11,7 @@ const Bucket = struct { const Block = struct { next: ?*Block }; - fn init(allocator: std.mem.Allocator, block_size: usize, block_count: usize) !Bucket { + fn init(allocator: std.mem.Allocator, block_size: usize, block_count: usize) std.mem.Allocator.Error!Bucket { const total_size = block_size * block_count; const buffer = try allocator.alloc(u8, total_size); diff --git a/src/root.zig b/src/root.zig index a1fea64..eeddced 100644 --- a/src/root.zig +++ b/src/root.zig @@ -18,7 +18,7 @@ const BufferRef = struct { .ref_count = .init(1), }; - fn init(buffer_ref: *BufferRef, allocator: Allocator, size: usize) !void { + fn init(buffer_ref: *BufferRef, allocator: Allocator, size: usize) Allocator.Error!void { buffer_ref.data = try allocator.alloc(u8, size); } @@ -78,7 +78,7 @@ pub const Packet = struct { /// Allocates an uninitialised owned buffer of `size` bytes. /// Use `mutableData()` to fill the buffer before sharing the packet. - pub fn alloc(allocator: Allocator, size: usize) !Packet { + pub fn alloc(allocator: Allocator, size: usize) Allocator.Error!Packet { const buffer_ref = try allocator.create(BufferRef); buffer_ref.* = .{ @@ -93,7 +93,7 @@ pub const Packet = struct { } /// Allocates an owned buffer and copies `src` into it (analogous to `std.mem.Allocator.dupe`). - pub fn dupe(allocator: Allocator, src: []const u8) !Packet { + pub fn dupe(allocator: Allocator, src: []const u8) Allocator.Error!Packet { var packet = try alloc(allocator, src.len); @memcpy(packet.mutableData().?, src); return packet;