首先录制桌面有很多种方法,原生windows api进行GDI抓屏,Mirror,Direct等,本文针对ffmpeg gdi抓屏进行介绍,也开始录屏软件开发之旅。
准备ffmpeg,无所谓是动态库、静态库,请自行前往下载并引入工程。
在本系列文章中,一些FFMPEG的结构、函数并不会做过多说明,请自行了解,重点放在了桌面录制的整体流程。
参考资料:
ffmpeg 源代码简单分析
初始化FFMPEG
av_register_all();
avdevice_register_all();
准备屏幕参数,包含了帧率、录制区域的起始坐标、大小、是否绘制鼠标等参数。
char buff_video_size[50] = { 0 };
sprintf_s(buff_video_size, 50, "%dx%d", rect.right - rect.left, rect.bottom - rect.top);
AVDictionary *options = NULL;
av_dict_set_int(&options, "framerate", fps, AV_DICT_MATCH_CASE);
av_dict_set_int(&options, "offset_x", rect.left, AV_DICT_MATCH_CASE);
av_dict_set_int(&options, "offset_y", rect.top, AV_DICT_MATCH_CASE);
av_dict_set(&options, "video_size", buff_video_size, AV_DICT_MATCH_CASE);
av_dict_set_int(&options, "draw_mouse", 1, AV_DICT_MATCH_CASE);
创建一个AVformatContext句柄,这个结构了不得,将会贯穿整个录屏软件的所有模块,描述了众多音视频信息。
AVFormatContext *_fmt_ctx = avformat_alloc_context();
获取gdigrab
AVInputFormat *_input_fmt = av_find_input_format("gdigrab");
打开gidgrab,这时候就要把准备好的捕获区域等配置信息一同传入api
avformat_open_input(&_fmt_ctx, "desktop", _input_fmt, &options);
获取桌面流信息和索引
avformat_find_stream_info(_fmt_ctx, NULL);
int stream_index = -1;
for (int i = 0; i < _fmt_ctx->nb_streams; i++) {
if (_fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
stream_index = i;
break;
}
}
请务必保存上面获取的索引,他将用于后面的捕获线程。
为gidgrab创建对应的解码器,这一点在后续的声音录制中也很重要,可以通过解码器获取数据的基本信息,如数据格式、采样率等等。
AVCodecContext *_codec_ctx = _fmt_ctx->streams[stream_index]->codec;
AVCodec *_codec = avcodec_find_decoder(_codec_ctx->codec_id);
查找到对应的解码器后,打开或初始化解码器。
avcodec_open2(_codec_ctx, _codec, NULL);
至此我们的初始化工作已经完成,这其中的API调用请务必判断所有的返回值,并进行相应的错误处理。最后,在初始化函数中进行最后一步,释放存放配置信息的字典。
av_dict_free(&options);
接下来就是我们的第一个录制线程了,请以喜欢或者习惯的方式启动一个线程。
为捕获出来的数据初始化两个结构体,AVFrame和AVPacket,这两个结构体也将在各种捕获、压缩、转码过程中出现,最好能读一下结构体介绍。
AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
AVFrame *frame_rgb = av_frame_alloc();
随后就是一个while循环进行屏幕数据的捕获以及解码。注意av_read_frame函数将会在某些情况下阻塞,例如读取网络流时,在本例中他将尽可能的按照我们所设定的帧率返回桌面图像数据。
int got_pic = 0;
while (_running == true) {
ret = av_read_frame(_fmt_ctx, packet);
if (ret < 0) {
al_fatal("read frame failed:%d", ret);
break;
}
if (packet->stream_index == _stream_index) {
ret = avcodec_decode_video2(_codec_ctx, frame_rgb, &got_pic, packet);
if (ret < 0) {
if (_on_error) _on_error(AE_FFMPEG_DECODE_FRAME_FAILED);
al_fatal("decode desktop frame failed");
break;
}
if (got_pic) {
//do something here with frame_rgba
}
}
av_free_packet(packet);
}
av_free(frame_rgb);
以上就是通过ffmpeg进行gdi抓屏的全部过程了。下一篇将对桌面数据压缩进行介绍。
CSDN源码下载
GitHub持续更新地址