头文件:
xtools.h
#pragma once
#include <thread>
#include <iostream>
#include <mutex>
//日志级别 DEBUG INFO ERROR FATAL
enum XLogLevel
{
XLOG_TYPE_DEBUG,
XLOG_TYPE_INFO,
XLOG_TPYE_ERROR,
XLOG_TYPE_FATAL
};
#define LOG_MIN_LEVEL XLOG_TYPE_DEBUG
#define XLOG(s,level) \
if(level>=LOG_MIN_LEVEL) \
std::cout<<level<<":"<<__FILE__<<":"<<__LINE__<<":\n"\
<<s<<std::endl;
#define LOGDEBUG(s) XLOG(s,XLOG_TYPE_DEBUG)
#define LOGINFO(s) XLOG(s,XLOG_TYPE_INFO)
#define LOGERROR(s) XLOG(s,XLOG_TPYE_ERROR)
#define LOGFATAL(s) XLOG(s,XLOG_TYPE_FATAL)
void MSleep(unsigned int ms);
//获取当前时间戳 毫秒
long long NowMs();
class XThread
{
public:
//启动线程
virtual void Start();
//停止线程(设置退出标志,等待线程退出)
virtual void Stop();
protected:
//线程入口函数
virtual void Main() = 0;
//标志线程退出
bool is_exit_ = false;
//线程索引号
int index_ = 0;
private:
std::thread th_;
std::mutex m_;
};
class XTools
{
};
xdemux_task.h
#pragma once
#include "xtools.h"
#include "xdemux.h"
class XDemuxTask :public XThread
{
public:
void Main();
/// <summary>
/// 打开解封装
/// </summary>
/// <param name="url">rtsp地址</param>
/// <param name="timeout_ms">超时时间 单位毫秒</param>
/// <returns></returns>
bool Open(std::string url,int timeout_ms = 1000);
private:
XDemux demux_;
std::string url_;
int timeout_ms_ = 0;//超时时间
};
xformat.h
#pragma once
/// <summary>
/// 封装和解封装基类
/// </summary>
#include <mutex>
#include"xtools.h"
struct AVFormatContext;
struct AVCodecParameters;
struct AVPacket;
struct AVCodecContext;
struct XRational
{
int num; ///< Numerator
int den; ///< Denominator
};
class XFormat
{
public:
/// <summary>
/// 复制参数 线程安全
/// </summary>
/// <param name="stream_index">对应c_->streams 下标</param>
/// <param name="dst">输出参数</param>
/// <returns>是否成功</returns>
bool CopyPara(int stream_index, AVCodecParameters* dst);
bool CopyPara(int stream_index, AVCodecContext* dts);
/// <summary>
/// 设置上下文,并且清理上次的设置的值,如果传递NULL,相当于关闭上下文3
/// 线程安全
/// </summary>
/// <param name="c"></param>
void set_c(AVFormatContext* c);
int audio_index() { return audio_index_; }
int video_index() { return video_index_; }
XRational video_time_base(){ return video_time_base_; }
XRational audio_time_base() { return audio_time_base_; }
//根据timebase换算时间
bool RescaleTime(AVPacket *pkt,long long offset_pts, XRational time_base);
int video_codec_id() { return video_codec_id_; }
//判断是否超时
bool IsTimeout()
{
if (NowMs() - last_time_ > time_out_ms_) //超时
{
last_time_ = NowMs();
is_connected_ = false;
return true;
}
return false;
}
//设定超时时间
void set_time_out_ms(int ms);
bool is_connected() { return is_connected_; }
protected:
int time_out_ms_ = 0; //超时时间 毫秒
long long last_time_ = 0;
bool is_connected_ = false; //是否连接成功
AVFormatContext* c_ = nullptr; //封装解封装上下文
std::mutex mux_; //c_ 资源互斥
int video_index_ = 0;//video和audio在stream中索引
int audio_index_ = 1;
XRational video_time_base_ = {1,25};
XRational audio_time_base_ = {1,9000};
int video_codec_id_ = 0; //编码器ID
};
xdemux.h
#pragma once
#include "xformat.h"
class XDemux :public XFormat
{
public:
/// <summary>
/// 打开解封装
/// </summary>
/// <param name="url">解封装地址 支持rtsp</param>
/// <returns>失败返回nullptr</returns>
static AVFormatContext* Open(const char* url);
/// <summary>
/// 读取一帧数据
/// </summary>
/// <param name="pkt">输出数据</param>
/// <returns>是否成功</returns>
bool Read(AVPacket* pkt);
bool Seek(long long pts,int stream_index);
};
源文件:
xtools.cpp
#include "xtools.h"
#include <sstream>
using namespace std;
void MSleep(unsigned int ms)
{
auto beg = clock();
for (int i = 0; i < ms; i++)
{
this_thread::sleep_for(1ms);
if ((clock() - beg) / (CLOCKS_PER_SEC / 1000) >= ms)
break;
}
}
long long NowMs()
{
return clock() / (CLOCKS_PER_SEC / 1000);
}
//启动线程
void XThread::Start()
{
unique_lock<mutex> lock(m_);
static int i = 0;
i++;
index_ = i;
is_exit_ = false;
//启动线程
th_ = thread(&XThread::Main, this);
stringstream ss;
ss << "XThread::Start()" << index_;
LOGINFO(ss.str());
}
//停止线程(设置退出标志,等待线程退出)
void XThread::Stop()
{
stringstream ss;
ss << "XThread::Stop() begin" << index_;
LOGINFO(ss.str());
is_exit_ = true;
if (th_.joinable()) //判断子线程是否可以等待
th_.join(); //等待子线程退出
ss.str("");
ss << "XThread::Stop() end" << index_;
LOGINFO(ss.str());
}
xdemux_task.cpp
#include "xdemux_task.h"
extern "C"
{
#include <libavformat/avformat.h>
}
using namespace std;
bool XDemuxTask::Open(std::string url, int timeout_ms)
{
LOGDEBUG("XDemuxTask::Open begin!");
demux_.set_c(nullptr);//断开之前的连接
this->url_ = url;
this->timeout_ms_ = timeout_ms;
auto c = demux_.Open(url.c_str());
if (!c)return false;
demux_.set_c(c);
demux_.set_time_out_ms(timeout_ms);
LOGDEBUG("XDemuxTask::Open end!");
return true;
}
void XDemuxTask::Main()
{
AVPacket pkt;
while (!is_exit_)
{
if (!demux_.Read(&pkt))
{
//读取失败
cout << "-" << flush;
if (!demux_.is_connected())//如果断开连接,需要重新连接
{
Open(url_, timeout_ms_);
}
this_thread::sleep_for(1ms);
continue;
}
cout << "." << flush;
this_thread::sleep_for(1ms);
}
}
xformat.cpp
#include "xformat.h"
#include <iostream>
#include <thread>
#include "xtools.h"
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavformat/avformat.h>
}
//预处理指令导入库
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
using namespace std;
static int TimeoutCallback(void* para)
{
auto xf = (XFormat*)para;
if (xf->IsTimeout())return 1;//超时退出Read
//cout << "TimeoutCallback" << endl;
return 0; //正常阻塞
}
void XFormat::set_c(AVFormatContext* c)
{
unique_lock<mutex> lock(mux_);
if (c_) //清理原值
{
if (c_->oformat) //输出上下文
{
if (c_->pb)
avio_closep(&c_->pb);
avformat_free_context(c_);
}
else if (c_->iformat) //输入上下文
{
avformat_close_input(&c_);
}
else
{
avformat_free_context(c_);
}
}
c_ = c;
if (!c_)
{
is_connected_ = false;
return;
}
is_connected_ = true;
//计时 用于超时判断
last_time_ = NowMs();
//设定超时处理回调
if (time_out_ms_ > 0)
{
AVIOInterruptCB cb = { TimeoutCallback ,this };
c_->interrupt_callback = cb;
}
//用于区分是否有音频或者视频流
audio_index_ = -1;
video_index_ = -1;
//区分音视频stream 索引
for (int i = 0; i < c->nb_streams; i++)
{
//音频
if (c->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
audio_index_ = i;
audio_time_base_.den = c->streams[i]->time_base.den;
audio_time_base_.num = c->streams[i]->time_base.num;
}
else if (c->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_index_ = i;
video_time_base_.den = c->streams[i]->time_base.den;
video_time_base_.num = c->streams[i]->time_base.num;
video_codec_id_ = c->streams[i]->codecpar->codec_id;
}
}
}
bool XFormat::CopyPara(int stream_index, AVCodecContext* dts)
{
unique_lock<mutex> lock(mux_);
if (!c_)
{
return false;
}
if (stream_index<0 || stream_index>c_->nb_streams)
return false;
auto re = avcodec_parameters_to_context(dts, c_->streams[stream_index]->codecpar);
if (re < 0)
{
return false;
}
return true;
}
/// <summary>
/// 复制参数 线程安全
/// </summary>
/// <param name="stream_index">对应c_->streams 下标</param>
/// <param name="dst">输出参数</param>
/// <returns>是否成功</returns>
bool XFormat::CopyPara(int stream_index, AVCodecParameters* dst)
{
unique_lock<mutex> lock(mux_);
if (!c_)
{
return false;
}
if (stream_index<0 || stream_index>c_->nb_streams)
return false;
auto re = avcodec_parameters_copy(dst, c_->streams[stream_index]->codecpar);
if (re < 0)
{
return false;
}
return true;
}
bool XFormat::RescaleTime(AVPacket* pkt, long long offset_pts, XRational time_base)
{
unique_lock<mutex> lock(mux_);
if (!c_)return false;
auto out_stream = c_->streams[pkt->stream_index];
AVRational in_time_base;
in_time_base.num = time_base.num;
in_time_base.den = time_base.den;
pkt->pts = av_rescale_q_rnd(pkt->pts- offset_pts, in_time_base,
out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
);
pkt->dts = av_rescale_q_rnd(pkt->dts- offset_pts, in_time_base,
out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
);
pkt->duration = av_rescale_q(pkt->duration, in_time_base, out_stream->time_base);
pkt->pos = -1;
return true;
}
//设定超时时间
void XFormat::set_time_out_ms(int ms)
{
unique_lock<mutex> lock(mux_);
this->time_out_ms_ = ms;
//设置回调函数,处理超时退出
if (c_)
{
//AVIOInterruptCB结构体:这是FFmpeg中用于处理I/O操作中断的结构体,包含一个回调函数指针和一个用户数据指针。
AVIOInterruptCB cb = { TimeoutCallback ,this };//看下面的注释结构体
c_->interrupt_callback = cb;
}
/* typedef struct AVIOInterruptCB {
int (*callback)(void*);
void* opaque;
} AVIOInterruptCB;*/
}
xdemux.cpp
#include "xdemux.h"
#include <iostream>
#include <thread>
#include"xtools.h"
#include"xcodec.h"
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavformat/avformat.h>
}
void PrintErr(int err);
#define BERR(err) if(err!= 0){PrintErr(err);return 0;}
AVFormatContext* XDemux::Open(const char* url)
{
AVFormatContext* c = nullptr;
AVDictionary* opts = nullptr;
//av_dict_set(&opts, "rtsp_transport", "tcp", 0);//传输媒体流为tcp协议,默认udp
av_dict_set(&opts, "stimeout", "1000000", 0);//连接超时1秒
//打开封装上下文
auto re = avformat_open_input(&c, url, nullptr, &opts);
if (opts)
av_dict_free(&opts);
BERR(re);
//获取媒体信息
re = avformat_find_stream_info(c, nullptr);
BERR(re);
//打印输入封装信息
av_dump_format(c, 0, url, 0);
return c;
}
bool XDemux::Read(AVPacket* pkt)
{
unique_lock<mutex> lock(mux_);
if (!c_)return false;
auto re = av_read_frame(c_, pkt);
BERR(re);
//计时 用于超时判断
last_time_ = NowMs();
return true;
}
bool XDemux::Seek(long long pts, int stream_index)
{
unique_lock<mutex> lock(mux_);
if (!c_)return false;
auto re = av_seek_frame(c_, stream_index, pts,
AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD);
BERR(re);
return true;
}
main.cpp
#include <iostream>
#include <thread>
#include "xtools.h"
#include "xdemux_task.h"
using namespace std;
//class TestThread :public XThread
//{
//public:
// void Main()
// {
// LOGDEBUG("TestThread Main begin");
// while (!is_exit_)
// {
// this_thread::sleep_for(1ms);
// }
// LOGDEBUG("TestThread Main end");
// }
//};
#define CAM1 \
"rtsp://admin:admin@192.168.2.108/cam/realmonitor?channel=1&subtype=0"
#define MP4 "v1080.mp4"
int main(int argc, char* argv[])
{
XDemuxTask det;
for (;;)//保证第一次连接就失败的情况
{
if (det.Open(CAM1))//进行解封装得到pkt
{
break;
}
MSleep(100);
continue;
}
//启动主线程Main函数进行解码
det.Start();
//TestThread tt;
//tt.Start();
//this_thread::sleep_for(3s);
//tt.Stop();
getchar();
return 0;
}
[点击并拖拽以移动]
主函数执行分析:
det对象用于对rtsp进行解封装,通过调用open函数,该函数调用Xdemux_task的成员demux对象对其进行解封装得到解封装上下文,再用set_c初始化设置解封装上下文,再用demux对象调用set_time_out_ms函数,该函数在xformat里,demux类继承xformat所以可以调用,作用是设定超时时间,如果断开连接则会调用一个回调函数TimeOutCallBack函数对时间进行判断是否超时,如果超时则退出,解封装后det再调用线程启动函数启动主线程Main函数,用read_frame函数把解封装后的rtsp进行读取存储到pkt中准备进行pkt->frame的解码,读取过程中如果摄像头断开会自动调用Open函数进行重新解封装。