图片时间轴
设实际audio_duration为D,当前保留照片数为N。每张分配D/N,以原图库顺序写ffconcat,duration写九位小数;最后重复最后一个文件条目,让其duration生效。它不多加一个独立照片段。多图切换按5fps帧时间量化,不能描述成逐音频采样精确对齐;也不根据文案语义或图像内容智能配时。
ffconcat version 1.0
file '/私有任务目录/cover.png'
duration 30.000000000
file '/私有任务目录/cover-1.png'
duration 30.000000000
file '/私有任务目录/cover-2.png'
duration 30.000000000
file '/私有任务目录/cover-2.png'
以上是90秒、3张图的机制示例,不是实际用户文件。路径由任务内部生成,不直接接受用户自由拼接。
Android实际编码参数
<nativeLibraryDir>/libffmpeg.so
-y -progress <job>/encode-progress.txt -nostats
-f concat -safe 0 -i <job>/image_sequence.ffconcat
-i <job>/audio.mp3
-filter_complex <按单图或多图生成的滤镜>
-map [v] -map 1:a
-c:v libx264 -preset veryfast -crf 21 -threads 2
-c:a aac -b:a 192k -ar 44100 -ac 2
-r <单图1/多图5>
-shortest -t <D> -movflags +faststart
<job>/video.pending.mp4
单图:
[0:v]loop=loop=-1:size=1:start=0,trim=duration=D,
setpts=N/(1*TB)[base];[base]format=yuv420p[v]
多图:
[0:v]fps=5,tpad=stop_mode=clone:stop_duration=2,
trim=duration=D[base];[base]format=yuv420p[v]
单图loop重复首帧减少反复解码,多图tpad延续末帧避免-shortest提前截尾;-t限制到音频时长。libx264为软件编码,veryfast和两线程是当前资源取舍,不存在“调用Android硬件加速”的分支。faststart调整MP4索引布局以便播放;格式机制参考FFmpeg格式文档,本项目参数以代码为准。
编码控制和实际进度
subprocess.Popen(...,start_new_session=True)启动独立进程组,循环约0.15秒检查Control并调用进度更新。暂停发SIGSTOP,继续发SIGCONT。异常/取消收尾先恢复停止态,再SIGTERM,等待3秒仍未退出则SIGKILL,防止只停止父进程而遗留编码子进程。
FFmpeg向encode-progress.txt输出out_time_us;读取文件尾8192字节,求out_time_seconds/D×100,编码阶段cap99。stdout/stderr追加到video-log.txt,非零退出时取日志尾用于中文异常说明。当前未实现整个视频编码的独立总超时或无进度watchdog,不能把TTS的120秒套到长视频。
最终验证与原子发布
编码成功后再check_cancel,FFprobe核对存在audio stream、视频尺寸恰为1920×1080、format duration有效且与D差不超过1.5秒。通过后replace临时视频到任务video_filename,设video_state=done、percent=100、保存video_duration和各图duration数组。这个规格检查不能替代完整逐帧解码或人工审核内容;回归用例中的完整主机解码是另外的测试证据。
依据:android_runtime.py:58–165、app.py:228–238、compose_video.py:104–123。