RTCP 是实时控制协议(Real-Time Control Protocol)的缩写。RTCP 由 RFC 3550 定义(取代作废的 RFC 1889)。

实时传输协议(RTP)和实时控制协议(RTCP)结合使用,可以监视大型多播网络的数据传递。RTP 承载媒体流,而 RTCP 用于监视传输统计信息和服务质量。监视使接收器能够检测是否有任何丢包并补偿任何延迟抖动。

两种协议都独立于基础传输层协议和网络层协议工作。RTP 标头中的信息告诉接收器如何重建数据,并描述编解码器比特流的打包方式。

下面我们重点讲解 RTCP 功能、RTCP 信息包等。最后 RTCP 协议解析实现。

RTCP 有哪些功能

1、RTCP 主要功能是提供有关质量的反馈数据分发。这是 RTP 角色不可或缺的一部分,传输协议,与流量和拥塞有关其他传输协议的控制功能。

2、RTCP 带有 RTP 的持久性传输级别标识符源称为规范名称或 CNAME。自从如果发现冲突或程序重新启动,接收方要求 CNAME 跟踪每个参与者。

接收者也可能要求 CNAME 将来自给定参与者的多个数据流关联到集合中相关 RTP 会话的数量,例如同步音频和视频。媒体间同步还需要 NTP 和 RTP 数据发送方在 RTCP 数据包中包含的时间戳。

3、前两个功能要求所有参与者发送 RTCP 数据包,因此必须控制速率以使 RTP 能够扩大到大量参与者。通过让每个参与者将其控制包发送给所有其他人,每个人都可以独立观察参与者的数量。

4、这项功能对于参加者可以任意进入和离开的松散会话进程十分有用,参加者可以自由进入或离开,没有成员控制或参数协调。

功能 1-3 应该在所有环境中使用,尤其是在 IP 多播环境。RTP 应用程序设计师应避免只能在单播模式下工作且无法扩展到的机制更大的数字。RTCP 的传输可以单独控制对于发送者和接收者,适用于例如单向链接,而接收者没有反馈可能的。

RTCP 协议的端口

RTSP 通常使用 RTP 协议来传送实时流,RTP 一般使用偶数端口,而 RTCP 使用相邻的奇数端口,即 RTP 端口号+1。

RTP 端口

RTCP 端口

RTCP 信息包有哪些

在 RTCP 通信控制中,RTCP 协议的功能是通过不同类型的 RTCP 包来实现的。RTCP 也是基于 UDP 包来传送的,主要有五种类型的封包:

  1. SR:发送端报告,由发送 RTP 数据报的应用程序或中端发出的。

  2. RR:接收端报告,由接受但不发送 RTP 数据报的应用程序或中端发出。

  3. SDES:源描述,传递与会话成员有关的标识信息的载体,如用户名、邮件、电话等。

  4. BYE:通知离开,通知回话中的其他成员将退出会话。

  5. APP:由应用程序自己定义,作为 RTCP 协议的扩展。

#define RTCP_SR      200
#define RTCP_RR 201
#define RTCP_SDES 202
#define RTCP_BYE 203
#define RTCP_APP 204

我们可以根据这五种类型包判断 RTCP 头部

static bool dissect_rtcp_heur(u_char *rtcp_info,int PayloadLen)
{
unsigned int offset = 0;
unsigned int first_byte = 0;
unsigned int packet_type = 0; /* 查看第一个字节 */
first_byte = rtcp_info[offset]; /* 版本位是否设置为2*/
//printf("version: %d\n",((first_byte & 0xC0) >> 6));
if (((first_byte & 0xC0) >> 6) != 2)
{
return false;
} /* 看包类型 */
offset += 1;
packet_type = rtcp_info[offset];
//printf("packet_type: %d\n",packet_type);
/* 复合数据包中的第一个数据包应该是发送方或接收者报告 */
if (!((packet_type == RTCP_SR) || (packet_type == RTCP_RR) ||
(packet_type == RTCP_BYE) || (packet_type == RTCP_APP) ||
(packet_type == RTCP_PSFB)))
{
return false;
} /*总长度必须是4个字节的倍数*/
//printf("PayloadLen: %d\n",PayloadLen);
if (PayloadLen % 4)
{
return false;
} /* OK, dissect as RTCP */
dissect_rtcp(rtcp_info,packet_type,offset,PayloadLen);
return true;
}

RTCP 协议报文格式

SR:发送端报告

版本(V):同 RTP 包头部

填充 ( P ) :同 RTP 包头部。

