前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >新手学习FFmpeg - 调用API完成录屏

新手学习FFmpeg - 调用API完成录屏

作者头像
随机来个数
发布2019-08-31 20:04:10
2K0
发布2019-08-31 20:04:10
举报
文章被收录于专栏:写代码的海盗

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

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

  1. 打开输入设备.
  2. 打开输出设备.
  3. 从输入设备读取视频流,然后经过解码->编码,写入到输出设备.
代码语言:javascript
复制
+--------------------------------------------------------------+
|   +---------+    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. 获取指定格式的输入设备
代码语言:javascript
复制
    pAVInputFormat = av_find_input_format("avfoundation");

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

  1. 打开设备
代码语言:javascript
复制
    value = avformat_open_input(&pAVFormatContext, "1", pAVInputFormat, &options);
    if (value != 0) {
        cout << "\nerror in opening input device";
        exit(1);
    }

"1"指代的是设备ID。 options是打开设备时输入参数,

代码语言:javascript
复制
    // 记录鼠标
    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)

构建编码器:

代码语言:javascript
复制
    // 根据output_file后缀名推测合适的编码器
    avformat_alloc_output_context2(&outAVFormatContext, NULL, NULL, output_file);
    if (!outAVFormatContext) {
        cout << "\nerror in allocating av format output context";
        exit(1);
    }

匹配编码器:

代码语言:javascript
复制
    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从输入设备读取数据:

代码语言:javascript
复制
while (av_read_frame(pAVFormatContext, pAVPacket) >= 0) {
    ...
}

对读取后的数据进行拆包,找到我们所感兴趣的数据

代码语言:javascript
复制
    // 最开始没有做这种判断,出现不可预期的错误。 在官网example中找到这句判断,但还不是很清楚其意义。应该和packet封装格式有关
    pAVPacket->stream_index == VideoStreamIndx

从FFmpeg 4.1开始,有了新的编解码函数。 为了长远考虑,直接使用新API。 使用avcodec_send_packet将输入设备的数据发往解码器进行解码,然后使用avcodec_receive_frame解码器接受解码之后的数据帧。代码大概是下面的样子:

代码语言:javascript
复制
            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
            }

读取到数据帧后,就可以对每一帧进行转码:

代码语言:javascript
复制
    sws_scale(swsCtx_, pAVFrame->data, pAVFrame->linesize, 0, pAVCodecContext->height, outFrame->data,outFrame->linesize);

最后将转码后的帧封装成输出设备可设别的数据包格式。也就是解码的逆动作,使用avcodec_send_frame将每帧发往编码器进行编码,通过avcodec_receive_packet一直接受编码之后的数据包。处理逻辑大致是:

代码语言:javascript
复制
                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) .

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-08-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
多媒体处理
多媒体处理(Multimedia Processing,MMP)是数据万象推出的音视频处理服务,集成音视频转码、极速高清、精彩集锦、超分辨率、数字水印等能力,满足传媒、文旅、电商等各行业多媒体处理需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档