XFormat.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
#pragma once
#include "xtools.h"
#include <mutex>

struct AVFormatContext;
struct AVCodecParameters;
struct AVPacket;
struct AVCodecContext;

struct XRational
{
int num;
int den;
};

class XFormat
{
public:

bool CopyPara(int stream_index, AVCodecParameters *dst);

/*
重载!!
由于 AVCodecParameters* 和 AVCodecContext* 都是指针类型,
编译器无法区分它们,因此无法重载这两个函数。

*/
bool CopyPara(int stream_index, AVCodecContext *dts);

std::shared_ptr<XPara> CopyVideoPara();
std::shared_ptr<XPara> CopyAudioPara();

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_; }

bool RescaleTime(AVPacket *pkt, long long offset_pts, XRational time_base);
bool RescaleTime(AVPacket *pkt, long long offset_pts, AVRational* time_base);
long long RescaleToMs(long long pts, int index);

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_;
int video_index_ = 0;
int audio_index_ = 1;
XRational video_time_base_ = {1,25};
XRational audio_time_base_ = {1, 9000};

int video_codec_id_ = 0;
};

参数说明:

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

IsTimeout()

1
2
3
4
5
6
7
8
9
10
11
12
// 判断是否超时
bool IsTimeout()
{
if (NowMs() - last_time_ > time_out_ms_) // 超时
{
last_time_ = NowMs();
is_connected_ = false;
return true;
}

return false;
}

XFormat.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#include "xformat.h"
#include <iostream>
#include <thread>
#include "xtools.h"

using namespace std;

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

#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")


static int TimeoutCallBack(void *para)
{
auto xf = (XFormat*)para;
if (xf->IsTimeout()) return 1;

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;

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


std::shared_ptr<XPara> XFormat::CopyAudioPara()
{
unique_lock<mutex> lock(mux_);
int index = audio_index();
shared_ptr<XPara> re;

if (index < 0 || !c_) return re;

re.reset(XPara::Create());
*re->time_base = c_->streams[index]->time_base;
avcodec_parameters_copy(re->para, c_->streams[index]->codecpar);

return re;
}


std::shared_ptr<XPara> XFormat::CopyVideoPara()
{
unique_lock<mutex> lock(mux_);
int index = video_index();
shared_ptr<XPara> re;

if (index < 0 || !c_) return re;

re.reset(XPara::Create());
*re->time_base = c_->streams[index]->time_base;
avcodec_parameters_copy(re->para, c_->streams[index]->codecpar);

return re;
}


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::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;
}


bool XFormat::RescaleTime(AVPacket *pkt, long long offset_pts, AVRational* time_base)
{
if (!pkt || !time_base) return false;
unique_lock<mutex> lock(mux_);
if (!c_) return false;

auto out_stream = c_->streams[pkt->stream_index];

pkt->pts = av_rescale_q_rnd(pkt->pts - offset_pts, *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, *time_base,
out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
);
pkt->duration = av_rescale_q(pkt->duration, *time_base,
out_stream->time_base
);

pkt->pos = -1;

return true;
}


bool XFormat::RescaleTime(AVPacket *pkt, long long offset_pts, XRational time_base)
{
AVRational in_time_base;
in_time_base.num = time_base.num;
in_time_base.den = time_base.den;

return RescaleTime(pkt, offset_pts, &in_time_base);
}


long long XFormat::RescaleToMs(long long pts, int index)
{
unique_lock<mutex> lock(mux_);
if (!c_ || index < 0 || index > c_->nb_streams) return 0;
auto in_timebase = c_->streams[index]->time_base;

AVRational out_timebase = { 1, 1000 };
return av_rescale_q(pts, in_timebase, out_timebase);
}


void XFormat::set_time_out_ms(int ms)
{
unique_lock<mutex> lock(mux_);
this->time_out_ms_ = ms;

if (c_)
{
AVIOInterruptCB cb = { TimeoutCallBack , this };
c_->interrupt_callback = cb;
}
}

TimeoutCallBack()

作用:一个用于超时检测的回调函数,通过检查 XFormat 对象的 IsTimeout 方法来确定是否超时,并返回相应的结果,用于中断或继续执行阻塞操作。

