You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用WinAPI为基于Allegro 5的任天堂模拟器录制视频?

解决方案:基于FFmpeg的Allegro 5模拟器画面录制库

核心思路

直接复用模拟器内部生成的帧数据(无需WinAPI抓屏,除非你需要捕获外部窗口),通过FFmpeg的编码库将Allegro 5的帧数据转换为标准视频文件,封装成独立的.lib/.a/.so库供模拟器调用。

步骤1:封装FFmpeg录制库

我们封装三个核心API:初始化录制器、添加单帧、停止录制,隐藏FFmpeg的复杂细节。

头文件(recorder.h)

#ifndef RECORDER_H
#define RECORDER_H

#include <stdint.h>

// 初始化视频录制器
// 参数:输出文件路径、帧宽、帧高、帧率
// 返回:0成功,-1失败
int recorder_init(const char* output_file, int width, int height, int fps);

// 向录制器传入一帧RGBA格式的像素数据
// 参数:Allegro Surface锁定后的像素指针
// 返回:0成功,-1失败
int recorder_add_frame(uint8_t* rgba_data);

// 停止录制并释放所有资源
void recorder_stop();

#endif

实现文件(recorder.c)

#include "recorder.h"
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <stdio.h>

static AVFormatContext* fmt_ctx = NULL;
static AVStream* video_stream = NULL;
static AVCodecContext* codec_ctx = NULL;
struct SwsContext* sws_ctx = NULL;
static int frame_count = 0;
static int frame_width, frame_height;

int recorder_init(const char* output_file, int w, int h, int fps) {
    frame_width = w;
    frame_height = h;

    // 初始化FFmpeg组件(兼容旧版本,新版可省略av_register_all)
    av_register_all();

    // 创建输出格式上下文(自动识别输出文件格式)
    if (avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, output_file) < 0) {
        fprintf(stderr, "Failed to create output context\n");
        return -1;
    }

    // 查找对应格式的视频编码器
    const AVCodec* codec = avcodec_find_encoder(fmt_ctx->oformat->video_codec);
    if (!codec) {
        fprintf(stderr, "No encoder found for output format\n");
        return -1;
    }

    // 创建视频流
    video_stream = avformat_new_stream(fmt_ctx, codec);
    if (!video_stream) {
        fprintf(stderr, "Failed to create video stream\n");
        return -1;
    }

    // 配置编码器参数
    codec_ctx = video_stream->codec;
    codec_ctx->codec_id = fmt_ctx->oformat->video_codec;
    codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 通用视频格式
    codec_ctx->width = frame_width;
    codec_ctx->height = frame_height;
    codec_ctx->time_base = (AVRational){1, fps};
    codec_ctx->framerate = (AVRational){fps, 1};
    codec_ctx->bit_rate = 4000000; // 4Mbps码率,可按需调整

    // 全局头标记(部分格式如MP4需要)
    if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
        codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    // 打开编码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Failed to open encoder\n");
        return -1;
    }

    // 打开输出文件
    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&fmt_ctx->pb, output_file, AVIO_FLAG_WRITE) < 0) {
            fprintf(stderr, "Failed to open output file\n");
            return -1;
        }
    }

    // 写入文件头
    if (avformat_write_header(fmt_ctx, NULL) < 0) {
        fprintf(stderr, "Failed to write header\n");
        return -1;
    }

    // 初始化格式转换器:RGBA → YUV420P
    sws_ctx = sws_getContext(frame_width, frame_height, AV_PIX_FMT_RGBA,
                             frame_width, frame_height, AV_PIX_FMT_YUV420P,
                             SWS_BILINEAR, NULL, NULL, NULL);
    if (!sws_ctx) {
        fprintf(stderr, "Failed to create scale context\n");
        return -1;
    }

    frame_count = 0;
    return 0;
}

int recorder_add_frame(uint8_t* rgba_data) {
    if (!fmt_ctx || !codec_ctx || !sws_ctx) return -1;

    // 分配YUV帧内存
    AVFrame* frame = av_frame_alloc();
    frame->format = codec_ctx->pix_fmt;
    frame->width = frame_width;
    frame->height = frame_height;
    if (av_frame_get_buffer(frame, 0) < 0) {
        av_frame_free(&frame);
        return -1;
    }

    // 转换RGBA到YUV420P
    const uint8_t* src_data[1] = {rgba_data};
    int src_linesize[1] = {frame_width * 4}; // RGBA每行4字节
    sws_scale(sws_ctx, src_data, src_linesize, 0, frame_height,
              frame->data, frame->linesize);

    // 设置帧时间戳
    frame->pts = frame_count++;

    // 发送帧到编码器
    if (avcodec_send_frame(codec_ctx, frame) < 0) {
        av_frame_free(&frame);
        return -1;
    }

    // 读取编码后的数据包并写入文件
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    while (avcodec_receive_packet(codec_ctx, &pkt) == 0) {
        av_packet_rescale_ts(&pkt, codec_ctx->time_base, video_stream->time_base);
        pkt.stream_index = video_stream->index;
        if (av_interleaved_write_frame(fmt_ctx, &pkt) < 0) {
            av_packet_unref(&pkt);
            av_frame_free(&frame);
            return -1;
        }
        av_packet_unref(&pkt);
    }

    av_frame_free(&frame);
    return 0;
}

