FFmpeg 新旧API编码

背景

直播SDK一开始使用的FFmpeg 2.8版本的,现在的FFmpeg最新版已经是4.4了。播放器编辑器使用的FFmpeg都是4.0的版本;新版本FFmpeg在内部结构也做了优化,效率、稳定性相比较旧版本都提升了不少。所以直播SDK FFmpeg也要必须升级了。

简介

直播SDK内部主要3部分使用了FFmpeg:

  1. 使用libavcodec 编码Audio;
  2. 使用libavcodec 编码Video;
  3. 使用libavformat 合成/推流;

我会先讲解使用旧AP Ilibavcodec编码Audio、Video的过程。然后再讲解使用新API编码Audio、Video的过程,通过前后的对比就可以很容易的知道直播SDK FFmpeg升级点在哪里?这里只是通过分析新旧API的使用来对比出我们直播SDK的更新点,同时也可以学习如何通过FFmpeg新旧API编码Audio、Video、Muxer。因为篇幅有限,这里并不会具体去讲解每个API内部的源码逻辑,感兴趣可以自行了解。

libavcodec旧API编码Audio

文本阐述感觉是比较枯燥的,这里先上图了解下通过FFmpeg libavcodec模块旧API如何编码Audio。后面在具体介绍API的作用与功能。
在这里插入图片描述

编码器注册

av_register_all()也可以使用avcodec_register_all()代替。查看源码可以发现av_register_all()内部调用了avcodec_register_all()。它的作用更就是注册所有的编解码器。

查找编码器

编码器注册好了之后就可以通过avcodec_find_encoder_by_name()、avcodec_find_encoder()获取我们想要的编码器。例如:通过avcodec_find_encoder(AV_CODEC_ID_AAC), 如果我们想使用libfdk-aac编码器编码音频必须在FFmpeg交叉编译的时候链接进去。否则我们在获取编码器的时候会使用FFmpeg内部默认的AAC编码器。

创建 AVCodecContext

当编码器创建好了之后,就需要根据编码器创建AVCodecContext,并初始化编码参数:采样里、声道数、采样格式等。

AVCodecContext *avCodecContext = avcodec_alloc_context3(codec);
avCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
avCodecContext->sample_rate = 44100;
avCodecContext->bit_rate = 64000;
avCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
avCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
avCodecContext->channels = av_get_channel_layout_nb_channels(avCodecContext->channel_layout);
avCodecContext->profile = FF_PROFILE_AAC_LOW;
avCodecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
avCodecContext->codec_id = codec->id;

打开编码器

编码器的参数设置好了之后,就可以打开编码器了。


if (avcodec_open2(avCodecContext, codec, NULL) < 0) {
   return -1;
}

创建AVFrame,并申请一块PCM内存

在FFmpeg 中编码前、解码后的数据用AVFrame表示;编码后,解码前的数据使用AVPacket表示;

这里需要为我们即将编码的数据创建一个AVFrame,并为它创建一个存放数据的空间。


// 创建AVFrame
AVFrame *encode_frame = av_frame_alloc();
encode_frame->nb_samples = avCodecContext->frame_size;
encode_frame->format = avCodecContext->sample_fmt;
encode_frame->channel_layout = avCodecContext->channel_layout;
encode_frame->sample_rate = avCodecContext->sample_rate;

// 申请一块PCM内存
int ret = av_samples_alloc_array_and_samples(&pcm_buffer, &src_samples_linesize, avCodecContext->channels, audio_nb_samples, avCodecContext->sample_fmt, 0);
if (ret < 0) {
    return -1;
}

编码

编码的过程是一个持续的循环过程。

  1. 从PCM队列中获取一帧音频数据到pcm_buffer

  2. 把pcm_buffer填充到AFrame中

  3. 音频编码,获取到编码后AVPacket数据

// 从PCM队列中获取一帧音频数据到pcm_buffer
pcm_frame_callback(pcm_buffer);

int ret;
int got_packet;
AVPacket *pkt = av_packet_alloc();
pkt->duration = (int) AV_NOPTS_VALUE;
pkt->pts = pkt->dts = 0;
  
// 把pcm_buffer填充到AFrame中
avcodec_fill_audio_frame(encode_frame, avCodecContext->channels, avCodecContext->sample_fmt, pcm_buffer[0], audioSamplesSize, 0);

// 音频编码,获取到编码后AVPacket数据
ret = avcodec_encode_audio2(avCodecContext, pkt, encode_frame, &got_packet);
if (ret < 0 || !got_packet) {
    av_packet_free(&pkt);
    return ret;
}

