主函数逐句解析:
由于代码太多我们只解析主函数,(其他封装函数见前面文章,同时用到了解码编码封装代码)。
初始化和参数处理
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;
}