NaraMotion / c_src /blend2d_video.cpp
ArifiIslam's picture
Release NaraMotion v0.26.0: Structure of Arrays (SoA), Path Arena Allocator, Uniform Grid Indexing, Dual-Buffer Rendering & Centroid Spring Pop-in
b516e00 verified
Raw History Blame Contribute Delete
21.2 kB
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <iostream>
#ifdef _WIN32
#include <windows.h>
#define BL_VIDEO_EXPORT __declspec(dllexport)
#else
#include <dlfcn.h>
#define BL_VIDEO_EXPORT __attribute__((visibility("default")))
#endif
// ============================================================================
// Exact FFmpeg C Types (Matching FFmpeg 6.x / 7.x ABI)
// ============================================================================
#define AV_PIX_FMT_YUV420P 0
#define AV_PIX_FMT_ARGB 25
#define AV_PIX_FMT_RGBA 26
#define AV_PIX_FMT_BGRA 28
#define SWS_FAST_BILINEAR 1
#define SWS_BILINEAR 2
#define SWS_BICUBIC 4
#define AVIO_FLAG_WRITE 2
#define AVFMT_NOFILE 0x0001
#define AVFMT_GLOBALHEADER 0x0040
#define AV_CODEC_FLAG_GLOBAL_HEADER (1 << 22)
#define AVMEDIA_TYPE_VIDEO 0
#define AVERROR_EAGAIN -11
#define AVERROR_EOF -541478725
struct AVRational {
int num;
int den;
};
struct AVStream {
int index;
int id;
void* codecpar;
AVRational time_base;
int64_t start_time;
int64_t duration;
int64_t nb_frames;
int64_t cur_dts;
void* metadata;
AVRational avg_frame_rate;
AVRational r_frame_rate;
AVRational sample_aspect_ratio;
int nb_side_data;
void* side_data;
};
struct AVOutputFormat {
const char* name;
const char* long_name;
const char* mime_type;
const char* extensions;
int audio_codec;
int video_codec;
int subtitle_codec;
int flags;
};
struct AVFormatContext {
const void* av_class;
void* iformat;
AVOutputFormat* oformat;
void* priv_data;
void* pb;
int ctx_flags;
unsigned int nb_streams;
AVStream** streams;
};
struct AVCodecContext {
const void* av_class;
int log_level_offset;
int codec_type;
const void* codec;
int codec_id;
unsigned int codec_tag;
void* priv_data;
void* internal;
void* opaque;
int64_t bit_rate;
int bit_rate_tolerance;
int global_quality;
int compression_level;
int flags;
int flags2;
uint8_t* extradata;
int extradata_size;
AVRational time_base;
int ticks_per_frame;
int delay;
int width, height;
int coded_width, coded_height;
AVRational sample_aspect_ratio;
int pix_fmt;
};
struct AVFrame {
uint8_t* data[8];
int linesize[8];
uint8_t** extended_data;
int width, height;
int nb_samples;
int format;
int key_frame;
int pict_type;
AVRational sample_aspect_ratio;
int64_t pts;
int64_t pkt_dts;
AVRational time_base;
};
struct AVPacket {
void* buf;
int64_t pts;
int64_t dts;
uint8_t* data;
int size;
int stream_index;
int flags;
void* side_data;
int side_data_elems;
int64_t duration;
int64_t pos;
void* opaque;
void* opaque_ref;
AVRational time_base;
};
// ============================================================================
// FFmpeg Function Pointer Declarations
// ============================================================================
typedef int (*fn_avformat_alloc_output_context2)(AVFormatContext** ctx, void* oformat, const char* format_name, const char* filename);
typedef AVStream* (*fn_avformat_new_stream)(AVFormatContext* s, const void* c);
typedef int (*fn_avformat_write_header)(AVFormatContext* s, void** options);
typedef int (*fn_av_write_trailer)(AVFormatContext* s);
typedef int (*fn_avio_open)(void** s, const char* url, int flags);
typedef int (*fn_avio_closep)(void** s);
typedef int (*fn_av_interleaved_write_frame)(AVFormatContext* s, AVPacket* pkt);
typedef void (*fn_avformat_free_context)(AVFormatContext* s);
typedef void* (*fn_avcodec_find_encoder_by_name)(const char* name);
typedef void* (*fn_avcodec_find_encoder)(int id);
typedef void* (*fn_avcodec_alloc_context3)(const void* codec);
typedef void (*fn_avcodec_free_context)(void** avctx);
typedef int (*fn_avcodec_open2)(void* avctx, const void* codec, void** options);
typedef int (*fn_avcodec_send_frame)(void* avctx, const AVFrame* frame);
typedef int (*fn_avcodec_receive_packet)(void* avctx, AVPacket* pkt);
typedef int (*fn_avcodec_parameters_from_context)(void* par, const void* codec);
typedef AVFrame* (*fn_av_frame_alloc)();
typedef void (*fn_av_frame_free)(AVFrame** frame);
typedef int (*fn_av_frame_get_buffer)(AVFrame* frame, int align);
typedef int (*fn_av_frame_make_writable)(AVFrame* frame);
typedef AVPacket* (*fn_av_packet_alloc)();
typedef void (*fn_av_packet_free)(AVPacket** pkt);
typedef void (*fn_av_packet_rescale_ts)(AVPacket* pkt, AVRational tb_src, AVRational tb_dst);
typedef void (*fn_av_packet_unref)(AVPacket* pkt);
typedef int (*fn_av_opt_set)(void* obj, const char* name, const char* val, int search_flags);
typedef int (*fn_av_opt_set_int)(void* obj, const char* name, int64_t val, int search_flags);
typedef int (*fn_av_opt_set_q)(void* obj, const char* name, AVRational val, int search_flags);
typedef int (*fn_av_opt_set_pixel_fmt)(void* obj, const char* name, int fmt, int search_flags);
typedef int (*fn_av_dict_set)(void** pm, const char* key, const char* value, int flags);
typedef void (*fn_av_dict_free)(void** pm);
typedef void* (*fn_sws_getContext)(int srcW, int srcH, int srcFormat, int dstW, int dstH, int dstFormat, int flags, void* srcFilter, void* dstFilter, const double* param);
typedef int (*fn_sws_scale)(void* c, const uint8_t* const srcSlice[], const int srcStride[], int srcSliceY, int srcSliceH, uint8_t* const dst[], const int dstStride[]);
typedef void (*fn_sws_freeContext)(void* swsContext);
struct FFmpegAPI {
bool loaded = false;
fn_avformat_alloc_output_context2 avformat_alloc_output_context2 = nullptr;
fn_avformat_new_stream avformat_new_stream = nullptr;
fn_avformat_write_header avformat_write_header = nullptr;
fn_av_write_trailer av_write_trailer = nullptr;
fn_avio_open avio_open = nullptr;
fn_avio_closep avio_closep = nullptr;
fn_av_interleaved_write_frame av_interleaved_write_frame = nullptr;
fn_avformat_free_context avformat_free_context = nullptr;
fn_avcodec_find_encoder_by_name avcodec_find_encoder_by_name = nullptr;
fn_avcodec_find_encoder avcodec_find_encoder = nullptr;
fn_avcodec_alloc_context3 avcodec_alloc_context3 = nullptr;
fn_avcodec_free_context avcodec_free_context = nullptr;
fn_avcodec_open2 avcodec_open2 = nullptr;
fn_avcodec_send_frame avcodec_send_frame = nullptr;
fn_avcodec_receive_packet avcodec_receive_packet = nullptr;
fn_avcodec_parameters_from_context avcodec_parameters_from_context = nullptr;
fn_av_frame_alloc av_frame_alloc = nullptr;
fn_av_frame_free av_frame_free = nullptr;
fn_av_frame_get_buffer av_frame_get_buffer = nullptr;
fn_av_frame_make_writable av_frame_make_writable = nullptr;
fn_av_packet_alloc av_packet_alloc = nullptr;
fn_av_packet_free av_packet_free = nullptr;
fn_av_packet_rescale_ts av_packet_rescale_ts = nullptr;
fn_av_packet_unref av_packet_unref = nullptr;
fn_av_opt_set av_opt_set = nullptr;
fn_av_opt_set_int av_opt_set_int = nullptr;
fn_av_opt_set_q av_opt_set_q = nullptr;
fn_av_opt_set_pixel_fmt av_opt_set_pixel_fmt = nullptr;
fn_av_dict_set av_dict_set = nullptr;
fn_av_dict_free av_dict_free = nullptr;
fn_sws_getContext sws_getContext = nullptr;
fn_sws_scale sws_scale = nullptr;
fn_sws_freeContext sws_freeContext = nullptr;
};
static FFmpegAPI g_ff;
#ifdef _WIN32
static void* load_sym(HMODULE mod, const char* name) {
return (void*)GetProcAddress(mod, name);
}
#else
static void* load_sym(void* mod, const char* name) {
return dlsym(mod, name);
}
#endif
extern "C" {
BL_VIDEO_EXPORT int bl_video_init_ffmpeg(
const char* avcodec_path,
const char* avformat_path,
const char* avutil_path,
const char* swscale_path
) {
if (g_ff.loaded) return 0;
#ifdef _WIN32
HMODULE h_util = LoadLibraryA(avutil_path ? avutil_path : "avutil-59.dll");
HMODULE h_codec = LoadLibraryA(avcodec_path ? avcodec_path : "avcodec-61.dll");
HMODULE h_format = LoadLibraryA(avformat_path ? avformat_path : "avformat-61.dll");
HMODULE h_sws = LoadLibraryA(swscale_path ? swscale_path : "swscale-8.dll");
#else
void* h_util = dlopen(avutil_path ? avutil_path : "libavutil.so", RTLD_NOW | RTLD_GLOBAL);
void* h_codec = dlopen(avcodec_path ? avcodec_path : "libavcodec.so", RTLD_NOW | RTLD_GLOBAL);
void* h_format = dlopen(avformat_path ? avformat_path : "libavformat.so", RTLD_NOW | RTLD_GLOBAL);
void* h_sws = dlopen(swscale_path ? swscale_path : "libswscale.so", RTLD_NOW | RTLD_GLOBAL);
#endif
if (!h_util || !h_codec || !h_format || !h_sws) {
return -1;
}
g_ff.avformat_alloc_output_context2 = (fn_avformat_alloc_output_context2)load_sym(h_format, "avformat_alloc_output_context2");
g_ff.avformat_new_stream = (fn_avformat_new_stream)load_sym(h_format, "avformat_new_stream");
g_ff.avformat_write_header = (fn_avformat_write_header)load_sym(h_format, "avformat_write_header");
g_ff.av_write_trailer = (fn_av_write_trailer)load_sym(h_format, "av_write_trailer");
g_ff.avio_open = (fn_avio_open)load_sym(h_format, "avio_open");
g_ff.avio_closep = (fn_avio_closep)load_sym(h_format, "avio_closep");
g_ff.av_interleaved_write_frame = (fn_av_interleaved_write_frame)load_sym(h_format, "av_interleaved_write_frame");
g_ff.avformat_free_context = (fn_avformat_free_context)load_sym(h_format, "avformat_free_context");
g_ff.avcodec_find_encoder_by_name = (fn_avcodec_find_encoder_by_name)load_sym(h_codec, "avcodec_find_encoder_by_name");
g_ff.avcodec_find_encoder = (fn_avcodec_find_encoder)load_sym(h_codec, "avcodec_find_encoder");
g_ff.avcodec_alloc_context3 = (fn_avcodec_alloc_context3)load_sym(h_codec, "avcodec_alloc_context3");
g_ff.avcodec_free_context = (fn_avcodec_free_context)load_sym(h_codec, "avcodec_free_context");
g_ff.avcodec_open2 = (fn_avcodec_open2)load_sym(h_codec, "avcodec_open2");
g_ff.avcodec_send_frame = (fn_avcodec_send_frame)load_sym(h_codec, "avcodec_send_frame");
g_ff.avcodec_receive_packet = (fn_avcodec_receive_packet)load_sym(h_codec, "avcodec_receive_packet");
g_ff.avcodec_parameters_from_context = (fn_avcodec_parameters_from_context)load_sym(h_codec, "avcodec_parameters_from_context");
g_ff.av_frame_alloc = (fn_av_frame_alloc)load_sym(h_util, "av_frame_alloc");
g_ff.av_frame_free = (fn_av_frame_free)load_sym(h_util, "av_frame_free");
g_ff.av_frame_get_buffer = (fn_av_frame_get_buffer)load_sym(h_util, "av_frame_get_buffer");
g_ff.av_frame_make_writable = (fn_av_frame_make_writable)load_sym(h_util, "av_frame_make_writable");
g_ff.av_packet_alloc = (fn_av_packet_alloc)load_sym(h_codec, "av_packet_alloc");
if (!g_ff.av_packet_alloc) g_ff.av_packet_alloc = (fn_av_packet_alloc)load_sym(h_util, "av_packet_alloc");
g_ff.av_packet_free = (fn_av_packet_free)load_sym(h_codec, "av_packet_free");
if (!g_ff.av_packet_free) g_ff.av_packet_free = (fn_av_packet_free)load_sym(h_util, "av_packet_free");
g_ff.av_packet_rescale_ts = (fn_av_packet_rescale_ts)load_sym(h_codec, "av_packet_rescale_ts");
if (!g_ff.av_packet_rescale_ts) g_ff.av_packet_rescale_ts = (fn_av_packet_rescale_ts)load_sym(h_util, "av_packet_rescale_ts");
g_ff.av_packet_unref = (fn_av_packet_unref)load_sym(h_codec, "av_packet_unref");
if (!g_ff.av_packet_unref) g_ff.av_packet_unref = (fn_av_packet_unref)load_sym(h_util, "av_packet_unref");
g_ff.av_opt_set = (fn_av_opt_set)load_sym(h_util, "av_opt_set");
g_ff.av_opt_set_int = (fn_av_opt_set_int)load_sym(h_util, "av_opt_set_int");
g_ff.av_opt_set_q = (fn_av_opt_set_q)load_sym(h_util, "av_opt_set_q");
g_ff.av_opt_set_pixel_fmt = (fn_av_opt_set_pixel_fmt)load_sym(h_util, "av_opt_set_pixel_fmt");
g_ff.av_dict_set = (fn_av_dict_set)load_sym(h_util, "av_dict_set");
g_ff.av_dict_free = (fn_av_dict_free)load_sym(h_util, "av_dict_free");
g_ff.sws_getContext = (fn_sws_getContext)load_sym(h_sws, "sws_getContext");
g_ff.sws_scale = (fn_sws_scale)load_sym(h_sws, "sws_scale");
g_ff.sws_freeContext = (fn_sws_freeContext)load_sym(h_sws, "sws_freeContext");
g_ff.loaded = (g_ff.avformat_alloc_output_context2 && g_ff.avcodec_send_frame && g_ff.sws_scale);
return g_ff.loaded ? 0 : -2;
}
// ============================================================================
// Blend2D Native C++ Video Encoder
// ============================================================================
struct BLVideoEncoder {
AVFormatContext* format_ctx = nullptr;
void* codec_ctx = nullptr;
AVStream* stream = nullptr;
void* sws_ctx = nullptr;
AVFrame* frame = nullptr;
AVPacket* pkt = nullptr;
int width = 0;
int height = 0;
double fps = 30.0;
int64_t pts = 0;
int64_t frame_count = 0;
bool is_open = false;
char last_error[256] = {0};
};
BL_VIDEO_EXPORT BLVideoEncoder* bl_video_encoder_create(
const char* filename,
int width,
int height,
double fps,
const char* codec_name,
int crf,
const char* preset,
int bitrate
) {
if (!g_ff.loaded || !filename || width <= 0 || height <= 0 || fps <= 0.0) {
return nullptr;
}
auto* enc = new BLVideoEncoder();
enc->width = (width % 2 == 0) ? width : width - 1;
enc->height = (height % 2 == 0) ? height : height - 1;
enc->fps = fps;
enc->pts = 0;
enc->frame_count = 0;
// 1. Allocate output format context
int ret = g_ff.avformat_alloc_output_context2(&enc->format_ctx, nullptr, nullptr, filename);
if (ret < 0 || !enc->format_ctx) {
snprintf(enc->last_error, sizeof(enc->last_error), "Could not deduce format for: %s", filename);
delete enc;
return nullptr;
}
// 2. Find video encoder
const void* codec = nullptr;
if (codec_name && strlen(codec_name) > 0) {
codec = g_ff.avcodec_find_encoder_by_name(codec_name);
}
if (!codec) {
codec = g_ff.avcodec_find_encoder_by_name("libx264");
}
if (!codec) {
codec = g_ff.avcodec_find_encoder(enc->format_ctx->oformat->video_codec);
}
if (!codec) {
snprintf(enc->last_error, sizeof(enc->last_error), "No suitable video encoder found");
g_ff.avformat_free_context(enc->format_ctx);
delete enc;
return nullptr;
}
// 3. Create video stream
enc->stream = g_ff.avformat_new_stream(enc->format_ctx, nullptr);
if (!enc->stream) {
snprintf(enc->last_error, sizeof(enc->last_error), "Failed to allocate stream");
g_ff.avformat_free_context(enc->format_ctx);
delete enc;
return nullptr;
}
enc->stream->id = enc->format_ctx->nb_streams - 1;
// 4. Allocate codec context
enc->codec_ctx = g_ff.avcodec_alloc_context3(codec);
if (!enc->codec_ctx) {
snprintf(enc->last_error, sizeof(enc->last_error), "Failed to allocate codec context");
g_ff.avformat_free_context(enc->format_ctx);
delete enc;
return nullptr;
}
// Set properties via AVOptions
g_ff.av_opt_set_int(enc->codec_ctx, "width", enc->width, 0);
g_ff.av_opt_set_int(enc->codec_ctx, "height", enc->height, 0);
AVRational tb = {1, (int)(fps * 1000)};
g_ff.av_opt_set_q(enc->codec_ctx, "time_base", tb, 0);
enc->stream->time_base = tb;
AVRational fr = {(int)fps, 1};
g_ff.av_opt_set_q(enc->codec_ctx, "framerate", fr, 0);
// Set pixel format
if (g_ff.av_opt_set_pixel_fmt) {
g_ff.av_opt_set_pixel_fmt(enc->codec_ctx, "pix_fmt", AV_PIX_FMT_YUV420P, 0);
} else {
g_ff.av_opt_set(enc->codec_ctx, "pix_fmt", "yuv420p", 0);
}
if (bitrate > 0) {
g_ff.av_opt_set_int(enc->codec_ctx, "b", bitrate, 0);
}
// CRF and Preset
void* opts = nullptr;
if (preset && strlen(preset) > 0) {
g_ff.av_dict_set(&opts, "preset", preset, 0);
} else {
g_ff.av_dict_set(&opts, "preset", "fast", 0);
}
if (crf >= 0) {
char crf_str[16];
snprintf(crf_str, sizeof(crf_str), "%d", crf);
g_ff.av_dict_set(&opts, "crf", crf_str, 0);
}
// 5. Open codec
ret = g_ff.avcodec_open2(enc->codec_ctx, codec, &opts);
g_ff.av_dict_free(&opts);
if (ret < 0) {
snprintf(enc->last_error, sizeof(enc->last_error), "Failed to open video codec");
g_ff.avcodec_free_context(&enc->codec_ctx);
g_ff.avformat_free_context(enc->format_ctx);
delete enc;
return nullptr;
}
if (enc->stream->codecpar) {
g_ff.avcodec_parameters_from_context(enc->stream->codecpar, enc->codec_ctx);
}
// 6. Open file
if (!(enc->format_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = g_ff.avio_open(&enc->format_ctx->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0) {
snprintf(enc->last_error, sizeof(enc->last_error), "Could not open file: %s", filename);
g_ff.avcodec_free_context(&enc->codec_ctx);
g_ff.avformat_free_context(enc->format_ctx);
delete enc;
return nullptr;
}
}
// 7. Write header
ret = g_ff.avformat_write_header(enc->format_ctx, nullptr);
if (ret < 0) {
snprintf(enc->last_error, sizeof(enc->last_error), "Failed to write header");
if (enc->format_ctx->pb) g_ff.avio_closep(&enc->format_ctx->pb);
g_ff.avcodec_free_context(&enc->codec_ctx);
g_ff.avformat_free_context(enc->format_ctx);
delete enc;
return nullptr;
}
// 8. Allocate frame
enc->frame = g_ff.av_frame_alloc();
enc->frame->format = AV_PIX_FMT_YUV420P;
enc->frame->width = enc->width;
enc->frame->height = enc->height;
g_ff.av_frame_get_buffer(enc->frame, 0);
// 9. Allocate SIMD SwsContext
enc->sws_ctx = g_ff.sws_getContext(
enc->width, enc->height, AV_PIX_FMT_BGRA,
enc->width, enc->height, AV_PIX_FMT_YUV420P,
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr
);
enc->pkt = g_ff.av_packet_alloc();
enc->is_open = true;
return enc;
}
BL_VIDEO_EXPORT int bl_video_encoder_write_raw(
BLVideoEncoder* enc,
const uint8_t* bgra_data,
int stride
) {
if (!enc || !enc->is_open || !bgra_data) return -1;
if (g_ff.av_frame_make_writable(enc->frame) < 0) {
return -2;
}
const uint8_t* src_slices[4] = { bgra_data, nullptr, nullptr, nullptr };
int src_strides[4] = { stride, 0, 0, 0 };
g_ff.sws_scale(
enc->sws_ctx,
src_slices, src_strides, 0, enc->height,
enc->frame->data, enc->frame->linesize
);
enc->frame->pts = enc->frame_count * 1000;
enc->frame_count++;
int ret = g_ff.avcodec_send_frame(enc->codec_ctx, enc->frame);
if (ret < 0) return ret;
while (ret >= 0) {
ret = g_ff.avcodec_receive_packet(enc->codec_ctx, enc->pkt);
if (ret == AVERROR_EAGAIN || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
return ret;
}
g_ff.av_packet_rescale_ts(enc->pkt, enc->stream->time_base, enc->stream->time_base);
enc->pkt->stream_index = enc->stream->id;
ret = g_ff.av_interleaved_write_frame(enc->format_ctx, enc->pkt);
g_ff.av_packet_unref(enc->pkt);
if (ret < 0) return ret;
}
return 0;
}
BL_VIDEO_EXPORT int bl_video_encoder_close(BLVideoEncoder* enc) {
if (!enc || !enc->is_open) return 0;
g_ff.avcodec_send_frame(enc->codec_ctx, nullptr);
while (g_ff.avcodec_receive_packet(enc->codec_ctx, enc->pkt) == 0) {
g_ff.av_packet_rescale_ts(enc->pkt, enc->stream->time_base, enc->stream->time_base);
enc->pkt->stream_index = enc->stream->id;
g_ff.av_interleaved_write_frame(enc->format_ctx, enc->pkt);
g_ff.av_packet_unref(enc->pkt);
}
if (enc->format_ctx) {
g_ff.av_write_trailer(enc->format_ctx);
}
if (enc->codec_ctx) {
g_ff.avcodec_free_context(&enc->codec_ctx);
}
if (enc->frame) {
g_ff.av_frame_free(&enc->frame);
}
if (enc->pkt) {
g_ff.av_packet_free(&enc->pkt);
}
if (enc->sws_ctx) {
g_ff.sws_freeContext(enc->sws_ctx);
enc->sws_ctx = nullptr;
}
if (enc->format_ctx) {
if (!(enc->format_ctx->oformat->flags & AVFMT_NOFILE) && enc->format_ctx->pb) {
g_ff.avio_closep(&enc->format_ctx->pb);
}
g_ff.avformat_free_context(enc->format_ctx);
enc->format_ctx = nullptr;
}
enc->is_open = false;
return 0;
}
BL_VIDEO_EXPORT void bl_video_encoder_destroy(BLVideoEncoder* enc) {
if (enc) {
if (enc->is_open) {
bl_video_encoder_close(enc);
}
delete enc;
}
}
BL_VIDEO_EXPORT int64_t bl_video_encoder_get_frame_count(const BLVideoEncoder* enc) {
return enc ? enc->frame_count : 0;
}
BL_VIDEO_EXPORT const char* bl_video_encoder_get_last_error(const BLVideoEncoder* enc) {
return enc ? enc->last_error : "Null encoder";
}
} // extern "C"