// write、enqueue

销毁

if (NULL != pcm_buffer) {
    av_free(pcm_buffer);
}
if (NULL != encode_frame) {
    av_frame_free(&encode_frame);
}
if (NULL != avCodecContext) {
   avcodec_close(avCodecContext);
   av_free(avCodecContext);
}

libavcodec 新API编码Audio

这里先上图了解下通过FFmpeg libavcodec模块新API如何编码Audio。后面在具体介绍核心API的作用与功能。

在这里插入图片描述

通过上图可以知道,FFmpeg新API在总编码过程中思想是不变的,只是调用的API改变了。这里重点介绍新API的使用,与旧API相同的部分就不再讲解。

创建AVFrame,并申请一块PCM内存

旧API创建方式:先申请一块内存,待填充好了pcm数据之后,再把该内存挂载到AVFrame上。

新API直接可以通过av_frame_get_buffer()为AVFrame创建好了内存。不过在调用av_frame_get_buffer()之前必须为AVFrame设置好采样率、声道数、采样格式、采样大小。

// 创建AVFrame
AVFrame *encode_frame = av_frame_alloc();
encode_frame->nb_samples = avCodecContext->frame_size;
encode_frame->format = avCodecContext->sample_fmt;
encode_frame->channel_layout = avCodecContext->channel_layout;
encode_frame->sample_rate = avCodecContext->sample_rate;

// 申请一块PCM内存
int ret = av_frame_get_buffer(encode_frame, 0);
if (ret < 0) {
    return -1;
}

编码

FFmpeg新API编码通过avcodec_send_frame()、avcodec_receive_packet()实现。他们内部实现原理可以参考文章底部的介绍。

    AVPacket pkt = { 0 };
    av_init_packet(&pkt);
    pkt.duration = (int) AV_NOPTS_VALUE;
    pkt.pts = pkt.dts = 0;
   
    while (true){
        do{
            ret = avcodec_receive_packet(avCodecContext, &pkt);
            // ret >= 0 获取编码后的视频流
            if(ret >= 0){
                
                av_free_packet(&pkt);
                return ret;
            }
            //
            if (ret == AVERROR(EAGAIN)) {
                // 跳出该循环。
                break;
            }
            // 编码出错
            if (ret < 0) {
                av_free_packet(&pkt);
                return ret;
            }
        }while (true);

        // 获取pcm数据
        pcm_frame_callback(encode_frame->data);
        ret = avcodec_send_frame(avCodecContext, encode_frame);
        if(ret >= 0){
//            LOGI("avcodec_send_frame success");
        }else{
            LOGI("avcodec_send_frame error: %s\n", av_err2str(ret));
        }
        av_packet_unref(&pkt);
    }

销毁

if (NULL != encode_frame) {
    av_frame_free(&encode_frame);
}
if (NULL != avCodecContext) {
   avcodec_free_context(avCodecContext)}

libavcodec 旧API编码Video

FFmpeg中通过libavcodec旧API编码Video,它的过程与libavcodec旧API编码Audio是非常相似的。我这里只给出API调用流程图,流程图过程中的每一步就不再详细分析了。你只要看懂了上面的分析,这里是非常简单的。
在这里插入图片描述

libavcodec新API编码Video

FFmpeg中通过libavcode新API编码Video,它的过程与libavcodec新API编码Audio是非常相似的。我这里只给出API调用流程图,流程图过程中的每一步就不再详细分析了。你只要看懂了上面的分析,这里是非常简单的。
在这里插入图片描述

音视频编解码器基本原理

在FFMPEG中 avcodec_send_frame() 和 avcodec_receive_packet() 通常是同时使用的,先调用 avcodec_send_frame() 送入要编码的音视频帧,然后调用 avcodec_receive_packet()获取编码后的数据包。但是需要注意的是:编码器内部是有缓冲区数据处理的,因此并不保证每送入一个音视频帧,就一定有相应的编码数据包输出,这两个函数对于数据处理,在时序上并不同步,这一点特别需要注意。

通常解码开始,通过avcodec_send_frame()送入几十个音视频帧,对应的avcodec_receive_packet()都没有数据包输出。等送入的帧足够多后,avcodec_receive_packet()才开始输出前面一开始送入进行编码的数据包。最后几十没有数据帧送入了,也要调用avcodec_send_frame()送入空帧,以驱动编码模块继续编码缓冲区中的数据,此时avcodec_receive_packet()还是会有数据包输出,直到返回AVERROR_EOF才表示所有音视频帧编码完成。

在这里插入图片描述

举个例子:

