diff options
| author | s-ol <s+removethis@s-ol.nu> | 2025-07-27 21:40:42 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-07-28 18:29:18 +0000 |
| commit | 6850ad3da37ae1d49b959ddb5e8c11a2a1481bda (patch) | |
| tree | b90e231de261c62d38c009d9e6cf907838f34b8c /src/ffmpeg.zig | |
| parent | fix texture binding bug (diff) | |
| download | glsl-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.zig | 163 |
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); + } +}; |
