290 lines
12 KiB
Plaintext
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";
|