七. 封装解封装

1. MP4格式分析

参考MP4格式分析

2. 相关API及数据结构

//打开要读取的文件,获得上下文
int avformat_open_input(
	AVFormatContext **ps, 			//上下文
	const char *url, 				//文件路径
	ff_const59 AVInputFormat *fmt, 	//输入的格式
	AVDictionary **options			//选项
);

//获取ic的信息
int avformat_find_stream_info(
	AVFormatContext *ic, 		//上下文
	AVDictionary **options		//选项
);

//从ic读取数据到packet
int av_read_frame(
	AVFormatContext *ic, 
	AVPacket *pkt
);
1. pkt本身的空间函数不维护,但每调用一次该函数,就会申请一次buffer空间,因此在每次调用完成后需要清理pkt的buffer空间的引用计数
2. pkt->pts/dts在无B帧时是相同的,若有B帧,因为后解码但却可能先播放,会导致二者不同
3. 但当读取的是H264数据时,其内部并未封装头信息,因此无法帧索引无法随意拖动进度条,因此pts、dts也不存在。但与AVstream->time_base时间基相关可以自己设置。

//pts转换:根据原来的时间基和新的时间基,将原来的pts转为新的pts
int64_t av_rescale_q_rnd(
	int64_t a, 			//pts
	AVRational bq, 		//原来的时间基
	AVRational cq, 		//现在的时间基
	enum AVRounding rnd	
);
实际上就是 a * bq / cq, 返回运算的结果
rnd:
AV_ROUND_ZERO/INF/DOWN/UP/NEAR_INF四舍五入

struct AVFormatContext
{
	unsigned int nb_streams;	//streams的数量
	AVStream **streams;			//数据流的配置信息
	void *priv_data;			//AVOptions
	AVIOContext *pb;			//I/O
	int64_t duration;			
	int64_t bit_rate;
	char *url;			
}

struct AVStream
{
	int index;					//流的索引号
	AVRational time_base;		//时间基
	AVRational avg_frame_rate;	
	AVCodecParameters *codecpar;//配置信息
}

struct AVCodecParameters
{
	AVMediaType codec_type;
	AVCodecID codec_id;
	int format;
	int64_t bit_rate;
	int width;
	int height;

	uint64_t channel_layout;
	int channels;
	int sample_rate;
	int frame_size;
}

option_table.h中含有AVOption avformat_options结构体的定义

3. 对视频进行解封装

  1. 打开文件
  2. 获取视频流信息
  3. 输出视频信息
  4. 读取每一帧的数据
  5. 对每一帧数据进行解码
  6. 对解码的每一帧数据进行渲染
#include <iostream>
#include "xvideo_view.h"
#include "xdecode.h"
#include "xencode.h"
extern "C"
{
#include <libavformat/avformat.h>
}

#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
//#pragma comment(lib, "avformat.lib")

using namespace std;

int main()
{
	//1.打开文件
	AVFormatContext* fmt_ctx = NULL;
	const char* url = "v1080.mp4";
	avformat_open_input(&fmt_ctx, url, NULL, NULL);
	//2.获取数据流到上下文
	avformat_find_stream_info(fmt_ctx, NULL);
	//3.输出文件的数据流信息
	av_dump_format(fmt_ctx, 0, url, 0);
	//获取视频流数据
	AVStream* vs = NULL;
	for (int i = 0; i < fmt_ctx->nb_streams; i++)
	{
		if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			vs = fmt_ctx->streams[i];
		}
		break;
	}
	//4.读取每帧视频数据
	//打开编码器
	auto decoder = new XDecode();
	auto decoder_ctx = decoder->Create(vs->codecpar->codec_id, NULL);
	avcodec_parameters_to_context(decoder_ctx, vs->codecpar);	//将编码器参数传给文件上下文
	decoder->set_c(decoder_ctx);
	decoder->Open();
	auto frame = decoder->CreateFrame();
	AVPacket packet;
	//打开渲染器
	auto view = XVideoView::Create();
	view->Init(vs->codecpar->width, vs->codecpar->height, (XVideoView::Format)vs->codecpar->format);
	while (true)
	{
		av_read_frame(fmt_ctx, &packet);
		//5.对每帧数据解码
		decoder->Send(&packet);
		decoder->Recv(frame);
		//6.渲染解码后的数据
		view->DrawFrame(frame);
		
		av_packet_unref(&packet);
	}

	//释放资源
	av_frame_free(&frame);
	delete decoder;
	avformat_close_input(&fmt_ctx);
}

