XCodec

作用:编码和解码的基类

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
#pragma once
#include <mutex>
#include <vector>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}

struct AVCodecContext;
struct AVPacket;
struct AVFrame;

class XCodec
{
public:

static AVCodecContext* Create(int codec_id, bool is_encode);

void set_c(AVCodecContext* c);

bool SetOpt(const char* key, const char* val);
bool SetOpt(const char* key, int val);

bool Open();

AVFrame *CreateFrame();

protected:
AVCodecContext* c_ = nullptr; // 编码器上下文
std::mutex mux_; // 编码器上下文锁
};

参数介绍

1
2
3
protected:
AVCodecContext* c_ = nullptr; // 编码器上下文
std::mutex mux_; // 编码器上下文锁

XCodec.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
#include "xcodec.h"
#include <iostream>
#include "xtools.h"

using namespace std;

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}

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

AVCodecContext* XCodec::Create(int codec_id, bool is_encode)
{
const AVCodec *codec = nullptr;
if(is_encode)
codec = avcodec_find_encoder((AVCodecID)codec_id);
else
codec = avcodec_find_decoder((AVCodecID)codec_id);
if (!codec)
{
cerr << "avcodec_find_encoder failed! " << codec_id << endl;
return nullptr;
}

auto c = avcodec_alloc_context3(codec);
if (!c)
{
cerr << "avcodec_alloc_context3 failed! " << codec_id << endl;
return nullptr;
}

c->time_base = { 1, 25 };
c->pix_fmt = AV_PIX_FMT_YUV420P;
c->thread_count = 16;

return c;
}

void XCodec::set_c(AVCodecContext* c)
{
unique_lock<mutex> lock(mux_);
if (c_)
{
avcodec_free_context(&c_);
}

this->c_ = c;
}

bool XCodec::SetOpt(const char* key, const char* val)
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;
auto re = av_opt_set(c_->priv_data, key, val, 0);
if (re != 0)
{
cerr << "av_opt_set failed!";
PrintErr(re);
}

return true;
}

bool XCodec::SetOpt(const char* key, int val)
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;
auto re = av_opt_set_int(c_->priv_data, key, val, 0);
if (re != 0)
{
cerr << "av_opt_set failed!";
PrintErr(re);
}

return true;
}

bool XCodec::Open()
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;
auto re = avcodec_open2(c_, NULL, NULL);
if (re != 0)
{
PrintErr(re);
return false;
}

return true;
}

AVFrame* XCodec::CreateFrame()
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;

auto frame = av_frame_alloc();
frame->width = c_->width;
frame->height = c_->height;
frame->format = c_->pix_fmt;
auto re = av_frame_get_buffer(frame, 0);
if (re != 0)
{
av_frame_free(&frame);
PrintErr(re);
return nullptr;
}
return frame;
}

Create()

作用:

  • 创建编解码上下文

  • @para codec_id 编码器Id号对于ffmpeg

  • @return AVCodecContext 编码器上下文,失败返回nullptr

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
AVCodecContext* XCodec::Create(int codec_id, bool is_encode)
{
// 1. 找到编码器 or 解码器
// 根据is_encode来判断,如果为true找编码器,false找解码器
const AVCodec *codec = nullptr;
if(is_encode)
codec = avcodec_find_encoder((AVCodecID)codec_id);
else
codec = avcodec_find_decoder((AVCodecID)codec_id);
if (!codec)
{
cerr << "avcodec_find_encoder failed! " << codec_id << endl;
return nullptr;
}

// 2. 为编码器\解码器创建上下文
auto c = avcodec_alloc_context3(codec);
if (!c)
{
cerr << "avcodec_alloc_context3 failed! " << codec_id << endl;
return nullptr;
}

// 3. 设置参数默认值
c->time_base = { 1, 25 }; // 时间基准
c->pix_fmt = AV_PIX_FMT_YUV420P;
c->thread_count = 16; // 线程数

return c;
}

编码器\解码器上下文

在 FFmpeg 中,AVCodecContext 是一个非常重要的结构体,用于存储编解码器的上下文信息。这个上下文包含了大量关于编解码器的配置、状态和其他重要的信息,用于控制和管理音视频的编码和解码过程。