接收报告计数器(RC):5b 该 SR 包中接收的报告块的数目。

包类型(PT): 8bit SR 包类型为 200

长度(length):SR 包以 32bit 为 1 单位的长度减 1

同步源(SSRC):SR 包发送的同步源标识符。与对应 RTP 包中的 SSRC 一样。

NTP 时间戳(Network Time Protocol):SR 包发送时的绝对时间。用于同步不同的流。

RTP 时间戳:与 NTP 时间戳对应,与 RTP 包中的时间戳具有相同的初始值。

Send’s Packet count:从开始发包到产生这个 SR 包的这段时间内发送者发送的有效数据的总字节数,不包括头部和填充,发送者改变 SSRC 时,该域要清零。

同步源 n 的 SSRC 标识符:该报告中包含的是从该源接收到的包的统计信息。

丢失率:表明从上一个 SR 或 RR 包发出依来从同步源 n 发送的 RTP 包的丢失率。

累计丢失数据:从开始接受 SSRC_n 的包到发送 SR 这个时间段内 SSRC_n 发送的 RTP 丢失的总数目。

收到的扩展最大序列号:从 SSRC_n 收到的从 RTP 数据包中的最大序列号。

接收抖动(Interarrival jitter):RTP 数据包接收时间的统计方差估计。

上次 SR 时间戳(Last SR):取最近从 SSRC_n 收到的 SR 包中的 NTP 时间戳中的中间 32bit。如果还未收到 SR 包,则为 0。

上次依赖 SR 延迟(Delay since Last SR):从上次 SSRC_n 收到 SR 包到发送本包的延迟

Wireshark 抓包:

活动会话的参与者在发送和接收 RTP 分组时使用 SR。SR 有三个不同的部分:报头信息、发送方信息和许多接收方报告块。SR 也可以有一个与大纲相关的扩展域。

Wireshark 抓包:

SDES:源描述

SDES 提供了传递与会话成员有关的标识信息的载体,如用户名、邮件、电话等。每个 RTCP 混合分组中必须有 SDES 分组。

报头包含一个长度域、一个净荷类型域(PT=202)和一个源计数(RC)域。RC 域 5 个 bit,表示分组中信息块的数量。

每个信息块包含一个 SSRC 或 CSRC 值,其后跟着一个或多个的标识符和一些可用于 SSRC 或 CSRC 的信息。

CNAME 项的 SDES 包必须包含在每个组合 RTCP 包中。SDES 包可能包括其他源描述项,这要根据特别的应用需要,并同时考虑带宽限制。

Wireshark 抓包:

SDES 源描述包提供了直观的文本信息来描述会话的参加者,包括 CNAME、NAME、EMAIL、PHONE、LOC 等源描述项。

这些为接收方获取发送方的有关信息提供了方便。SDES 包由包头与数据块组成,数据块可以没有,也可有多个。包头由版本(V)、填充(P)、长度指示、包类型(PT)和源计数(SC)组成。

PT 占 8 位,用于识别 RTCP 的 SDES 包,SC 占 5 位,指示包含在 SDES 包中的 SSRC/CSRC 块数量,零值有效,但没有意义。

BYE: 通知离开

BYE 分组用于表示一个或多个媒体源不再是处于激活状态。

Wireshark 抓包:

作为可选项,BYE 包可包括一个 8 位八进制计数,后跟文本信息,表示离开原因。

最后,组合包中每个 RTCP 包可独立处理,而不需要按照包组合的先后顺序处理。

在组合包中有以下几条强制约束。

  1. 只要带宽允许,在 SR 包或 RR 包中的接收统计应该经常发送,因此每个周期发送的组合 RTCP 包中应包含报告包。

  2. 每个组合包中都应该包含 SDES CNAME,因为新接收者需要通过接收 CNAME 来识别源,并与媒体联系进行同步。

  3. 组合包前面是包类型数量,其增长应该受到限制。

RTCP 协议如何实现媒体流的同步

通过抓包分析 RTCP 发送端报告,RTP 的同步其实就靠这三个域:

  1. sender SSRC :SR 包发送的同步源标识符。与对应 RTP 包中的 SSRC 一样。

  2. NTP timestamp:SR 包发送时的绝对时间。用于同步不同的流。

  3. RTP timestamp:与 NTP 时间戳对应,与 RTP 包中的时间戳具有相同的初始值。

那怎么计算 NTP 时间呢?在 RTCP 中 NTP 时间存放在 8 个字节中,分为:MSW 和 LSW,分别占用 4 个字节。

