also looking at this
feat(media): add bounded PCM capture, owned snapshots and A/V streaming
#917
11 files
+683
-0
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -1,6 +1,5 @@ | ||
| 1 | 1 | //! Home opaque, header-checked ALSA capture. Build C boundary and link libasound. |
| 2 | 2 | //! Calls serialize; close only after read returns. S16LE mono/stereo, BOOTTIME ns. |
| 3 | const std = ; | |
| 4 | 3 | const Handle = opaque {}; |
| 5 | 4 | pub const Config = extern struct { sample_rate: u32, channels: u32, block_frames: u32 }; |
| 6 | 5 | extern fn home_audio_open(*const Config, [*:0]const u8, *?*Handle) c_int; |
| @@ -23,7 +22,7 @@ pub const Capture = struct { | ||
| 23 | 22 | if (s.failed) return error.CaptureFailed; |
| 24 | 23 | if (data.len < @as(u64, s.config.block_frames) * s.config.channels * 2 or timeout_ms > 1000) return error.InvalidConfiguration; |
| 25 | 24 | var block: Block = undefined; |
| 26 | const rc = home_audio_read(s.handle, data.ptr, @intCast(data.len / (s.config.channels * 2)), timeout_ms, &block.frames, &block.timestamp_ns, &block.clock_error_ns); | |
| 25 | const rc = home_audio_read(s.handle, data.ptr, @intCast(s.config.block_frames), timeout_ms, &block.frames, &block.timestamp_ns, &block.clock_error_ns); | |
| 27 | 26 | if (rc == 1) return null; |
| 28 | 27 | check(rc) catch |err| { |
| 29 | 28 | s.failed = true; |
| @@ -42,13 +42,8 @@ pub const Queue = struct { | ||
| 42 | 42 | if (data.len == 0 or data.len % stride != 0) return error.InvalidPcm; |
| 43 | 43 | const frames = data.len / stride; |
| 44 | 44 | if (frames > s.capacity - s.used or s.count == s.entries.len) return error.BufferFull; |
| 45 | var ts = timestamp; | |
| 46 | if (s.last_end) |end| { | |
| 47 | if (ts < end) { | |
| 48 | if (end - ts > (1_000_000_000 + s.rate - 1) / s.rate) return error.InvalidTimestamp; | |
| 49 | ts = end; | |
| 50 | } | |
| 51 | } | |
| 45 | const ts = timestamp; | |
| 46 | if (s.last_end) |end| if (ts < end) return error.InvalidTimestamp; | |
| 52 | 47 | const end = try at(ts, frames, s.rate); |
| 53 | 48 | const first = @min(frames, s.capacity - s.write_pos); |
| 54 | 49 | @memcpy(s.data[s.write_pos * stride ..][0 .. first * stride], data[0 .. first * stride]); |
| @@ -115,6 +110,9 @@ test "PCM rejects full queues, invalid inputs and overflow before modifying hist | ||
| 115 | 110 | try std.testing.expectError(error.InvalidPcm, q.push(&.{1}, 2000000)); |
| 116 | 111 | try std.testing.expectError(error.InvalidTimestamp, q.push(&.{ 1, 0 }, 1)); |
| 117 | 112 | try std.testing.expectError(error.TimestampOverflow, at(std.math.maxInt(u64), 1, 48000)); |
| 113 | const previous = q.last_end.?; | |
| 114 | try std.testing.expectError(error.InvalidTimestamp, q.push(&.{ 1, 0 }, previous - 1)); | |
| 115 | try std.testing.expectEqual(previous, q.last_end.?); | |
| 118 | 116 | try std.testing.expectEqual(@as(u64, 1_000_000_000), try at(0, 44100, 44100)); |
| 119 | 117 | } |
| 120 | 118 | test "PCM partial allocation failure cleanup" { |