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()); //移动到开始帧}//计算音频的开始播放ptsif (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);}// 写入头部,会改变timebasemux.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();//写入视频帧 会清理pktmux.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++;//写入音频帧 会清理pktmux.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()); //移动到开始帧}//计算音频的开始播放ptsif (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);}// 写入头部,会改变timebasemux.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();//写入视频帧 会清理pktmux.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++;//写入音频帧 会清理pktmux.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.mzph.cn/diannao/28647.shtml

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

相关文章

【计算机网络】已解决:“‘ping‘ 不是内部或外部命令,也不是可运行的程序或批处理文件”报错

文章目录 一、问题分析背景二、可能出错的原因三、错误代码示例四、正确解决方法与示例五、注意事项 已解决“‘ping’ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件”报错 一、问题分析背景 在Windows操作系统中&#xff0c;ping 命令是一个常用的网络诊断…

线程池ThreadPoolExecutor使用指南

线程池ThreadPoolExecutor使用指南 &#x1f9d0;使用线程池的好处是什么&#xff1f; 统一管理&#xff0c;减少资源获取创建的开销&#xff0c;提高利用率。 &#x1f527;线程池的参数 ​ThreadPoolExecutor​ 3 个最重要的参数&#xff1a; ​corePoolSize​ : 任务队列…

docker login 报错: http: server gave HTTP response to HTTPS client

环境&#xff1a; 自建 Harbor、Docker 1. 问题分析 # 命令&#xff0c;这里用的是 IP&#xff0c;可以为域名 docker login -u test 172.16.51.182:31120 # 输入密码 Password:# 报错如下&#xff1a; Error response from daemon: Get "https://172.16.51.182:31120/…

[Algorithm][贪心][增减字符串匹配][分发饼干][最优除法][跳跃游戏Ⅱ][跳跃游戏]详细讲解

目录 1.增减字符串匹配1.题目链接2.算法原理详解3.代码实现 2.分发饼干1.题目链接2.算法原理详解3.代码实现 3.最优除法1.题目链接2.算法原理详解3.代码实现 4.跳跃游戏 II1.题目链接2.算法原理详解3.代码实现 5.跳跃游戏1.题目链接2.算法原理详解3.代码实现 1.增减字符串匹配 …

期末复习6--链表头插法(逆序)尾插法(顺序)---输出链表

头插法 #include <stdio.h> #include <stdlib.h>struct Node //定义结构体 {char data; //数据域struct Node * next; //指针域 };/* 请在这里填写答案 */void PrintList (struct Node * head) {struct Node * s;if(head NULL){printf("None&qu…

Apipost模拟HTTP客户端

模拟HTTP客户端的软件有很多&#xff0c;其中比较著名的就有API-FOX、POSTMAN。 相信很多小伙伴都使用POSTMAN。这篇博客主要介绍Apipost的原因是&#xff0c;Apipost无需下载&#xff0c;具有网页版。 APIFOX的站内下载&#xff1a; Api-Fox&#xff0c;类似于PostMan的软件…

JavaFX 节点

JavaFX Node类javafx.scene.Node是添加到JavaFX 场景图的所有组件 的基类&#xff08;超类&#xff09; 。JavaFX Node 类是抽象的&#xff0c;因此你只需将 Node 类的子类添加到场景图中。场景图中的所有 JavaFX Node 实例共享一组由 JavaFX Node 类定义的公共属性。本 JavaFX…

一文了解Redis

一.什么是Redis 与MySQL一样&#xff0c;Redis也是客户端服务器结构的程序&#xff0c;是基于内存的键值对存储系统&#xff0c;属于NoSQL的一种。与很多键值对数据库不同的是&#xff0c;Redis 中的值可以是由 string&#xff08;字符串&#xff09;、hash&#xff08;哈希&a…

【算法专题--链表】删除排序链表中的重复元素II -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐ 双指针 -- 采用 哨兵位头节点 &#x1f95d; 什么是哨兵位头节点&#xff1f; &#x1f34d; 解题思路 &#x1f34d; 案例图解 四、总结与提炼 五、共勉 一、前言 删除排序链表中的重复元素II元素这道题&#xff0c…

