如何用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)
- 编译FFmpeg:下载FFmpeg源码,配置编译参数(需开启x264支持):
./configure --enable-static --disable-shared --disable-programs --disable-doc --enable-libx264 --prefix=/path/to/ffmpeg-build make && make install - 编译自己的库:
gcc -c recorder.c -I/path/to/ffmpeg-build/include -o recorder.o ar rcs librecorder.a recorder.o - 链接模拟器时需添加FFmpeg依赖:
-lavformat -lavcodec -lswscale -lavutil -lx264 -lm -lpthread
Linux平台
- 安装FFmpeg开发依赖:
sudo apt-get install libavformat-dev libavcodec-dev libswscale-dev libavutil-dev libx264-dev - 编译静态库:
gcc -c recorder.c -fPIC -o recorder.o ar rcs librecorder.a recorder.o - 或编译动态库:
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
相关产品推荐
相关产品推荐

