aboutsummaryrefslogtreecommitdiffstats
path: root/src/ffmpeg.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ffmpeg.zig')
-rw-r--r--src/ffmpeg.zig136
1 files changed, 135 insertions, 1 deletions
diff --git a/src/ffmpeg.zig b/src/ffmpeg.zig
index fdf9068..6846eb1 100644
--- a/src/ffmpeg.zig
+++ b/src/ffmpeg.zig
@@ -40,7 +40,6 @@ fn get_format_context(
var options: ?*c.AVDictionary = null;
var i: usize = 0;
std.debug.assert(format_options.len % 2 == 0);
- std.debug.print("len {}\n", .{format_options.len});
while (i < format_options.len) : (i += 2) {
try check(c.av_dict_set(
&options,
@@ -421,3 +420,138 @@ pub const StreamSource = struct {
allocator.destroy(self);
}
};
+
+pub const BufferedStreamSource = struct {
+ source: src.Source,
+ flags: src.StreamFlags,
+
+ decoder: *Decoder,
+ format: *c.AVFormatContext,
+ stream: i32,
+ depth: i32,
+ packet: *c.AVPacket,
+
+ thread: *gl.Thread,
+
+ pub fn init(
+ allocator: std.mem.Allocator,
+ constants: *const gl.Constants,
+ texture_type: gl.Texture.Type,
+ depth: i32,
+ filename: [*:0]const u8,
+ format_name: ?[*:0]const u8,
+ format_options: []const [*c]c.lo_arg,
+ ) !*src.Source {
+ const self = try allocator.create(BufferedStreamSource);
+ errdefer allocator.destroy(self);
+
+ var format = try get_format_context(filename, format_name, format_options);
+ errdefer c.avformat_close_input(@ptrCast(&format));
+ format.flags |= c.AVFMT_FLAG_NONBLOCK;
+
+ try check(c.avformat_find_stream_info(format, null));
+
+ const video_stream = for (format.streams, 0..format.nb_streams) |c_stream, _| {
+ const stream = @as(*c.AVStream, c_stream orelse unreachable);
+ if (stream.codecpar.*.codec_type == c.AVMEDIA_TYPE_VIDEO) break stream;
+ } else unreachable;
+
+ const codec_par = video_stream.codecpar.*;
+
+ var packet = c.av_packet_alloc();
+ errdefer c.av_packet_free(&packet);
+
+ const texture = switch (texture_type) {
+ .TEXTURE_3D, .TEXTURE_2D_ARRAY => |t| gl.Texture.create(t),
+ else => return error.textureTypeInvalid,
+ };
+ texture.allocate(
+ codec_par.width,
+ codec_par.height,
+ depth,
+ c.GL_RGBA8,
+ );
+
+ const decoder = try AVDecoder.init(allocator, codec_par);
+ errdefer decoder.deinit(allocator);
+
+ self.* = .{
+ .source = .{
+ .texture = texture,
+ .deinit_fn = deinit,
+ .register_fn = register_methods,
+ .unregister_fn = unregister_methods,
+ .update_uniform_fn = update_uniform,
+ },
+ .flags = .{},
+
+ .decoder = decoder,
+ .format = format,
+ .stream = video_stream.index,
+ .depth = depth,
+ .packet = packet,
+
+ .thread = undefined,
+ };
+
+ try self.update();
+ self.thread = try gl.Thread.init(allocator, constants, update_loop, .{self});
+
+ return &self.source;
+ }
+
+ fn register_methods(source: *src.Source, name: []const u8, control: *ctrl.ControlServer) void {
+ const self: *BufferedStreamSource = @fieldParentPtr("source", source);
+ self.flags.register(name, control);
+ }
+ fn unregister_methods(source: *src.Source, name: []const u8, control: *ctrl.ControlServer) void {
+ const self: *BufferedStreamSource = @fieldParentPtr("source", source);
+ self.flags.unregister(name, control);
+ }
+
+ fn update_uniform(source: *src.Source, param: []const u8, value: gl.UniformPointer) !void {
+ const self: *BufferedStreamSource = @fieldParentPtr("source", source);
+
+ if (!std.mem.eql(u8, param, "offset")) return error.unknownSourceParam;
+ const frame = @rem(self.decoder.num_frames + self.depth - 1, self.depth);
+ try switch (value) {
+ .FLOAT => |val| val.* = @as(f32, @floatFromInt(frame)) / @as(f32, @floatFromInt(self.depth)),
+ .DOUBLE => |val| val.* = @as(f64, @floatFromInt(frame)) / @as(f64, @floatFromInt(self.depth)),
+ .INT => |val| val.* = frame,
+ .UNSIGNED_INT => |val| val.* = @intCast(frame),
+ else => error.uniformNotSupported,
+ };
+ }
+
+ fn update_loop(self: *BufferedStreamSource) !void {
+ while (!self.thread.quit) {
+ try self.update();
+ c.glFlush();
+ try std.Thread.yield();
+ }
+ }
+
+ fn update(self: *BufferedStreamSource) !void {
+ try check(c.av_read_frame(self.format, self.packet));
+ defer c.av_packet_unref(self.packet);
+
+ if (self.packet.*.stream_index != self.stream) return;
+
+ _ = try self.decoder.process_packet_fn(
+ self.decoder,
+ self.packet,
+ if (self.flags.shouldStep()) &self.source.texture else null,
+ );
+ self.decoder.num_frames = @rem(self.decoder.num_frames, self.depth);
+ }
+
+ fn deinit(source: *src.Source, allocator: std.mem.Allocator) void {
+ const self: *BufferedStreamSource = @fieldParentPtr("source", source);
+
+ self.thread.deinit(allocator);
+ self.decoder.deinit(allocator);
+ c.av_packet_free(@ptrCast(&self.packet));
+ c.avformat_close_input(@ptrCast(&self.format));
+ allocator.destroy(self);
+ }
+};