总计有100个视频帧要送入编码器编码,最终输出视频帧也是100个数据包输出。

  1. 前面20次调用 avcodec_send_frame()可以不断的送入第1~20个视频帧,但是前面20次调用avcodec_receive_packet()函数总是返回AVERROR(EAGAIN),没有数据包输出。

  2. 从第21次调用avcodec_send_frame()送入第21个视频帧开始,这次再调用avcodec_receive_packet()函数可以返回0,并且有数据包输出,但是输出的数据包pts是0(也即第一个数据包对应的视频帧),之后avcodec_send_frame()不断送入第22、23…个视频帧,avcodec_receive_packet()不断输出第1、2…个数据包

  3. 最后第100个数据包通过avcodec_send_frame()送入完成了,但是此时avcodec_receive_packet()才获取到第82帧输出数据包,此时需要继续不断调用avcodec_send_frame()送入空帧,同时不断调用avcodec_receive_packet()获取输出数据包,直到返回AVERROR_EOF,可以获取到最后第100个输出的数据包。

在这里插入图片描述

音视频编解码器基本原理参考文档:
https://zhuanlan.zhihu.com/p/346010443

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
敬告:该系列的课程在抓紧录制更新中,敬请大家关注。敬告:本课程项目仅供学习参考,请不要直接商用,概不负责任何法律责任。 该系列的课程涉及:FFmpeg,WebRTC,SRS,Nginx,Darwin,Live555,等。包括:音视频、流媒体、直播、Android、视频监控28181、等。 我将带领大家一起来学习使用FFmpeg开发视频监控项目,并动手操练。具体内容包括: 一、视频监控的架构和流程二、FFmpeg4.3+SDL2+Qt5开发环境的搭建三、FFmpeg的SDK编程回顾总结并操练四、SDL2.0的编程回顾总结并操练五、颜色空间转换RGB和YUV的原理与实战六、Qt5+FFmpeg本地摄像头采集预览实战七、代码封装:摄像头h264/5编码并存储八、Qt5+FFmpeg单路网络摄像头采集预览九、Qt5+FFmpeg单路网络摄像头采集预览录制会看十、onvif与GB/T-28181的简介  音视频与流媒体是一门很复杂的技术,涉及的概念、原理、理论非常多,很多初学者不学 基础理论,而是直接做项目,往往会看到c/c++的代码时一头雾水,不知道代码到底是什么意思,这是为什么呢?   因为没有学习音视频和流媒体的基础理论,就比如学习英语,不学习基本单词,而是天天听英语新闻,总也听不懂。 所以呢,一定要认真学习基础理论,然后再学习播放器、转码器、非编、流媒体直播、视频监控、等等。   梅老师从事音视频与流媒体行业18年;曾在永新视博、中科大洋、百度、美国Harris广播事业部等公司就职,经验丰富;曾亲手主导广电直播全套项目,精通h.264/h.265/aac,曾亲自参与百度app上的网页播放器等实战产品。  目前全身心自主创业,主要聚焦音视频+流媒体行业,精通音视频加密、流媒体在线转码快编等热门产品。  
FFmpeg是一个开源的跨平台的音视频处理框架,而FFmpeg Java API则是基于Java语言封装了FFmpeg功能的一个库。 使用FFmpeg Java API,我们可以在Java程序中方便地进行音视频的解码、编码、转码、剪切、合并等操作。通过调用FFmpeg的各种命令和参数,可以实现对音视频文件的各种处理需求。 FFmpeg Java API的主要特点包括: 1. 跨平台:由于基于Java语言开发,FFmpeg Java API可以在各种操作系统上使用,包括Windows、Linux、Mac等。 2. 功能强大:FFmpeg提供了丰富的音视频处理功能,FFmpeg Java API则封装了这些功能,使得在Java程序中可以方便地调用。 3. 简单易用:FFmpeg Java API提供了简洁的接口和方法,使得开发者可以快速上手,并快速实现各种音视频处理需求。 4. 高效性能:FFmpeg本身就是一个高性能的音视频处理框架,而FFmpeg Java API则是通过JNI技术与Java进行交互,保证了高效的执行速度和内存管理。 除了基本的音视频编解码功能外,FFmpeg Java API还支持基于滤镜的视频处理、音频处理、字幕添加等功能,使得开发者可以实现更加丰富的音视频处理效果。 总而言之,FFmpeg Java API是一个功能强大、跨平台、简单易用的音视频处理库,可以帮助开发者在Java程序中实现各种音视频处理需求。无论是简单的音视频格式转换,还是复杂的剪辑合成,FFmpeg Java API都能提供便捷的解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值