const std = @import("std"); const c = @import("c.zig"); const gl = @import("gl.zig"); const src = @import("source.zig"); const ctrl = @import("control.zig"); const build_config = @import("build_config"); 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; } fn get_format_context( filename: [*:0]const u8, format_name: ?[*:0]const u8, format_options: []const [*c]c.lo_arg, ) !*c.AVFormatContext { c.avdevice_register_all(); var format: ?*const c.AVInputFormat = null; if (format_name) |name| { format = c.av_find_input_format(name); } var options: ?*c.AVDictionary = null; var i: usize = 0; std.debug.assert(format_options.len % 2 == 0); std.debug.print("len {}\n", .{format_options.len}); while (i < format_options.len) : (i += 2) { try check(c.av_dict_set( &options, &format_options[i].*.s, &format_options[i + 1].*.s, 0, )); } var format_ctx: ?*c.AVFormatContext = null; try check(c.avformat_open_input(&format_ctx, filename, format, &options)); errdefer c.avformat_close_input(&format_ctx); return format_ctx orelse 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; if (texture) |tex| { if (tex.type == .TEXTURE_2D) 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); } }; pub const AVDecoder = struct { decoder: 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) Errors!*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; } fn deinit(decoder: *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); } fn reset(decoder: *Decoder) void { const self: *AVDecoder = @fieldParentPtr("decoder", decoder); c.avcodec_flush_buffers(self.codec_ctx); } fn initTexture(decoder: *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, ); } fn processPacket(decoder: *Decoder, packet: *c.AVPacket, texture: ?*const gl.Texture) 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 true; } self.decoder.num_frames += 1; } } }; pub const VideoSource = struct { source: src.Source, decoder: *Decoder, pub fn init( allocator: std.mem.Allocator, progress_root: std.Progress.Node, texture_type: gl.Texture.Type, filename: [*:0]const u8, format_name: ?[*:0]const u8, format_options: []const [*c]c.lo_arg, ) !*src.Source { const self = try allocator.create(VideoSource); errdefer allocator.destroy(self); const progress = progress_root.start("loading texture", 2); defer progress.end(); var format = try get_format_context(filename, format_name, format_options); defer c.avformat_close_input(@ptrCast(&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.*; var packet = c.av_packet_alloc(); defer c.av_packet_free(&packet); const is_hap = codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '1') or codec_par.codec_tag == c.MKTAG('H', 'a', 'p', '5'); self.* = .{ .source = .{ .texture = undefined, .deinit_fn = deinit, }, .decoder = if (is_hap and build_config.have_hap) try @import("hap.zig").HAPDecoder.init(allocator, codec_par) else try AVDecoder.init(allocator, codec_par), }; errdefer self.decoder.deinit(allocator); self.source.texture = try self.decoder.createTexture(progress, format, video_stream, texture_type); return &self.source; } fn deinit(source: *const src.Source, allocator: std.mem.Allocator) void { const self: *const VideoSource = @fieldParentPtr("source", source); self.decoder.deinit(allocator); allocator.destroy(self); } }; pub const StreamSource = struct { source: src.Source, flags: src.StreamFlags, decoder: *Decoder, format: *c.AVFormatContext, stream: i32, packet: *c.AVPacket, thread: *gl.Thread, pub fn init( allocator: std.mem.Allocator, constants: *const gl.Constants, filename: [*:0]const u8, format_name: ?[*:0]const u8, format_options: []const [*c]c.lo_arg, ) !*src.Source { const self = try allocator.create(StreamSource); errdefer allocator.destroy(self); var format = try get_format_context(filename, format_name, format_options); errdefer c.avformat_close_input(@ptrCast(&format)); format.flags |= c.AVFMT_FLAG_NONBLOCK; 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.*; var packet = c.av_packet_alloc(); errdefer c.av_packet_free(&packet); const texture = gl.Texture.create(.TEXTURE_2D); texture.allocate( codec_par.width, codec_par.height, 1, c.GL_RGBA8, ); const decoder = try AVDecoder.init(allocator, codec_par); errdefer decoder.deinit(allocator); self.* = .{ .source = .{ .texture = texture, .deinit_fn = deinit, .register_fn = register_methods, .unregister_fn = unregister_methods, }, .flags = .{}, .decoder = decoder, .format = format, .stream = video_stream.index, .packet = packet, .thread = undefined, }; try self.update(); self.thread = try gl.Thread.init(allocator, constants, update_loop, .{self}); return &self.source; } fn register_methods(source: *src.Source, name: []const u8, control: *ctrl.ControlServer) void { const self: *StreamSource = @fieldParentPtr("source", source); self.flags.register(name, control); } fn unregister_methods(source: *src.Source, name: []const u8, control: *ctrl.ControlServer) void { const self: *StreamSource = @fieldParentPtr("source", source); self.flags.unregister(name, control); } fn update_loop(self: *StreamSource) !void { while (!self.thread.quit) { try self.update(); c.glFlush(); try std.Thread.yield(); } } fn update(self: *StreamSource) !void { try check(c.av_read_frame(self.format, self.packet)); defer c.av_packet_unref(self.packet); if (self.packet.*.stream_index != self.stream) return; self.decoder.num_frames = 0; _ = try self.decoder.process_packet_fn( self.decoder, self.packet, if (self.flags.shouldStep()) &self.source.texture else null, ); } fn deinit(source: *src.Source, allocator: std.mem.Allocator) void { const self: *StreamSource = @fieldParentPtr("source", source); self.thread.deinit(allocator); self.decoder.deinit(allocator); c.av_packet_free(@ptrCast(&self.packet)); c.avformat_close_input(@ptrCast(&self.format)); allocator.destroy(self); } };