1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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;
}
|