aboutsummaryrefslogtreecommitdiffstats
path: root/src/video/ffmpeg.zig
blob: 03456d5bc269c3d3cec062797d7eb39bb6ad250a (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
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
const std = @import("std");
const c = @import("../c.zig");
const gl = @import("../gl.zig");
const build_config = @import("build_config");
const dec = @import("decoder.zig");

const check = dec.check;

pub const AVDecoder = struct {
    decoder: dec.Decoder,

    sws_ctx: *c.SwsContext,
    codec_par: c.AVCodecParameters,
    codec_ctx: [*c]c.AVCodecContext,

    raw_frame: [*c]c.AVFrame,
    rgba_frame: [*c]c.AVFrame,

    pub fn init(allocator: std.mem.Allocator, codec_par: c.AVCodecParameters) dec.Errors!*dec.Decoder {
        const self = try allocator.create(AVDecoder);
        errdefer allocator.destroy(self);

        const codec = c.avcodec_find_decoder(codec_par.codec_id) orelse return error.UnsupportedCodec;

        var codec_ctx = c.avcodec_alloc_context3(codec) orelse return error.AVAllocationError;
        errdefer c.avcodec_free_context(&codec_ctx);
        var raw_frame = c.av_frame_alloc() orelse return error.AVAllocationError;
        errdefer c.av_frame_free(&raw_frame);
        var rgba_frame = c.av_frame_alloc() orelse return error.AVAllocationError;
        errdefer c.av_frame_free(&rgba_frame);

        try check(c.avcodec_parameters_to_context(codec_ctx, &codec_par));
        try check(c.avcodec_open2(codec_ctx, codec, null));
        errdefer check(c.avcodec_close(codec_ctx)) catch unreachable;

        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 return error.AVGenericError;
        errdefer c.sws_freeContext(sws_ctx);

        self.* = .{
            .decoder = .{
                .process_packet_fn = processPacket,
                .init_texture_fn = initTexture,
                .reset_fn = reset,
                .deinit_fn = deinit,
                .num_frames = 0,
            },
            .sws_ctx = sws_ctx,
            .codec_par = codec_par,
            .codec_ctx = codec_ctx,
            .raw_frame = raw_frame,
            .rgba_frame = rgba_frame,
        };
        return &self.decoder;
    }

    pub fn deinit(decoder: *dec.Decoder, allocator: std.mem.Allocator) void {
        const self: *AVDecoder = @fieldParentPtr("decoder", decoder);

        const raw_ptr: [*c][*c]c.AVFrame = &self.raw_frame;
        const rgba_ptr: [*c][*c]c.AVFrame = &self.rgba_frame;
        c.av_frame_free(raw_ptr);
        c.av_frame_free(rgba_ptr);

        c.sws_freeContext(self.sws_ctx);
        check(c.avcodec_close(self.codec_ctx)) catch unreachable;
        c.avcodec_free_context(&self.codec_ctx);

        allocator.destroy(self);
    }

    pub fn reset(decoder: *dec.Decoder) void {
        const self: *AVDecoder = @fieldParentPtr("decoder", decoder);
        c.avcodec_flush_buffers(self.codec_ctx);
    }

    pub fn initTexture(decoder: *dec.Decoder, texture: *const gl.Texture) void {
        const self: *AVDecoder = @fieldParentPtr("decoder", decoder);

        texture.allocate(
            self.codec_par.width,
            self.codec_par.height,
            self.decoder.num_frames,
            c.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
        );
    }

    pub fn processPacket(decoder: *dec.Decoder, packet: *c.AVPacket, texture: ?*const gl.Texture) dec.Errors!bool {
        const self: *AVDecoder = @fieldParentPtr("decoder", decoder);

        try check(c.avcodec_send_packet(self.codec_ctx, packet));
        while (true) {
            const ires = c.avcodec_receive_frame(self.codec_ctx, self.raw_frame);
            if (ires == c.AVERROR_EOF) return false;
            if (ires == c.AVERROR(c.EAGAIN)) return true;
            try check(ires);
            try check(c.sws_scale_frame(self.sws_ctx, self.rgba_frame, self.raw_frame));

            c.glPixelStorei(c.GL_UNPACK_ROW_LENGTH, @divFloor(self.rgba_frame.*.linesize[0], @sizeOf(u8) * 4));

            if (texture) |tex| {
                tex.setLayer(
                    self.rgba_frame.*.width,
                    self.rgba_frame.*.height,
                    self.decoder.num_frames,
                    c.GL_RGBA,
                    self.rgba_frame.*.data[0],
                );

                if (tex.type == .TEXTURE_2D) return false;
            }

            self.decoder.num_frames += 1;
        }
    }
};