aboutsummaryrefslogtreecommitdiffstats
path: root/src/ffmpeg.zig
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2025-07-27 21:40:42 +0000
committers-ol <s+removethis@s-ol.nu>2025-07-28 18:29:18 +0000
commit6850ad3da37ae1d49b959ddb5e8c11a2a1481bda (patch)
treeb90e231de261c62d38c009d9e6cf907838f34b8c /src/ffmpeg.zig
parentfix texture binding bug (diff)
downloadglsl-view-6850ad3da37ae1d49b959ddb5e8c11a2a1481bda.tar.gz
glsl-view-6850ad3da37ae1d49b959ddb5e8c11a2a1481bda.zip
add libav StreamSource, run Sources in threads
Diffstat (limited to 'src/ffmpeg.zig')
-rw-r--r--src/ffmpeg.zig163
1 files changed, 135 insertions, 28 deletions
diff --git a/src/ffmpeg.zig b/src/ffmpeg.zig
index f848ef8..be7b6bf 100644
--- a/src/ffmpeg.zig
+++ b/src/ffmpeg.zig
@@ -23,6 +23,23 @@ pub fn check(err: c_int) Errors!void {
return error.AVGenericError;
}
+fn get_format_context(filename: [*:0]const u8, format_name: ?[*:0]const u8) !*c.AVFormatContext {
+ var format_ctx: ?*c.AVFormatContext = null;
+ var format: ?*const c.AVInputFormat = null;
+ var options: ?*c.AVDictionary = null;
+
+ c.avdevice_register_all();
+
+ if (format_name) |name| {
+ format = c.av_find_input_format(name);
+ }
+
+ try check(c.avformat_open_input(&format_ctx, filename, format, &options));
+ errdefer c.avformat_close_input(&format_ctx);
+
+ return format_ctx orelse error.AVGenericError;
+}
+
pub const Decoder = struct {
num_frames: i32,
@@ -231,42 +248,38 @@ pub const VideoSource = struct {
const progress = progress_root.start("loading texture", 2);
defer progress.end();
- var format_ctx: ?*c.AVFormatContext = null;
- try check(c.avformat_open_input(&format_ctx, filename, null, null));
- defer c.avformat_close_input(&format_ctx);
+ var format = try get_format_context(filename, null);
+ defer c.avformat_close_input(@ptrCast(&format));
- if (format_ctx) |format| {
- try check(c.avformat_find_stream_info(format, null));
+ 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 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.*;
+ const codec_par = video_stream.codecpar.*;
- var packet = c.av_packet_alloc();
- defer c.av_packet_free(&packet);
+ var packet = c.av_packet_alloc();
+ defer c.av_packet_free(&packet);
- const is_hap = codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '1') or
- codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '5');
+ const is_hap = codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '1') or
+ codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '5');
- self.* = .{
- .source = .{
- .texture = undefined,
+ self.* = .{
+ .source = .{
+ .texture = undefined,
- .update_fn = null,
- .deinit_fn = deinit,
- },
- .decoder = if (is_hap and build_config.have_hap)
- try @import("hap.zig").HAPDecoder.init(allocator, codec_par)
- else
- try AVDecoder.init(allocator, codec_par),
- };
+ .deinit_fn = deinit,
+ },
+ .decoder = if (is_hap and build_config.have_hap)
+ try @import("hap.zig").HAPDecoder.init(allocator, codec_par)
+ else
+ try AVDecoder.init(allocator, codec_par),
+ };
- self.source.texture = try self.decoder.createTexture(progress, format, video_stream, texture_type);
- return &self.source;
- } else unreachable;
+ self.source.texture = try self.decoder.createTexture(progress, format, video_stream, texture_type);
+ return &self.source;
}
fn deinit(source: *const gl.Source, allocator: std.mem.Allocator) void {
@@ -276,3 +289,97 @@ pub const VideoSource = struct {
allocator.destroy(self);
}
};
+
+pub const StreamSource = struct {
+ source: gl.Source,
+
+ decoder: *Decoder,
+ format: *c.AVFormatContext,
+ stream: i32,
+ packet: *c.AVPacket,
+
+ thread: *gl.Thread,
+
+ pub fn init(
+ allocator: std.mem.Allocator,
+ constants: *const gl.Constants,
+ filename: [*:0]const u8,
+ ) !*gl.Source {
+ const self = try allocator.create(StreamSource);
+ errdefer allocator.destroy(self);
+
+ var format = try get_format_context(filename, null);
+ 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 = gl.Texture.create(.TEXTURE_2D);
+ texture.allocate(
+ codec_par.width,
+ codec_par.height,
+ 1,
+ c.GL_RGBA8,
+ );
+
+ const decoder = try AVDecoder.init(allocator, codec_par);
+
+ self.* = .{
+ .source = .{
+ .texture = texture,
+
+ .deinit_fn = deinit,
+ },
+
+ .decoder = decoder,
+ .format = format,
+ .stream = video_stream.index,
+ .packet = packet,
+
+ .thread = undefined,
+ };
+
+ try self.update();
+ self.thread = try gl.Thread.init(allocator, constants, update_loop, .{self});
+
+ return &self.source;
+ }
+
+ fn update_loop(self: *StreamSource) !void {
+ while (!self.thread.quit) {
+ try self.update();
+ c.glFlush();
+ try std.Thread.yield();
+ }
+ }
+
+ fn update(self: *StreamSource) !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;
+
+ self.decoder.num_frames = 0;
+ _ = try self.decoder.process_packet_fn(self.decoder, self.packet, &self.source.texture);
+ }
+
+ fn deinit(source: *gl.Source, allocator: std.mem.Allocator) void {
+ const self: *StreamSource = @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);
+ }
+};