XTools.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#pragma once
#include <thread>
#include <iostream>
#include <mutex>
#include <list>

struct AVPacket;
struct AVCodecParameters;
struct AVRational;
struct AVFrame;

enum XLogLevel
{
XLOG_TYPE_DEBUG,
XLOG_TYPE_INFO,
XLOG_TYPE_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__ << " : " << 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_TYPE_ERROR)
#define LOGFATAL(s) XLOG(s, XLOG_TYPE_FATAL)


void MSleep(unsigned int ms);

long long NowMs();

void XFreeFrame(AVFrame **frame);

void PrintErr(int err);

class XThread
{
public:

virtual void Start();

virtual void Stop();

virtual void Do(AVPacket *pkt) {};

virtual void Next(AVPacket *pkt)
{
std::unique_lock<std::mutex> lock(m_);
if (next_)
next_->Do(pkt);
}

void set_next(XThread *xt)
{
std::unique_lock<std::mutex> lock(m_);
next_ = xt;
}

protected:

virtual void Main() = 0;

bool is_exit_ = false;

int index_ = 0;

private:
std::thread th_;
std::mutex m_;
XThread *next_ = nullptr;
};

class XTools
{
};


class XPara
{
public:
AVCodecParameters *para = nullptr;
AVRational *time_base = nullptr;

static XPara *Create();
~XPara();

private:
XPara();
};


class XAVPacketList
{
public:
AVPacket *Pop();
void Push(AVPacket *pkt);

private:
std::list<AVPacket*> pkts_;
int max_packets_ = 100;
std::mutex mux_;
};

参数说明:

普通成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 日志级别 DEBUG INFO ERROR FATAL
enum XLogLevel
{
XLOG_TYPE_DEBUG,
XLOG_TYPE_INFO,
XLOG_TYPE_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__ << " : " << 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_TYPE_ERROR)
#define LOGFATAL(s) XLOG(s, XLOG_TYPE_FATAL)


void MSleep(unsigned int ms);

// 获取当前的时间戳 毫秒
long long NowMs();

void XFreeFrame(AVFrame **frame);

void PrintErr(int err);

XThread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class XThread
{
public:
// 启动线程
virtual void Start();

// 停止线程(设置退出标志,等待线程退出)
virtual void Stop();

// 执行责任链传递给的任务,需要重载
virtual void Do(AVPacket *pkt) {};

// 传递到责任链下一个函数
virtual void Next(AVPacket *pkt)
{
std::unique_lock<std::mutex> lock(m_);
if (next_)
next_->Do(pkt);
}

// 设置责任链下一个节点(线程安全)
void set_next(XThread *xt)
{
std::unique_lock<std::mutex> lock(m_);
next_ = xt;
}

protected:

// 线程入口函数
virtual void Main() = 0;

// 标志线程退出
bool is_exit_ = false;

// 线程索引号
int index_ = 0;


private:
std::thread th_;
std::mutex m_;
XThread *next_ = nullptr; // 责任链的下一个节点
};

XPara

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 音视频参数,用于智能指针自动释放
class XPara
{
public:
AVCodecParameters *para = nullptr; // 音视频参数
AVRational *time_base = nullptr; // 时间基数

// 创建 XPara 对象的实例
static XPara *Create();
~XPara();

private:
// 构造函数被声明为私有,以防止直接在栈上创建 XPara 对象。
// 这意味着用户无法通过 XPara xpara; 这样的方式创建对象,只能通过 Create 静态方法在堆上创建对象。
// 这样做的目的:控制对象的生命周期、管理资源释放、防止栈溢出
XPara();
};

XAVPacketList

作用:由于std::list线程不是安全的。所以做了用于管理一个线程安全的 AVPacket 列表。这个类允许在多个线程之间安全地添加和移除 AVPacket 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 线程安全AVPacket list
class XAVPacketList
{
public:
// 从列表中弹出并返回一个 AVPacket 对象。
AVPacket *Pop();

// 将一个 AVPacket 对象推入列表。
// 具体实现应确保线程安全,并且在列表大小超过 max_packets_ 时进行清理。
void Push(AVPacket *pkt);

private:
std::list<AVPacket*> pkts_; // 内部定义AVPacket的List
int max_packets_ = 100; // 最大列表数量,超出清理
std::mutex mux_;
};

