MoWorld 生成结果如果直接保存为 .bin 或自定义扩展名文件,通常不是标准视频容器,播放器无法直接识别。需要先确认文件内部是原始像素帧、带时间戳的帧序列,还是图像序列打包,再决定用 ffmpeg 重新封装还是先提取再转码。下面给出针对常见原始帧输出的识别、提取和封装流程。
适用场景:拿到 MoWorld 输出的非标准视频文件,希望转为电脑和手机可播放的 MP4。操作方向:先用 file 判断类型,再根据宽高、像素格式和帧率用 ffmpeg 封装;若文件带自定义帧头,则需要先用 Python 提取裸帧和时间戳。验证方式:封装后使用 ffprobe 查看 avg_frame_rate 与 nb_frames,播放确认音画同步。风险边界:MoWorld 输出格式不确定时,盲目指定 -s、-pix_fmt 和 -r 会导致花屏或时长错误,必须先确认参数。
用 file 命令识别输出文件类型
在转换前,先对 MoWorld 生成的文件执行 file output.bin,查看输出的文件类型说明。该命令会提示文件是“data”还是带有特定编码头的流。如果输出为 raw video 或 video data,说明是裸帧数据;如果输出为一堆图片文件,则是图像序列;如果显示为 MP4、MOV 等容器名称,说明已经有封装,只是扩展名不对。
这个步骤能避免盲目用 ffmpeg 去猜格式。比如文件实际是 YUV420p 的裸帧,却按 RGB24 解析,画面颜色会完全错乱。建议同时查看文件大小,结合分辨率和帧数估算是否符合裸帧体积(宽×高×像素字节数×帧数)。这一步不需要精确计算,主要是建立判断方向。
提取原始帧数据与时间戳
MoWorld 某些输出会在帧头写入自定义标记或时间信息,直接交给 ffmpeg 会把它当成像素数据。这时需要用 Python 读取二进制帧,记录宽高和帧标记,导出时间戳序列。以下是一个通用读取骨架,需要根据实际帧头结构修改偏移量:
import struct, json
width, height = 640, 360
frame_size = width * height * 3 # 假设 RGB24
pix_fmt = 'rgb24'
timestamps = []
raw_frames = b''
with open('output.bin', 'rb') as f:
while True:
header = f.read(8)
if len(header) < 8:
break
# 假设前 4 字节是帧号,后 4 字节是时间戳(整数毫秒)
frame_no, ts_ms = struct.unpack('<II', header)
frame_data = f.read(frame_size)
if len(frame_data) < frame_size:
break
raw_frames += frame_data
timestamps.append({'frame': frame_no, 'timestamp_ms': ts_ms})
with open('extracted_frames.rgb', 'wb') as out:
out.write(raw_frames)
with open('timestamps.json', 'w') as jf:
json.dump(timestamps, jf, indent=2)执行后得到 extracted_frames.rgb 和 timestamps.json。时间戳文件可以用于后续检查帧间隔是否均匀,也能作为设置 ffmpeg 帧率的依据。如果帧数据直接是连续裸流,没有自定义头,这一步可以跳过。
使用 ffmpeg 封装为 mp4
对于已经确认是裸 RGB 数据的文件,可以直接用 ffmpeg 封装。注意参数中的分辨率、像素格式和帧率必须与 MoWorld 输出一致。示例如下:
ffmpeg -f rawvideo -pix_fmt rgb24 -s 640x360 -r 30 -i input.bin -c:v libx264 out.mp4如果文件是从 extracted_frames.rgb 提取出来的,就把 input.bin 替换成该文件名。若原始像素格式是 yuv420p,则将 -pix_fmt rgb24 改为 -pix_fmt yuv420p。这里没有指定码率,libx264 会根据内容自动选择,通常足够播放。封装后建议用播放器打开检查是否出现绿屏、花屏或时长异常。
如果 MoWorld 输出的是图像序列(如 frame_0001.png),则改用 ffmpeg -framerate 30 -i frame_%04d.png -c:v libx264 out.mp4。总之,核心原则是把原始数据转换为标准编码流后再放入 MP4 容器。
检查音画同步与帧率标签
封装完成后,用 ffprobe 查看输出文件的帧率标签和帧数:
ffprobe -v error -select_streams v:0 -show_entries stream=avg_frame_rate,nb_frames -of default=noprint_wrappers=1 out.mp4检查 avg_frame_rate 是否为期望值(如 30/1),nb_frames 是否与原始帧数一致。如果 ffprobe 显示的帧率是 0/0 或者实际播放卡顿,可以在封装时加 fps 滤镜强制统一帧率:
ffmpeg -f rawvideo -pix_fmt rgb24 -s 640x360 -r 30 -i input.bin -vf "fps=30" -c:v libx264 out.mp4注意:fps 滤镜会复制或丢弃帧来达到目标帧率,并非无损操作。如果 MoWorld 输出本身带有不均匀时间戳,建议先分析 timestamps.json 中的间隔,再决定是否需要调整帧率。若源数据没有音频轨道,ffprobe 不会显示音频信息,这属于正常现象。
写一个自动转换脚本示例
批量处理多个 MoWorld 输出文件时,可以用 Python 的 subprocess 调用 ffmpeg,遍历目录并保存转换日志。以下脚本扫描当前目录下所有 .bin 文件,假设它们都是 640x360 RGB24 30fps 的裸流:
import subprocess, pathlib, logging
logging.basicConfig(filename='convert.log', level=logging.INFO,
format='%(asctime)s - %(message)s')
for bin_file in pathlib.Path('.').glob('*.bin'):
out_file = bin_file.with_suffix('.mp4')
cmd = [
'ffmpeg', '-y',
'-f', 'rawvideo',
'-pix_fmt', 'rgb24',
'-s', '640x360',
'-r', '30',
'-i', str(bin_file),
'-c:v', 'libx264',
str(out_file)
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
logging.info(f'OK: {bin_file.name} -> {out_file.name}')
else:
logging.error(f'FAIL: {bin_file.name}: {result.stderr}')
print('转换结束,详情见 convert.log')脚本会把成功和失败的记录写入 convert.log。如果实际文件的分辨率、像素格式或帧率不同,需要先修改参数,或者先通过 file 命令和 ffprobe 验证结果。对于带有自定义帧头的文件,脚本中需要加入提取逻辑,不能直接用原始文件喂给 ffmpeg。这样批量转换时就不用每个文件手动敲命令了。