aboutsummaryrefslogtreecommitdiffstats
path: root/src/video.zig
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2025-02-24 10:16:36 +0000
committers-ol <s+removethis@s-ol.nu>2025-02-24 13:18:04 +0000
commit96063a5d32d41a70cdbfba68e3f018c9c320aa68 (patch)
tree3adadd5cc7ce41f68919ee28f9e56b2b1fec2c54 /src/video.zig
parentupdate for zig 0.13.0 (diff)
downloadglsl-view-96063a5d32d41a70cdbfba68e3f018c9c320aa68.tar.gz
glsl-view-96063a5d32d41a70cdbfba68e3f018c9c320aa68.zip
load videos and textures using libav
Diffstat (limited to 'src/video.zig')
-rw-r--r--src/video.zig138
1 files changed, 138 insertions, 0 deletions
diff --git a/src/video.zig b/src/video.zig
new file mode 100644
index 0000000..368d46f
--- /dev/null
+++ b/src/video.zig
@@ -0,0 +1,138 @@
+const std = @import("std");
+const c = @import("c.zig");
+const gl = @import("gl.zig");
+
+fn check(err: c_int) !void {
+ if (err >= 0) return;
+ var buf: [c.AV_ERROR_MAX_STRING_SIZE]u8 = undefined;
+ _ = c.av_make_error_string(&buf, buf.len, err);
+ std.debug.print("libav error: {s}\n", .{buf});
+ return error.AVError;
+}
+
+pub fn loadVideo(progress_root: std.Progress.Node, filename: [*:0]const u8, texture: *gl.Texture) !void {
+ const outer_progress = progress_root.start("load video", 0);
+ defer outer_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);
+
+ if (format_ctx) |format| {
+ 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.*;
+ const codec = c.avcodec_find_decoder(codec_par.codec_id) orelse unreachable;
+ var codec_ctx = c.avcodec_alloc_context3(codec) orelse unreachable;
+ defer c.avcodec_free_context(&codec_ctx);
+
+ try check(c.avcodec_parameters_to_context(codec_ctx, &codec_par));
+ try check(c.avcodec_open2(codec_ctx, codec, null));
+ defer check(c.avcodec_close(codec_ctx)) catch unreachable;
+
+ std.debug.print("opened codec, size {}x{}, ~{} frames\n", .{ codec_par.width, codec_par.height, video_stream.nb_frames });
+
+ const sws_ctx = c.sws_getContext(
+ codec_par.width,
+ codec_par.height,
+ codec_par.format,
+ codec_par.width,
+ codec_par.height,
+ c.AV_PIX_FMT_RGBA,
+ c.SWS_POINT,
+ null,
+ null,
+ 0,
+ ) orelse unreachable;
+ defer c.sws_freeContext(sws_ctx);
+
+ var packet = c.av_packet_alloc();
+ defer c.av_packet_free(&packet);
+
+ var raw_frame = c.av_frame_alloc();
+ defer c.av_frame_free(&raw_frame);
+ var rgba_frame = c.av_frame_alloc();
+ defer c.av_frame_free(&rgba_frame);
+
+ const frame_count = blk: {
+ const progress = outer_progress.start("scanning video frames", @intCast(video_stream.nb_frames));
+ defer progress.end();
+
+ var i: usize = 0;
+ var res = c.av_read_frame(format, packet);
+ while (res >= 0) : (res = c.av_read_frame(format, packet)) {
+ if (packet.*.stream_index == video_stream.index) {
+ try check(c.avcodec_send_packet(codec_ctx, packet));
+
+ const ires = c.avcodec_receive_frame(codec_ctx, raw_frame);
+ if (ires == c.AVERROR_EOF) break;
+ if (ires == c.AVERROR(c.EAGAIN)) continue;
+ try check(ires);
+
+ i += 1;
+ progress.completeOne();
+ }
+ c.av_packet_unref(packet);
+ }
+
+ try check(c.avformat_seek_file(format, video_stream.index, 0, 0, 0, 0));
+ c.avcodec_flush_buffers(codec_ctx);
+
+ break :blk i;
+ };
+
+ std.debug.print("counted frames, found {}\n", .{frame_count});
+ switch (texture.type) {
+ .TEXTURE_2D => {},
+ .TEXTURE_3D, .TEXTURE_2D_ARRAY => texture.allocate3D(codec_par.width, codec_par.height, @intCast(frame_count)),
+ else => unreachable,
+ }
+
+ {
+ const progress = outer_progress.start("loading video frames", frame_count);
+ defer progress.end();
+
+ defer c.glPixelStorei(c.GL_UNPACK_ROW_LENGTH, 0);
+
+ var i: i32 = 0;
+ var res = c.av_read_frame(format, packet);
+ while (res >= 0) : (res = c.av_read_frame(format, packet)) {
+ if (packet.*.stream_index == video_stream.index) {
+ try check(c.avcodec_send_packet(codec_ctx, packet));
+
+ const ires = c.avcodec_receive_frame(codec_ctx, raw_frame);
+ if (ires == c.AVERROR_EOF) break;
+ if (ires == c.AVERROR(c.EAGAIN)) continue;
+ try check(ires);
+ try check(c.sws_scale_frame(sws_ctx, rgba_frame, raw_frame));
+
+ c.glPixelStorei(c.GL_UNPACK_ROW_LENGTH, @divFloor(rgba_frame.*.linesize[0], @sizeOf(u8) * 4));
+
+ switch (texture.type) {
+ .TEXTURE_2D => {
+ texture.setData2D(codec_par.width, codec_par.height, rgba_frame.*.data[0]);
+ break;
+ },
+
+ .TEXTURE_3D,
+ .TEXTURE_2D_ARRAY,
+ => texture.setLayer3D(codec_par.width, codec_par.height, i, rgba_frame.*.data[0]),
+
+ else => unreachable,
+ }
+
+ i += 1;
+ progress.completeOne();
+ }
+ c.av_packet_unref(packet);
+ } else {
+ if (res != c.AVERROR_EOF) try check(res);
+ }
+ }
+ } else unreachable;
+}