想着这个东西以后应该还会用到,所以就整理一下,供以后参考。
先说一下大致的方法:
先将帧缓冲区中RGB颜色数据读入到内存中,如果每个R、G、B都用GLbyte
来存的话,一个点就是3byte,如果要取的区域比较大,FPS又较高,则数据量会很大,内存会很快耗费掉一大块,尽管如此,还是推荐在渲染过程中只把数据先读到内存中,等渲染结束后再全部输出到文件中,因为每一帧数据量过大时输出到文件的过程会较慢,可能会影响渲染过程,虽然理论上想应该不会影响,但是在我这确实出现了问题,视频处理后会出现丢帧的现象。输出的文件叫做raw video
,体积大的吓人,所以我们需要对其编码压缩,需要的话还要加上声音,这就要用到神通广大的ffmpeg
了。
在说导出视频之前还需提一点:平滑fps。如果每帧的渲染任务量较小(大),则每帧的渲染时间就会较少(多),fps就会较大(小),但是视频的fps一般都不超过60,所以为了ffmpeg
编码方便,我们需要控制渲染的fps,也就是raw video的fps,一般可选60,25,24,15等值,24是大多数电影的fps,也是人眼识别连贯图像的最低fps。1
2
3
4
5
6double time_current = glfwGetTime();
double time_accurate = frame_count / 1.0 / fps;
double time_delta = time_accurate - time_current;
time_delta = time_delta > 0 ? time_delta : 0;
if(print) printf("frame_count:%d time_accurate:%lf time_current:%lf time_delta:%lf\n", frame_count, time_accurate, time_current, time_delta);
usleep(time_delta * 1000000);
只需将以上代码加到每帧最后即可,通过挂起强制平滑fps,与实际时间保持同步。
接下来说具体步骤:
在渲染前先建立一个存储帧的数组
GLbyte *frame[MAX_FRAME]
和帧计数器int frame_count = 0
;1
2GLbyte *frame[MAX_FRAME];
int frame_count = 0;在渲染完每一帧后对
frame
中的指针new
出一块固定大小的内存,该大小由要取的区域大小来定,然后用glReadPixes()
将帧缓冲区中的RGB颜色数据读入到内存中。glReadPixel()
用法见官方文档:glReadPixel()1
2frame[frame_count] = new GLbyte[window_width * window_height * 3];
glReadPixels(0, 0, window_width, window_height, GL_RGB, GL_UNSIGNED_BYTE, frame[frame_count++]);- 我这里是读取了整个窗口帧缓冲区。
- 网上说
glReadPixes()
读取的是当前显示的帧的缓冲区,即前帧缓冲区,如果要读取刚刚填充好的但并未显示的帧缓冲区,需先交换帧缓冲区。不过我感觉无所谓,读哪个都一样,所以直接在交换之前读了。我也试了交换之后读,感觉并无异样。
渲染过程结束后,将帧数组中的数据直接写入文件中即可,数据量太大,写入过程会很久。比如我渲染的一个30s的过程,FPS为32,帧缓冲区大小为1920x1080,输出的文件竟有6个G!也可直接算出来:1920x1080x3x32x30/(2^30)=5.56。
1
2FILE *out = fopen("raw_video", "wb");
for(int i = 0; i < frame_count; i++) fwrite(frame[i], window_width * window_height * 3, 1, out);用
ffmpeg
编码压缩并加上声音1
ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1920x1080 -framerate 32 -i raw_video -i sound.wav -strict -2 -vf "vflip" result.mp4
-f rawvideo
指定文件是raw video格式,即直接存储每一帧的全部点的数据;-pixel_format rgb24
指定点的格式是RGB格式,每个点24bit,也就是3byte;-video_size 1920x1080
指定视频小;-framerate 32
指定帧的速率FPS,即每秒的帧数;-i raw_video
指定输入文件为raw_video
,也就是之前渲染的输出文件,文件名可任取;前面的四个选项都是针对此输入文件设定的;-i sound.wav
指定另一个输入文件sound.wav
,也就是要加上的声音文件,如果想用其他格式,只需改变后缀名即可,只要ffmpeg
支持;-strict -2
是为了开启处于试验阶段的AAC
音频编码方式;(我的理解)-vf "vflip"
中的-vf
表示视频过滤器,后面引号中的为过滤器内容,vflip
表示将视频上下颠倒,因为用glReadPixels()
读取的第一个点在区域中的左下角,而ffmpeg
编码压缩的时候认为第一个点在左上角;result.mp4
为输出文件,如果想获得其他格式,只需改变后缀名即可,只要ffmpeg
支持。
这样就可以得到一个经过编码压缩且有声音的视频文件了,如果不想加声音,去掉-i sound.wav
即可。
如果想要给编码压缩后的视频文件加上声音,可用如下命令:1
ffmpeg -i result.mp4 -i sound.wav -strict -2 result_with_sound.mp4
如果想要截取一段视频或音频,可用如下命令:1
ffmpeg -i input.mp4 -ss 1.5 -to 14.275 output.mp4
-ss 1.5
指定从1.5秒开始;-to 14.275
指定到14.275秒结束。
之前写的可视化插件都是用这种方法做的视频,效果还不错。
ffmpeg
功能过于强大,剪辑、合成、编码、解码等功能都能实现,一些复杂的功能则可以通过一些基础功能的组合来实现,所以学一点ffmpeg
的使用还是很有必要的。