【JKI SMO】框架讲解(二)

JKI State Machine 讲解 将JKI State Machine 模板拖曳到程序框图中&#xff0c; 如下图&#xff0c; 此模板会默认放置一个OK按钮在前面板中&#xff0c;用于提示用户如何增加一个简单的用户事件去使用此框架。 “Event Structure”&#xff0c;Idle&#xff1a;此分支可以设…

【JS重点17】原型链(面试重点)

一&#xff1a;原型链底层原理 以下面一段代码为例&#xff0c;基于原型对象&#xff08;Star构造函数的原型对象&#xff09;的继承使得不同构造函数的原型对象关联在一起&#xff08;此处是最大的构造函数Object原型对象&#xff09;&#xff0c;并且这种关联的关系是一种链…

C#联合Halcon机器视觉框架源码—升级版

相较于之前的NxtVision&#xff0c;本软件代码架构更加合理&#xff0c;且新增ui设计器、原来的vb脚本改为C#脚本&#xff0c;并尝试将视觉与运动控制相结合&#xff0c;是一体化的框架。 对源码有需求的&#xff0c;订阅本专栏后&#xff0c;私信我领取。

活动集锦 | 英码科技积极参与行业盛会,AI赋能城市数字化转型

在当今数字经济时代&#xff0c;城市全域数字化转型已经成为提升城市管理效能、优化资源配置、推动经济发展的重要手段。英码科技始终致力于为企业打造高效、低成本的行业应用方案&#xff0c;助力企业实现数字化转型。近日&#xff0c;英码科技受邀参加了多场行业展示活动&…

操作系统复习-线程同步

互斥量 两个线程的指令交叉执行互斥量可以保证先后执行称为原子性 原子性是指一系列操作不可被中断的特性这一系列操作要么全部执行完成&#xff0c;要么全部没有执行不存在部分执行部分未执行的情况 互斥锁 互斥量是最简单的线程同步的方法互斥锁&#xff0c;处于两态之一的…

01 飞行器设计 —— 一门独立的学科

01 飞行器设计 —— 一门独立的学科 01 引言02 飞机设计概述2-1 什么是飞机设计&#xff1f;2-1 飞机设计是从哪里开始的&#xff1f;2-2 如何成为一名飞机设计师&#xff1f;2-4 本书的组织 参考文献 说明&#xff1a;关于Raymer的《Aircraft Design》的读书笔记&#xff1b; …

解读ROS功能包模块的步骤

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言解读ROS功能包模块的步骤前言 认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长! 推荐开发经验及方法博客专栏: [https:/…

哇塞,超好吃的麻辣片,一口就爱上

最近&#xff0c;我发现了一款让人欲罢不能的美食——食家巷麻辣片&#xff01;&#x1f60d; 一打开包装&#xff0c;那浓郁的麻辣香气就扑鼻而来&#xff0c;瞬间刺激着我的嗅觉神经。&#x1f603;食家巷麻辣片的外观色泽鲜艳&#xff0c;红通通的一片&#xff0c;看着就特…

Android断点续传原理及实现

常见两种网络请求方式 一、 HttpURLConnection HttpURLConnection的setRequestProperty()方法&#xff0c;对我们要读取的字节部分进行控制&#xff0c;比如: 1.Range0-100代表只读取前100个字节。 2.Range100-500代表读取从第100个字节开始&#xff0c;读到第500个字节为止。…

常见的宽基指数基金

指数基金投资指南 ❝ 这篇博客里面的内容主要来自于银行螺丝钉的《定投十年&#xff0c;财务自由》和《指数基金投资指南》这两本书中章“常见的宽基指数”&#xff0c;最近第三次读这本书&#xff0c;打算做一点笔记加深自己的印象。 博客中很多内容是从书中摘抄的&#xff0c…

【git使用三】git工作机制与命令用法

目录 git工作机制和相关概念 四个重要区域 分支的概念 上传代码到远程分支的基本流程 克隆代码 仓库同步 开发者如何提交代码到远程仓库分支 1.初始化本地仓库 2.关联本地仓库和远程仓库 创建关联 查看关联情况 如何解除关联 3.推送代码到远程仓库 3.1先下拉远程…