富贵山庄

恒者行远,思者常新

WebCodec 是一个较新的 Web API,它为网页应用提供了接近硬件级的编解码能力。使用 WebCodec,开发者可以在浏览器中直接对音频和视频数据进行高效编解码,这对于实现复杂的媒体应用(如实时视频通信、视频编辑和游戏流)非常有用。

WebCodec 编解码流程

解码

创建解码器

设置解码器名称

WebCodec设置上参数与其它平台不同的一点,在于其名称字段比较繁琐

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
   if (VideoType == H264)
{
...
std::stringstream stream;
stream << "avc1.";
stream << decimalToHex(profile_idc);
stream << decimalToHex(constraint_set_flags);
stream << decimalToHex(level_idc);
codecName = stream.str();
// codecName = "avc1.640020";
}else if(VideoType == H265)
{
std::stringstream stream;
stream << "hev1.";
switch (general_profile_space) {
case 0:
break;
case 1:
stream << 'A';
break;
case 2:
stream << 'B';
break;
case 3:
stream << 'C';
break;
}
stream << general_profile_idc;
stream << '.';

MUInt32 val = (MUInt32)general_profile_compatibility_flag;
int reversed = 0;
for (int i = 0; i < 32; i++)
{
reversed |= val & 1;
if (i == 31)
break;
reversed <<= 1;
val >>= 1;
}
stream << decimalToHex(reversed, 0);
stream << '.';
if (general_tier_flag == 0)
{
stream << 'L';
}
else
{
stream << 'H';
}
stream << general_level_idc;
MBool hasByte = false;

std::string constraint_string = "";
for (int i = 5; i >= 0; i--)
{
int val = 0;
if (general_constraint_indicator_flags[i] != '\0')
{
val = general_constraint_indicator_flags[i];
}
if (val != 0 || hasByte)
{
constraint_string = "." + decimalToHex(val, 0) + constraint_string;
hasByte = true;
}
}
stream << constraint_string;
codecName = stream.str();
// codecName = "hev1.1.6.L120.90";
}
阅读全文 »

std::declval 的作用

std::declval 是 C++ 标准库中的一个工具,用于在不创建对象的情况下获得一个类型的引用。其主要作用是在编译时期在表达式中使用一个类型的实例而不实际构造对象。这在模板元编程和 SFINAE 中尤其有用,因为它允许我们在编译时对类型的潜在成员进行推断和检查。

std::declval<utility> 头文件中定义,并且仅在 unevaluated context(未求值上下文,例如 sizeof 和 decltype 中)是合法的,这意味着它不能用于在运行时创建对象。

std::declval 的使用场景

1. 类型推导

最常见的场景是在使用 decltype 来推导表达式的类型时,尤其是当你需要推导一个类的成员函数的返回类型,但又不想或不能创建这个类的实例。

例如,下面的代码展示了如何使用 std::declval 来推导成员函数的返回类型:

1
2
3
4
5
6
7
8
#include <utility>

struct MyClass {
int myFunction() const { return 42; }
};

// 不需要创建 MyClass 的实例
decltype(std::declval<MyClass>().myFunction()) myVar;

在这里,decltype 用来确定 myFunction 的返回类型,而 std::declval<MyClass>() “假装” 有一个 MyClass 的对象来允许我们调用成员函数。

2. SFINAE 和类型萃取

在 SFINAE(替换失败并非错误)或写类型萃取(trait)时,std::declval 能够确保在没有默认构造函数的情况下依然能进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <type_traits>
#include <utility>

struct MyClass {
using value_type = int;
value_type myFunction() const { return 42; }
};

// 一个用于萃取成员函数返回类型的类型萃取
template<typename T>
struct ReturnType {
using type = decltype(std::declval<T>().myFunction());
};

static_assert(std::is_same<ReturnType<MyClass>::type, int>::value, "Type must be int");

在这个例子中,即使 MyClass 没有默认构造函数,我们也能用 std::declval 来进行编译时检查。

3. 重载决策

在某些复杂的重载决策场景中,你可能希望根据类型是否支持某个操作来选择不同的函数重载。std::declval 可以在不创建对象的情况下 “假设” 该对象的存在,以便进行这样的检查。

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
#include <iostream>
#include <type_traits>
#include <utility>