const char *tvb_ntp_fmt_ts_sec(u_char *rtcp_info, int offset)
{
uint32_t tempstmp = 0;
time_t temptime = 0;
struct tm *bd;
char *buff = NULL; tempstmp = ntohl(*(uint32_t*)(rtcp_info + offset));
if (tempstmp == 0){
return "NULL";
} /* We need a temporary variable here so the unsigned math
* works correctly (for years > 2036 according to RFC 2030
* chapter 3).
*/
temptime = (time_t)(tempstmp - NTP_BASETIME);
bd = gmtime(&temptime);
if (!bd){
return "Not representable";
} buff = (char *)malloc(NTP_TS_SIZE);
snprintf(buff, NTP_TS_SIZE,
"%s %2d, %d %02d:%02d:%02d UTC",
mon_names[bd->tm_mon],
bd->tm_mday,
bd->tm_year + 1900,
bd->tm_hour,
bd->tm_min,
bd->tm_sec);
return buff;
}

NTP timestamp

 
/* NTP timestamp */
ts_msw = ntohl(*(uint32_t*)(rtcp_info + offset));
printf("ts_msw: 0x%x\n",ts_msw);
ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4));
printf("ts_lsw: 0x%x\n",ts_lsw);
printf("MSW: %s\n",tvb_ntp_fmt_ts_sec(rtcp_info,offset));
offset += 8;
 

RTCP 协议实现

下面我给出对 RTCP 协议解析实现的代码,根据回放报文,解析字段信息。

