ffmpeg封装和解封装介绍-(10)综合完成视频重编码为h265,解封装解码编码再封装

主函数逐句解析:

由于代码太多我们只解析主函数,(其他封装函数见前面文章,同时用到了解码编码封装代码)。

初始化和参数处理 

int main(int argc, char* argv[])
{
    /// 输入参数处理
    string useage = "124_test_xformat 输入文件 输出文件 开始时间(秒) 结束时间(秒) 视频宽 视频高\n";
    useage += "124_test_xformat v1080.mp4 test_out.mp4 10 20 400 300";
    cout << useage << endl;
    if (argc < 3)
    {
        return -1;
    }

    string in_file = argv[1];//输入文件参数
    string out_file = argv[2];//输出文件参数

 这段代码是程序的入口。它首先定义了程序的用法提示,并将其打印出来。然后,它检查命令行参数的数量是否正确,若不足三个参数,则程序退出。接着,它从命令行参数中获取输入文件和输出文件的路径。

截取时间参数和视频尺寸参数 

    
    /// 截取10 ~ 20 秒之间的音频视频 取多不取少
    // 假定 9 11秒有关键帧 我们取第9秒
    int begin_sec = 0;    //截取开始时间
    int end_sec = 0;      //截取结束时间
    if (argc > 3)
        begin_sec = atoi(argv[3]);
    if (argc > 4)
        end_sec = atoi(argv[4]);

    int video_width = 0;
    int video_height = 0;
    if (argc > 6)
        video_width = atoi(argv[5]);
    video_height = atoi(argv[6]);

 这段代码获取截取的开始和结束时间(以秒为单位),以及视频的宽度和高度。如果命令行参数中没有提供这些参数,则使用默认值。

解封装输入文件 

    /// 解封装
    //解封装输入上下文

    XDemux demux;
    AVFormatContext* demux_c = demux.Open(in_file.c_str());

    demux.set_c(demux_c);

    long long video_begin_pts = 0;
    long long audio_begin_pts = 0;  //音频的开始时间
    long long video_end_pts = 0;
    //开始截断秒数 算出输入视频的pts
    //if (begin_sec > 0)
    {
        //计算视频的开始和结束播放pts 
        if (demux.video_index() >= 0 && demux.video_time_base().num > 0)
        {
            double t = (double)demux.video_time_base().den / (double)demux.video_time_base().num;
            video_begin_pts = t * begin_sec;
            video_end_pts = t * end_sec;
            demux.Seek(video_begin_pts, demux.video_index()); //移动到开始帧
        }

        //计算音频的开始播放pts
        if (demux.audio_index() >= 0 && demux.audio_time_base().num > 0)
        {
            double t = (double)demux.audio_time_base().den / (double)demux.audio_time_base().num;
            audio_begin_pts = t * begin_sec;
        }
    }

这段代码创建了解封装对象 XDemux 并打开输入文件。它计算开始和结束时间对应的视频和音频PTS(Presentation Timestamps),然后将解封装器定位到视频开始时间的关键帧。 

视频解码器初始化 

    /
     视频解码器的初始化并打开解码器
    XDecode decode;
    AVCodecContext* decode_c = decode.Create(demux.video_codec_id(), false);
    //设置视频解码器参数
    demux.CopyPara(demux.video_index(), decode_c);

    decode.set_c(decode_c);
    decode.Open();
    auto frame = decode.CreateFrame(); //解码后存储
    /

这段代码初始化视频解码器 XDecode 并打开解码器。它从解封装器中复制视频参数,并为解码后的帧数据分配内存。 

视频编码器初始化 

    /
     视频编码的初始化

    if (demux.video_index() >= 0)
    {
        if (video_width <= 0)
            video_width = demux_c->streams[demux.video_index()]->codecpar->width;
        if (video_height <= 0)
            video_height = demux_c->streams[demux.video_index()]->codecpar->height;
    }
    XEncode encode;
    auto encode_c = encode.Create(AV_CODEC_ID_H265, true);
    encode_c->pix_fmt = AV_PIX_FMT_YUV420P;
    encode_c->width = video_width;
    encode_c->height = video_height;
    encode.set_c(encode_c);
    encode.Open();
    /

 这段代码初始化视频编码器 XEncode 并打开编码器。如果没有指定视频宽度和高度,则使用解封装器中的视频参数。编码器设置为 H.265 编码,像素格式为 YUV420P。

封装初始化 

    
    /// 封装
    //编码器上下文
    //const char* out_url = "test_mux.mp4";

    XMux mux;
    auto mux_c = mux.Open(out_file.c_str());
    mux.set_c(mux_c);
    auto mvs = mux_c->streams[mux.video_index()]; //视频流信息
    auto mas = mux_c->streams[mux.audio_index()]; //视频流信息

    //有视频
    if (demux.video_index() >= 0)
    {
        mvs->time_base.num = demux.video_time_base().num;
        mvs->time_base.den = demux.video_time_base().den;

        //复制视频参数
        //demux.CopyPara(demux.video_index(), mvs->codecpar);
        // 复制编码器格式
        avcodec_parameters_from_context(mvs->codecpar, encode_c);
    }
    //有音频
    if (demux.audio_index() >= 0)
    {
        mas->time_base.num = demux.audio_time_base().num;
        mas->time_base.den = demux.audio_time_base().den;
        //复制音频参数
        demux.CopyPara(demux.audio_index(), mas->codecpar);
    }

    // 写入头部,会改变timebase
    mux.WriteHead();

这段代码初始化封装器 XMux 并打开输出文件。它设置视频和音频流的时间基,并复制编码器参数到封装器。最后,它写入文件头。

为什么初始化封装器的时候复制编码器参数到封装器而不是解码器参数给封装器

在视频处理的流程中,封装器(muxer)初始化时需要复制编码器的参数,而不是解码器的参数,这是因为封装器的目的是将编码后的数据打包成一个文件格式,而不是处理原始解码后的数据。让我们具体看看原因:

  • 解码器参数:这些参数用于描述如何从压缩格式(如H.264、HEVC等)解码出原始的未压缩数据(如YUV帧)。解码器参数包括压缩格式、比特率、分辨率等,但这些参数主要用于解码过程。

  • 编码器参数:这些参数用于描述如何将原始的未压缩数据(如YUV帧)压缩成目标格式(如H.264、HEVC等)。编码器参数包括目标压缩格式、比特率、分辨率、帧率等。封装器需要这些参数来正确打包和封装编码后的数据流。

  • 封装器的任务是将已经编码的数据按照特定的容器格式(如MP4、MKV等)打包成文件。因此,它需要知道数据的编码方式(即编码器参数)来正确地打包数据流。

读取、解码、编码和写入数据

    int audio_count = 0;
    int video_count = 0;
    double total_sec = 0;
    AVPacket pkt;
    for (;;)
    {
        if (!demux.Read(&pkt))
        {
            break;
        }

        // 视频 时间大于结束时间
        if (video_end_pts > 0
            && pkt.stream_index == demux.video_index()
            && pkt.pts > video_end_pts)
        {
            av_packet_unref(&pkt);
            break;
        }

        if (pkt.stream_index == demux.video_index()) //视频
        {
            mux.RescaleTime(&pkt, video_begin_pts, demux.video_time_base());

            //解码视频
            if (decode.Send(&pkt))
            {
                while (decode.Recv(frame))
                {
                    // 修改图像尺寸 
                    //视频编码
                    AVPacket* epkt = encode.Encode(frame);
                    if (epkt)
                    {
                        epkt->stream_index = mux.video_index();
                        //写入视频帧 会清理pkt
                        mux.Write(epkt);
                        //av_packet_free(&epkt);
                    }
                }
            }

            video_count++;
            if (demux.video_time_base().den > 0)
                total_sec += pkt.duration * ((double)demux.video_time_base().num / (double)demux.video_time_base().den);
            av_packet_unref(&pkt);
        }
        else if (pkt.stream_index == demux.audio_index())
        {
            mux.RescaleTime(&pkt, audio_begin_pts, demux.audio_time_base());
            audio_count++;
            //写入音频帧 会清理pkt
            mux.Write(&pkt);
        }
        else
        {
            av_packet_unref(&pkt);
        }
    }

这段代码读取、解码和编码数据。它从输入文件中读取数据包,并根据数据包的类型进行不同的处理。如果是视频数据包,则解码后编码并写入输出文件;如果是音频数据包,则直接写入输出文件。 

写入结尾并释放资源 

    //写入结尾 包含文件偏移索引
    mux.WriteEnd();
    demux.set_c(nullptr);
    mux.set_c(nullptr);
    encode.set_c(nullptr);
    cout << "输出文件" << out_file << ":" << endl;
    cout << "视频帧:" << video_count << endl;
    cout << "音频帧:" << audio_count << endl;
    cout << "总时长:" << total_sec << endl;
    getchar();
    return 0;
}

