跨平台开发_RTC程序设计:实时音视频权威指南 2

news2024/11/25 7:41:16

1.2.1 一切皆bit

将8 bit分为一组,我们定义了字节(Byte)。
1956年6月,使用了Byte这个术语,用来表示数字信息的基本单元。
最早的字节并非8 bit。《计算机程序设计的艺术》一书中的MIX机器采用6bit作为1Byte。8 bit的Byte约定,和IBM System/360大型机的研发有关,由其领导者布鲁克斯(Frederick P.Brooks,美国,1931-2022)推行开来。
对于单字节中的位,从左到右,我们依次编号为bit7~bit0

字节是计算机世界中计量大小的基本单位,例如:1KB=1024Byte,1MB=1024KB1GB=1024 MB。这里1024=210。为了方便表示二进制,人们将其4位一组,使用0~F这16个字符进行十六进制编码

字节序

例如,0x12345678,在内存中的存储可能是0x12、0x34、0x56、0x78(内存的高位地址存的是高位)

也可能是0x78、0x56、0x34、0x12。前者被称为大端法(Big-Endian),后者被称为小端法(Little-Endian)。

比如,在计算机系统的本地内存中,Intel与ARM架构常使用小端法表示法,也叫作本机序(Host 0rder)。而在网络传输上,人们习惯于使用大端法,即网络序(Net 0rder)。又如,BMP文件使用的是小端法,而MP4文件使用的是大端法。

位序
但是我们在读取位的时候,会存在两个方向:从bit0读到bit7,或从bit7读到bit0。
例如,对于0x39=0b 00111001,从不同的方向读取,就会得到00111001或者10011100两种结果。
后面我们会看到,位序对于理解图像格式和压缩算法都是非常重要的。例如,对于PNG、GIF图像格式,位序是从bit0开始的。但是对于JPEG图像格式,位序则是从bit7开始的。
比特流与比特率
对于计算机中连续存储的字节单元,我们可以将其看作一个比特流
为了衡量信息的传递速度,我们定义比特率为每秒钟传送的比特,单位为bps(bit persecond)。比特率也称为码率。

bps中的b是小写的。对于字节,用大写的B表示Byte,1Bps=8 bps。
在音视频通话时,若带宽小于300 kbps,则我们称之为低带宽。
低带宽下RTC优化标准为:纯音频最低支持60kbps,音视频最低支持100kbps。
Base64表示法
十六进制通过将4 bit分为一组,得到了0x00~0x0F这16种不同的值
在早期,由于历史原因,电子邮件只允许传输英文字符数据[10]。当传输非字符数据时,网Gateway)会将字节中8位的最高位置为0。
为了能将二进制值用英文字符表示出来,人们设计了Base64编码。
Base64编码将6 bit分为一组,并用26-64个不同的字符来表示。

由于字节是8 bit一组的,当字节数不是3的倍数时,比特数不能被6整除。
Base64编码使用等号字符=在最后做附加(padding),最终可能是3种情况:没有=,1个或者2个=,表示此Base64串末尾有多少个附加字节。

1.2.2 字符管理类

最简单的Buffer类,通常结构如下:

#pragma once
#include "DTypes.h"
typedef struct tagBuffer {
	DByte* pBuf; // 指向二进制数据的开始位置
	DUInt32 nSize; // 二进制数据的字节
};


其中,pBuf指向二进制数据的开始地址,nSize为二进制数据的字节数,如图1-6所示。在开源web服务器Nginx中,字符串就是这样定义的。

我们可以用下面的代码进行一下测试:

#include "Buffer.h"
#include <stdio.h>
int main() {
	tagBuffer buffer;
	//分配多少个字节
	int byteSize = 128;
	buffer.pBuf = (DByte*)malloc(sizeof(DByte) * byteSize);
	buffer.nSize = byteSize;
	//使用buffer
	if (buffer.pBuf) {
		//do something with buffer.pBuf
		for (int i = 0; i < 10; i++) {
			buffer.pBuf[i] = i;
		}
		//show buffer contents
		for (int i = 0; i < 10; i++) {
			printf("%d ", buffer.pBuf[i]);
		}
		//free buffer
		free(buffer.pBuf);
		buffer.pBuf = NULL;
		buffer.nSize = 0;
	}
	return 0;
}

然而上述定义存在很多问题,比如,如何控制内存分配与释放,如何让多个线程安全地共享,如何在函数与线程间高效地传递,减少复制次数
Buffer作为传输通信中最常用的对象,我们需要为其实现更多的易用特性
极小栈消耗。
内存自动回收。
跨线程安全传递。
·写时复制(Copy-on-Write)
·十六进制表示。
·Base64转换。

简单定义

优化定义

