```c
// avcodec_shim.c
// LD_PRELOAD shim: 让 headless-shell 以为有 H.264/AAC 解码器,实际不干活
// 编译: gcc -shared -fPIC -o libavcodec_shim.so avcodec_shim.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// ========== 模拟的结构体 ==========
typedef struct AVCodec {
const char *name;
const char *long_name;
int type;
int id;
int capabilities;
void *priv_data_size;
// 其他字段用 padding 占位,保证 sizeof 和真实 libavcodec 一致
char padding[96];
} AVCodec;
typedef struct AVCodecContext {
void *av_class;
int bit_rate;
int width;
int height;
char padding[1024];
} AVCodecContext;
typedef struct AVPacket {
char padding[128];
} AVPacket;
typedef struct AVFrame {
char padding[256];
} AVFrame;
enum AVCodecID {
AV_CODEC_ID_NONE,
AV_CODEC_ID_H264,
AV_CODEC_ID_H265,
AV_CODEC_ID_AAC,
AV_CODEC_ID_MP3,
AV_CODEC_ID_VP8,
AV_CODEC_ID_VP9,
};
enum AVMediaType {
AVMEDIA_TYPE_UNKNOWN = -1,
AVMEDIA_TYPE_VIDEO,
AVMEDIA_TYPE_AUDIO,
};
// ========== 假解码器实例 ==========
static AVCodec fake_h264_decoder = {
.name = "h264",
.long_name = "H.264 / AVC / MPEG-4 AVC",
.type = AVMEDIA_TYPE_VIDEO,
.id = AV_CODEC_ID_H264,
.capabilities = 0x0001, // CODEC_CAP_DRAW_HORIZ_BAND
};
static AVCodec fake_aac_decoder = {
.name = "aac",
.long_name = "AAC (Advanced Audio Coding)",
.type = AVMEDIA_TYPE_AUDIO,
.id = AV_CODEC_ID_AAC,
.capabilities = 0,
};
static AVCodec fake_mp3_decoder = {
.name = "mp3",
.long_name = "MP3 (MPEG audio layer 3)",
.type = AVMEDIA_TYPE_AUDIO,
.id = AV_CODEC_ID_MP3,
.capabilities = 0,
};
static AVCodec fake_vp8_decoder = {
.name = "vp8",
.long_name = "On2 VP8",
.type = AVMEDIA_TYPE_VIDEO,
.id = AV_CODEC_ID_VP8,
.capabilities = 0,
};
static AVCodec fake_vp9_decoder = {
.name = "vp9",
.long_name = "Google VP9",
.type = AVMEDIA_TYPE_VIDEO,
.id = AV_CODEC_ID_VP9,
.capabilities = 0,
};
// ========== 劫持函数 ==========
AVCodec *avcodec_find_decoder(enum AVCodecID id) {
static int in_call = 0;
if (in_call) {
// 递归保护:如果真实 libavcodec 不存在,直接返回假解码器
switch (id) {
case AV_CODEC_ID_H264: return &fake_h264_decoder;
case AV_CODEC_ID_H265: return &fake_h264_decoder; // H.265 也用 H.264 假装
case AV_CODEC_ID_AAC: return &fake_aac_decoder;
case AV_CODEC_ID_MP3: return &fake_mp3_decoder;
case AV_CODEC_ID_VP8: return &fake_vp8_decoder;
case AV_CODEC_ID_VP9: return &fake_vp9_decoder;
default: return NULL;
}
}
// 先尝试调用真实的 avcodec_find_decoder
in_call = 1;
typedef AVCodec* (*func_t)(enum AVCodecID);
static func_t real_func = NULL;
if (!real_func) {
real_func = (func_t)dlsym(RTLD_NEXT, "avcodec_find_decoder");
}
in_call = 0;
if (real_func && real_func != avcodec_find_decoder) {
AVCodec *result = real_func(id);
if (result) return result;
}
// 真实函数不存在或返回 NULL,返回假解码器
switch (id) {
case AV_CODEC_ID_H264: return &fake_h264_decoder;
case AV_CODEC_ID_H265: return &fake_h264_decoder;
case AV_CODEC_ID_AAC: return &fake_aac_decoder;
case AV_CODEC_ID_MP3: return &fake_mp3_decoder;
case AV_CODEC_ID_VP8: return &fake_vp8_decoder;
case AV_CODEC_ID_VP9: return &fake_vp9_decoder;
default: return NULL;
}
}
AVCodec *avcodec_find_decoder_by_name(const char *name) {
if (!name) return NULL;
if (strstr(name, "h264") || strstr(name, "H264")) return &fake_h264_decoder;
if (strstr(name, "hevc") || strstr(name, "h265") || strstr(name, "HEVC")) return &fake_h264_decoder;
if (strstr(name, "aac") || strstr(name, "AAC")) return &fake_aac_decoder;
if (strstr(name, "mp3") || strstr(name, "MP3")) return &fake_mp3_decoder;
if (strstr(name, "vp8") || strstr(name, "VP8")) return &fake_vp8_decoder;
if (strstr(name, "vp9") || strstr(name, "VP9")) return &fake_vp9_decoder;
return NULL;
}
// avcodec_open2: 总是成功
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, void *options) {
return 0; // 0 = 成功
}
// avcodec_close: 无操作
int avcodec_close(AVCodecContext *avctx) {
return 0;
}
// avcodec_send_packet: 接受但不处理
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt) {
return 0;
}
// avcodec_receive_frame: 返回 EAGAIN(没有帧可用),合理的不干活状态
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame) {
return -11; // -EAGAIN
}
// avcodec_free_context: 无操作
void avcodec_free_context(AVCodecContext **avctx) {
if (avctx && *avctx) {
free(*avctx);
*avctx = NULL;
}
}
// avcodec_alloc_context3: 分配一个空结构体
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec) {
AVCodecContext *ctx = calloc(1, sizeof(AVCodecContext));
return ctx;
}
```
**编译**:
```bash
gcc -shared -fPIC -o libavcodec_shim.so avcodec_shim.c -ldl
```
**使用**:
```dockerfile
# Dockerfile
COPY libavcodec_shim.so /usr/local/lib/
ENV LD_PRELOAD=/usr/local/lib/libavcodec_shim.so
```
或者在启动命令前:
```bash
LD_PRELOAD=/usr/local/lib/libavcodec_shim.so ./headless-shell --no-sandbox --headless ...
```
这个 shim 只劫持 FFmpeg 解码器查找和初始化函数,让 headless-shell 以为 H.264/AAC 解码器存在。解码器找到了、打开了、但 `receive_frame` 永远返回 EAGAIN(没有帧),所以不产生实际解码开销。MSE 管道畅通,`FMP4_CAPTURE_SCRIPT` 正常抓流。
体积:约 20KB,几乎零开销。
No Comments