4. 封装

  1. 创建封装文件的格式上下文
  2. 创建封装的流
  3. 打开文件
  4. 设置封装流的封装编码等参数
申请AVFormatContext上下文
int avformat_alloc_output_context2(
	AVFormatContext **ctx, 
	ff_const59 AVOutputFormat *oformat,
    const char *format_name, 
    const char *filename
);

创建要封装的数据流
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

打开或者关闭文件
int avio_open(AVIOContext **s, const char *url, int flags);
int avio_closep(AVIOContext **s);

编码器参数拷贝
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);

写入数据到文件
int avformat_write_header(AVFormatContext *s, AVDictionary **options);	写头部信息
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);	写具体数据信息
int av_write_trailer(AVFormatContext *s);	写尾部信息
#include <iostream>
#include "xvideo_view.h"
#include "xdecode.h"
#include "xencode.h"
extern "C"
{
#include <libavformat/avformat.h>
}

#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
//#pragma comment(lib, "avformat.lib")

using namespace std;

int main()
{
	//1.打开文件
	AVFormatContext* fmt_ctx = NULL;
	const char* url = "v1080.mp4";
	avformat_open_input(&fmt_ctx, url, NULL, NULL);
	//2.获取数据流到上下文
	avformat_find_stream_info(fmt_ctx, NULL);
	//3.输出文件的数据流信息
	av_dump_format(fmt_ctx, 0, url, 0);
	//获取视频流数据
	AVStream* vs = NULL;
	AVStream* as = NULL;
	for (int i = 0; i < fmt_ctx->nb_streams; i++)
	{
		if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			vs = fmt_ctx->streams[i];
		}
		else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			as = fmt_ctx->streams[i];
		}
	}

	//创建输出文件上下文
	const char* out_url = "out.mp4";
	AVFormatContext* out_fmt_ctx = NULL;
	avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, out_url);
	//创建输出的数据流
	auto v_s = avformat_new_stream(out_fmt_ctx, NULL);
	auto a_s = avformat_new_stream(out_fmt_ctx, NULL);
	//打开输出文件
	avio_open(&out_fmt_ctx->pb, out_url, AVIO_FLAG_WRITE);
	//设置视频流参数
	if (v_s)
	{
		v_s->time_base = vs->time_base;
		avcodec_parameters_copy(v_s->codecpar, vs->codecpar);
	}
	if (a_s)
	{
		a_s->time_base = as->time_base;
		avcodec_parameters_copy(a_s->codecpar, as->codecpar);
	}
	
	
	//写入头部信息
	avformat_write_header(out_fmt_ctx, NULL);
	//av_dump_format(out_fmt_ctx, 0, out_url, 1);
	//4.读取每帧视频数据
	AVPacket packet;
	while (true)
	{
		//cout << "test" << endl;
		auto ret = av_read_frame(fmt_ctx, &packet);
		if (ret != 0)
		{
			break;
		}
		//写入数据
		av_interleaved_write_frame(out_fmt_ctx, &packet);

		//av_packet_unref(&packet);
	}

	//写入尾部信息
	av_write_trailer(out_fmt_ctx);
	//释放资源
	avformat_close_input(&fmt_ctx);
	avio_closep(&out_fmt_ctx->pb);
	avformat_close_input(&out_fmt_ctx);
}

截取10-20之间的10s视频

#include <iostream>
#include "xvideo_view.h"
#include "xdecode.h"
#include "xencode.h"
extern "C"
{
#include <libavformat/avformat.h>
}

#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
//#pragma comment(lib, "avformat.lib")