struct A {};

struct B {
void print() const { std::cout << "B::print" << std::endl; }
};

template<typename T>
typename std::enable_if<std::is_member_function_pointer<decltype(&T::print)>::value>::type
testPrint(T&& t) {
t.print();
}

template<typename T>
void testPrint(T&&) {
std::cout << "No print function" << std::endl;
}

int main() {
A a;
B b;
testPrint(a); // 输出 "No print function"
testPrint(b); // 输出 "B::print"
}

这里 std::declval 在编译时帮助确定 T 类型是否有一个名为 print 的成员函数。

小结

std::declval 是模板编程中一个极为有用的工具,它能够让程序员在不实例化对象的情况下引用任意类型,特别是在仅仅需要类型信息而不需要对象的场合。它通常与 decltype 结合使用,以便在编译时对表达式类型进行操作和推导。由于 std::declval 仅在未求值上下文中有效,因此它

主要用于模板元编程和编译时类型推导,而不是在实际运行时代码中创建对象。

什么是 SFINAE?

SFINAE 是 “Substitution Failure Is Not An Error” 的缩写。这是 C++ 模板元编程中的一个重要概念,它允许在模板类型推导过程中,如果某个替换失败,这个失败不会立即导致编译错误。而是使得编译器简单地放弃这次替换,并尝试其他重载或模板特化。

SFINAE 的用途

SFINAE 最常见的用途包括:

  • 编写类型特征:检查类型是否具有某个成员函数、成员类型或操作符。
  • 函数模板重载决策:在多个函数模板重载之间选择最适合的一个。
  • 类模板特化:在多个类模板特化之间选择最适合的一个。

如何使用 SFINAE

在 C++11 及其之后的版本中,SFINAE 最常见的实现方式是使用 decltypestd::enable_if 和类型特征(如 std::is_integral 等)。下面是一些使用 SFINAE 的例子:

例子 1:使用 std::enable_if 禁用某些函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <type_traits>
#include <iostream>

template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
foo(T t) {
std::cout << "Integral version: " << t << std::endl;
return t;
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
foo(T t) {
std::cout << "Floating point version: " << t << std::endl;
return t;
}

int main() {
foo(10); // Calls the integral version
foo(3.14); // Calls the floating point version
}

在这个例子中,foo 函数模板有两个重载。通过 std::enable_if,我们能够让编译器在模板类型是整型时选择第一个重载,在模板类型是浮点型时选择第二个重载。

例子 2:检查类型是否有成员函数

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
#include <type_traits>
#include <iostream>

template<typename, typename T>
struct has_serialize {

};


template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
// 如果C类型中,包含serialize函数,并且参数符合Args...,则匹配这条规则
template<typename T>
static constexpr auto check(T*)
-> typename std::is_same<
decltype(std::declval<T>().serialize(std::declval<Args>()...)),
Ret
>::type; // 当返回类型为auto时,在参数后通过->来指定返回类型

// 否则匹配这条规则
template<typename>
static constexpr std::false_type check(...);

// 得到C类型check后的结果类型,false_type or true_type
typedef decltype(check<C>(0)) type;

public:
static constexpr bool value = type::value;
};

struct MyType {
void serialize(int) {}
};

int main() {
std::cout << has_serialize<MyType, void(int)>::value << std::endl;
}

在这个例子中,我们使用模板特化和 decltype 来检查 MyType 是否有一个接受 int 参数的 serialize 成员函数,decltype检测函数返回类型时,函数只需声明即可,无需实现。

C++17 中的 SFINAE 友好的特性

C++17 引入了 if constexprauto 类型推导的新特性,让 SFINAE 使用起来更为方便。

例子 3:使用 if constexpr

1
2
3
4
5
6
7
8
9
10
template<typename T>
auto foo(T t) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integral version: " << t << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Floating point version: " << t << std::endl;
} else {
static_assert(false, "Unsupported type");
}
}

这个例子演示了如何在函数模板内部使用 `

if constexpr` 来在编译时选择执行路径,这样可以在不生成错误的情况下根据类型条件编译代码。

小结

