XMux.h

继承于XFormat

作用:处理多媒体文件的封装,提供了一系列函数用于打开封装上下文、写入数据包、写入头信息和结束信息,同时处理音视频的时间基准。

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
#pragma once
#include "xformat.h"

class XMux : public XFormat
{
public:

static AVFormatContext *Open(const char *url,
AVCodecParameters *video_para = nullptr,
AVCodecParameters *audio_para = nullptr
);

bool WriteHead();

bool Write(AVPacket *pkt);

bool WriteEnd();

void set_src_video_time_base(AVRational *tb);
void set_src_audio_time_base(AVRational *tb);

~XMux();

private:

AVRational *src_video_time_base_ = nullptr;
AVRational *src_audio_time_base_ = nullptr;

long long begin_video_pts_ = -1;
long long begin_audio_pts_ = -1;
};

参数说明:

1
2
3
4
5
6
7
private:

AVRational *src_video_time_base_ = nullptr;
AVRational *src_audio_time_base_ = nullptr;

long long begin_video_pts_ = -1; // 原视频开始时间
long long begin_audio_pts_ = -1; // 原音频开始时间

src_video_time_base_

参数 src_video_time_base_XMux 类中起到了关键作用,主要用于管理视频流的时间基准。时间基准(Time Base)是指视频或音频流中时间戳的单位和精度。以下是具体作用和使用场景的详细分析:

作用

  1. 时间戳转换
    • 视频流中的每个数据包(frame)都有一个时间戳(timestamp),表示这个数据包在视频播放中的具体时间点。src_video_time_base_ 用于定义这些时间戳的单位和精度,确保时间戳在封装和解封装过程中保持一致。
  2. 时间基准设置
    • set_src_video_time_base 函数用于设置 src_video_time_base_ 的值。如果传入的 AVRational 指针非空,则将其值赋给 src_video_time_base_。这通常在封装开始之前调用,以确保视频流的时间戳基准正确设置。
  3. 时间戳调整
    • 在写入数据包时,需要根据 src_video_time_base_ 进行时间戳的调整和转换,以确保写入的数据包时间戳与封装文件的时间基准一致。

使用场景

  • 封装视频流
    • 在封装(muxing)过程中,视频流的数据包需要按照正确的时间戳顺序写入文件。src_video_time_base_ 确保这些时间戳按照预期的时间基准进行转换和写入。
  • 多媒体处理
    • 在处理包含多个视频流或音频流的多媒体文件时,每个流可能有不同的时间基准。src_video_time_base_ 用于统一和管理这些时间基准,以确保时间戳的一致性和准确性。

AVFormatContext

AVFormatContext 是一个多媒体文件的容器,管理文件的整体格式信息,包括文件的输入输出、流信息等。

主要作用

  1. 文件格式信息
    • 管理多媒体文件的格式信息(如文件类型、文件名等)。
  2. 多媒体流管理
    • 包含了文件中所有音频、视频、字幕流的信息。
    • 通过 streams 数组存储每个流的 AVStream 信息。
  3. I/O 管理
    • 负责文件的输入输出操作,如读取或写入文件数据。
    • 包含 AVIOContext 指针,用于管理具体的 I/O 操作。

主要字段

  • AVIOContext *pb:指向 I/O 上下文,用于管理文件的读写。
  • unsigned int nb_streams:文件中包含的流的数量。
  • AVStream **streams:指向流数组,每个流由一个 AVStream 结构体表示。

与AVCodecContext的区别

AVFormatContextAVCodecContext 是 FFmpeg 库中的两个核心结构体,分别用于管理多媒体文件的格式和编解码过程。

AVCodecContext 是一个编解码器的上下文,用于管理音视频数据的编解码过程。

主要作用

  1. 编解码参数管理
    • 包含了编解码器的相关参数(如比特率、采样率、帧率、分辨率等)。
  2. 编解码操作
    • 负责具体的编解码操作,包括初始化、编码、解码、关闭等。
  3. 编解码器配置
    • 需要与特定的 AVCodec 配合使用,AVCodec 描述了具体的编解码器(如 H.264、AAC 等)。

主要字段

  • AVCodec *codec:指向具体的编解码器。
  • int bit_rate:比特率。
  • int width, height:视频的宽度和高度。
  • int sample_rate:音频的采样率。
  • AVRational time_base:时间基准,用于时间戳的转换。