/* 接收者/发送者计数是最后5位   */
#define RTCP_COUNT(octet) ((octet) & 0x1F) #define RTCP_PT_MIN 192
/* Supplemental H.261 specific RTCP packet types according to Section C.3.5 */
#define RTCP_FIR 192
#define RTCP_NACK 193
#define RTCP_SMPTETC 194
#define RTCP_IJ 195
/* RTCP packet types according to Section A.11.1 */
/* And https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
#define RTCP_SR 200
#define RTCP_RR 201
#define RTCP_SDES 202
#define RTCP_BYE 203
#define RTCP_APP 204
#define RTCP_RTPFB 205
#define RTCP_PSFB 206
#define RTCP_XR 207
#define RTCP_AVB 208
#define RTCP_RSI 209
#define RTCP_TOKEN 210 #define RTCP_PT_MAX 210 static const char mon_names[12][4] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
}; /** data structure to hold time values with nanosecond resolution*/
typedef struct {
time_t secs;
int nsecs;
} nstime_t; /*
* 1900-01-01 00:00:00 (proleptic?) UTC.
* Used by a number of time formats.
*/
#define EPOCH_DELTA_1900_01_01_00_00_00_UTC 2208988800U /*
* NTP_BASETIME is in fact epoch - ntp_start_time; ntp_start_time
* is January 1, 2036, 00:00:00 UTC.
*/
#define NTP_BASETIME EPOCH_DELTA_1900_01_01_00_00_00_UTC
#define NTP_FLOAT_DENOM 4294967296.0
#define NTP_TS_SIZE 100 /* 解剖长度字段。附加到此字段的文字表示转换为的实际字节数 (即 (原始值 + 1) * 4) */
static int dissect_rtcp_length_field(u_char *rtcp_info, int offset)
{ uint16_t raw_length = ntohs(*(uint16_t*)(rtcp_info + offset));
printf("(%u bytes)\n", (raw_length+1)*4);
offset += 2;
return offset;
}
static int dissect_rtcp_rr(u_char *rtcp_info, int offset,int count, int packet_length )
{
int counter = 0;
uint8_t rr_flt = 0;
int rr_offset = offset; counter = 1;
while ( counter <= count ) {
uint32_t lsr = 0, dlsr = 0; /* Create a new subtree for a length of 24 bytes */ /* SSRC_n source identifier, 32 bits */ offset += 4; /* Fraction lost, 8bits */
rr_flt = rtcp_info[offset]; offset++; /* Cumulative number of packets lost, 24 bits */
offset += 3; /* Sequence number cycles */ offset += 2;
/* highest sequence number received */ offset += 2; /* Interarrival jitter */ offset += 4; /* Last SR timestamp */
lsr = ntohl(*(uint32_t*)(rtcp_info + offset));
printf("Last SR timestamp: 0x%x\n",lsr);
offset += 4; /* Delay since last SR timestamp */
dlsr = ntohl(*(uint32_t*)(rtcp_info + offset)); printf("(%d milliseconds)\n",(int)(((double)dlsr/(double)65536) * 1000.0));
offset += 4; counter++;
} return offset;
} const char *tvb_ntp_fmt_ts_sec(u_char *rtcp_info, int offset)
{
uint32_t tempstmp = 0;
time_t temptime = 0;
struct tm *bd;
char *buff = NULL; tempstmp = ntohl(*(uint32_t*)(rtcp_info + offset));
if (tempstmp == 0){
return "NULL";
} /* We need a temporary variable here so the unsigned math
* works correctly (for years > 2036 according to RFC 2030
* chapter 3).
*/
temptime = (time_t)(tempstmp - NTP_BASETIME);
bd = gmtime(&temptime);
if (!bd){
return "Not representable";
} buff = (char *)malloc(NTP_TS_SIZE);
snprintf(buff, NTP_TS_SIZE,
"%s %2d, %d %02d:%02d:%02d UTC",
mon_names[bd->tm_mon],
bd->tm_mday,
bd->tm_year + 1900,
bd->tm_hour,
bd->tm_min,
bd->tm_sec);
return buff;
} static int dissect_rtcp_sr(u_char *rtcp_info, int offset,int count, int packet_length)
{ uint32_t ts_msw = 0, ts_lsw = 0;
int sr_offset = offset; /* NTP timestamp */
ts_msw = ntohl(*(uint32_t*)(rtcp_info + offset));
printf("ts_msw: 0x%x\n",ts_msw);
ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4));
printf("ts_lsw: 0x%x\n",ts_lsw); //printf("offset: 0x%x 0x%x 0x%x 0x%x\n",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]);
printf("MSW: %s\n",tvb_ntp_fmt_ts_sec(rtcp_info,offset));
offset += 8; /* RTP timestamp, 32 bits */ offset += 4;
/* Sender's packet count, 32 bits */ offset += 4;
/* Sender's octet count, 32 bits */ offset += 4; /* The rest of the packet is equal to the RR packet */
if ( count != 0 )
offset = dissect_rtcp_rr(rtcp_info, offset, count, packet_length-(offset-sr_offset));
else
{
/* If length remaining, assume profile-specific extension bytes */
if ((offset-sr_offset) < packet_length)
{ offset = sr_offset + packet_length;
}
} return offset;
} static int dissect_rtcp_sdes(u_char *rtcp_info, int offset, int count)
{
int chunk = 0; int start_offset = 0;
int items_start_offset = 0;
uint32_t ssrc = 0;
unsigned int item_len = 0;
unsigned int sdes_type = 0;
unsigned int prefix_len = 0; chunk = 1;
while ( chunk <= count )
{
/* Create a subtree for this chunk; we don't yet know
the length. */
start_offset = offset; ssrc = ntohl(*(uint32_t*)(rtcp_info + offset));
printf("Chunk %u, SSRC/CSRC 0x%X\n", chunk, ssrc); /* SSRC_n source identifier, 32 bits */
offset += 4; /* Create a subtree for the SDES items; we don't yet know
the length */ /*
* Not every message is ended with "null" bytes, so check for
* end of frame as well.
*/ /* ID, 8 bits */
sdes_type = rtcp_info[offset];
printf("Type: %d\n",sdes_type);
if (sdes_type == 0)
break;
offset++;
/* Item length, 8 bits */
item_len = rtcp_info[offset];
printf("Length: %d\n",item_len);
offset++; char *pszText = (char*)malloc(item_len);
if (pszText != 0)
{
memcpy(pszText, rtcp_info + offset,item_len);
pszText[item_len] = '\0';
printf("Text = %s\n",pszText);
} chunk++;
} return offset;
} static void dissect_rtcp(u_char *rtcp_info,int packet_type, int offset,int PayloadLen)
{
unsigned int temp_byte = 0;
int elem_count = 0;
int packet_length = 0;
int total_packet_length = 0;
int loop = 2;
bool flag_rtcp = false; /*检查是否为有效类型*/
if ( ( packet_type < RTCP_PT_MIN ) || ( packet_type > RTCP_PT_MAX ) )
exit(-1); /*
* 获取完整的RTCP数据包的长度
*/ packet_length = (ntohs(*(uint16_t*)(rtcp_info + offset + 1)) + 1) * 4 ;
//printf("packet_length: %d\n",packet_length); temp_byte = rtcp_info[offset-1];
elem_count = RTCP_COUNT( temp_byte );/* Source count, 5 bits */
printf("Reception report count: %d\n",elem_count); switch ( packet_type )
{ case RTCP_SR:
case RTCP_RR:
/*
Real-time Transport Control Protocol (Receiver Report)
10.. .... = Version: RFC 1889 Version (2)
..0. .... = Padding: False
...0 0001 = Reception report count: 1
Packet type: Receiver Report (201)
Length: 7 (32 bytes)
Sender SSRC: 0xb584b03e (3045371966)
Source 1
*/ /* Packet type, 8 bits */
offset++;
/* Packet length in 32 bit words MINUS one, 16 bits */
offset = dissect_rtcp_length_field(rtcp_info, offset);
/* Sender Synchronization source, 32 bits */
offset += 4; if ( packet_type == RTCP_SR )
{
offset = dissect_rtcp_sr(rtcp_info, offset, elem_count, packet_length-8 );
printf("dissect_rtcp_sr\n");
}
else
{
offset = dissect_rtcp_rr(rtcp_info, offset, elem_count, packet_length-8 );
} //uint16_t second_packet_type = ntohs(*(uint16_t*)(rtcp_info + offset)); //printf("111offset: 0x%x 0x%x 0x%x 0x%x\n",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]); if (rtcp_info[offset + 1] == RTCP_SDES)
{ /* Source count, 5 bits */
offset++;
/* Packet type, 8 bits */
offset++;
/* Packet length in 32 bit words MINUS one, 16 bits */
offset = dissect_rtcp_length_field(rtcp_info, offset);
offset = dissect_rtcp_sdes(rtcp_info,offset,elem_count); } break; default:
/*
* To prevent endless loops in case of an unknown message type
* increase offset. Some time the while will end :-)
*/
offset++;
break; } } static bool dissect_rtcp_heur(u_char *rtcp_info,int PayloadLen)
{
unsigned int offset = 0;
unsigned int first_byte = 0;
unsigned int packet_type = 0; /* 查看第一个字节 */
first_byte = rtcp_info[offset]; /* 版本位是否设置为2*/
//printf("version: %d\n",((first_byte & 0xC0) >> 6));
if (((first_byte & 0xC0) >> 6) != 2)
{
return false;
} /* 看包类型 */
offset += 1;
packet_type = rtcp_info[offset];
//printf("packet_type: %d\n",packet_type);
/* 复合数据包中的第一个数据包应该是发送方或接收者报告 */
if (!((packet_type == RTCP_SR) || (packet_type == RTCP_RR) ||
(packet_type == RTCP_BYE) || (packet_type == RTCP_APP) ||
(packet_type == RTCP_PSFB)))
{
return false;
} /*总长度必须是4个字节的倍数*/
//printf("PayloadLen: %d\n",PayloadLen);
if (PayloadLen % 4)
{
return false;
} /* OK, dissect as RTCP */
dissect_rtcp(rtcp_info,packet_type,offset,PayloadLen);
return true;
} static void confirm_rtcp_packet(struct ip *pIp)
{
int iIpTotalLen = ntohs(pIp->ip_len);
int offset = 0;
int nFragSeq = 0;
struct udphdr* pUdpHdr = (struct udphdr*)((char*)pIp + (pIp->ip_hl<<2));
if (pIp->ip_p == IPPROTO_UDP)
{
printf("\n"); int iPayloadLen = iIpTotalLen - (pIp->ip_hl<<2) - 8;
printf("UDP Payload Len %d\n", iPayloadLen); u_char *pDnsHdr = (u_char*)(pUdpHdr+1);
dissect_rtcp_heur(pDnsHdr,iPayloadLen); }
}

