新手学习FFmpeg - 调用API完成录屏
2019-08-30 14:57
1126 查看
调用FFMPEG Device API完成Mac录屏功能。
调用FFMPEG提供的API来完成录屏功能,大致的思路是:
- 打开输入设备.
- 打开输出设备.
- 从输入设备读取视频流,然后经过解码->编码,写入到输出设备.
+--------------------------------------------------------------+ | +---------+ decode +------------+ | | | Input | ----------read -------->| Output | | | +---------+ encode +------------+ | +--------------------------------------------------------------+
因此主要使用的API就是:
- avformat_open_input
- avcodec_find_decoder
- av_read_frame
- avcodec_send_packet/avcodec_receive_frame
- avcodec_send_frame/avcodec_receive_packet
- 打开输入设备
如果使用FFmpeg提供的
-list_devices命令可以查询到当前支持的设备,其中分为两类:
- AVFoundation video devices
- AVFoundation audio devices
AVFoundation 是Mac特有的基于时间的多媒体处理框架。本次是演示录屏功能,因此忽略掉audio设备,只考虑video设备。在
avfoundation.m文件中没有发现可以程序化读取设备的API。FFmpeg官方也说明没有程序化读取设备的方式,通用方案是解析日志来获取设备(https://trac.ffmpeg.org/wiki/DirectShow#Howtoprogrammaticallyenumeratedevices),下一篇再研究如何通过日志获取当前支持的设备,本次就直接写死设备ID。
- 获取指定格式的输入设备
pAVInputFormat = av_find_input_format("avfoundation");
通过指定格式名称获取到AVInputFormat结构体。
- 打开设备
value = avformat_open_input(&pAVFormatContext, "1", pAVInputFormat, &options); if (value != 0) { cout << "\nerror in opening input device"; exit(1); }
"1"指代的是设备ID。 options是打开设备时输入参数,
// 记录鼠标 value = av_dict_set(&options, "capture_cursor", "1", 0); if (value < 0) { cout << "\nerror in setting capture_cursor values"; exit(1); } // 记录鼠标点击事件 value = av_dict_set(&options, "capture_mouse_clicks", "1", 0); if (value < 0) { cout << "\nerror in setting capture_mouse_clicks values"; exit(1); } // 指定像素格式 value = av_dict_set(&options, "pixel_format", "yuyv422", 0); if (value < 0) { cout << "\nerror in setting pixel_format values"; exit(1); }
通过value值判断设备是否正确打开。 然后获取设备视频流ID(解码数据包时需要判断是否一致),再获取输入编码器(解码时需要)。
- 打开输出设备
假设需要将从输入设备读取的数据保存成
mp4格式的文件。
将视频流保存到文件中,只需要一个合适的编码器(用于生成符合MP4容器规范的帧)既可。 获取编码器大致分为两个步骤:
- 构建编码器上下文(AVFormatContext)
- 匹配合适的编码器(AVCodec)
构建编码器:
// 根据output_file后缀名推测合适的编码器 avformat_alloc_output_context2(&outAVFormatContext, NULL, NULL, output_file); if (!outAVFormatContext) { cout << "\nerror in allocating av format output context"; exit(1); }
匹配编码器:
output_format = av_guess_format(NULL, output_file, NULL); if (!output_format) { cout << "\nerror in guessing the video format. try with correct format"; exit(1); } video_st = avformat_new_stream(outAVFormatContext, NULL); if (!video_st) { cout << "\nerror in creating a av format new stream"; exit(1); }
- 编解码
从输入设备读取的是原生的数据流,也就是经过设备编码之后的数据。 需要先将原生数据进行解码,变成程序
可读的数据,在编码成输出设备可识别的数据。 所以这一步的流程是:
- 解码输入设备数据
- 转码
- 编码写入输出设备
通过
av_read_frame从输入设备读取数据:
while (av_read_frame(pAVFormatContext, pAVPacket) >= 0) { ... }
对读取后的数据进行拆包,找到我们所感兴趣的数据
// 最开始没有做这种判断,出现不可预期的错误。 在官网example中找到这句判断,但还不是很清楚其意义。应该和packet封装格式有关 pAVPacket->stream_index == VideoStreamIndx
从FFmpeg 4.1开始,有了新的编解码函数。 为了长远考虑,直接使用新API。 使用
avcodec_send_packet将输入设备的数据发往
解码器进行解码,然后使用
avcodec_receive_frame从
解码器接受解码之后的数据帧。代码大概是下面的样子:
value = avcodec_send_packet(pAVCodecContext, pAVPacket); if (value < 0) { fprintf(stderr, "Error sending a packet for decoding\n"); exit(1); } while(1){ value = avcodec_receive_frame(pAVCodecContext, pAVFrame); if (value == AVERROR(EAGAIN) || value == AVERROR_EOF) { break; } else if (value < 0) { fprintf(stderr, "Error during decoding\n"); exit(1); } .... do something }
读取到数据帧后,就可以对每一帧进行
转码:
sws_scale(swsCtx_, pAVFrame->data, pAVFrame->linesize, 0, pAVCodecContext->height, outFrame->data,outFrame->linesize);
最后将转码后的帧封装成输出设备可设别的数据包格式。也就是解码的逆动作,使用
avcodec_send_frame将每帧发往编码器进行编码,通过
avcodec_receive_packet一直接受编码之后的数据包。处理逻辑大致是:
value = avcodec_send_frame(outAVCodecContext, outFrame); if (value < 0) { fprintf(stderr, "Error sending a frame for encoding\n"); exit(1); } while (value >= 0) { value = avcodec_receive_packet(outAVCodecContext, &outPacket); if (value == AVERROR(EAGAIN) || value == AVERROR_EOF) { break; } else if (value < 0) { fprintf(stderr, "Error during encoding\n"); exit(1); } ... do something; av_packet_unref(&outPacket); }
以后就按照这种的处理逻辑,不停的从输入设备读取数据,然后经过解码->转码->编码,最后发送到输出设备。 这样就完成了录屏功能。
上面是大致处理思路,完整源代码可以参考 (https://github.com/andy-zhangtao/ffmpeg-examples/tree/master/ScreenRecord) .
相关文章推荐
- VB调用API的学习
- Python调用face++API完成本地图片的人脸检测
- 新手学python(2):C语言调用完成数据库操作
- HIVE学习笔记:HiveServer2,调用HIVE的JavaAPI
- MOOC《Linux内核分析》——使用库函数API与C代码嵌入汇编完成同一个系统调用
- Activiti工作流框架学习(二)——使用Activiti提供的API完成流程操作
- 0基础学习音视频编程技术(三)Qt+ffmpeg开发环境搭建+简单QT项目调用ffmpeg
- js调用API学习
- [MOS学习笔记] 完成系统调用read的11个步骤
- FFmpeg学习笔记-新旧API替换
- Linux系统调用及用户编程接口(API)学习
- 新手学python(2):C语言调用完成数据库操作
- 请先调用 init 完成初始化后再调用其他云 API。init 方法可传入一个对象用于设置默认配置,详见文档。; at cloud.callFunction api 解决方案
- FFMPEG理解一个偶然遇到了ffmpeg,看起来不多,而且通用性很强,算是一个扎实的技术。 研究了两天了,万事开头难啊。 主要是新手学习一个东西的时候,没有宏观的概念,如果猛地往某个细节去钻,往往碰
- Linux内核设计第四周学习总结 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
- 人人网SDK Demo项目学习 3 在apilist中绑定事件调用Activity
- Linux系统调用及用户编程接口(API)学习
- 新手学习简单的调用系统Dialog
- Struts2.0深入学习 Strust2与Servlet API,Action多方法调用,result标签的type的类型
- 新手:学习计划(完成于九日)