aboutsummaryrefslogtreecommitdiffstats
path: root/src/video/decoder.zig
blob: 4c78374032635965e905a9d0e24527159f77e3ff (plain)
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
const std = @import("std");
const c = @import("../c.zig");
const gl = @import("../gl.zig");

pub const Errors = error{
    AVGenericError,
    AVAllocationError,
    HAPBadArguments,
    HAPBufferTooSmall,
    HAPBadFrame,
    HAPInternalError,
    UnsupportedCodec,
    OutOfMemory,
    NoFrames,
};

pub fn check(err: c_int) Errors!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.AVGenericError;
}

pub const Decoder = struct {
    num_frames: i32,

    process_packet_fn: *const fn (self: *Decoder, packet: *c.AVPacket, texture: ?*const gl.Texture) Errors!bool,
    init_texture_fn: *const fn (self: *Decoder, texture: *const gl.Texture) void,
    reset_fn: ?*const fn (self: *Decoder) void,
    deinit_fn: *const fn (self: *Decoder, allocator: std.mem.Allocator) void,

    fn processStream(
        self: *Decoder,
        progress: std.Progress.Node,
        format: *c.AVFormatContext,
        stream: *c.AVStream,
        texture: ?*const gl.Texture,
    ) Errors!void {
        defer progress.end();

        try check(c.avformat_seek_file(format, stream.index, 0, 0, 0, 0));
        if (self.reset_fn) |reset_fn| reset_fn(self);

        var packet = c.av_packet_alloc();
        defer c.av_packet_free(&packet);

        self.num_frames = 0;
        var res = c.av_read_frame(format, packet);
        while (res >= 0) : (res = c.av_read_frame(format, packet)) {
            if (packet.*.stream_index == stream.index) {
                const more = try self.process_packet_fn(self, packet, texture);
                progress.setCompletedItems(@intCast(self.num_frames));
                if (!more) break;
            }
            c.av_packet_unref(packet);
        } else {
            if (res != c.AVERROR_EOF) try check(res);
        }
    }

    pub fn createTexture(
        self: *Decoder,
        outer_progress: std.Progress.Node,
        format: *c.AVFormatContext,
        stream: *c.AVStream,
        texture_type: gl.Texture.Type,
    ) Errors!gl.Texture {
        try self.processStream(
            outer_progress.start("scanning video frames", @intCast(stream.nb_frames)),
            format,
            stream,
            null,
        );
        if (self.num_frames <= 0) return error.NoFrames;

        const texture = gl.Texture.create(texture_type);
        errdefer texture.destroy();

        self.init_texture_fn(self, &texture);
        try self.processStream(
            outer_progress.start("loading video frames", @intCast(self.num_frames)),
            format,
            stream,
            &texture,
        );
        return texture;
    }

    pub fn deinit(self: *Decoder, allocator: std.mem.Allocator) void {
        self.deinit_fn(self, allocator);
    }
};