编译运行:

总结

RTCP 协议是流媒体通信的基石。RTCP 协议则负责可靠传输、流量控制和拥塞控制等服务质量保证。上面讲解了 RTCP 功能、RTCP 数据包格式及代码实现。最后,学习一个新的协议,最好还是研究学习官方文档,因为这是最权威的资料。

音视频同步!RTCP 协议解析及代码实现的更多相关文章

  1. FFmpeg简易播放器的实现-音视频同步

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10284653.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...

  2. vlc源码分析(五) 流媒体的音视频同步

    vlc播放流媒体时实现音视频同步,简单来说就是发送方发送的RTP包带有时间戳,接收方根据此时间戳不断校正本地时钟,播放音视频时根据本地时钟进行同步播放.首先了解两个概念:stream clock和sy ...

  3. WebRTC 音视频同步原理与实现

    所有的基于网络传输的音视频采集播放系统都会存在音视频同步的问题,作为现代互联网实时音视频通信系统的代表,WebRTC 也不例外.本文将对音视频同步的原理以及 WebRTC 的实现做深入分析. 时间戳 ...

  4. Android 音视频同步机制

    一.概述 音视频同步(avsync),是影响多媒体应用体验质量的一个重要因素.而我们在看到音视频同步的时候,最先想到的就是对齐两者的pts,但是实际使用中的各类播放器,其音视频同步机制都比这些复杂的多 ...

  5. ffmpeg 2.3版本号, 关于ffplay音视频同步的分析

    近期学习播放器的一些东西.所以接触了ffmpeg,看源代码的过程中.就想了解一下ffplay是怎么处理音视频同步的,之前仅仅大概知道通过pts来进行同步,但对于怎样实现却不甚了解,所以想借助这个机会, ...

  6. ffplay的音视频同步分析

    以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事.只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmpeg,他在各 ...

  7. ffplay(2.0.1)中的音视频同步

    最近在看ffmpeg相关的一些东西,以及一些播放器相关资料和代码. 然后对于ffmpeg-2.0.1版本下的ffplay进行了大概的代码阅读,其中这里把里面的音视频同步,按个人的理解,暂时在这里作个笔 ...

  8. (转)ffplay的音视频同步分析之视频同步到音频

          以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事.只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmp ...

  9. [SimplePlayer] 8. 音视频同步

    音频与视频在播放当中可能会由于种种原因(如:音视频并非在同一时间开始播放,或视频由于解码任务繁重导致输出图像延迟等)导致音频与视频的播放时间出现偏差,这种就是音视频的同步问题,本文会对音视频同步进行讨 ...

  10. Android 音视频同步(A/V Sync)

    1.  音视频同步原理 1)时间戳 音视频同步主要用于在音视频流的播放过程中,让同一时刻录制的声音和图像在播放的时候尽可能的在同一个时间输出. 解决音视频同步问题的最佳方案就是时间戳:首先选择一个参考 ...