运行结果:

以h265重新编码了原视频,生成了一段视频。

 主函数代码总览:


#include <iostream>
#include <thread>
#include "xdemux.h"
#include "xmux.h"
#include "xdecode.h"
#include "xencode.h"
#include "xvideo_view.h"
using namespace std;
extern "C"
{ 
#include <libavformat/avformat.h>
}

int main(int argc, char* argv[])
{
    /// 输入参数处理
    string useage = "124_test_xformat 输入文件 输出文件 开始时间(秒) 结束时间(秒) 视频宽 视频高\n";
    useage += "124_test_xformat v1080.mp4 test_out.mp4 10 20 400 300";
    cout << useage << endl;
    if (argc < 3)
    {
        return -1;
    }

    string in_file = argv[1];//输入文件参数
    string out_file = argv[2];//输出文件参数
    
    /// 截取10 ~ 20 秒之间的音频视频 取多不取少
    // 假定 9 11秒有关键帧 我们取第9秒
    int begin_sec = 0;    //截取开始时间
    int end_sec = 0;      //截取结束时间
    if (argc > 3)
        begin_sec = atoi(argv[3]);
    if (argc > 4)
        end_sec = atoi(argv[4]);

    int video_width = 0;
    int video_height = 0;
    if (argc > 6)
        video_width = atoi(argv[5]);
    video_height = atoi(argv[6]);


    /// 解封装
    //解封装输入上下文

    XDemux demux;
    AVFormatContext* demux_c = demux.Open(in_file.c_str());

    demux.set_c(demux_c);

    long long video_begin_pts = 0;
    long long audio_begin_pts = 0;  //音频的开始时间
    long long video_end_pts = 0;
    //开始截断秒数 算出输入视频的pts
    //if (begin_sec > 0)
    {
        //计算视频的开始和结束播放pts 
        if (demux.video_index() >= 0 && demux.video_time_base().num > 0)
        {
            double t = (double)demux.video_time_base().den / (double)demux.video_time_base().num;
            video_begin_pts = t * begin_sec;
            video_end_pts = t * end_sec;
            demux.Seek(video_begin_pts, demux.video_index()); //移动到开始帧
        }

        //计算音频的开始播放pts
        if (demux.audio_index() >= 0 && demux.audio_time_base().num > 0)
        {
            double t = (double)demux.audio_time_base().den / (double)demux.audio_time_base().num;
            audio_begin_pts = t * begin_sec;
        }

    }

    /
     视频解码器的初始化并打开解码器
    XDecode decode;
    AVCodecContext* decode_c = decode.Create(demux.video_codec_id(), false);
    //设置视频解码器参数
    demux.CopyPara(demux.video_index(), decode_c);

    decode.set_c(decode_c);
    decode.Open();
    auto frame = decode.CreateFrame(); //解码后存储
    /

    /
     视频编码的初始化

    if (demux.video_index() >= 0)
    {
        if (video_width <= 0)
            video_width = demux_c->streams[demux.video_index()]->codecpar->width;
        if (video_height <= 0)
            video_height = demux_c->streams[demux.video_index()]->codecpar->height;
    }
    XEncode encode;
    auto encode_c = encode.Create(AV_CODEC_ID_H265, true);
    encode_c->pix_fmt = AV_PIX_FMT_YUV420P;
    encode_c->width = video_width;
    encode_c->height = video_height;
    encode.set_c(encode_c);
    encode.Open();

    /


    
    /// 封装
    //编码器上下文
    //const char* out_url = "test_mux.mp4";

    XMux mux;
    auto mux_c = mux.Open(out_file.c_str());
    mux.set_c(mux_c);
    auto mvs = mux_c->streams[mux.video_index()]; //视频流信息
    auto mas = mux_c->streams[mux.audio_index()]; //视频流信息

    //有视频
    if (demux.video_index() >= 0)
    {
        mvs->time_base.num = demux.video_time_base().num;
        mvs->time_base.den = demux.video_time_base().den;

        //复制视频参数
        //demux.CopyPara(demux.video_index(), mvs->codecpar);
        // 复制编码器格式
        avcodec_parameters_from_context(mvs->codecpar, encode_c);

    }
    //有音频
    if (demux.audio_index() >= 0)
    {
        mas->time_base.num = demux.audio_time_base().num;
        mas->time_base.den = demux.audio_time_base().den;
        //复制音频参数
        demux.CopyPara(demux.audio_index(), mas->codecpar);
    }

    // 写入头部,会改变timebase
    mux.WriteHead();

    

    int audio_count = 0;
    int video_count = 0;
    double total_sec = 0;
    AVPacket pkt;
    for (;;)
    {
        if (!demux.Read(&pkt))
        {
            break;
        }

        // 视频 时间大于结束时间
        if (video_end_pts > 0
            && pkt.stream_index == demux.video_index()
            && pkt.pts > video_end_pts)
        {
            av_packet_unref(&pkt);
            break;
        }

        if (pkt.stream_index == demux.video_index()) //视频
        {
            mux.RescaleTime(&pkt, video_begin_pts, demux.video_time_base());

            //解码视频
            if (decode.Send(&pkt))
            {
                while (decode.Recv(frame))
                {
                    // 修改图像尺寸 
                    //视频编码
                    AVPacket* epkt = encode.Encode(frame);
                    if (epkt)
                    {
                        epkt->stream_index = mux.video_index();
                        //写入视频帧 会清理pkt
                        mux.Write(epkt);
                        //av_packet_free(&epkt);
                    }
                }
            }

            video_count++;
            if (demux.video_time_base().den > 0)
                total_sec += pkt.duration * ((double)demux.video_time_base().num / (double)demux.video_time_base().den);
            av_packet_unref(&pkt);
        }
        else if (pkt.stream_index == demux.audio_index())
        {
            mux.RescaleTime(&pkt, audio_begin_pts, demux.audio_time_base());
            audio_count++;
            //写入音频帧 会清理pkt
            mux.Write(&pkt);
        }
        else
        {
            av_packet_unref(&pkt);
        }
    }

    //写入结尾 包含文件偏移索引
    mux.WriteEnd();
    demux.set_c(nullptr);
    mux.set_c(nullptr);
    encode.set_c(nullptr);
    cout << "输出文件" << out_file << ":" << endl;
    cout << "视频帧:" << video_count << endl;
    cout << "音频帧:" << audio_count << endl;
    cout << "总时长:" << total_sec << endl;
    getchar();

    return 0;
}

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

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