1
2
3
4
5
6
7
static int TimeoutCallBack(void *para)
{
auto xf = (XFormat*)para;
if (xf->IsTimeout()) return 1; // 超时退出Read

return 0; // 正常阻塞
}

set_c()

作用:函数的主要作用是设置 AVFormatContext 对象,并进行一系列的初始化和清理操作,以确保新的 AVFormatContext 对象能够正确使用。具体步骤包括清理旧的上下文、设置新的上下文、初始化音视频流索引、设定超时回调函数和更新连接状态等。这些操作确保了在多线程环境下安全且有效地管理音视频流的处理。

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
void XFormat::set_c(AVFormatContext *c)
{
unique_lock<mutex> lock(mux_);
if (c_) // 清理原值AVFormatContext
{
if (c_->oformat) // 输出上下文是否存在
{
// pb 是一个指向 AVIOContext 的指针
// 用于管理文件或流的 I/O 操作。
if (c_->pb)
avio_closep(&c_->pb); // 关闭pb
avformat_free_context(c_); // 释放上下文
}
else if (c_->iformat) // 输入上下文是否存在
{
// 不需要avformat_free_context(c_);
// 其内部包含了关闭输入上下文和释放相关资源的逻辑
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 结构体:

AVIOInterruptCB 是 FFmpeg 库中的一个结构体,用于定义 I/O 操作的中断回调。
它的定义如下:

typedef struct AVIOInterruptCB {
int (*callback)(void*);
void *opaque;
} AVIOInterruptCB;

callback:指向回调函数的指针。
opaque:用户自定义的数据指针,会传递给回调函数。
*/

// 设定超时处理 回调函数
if (time_out_ms_ > 0)
{
// 如果超时时间 time_out_ms_ 大于 0,设置回调函数 TimeoutCallBack
// 并将当前对象指针 this 传递给回调函数。
AVIOInterruptCB cb = { TimeoutCallBack , this };

// ☆☆☆ 将cb传递给interrupt_callback
// 这意味着在执行 I/O 操作时
// FFmpeg 会调用 TimeoutCallBack 函数,并传递 this 指针作为参数。
c_->interrupt_callback = cb;
}

// 用于区分是否有音频或者视频流
audio_index_ = -1;
video_index_ = -1;

// 区分音视频
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;
}
}
}

CopyAudioPara()

作用:复制音频参数并将其封装在一个智能指针 std::shared_ptr<XPara> 中返回

XPara的介绍在XTool中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::shared_ptr<XPara> XFormat::CopyAudioPara()
{
// 拿到音频参数,传入智能指针中,返回出去
unique_lock<mutex> lock(mux_);
int index = audio_index(); // 获取音频流的索引
shared_ptr<XPara> re; // 用于存储复制的音频参数

if (index < 0 || !c_) return re;

// reset将智能指针 re 重置为一个新的 Xpara 对象(XPara在XTool中定义)
re.reset(XPara::Create());
*re->time_base = c_->streams[index]->time_base;

// 将 c_ 中对应音频流的编解码参数 codecpar 复制到 re 的 para 成员。
avcodec_parameters_copy(re->para, c_->streams[index]->codecpar);

return re;
}

CopyVideoPara()

作用:复制视频参数并将其封装在一个智能指针 std::shared_ptr<XPara> 中返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::shared_ptr<XPara> XFormat::CopyVideoPara()
{
// 拿到视频参数,传入智能指针中,返回出去
unique_lock<mutex> lock(mux_);
int index = video_index();
shared_ptr<XPara> re;

if (index < 0 || !c_) return re;

re.reset(XPara::Create());
*re->time_base = c_->streams[index]->time_base;
avcodec_parameters_copy(re->para, c_->streams[index]->codecpar);

return re;
}

CopyPara()

作用:用于复制特定流的编解码参数到目标 AVCodecParameters 结构体中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 复制参数 线程安全
// stream_index:对于c_->streams 下标
// dst 输出参数
// return 是否成功
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;

// 将codecpar复制给dst
// 【注】c_->streams[stream_index]->codecpar 是指向 AVCodecParameters 结构体的指针,包含了与该流相关的编解码参数。
// AVCodecParameters 结构体包含了编解码器需要的参数,例如编解码器 ID、比特率、采样率、分辨率等。
auto re = avcodec_parameters_copy(dst, c_->streams[stream_index]->codecpar);
if (re < 0)
return false;