本书提供的Buffer实现,采用内存布局。对比简单定义,新的内存布局有如下特点。
(1)栈区只使用了一个指向堆区的指针,这使得占用栈空间的代价极小
(2)栈指针指向堆区Buffer的起始地址,这使得可以随时访问Buffer的内容
(3)在堆区Buffer前面,还有两个成员:nRefCount用来维护Buffer的引用计数;nSize用来表示Buffer的大小

struct DBufferData {
	DInt32 nRefCount;
	DInt32 nSize;
	DByte* pBuf;
};

typedef struct StackBuffer {
	DBufferData* pData;
};
#include "Buffer.h"
#include <stdio.h>
#include <iostream>
using namespace std;
int main() {
	DBufferData data;
	data.pBuf = new DByte[10];
	data.nSize = 10;
	data.nRefCount = 0; // 初始为0

	StackBuffer stackbuffer;
	if (data.pBuf != NULL) {
		//我们让stackbuffer指向它
		stackbuffer.pData = &data;
		//让data的引用计数加1
		data.nRefCount++;
	}
	cout << "stackbuffer.pData->nRefCount = " << stackbuffer.pData->nRefCount << endl;
	stackbuffer.pData->nRefCount--; // 引用计数减1
	stackbuffer.pData = NULL;
	if (data.nRefCount == 0) {
		delete[] data.pBuf; // 引用计数为0,释放内存
		data.pBuf = NULL;
		data.nSize = 0;
	}
	return 0;
}

引用计数

引用计数可以有效地记录对象自身被线程持有的情况:
(1)当Buffer初始化时,其引用计数为1
(2)当需要读取访问或复制构造Buffer时,无须复制Buffer的内容。我们仅需新增一个指向堆区的栈指针,并且让Buffer的引用计数加1。这里的指针,可能来自同一个线程的栈,也可能来自不同线程的栈。
(3)当不再需要访问Buffer时,将其引用计数减1
(4)当引用计数减到0时,对象不再被任何线程持有,可安全地销毁[12]堆区Buffer。
(5)当需要修改Buffer时,根据引用计数的情况,决定是否需要写时复制
由于涉及多个线程之间的共享操作,引用计数必须用原子变量来实现。
在Common文件夹下,有一个DAtomic.h文件,提供了对C++11<atomic>的类型封装。

我们先来进行原子操作的定义。

#pragma once
#ifndef DATOMIC_H
#define DATOMIC_H
#include <atomic>

template<class T>
class DAtomic {
public:
    // 构造函数初始化原子变量
    DAtomic(const T& initialValue = T()) : value(initialValue) {}

    // 原子加载
    T load() const {
        return value.load();
    }

    // 原子存储
    void store(T newValue) {
        value.store(newValue);
    }

    // 原子增加
    T fetch_add(T addValue) {
        return value.fetch_add(addValue);
    }

    // 原子减少
    T fetch_sub(T subValue) {
        return value.fetch_sub(subValue);
    }

private:
    std::atomic<T> value;
};

#endif // DATOMIC_H

测试atomic

#include <iostream>
#include "DAtomic.h"
using namespace std;

int main() {
	DAtomic<int> counter;
	int currentValue = counter.load();
	cout << "Initial value: " << currentValue << endl;
	counter.store(10);
	cout << "New value: " << counter.load() << endl;
	int newValue = counter.fetch_add(5);
	cout << "New value after fetch_add(5): " << newValue << endl;
	newValue = counter.fetch_sub(3);
	cout << "New value after fetch_sub(3): " << newValue << endl;
	cout << "Final value: " << counter.load() << endl;
	return 0;
}

写时复制

当引用计数为1时,我们可以正常地随意读写Buffer内的所有数据。
但当引用计数大于1时,如要修改Buffer的内容,我们就需要对其数据进行复制。这样各个线程仍可以像自己独占Buffer一样,访问各自的Buffer数据,使读写互不影响。

写时复制(Copy-On-Write,简称COW)是一种优化技术,用于减少内存使用和提高性能。其核心思想是,当多个用户共享同一份数据时,只要这些用户只读取而不修改数据,那么它们可以共享同一份物理副本。只有当某个用户尝试修改数据时,才会创建数据的独立副本供该用户使用

  1. 初始状态

    • 当创建一个新的Buffer对象时,它的引用计数为1,表示只有一个所有者。
    • 当通过复制构造函数或赋值操作符创建新的Buffer对象时,引用计数会增加,因为多个对象共享同一份数据。
  2. 读取操作

    • 读取操作不会改变数据,因此不需要创建新的副本。所有共享同一份数据的对象都可以安全地读取数据
  3. 写入操作

    • 当某个Buffer对象尝试修改数据时,首先检查引用计数。
    • 如果引用计数大于1,说明有其他对象也在共享这份数据。此时,需要创建一个新的副本,以便修改操作不会影响其他共享者。
    • 创建新的副本后,修改新的副本,然后更新当前对象的内部状态,使其指向新的副本。