随机推荐

  1. Delphi实现大写字母键打开提示

    uses XPMan 编辑框的passwordChar属性设置成 *

  2. 复杂的光照与更复杂的阴影实现过程——ShaderCP9

    --20.8.28 这章的内容看了很久,也有很多复杂的内容.中途还有事情耽搁了一会.开学后就继续好好记录努力. 我们在游戏中能看到的让人觉得真实感的来源之一就是真实的光照以及光照所产生的阴影.下面的内 ...

  3. 在Unity3D中开发的Sketch Shader

    Pencil Sketch Shader 特点 此素描渲染风格的Shader是顶点片元Shader,由本人手动编写完成. 此素描渲染风格的Shader已经在移动设备真机上进行过测试,可以直接应用到您的 ...

  4. 记录一次 网关负载 流量不均匀 cpu使用率不均衡问题

    网关负载  流量不均匀  cpu使用率不均衡问题??? 1.压力机访问源  有多少ip 有10个? 还是20个? 就是样本源不多的话,负载上hash的话  就你可能不是真实的访问需求 ,你客户端就那么 ...

  5. https://计算机三级

    计算机三级内容:"网络技术","数据库技术","软件测试技术","信息安全技术","嵌入式系统开发技术&quo ...

  6. pwm 理解

    PWM:  假设PWM的时钟主频是 PWM_CLK_FREQ Hz,则如果需要输出频率为 xHz,占空比为 y% 的波形时, 则只需要在定时器的周期寄存器中写入(PWM_CLK_FREQ / x),在 ...

  7. Bug的前后台分类及定位技巧

    必备工具:Firefox debug工具 一般浏览器F12即可   如何区分页面的bug问题归属:前端or后端 前端bug主要分为3个类别:HTML,CSS,Javascript三类问题 给个最大的区 ...

  8. PTA_Maximum Subsequence Sum

    Given a sequence of K integers { N1​, N2​, ..., NK​ }. A continuous subsequence is defined to be { N ...

  9. intellij idea中怎么没有git版本控制设置项

    在使用intellij idea的时候想要使用git进行版本控制,但是在设置项和界面没有发现相关内容,怎么回事呢? 我们先打开电脑,从桌面的快捷方式打开intellij idea,进入到intelli ...

  10. 认识flutter

    flutter是谷歌的移动的ui框架,可以快速的在ios和安卓上构建高质量的原生用户界面.最主要的是完全免费开源.开发快,最重要的是使用flutter开发的开发工作者也越来越多了,生态圈也越来越好了. ...