XTools.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include "xtools.h"
#include <sstream>

using namespace std;

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}


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 XFreeFrame(AVFrame **frame)
{
if (!frame || !(*frame)) return;
av_frame_free(frame);
}


void PrintErr(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << buf << endl;
}


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());
}


XPara *XPara::Create()
{
return new XPara();
}


XPara::~XPara()
{
if (para)
{
avcodec_parameters_free(&para);
}
if (time_base)
{
delete time_base;
time_base = nullptr;
}
}


XPara::XPara()
{
para = avcodec_parameters_alloc();
time_base = new AVRational();
}


AVPacket *XAVPacketList::Pop()
{
unique_lock<mutex> lock(mux_);
if (pkts_.empty()) return nullptr;
auto pkt = pkts_.front();
pkts_.pop_front();
return pkt;
}


void XAVPacketList::Push(AVPacket *pkt)
{
unique_lock<mutex> lock(mux_);
auto p = av_packet_alloc();
av_packet_ref(p, pkt);
pkts_.push_back(p);

if (pkts_.size() > max_packets_)
{
if (pkts_.front()->flags & AV_PKT_FLAG_KEY)
{
av_packet_free(&pkts_.front());
pkts_.pop_front();
return;
}

while (pkts_.empty())
{
if (pkts_.front()->flags & AV_PKT_FLAG_KEY)
{
return;
}
av_packet_free(&pkts_.front());
pkts_.pop_front();
}
}
}

普通成员函数

MSleep()

作用:使当前线程暂停执行一段时间(以毫秒为单位)。

1
2
3
4
5
6
7
8
9
10
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;
}
}

NowMs()

1
2
3
4
5
// 获得当前时间的毫秒数
long long NowMs()
{
return clock() / (CLOCKS_PER_SEC / 1000);
}

XFreeFrame()

作用:释放AVFrame资源

1
2
3
4
5
void XFreeFrame(AVFrame **frame)
{
if (!frame || !(*frame)) return;
av_frame_free(frame);
}

PrintErr()

作用:打印错误信息。

1
2
3
4
5
6
void PrintErr(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << buf << endl;
}

XThread

责任链模式

类的成员和方法

  1. 公共成员函数
    • Start():启动线程。
    • Stop():停止线程,设置退出标志并等待线程退出。
    • Do(AVPacket *pkt):执行责任链传递的任务,虚函数,需在派生类中重载。
    • Next(AVPacket *pkt):传递任务到责任链的下一个节点。
    • set_next(XThread *xt):设置责任链的下一个节点,线程安全。
  2. 保护成员
    • Main():线程入口函数,纯虚函数,需要在派生类中实现。
    • is_exit_:标志线程是否退出。
    • index_:线程索引号,用于标识不同的线程。
  3. 私有成员
    • th_std::thread 对象,用于实际的线程操作。
    • m_std::mutex 对象,用于保护对 next_ 成员的访问,确保线程安全。
    • next_:指向责任链中的下一个节点。

应用场景】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 播放视频
bool XCameraWidget::Open(const char *url)
{
if (demux_)
demux_->Stop();
if (decode_)
decode_->Stop();

// 创建XDemux任务
demux_ = new XDemuxTask();
if (!demux_->Open(url))
{
return false;
}

// 创建XDecode任务
decode_ = new XDecodeTask();

...

// 【责任链】在demux_中设置下一个任务
demux_->set_next(decode_);

...

// 启动解封装和解码线程
/*
在demux_的Start()函数中会调用XDemuxTask中的Main()函数
在Main()函数中会调用基类XThread中的Next(&pkt)函数

在基类XThread中定义的
virtual void Next(AVPacket *pkt)
{
std::unique_lock<std::mutex> lock(m_);
if (next_)
next_->Do(pkt);
}

接着会调用next_的Do函数,也就是decode_中的Do函数
*/
demux_->Start();
decode_->Start();

return true;
}

Start()

