如何加载大型MIDI文件到内存 解决mido打开时设备崩溃问题
问题根源
mido默认加载MIDI文件时会将所有轨道、所有事件全量解析为Python对象存入内存,16GB的源文件解析后实际内存占用会达到原始体积的34倍(约4864GB),远超普通消费级设备的内存上限,直接触发系统OOM崩溃。此外你编写的代码存在两处致命问题:
- 初始化输出文件时传入了源文件路径,会触发第二次全量加载,直接占用双倍内存
- 双层循环的合并逻辑会重复读取、追加轨道,生成巨量冗余数据,进一步放大内存压力
可行修改方案
1. 调整加载配置,启用按需加载模式
初始化MidiFile对象时传入lazy=True参数,开启懒加载模式:该模式下mido不会在打开文件时就解析所有事件,只会在你迭代访问事件时按需读取解析,基础内存占用可以降到百MB级别。
注意:开启懒加载后不要调用会触发全量扫描的操作,比如提前打印所有事件、对轨道做list()转换等,否则会失去懒加载的内存优势。
2. 修正代码逻辑错误
- 不要给输出用的
MidiFile传入源文件路径,直接初始化空对象,手动复制源文件的ticks_per_beat等头属性即可 - 删除多余的外层轨道遍历循环,原双层循环会导致同一段轨道被重复合并上百次,直接按步长遍历一次轨道索引即可
- 注意API名拼写:合并轨道的方法是
mido.merge_tracks(),不是mido.merge_track()
3. 极端场景优化
如果你的设备内存小于16G,懒加载+高层API的方案还是可能因为合并时临时存储事件导致OOM,此时建议放弃mido的高层事件接口,直接以二进制流方式逐块读取MIDI文件:MIDI文件本身是固定块结构,开头14字节为头信息,后续为独立的MTrk轨道块,你可以逐块读取轨道、重新计算事件偏移时间后直接写入输出文件,全程不需要将全量数据载入内存,内存占用可以稳定在50MB以内。
修正后的参考代码
import mido SRC_FILE = "<你的源MIDI文件路径>" OUT_FILE = "<合并后输出路径>" # 开启懒加载模式,不预读全量事件 src_midi = mido.MidiFile(SRC_FILE, lazy=True) # 初始化空输出文件,复制源文件时间精度 out_midi = mido.MidiFile(ticks_per_beat=src_midi.ticks_per_beat) merged_track_list = [] # 仅需一层循环按步长取轨道对合并 for idx in range(0, len(src_midi.tracks), 2): track_group = src_midi.tracks[idx:idx+2] merged_track = mido.merge_tracks(track_group) merged_track_list.append(merged_track) out_midi.tracks = merged_track_list out_midi.save(OUT_FILE)
补充说明:22亿音符级别的黑乐谱本身已经超出标准MIDI格式的常规设计承载范围,如果合并过程中出现事件截断、时间错乱问题,优先检查是否是delta time溢出导致的。
内容的提问来源于stack exchange,提问作者Loic Author
相关产品推荐
相关产品推荐