作用和功能:

  1. 参数配置
    • AVCodecContext 包含了编码或解码过程中需要的大量参数,例如编码器的比特率、帧率、分辨率、采样率、像素格式等。这些参数可以通过 AVCodecContext 进行设置和调整,以控制编解码器的行为。
  2. 状态管理
    • 编解码器的状态信息(如当前处理的帧、缓冲区状态等)都保存在 AVCodecContext 中。在编码或解码过程中,上下文会随着处理的进展而更新,确保每一步都能正确进行。
  3. 资源管理
    • AVCodecContext 还负责管理编解码过程中使用的各种资源,包括内部的缓冲区、滤波器、线程等。这些资源在上下文被分配时初始化,并在上下文被释放时清理。
  4. 接口与扩展
    • 通过 AVCodecContext,用户可以调用 FFmpeg 提供的各种接口函数,如 avcodec_open2avcodec_send_packetavcodec_receive_frame 等。这些函数以上下文为参数,执行具体的编解码操作。

set_c()

作用:用于将创建的上下文c赋值给类中的上下文c_,若传入的参数为nullptr,则相当于清理上下文资源。

设置对象的编码器上下文 上下文传递到对象中,资源由XEncode维护
线程安全 需要+锁
@para c 编码器上下文 如果c_不为nullptr,则先清理资源,然后再赋值

1
2
3
4
5
6
7
8
9
10
void XCodec::set_c(AVCodecContext* c)
{
unique_lock<mutex> lock(mux_);
if (c_)
{
avcodec_free_context(&c_);
}

this->c_ = c;
}

SetOpt()——接受字符串参数

作用:用于设置编解码器上下文的选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool XCodec::SetOpt(const char* key, const char* val)
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;

/*
auto re = av_opt_set(c_->priv_data, key, val, 0);:调用 av_opt_set 函数设置选项。
av_opt_set 用于设置 AVClass 对象的选项。

c_->priv_data:指向编解码器私有数据的指针。
key:要设置的选项的名称。
val:要设置的选项的值。
0:标志参数,通常为 0。
*/
auto re = av_opt_set(c_->priv_data, key, val, 0);
if (re != 0)
{
cerr << "av_opt_set failed!";
PrintErr(re);
}

return true;
}

av_opt_set(c_->priv_data, key, val, 0)其中的keyval参数

视频编码器常用选项

  1. preset
    • 描述:预设选项,用于快速配置编码器的参数,影响编码速度和质量。
    • 可能值ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
    • 示例key = "preset", val = "fast"
  2. crf(适用于如 libx264 编码器):
    • 描述:恒定质量参数,控制视频的质量与文件大小之间的平衡。
    • 可能值:范围从 0 到 51,值越小质量越高,文件越大。
    • 示例key = "crf", val = "23"
  3. bitrate
    • 描述:目标比特率,控制视频的编码比特率。
    • 可能值:任何正整数,通常以 k 为单位表示。
    • 示例key = "bitrate", val = "2000k"
  4. profile
    • 描述:编码器的配置档案。
    • 可能值baseline, main, high
    • 示例key = "profile", val = "high"
  5. g(GOP 大小):
    • 描述:关键帧间隔,控制两个关键帧之间的帧数。
    • 可能值:任何正整数。
    • 示例key = "g", val = "250"

音频编码器常用选项

  1. bit_rate
    • 描述:目标音频比特率。
    • 可能值:任何正整数,通常以 k 为单位表示。
    • 示例key = "bit_rate", val = "128k"
  2. sample_rate
    • 描述:音频采样率。
    • 可能值:常见值有 44100, 48000, 96000 等。
    • 示例key = "sample_rate", val = "44100"
  3. channels
    • 描述:音频通道数。
    • 可能值1(单声道), 2(立体声)
    • 示例key = "channels", val = "2"

其他常用选项

  1. threads
    • 描述:线程数量,控制编码或解码时使用的线程数量。
    • 可能值:任何正整数。
    • 示例key = "threads", val = "4"
  2. max_b_frames
    • 描述:最大 B 帧数量。
    • 可能值:任何正整数。
    • 示例key = "max_b_frames", val = "2"