using namespace std;

/*解封装视频、截取10s后封装成新的视频*/

int main()
{
	int ret = -1;
	/*解封装视频*/
	
	//打开输入文件并打印信息
	AVFormatContext* in_fmt_ctx = NULL;
	const char* in_url = "v1080.mp4";
	avformat_open_input(&in_fmt_ctx, in_url, NULL, NULL);
	avformat_find_stream_info(in_fmt_ctx, NULL);
	av_dump_format(in_fmt_ctx, 0, in_url, 0);

	//打开输出文件,创建流
	AVFormatContext* out_fmt_ctx = NULL;	
	const char* out_url = "out.mp4";
	avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, out_url);
	AVStream *out_vs = avformat_new_stream(out_fmt_ctx, NULL);
	AVStream* out_as = avformat_new_stream(out_fmt_ctx, NULL);
	avio_open(&out_fmt_ctx->pb, out_url, AVIO_FLAG_WRITE);

	//设置输出文件的流参数
	AVStream* in_vs = NULL;
	AVStream* in_as = NULL;
	for (int i = 0; i < in_fmt_ctx->nb_streams; i++)
	{
		if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			in_vs = in_fmt_ctx->streams[i];
			out_vs->time_base = in_vs->time_base;
			avcodec_parameters_copy(out_vs->codecpar, in_vs->codecpar);
		}
		else if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			in_as = in_fmt_ctx->streams[i];
			out_as->time_base = in_as->time_base;
			avcodec_parameters_copy(out_as->codecpar, in_as->codecpar);
		}
	}
	avformat_write_header(out_fmt_ctx, NULL);	//写入头信息
	av_dump_format(out_fmt_ctx, 0, out_url, 1);
	//设置截取的时间
	double s_t = 10.0, e_t = 20.0;
	auto in_vs_tmp = (double)in_vs->time_base.den / (double)in_vs->time_base.num;	//时间间隔
	auto in_as_tmp = (double)in_as->time_base.den / (double)in_as->time_base.num;

	auto s_pts = s_t * in_vs_tmp;	//截取的pts
	auto a_s_pts = s_t * in_as_tmp;
	auto e_pts = e_t * in_vs_tmp;
	
	/*截取视频后封装视频*/
	av_seek_frame(in_fmt_ctx, in_vs->index, s_pts, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);	//跳到截取位置
	//av_seek_frame(in_fmt_ctx, in_as->index, s_pts, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
	
	AVPacket packet;
	while (true)
	{
		//读取数据并截取
		ret = av_read_frame(in_fmt_ctx, &packet);
		if (ret != 0)
		{
			break;
		}
		
		//修改新视频的pts、dts、duration后写入文件
		if (packet.stream_index == in_vs->index)
		{
			if (packet.pts >= e_pts)	//截取结束
			{
				av_packet_unref(&packet);
				break;
			}
			cout << "vs_pkt_pts" << packet.pts << endl;
			packet.pts = av_rescale_q_rnd(packet.pts - s_pts, in_vs->time_base, out_vs->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
			packet.dts = av_rescale_q_rnd(packet.dts - s_pts, in_vs->time_base, out_vs->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
			packet.duration = av_rescale_q(packet.duration, in_vs->time_base, out_vs->time_base);
		}
		else if (packet.stream_index == in_as->index)
		{
			cout << "as_pkt_pts" << packet.pts << endl;
			packet.pts = av_rescale_q_rnd(packet.pts - a_s_pts, in_as->time_base, out_as->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
			packet.dts = av_rescale_q_rnd(packet.dts - a_s_pts, in_as->time_base, out_as->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
			packet.duration = av_rescale_q(packet.duration, in_as->time_base, out_as->time_base);
		}
		packet.pos = -1;
		av_interleaved_write_frame(out_fmt_ctx, &packet);
	}

	av_write_trailer(out_fmt_ctx);

	avio_closep(&out_fmt_ctx->pb);
	avformat_close_input(&in_fmt_ctx);
	avformat_free_context(out_fmt_ctx);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值