概述
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;
}
最后
以上就是瘦瘦大炮为你收集整理的【QT项目——视频播放器——解封装】4.5AVformatContext结构体解析4.6AVStream4.11av_read_frame4.14整个解封装过程代码4.5 AVformatContext成员很多,在本项目中,只用到以下几个4.6 AVStream4.11av_read_frame 读取帧数据4.12av_seek_frame函数 进度条拖动操作音频和视频顺序:一般是一帧图像后跟2帧音频时间一开始是负数?整个解封装过程代码的全部内容,希望文章能够帮你解决【QT项目——视频播放器——解封装】4.5AVformatContext结构体解析4.6AVStream4.11av_read_frame4.14整个解封装过程代码4.5 AVformatContext成员很多,在本项目中,只用到以下几个4.6 AVStream4.11av_read_frame 读取帧数据4.12av_seek_frame函数 进度条拖动操作音频和视频顺序:一般是一帧图像后跟2帧音频时间一开始是负数?整个解封装过程代码所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复