关系与区别

  • 管理层级
    • AVFormatContext 主要用于管理文件级别的格式信息和流信息。
    • AVCodecContext 主要用于管理单个流的编解码过程。
  • 职责范围
    • AVFormatContext 负责文件的整体操作,包括读取、写入、格式解析等。
    • AVCodecContext 负责具体的数据处理,包括编码、解码等。
  • 使用场景
    • 在处理多媒体文件时,首先使用 AVFormatContext 打开文件并读取流信息,然后对每个流使用相应的 AVCodecContext 进行编解码操作。

XMux.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
#include "xmux.h"

#include <iostream>
#include <thread>

using namespace std;

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

static void PrintErr(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << buf << endl;
}
#define BERR(err) if(err != 0) {PrintErr(err); return 0;}


void XMux::set_src_video_time_base(AVRational *tb)
{
if (!tb) return;
unique_lock<mutex> lock(mux_);
if (!src_video_time_base_)
src_video_time_base_ = new AVRational();
*src_video_time_base_ = *tb;
}
void XMux::set_src_audio_time_base(AVRational *tb)
{
if (!tb) return;
unique_lock<mutex> lock(mux_);
if (!src_audio_time_base_)
src_audio_time_base_ = new AVRational();
*src_audio_time_base_ = *tb;
}


XMux::~XMux()
{
unique_lock<mutex> lock(mux_);
if (src_video_time_base_)
delete src_video_time_base_;
src_video_time_base_ = nullptr;

if (src_audio_time_base_)
delete src_audio_time_base_;
src_audio_time_base_ = nullptr;
}


AVFormatContext *XMux::Open(const char *url,
AVCodecParameters *video_para,
AVCodecParameters *audio_para)
{
AVFormatContext *c = nullptr;

auto re = avformat_alloc_output_context2(&c, NULL, NULL, url);
BERR(re);

if (video_para)
{
auto vs = avformat_new_stream(c, NULL);
avcodec_parameters_copy(vs->codecpar, video_para);
}
if (audio_para)
{
auto as = avformat_new_stream(c, NULL);
avcodec_parameters_copy(as->codecpar, audio_para);
}

re = avio_open(&c->pb, url, AVIO_FLAG_WRITE);
BERR(re);
av_dump_format(c, 0, url, 1);

return c;
}


bool XMux::Write(AVPacket *pkt)
{
if (!pkt) return false;
unique_lock<mutex> lock(mux_);
if (!c_) return false;

if (pkt->pts == AV_NOPTS_VALUE)
{
pkt->pts = 0;
pkt->dts = 0;
}
if (pkt->stream_index == video_index_)
{
if (begin_video_pts_ < 0)
begin_video_pts_ = pkt->pts;
lock.unlock();
RescaleTime(pkt, begin_video_pts_, src_video_time_base_);
lock.lock();
}
else if (pkt->stream_index == audio_index_)
{
if (begin_audio_pts_ < 0)
begin_audio_pts_ = pkt->pts;
lock.unlock();
RescaleTime(pkt, begin_audio_pts_, src_audio_time_base_);
lock.lock();
}

auto re = av_interleaved_write_frame(c_, pkt);
BERR(re);

return true;
}


bool XMux::WriteEnd()
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;
av_interleaved_write_frame(c_, nullptr);
auto re = av_write_trailer(c_);
BERR(re);

return true;
}


bool XMux::WriteHead()
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;

cout << "============================" << endl;
auto re = avformat_write_header(c_, nullptr);
BERR(re);
cout << "============================" << endl;

av_dump_format(c_, 0, c_->url, 1);

this->begin_audio_pts_ = -1;
this->begin_video_pts_ = -1;

return true;
}

set_src_video/audio_time_base()

作用:设置视频和音频的时间基准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void XMux::set_src_video_time_base(AVRational *tb)
{
if (!tb) return;
unique_lock<mutex> lock(mux_);
if (!src_video_time_base_)
src_video_time_base_ = new AVRational();
*src_video_time_base_ = *tb;
}
void XMux::set_src_audio_time_base(AVRational *tb)
{
if (!tb) return;
unique_lock<mutex> lock(mux_);
if (!src_audio_time_base_)
src_audio_time_base_ = new AVRational();
*src_audio_time_base_ = *tb;
}

~XMux()

作用:析构函数,用于清理时间基准,防止出现内存泄漏

1
2
3
4
5
6
7
8
9
10
11
XMux::~XMux()
{
unique_lock<mutex> lock(mux_);
if (src_video_time_base_)
delete src_video_time_base_;
src_video_time_base_ = nullptr;

if (src_audio_time_base_)
delete src_audio_time_base_;
src_audio_time_base_ = nullptr;
}

Open()

用于创建和初始化一个 AVFormatContext 上下文,并为其添加视频和音频流。