void recorder_stop() {
    if (!fmt_ctx || !codec_ctx) return;

    // 刷新编码器剩余数据
    avcodec_send_frame(codec_ctx, NULL);
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    while (avcodec_receive_packet(codec_ctx, &pkt) == 0) {
        av_packet_rescale_ts(&pkt, codec_ctx->time_base, video_stream->time_base);
        pkt.stream_index = video_stream->index;
        av_interleaved_write_frame(fmt_ctx, &pkt);
        av_packet_unref(&pkt);
    }

    // 写入文件尾
    av_write_trailer(fmt_ctx);

    // 关闭文件
    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_close(fmt_ctx->pb);
    }

    // 释放资源
    avcodec_close(codec_ctx);
    avformat_free_context(fmt_ctx);
    sws_freeContext(sws_ctx);

    fmt_ctx = NULL;
    codec_ctx = NULL;
    sws_ctx = NULL;
}

步骤2:编译录制库

Windows平台(MinGW)

  1. 编译FFmpeg:下载FFmpeg源码,配置编译参数(需开启x264支持):
    ./configure --enable-static --disable-shared --disable-programs --disable-doc --enable-libx264 --prefix=/path/to/ffmpeg-build
    make && make install
    
  2. 编译自己的库:
    gcc -c recorder.c -I/path/to/ffmpeg-build/include -o recorder.o
    ar rcs librecorder.a recorder.o
    
  3. 链接模拟器时需添加FFmpeg依赖:
    -lavformat -lavcodec -lswscale -lavutil -lx264 -lm -lpthread
    

Linux平台

  1. 安装FFmpeg开发依赖:
    sudo apt-get install libavformat-dev libavcodec-dev libswscale-dev libavutil-dev libx264-dev
    
  2. 编译静态库:
    gcc -c recorder.c -fPIC -o recorder.o
    ar rcs librecorder.a recorder.o
    
  3. 或编译动态库:
    gcc -shared -o librecorder.so recorder.o -lavformat -lavcodec -lswscale -lavutil -lx264
    

步骤3:在Allegro 5模拟器中调用

#include <allegro5/allegro.h>
#include "recorder.h"

int main() {
    al_init();
    ALLEGRO_DISPLAY* display = al_create_display(640, 480);
    ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();
    al_register_event_source(queue, al_get_display_event_source(display));

    // 初始化录制器:输出MP4,640x480,30fps
    if (recorder_init("simulator_recording.mp4", 640, 480, 30) != 0) {
        fprintf(stderr, "Recorder init failed\n");
        return 1;
    }

    bool running = true;
    while (running) {
        ALLEGRO_EVENT ev;
        al_wait_for_event(queue, &ev);
        if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
            running = false;
        }

        // 绘制模拟器画面(替换为你的渲染逻辑)
        al_clear_to_color(al_map_rgb(255, 0, 0));
        al_flip_display();

        // 锁定显示 bitmap 获取RGBA数据
        ALLEGRO_LOCKED_REGION* locked = al_lock_display_bitmap(display, ALLEGRO_PIXEL_FORMAT_RGBA_8888, ALLEGRO_LOCK_READONLY);
        if (locked) {
            recorder_add_frame((uint8_t*)locked->data);
            al_unlock_display_bitmap(display);
        }
    }

    // 停止录制,确保所有数据写入文件
    recorder_stop();

    al_destroy_event_queue(queue);
    al_destroy_display(display);
    return 0;
}

关键注意事项

  • 像素格式匹配:确保Allegro锁定的像素格式是ALLEGRO_PIXEL_FORMAT_RGBA_8888,如果你的模拟器用其他格式,需要修改sws_getContext中的源格式参数(比如AV_PIX_FMT_BGRA对应Allegro的ALLEGRO_PIXEL_FORMAT_BGRA_8888)。
  • FFmpeg版本兼容:如果使用FFmpeg 5.x+,av_register_all()已被弃用,可直接移除该调用,其余API基本兼容。
  • 性能优化:如果模拟器帧率较高,可考虑在单独线程中处理帧编码,避免阻塞渲染线程。
  • 错误处理:示例代码简化了错误处理,实际项目中需添加更详细的日志和回退逻辑。

内容的提问来源于stack exchange,提问作者Lucas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:30:55