mcap/modules/ffmpeg/generate.jai

290 lines
12 KiB
Plaintext

// Generate bindings for ffmpeg.
//
// @TODO:
// - Get rid of the dlls having version number in their name, it's annoying. Probably need to compile ffmpeg
// myself to do that...
// - Fix warning about generating type info for large ffmpeg struct. Probably just don't generate type info
// for any ffmpeg types? (Hmm, type info is probably useful for the smaller types, I think it's needed to
// be able to print % structs out nicely).
//
AT_COMPILE_TIME :: true;
#if AT_COMPILE_TIME {
#run {
set_build_options_dc(.{do_output=false});
if !generate_bindings() {
compiler_set_workspace_status(.FAILED);
}
}
} else {
#import "System";
main :: () {
set_working_directory(path_strip_filename(get_path_of_running_executable()));
if !generate_bindings() {
exit(1);
}
}
}
HEADER :: #string DONE
AV_NOPTS_VALUE :: 0x8000000000000000;
DONE
FOOTER :: #string DONE
// Bindings generator stopped generating this for some reason?
// Looks like it only adds the Basic import if you have the struct checks on, but it is also
// needed for print like functions.
// @TODO: Test and report if so.
//
// Needed to compile print like log functions (although not sure you'll ever really want the av log functions?).
#import "Basic";
DONE
// Windows: the headers of the downloaded build, copied to src. macOS: those build_macos.jai installs.
#if OS == .MACOS INCLUDE_PATH :: "macos-build/install/include";
else INCLUDE_PATH :: "src";
generate_bindings :: () -> bool {
#if OS == .MACOS output_filename := "macos.jai";
else output_filename := "windows.jai";
opts: Generate_Bindings_Options;
{
using opts;
try_to_preserve_comments = true; // @Tmp
generate_compile_time_struct_checks = false;
// generate_printf_wrappers = true;
// c_enum_emulation = true;
// auto_detect_enum_prefixes = false;
// strip_enum_prefixes = false;
// alias_original_enum_names = true;
visitor = my_visitor;
// Can't load all the libraries for some reason, just disable stripping thingz. Nevermind, this
// doesn't work, just get a load of __UnknownLib dont you :roll:.
// strip_flags &= ~.SYMBOLS_WITH_UNKNOWN_FOREIGN_LIBS;
#if OS == .MACOS {
// The five libraries build_macos.jai makes, as their major versions name them; with the
// extension, since the generator takes a name with a dot in it for a whole file name.
array_add(*libpaths, "macos");
library_names :: string.[
"libavcodec.60.dylib",
"libavformat.60.dylib",
"libavutil.58.dylib",
"libswresample.4.dylib",
"libswscale.7.dylib",
];
} else {
array_add(*libpaths, "windows");
library_names :: string.[
"avcodec-60",
"avdevice-60",
"avfilter-9",
"avformat-60",
"avutil-58",
"postproc-57",
"swresample-4",
"swscale-7",
];
}
for library_names array_add(*libnames, it);
array_add(*include_paths, INCLUDE_PATH);
// An attmept to just add all includes. For some great reason the headers also include a bunch of
// third party headers not included with the ffmpeg headers...
// I could just make empty dummy headers for these things. But instead I am just going to manually
// pick the headers I want!
#if false {
dirs_to_visit: [..] string;
for library_names array_add(*dirs_to_visit, tprint("%/lib%", INCLUDE_PATH, it));
file_visitor :: (info: *File_Visit_Info, source_files : *[..] string) {
assert(info != null, "info (%) != null", info);
assert(source_files != null, "modules (%) != null", source_files);
assert(!info.is_directory);
assert(has_extension(info.short_name, ".h"));
if ends_with(info.short_name, "qsv.h") {
print("Skipping % because it contains an include to mfx/mfxvideo.h that I don't want to deal with right now!\n", info.short_name);
} else if ends_with(info.short_name, "vdpau.h") {
print("More fuckers\n");
} else {
array_add(source_files, copy_string(info.full_name));
}
}
for dirs_to_visit {
print("Visiting: %\n", it);
visit_files(it, false, *source_files, file_visitor, true, false);
}
#if false {
print("Found % files: \n", source_files.count);
for source_files print(" %\n", it);
}
}
array_add(*source_files, tprint("%/libavcodec/avcodec.h", INCLUDE_PATH));
array_add(*source_files, tprint("%/libavformat/avformat.h", INCLUDE_PATH));
array_add(*source_files, tprint("%/libswscale/swscale.h", INCLUDE_PATH));
// system_include_paths left empty: the generator fills in this system's own.
builder: String_Builder;
append(*builder, HEADER);
generate_error_codes(*builder);
header = builder_to_string(*builder);
footer = FOOTER;
}
print("Generating ffmpeg bindings...\n");
result := generate_bindings(opts, output_filename);
// The compiler loads each #library to run code at compile time, but there the dylibs cannot find
// each other through @rpath; nothing of FFmpeg's is run then, so none is loaded (no_dll).
#if OS == .MACOS if result {
text, ok := read_entire_file(output_filename);
if !ok return false;
result = write_entire_file(output_filename, replace(text, " :: #library \"macos/", " :: #library,no_dll \"macos/"));
}
return result;
}
DECLARATIONS_TO_OMIT :: string.[
"av_log_set_callback",
"av_fopen_utf8",
"av_pkt_dump2",
"av_hex_dump",
];
Pair :: struct {
type: string;
replacement: string;
}
TYPE_REPLACEMENTS_TO_MAKE :: Pair.[
Pair.{"__some_type", "some_cooler_type"},
];
// This one warns about being large. Probably can also get rid of others?
TYPES_WITHOUT_TYPE_INFO :: string.[
"AVCodecContext",
];
// NOTE(Charles): Most of this stuff doesn't acheive anything. I just yoinked it from other examples of
// visitor functions for reference.
my_visitor :: (decl: *Declaration, parent_decl: *Declaration) -> Declaration_Visit_Result {
if decl.kind == .MACRO_DEFINITION && (ends_with(decl.name, "_H") || ends_with(decl.name, "_h_")) {
print("MACRO_DEFINITION removed: %\n", decl.name);
decl.decl_flags |= .OMIT_FROM_OUTPUT;
return .STOP;
}
if !parent_decl {
if array_find(DECLARATIONS_TO_OMIT, decl.name) {
print("DECLARATIONS_TO_OMIT: %\n", decl.name);
decl.decl_flags |= .OMIT_FROM_OUTPUT;
return .STOP;
}
if decl.kind == .TYPEDEF {
for TYPE_REPLACEMENTS_TO_MAKE if decl.name == it.type {
print("TYPE_REPLACEMENTS_TO_MAKE: % -> %\n", it.type, it.replacement);
decl.output_name = it.replacement;
decl.decl_flags |= .OMIT_FROM_OUTPUT;
return .STOP;
}
}
if array_find(TYPES_WITHOUT_TYPE_INFO, decl.name) {
decl.decl_flags |= .TYPE_INFO_NONE;
}
}
return .RECURSE;
}
// ffmpeg defines error codes using some weird ass macros. Need to manually define them. See libavutil/error.h.
generate_error_codes :: (b: *String_Builder) {
fferrtag :: (a: u32, b: u32, c: u32, d: u32) -> s32 #expand {
// #define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
// #define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
tag : u32 = a | (b << 8) | (c << 16) | (d << 24);
return -cast(s32) tag;
}
Av_Error_Codes :: struct {
AVERROR_BSF_NOT_FOUND := #run fferrtag( 0xF8, #char "B", #char "S", #char "F"); ///< Bitstream filter not found
AVERROR_BUG := #run fferrtag(#char "B", #char "U", #char "G", #char "!"); ///< Internal bug, also see AVERROR_BUG2
AVERROR_BUFFER_TOO_SMALL := #run fferrtag(#char "B", #char "U", #char "F", #char "S"); ///< Buffer too small
AVERROR_DECODER_NOT_FOUND := #run fferrtag( 0xF8, #char "D", #char "E", #char "C"); ///< Decoder not found
AVERROR_DEMUXER_NOT_FOUND := #run fferrtag( 0xF8, #char "D", #char "E", #char "M"); ///< Demuxer not found
AVERROR_ENCODER_NOT_FOUND := #run fferrtag( 0xF8, #char "E", #char "N", #char "C"); ///< Encoder not found
AVERROR_EOF := #run fferrtag(#char "E", #char "O", #char "F", #char " "); ///< End of file
AVERROR_EXIT := #run fferrtag(#char "E", #char "X", #char "I", #char "T"); ///< Immediate exit was requested; the called function should not be restarted
AVERROR_EXTERNAL := #run fferrtag(#char "E", #char "X", #char "T", #char " "); ///< Generic error in an external library
AVERROR_FILTER_NOT_FOUND := #run fferrtag( 0xF8, #char "F", #char "I", #char "L"); ///< Filter not found
AVERROR_INVALIDDATA := #run fferrtag(#char "I", #char "N", #char "D", #char "A"); ///< Invalid data found when processing input
AVERROR_MUXER_NOT_FOUND := #run fferrtag( 0xF8, #char "M", #char "U", #char "X"); ///< Muxer not found
AVERROR_OPTION_NOT_FOUND := #run fferrtag( 0xF8, #char "O", #char "P", #char "T"); ///< Option not found
AVERROR_PATCHWELCOME := #run fferrtag(#char "P", #char "A", #char "W", #char "E"); ///< Not yet implemented in FFmpeg, patches welcome
AVERROR_PROTOCOL_NOT_FOUND := #run fferrtag( 0xF8, #char "P", #char "R", #char "O"); ///< Protocol not found
AVERROR_STREAM_NOT_FOUND := #run fferrtag( 0xF8, #char "S", #char "T", #char "R"); ///< Stream not found
AVERROR_BUG2 := #run fferrtag(#char "B", #char "U", #char "G", #char " ");
AVERROR_UNKNOWN := #run fferrtag(#char "U", #char "N", #char "K", #char "N"); ///< Unknown error, typically from an external library
// AVERROR_EXPERIMENTAL (-0x2bb2afa8) ///< Requested feature is flagged experimental. Set strict_std_compliance if you really want to use it.
// AVERROR_INPUT_CHANGED (-0x636e6701) ///< Input changed between calls. Reconfiguration is required. (can be OR-ed with AVERROR_OUTPUT_CHANGED)
// AVERROR_OUTPUT_CHANGED (-0x636e6702) ///< Output changed between calls. Reconfiguration is required. (can be OR-ed with AVERROR_INPUT_CHANGED)
// HTTP & RTSP errors
AVERROR_HTTP_BAD_REQUEST := #run fferrtag(0xF8, #char "4", #char "0", #char "0");
AVERROR_HTTP_UNAUTHORIZED := #run fferrtag(0xF8, #char "4", #char "0", #char "1");
AVERROR_HTTP_FORBIDDEN := #run fferrtag(0xF8, #char "4", #char "0", #char "3");
AVERROR_HTTP_NOT_FOUND := #run fferrtag(0xF8, #char "4", #char "0", #char "4");
AVERROR_HTTP_OTHER_4XX := #run fferrtag(0xF8, #char "4", #char "X", #char "X");
AVERROR_HTTP_SERVER_ERROR := #run fferrtag(0xF8, #char "5", #char "X", #char "X");
}
// My plan was to define constants on a struct, loop of the struct members and print out the
// name :: value's. I couldn't see a way to get the values of constant struct members like that though, so
// instead I just declared them as fields and used a dummy instance. Probably there is a smarter thing to do
// here!
dummy: Av_Error_Codes;
info_struct := type_info(Av_Error_Codes);
longest_name_length := 0;
for info_struct.members longest_name_length = max(longest_name_length, it.name.count);
for info_struct.members {
slot := cast (*u8) *dummy + it.offset_in_bytes;
value := (cast(*s32) slot).*;
append(b, it.name);
num_spaces := longest_name_length - it.name.count;
for 1..num_spaces append(b, " ");
print_to_builder(b, " :: 0x%; // %\n", formatInt(value, base = 16, minimum_digits = 8), value);
}
}
has_extension :: (name: string, extension: string) -> bool {
if name.count < extension.count return false;
test := name;
advance(*test, name.count - extension.count);
return test == extension;
}
#import "Basic";
#import "String";
#import "File_Utilities";
#import "Compiler";
#import "Bindings_Generator";
#import "File";