Open 函数的主要作用是:

  1. 创建一个新的 AVFormatContext 上下文,用于输出多媒体文件。
  2. 根据传入的视频和音频参数添加视频和音频流。
  3. 打开输入输出(I/O)上下文,准备写入操作。
  4. 将输出文件 urlAVFormatContext相关联。
  5. 返回初始化好的 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
AVFormatContext *XMux::Open(const char *url, 
AVCodecParameters *video_para,
AVCodecParameters *audio_para)
{
AVFormatContext *c = nullptr;
// 创建上下文
auto re = avformat_alloc_output_context2(&c, NULL, NULL, url);
BERR(re);

// 如果提供了音视频参数,则添加视频音频流
if (video_para)
{
auto vs = avformat_new_stream(c, NULL); // 创建一个新的视频流,并将其添加到上下文 c 中。
avcodec_parameters_copy(vs->codecpar, video_para); // 将传入的 video_para 参数复制到新创建的视频流的编解码器参数中。
}
if (audio_para)
{
auto as = avformat_new_stream(c, NULL); // 创建一个新的音频流,并将其添加到上下文 c 中。
avcodec_parameters_copy(as->codecpar, audio_para); // 将传入的 audio_para 参数复制到新创建的音频流的编解码器参数中。
}

// 打开指定的 URL 进行写操作,并将打开的 I/O 上下文指针赋值给 c->pb。
re = avio_open(&c->pb, url, AVIO_FLAG_WRITE);
BERR(re);
av_dump_format(c, 0, url, 1); // 打印格式信息,用于调试和验证。

return c;
}

Write()

作用:用于将外部编码后的数据包(AVPacket)写入多媒体文件中。

Write 函数的主要作用是:

  1. 检查数据包的有效性。
  2. 根据数据包的时间戳和流类型进行时间戳调整。
  3. 将调整后的数据包写入多媒体文件。
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
bool XMux::Write(AVPacket *pkt)
{
if (!pkt) return false;
unique_lock<mutex> lock(mux_);
if (!c_) return false;

// 检查数据包的显示时间戳(PTS)是否为无效值 AV_NOPTS_VALUE。
// 重构考虑通过duration计算
if (pkt->pts == AV_NOPTS_VALUE)
{
pkt->pts = 0;
pkt->dts = 0;
}
if (pkt->stream_index == video_index_) // 如果数据包属于视频流
{
if (begin_video_pts_ < 0)
begin_video_pts_ = pkt->pts;

// 这里解锁,是因为RescaleTime中也有锁,防止互斥
lock.unlock();
// 对时间戳进行重新缩放
RescaleTime(pkt, begin_video_pts_, src_video_time_base_);
lock.lock();
}
else if (pkt->stream_index == audio_index_)
{
if (begin_audio_pts_ < 0)
begin_audio_pts_ = pkt->pts;
lock.unlock();
RescaleTime(pkt, begin_audio_pts_, src_audio_time_base_);
lock.lock();
}

// 写入一帧数据,内部缓冲排序dts,通过pkt=null可写入缓冲
auto re = av_interleaved_write_frame(c_, pkt);
BERR(re);

return true;
}

WriteEnd()

作用:用于结束音视频数据的写入操作。该函数的主要目的是在所有数据包都写入后,写入文件尾部信息并清理资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool XMux::WriteEnd()
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;

// av_interleaved_write_frame表示将已经排序好的缓冲数据写入文件。
// 这个操作会将内部缓存中的所有数据刷新到输出文件中。
av_interleaved_write_frame(c_, nullptr);

// 调用 av_write_trailer 函数写入文件尾部信息。
// 这是文件写入的最后一步,通常包括写入文件结束标志、清理和关闭文件等操作。
auto re = av_write_trailer(c_);
BERR(re);

return true;
}

WriteHead()

作用:用于在音视频数据写入之前,初始化文件头并设置一些初始状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool XMux::WriteHead()
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;

cout << "============================" << endl;
auto re = avformat_write_header(c_, nullptr);
BERR(re);
cout << "============================" << endl;

// 打印输出上下文
// 调用 av_dump_format 函数打印输出上下文信息,便于调试和确认输出文件的格式。
// c_ 是输出上下文指针,0 是索引,c_->url 是输出文件的URL或路径,1 表示输出是输出格式信息。
av_dump_format(c_, 0, c_->url, 1);

// 初始化音频和视频的起始时间戳。
// 这些变量用于后续的时间戳处理,以确保音视频数据按正确的时间顺序封装。
this->begin_audio_pts_ = -1;
this->begin_video_pts_ = -1;

return true;
}