相关文章

1.华为路由器-三层交换机-二层交换机组网连接

AR1配置GE 0/0/0接口IP [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0] [Huawei-GigabitEthernet0/0/0]ip add 1.1.1.1 24 [Huawei]iP route-static 192.168.0.0 16 1.1.1.2三层交换机配置如下 创建vlan [Huawei]vlan batch 10 20配置接口ip [Huawei]int g0/0/1 [Huawei…

让AI 赋予人类超强的记忆力

遗忘曲线告诉我们&#xff0c;绝大部分新掌握的知识约在一周后被遗忘&#xff0c;一个月左右基本忘光。「好记性不如一个烂笔头」&#xff0c;借助AI还真能做出这样「烂笔头」。 提升个人的记忆力-个人搜索引擎 个人搜索引擎的想法是一个强大而诱人的想法。如果有一个应用程序可…

你的iPhone安全吗?想要保护个人隐私一定要这么做

在这个数字化时代&#xff0c;个人隐私安全显得尤为重要&#xff0c;尤其是对于那些依赖智能手机处理日常事务的用户而言。作为市场上最受欢迎的智能手机之一&#xff0c;iPhone的安全性备受关注&#xff0c;但即便如此&#xff0c;它也可能成为黑客攻击和非法监控的目标。如何…

慎投!新增1本中科院1区顶刊被“On Hold”

本周投稿推荐 SSCI • 中科院2区&#xff0c;6.0-7.0&#xff08;录用友好&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&#xff09; SCI&EI • 4区生物医学类&#xff0c;0.5-1.0&#xff08;录用…

CC1310 LaunchPad开发板底噪测试

测试射频底噪时&#xff0c;主要关注的是在无信号输入时&#xff0c;系统或器件产生的最小噪声功率。这通常涉及到使用频谱分析仪&#xff08;频谱仪&#xff09;来测量输出噪声功率谱密度。以下是进行射频底噪测试的几种方法&#xff1a; 使用频谱仪直接测量&#xff1a; 通过…

做LLM推理时,常见的显卡如何选择?

随着开源LLM越来越成熟&#xff0c;业务接入LLM推理也成为必然&#xff0c;如何选模型大小和显卡&#xff0c;主要看下面这些。 一、选GPU显卡 在选择显卡进行大型语言模型推理时&#xff0c;主要要看下面几个指标&#xff1a; 1、 VRAM&#xff08;视频随机存取存储器&…

Docker部署Nginx下载站点服务

1、下载镜像 由于docker官方镜像站点被封了&#xff0c;所以我把镜像上传到阿里云镜像仓库了 docker pull registry.cn-hangzhou.aliyuncs.com/qinzt-tools/file-nginx:1.18.02、运行容器实例 运行变量解释&#xff1a; 变量名称默认值解释USERhyadmin访问下载站点的认证用…

Typora—适用于 Mac 和 Win 系统的优秀 Markdown 文本编辑器

Typora 是一款适用于 Mac 和 Win 系统的优秀 Markdown 文本编辑器&#xff0c;它以其简洁易用的界面和强大的功能受到了众多用户的喜爱。 首先&#xff0c;Typora 的界面设计非常简洁直观&#xff0c;没有过多繁杂的菜单和按钮&#xff0c;让用户能够专注于写作本身。它采用实时…

C#结合JS 修改解决 KindEditor 弹出层问题

目录 问题现象 原因分析 范例运行环境 解决问题 修改 kindeditor.js C# 服务端更新 小结 问题现象 KindEditor 是一款出色的富文本HTML在线编辑器&#xff0c;关于编辑器的详细介绍可参考我的文章《C# 将 TextBox 绑定为 KindEditor 富文本》&#xff0c;这里我们讲述在…

如何利用被动DNS(Passive DNS)加强网络安全

通过收集和分析被动DNS数据&#xff0c;可以帮助识别恶意站点&#xff0c;打击钓鱼和恶意软件&#xff0c;本文将介绍如何利用被动DNS&#xff08;Passive DNS&#xff09;加强网络安全。 在过去的一些年里&#xff0c;我们目睹了对DNS基础设施的攻击日益增多&#xff1a;对权…

【嵌入式】CAN总线详解

【嵌入式】CAN总线详解 一、CAN总线简介 CAN总线是一种控制器局域网总线&#xff0c;每一个挂载在CAN局域网的设备&#xff0c;都可以利用CAN去发送信息&#xff0c;也可以接收局域网的各种信息&#xff0c;每个设备都是平等的&#xff0c;共享CAN的资源。广泛应用于汽车、嵌…

101.qt qml-自定义日历控件2-附带动画效果

黑色风格截图如下所示: 白色风格如下所示: GIF效果如下所示: 1.控件使用介绍 QianWindow2.5版本及以上提供,源码位于:qrc:/common/qmlQianDateTime/QianCalendarInputField.qml QianWindow2.5版本及以上提供,示例使用代码位于:qrc:/pages/QianControlPages/QianDateTimeP…

金鸣识别:图片转excel的“黑科技”神器

近期&#xff0c;我意外发现了一个令人惊艳的工具——金鸣表格文字识别系统。起初&#xff0c;我只是出于好奇尝试了一下&#xff0c;但使用体验远远超出了我的预期&#xff0c;让我深感其价值。 在日常生活和工作中&#xff0c;我们经常需要从各类图片中提取文字信息&#xf…

express+vue在线im实现【一】

在线体验地址 需要用邮箱注册一个账号 在线链接 目前实现的功能 1、在线聊天(群聊) 2、实时监控成员状态 3、历史聊天&#xff0c;下拉加载 4、有新消息&#xff0c;自动滚动到最新消息&#xff0c;如果自己在查看历史记录&#xff0c;不会强行滚动 后续计划新增功能 感兴…

Java健身私教服务师傅小程序APP源码(APP+小程序+公众号+H5)

私人定制的健身之旅 &#x1f3cb;️ 引言&#xff1a;探索私人健身新纪元 在现代都市的快节奏生活中&#xff0c;越来越多的人开始注重身体健康和健身塑形。然而&#xff0c;传统的健身房模式可能无法满足每个人的个性化需求。这时&#xff0c;一款名为“健身私教服务师傅”的…

Spring IoC【控制反转】DI【依赖注入】

文章目录 控制反转&#xff08;IoC&#xff09;依赖注入&#xff08;DI&#xff09;IoC原理及解耦IoC 容器的两种实现BeanFactoryApplicationContext IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&…

centos7.9部署k8s的几种方式

文章目录 一、常见的k8s部署方式1、使用kubeadm工具部署2、基于二进制文件的部署方式3、云服务提供商的托管 Kubernetes 服务4、使用容器镜像部署或自动化部署工具 二、使用kubeadm工具部署1、硬件准备&#xff08;虚拟主机&#xff09;2、环境准备2.1、所有机器关闭防火墙2.2、…

Cisco Catalyst 9800 wireless Controller配置操作指引

一、控制器基本信息 外立面信息&#xff1a; 硬件规格如下&#xff1a; 序号 硬件规格满配能力1业务端口 4个1G/10G光口 2 冗余端口 1个GE电口或1G光口 3 最大管理AP数量 20004 最大接入客户端数量 320005 最大WLAN数量(SSID) 40966电源模块数量 2 7 最大吞吐量 40 …

云计算 | (四)基本云安全

文章目录 📚基本云安全🐇云安全背景🐇基本术语和概念⭐️风险(risk)⭐️安全需求🐇威胁作用者⭐️威胁作用者(threat agent)⭐️匿名攻击者(anonymous attacker)⭐️恶意服务作用者(malicious service agent)⭐️授信的攻击者(trusted attacker)⭐️恶意的内部人员(mal…

Neo4j Desktop界面认识以及数据库备份与还原

Neo4j Desktop界面认识以及数据库备份与还原 neo4j 版本信息&#xff1a;Neo4j Desktop Version 1.5.9&#xff1b;neo4j 5.12.0 系统信息&#xff1a;windows 11 Neo4j Desktop 界面 每个 Project 下可以有多个 DBMS&#xff0c;而每个 DBMS 中默认有 system 和 neo4j (def…