diff options
| author | s-ol <s+removethis@s-ol.nu> | 2025-02-24 10:16:36 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-02-24 13:18:04 +0000 |
| commit | 96063a5d32d41a70cdbfba68e3f018c9c320aa68 (patch) | |
| tree | 3adadd5cc7ce41f68919ee28f9e56b2b1fec2c54 /src/video.zig | |
| parent | update for zig 0.13.0 (diff) | |
| download | glsl-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.zig | 138 |
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; +} |
