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

news2025/1/15 23:44:43

主函数逐句解析:

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

初始化和参数处理 

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.coloradmin.cn/o/1827347.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Vulnhub]Wintermute LFI+SMTP+Screen+Structv2-RCE+Lxc逃逸

概要 靶机 192.168.8.104 信息收集 $ nmap 192.168.8.103 --min-rate 1000 -sC -sV 结果: Starting Nmap 7.92 ( https://nmap.org ) at 2024-06-15 05:54 EDT Nmap scan report for 192.168.8.103 (192.168.8.103) Host is up (0.035s latency). Not shown: 997 closed t…

docker 简单在线安装教程

1、配置阿里镜像源 wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo 2、指定版本安装docker 本次制定安装 docker 服务版本、客户端版本都为&#xff1a; 19.03.14-3.el7 yum -y install docker-ce-19.03.14-3.e…

动态规划的递归写法和递推写法详解

目录 动态规划的概念 动态规划的递归写法 动态规划的递推写法 动态规划的概念 动态规划是一种用来解决一类最优化问题的算法思想。简单来说&#xff0c;动态规划将一个复杂的问题分解成若干个子问题&#xff0c;通过综合子问题的最优解来得到原问题的最优解。需要注意的是&…

论文阅读笔记:Towards Higher Ranks via Adversarial Weight Pruning

论文阅读笔记&#xff1a;Towards Higher Ranks via Adversarial Weight Pruning 1 背景2 创新点3 方法4 模块4.1 问题表述4.2 分析高稀疏度下的权重剪枝4.3 通过SVD进行低秩逼近4.4 保持秩的对抗优化4.5 渐进式剪枝框架 5 效果5.1 和SOTA方法对比5.2 消融实验5.3 开销分析 6 结…

秋招突击——6/14——复习{(树形DP)树的最长路径,(单调队列优化DP)——最大子序和}——新作{四数之和}

文章目录 引言复习树形DP——树的最长路径实现代码答疑 单调队列优化DP——最大子序和个人实现思路参考思路分析实现代码 无重复最长字串思路分析实现代码 新作四数之和实现思路需要注意的问题 参考代码分析思路实现代码 总结 引言 今天好好看看树的最长的路径&#xff0c;自己…

《Deep learning practice》learning notes

学习笔记&#xff1a; 【公开课】旷视x北大《深度学习实践》&#xff08;28课时全&#xff09; R Talk | 旷视科技目标检测概述&#xff1a;Beyond RetinaNet and Mask R-CNN 文章目录 Lecture 1: Introduction to Computer Vision and Deep Learning&#xff08;孙剑&#x…

apt-get update和apt-get upgrade的区别

apt-get update apt-get update 命令用于更新本地软件包列表。具体来说&#xff0c;做了以下事情&#xff1a; ①从 /etc/apt/sources.list 文件和 /etc/apt/sources.list.d/ 目录下的所有文件中读取软件源配置。 ②连接到这些软件源&#xff0c;并下载最新的软件包列表。 ③将…

1832javaERP管理系统之车间计划管理Myeclipse开发mysql数据库servlet结构java编程计算机网页项目

一、源码特点 java erp管理系统之车间计划管理是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助采用了serlvet设计&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Mye…

欧洲杯赛况@20240615

点击标题下「蓝色微信名」可快速关注 欧洲杯首战&#xff0c;德国5:1狂胜苏格兰&#xff0c;大比分、红点套餐、超新星登场进球&#xff0c;好像这些能想到的元素都发挥了作用&#xff0c;作为东道主&#xff0c;聚集了天时地利人和&#xff0c;可以说是完美&#xff0c;这就是…

【python】Sklearn—Cluster

参考学习来自 10种聚类算法的完整python操作示例 文章目录 聚类数据集亲和力传播——AffinityPropagation聚合聚类——AgglomerationClusteringBIRCH——Birch&#xff08;✔&#xff09;DBSCAN——DBSCANK均值——KMeansMini-Batch K-均值——MiniBatchKMeans均值漂移聚类——…

21. 第21章 算法分析

21. 算法分析 这个附录选自OReilly Media出版的Alen B.Downey的Think Complexity(2012)一书. 当你读完本书之后, 可能会像继续读读那本书.算法分析是计算机科学的一个分支, 研究算法的性能, 尤其是他们的运行时间和空间需求. 参见http://en.wikipedia.org/wiki/Analysis_of_al…

Mac M3 Pro安装Hadoop-3.3.6

1、下载Hadoop安装包 可以到官方网站下载&#xff0c;也可以使用网盘下载 官网下载地址&#xff1a;Hadoop官网下载地址 网盘地址&#xff1a;https://pan.baidu.com/s/1p4BXq2mvby2B76lmpiEjnA?pwdr62r提取码: r62r 2、解压并添加环境变量 # 将安装包移动到指定目录 mv …

回归预测 | Matlab实现GWO-ESN基于灰狼算法优化回声状态网络的多输入单输出回归预测

回归预测 | Matlab实现GWO-ESN基于灰狼算法优化回声状态网络的多输入单输出回归预测 目录 回归预测 | Matlab实现GWO-ESN基于灰狼算法优化回声状态网络的多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现GWO-ESN基于灰狼算法优化回声状态…

学习前端第一关

作者&#xff1a;周棋洛 文章目录 前言超文本标记语言&#xff1f;编辑器推荐手刃一个网页&#xff0c;Hello World&#xff01;网页骨架讲解标签学习HTML标题HTML段落HTML换行HTML居中HTML水平线HTML格式化HTML注释HTML链接 前言 网站并不是多么复杂的技术&#xff0c;今天这…

leetcode LRU 缓存

leetcode: LRU 缓存 LRU 全称为 Least Recently Used&#xff0c;最近最少使用&#xff0c;常常用于缓存机制&#xff0c;比如 cpu 的 cache 缓存&#xff0c;使用了 LRU 算法。LRU 用于缓存机制时&#xff0c;关键的是当缓存满的时候有新数据需要加载到缓存的&#xff0c;这个…

HTML5的未来:掌握最新技术,打造炫酷网页体验

引言 随着互联网技术的飞速发展&#xff0c;HTML5已经成为构建现代网页和应用的核心技术之一。HTML5不仅提供了丰富的语义化标签&#xff0c;还引入了多项前沿技术&#xff0c;使得网页体验更加丰富多彩。本文将探讨HTML5的最新技术&#xff0c;并结合行业实践&#xff0c;提供…

智能体(Agent)实战——从gpts到auto gen

一.GPTs 智能体以大模型作为大脑&#xff0c;同时配备技能&#xff0c;使其能够完成具体的任务。同时&#xff0c;为了应用于垂直领域&#xff0c;我们需要为大模型定义一个角色&#xff0c;并构建知识库。最后&#xff0c;定义完整的流程&#xff0c;使其完成整个任务。以组会…

GenICam标准(五)

系列文章目录 GenICam标准&#xff08;一&#xff09; GenICam标准&#xff08;二&#xff09; GenICam标准&#xff08;三&#xff09; GenICam标准&#xff08;四&#xff09; GenICam标准&#xff08;五&#xff09; GenICam标准&#xff08;六&#xff09; 文章目录 系列文…

什么是浏览器指纹

在数字互联网时代&#xff0c;我们的在线活动几乎都会留下痕迹。其中&#xff0c;浏览器指纹就像我们的数字身份证&#xff0c;让网站能够识别和追踪用户。本文将详细介绍浏览器指纹是什么&#xff0c;它如何工作。 一、什么是浏览器指纹 浏览器指纹&#xff08;Browser Fing…

【odoo】odoo.conf文件配置

概要 odoo.conf 文件是 Odoo 服务器的配置文件&#xff0c;它用于定义和管理 Odoo 运行时的各种参数。这个文件包含了许多配置选项&#xff0c;可以帮助管理员根据特定的需求和环境来调整 Odoo 服务器的行为。 主要功能 数据库连接设置&#xff1a;定义 Odoo 连接到 PostgreSQL…