ffmpeg视频编码

一、视频编码流程

使用ffmpeg解码视频帧主要可分为两大步骤:初始化编码器编码视频帧,以下代码以h264为例

1. 初始化编码器

初始化编码器包含以下步骤:

(1)查找编码器

videoCodec = avcodec_find_encoder_by_name(videoCodecName);
if (!videoCodec) {
    release();
    return false;
}

(2)设置编码器上下文参数

pCodecCtx = avcodec_alloc_context3(videoCodec);
pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准
pCodecCtx->framerate = { m_fps, 1 };
pCodecCtx->bit_rate = videoBitrate;
pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0); // 设置编码速度
// 以下为根据对应的编码格式设置相关的参数
if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
{
    pCodecCtx->keyint_min = m_fps;
    av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);
    av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);
    av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
}
else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
    pCodecCtx->max_b_frames = 2;
else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
    pCodecCtx->mb_decision = 2;

(3)打开编码器

if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联
{
    release();
    return false;
}

(4)设置图像帧参数

// 初始化编码帧
in_frame = av_frame_alloc();
if (!in_frame) {

    return false;
}
// 设置 AVFrame 的其他属性
in_frame->width = pCodecCtx->width;
in_frame->height = pCodecCtx->height;
in_frame->format = pCodecCtx->pix_fmt;

// 计算所需缓冲区大小
int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
in_frame_buf = (uint8_t*)av_malloc(size); // 分配图像帧内存空间
// 填充 AVFrame
av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);

(5)创建格式转换上下文
ffmpeg的libx264编码器只支持输入格式为P,如果输入格式不是YUV420P,则需要转换

// 创建缩放上下文,图像缩放和格式转换上下文
if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
    sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,
        in_w, in_h, AV_PIX_FMT_YUV420P,
        SWS_BILINEAR, NULL, NULL, NULL);
    if (!sws_ctx) {
        release();
        return false;
    }
}

(6)封装设置和打开文件
如果要将编码后数据进行封装(如封装成mp4文件),则需要使用AVFormatContext,并设置视频流

// 打开格式上下文
 if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {
    release();
    return false;
 }
  // 初始化视频码流
video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);
if (!video_st) {
    std::cout << "avformat_new_stream error" << endl;
    return false;
}
fmt = pFormatCtx->oformat;
video_st->id = pFormatCtx->nb_streams - 1;
avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);
pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;

// 打开文件
if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {
    //std::cout << "output file open fail!" << endl;
    swprintf(buf, 1024, L"avio_open error");
    logger.logg(buf);
    return false;
}
// 输出格式信息
av_dump_format(pFormatCtx, 0, mp4_file, 1);

2. 编码视频帧

(1)将编码数据送往解码器

    
    // data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧的大小
    // 如果输入数据格式不是YUV420P则需要转化
    if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
        if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {
            
            return false;
        }
    }
    else {
        memcpy(in_frame->data[0],  data, size);
    }

    in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号
   
    // 编码
    int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器
    if (ret < 0) {
        return false;
    }

(2)接收编码数据

AVPacket* vicdeo_pkt = av_packet_alloc();
while (ret >= 0) {
    ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        break;
    else if (ret < 0) {
        //cout << "Error during encoding" << endl;
        return false;
    }

    // Prepare packet for muxing
    pkt.stream_index = video_st->index;
    av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基
    pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。

    ret = av_interleaved_write_frame(pFormatCtx, &pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中
    if (ret < 0) {
        
        return false;
    }
    
    // Free the packet
    av_packet_unref(&pkt);
}
++videoPktCount; // 每编码完成一帧,videoPktCount加一
av_packet_free(&vicdeo_pkt);
videoPkt = nullptr;

二、使用ffmpeg实现对内存中的视频帧数据编码

以下代码实现了ffmpeg对视频流数据进行编码的主要过程,可分为初始化编码器(InitEncoder)和编码视频帧(DecodeVideoFrame)


extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/error.h>
}

bool mHasVideo = false;
char* mp4_file = "test.mp4";
int in_w = 1920;
int in_h = 1080;
int m_fps = 30;
int64_t videoBitrate = 6000000;
int      mVideoSrcChannel = 0;
int      mVideoSrcStride[1] = { 0 };
int64_t   videoPktCount = 0;