return true;
}

CopyPara()

作用:当前代码的 CopyPara 函数将特定流的编解码参数从 AVFormatContext 中的 AVCodecParameters 复制到目标 AVCodecContext 结构体 dts 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

两个CopyPara()的区别

  1. 目标对象不同

    • 第一段代码的目标对象是 AVCodecParameters *dst

      1
      auto re = avcodec_parameters_copy(dst, c_->streams[stream_index]->codecpar);
    • 第二段代码的目标对象是 AVCodecContext *dts

      1
      auto re = avcodec_parameters_to_context(dts, c_->streams[stream_index]->codecpar);
  2. 使用的函数不同

    • 第一段代码使用了 avcodec_parameters_copy函数,将参数从 AVCodecParameters复制到另一个 AVCodecParameters

      1
      auto re = avcodec_parameters_copy(dst, c_->streams[stream_index]->codecpar);
    • 第二段代码使用了 avcodec_parameters_to_context函数,将参数从 AVCodecParameters复制到 AVCodecContext

      1
      auto re = avcodec_parameters_to_context(dts, c_->streams[stream_index]->codecpar);

总结

这两段代码的主要区别在于复制的目标对象和使用的函数不同。第一段代码将参数复制到 AVCodecParameters 结构体中,而第二段代码将参数复制到 AVCodecContext 结构体中。这种设计是为了满足不同的需求:当需要简单的参数复制时,使用 AVCodecParameters;当需要完整的编解码上下文时,使用 AVCodecContext

RescaleTime()

作用:用于对给定的 AVPacket 进行时间基准的重新缩放。具体来说,它将 AVPacket 的时间戳(PTS 和 DTS)和持续时间根据新的时间基准进行重新计算,并应用一个偏移量。

使用场景

  • 在处理音视频数据时,可能需要对不同时间基准的时间戳进行转换,以便在不同的媒体流之间进行同步或处理。
  • 该函数确保 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
bool XFormat::RescaleTime(AVPacket *pkt, long long offset_pts, AVRational* time_base)
{
if (!pkt || !time_base) return false;
unique_lock<mutex> lock(mux_);
if (!c_) return false;

// 获取 AVPacket 所属流的 AVStream,通过 pkt->stream_index 进行索引。
auto out_stream = c_->streams[pkt->stream_index];

/*
使用 av_rescale_q_rnd 函数重新缩放 PTS(展示时间戳)和 DTS(解码时间戳),并应用偏移量 offset_pts。
av_rescale_q_rnd 函数的参数:
第一个参数:需要重新缩放的值。
第二个参数:当前时间基准。
第三个参数:目标时间基准。
第四个参数:舍入方法,这里使用 AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX,表示尽可能接近目标值,并在必要时通过最小值和最大值进行舍入。
*/
pkt->pts = av_rescale_q_rnd(pkt->pts - offset_pts, *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, *time_base,
out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
);
pkt->duration = av_rescale_q(pkt->duration, *time_base,
out_stream->time_base
);


/*
在 RescaleTime 函数中,将 pkt->pos 赋值为 -1 的主要目的是为了将其标记为无效或未知。
这种做法可以防止后续代码误用不准确的位置信息,并明确表明该数据包的文件位置信息不可用或不相关。
这在处理经过时间基准变换的数据包时尤为重要,确保数据处理过程中的一致性和正确性。
*/
pkt->pos = -1;

return true;
}

RescaleTime()

作用:调用另一个 RescaleTime 函数,该函数接受一个 AVRational 类型的时间基准指针。传递参数 pktoffset_pts 和转换后的 in_time_base,并返回调用结果。

使用场景

  • 当外部代码使用 XRational 类型的时间基准时,可以调用这个函数来简化类型转换的过程,并确保时间基准可以被正确处理。
  • 提供了对 XRational 类型的时间基准的支持,使得代码更加灵活,可以处理多种时间基准表示。
1
2
3
4
5
6
7
8
bool XFormat::RescaleTime(AVPacket *pkt, long long offset_pts, XRational time_base)
{
AVRational in_time_base;
in_time_base.num = time_base.num;
in_time_base.den = time_base.den;

return RescaleTime(pkt, offset_pts, &in_time_base);
}

RescaleToMs()

