调用FFMPEG Device API完成Mac录屏功能。

调用FFMPEG提供的API来完成录屏功能,大致的思路是:

  1. 打开输入设备.
  2. 打开输出设备.
  3. 从输入设备读取视频流,然后经过解码->编码,写入到输出设备.
+--------------------------------------------------------------+
| +---------+ decode +------------+ |
| | Input | ----------read -------->| Output | |
| +---------+ encode +------------+ |
+--------------------------------------------------------------+

因此主要使用的API就是:

  1. avformat_open_input
  2. avcodec_find_decoder
  3. av_read_frame
  4. avcodec_send_packet/avcodec_receive_frame
  5. 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。

  1. 获取指定格式的输入设备
    pAVInputFormat = av_find_input_format("avfoundation");

通过指定格式名称获取到AVInputFormat结构体。

  1. 打开设备
    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容器规范的帧)既可。 获取编码器大致分为两个步骤:

  1. 构建编码器上下文(AVFormatContext)
  2. 匹配合适的编码器(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);
}
  • 编解码

从输入设备读取的是原生的数据流,也就是经过设备编码之后的数据。 需要先将原生数据进行解码,变成程序可读的数据,在编码成输出设备可识别的数据。 所以这一步的流程是:

  1. 解码输入设备数据
  2. 转码
  3. 编码写入输出设备

通过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) .

新手学习FFmpeg - 调用API完成录屏的更多相关文章

  1. 新手学习FFmpeg - 调用API完成录屏并进行H.264编码

    Screen Record H.264 目前在网络传输视频/音频流都一般会采用H.264进行编码,所以尝试调用FFMPEG API完成Mac录屏功能,同时编码为H.264格式. 在上一篇文章中,通过调 ...

  2. 新手学习FFmpeg - 调用API编写实现多次淡入淡出效果的滤镜

    前面几篇文章聊了聊FFmpeg的基础知识,我也是接触FFmpeg不久,除了时间处理之外,很多高深(滤镜)操作都没接触到.在学习时间处理的时候,都是通过在ffmpeg目前提供的avfilter基础上面修 ...

  3. 新手学习FFmpeg - 调用API完成视频的读取和输出

    在写了几个avfilter之后,原本以为对ffmpeg应该算是入门了. 结果今天想对一个视频文件进行转码操作,才发现基本的视频读取,输出都搞不定. 痛定思痛,仔细研究了一下ffmpeg提供的examp ...

  4. 新手学习FFmpeg - 调用API完成两个视频的任意合并

    本次尝试在视频A中的任意位置插入视频B. 在上一篇中,我们通过调整PTS可以实现视频的加减速.这只是对同一个视频的调转,本次我们尝试对多个视频进行合并处理. Concat如何运行 ffmpeg提供了一 ...

  5. 新手学习FFmpeg - 调用API计算关键帧渲染时间点

    通过简单的计算来,线上I帧在视频中出现的时间点. 完整代码请参考 https://andy-zhangtao.github.io/ffmpeg-examples/ 名词解释 首先需要明确以下名词概念: ...

  6. 新手学习FFmpeg - 调用API调整视频局部速率

    通过修改setpts代码实现调整视频部分的播放速率. 完整代码可参考: https://andy-zhangtao.github.io/ffmpeg-examples/ 在前面提到了PTS/DTS/T ...

  7. 新手学习FFmpeg - 通过API实现可控的Filter调用链

    虽然通过声明[x][y]avfilter=a=x:b=y;avfilter=xxx的方式可以创建一个可用的Filter调用链,并且在绝大多数场合下这种方式都是靠谱和实用的. 但如果想精细化的管理AVF ...

  8. 新手学习FFmpeg - 通过API完成filter-complex功能

    本篇尝试通过API实现Filter Graph功能. 源码请参看 https://andy-zhangtao.github.io/ffmpeg-examples/ FFmpeg提供了很多实用且强大的滤 ...

  9. android 调用 screenrecord 实现录屏

    首先要说明的是并未实现,本文讲一下自己的思路. adb 使用shell 命令 screenrecord 可录屏. 自己写了个app,通过Process p = Runtime.getRuntime() ...

随机推荐

  1. LiteDB源码解析系列(2)数据库页详解

    在这一篇里,我将用图文的方式展示LiteDB中页的结构及作用,内容都是原创,在描述的过程中有不准确的地方烦请指出. 1.LiteDB页的技术工作原理 LiteDB虽然是单个文件类型的数据库,但是数据库 ...

  2. websocket的加密和解密过程

    加密: import struct msg_bytes = "the emperor has not been half-baked in the early days of the col ...

  3. 《 C#语言学习笔记》——自动属性

    属性是访问对象状态的首选方式,因为它们禁止外部代码实现对象内部的数据存储机制.属性还对内部数据的访问方式有了更多控制.一般以非常标准的方式定义属性,即通过一个公共属性直接访问一个私有成员. 利用自动属 ...

  4. <<Modern CMake>> 翻译 1. CMake 介绍

    <<Modern CMake>> 翻译 1. CMake 介绍 人们喜欢讨厌构建系统. 仅仅观看 CppCon17 上的演讲,就可以看到开发人员因为构建系统而闹笑话的例子. 这 ...

  5. java性能优化--字符串优化处理

    String对象 String对象是java中重要的数据类型,在大部分情况下我们都会用到String对象.其实在Java语言中,其设计者也对String做了大量的优化工作,这些也是String对象的特 ...

  6. PHP验证身份证格式

    互联网公司对身份证验证的需求越来越多,然而普通的小公司是无法对接公安部门的身份认证系统的.几乎都是在网上买一些大的互联网公司的一些认证服务.即使是便宜一些的认证价格也达到了10万次/万元.也就是一角钱 ...

  7. Consul和Kong的实践(一)

    Consul和Kong的实践(一) 这一篇先介绍consul集群,以及consul和应用服务的结合使用,下一篇是和kong网关的结合. 一.Consul的集群安装 以其中一台机器为例: mkdir / ...

  8. 整理用Java实现数字转化成字符串左边自动补零方法

    Java 中给数字左边补0 (1)方法一 import java.text.NumberFormat; public class NumberFormatTest { public static vo ...

  9. 四、Python基础(1)

    目录 四.Python基础(1) 四.Python基础(1) 1.什么是变量? 一种变化的量,量是记录世界上的状态,变指得是这些状态是会变化的. 2.为什么有变量? 因为计算机程序的运行就是一系列状态 ...

  10. 洛谷P2125 题解

    吐槽: 只能说这道题很数学,本数学蒟蒻推了半天没推出来,只知道要用绝对值,幸亏教练提醒,才勉强想出正解(似乎不是这样的),真的是很无语. 以上皆为吐槽本题,可直接 跳过 分析: 既然题目是要使书架上的 ...