作用:启动线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void XThread::Start()
{
unique_lock<mutex> lock(m_);

// i 是一个静态局部变量,它的值在所有 XThread 实例中是共享的。
// 每调用一次 Start 方法,i 的值就会增加 1,并将其赋值给当前线程的 index_ 成员变量。
// 这为每个 XThread 实例分配了一个唯一的索引。
static int i = 0;
i++;
index_ = i;

// is_exit_为false表明线程不应该退出。这用于控制线程的运行状态。
// 这通常用于在 Main 方法中的循环中检查 is_exit_ 标志,以决定是否继续运行还是退出。
is_exit_ = false;

// 开始启动!
// 使用 std::thread 创建并启动一个新线程,执行 Main 方法。
// &XThread::Main 是 Main 方法的指针,this 是当前对象的指针,确保 Main 方法在当前对象的上下文中运行。
th_ = thread(&XThread::Main, this);
stringstream ss;
ss << "XThread::Start() " << index_;

LOGINFO(ss.str());
}

Stop()

作用:停止线程(设置退出标志,等待线程退出)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void XThread::Stop()
{
stringstream ss;
ss << "XThread::Stop() begin " << index_;
LOGINFO(ss.str());

// 将 is_exit_ 标志设置为 true,通知线程应该退出。
// 这通常用于在 Main 方法中的循环中检查 is_exit_ 标志,以决定是否继续运行还是退出。
is_exit_ = true;

// 检查 th_ 是否可等待,即线程是否正在运行。
// 如果线程正在运行,则调用 join 方法等待线程结束。
// join 方法会阻塞当前线程,直到 th_ 代表的线程执行完成。
if (th_.joinable()) // 判断线程是否可以等待
th_.join(); // 等待子线程退出

ss.str("");
ss << "XThread::Stop() end " << index_;
LOGINFO(ss.str());
}

XPara

Create()

作用:防止直接在栈上创建 XPara 对象。

1
2
3
4
XPara *XPara::Create()
{
return new XPara();
}

XAVPacketList

Pop()

作用:返回List栈中的AVPacket

1
2
3
4
5
6
7
8
AVPacket *XAVPacketList::Pop()
{
unique_lock<mutex> lock(mux_);
if (pkts_.empty()) return nullptr;
auto pkt = pkts_.front();
pkts_.pop_front();
return pkt;
}

Push()

作用:用于将 AVPacket 对象推入到一个线程安全的列表中,并在列表超出最大容量时进行清理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void XAVPacketList::Push(AVPacket *pkt)
{
unique_lock<mutex> lock(mux_);
// 生成新的AVPacket 对象 引用计数+1
auto p = av_packet_alloc();

// 增加 pkt 的引用计数并将其数据复制到新分配的 AVPacket 对象 p 中。
// 这可以减少数据复制,同时保证线程安全。
av_packet_ref(p, pkt);

pkts_.push_back(p);


/*
当列表大小超过最大容量 max_packets_ 时,自动清理最早的非关键帧包,直到遇到关键帧或列表为空。
这确保了列表中保留关键帧以后的数据,从而在需要时可以快速恢复到关键帧后的状态。
*/
if (pkts_.size() > max_packets_)
{
// 处理第一帧
if (pkts_.front()->flags & AV_PKT_FLAG_KEY) // 到了视频关键帧,就舍去这一段
{
av_packet_free(&pkts_.front()); // 清理
pkts_.pop_front(); // 出队
return;
}


/*
在视频编码中,关键帧(I-frame)是独立存在的帧,可以独立解码。
非关键帧(P-frame 和 B-frame)则依赖于之前的帧进行解码。

如果缺少了关键帧,那么后续的非关键帧将无法正确解码。

处理队列中的第一帧:
如果队列中的第一帧是关键帧(I-frame),则直接清理这个关键帧并出队,然后返回。
这样可以腾出空间但保留了后续帧的完整性。

清理非关键帧之前的数据:
如果第一帧不是关键帧,则进入 while 循环,逐个清理非关键帧,直到遇到一个关键帧或队列为空。
清理操作包括释放 AVPacket 对象的内存,并将其从队列中移除。
*/
while (pkts_.empty())
{
if (pkts_.front()->flags & AV_PKT_FLAG_KEY) // 到了视频关键帧,就舍去这一段
{
return;
}
av_packet_free(&pkts_.front()); // 清理
pkts_.pop_front(); // 出队
}
}
}