//buffer 原子操作
class DBufferAtomic {
public:
	DBufferAtomic(size_t size) : buffer(size) , m_nRefCount(1) {}
	DBufferAtomic(DBufferAtomic& other) : buffer(other.buffer){
		other.m_nRefCount.fetch_add(1);
		m_nRefCount.store(other.m_nRefCount.load());
	}
	DBufferAtomic& operator=(DBufferAtomic& other) {
		if (this!= &other) {
			other.m_nRefCount.fetch_add(1);
			m_nRefCount.store(other.m_nRefCount.load());
			buffer = other.buffer;
		}
		return *this;
	}
	
	const std::vector<char>& getData() const {
		return buffer;
	}
	void modifyData(size_t index, char value) {
		if (m_nRefCount.load() > 1) {
			//复制一份数据
			std::vector<char> temp(buffer);
			temp[index] = value;
			buffer = temp;
			(*this).buffer = std::move(temp);
		}
		else {
			buffer[index] = value;
		}
	}
private:
	DAtomic<int> m_nRefCount;
	std::vector<char> buffer;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2247154.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WIFI:长GI与短GI有什么区别和影响

1、GI的作用 Short GI(Guard Interval)是802.11n针对802.11a/g所做的改进。射频芯片在使用OFDM调制方式发送数据时&#xff0c;整个帧是被划分成不同的数据块进行发送的&#xff0c;为了数据传输的可靠性&#xff0c;数据块之间会有GI&#xff0c;用以保证接收侧能够正确的解析…

ssm实战项目──哈米音乐(二)

目录 1、流派搜索与分页 2、流派的添加 3、流派的修改 4、流派的删除 接上篇&#xff1a;ssm实战项目──哈米音乐&#xff08;一&#xff09;&#xff0c;我们完成了项目的整体搭建&#xff0c;接下来进行后台模块的开发。 首先是流派模块&#xff1a; 在该模块中采用分…

C++使用minio-cpp(minio官方C++ SDK)与minio服务器交互简介

目录 minio简介minio-cpp简介minio-cpp使用 minio简介 minio是一个开源的高性能对象存储解决方案&#xff0c;完全兼容Amazon S3 API&#xff0c;支持分布式存储&#xff0c;适用于大规模数据架构&#xff0c;容易集成&#xff0c;而且可以方便的部署在集群中。 如果你已经部…

细说敏捷:敏捷四会之standup meeting

上一篇文章中&#xff0c;我们讨论了 敏捷四会 中 冲刺计划会 的实施要点&#xff0c;本篇我们继续分享敏捷四会中实施最频繁&#xff0c;团队最容易实施但往往也最容易走形的第二个会议&#xff1a;每日站会 关于每日站会的误区 站会是一个比较有标志性的仪式活动&#xff0…

二分法(折半法)查找【有动图】

二分法&#xff0c;也叫做折半法&#xff0c;就是一种通过有序表的中间元素与目标元素进行对比&#xff0c;根据大小关系排除一半元素&#xff0c;然后继续在剩余的一半中进行查找&#xff0c;重复这个过程直至找到目标值或者确定目标值不存在。 我们从结论往回推&#xff0c;…

FreeRTOS——低功耗管理

目录 一、概念及其应用 1.1应用 1.2STM32电源管理系统 2.3STM32低功耗模式 2.3.1睡眠模式 2.3.2停止模式 2.3.3待机模式 三、Tickless低功耗模式 3.1低功耗模式配置 3.2低功耗模式应用 3.3低功耗电路分析 3.4低功耗处理相关接口 四、实现原理 4.1任务等待删除的检查…

【STM32】MPU6050初始化常用寄存器说明及示例代码

一、MPU6050常用配置寄存器 1、电源管理寄存器1&#xff08; PWR_MGMT_1 &#xff09; 此寄存器允许用户配置电源模式和时钟源。 DEVICE_RESET &#xff1a;用于控制复位的比特位。设置为1时复位 MPU6050&#xff0c;内部寄存器恢复为默认值&#xff0c;复位结束…

2024年亚太地区数学建模大赛A题-复杂场景下水下图像增强技术的研究

复杂场景下水下图像增强技术的研究 对于海洋勘探来说&#xff0c;清晰、高质量的水下图像是深海地形测量和海底资源调查的关键。然而&#xff0c;在复杂的水下环境中&#xff0c;由于光在水中传播过程中的吸收、散射等现象&#xff0c;导致图像质量下降&#xff0c;导致模糊、…

自动驾驶3D目标检测综述(三)

前两篇综述阅读理解放在这啦&#xff0c;有需要自行前往观看&#xff1a; 第一篇&#xff1a;自动驾驶3D目标检测综述&#xff08;一&#xff09;_3d 目标检测-CSDN博客 第二篇&#xff1a;自动驾驶3D目标检测综述&#xff08;二&#xff09;_子流行稀疏卷积 gpu实现-CSDN博客…

【Linux | 计网】TCP协议详解:从定义到连接管理机制

目录 1.TCP协议的定义&#xff1a; 2.TCP 协议段格式 3.TCP两种通信方式 4.确认应答(ACK)机制 解决“后发先至”问题 5.超时重传机制 那么, 超时的时间如何确定? 6.连接管理机制&#xff1a; 6.1.三次握手&#xff1a; 为什么需要3次握手&#xff0c;一次两次不行吗…

Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图

用户打开 PDF 文档时&#xff0c;他们会看到 PDF 的初始视图。默认情况下&#xff0c;打开 PDF 时不会显示书签面板或缩略图面板。在本文中&#xff0c;我们将演示如何设置文档属性&#xff0c;以便每次启动文件时都会打开书签面板或缩略图面板。 Spire.PDF for .NET 是一款独…

2024年12月Gesp七级备考知识点拾遗第一期(图的定义及遍历)

目录 总序言 知识点拾遗​编辑 度数 环 二叉树 图的遍历 深度优先 广度优先 连通与强连通 有什么不同 构成分别至少需要几条边&#xff08;易错题&#xff09;&#xff1f; 无向连通图 有向强连通图 完全图 什么是完全图 无向完全图最少边数 有向完全图最少边…

Doris 的Explain 和 Profile

什么是 explain&#xff1f; 执行计划是对一条 SQL 具体的执行方式和执行过程的描述。例如&#xff0c;对于一个涉及两表连接的 SQL&#xff0c;执行计划会展示这两张表的访问方式信息、连接方式信息&#xff0c;以及各个操作之间的顺序。 在 Doris 系统中提供了 Explain 工具…

QT QVBoxLayout控件 全面详解

本系列文章全面的介绍了QT中的57种控件的使用方法以及示例&#xff0c;包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizonta…

对sklearn库中的鸢尾花数据集内容和结构的详解认识和load_iris()函数查找学习举例

对sklearn库中的鸢尾花数据集内容和结构的详解认识和load_iris()函数查找学习举例 对sklearn库中的鸢尾花数据集内容和结构的详解认识和load_iris函数查找学习举例 对sklearn库中的鸢尾花数据集内容和结构的详解认识和load_iris()函数查找学习举例一、鸢尾花数据位置二、鸢尾花…

动态反馈控制器(DFC)和 服务率控制器(SRC);服务率和到达率简单理解

目录 服务率和到达率简单理解 服务率 到达率 排队论中的应用 论文解析:队列等待成本动态感知控制模型 动态反馈和队列等待成本意识: 服务速率调整算法: 动态反馈控制器(DFC)和 服务率控制器(SRC) SRC公式4的原理 算力资源分配系统中的调整消耗 举例说明 服务…

微信小程序上传微信官方审核流程(1)

1&#xff0c;打开微信开发者工具 2&#xff0c;微信开发者工具右上角有一个上传按钮&#xff0c;点击上传按钮 3&#xff0c;点击完上传按钮会弹出一个上传成功的提示&#xff0c;点击提示框中的确定按钮 4&#xff0c;点击完确定按钮后会显示填写版本好和项目备注 5&#x…

优先算法 —— 双指针系列 - 复写零

目录 1. 复写零 2. 算法原理 一般情况下 改为就地操作&#xff1a;从左到右&#xff08;错误&#xff09; 从右到左 总结一下解决方法&#xff1a; 如何找到最后一个复写的数 特殊情况 完整步骤&#xff1a; 3. 代码 1. 复写零 题目链接&#xff1a;1089. 复写零 - 力…

Spring源码学习(一):Spring初始化入口

写在前面 ​   作为一个刚步入职场的小白&#xff0c;对Spring&#xff08;SpringBoot&#xff09;的了解只停留在会用&#xff0c;并未对其内部的原理有过学习。在公司导师的指导下&#xff0c;开始进一步学习Spring的源码&#xff0c;毕竟Spring源码是Spring全家桶的基础&…

IntelliJ+SpringBoot项目实战(十三)--在SpringBoot中整合Mybatis-plus

mybatis-plus是基于mybatis基础上的增强的持久层框架&#xff0c;也是目前Java项目开发的主流框架。本文介绍在SpringBoot中集成mybtais-plus的方法以及使用mybatis-plus开发增删改查模块。 一、引入mybatis-plus 在openjweb-core工程中引入mybatis-plus依赖: <dependency&…