AVPixelFormat videoSrcFormat = AV_PIX_FMT_YUV420P; // 输入的像素格式

AVStream* video_st = nullptr;
const AVCodec* videoCodec = nullptr; // 视频编码器
AVCodecContext* pCodecCtx = nullptr; // 视频编码器上下文

uint8_t* in_frame_buf = nullptr;
AVFrame* in_frame = nullptr;
SwsContext* sws_ctx = nullptr;

// 初始化编码器
bool InitEncoder() {
     /****   编码器设置      ****/
    // 查找编码器
    videoCodec = avcodec_find_encoder_by_name(videoCodecName);
    if (!videoCodec) {
        release();
        return false;
    }
    pCodecCtx = avcodec_alloc_context3(videoCodec);
    pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准
    pCodecCtx->framerate = { m_fps, 1 };
    pCodecCtx->bit_rate = videoBitrate;
    pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响
    pCodecCtx->width = in_w;
    pCodecCtx->height = in_h;
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0);

    // 以下为根据对应的编码格式设置相关的参数
    if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
    {
        pCodecCtx->keyint_min = m_fps;
        av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);
        av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);
        av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
    }
    else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
        pCodecCtx->max_b_frames = 2;
    else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
        pCodecCtx->mb_decision = 2;

    if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联
    {
        release();
        return false;
    }

    /****   封装设置      ****/
    if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {
        swprintf(buf, 1024, L"avformat_alloc_output_context2 error");
        logger.logg(buf);
        return false;
    }
    
    // 初始化视频码流
    video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);
    if (!video_st) {
        std::cout << "avformat_new_stream error" << endl;
        return false;
    }
    video_st->id = pFormatCtx->nb_streams - 1;
    avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);
    pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;
    

    // 打开文件
    if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {
        release();
        return false;
    }
    // 输出格式信息
    av_dump_format(pFormatCtx, 0, mp4_file, 1);
    
    /****** 输入参数 *********/
    // 初始化编码帧
    in_frame = av_frame_alloc();
    if (!in_frame) {

        return false;
    }
    // 设置 AVFrame 的其他属性
    in_frame->width = pCodecCtx->width;
    in_frame->height = pCodecCtx->height;
    in_frame->format = pCodecCtx->pix_fmt;

    // 计算所需缓冲区大小
    int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
    in_frame_buf = (uint8_t*)av_malloc(size);
    // 填充 AVFrame
    av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);


    // 创建缩放上下文,图像缩放和格式转换上下文
    if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
        sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,
            in_w, in_h, AV_PIX_FMT_YUV420P,
            SWS_BILINEAR, NULL, NULL, NULL);
        if (!sws_ctx) {
            release();
            return false;
        }
    }

    av_image_fill_linesizes(mVideoSrcStride, videoSrcFormat, in_w); // 计算图像的行大小,格式转换时会用上
    
    videoPktCount = 0;
    
    

}

bool DecodeVideoFrame(uint8_t* data, int size) {

    // data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧大小
    // 如果输入数据格式不是YUV420P则需要转化
    if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
        if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {
            
            return false;
        }
    }
    else {
        memcpy(in_frame->data[0],  data, size);
    }

    in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号
   
    // 编码
    int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器
    if (ret < 0) {
        return false;
    }

    AVPacket* vicdeo_pkt = av_packet_alloc();
    while (ret >= 0) {
        ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0) {
            
            return false;
        }

        // Prepare packet for muxing
        vicdeo_pkt.stream_index = video_st->index;
        av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基
        vicdeo_pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。

        ret = av_interleaved_write_frame(pFormatCtx, vicdeo_pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中
        if (ret < 0) {
            
            return false;
        }
        
        // Free the packet
        av_packet_unref(vicdeo_pkt);
        
    }
    av_packet_free(&vicdeo_pkt);
    videoPkt = nullptr;
    ++videoPktCount; // 每编码完成一帧,videoPktCount加一
}

