解封装的流程整合为类
1、需要的头文件
#include //多线程的互斥锁
#include //C++输入输出流
extern “C”
{
#include “libavformat/avformat.h”//解封装
#include “libavcodec/avcodec.h”//解码
#include “libavutil/avutil.h”//工具
}
#pragma comment(lib,“avformat.lib”)//解封装
#pragma comment(lib,“avutil.lib”)//工具库,包含打印错误信息函数
#pragma comment(lib,“avcodec.lib”)//解码
2、成员变量
开放的变量
public:
int total_time = 0;//媒体总时长ms
int width = 0;//保存视频的宽高
int height = 0;
int sampleRate = 0;//保存音频的采样率和通道数
int channels = 0;
不开放的变量
protected:
std::mutex mux;//互斥对象,互斥锁,保证多线程对变量的正确访问
AVFormatContext* ic = NULL;//解封装上下文,在Open()函数中开辟空间,释放
int videoStream = 0;//视频索引
int audioStream = 1;//音频索引
函数声明
XDemux();
//初始化ffmpeg库
virtual ~XDemux();
//虚析构函数,保证继承类指针空间正确释放
//打开媒体文件或者流媒体 (rtmp http rstp),打开ic的空间,ic自己释放
virtual bool Open(const char* url);
//读取一帧数据包pkt
//返回的空间需要清理,空间需要调用者释放
,分配av_packet_alloc(),
//av_read_frame()。
//释放AVPacket对象空间和其数据空间,使用av_packet_free()
virtual AVPacket* Read();
//获取视频参数,返回的空间需要调用者清理
,分配avcodec_parameters_alloc()清理avcodec_parameters_free()
virtual AVCodecParameters* copyVparam();
//获取音频参数,返回的空间需要清理
virtual AVCodecParameters* copyAparam();
//seek位置 pos:0.0 - 1.0 跳转到媒体流的百分比位置
virtual bool Seek(double pos);
//清空ic读取缓存
virtual void Clear();
//清理ic上下文的内存空间
virtual void Close();
//显示音频和视频的流媒体信息,并且保存音频和视频流的一些参数
virtual void printInformation(const char* url);
//判断是视频包还是音频包
virtual bool isAudio(AVPacket* pkt);
.cpp与.h文件
#pragma once
#include <mutex>
#include <iostream>
extern "C"
{
#include "libavformat/avformat.h"//解封装
#include "libavcodec/avcodec.h"//解码
#include "libavutil/avutil.h"//工具
}
#pragma comment(lib,"avformat.lib")//解封装
#pragma comment(lib,"avutil.lib")//工具库,包含打印错误信息函数
#pragma comment(lib,"avcodec.lib")//解码
class XDemux
{
public:
XDemux();//初始化ffmpeg库
virtual ~XDemux();
//打开媒体文件或者流媒体 rtmp http rstp
virtual bool Open(const char* url);
//读取一帧数据
//返回的空间需要清理,空间需要调用者释放,分配av_packet_alloc(),av_read_frame()。
//释放AVPacket对象空间和其数据空间,使用av_packet_free()
virtual AVPacket* Read();
//获取视频参数,返回的空间需要清理,分配avcodec_parameters_alloc()清理avcodec_parameters_free()
virtual AVCodecParameters* copyVparam();
//获取音频参数,返回的空间需要清理
virtual AVCodecParameters* copyAparam();
//seek位置 pos:0.0 - 1.0 跳转到媒体流的百分比位置
virtual bool Seek(double pos);
//清空ic读取缓存
virtual void Clear();
//清理ic上下文的内存空间
virtual void Close();
//显示音频和视频的流媒体信息,并且保存音频和视频流的一些参数
virtual void printInformation(const char* url);
//判断是视频包还是音频包
virtual bool isAudio(AVPacket* pkt);
public:
//媒体总时长ms
int total_time = 0;
int width = 0;//保存视频的宽高
int height = 0;
int sampleRate = 0;//保存音频信息
int channels = 0;
protected:
//互斥对象
std::mutex mux;
//解封装上下文
AVFormatContext* ic = NULL;
//音视频索引
int videoStream = 0;
int audioStream = 1;
};
开辟了ic的空间,自己释放ic空间。开辟了pkt的空间,等待调用者释放。开辟了音频和视频参数Aparam、Vparam的空间,等待调用着释放。保存了视频的宽高参数,保存了音频的采样率和通道数参数。
#include "XDemux.h"
using namespace std;
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
bool XDemux::isAudio(AVPacket* pkt)
{
if (!pkt)return false;
if (pkt->stream_index == videoStream)
return false;
return true;
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
XDemux::XDemux()
{
static bool isFirst = true;
static std::mutex dmux;//互斥变量
dmux.lock();//上锁,其它线程等待,防止多次初始化
if (isFirst)
{
//初始化
//初始化封装库
av_register_all();
//初始化网络库(可以打开rtsp rtmp http 协议的流媒体视频)
avformat_network_init();
isFirst = false;
}
dmux.unlock();//解锁,其它线程执行
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
XDemux::~XDemux()
{
this->Close();
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//显示信息
void XDemux::printInformation(const char* url)
{
//获取音视频流信息,包含了视频和音频
//mp4在这里flv有所不同
int res = avformat_find_stream_info(ic, 0);
//AV_TIME_BASE是时间的计数百万, ic->duration / AV_TIME_BASE是秒。
//打印总时长 ms
total_time = ic->duration / (AV_TIME_BASE / 1000);
cout << "total time : " << total_time << " ms" << endl;
//打印视频流的详细信息
cout << "=======================媒流体的所有信息如下:========================" << endl;
av_dump_format(ic, 0, url, 0);
cout << "==============================end=======================================" << endl << endl;
//分别获取音视频索引,并打印音频和视频的相关信息
//获取音视频流信息(遍历,函数获取),ic->nb_streams是媒体流的数量
videoStream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
//音频
AVStream* stream = ic->streams[audioStream];
sampleRate = stream->codecpar->sample_rate;//保存音频信息
channels = stream->codecpar->channels;
cout << "==========================" << audioStream << " : 音频信息如下:" << "==================" << endl;
//stream->codecpar->codec_type 中保存流的类别
cout << "codec_id = " << stream->codecpar->codec_id << endl;//AVCodecID
cout << "sample_rate = " << stream->codecpar->sample_rate << endl;
cout << "format = " << stream->codecpar->format << endl;//AVSampleFormat
cout << "channnels = " << stream->codecpar->channels << endl;
cout << "audio fps = " << av_q2d(stream->avg_frame_rate) << endl;//存储浮点数是按照分子分母保存的
//音频一帧数据 是 一定量的样本数 单通道
cout << "frame_size = " << stream->codecpar->frame_size << endl;//fps = sample_rate/frame_size
cout << "==============================end=======================================" << endl << endl;
//视频
stream = ic->streams[videoStream];
width = stream->codecpar->width;//将视频的宽给成员变量
height = stream->codecpar->height;//将视频的高给成员变量
int vs_duration = (stream->duration) * av_q2d(stream->time_base);//打印时长 av_q2d = r2d
cout << "==========================" << videoStream << " : 视频信息如下:" << "==================" << endl;
cout << "codec_id = " << stream->codecpar->codec_id << endl;//AVCodecID
cout << "sample_rate = " << stream->codecpar->sample_rate << endl;
cout << "width = " << stream->codecpar->width << endl;
cout << "height = " << stream->codecpar->height << endl;
cout << "vedio fps = " << av_q2d(stream->avg_frame_rate) << endl;//帧率 fps 分数转换
cout << "视频的总时长为: " << vs_duration / 3600 << ":" << vs_duration % 3600 / 60 << ":" << vs_duration % 60 << endl;
cout << "==============================end=======================================" << endl << endl;
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//打开媒体文件或者流媒体 rtmp http rstp
bool XDemux::Open(const char* url)
{
//打开前先清空ic的内容,不能在上锁后执行,会死锁
this->Close();
const char* path = url;
//参数设置
AVDictionary* opts = NULL;
//设置rtsp流已tcp协议打开
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
//设置网络延迟时间
av_dict_set(&opts, "max_delay", "500", 0);
mux.lock();//上锁,防止多次打开
//打开流媒体
int re = avformat_open_input(
&ic, //上下文结构体:保存音视频的构成和基本信息
path, //音视频文件路径
0, //自动选择解封器
&opts//参数设置,比如rtsp的延迟设置
);
if (re != 0)//0为成功打开文件
{
mux.unlock();//解锁,打开失败就解锁,解锁在返回之前
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);//打印错误原因
cout << " open--" << path << "--failed! : " << buf << endl;
return false;
}
cout << "open--" << path << "--success! : " << endl;
//打印流媒体信息
this->printInformation(path);
mux.unlock();//解锁,使用完了就解锁,解锁在返回之前
return true;
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//空间需要调用者释放,释放AVPacket对象空间 和 数据空间 av_packet_free
AVPacket* XDemux::Read()
{
mux.lock();//上锁
if (!ic)//流媒体上下文不存在
{
mux.unlock();//解锁在返回之前
return NULL;
}
AVPacket* pkt = av_packet_alloc();//只是分配对象空间,没有分配数据空间
//可能读取的为视频帧,也可能为音频帧
int re = av_read_frame(ic, pkt);//读取一帧,并且分配了数据的空间
if (re != 0)//一帧数据不存在 0代表成功
{
mux.unlock();//解锁在返回之前
av_packet_free(&pkt);
return NULL;
}
//pts同一转换为毫秒
pkt->pts = pkt->pts * av_q2d(ic->streams[pkt->stream_index]->time_base) * 1000;
pkt->dts = pkt->dts * av_q2d(ic->streams[pkt->stream_index]->time_base) * 1000;
mux.unlock();//解锁在返回之前
cout << "时间戳 pts = " << pkt->pts << endl;
return pkt;
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//获取视频参数,返回的空间需要清理,分配avcodec_parameters_alloc()清理avcodec_parameters_free()
AVCodecParameters* XDemux::copyVparam()
{
mux.lock();//上锁
if (!ic)
{
mux.unlock();//解锁在返回之前
return NULL;
}
//分配参数的内存
AVCodecParameters* Vparam = avcodec_parameters_alloc();
//把参数里面的数据复制一份,不是赋值,赋值只是地址,程序结束了被会清空
avcodec_parameters_copy(Vparam, ic->streams[videoStream]->codecpar);
mux.unlock();//解锁在返回之前
return Vparam;
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//获取音频参数,返回的空间需要清理
AVCodecParameters* XDemux::copyAparam()
{
mux.lock();//上锁
if (!ic)
{
mux.unlock();//解锁在返回之前
return NULL;
}
//分配参数的内存
AVCodecParameters* Aparam = avcodec_parameters_alloc();
//把参数里面的数据复制一份,不是赋值,赋值只是地址,程序结束了被会清空
avcodec_parameters_copy(Aparam, ic->streams[audioStream]->codecpar);
mux.unlock();//解锁在返回之前
return Aparam;
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//seek位置 pos:0.0 - 1.0
bool XDemux::Seek(double pos)
{
mux.lock();
if (!ic)
{
mux.unlock();//解锁在返回之前
return NULL;
}
//清理读取缓冲,必须单独清除,不能调用清除函数,会死锁
avformat_flush(ic);
long long seekPos = 0;
seekPos = ic->streams[videoStream]->duration* pos;
//long long pos = (double)ms / 1000 * r2d(ic->streams[pkt->stream_index]->time_base);
int re = av_seek_frame(ic, videoStream, seekPos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);//往后找一帧,且找到关键帧
mux.unlock();//解锁在返回之前
if (re < 0)
{
return false;
}
return true;
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//清空读取缓存
void XDemux::Clear()
{
mux.lock();
if (!ic)
{
mux.unlock();//解锁在返回之前
return;
}
//清理读取缓冲,不会清空内容,必须单独清除,不能调用清除函数,会死锁
avformat_flush(ic);
mux.unlock();//解锁在返回之前
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//清空媒体流上下文的内容
void XDemux::Close()
{
mux.lock();//如果还有一个线程要打开,因为打开的ic还没解锁
if (!ic)//如果ic为空,还没有打开
{
mux.unlock();//解锁在返回之前
return;
}
//清理ic 清空媒体流上下文的内容
avformat_close_input(&ic);
//媒体总时长
total_time = 0;
mux.unlock();//解锁在返回之前
}
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//