1
2
3
4
5
6
7
8
9
10
// 把pts dts duration值转为毫秒
long long XFormat::RescaleToMs(long long pts, int index)
{
unique_lock<mutex> lock(mux_);
if (!c_ || index < 0 || index > c_->nb_streams) return 0;
auto in_timebase = c_->streams[index]->time_base;

AVRational out_timebase = { 1, 1000 }; // 输出timebase 毫秒
return av_rescale_q(pts, in_timebase, out_timebase);
}

时间戳(PTS、DTS)

DTS(解码时间戳)和 PTS(显示时间戳)是音视频处理中的两个重要概念。DTS 确保解码顺序正确,而 PTS 确保播放顺序正确。在实际应用中,这两个时间戳共同作用,确保音视频流的同步和正确播放。理解并正确处理 DTSPTS 对于开发高效的音视频处理应用至关重要。

av_rescale_q(pts, in_timebase, out_timebase);

FFmpeg 库中的一个函数,用于在不同时间基准(timebase)之间转换时间戳,既可以计算PTS,也可以计算DTS。其原型定义如下:

1
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq);

其中:

  • a 是要转换的时间戳值。
  • bq 是输入时间基准(in_timebase),以 AVRational 类型表示。
  • cq 是输出时间基准(out_timebase),以 AVRational 类型表示。

AVRational 是一个表示有理数的结构,定义如下:

1
2
3
4
typedef struct AVRational {
int num; // numerator
int den; // denominator
} AVRational;

该函数的作用是将时间戳从 bq 基准转换到 cq 基准,其计算公式为:

$\displaystyle result = a \times \frac{cq.num}{cq.den} ÷ \frac{dq.num}{dq.den}$

例子

假设你有一个时间戳 pts = 1000,输入时间基准是 1/1000(每秒 1000 帧),输出时间基准是 1/1000000(每秒 1000000 微秒),你可以使用 av_rescale_q 函数进行转换。

$\displaystyle 1000\times \frac{1}{100000}÷\frac{1}{1000}=1000000$

转换结果为:rescaled_pts 将从 1000 转换为 1000000

I、P、B帧介绍

PTS

转换 PTS(Presentation Timestamp,显示时间戳)在音视频处理中的作用非常重要,主要包括以下几个方面:

  1. 时间同步

不同的媒体流(例如视频流和音频流)可能有不同的时间基准(timebase)。为了确保不同媒体流在播放时能够同步,需要将这些流的时间戳转换到同一个时间基准。例如,在视频编辑或播放时,需要确保视频和音频在时间上准确对齐。

  1. 格式转换

在进行视频格式转换时,输入和输出格式可能有不同的时间基准。为了正确地转换和显示时间戳,必须将时间戳从输入格式的时间基准转换为输出格式的时间基准。

  1. 帧率变化

当视频的帧率发生变化时,例如从 30fps 转换到 60fps,需要重新计算每一帧的显示时间戳。这种情况下,转换 PTS 有助于确保视频在新的帧率下能够正确播放。

  1. 精确定位

在视频剪辑或处理过程中,精确定位某一帧或某一时刻需要准确的时间戳转换。例如,剪辑工具需要将用户指定的时间点转换为实际的视频帧时间戳,以便于精确地剪切视频。

示例场景

假设你有一个音视频播放器,需要处理不同来源的媒体文件,这些文件的时间基准可能不同。为了使播放器能够正确地解码和播放这些文件,你需要使用 av_rescale_q 将不同时间基准的时间戳统一转换到播放器使用的时间基准。

DTS

在音视频处理领域,除了 PTS(显示时间戳,Presentation Timestamp),还有一个重要的概念是 DTS(解码时间戳,Decoding Timestamp)。理解 DTS 的作用有助于更好地处理和同步音视频数据。

什么是 DTS?

  • DTS(Decoding Timestamp)是指解码时间戳,它标识了视频帧或音频样本在解码过程中的顺序。
  • PTS 不同,PTS 决定了帧或样本的播放顺序,而 DTS 决定了解码顺序。