SetOpt()——接收整数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
bool XCodec::SetOpt(const char* key, int val)
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;
auto re = av_opt_set_int(c_->priv_data, key, val, 0);
if (re != 0)
{
cerr << "av_opt_set failed!";
PrintErr(re);
}

return true;
}

上述SetOpt()的区别

  1. 参数类型
    • 之前的 SetOpt 函数接受字符串类型的选项值,适用于设置那些需要字符串的选项(如编码器预设、编码配置档案等)。
    • 新的 SetOpt 函数接受整数类型的选项值,适用于设置那些需要整数的选项(如比特率、线程数量、GOP 大小等)。
  2. 使用的 FFmpeg 函数
    • 之前的 SetOpt 函数使用 av_opt_set 来设置字符串类型的选项。
    • 新的 SetOpt 函数使用 av_opt_set_int 来设置整数类型的选项。

示例

以下是使用两个 SetOpt 函数的示例:

1
2
3
4
5
6
7
8
9
10
XCodec codec;

// 设置字符串类型的选项
codec.SetOpt("preset", "fast"); // 设置编码预设
codec.SetOpt("profile", "high"); // 设置编码档案

// 设置整数类型的选项
codec.SetOpt("bitrate", 2000000); // 设置目标比特率(单位:比特)
codec.SetOpt("threads", 4); // 设置线程数量
codec.SetOpt("g", 250); // 设置关键帧间隔

在这些示例中,SetOpt 函数被用来设置各种编解码器选项,以配置编码和解码行为。通过这些选项,可以灵活地控制编码和解码的参数,满足具体的音视频处理需求。

Open()

作用:打开编解码器,线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool XCodec::Open()
{
unique_lock<mutex> lock(mux_);
if (!c_) return false;
/*
用于打开指定的编解码器,以便进行视频或音频的编码或解码。
它的作用是将编解码器上下文与实际的编解码器关联起来,从而使得该上下文可以用于编码或解码操作。
*/
auto re = avcodec_open2(c_, NULL, NULL);
if (re != 0)
{
PrintErr(re);
return false;
}

return true;
}

avcodec_open2 是 FFmpeg 库中的一个函数,用于初始化一个编解码器上下文,以便进行编码或解码操作。该函数会根据指定的编解码器打开 AVCodecContext,并分配必要的资源。

函数原型

1
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

参数解释

  1. AVCodecContext *avctx
    • 指向要初始化的编解码器上下文的指针。这个上下文必须已经通过 avcodec_alloc_context3 分配,并设置了基本参数。
  2. const AVCodec *codec
    • 指向要使用的编解码器的指针。如果传递 NULL,则使用上下文中已经设置的编解码器。
  3. AVDictionary **options
    • 一个指向字典的指针,用于传递额外的选项和参数。如果不需要传递额外的选项,可以传递 NULL

示例解释

1
int result = avcodec_open2(c_, NULL, NULL);

在这个示例中:

  • c_:指向要初始化的 AVCodecContext 的指针。
  • NULL(第一个):表示使用上下文中已经设置的编解码器。
  • NULL(第二个):表示没有额外的选项需要传递。

CreateFrame()

作用:根据AVcodecContext 创建一个AVFrame 需要调用者释放av_frame_free

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

auto frame = av_frame_alloc();
frame->width = c_->width;
frame->height = c_->height;
frame->format = c_->pix_fmt;

// auto re = av_frame_get_buffer(frame, 0);
// 调用 av_frame_get_buffer 为帧分配缓冲区,以存储帧的数据。
// 第二个参数 0 表示使用默认对齐方式。
auto re = av_frame_get_buffer(frame, 0);
if (re != 0)
{
av_frame_free(&frame);
PrintErr(re);
return nullptr;
}
return frame;
}

通过 AVCodecContext 创建 AVFrame 可以确保帧的参数与编解码器的参数一致,简化编码和解码流程,保证资源分配正确,并提高数据处理效率。这样可以有效减少编码和解码过程中可能出现的错误,提高程序的稳定性和性能。