【QT项目——视频播放器——解封装】4.5AVformatContext结构体解析4.6AVStream4.11av_read_frame4.14整个解封装过程代码

4.5 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可以看具体信息
在这里插入图片描述

4.6 AVStream

1、AVCodecContext *codec;用来做解码器,但是已过时
2、AVRational time_base; 时间基数,他是一个分数
3、int64_t duration; 多少分之一秒,假定time_base=1000分之一,duration就是毫秒
得到毫秒:duration *((double)time_base.num/(double)time_base.den)*1000,den是分母(确保分母不为0),num是分子
4、int64_t nb_frames;
5、AVRational avg_frame_rate; 帧率AVRational 表示有理数
6、**AVCodecParameters codecpar;(音视频参数),用来替代[AVCodecContext codec]

AVCodecParameters *codecpar
1、enum AVMediaType codec_type; 表示编码类型,标识是音频还是视频
2、enum AVCodecID codec_id ; 标识编码格式,是264或者265等
3、unit32_t codec_tag; 用4个字节表示各种编码器
4、int format; 格式,像素格式,或者音频采样格式
5、int width;int height; 视频宽高
6、unit64_t channel_layout; 声道
int channel; 声道数
int sample_rate;样本率
int frame_size;帧大小

4.11av_read_frame 读取帧数据

在读帧和解码时要关注内存问题,ffmpeg的复杂就在于此,会出现内存泄漏
C++在调用函数时候要考虑传入的参数,产生什么空间,空间是怎么清理

该函数有两个参数:
AVFormatContext *s——文件格式上下文
AVPacket *pkt——Packer传了一个指针,需要考虑一些问题,传的这个指针要不要考虑指针空间,要不要预先分配好地址;第二次调用的时候上次的空间怎么处理,比如每次都读的都是同一帧,同一个AVPacket进来,会怎么处理。
注意,pkt不能传null,必须是一个空间
每次传同一个对象进来不会清理旧的内存空间,会开辟新的地址空间使用,导致内存不断增加
return 0 表示成功,<0 on error or end of file

AVPacket 成员

AVBufferRef buf ; 存储引用计数
int64_t pts; // pts
(num/den); 显示时间
int64_t dts;// 解码时间
unit8_t* data; 指向AVBufferRef 再分配的空间
int size; 用接口删除

AVPacket 函数 ——空间申请赋值清理用到的函数

AVPacker * av_packet_alloc(void) ; 空间创建并初始化;创建对象,申请堆上的空间,需要释放
AVPacket * av_packet_clone(const AVPacket * str) ; 空间复制,创建并引用计数
int av_packer_ref(AVPacket *dst,const AVPacket * str); 手动引用加1,是内部做封装或者做处理的时候,手动地把原始空间加到目标空间,要确保AVPacket 已经创建并且初始化好。等同于前面的clone
av_packet_unref(AVPacket *pkt) ; 减少引用,把packet引用计数减1,减到0就会删掉
void av_packet_free(AVPacket **pkt);清空对象并减引用计数
void av_init_packet(AVPacket *pkt);默认值,初始化接口
int av_packet_from_data(AVPacket *pkt, unit8_t *data,int size); 自定义转成avpacket 的包
int av_copy_packet(AVPacket *dst,const AVPacket *src); attribute_deprecated 早期的函数,已经被抛弃

4.12av_seek_frame函数 进度条拖动操作

函数原型
int av_seek_frame(AVFormatContext *s, // 封装格式上下文
int stream_index, // 索引,针对音频和视频都可以 -1 default (默认视频)
int64_t timestamp, // 时间戳,移动到哪个时间位置,与AVStream.time_base时间基数一样
int flags); // 标识位。表示移的方法
Q:一段媒体中,有音视频,选择哪个来移动
A:用视频来做seek,选择关键帧
用音频来做seek有这样的问题:音频没有b帧
视频有b帧,比如第7秒不是关键帧,则解不出来,视频必须移动到关键帧的位置,所以stream_index选择videostream视频
Q:时间戳近似问题
A:当前位置和进度条的比例✖总时间得到位置,

av_seek_frame flag

#define AVSEEK_FLAG_BACKWARD 1 // seek backward 往后(后指的是时间早的)走
#define AVSEEK_FLAG_BYTE 2 // seek based on position in bytes
#define AVSEEK_FLAG_ANY 4 // seek to any frame,even non-keyframes 找最近(后)的一帧,但是一般不这么做,都要找关键帧,不是关键帧会花屏
#define AVSEEK_FLAG_FRAME 8 // seek based on frame number 往后找关键帧

音频和视频顺序:一般是一帧图像后跟2帧音频

在这里插入图片描述

时间一开始是负数?

在这里插入图片描述
设置断点调试,发现一开始就是负值,原始数据就是负值,表示在0之前,要预先处理
在这里插入图片描述

整个解封装过程代码

整个解封装代码
#include <iostream>
#include<thread> //线程
extern "C" {
#include "libavformat/avformat.h"
}
using namespace std;
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")  //ffmpeg库里面包含的库
#pragma comment(lib,"avcodec.lib") 
static double r2d(AVRational r)  //static仅在当前文件有效
{
	return r.den == 0 ? 0 : (double)r.num / (double)r.den;

}