SFINAE 是 C++ 模板编程中一项强大的技术,可以用来在编译时根据类型特性来选择不同的代码路径。虽然 SFINAE 可以使代码变得非常灵活,但它也可能使得代码难以阅读和维护。随着 C++ 标准的发展,引入了更简洁的特性如 if constexpr 和概念(C++20 中的 concepts),它们可以使得之前需要 SFINAE 来实现的功能变得更简单和直观。

const

修饰变量

const关键字可以用来防止对象发生变化。一个const对象必须有以下特征:

  • 必须已经初始化
  • 不能被修改
  • 是线程安全的
  • 只能调用const成员函数

1
const int ci = 1;

指针

常量指针

指向常量的指针,简称常量指针。

1
2
const int ci = 1;
const int* pci = &ci;

指针常量

指针本身是一个常量。

1
2
int i = 1;
int const* cpi = &i;
阅读全文 »

为什么要了解类型推断?

C++98

在c++98的年代,只有模板才有类型推断,如果你不使用模板,那么你完全不需要理解类型推断。

C++11

在C++11中,类型推断在模板之外被大量应用。

  • auto
  • lambda captures
  • function return
  • decltype

想要熟练的使用上述新特性,那么就必须要了解类型推断的规则。

阅读全文 »

片段着色器(Fragment Shader)

片段着色器的主要目的是计算每个像素应该输出什么颜色,在片段着色器中可以接收到顶点着色器输出的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

// texture sampler
uniform sampler2D texture1;

void main()
{
FragColor = texture(texture1, TexCoord);
}
阅读全文 »

顶点

顶点用于图元装配,OpenGL支持的图元有三种:点、线、三角形,对于点只需要一个顶点,对于三角形需要三个顶点。

1
2
3
4
5
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};

顶点坐标

OpenGL不是简单的把所有的3D坐标变换成屏幕上的2D像素,仅当3D坐标在3个轴上-1.0到1.0的范围内时才处理它,所有在这个范围内的坐标叫做标准化

NDC
(0,0)是坐标的中心,y轴正方向向上,x轴正方向向右。

阅读全文 »

EGL

通过Surface对象创建一个EGLSurface,将其连接到Surface的BufferQueue上,EGLSurface将会作为一个生产方,会将OpenGL-ES生产的缓冲区加入到该BufferQueue中,然后由该Surface的消费方进行处理。

Surface是一个生产方,EGLSurface也是一个独立的生产方。

一个Surface一次只能和一个EGLSurface进行连接,可以通过断开,然后重新连接另一个EGLSurface。

阅读全文 »

GLSL

OpenGL的着色器使用GLSL语言编写,类C风格。一个着色器由变量声明和main函数组成, 能声明的变量数量是有限的,具体由硬件决定。

顶点着色器样例

1
2
3
4
5
6
7
8
9
10
11
12
13
#version 330 core
// 定义两个属性,通过location指定该属性的索引值
layout(location = 0) in vec4 vPosition;
layout(location = 1) in vec4 aTextureCoord;
// 定义一个全局常量
uniform mat4 uTextureMatrix;
// 输出一个属性给片段着色器
out vec2 yuvTexCoords;
void main() {
// 输出顶点坐标
gl_Position = uTextureMatrix * vPosition;
yuvTexCoords = aTextureCoord;
}
阅读全文 »

通俗来讲,OpenGL是一个图形库,提供了一系列操作图形图像的API。因为OpenGL只是一个规范,具体的实现一般是由显卡提供。类似的图形库还有Windows平台的DirectX、iOS平台的Metal以及新一代的Vulkan

图形渲染管线

图形渲染管线也叫做渲染流水线,指的是将输入的原始图形数据经过渲染管线处理,输出一帧想要的图像的过程。

在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,所以图形渲染管线主要是在将输入的3D坐标转换成2D坐标,再将2D坐标转换成实际有颜色的像素。(将输入的3D坐标画在3D坐标系中,然后根据视锥范围截取一个2D平面,将截取的平面转换为平面坐标显示)

2D坐标精确的表示一个点在2D空间的位置;而像素是这个点的近似值,像素的显示受到屏幕分辨率的限制

阅读全文 »
0%