void release() {
    
    if (in_frame_buf) {
        av_free(in_frame_buf);
        in_frame_buf = nullptr;
    }
    if (in_frame) {
        av_frame_free(&in_frame);
        in_frame = nullptr;
    }
   
    if (pCodecCtx) {
        avcodec_close(pCodecCtx);
        avcodec_free_context(&pCodecCtx);
        pCodecCtx = nullptr;

    }
    if (sws_ctx) {
        sws_freeContext(sws_ctx);
        sws_ctx = nullptr;
    }

    if (pFormatCtx) {
        avio_close(pFormatCtx->pb);
        avformat_free_context(pFormatCtx);
        pFormatCtx = nullptr;
    }
}
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/918741.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习day5-随机森林和线性代数1最小二乘法

十 集成学习方法之随机森林 集成学习的基本思想就是将多个分类器组合&#xff0c;从而实现一个预测效果更好的集成分类器。大致可以分为&#xff1a;Bagging&#xff0c;Boosting 和 Stacking 三大类型。 &#xff08;1&#xff09;每次有放回地从训练集中取出 n 个训练样本&…

SpringCloud框架学习(第三部分:Resilience4j 与 Micrometer)

目录 九、CircuitBreaker断路器 1.前言&#xff08;Hystrix&#xff09; 2.服务雪崩 3.Circuit Breaker 4. Resilience4j 5.案例实战 &#xff08;1&#xff09;熔断&#xff08;服务熔断 服务降级&#xff09; Ⅰ. 按照 COUNT_BASED&#xff08;计数的滑动窗口&#xf…

使用WebVTT和Track API增强HTML5视频的可访问性和互动性

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用WebVTT和Track API增强HTML5视频的可访问性和互动性 使用WebVTT和Track API增强HTML5视频的可访问性和互动性 使用WebVTT和T…

vue2中引入cesium全步骤

1.npm 下载cesium建议指定版本下载&#xff0c;最新版本有兼容性问题 npm install cesium1.95.0 2.在node_models中找到cesium将此文件下的Cesium文件复制出来放在项目的静态资源public中或者static中&#xff0c;获取去github上去下载zip包放在本地也可以 3.在index.html中引…

VTK知识学习(9)-空间变换

1、前言 在三维空间里定义的三维模型&#xff0c;最后显示时都是投影到二维平面&#xff0c;比如在屏幕上显示。 三维到二维的投影包括透视投影&#xff08;Perspective Projection&#xff09;和正交投影&#xff08;Orthogonale Projection&#xff09;。正交投影也叫平行投…

Python学习从0到1 day29 Python 高阶技巧 ⑦ 正则表达式

目录 一、正则表达式 二、正则表达式的三个基础方法 1.match 从头匹配 2.search&#xff08;匹配规则&#xff0c;被匹配字符串&#xff09; 3.findall&#xff08;匹配规则&#xff0c;被匹配字符串&#xff09; 三、元字符匹配 单字符匹配&#xff1a; 注&#xff1a; 示例&a…

日常ctf

15&#xff0c; [MoeCTF 2021]Web安全入门指北—小饼干 直接改就行了 16&#xff0c; [MoeCTF 2021]2048 传入参数就获取到flag了 /flag.php?score500000000 17&#xff0c; [SWPUCTF 2022 新生赛]funny_web 账户密码是 NSS 2122693401 登录进去查看源码 考intval缺陷&…

【MySql】实验十六 综合练习:图书管理系统数据库结构

文章目录 创建图书管理系统数据库结构一、创建数据表1.1 book表1.2 reader表1.3 borrow表 二、插入示例数据2.1 向book表插入数据2.2 向reader表插入数据2.3 向borrow表插入数据 三、查询操作3.1 根据语义为借书表borrow的bno列和 rno列建立外键3.2 查询张小海编写的“数据库原…

通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制

目录 通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制 一、引言 二、语义搜索与 MongoDB Atlas 的背景 三、MongoDB Atlas 的向量搜索功能 1. 向量搜索的实现方式 2. 典型操作示例 四、RAG 在 MongoDB Atlas 的应用 1、RAG是什么 2、RAG 的实现过程 3、RA…

51单片机基础01 单片机最小系统

