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; }