void XSleep(int ms)  //用毫秒展示
{
	// C++ 11
	chrono::milliseconds du(ms);
	this_thread::sleep_for(du);
}

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,成功

	// 获取流信息
	re = avformat_find_stream_info(ic, 0);

	// 音视频索引,读取时区分音视频
	int videoStream = 0;
	int audioStream = 1;

	// 获取总时长 毫秒
	int totalMs = ic->duration / (AV_TIME_BASE / 1000);
	cout << "totalMs = " << totalMs << endl;

	// 打印视频流详细信息
	av_dump_format(ic, 0, path, 0);//第四个参数 含义:input(0) or output(1)

	// 获取音视频流信息(遍历,函数获取)
	for (int i = 0; i < ic->nb_streams; i++)
	{
		// 如何判断音视频?找到索引后,取出AVStream后找到下标,存放在ic里面
		AVStream *as = ic->streams[i];
		cout << "codec_id = " << as->codecpar->codec_id << endl;  // 打开解封装器
		cout << "format = " << as->codecpar->format << endl;  // 存储格式,format = 8,平面存储方式
		// 一定要重采样,转化成16位/24位,否则无法播放
		// 音频
		if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) 
		{
			audioStream = i;
			cout << i << "音频信息" << endl;
			cout << "sample_rate = " << as->codecpar->sample_rate << endl; // 样本率

			cout << "channels = " << as->codecpar->channels<< endl;  // 通道数,单通道还是双通道
			cout << "audio fps = " << r2d(as->avg_frame_rate)<<endl;
			// 那么一帧数据是什么???   一定量的样本数
			// 那一帧数据存多少样本数
			// 一帧数据的单通道样本数
			cout << "frame_size = " << as->codecpar->frame_size << endl;  //1024
			// 双通道 = 1024 * 2(双通道)* 2 (16位,8位1字节) = 4096
			// fps = sample_rate / frame_size;

		}
		// 视频
		else if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoStream = i;
			cout << i << "视频信息" << endl; // 0是视频1是音频
			cout << "width=" << as->codecpar->width << endl; 
			cout << "height=" << as->codecpar->height << endl;
			// 帧率 fps   视频的fps一定是整数,音频 的fps有可能是分数
			cout << "video fps = " << r2d(as->avg_frame_rate) << endl;
			// 对于视频,一帧数据一帧画面就是一幅图像

		}
	}

	//第二种方法获取流信息(第一种方法是遍历)
	// 获取视频流
	videoStream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);  // -1是自动获取
	// ic->streams[videoStream]   // 通过ic->streams下标就可以访问到,从而不需要遍历

	// 读取一定是在关闭上下文之前
	// malloc AVPacket并初始化
	AVPacket *pkt = av_packet_alloc();
	for (;;)  // 先用一个无限循环,因为不确定文件有多大
	{
		//av_read_frame(ic, pkt);   // ic就是刚刚打开的解封装的上下文,第二个参数是pkt,要申请空间,一般是通过接口来申请
		int re = av_read_frame(ic, pkt);
		if (re != 0)  // 读到结尾如何让其回到开头?用av_seek_frame
		{
			// 显示结尾后循环播放到开头第三秒的位置
			cout << "===================end=======================" << endl;
			int ms = 3000; // 三秒位置  根据时间基数(分数)转换
			long long pos = (double)ms /(double)1000 * r2d(ic->streams[pkt->stream_index]->time_base);
			// ms / 1000 转化成秒s,然后转化成浮点数,再×时间基数
			av_seek_frame(ic, videoStream,  pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME );
			// av_seek_frame 索引默认0(视频)  AVSEEK_FLAG_BACKWARD往后找,且找到关键帧
			continue;
	        //break;  // 失败,直接跳出,不需要释放空间
		}
		cout << "pkt->size"<<pkt->size << endl;  // 成功,打印大小
		//显示的时间
		cout << "pkt->pts" << pkt->pts << endl;
		//解码时间
		cout << "pkt->dts" << pkt->dts << endl;
		// 转换为毫秒,方便做同步。 音频的time_base是采样率分之一,而视频有时候是千分之一,很难一致,所以将其转化成毫秒进行同步
		cout << "pkt->dts = " << pkt->pts * (r2d(ic->streams[pkt->stream_index]->time_base) * 1000) << endl;
		// pts×time_base分数,得出来的就是具体的多少秒   * 1000 转成ms毫秒

		// 判断这帧存放音频还是视频
		if (pkt->stream_index == videoStream)  // 视频帧率25
		{
			cout << "图像" << endl;
		}
		if (pkt->stream_index == audioStream)  // 音频帧率44
		{
			cout << "音频" << endl;
		}

		
		//XSleep(50);  //打印频率500ms一次,一秒2次

		av_packet_unref(pkt); // 释放,引用计数-1, 为0释放空间
	}
	av_packet_free(&pkt);  // 释放空间




	if (ic)  // 如果指针不为空,也就是播放成功的时候,则释放指针
	{
		// 释放封装上下文,并且把ic置0
		avformat_close_input(&ic);
	}

	getchar(); // 停止
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值