目录 一、什么是51单片机 二、51单片机的引脚介绍 1、VCC GND 2、XTAL1 2 3、RST 4、EA 5、PSEN 6、ALE 7、RXD、TXD 8、INT0、INT1 9、T0、T1 10、MOSI、MISO、SCK 11、WR、RD 12、通用IO P0 13、通用IO P1 14、通用IO P2 三、51单片机的最小系统 1、供电与…

【原创】如何备份和还原Ubuntu系统,非常详细!!

前言 我在虚拟机装了一个xfce4的Ubuntu桌面版&#xff0c;外加输入法、IDEA等&#xff0c;我想将这个虚拟机里的系统直接搬到物理机中&#xff0c;那我可以省的再重新装一遍、配置xfce4桌面、修改一堆快捷键还有配置idea了&#xff0c;那直接说干就干。 本教程基于Ubuntu24.0…

萤石设备视频接入平台EasyCVR私有化视频平台视频监控系统的需求及不同场景摄像机的选择

在现代社会&#xff0c;随着安全意识的提高和技术的进步&#xff0c;安防监控视频系统已成为保障人们生活和财产安全的重要工具。EasyCVR安防监控视频系统&#xff0c;以其先进的网络传输技术和强大的功能&#xff0c;为各种规模的项目提供了一个高效、可靠的监控解决方案。以下…

基于yolov8、yolov5的行人检测识别系统(含UI界面、训练好的模型、Python代码、数据集)

摘要&#xff1a;行人检测在交通管理、智能监控和公共安全中起着至关重要的作用&#xff0c;不仅能帮助相关部门实时监控人群动态&#xff0c;还为自动化监控系统提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的行人检测模型&#xff0c;该模型使用了…

Django5 2024全栈开发指南(二):Django项目配置详解

目录 一、基本配置信息二、资源文件配置2.1 资源路由——STATIC_URL2.2 资源集合——STATICFILES_DIRS2.3 资源部署——STATIC_ROOT2.2.4 媒体资源——MEDIA 三、模板配置四、数据库配置4.1 mysqlclient连接MySQL4.2 pymysql连接MySQL4.3 多个数据库的连接方式4.4 使用配置文件…

2、 家庭网络发展现状

上一篇我们讲了了解家庭网络历史(https://blog.csdn.net/xld_hung/article/details/143639618?spm1001.2014.3001.5502),感兴趣的同学可以看对应的文章&#xff0c;本章我们主要讲家庭网络发展现状。 关于家庭网络发展现状&#xff0c;我们会从国内大户型和小户型的网络说起&…

一文速学---红黑树

文章目录 一、红黑树简介二、 红黑树特性三、红黑树插入3.1 红黑树为空3.2 父节点为黑色3.3 父节点为红色3.3.1 父亲和叔叔都是红色3.3.2 父节点为红色&#xff0c;叔叔节点为黑色3.3.2.1 父节点在左节点&#xff0c;插入节点在父亲左节点3.3.2.2 父节点在左节点&#xff0c;插…

学习日记_20241117_聚类方法(高斯混合模型)

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

ISP——你可以从这里起步(二)

接上一篇&#xff0c;上一篇是原理篇&#xff0c;这一篇是实战篇&#xff0c;为了实现下面框图中的不完美ISP。 第一章 做一张RAW图自己用 不是所有的人都能获得raw图&#xff0c;即使获得了raw图也需要对应的sensor参数才能把它用起来&#xff0c;所以我找了一条野路子可以把…

shell bash---类似数组类型

0 Preface/Foreword C/C,Python&#xff0c;Java等编程语言&#xff0c;都含有数组类型&#xff0c;那么shell脚本是不是也有类似的语法呢&#xff1f; 1 类似数组类型 1.1 &#xff08;&#xff09;类似数组类型 #! /bin/bashecho "Welcome to bash world!" anim…

QT中使用图表之QChart绘制面积图

绘制面积图&#xff0c;则系列选择面积系列QAreaSeries 需要给系列设置上折线和下折线&#xff08;QLineSeries&#xff09;&#xff0c;如果没有设置下折线&#xff0c;则默认x轴为下折线 1、创建图表视图 //1、创建图表视图 QChartView * view new QChartView(this); //开…