【QT项目——视频播放器——解封装】4.1-4.4

1、解封装(解释文件格式)

第一步注册函数

av_register_all(): 注册所有的封装格式:加封装格式和解封装格式
也可以单独注册mp4或者其他格式

第二步打开封装

在open之前要调用注册函数(调用一次就行),否则解封装文件会失败
avformat_network_init(): 初始化网络模块,该模块通过网络直接解封装rtsp(监控摄像机、或者支持rtsp协议的摄像机),http(将视频放在web服务器里,传http地址同样可以解封装)

为什么有这些函数?要先初始化这些?
因为ffmpeg是用c语言写的,没有类,需要统一先注册好,后面就不需要再调用。
C++可以有构造函数、初始化来完成操作。

avformat_open_input(…) 打开一个输入的封装器,(文件), 并且解析出文件格式,比如音频流、视频流、音频流和视频流参数、还有视频帧的索引

int avformat_open_input

在调用前需要确保av_register_all avformat_network_init已调用

参数如下:
AVFormatContext **ps: ——格式化的上下文,**ps表示指针的指针,其实就是指针的地址,这样才能改变指针的指向
第一种情况:可以存一个null值,存AVFormatContext的一个指针, 指针值为null,将这个指针的地址传给ps,传了一个指针地址,指针指向空的空间,在内部会创建空间;
第二种情况:直接创建AVFormatContext结构体 ,创建好再传进来,会把解封装的内容写到创建好的结构体里。但是清理可能会出问题。。因为动态链接库创建的空间只能在动态空间来清理,外部空间在动态空间中清理会出问题,因为他们的内存是分割开的。
因此,如果是自己创建的空间要自己清理,如果传的是AVFormatContext的指针,指针本身指向一个空,那么空间直接用close来清理就行。

const char *url: 地址,网络打开的话支持rtsp、http以及本地文件,地址打开,会把地址存到AVFormatContext 中的filename成员中,支持断开重连

AVInputFormat *fmt:指定输入的封装格式,一般传null就行(开销不大),让函数自行探测;极端需求,用量并发数量多的时候需要

AVDictionary **options:字典数组,包含了key和value,也可以直接传null

#include <iostream>

extern "C" {
#include "libavformat/avformat.h"
}
using namespace std;
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")  //ffmpeg库里面包含的库

int main(int argc, char *argv[])
{
	const char * path = "WeChat_20220914115137.mp4";

	cout << "Test Demux FFmpeg.club" << endl;
	// 初始化封装库
	//av_register_all();  // 这个函数已经废弃

	// 初始化网络库(可以打开rtsp(网络摄像头) rtmp(直播) http(网站或者直播) 协议的流媒体视频)
	avformat_network_init();

	// 参数设置
	AVDictionary *opts = NULL;
		// 添加属性
	//设置rtsp流以tcp协议打开
	av_dict_set(&opts, "rtsp_transport", "tcp", 0);  

	// 网络延时时间
	av_dict_set(&opts, "max_delay", "500", 0);

	// 解封装上下文
	AVFormatContext *ic = NULL; //ic指针指向null
	// 打开视频
	int re = avformat_open_input(
		&ic,  // 传指针没有意义,要传指针的地址 相当于 &(*ic),指针传进去函数调用后,会把AVFormatContext的空间用ic申请出来,并且在里面填入打开的视频信息内容
		path, // 路径,先写死
		0,  // 0或者null 表示自动选择解封装器
		&opts  // 参数设置,传递的是指针的指针,比如rtsp的延时时间
	);
	if (re != 0)
	{
		char buf[1024] = { 0 };  
		av_strerror(re, buf, sizeof(buf) - 1);  // 将re传进去,用buf存储,buf长度不让buf溢出
		cout << "open" << path << "failed!:" << buf << endl;  // 打印失败原因
		getchar();
		return -1;
	}
	cout << "open" << path << "success!" << endl;  // re = 0,成功

	getchar(); // 停止
	return 0;
}

报错1:无法解析的外部符号,没有引用相应的库文件,在头文件添加#pragma comment(lib,“avformat.lib”)即可

在这里插入图片描述

报错2:无法打开文件“avformat.lib”,一般是库目录有问题,修改库目录

在这里插入图片描述
在这里插入图片描述

报错3:无法解析的外部符号_av_strerror.——因为_av_strerror是ffmpeg工具里面包含的库,因此添加头文件#pragma comment(lib,“avutil.lib”)

在这里插入图片描述

第三步找到音视频流信息,存到stream里面

avformat_find_stream_info(…) : 查找文件格式和索引

第四步找到对应的音视频流

av_find_best_stream(…):找对应的音频流和视频流
解封装后,需要分开处理音频和视频参数

第五步,读取,进而区分音频流和视频流

解封装涉及的三个封装结构体

AVFormatContext:封装的上下文,加封装和解封装都是用这个结构体,除了封装参数,大部分信息都存在了AVStream的成员里面

AVformatContext成员很多,在本项目中,只用到以下几个

1、AVIOContext * pb; char filename[1024];
文件IO上下文,可以自定义读写格式 ,自定义读写或者从内存中读,都需要用到AVIOContext
文件名,要把打开的文件名存下来(会断开重连)
2、unsigned int nb_streams; 核心
存数组大小
3、AVStream **streams; 核心
存音视频信息
4、int64_t duration; 获取总长度 ,duration表示媒体文件总长,以AV_TIME_BASE基数为单位,表示1秒钟有多少个单位。其实就是微秒值。可以用宏//AV_TIME_BASE
5、int64_t bit_rate; 比特率,8比特1个字节
6、void avformat_close_input(AVFormatContext **s); 清理封装的上下文——关闭输入的上下文,传指针地址,清理指针空间后,把指针置0
7、其他记录
调试,debug可以看具体信息
在这里插入图片描述

AVStream:有个stream数组

AVPacket:针对具体的解封装完后的一个个数据包,用av_read_frame(…)读取AVPacket的包,AVPacket包含pts/dts、stream、index(音频/视频,但是无法确定是谁的索引,需要通过av_find_best_stream()先存索引)

解封装和解码应该是可以独立的
比如推流,解封装后就可以直接发出去(仅仅做一些筛选和过滤,不需要解码)

2、软硬件解码

3、像素格式转换(视频)

4、重采样(音频)

5、pts/dts(用OpenGLES进行播放)

pts:显示时间
dts:解码时间

6、同步策略

7、SEEK(控制播放进度)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值