为什么需要 DTS?

  1. 顺序解码:在一些视频编码格式中(例如H.264),帧的显示顺序和解码顺序可能不同。B帧(双向预测帧)需要参考前后的帧才能解码,因此需要 DTS 来确保解码器按照正确的顺序进行解码。
  2. 处理延迟:一些视频处理操作(例如去交错、降噪等)需要按照解码顺序来进行处理,然后按照 PTS 顺序来显示。如果没有 DTS,解码器无法正确处理这些操作。
  3. 同步播放:在多媒体播放中,需要同步解码和播放多个音视频流。DTS 确保解码顺序正确,而 PTS 确保播放顺序正确。

DTS 和 PTS 的关系

  • 顺序不同:在一些情况下,PTSDTS 可能是相同的,但对于涉及B帧的视频,PTSDTS 会不同。通常,DTS 先于 PTS
  • 计算顺序:解码器首先使用 DTS 进行解码,然后使用 PTS 进行播放。因此,音视频数据在解码和播放之间可能存在缓冲区。

示例场景

假设你有一个视频流,其中包含I帧(关键帧)、P帧(预测帧)和B帧(双向预测帧)。这些帧的解码顺序和显示顺序可能如下:

1
2
3
帧顺序:         I1, B1, P1
解码顺序(DTS):I1, P1, B1
显示顺序(PTS):I1, B1, P1

在这个示例中,DTS 确保帧按照正确的顺序解码,而 PTS 确保帧按照正确的顺序显示,DTS中的B帧需要I帧、P帧解码完毕后,才能进行解码

DTS 的计算与使用

在 FFmpeg 中,DTSPTS 的计算和使用类似,通常通过解码器和复用器自动处理。然而,在一些高级应用中,开发者可能需要手动处理这些时间戳以确保正确的解码和同步。

I、P、B帧

I帧(关键帧,Intra Frame)

  1. 定义:I帧是独立编码的帧,它不依赖其他帧的信息进行解码。

  2. 特点

    • 自包含:I帧包含完整的图像信息,可以独立解码。
    • 高质量:由于包含完整的图像信息,I帧通常质量较高,但也因此占用较大的存储空间。
    • 随机访问:由于不依赖其他帧,可以作为随机访问点,非常适合用于快进、快退等操作。
  3. 应用:I帧通常出现在视频序列的开头或者定期插入视频流中,以便于解码器重新同步和快速跳转。


P帧(预测帧,Predicted Frame)

  1. 定义:P帧是基于前面的I帧或P帧进行预测编码的帧。

  2. 特点

    • 差值编码:P帧只存储与前一帧(参考帧)的差异部分,节省存储空间。
    • 依赖性:P帧的解码依赖于前面的I帧或P帧,需要解码器按顺序解码。
    • 压缩效率:由于只存储变化部分,P帧压缩效率较高。
  3. 应用:P帧广泛用于视频流中,以降低数据量,提高压缩效率。


B帧(双向预测帧,Bi-directional Predicted Frame)

  1. 定义:B帧是基于前面的I帧或P帧和后面的I帧或P帧进行双向预测编码的帧。

  2. 特点

    • 双向预测:B帧可以利用前后两帧的信息进行差值编码,进一步提高压缩效率。
    • 更高压缩率:由于利用了更多的预测信息,B帧通常能达到更高的压缩率。
    • 解码顺序:B帧的解码依赖于前后的参考帧,需要解码器有足够的缓冲区来处理帧顺序。
  3. 应用:B帧用于进一步提高视频流的压缩效率,通常出现在P帧之间。


在视频编码中,帧的排列和依赖关系如下:

  • I帧:独立存在,不依赖其他帧。
  • P帧:依赖前面的I帧或P帧。
  • B帧:依赖前后的I帧或P帧。

I帧、P帧和B帧在视频编码中各自有不同的作用和特点。I帧提供独立的解码点,P帧通过前向预测减少数据量,而B帧通过双向预测进一步提高压缩效率。理解这些帧的特点和作用,有助于更好地进行视频编码、解码和处理。

set_time_out_ms()

作用:set_time_out_ms 函数用于设置超时时间,并在 AVFormatContext 对象存在时,配置一个回调函数 TimeoutCallBack 来处理超时退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 设定超时时间
void XFormat::set_time_out_ms(int ms)
{
unique_lock<mutex> lock(mux_);
this->time_out_ms_ = ms;

// 设置回调函数,处理超时退出
if (c_)
{
AVIOInterruptCB cb = { TimeoutCallBack , this };
c_->interrupt_callback = cb;
}
}