目录

  • 源码框架分析
    • RtspServer_init
    • SAMPLE_VENC_720P_CLASSIC
  • 详细分析
  • RtspServer_init
    • RtspServerListen
      • ParseRequestString
      • OPTIONS
      • DESCRIBE
      • PLAY
    • vdRTPSendThread

源码框架分析


主函数中只有两部分,初始化rtsp服务和视频编码程序

RtspServer_init

我们在编写代码前的设想是让开发板作为服务器,windows作为客户端。于是服务器一定是先运行,然后像socket那样初始化完成后阻塞监听,等待客户端连接。

S(server)C(client)模式,基本都是这样的结构。

初始化部分代码如下

void RtspServer_init(void)
{int i;pthread_t threadId = 0;memset(&g_rtp_playload,0,sizeof(g_rtp_playload));strcpy(&g_rtp_playload,"G726-32");g_audio_rate = 8000;pthread_mutex_init(&g_sendmutex,NULL);pthread_mutex_init(&g_mutex,NULL);pthread_cond_init(&g_cond,NULL);memset(&g_rtspClients,0,sizeof(RTSP_CLIENT)*MAX_RTSP_CLIENT);//pthread_create(&g_SendDataThreadId, NULL, SendDataThread, NULL);struct sched_param thdsched;thdsched.sched_priority = 2;//to listen visitingpthread_create(&threadId, NULL, RtspServerListen, NULL);//pthread_setschedparam(threadId,SCHED_RR,&thdsched);printf("RTSP:-----Init Rtsp server\n");pthread_create(&gs_RtpPid, 0, vdRTPSendThread, NULL);//exitok++;
}

一上来先申请一个线程,盲猜就是用来监听

接着为payload的存放申请空间,以RTP开头,是应为rtsp与rtp之间有联系,rtsp实际上是以rtp包来发送的,所以以rtp开头
g_rtp_playload是一个20个字节的数组,后面将G726-32放入数组,这表示是一种音频标准的payload

下面的g_audio_rate = 8000;就是音频的采样率,后续没有用到,如果做音频相关的东西就可以进行使用

接着初始化全局的mutex和cond

再为g_rtspClients申请空间,用于存放连接的客户端的信息,数据结构如下

typedef struct
{int index;int socket;int reqchn;int seqnum;int seqnum2;unsigned int tsvid;unsigned int tsaud;int status;int sessionid;int rtpport[2];int rtcpport;char IP[20];char urlPre[PARAM_STRING_MAX];
}RTSP_CLIENT;

接着配置sched_param结构体,看起来是和线程调度的优先级有关。如果线程比较多,对CPU资源进行争抢的时候,就需要设置,可以调用系统API进行线程的调度。
这里作为预留,实际代码中没有必要使用
pthread_setschedparam(threadId,SCHED_RR,&thdsched);注释掉了

RtspServer_init中真正被调用的函数是RtspServerListen,开启了一个线程进行监听。socket的转网络字节序、servaddr的配置、bind、listen都放在了这个线程里面。

vdRTPSendThread用于发送,也开启了一个线程进行

SAMPLE_VENC_720P_CLASSIC

这个函数就是标准的海思sample,但是加入了rtsp的内容

我们将sample修改为只用一路,码率控制模式改为固定fixQP

其他内容没有什么变动,具体参考专栏海思3518E开发笔记中2开头的内容

修改的部分是第六步,从venc编码通道(vb)中获取视频流并保存,这里改为获取视频流后不保存,直接放到rtsp通道中传输

于是在原来保存264码流的地方,换成通过rtsp发送出去

HI_S32 SAMPLE_COMM_VENC_Sentjin(VENC_STREAM_S *pstStream)
{HI_S32 i,flag=0;for(i=0;i<MAX_RTSP_CLIENT;i++)//have atleast a connect{if(g_rtspClients[i].status == RTSP_SENDING){flag = 1;break;}}if(flag){for (i = 0; i < pstStream->u32PackCount; i++){HI_S32 lens=0,j,lastadd=0,newadd=0,showflap=0;char sendbuf[320*1024];//char tmp[640*1024];lens = pstStream->pstPack[i].u32Len-pstStream->pstPack[i].u32Offset;memcpy(&sendbuf[0],pstStream->pstPack[i].pu8Addr+pstStream->pstPack[i].u32Offset,lens);//printf("lens = %d, count= %d\n",lens,count++);VENC_Sent(sendbuf,lens);        lens = 0;}}return HI_SUCCESS;
}

实际应用场景中,可能有很多路的rtsp流,这里我们宏定义只用一路

下面对rtsp的状态进行判断,如果处于发送状态就进行发送

然后调用VENC_Sent将准备好的包发出去

详细分析

RtspServer_init

RtspServerListen

这里主要就是网络编程中配置端口号,修改网络字节序的内容
端口号使用的是554,这是RTSP的默认端口号

setsockopt来设置socket属性,设置了SO_REUSEADDR可以用来复用

然后就是bind和listen

接下来到accept
如果没人连接,就阻塞到while的判断中,有人连接就进入while
并且将连接过来的ip地址打印出来

接着再进行socket的设置,选项是SO_SNDBUF,即设置发送缓冲区的大小

然后对新连接的rtspClient的status进行判断,如果等于RTSP_IDLE就进行处理。由于g_rtspClients是全局变量,定义之后自动初始化为0,就是status枚举里的第一个。
这样处理,只要是新的ip地址,就一定是空闲状态,进if后,就把他的状态换成connected,避免反复操作

进入if之后,将接入的客户端的属性进行一一填充。

memset(&g_rtspClients[i],0,sizeof(RTSP_CLIENT));
g_rtspClients[i].index = i;
g_rtspClients[i].socket = s32CSocket;
g_rtspClients[i].status = RTSP_CONNECTED ;//RTSP_SENDING;
g_rtspClients[i].sessionid = nSessionId++;
strcpy(g_rtspClients[i].IP,inet_ntoa(addrAccept.sin_addr));

填充完属性后建立一个线程,进行当前接入客户端的处理。

如果超过最大限制,那么就将超过的覆盖到原来的0,这样循环覆盖下去(设计方面的思路,也可以超过直接不再连接)


处理的函数是RtspClientMsg,只服务当前接入的客户端
在看这一段代码的时候,就需要对RTSP传输协议有一定的了解,函数中ParseRequestString对RTSP消息头进行解析

RtspClientMsg函数一进去,做了一个RTSP_CLIENT * pClient来接受传进来的结构体,不直接用是避免修改原来结构体里的内容
当连接进来的客户端的状态不是架空的时候,就进行数据的receive,一次只读RTSP_RECV_SIZE这么多,没有读完的部分留到下一次继续读,如果读出来的字节数小于0,那么是错误的,把状态重新改为置空

接下来定义了一系列的局部变量,作为ParseRequestString的输出参数。
该函数原型如下

int ParseRequestString(char const* reqStr,unsigned reqStrSize,char* resultCmdName,unsigned resultCmdNameMaxSize,char* resultURLPreSuffix,unsigned resultURLPreSuffixMaxSize,char* resultURLSuffix,unsigned resultURLSuffixMaxSize,char* resultCSeq,unsigned resultCSeqMaxSize)

第一个参数为前面socket接收到的字符串,就是进行分析的目标
第二个参数是接受到字符串的大小
第三个参数用来存放解析出来的RTSP method
第四个参数是函数执行前申请存放第三个参数的数组的大小
第五个参数是解析出来的URL前缀
第六个参数是用来存放第五个参数的数组大小
第七个参数是解析出来的URL后缀
第八个参数是存放第七个参数的数组大小
第九个参数是解析出来的会话序列号
第十个参数是存放第九个参数的数组大小

以上解析出来的参数都和RTSP会话内容对应,详细参考我之前写的RTSP协议详解

后面的内容就是对解析出来的RTSP methods进行对应操作

接下来仔细分析ParseRequestString函数

ParseRequestString

这个函数的作用就是解析RTSP协议中CS交互信息,多为一些字符串操作

第一段

// Read everything up to the first space as the command name:int parseSucceeded = FALSE;unsigned i;for (i = 0; i < resultCmdNameMaxSize-1 && i < reqStrSize; ++i) {char c = reqStr[i];if (c == ' ' || c == '\t') {parseSucceeded = TRUE;break;}resultCmdName[i] = c;}resultCmdName[i] = '\0';if (!parseSucceeded) return FALSE;

首先做了一个flag,来表示是否成功分析
接着对获取的数据进行分段,标志是空格或制表符
这个循环就获取了RTSP 的method
由于解析出来的是字符串,就需要将最后一个字符替换为字符串的结束标志

第二段

// Skip over the prefix of any "rtsp://" or "rtsp:/" URL that follows:unsigned j = i+1;while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j; // skip over any additional white spacefor (j = i+1; j < reqStrSize-8; ++j) {if ((reqStr[j] == 'r' || reqStr[j] == 'R')&& (reqStr[j+1] == 't' || reqStr[j+1] == 'T')&& (reqStr[j+2] == 's' || reqStr[j+2] == 'S')&& (reqStr[j+3] == 'p' || reqStr[j+3] == 'P')&& reqStr[j+4] == ':' && reqStr[j+5] == '/') {j += 6;if (reqStr[j] == '/') {// This is a "rtsp://" URL; skip over the host:port part that follows:++j;while (j < reqStrSize && reqStr[j] != '/' && reqStr[j] != ' ') ++j;} else {// This is a "rtsp:/" URL; back up to the "/":--j;}i = j;break;}}// Look for the URL suffix (before the following "RTSP/"):parseSucceeded = FALSE;unsigned k;for (k = i+1; k < reqStrSize-5; ++k) {if (reqStr[k] == 'R' && reqStr[k+1] == 'T' &&reqStr[k+2] == 'S' && reqStr[k+3] == 'P' && reqStr[k+4] == '/') {while (--k >= i && reqStr[k] == ' ') {} // go back over all spaces before "RTSP/"unsigned k1 = k;while (k1 > i && reqStr[k1] != '/' && reqStr[k1] != ' ') --k1;// the URL suffix comes from [k1+1,k]// Copy "resultURLSuffix":if (k - k1 + 1 > resultURLSuffixMaxSize) return FALSE; // there's no roomunsigned n = 0, k2 = k1+1;while (k2 <= k) resultURLSuffix[n++] = reqStr[k2++];resultURLSuffix[n] = '\0';// Also look for the URL 'pre-suffix' before this:unsigned k3 = --k1;while (k3 > i && reqStr[k3] != '/' && reqStr[k3] != ' ') --k3;// the URL pre-suffix comes from [k3+1,k1]// Copy "resultURLPreSuffix":if (k1 - k3 + 1 > resultURLPreSuffixMaxSize) return FALSE; // there's no roomn = 0; k2 = k3+1;while (k2 <= k1) resultURLPreSuffix[n++] = reqStr[k2++];resultURLPreSuffix[n] = '\0';i = k + 7; // to go past " RTSP/"parseSucceeded = TRUE;break;}}if (!parseSucceeded) return FALSE;

首先用j获取第二段关键信息,就是用上次的最后地址i再往后偏移一个地址
然后兼容大小写,作用是跳过rtsp://或rtsp:/
然后找到/后面的不是/或者空格的那一个地址,就是解析出来的URL的首地址

第三段

  // Look for "CSeq:", skip whitespace,// then read everything up to the next \r or \n as 'CSeq':parseSucceeded = FALSE;for (j = i; j < reqStrSize-5; ++j) {if (reqStr[j] == 'C' && reqStr[j+1] == 'S' && reqStr[j+2] == 'e' &&reqStr[j+3] == 'q' && reqStr[j+4] == ':') {j += 5;unsigned n;while (j < reqStrSize && (reqStr[j] ==  ' ' || reqStr[j] == '\t')) ++j;for (n = 0; n < resultCSeqMaxSize-1 && j < reqStrSize; ++n,++j) {char c = reqStr[j];if (c == '\r' || c == '\n') {parseSucceeded = TRUE;break;}resultCSeq[n] = c;}resultCSeq[n] = '\0';break;}}if (!parseSucceeded) return FALSE;

用于解析出CSeq字段

以上就是作为服务器的开发板对客户端发来的请求信息的解析,解析完毕后做相应的处理

OPTIONS

以OptionAnswer为例

int OptionAnswer(char *cseq, int sock)
{if (sock != 0){char buf[1024];memset(buf,0,1024);char *pTemp = buf;pTemp += sprintf(pTemp,"RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n",cseq,dateHeader(),"OPTIONS,DESCRIBE,SETUP,PLAY,PAUSE,TEARDOWN");int reg = send(sock, buf,strlen(buf),0);if(reg <= 0){return FALSE;}else{printf(">>>>>%s\n",buf);}return TRUE;}return FALSE;
}

服务端收到客户端的请求后,向客户端返回信息
返回的信息放在buf中,通过socket通道发送
返回的信息按照RTSP规则

DESCRIBE

int DescribeAnswer(char *cseq,int sock,char * urlSuffix,char* recvbuf)
{if (sock != 0){char sdpMsg[1024];char buf[2048];memset(buf,0,2048);memset(sdpMsg,0,1024);char*localip;localip = GetLocalIP(sock);char *pTemp = buf;pTemp += sprintf(pTemp,"RTSP/1.0 200 OK\r\nCSeq: %s\r\n",cseq);pTemp += sprintf(pTemp,"%s",dateHeader());pTemp += sprintf(pTemp,"Content-Type: application/sdp\r\n");char *pTemp2 = sdpMsg;pTemp2 += sprintf(pTemp2,"v=0\r\n");pTemp2 += sprintf(pTemp2,"o=StreamingServer 3331435948 1116907222000 IN IP4 %s\r\n",localip);pTemp2 += sprintf(pTemp2,"s=H.264\r\n");pTemp2 += sprintf(pTemp2,"c=IN IP4 0.0.0.0\r\n");pTemp2 += sprintf(pTemp2,"t=0 0\r\n");pTemp2 += sprintf(pTemp2,"a=control:*\r\n");/*H264 TrackID=0 RTP_PT 96*/pTemp2 += sprintf(pTemp2,"m=video 0 RTP/AVP 96\r\n");pTemp2 += sprintf(pTemp2,"a=control:trackID=0\r\n");pTemp2 += sprintf(pTemp2,"a=rtpmap:96 H264/90000\r\n");pTemp2 += sprintf(pTemp2,"a=fmtp:96 packetization-mode=1; sprop-parameter-sets=%s\r\n", "AAABBCCC");
#if 1/*G726*/pTemp2 += sprintf(pTemp2,"m=audio 0 RTP/AVP 97\r\n");pTemp2 += sprintf(pTemp2,"a=control:trackID=1\r\n");if(strcmp(g_rtp_playload,"AAC")==0){pTemp2 += sprintf(pTemp2,"a=rtpmap:97 MPEG4-GENERIC/%d/2\r\n",16000);pTemp2 += sprintf(pTemp2,"a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1410\r\n");}else{pTemp2 += sprintf(pTemp2,"a=rtpmap:97 G726-32/%d/1\r\n",8000);pTemp2 += sprintf(pTemp2,"a=fmtp:97 packetization-mode=1\r\n");}
#endifpTemp += sprintf(pTemp,"Content-length: %d\r\n", strlen(sdpMsg));     pTemp += sprintf(pTemp,"Content-Base: rtsp://%s/%s/\r\n\r\n",localip,urlSuffix);//printf("mem ready\n");strcat(pTemp, sdpMsg);free(localip);//printf("Describe ready sent\n");int re = send(sock, buf, strlen(buf),0);if(re <= 0){return FALSE;}else{printf(">>>>>%s\n",buf);}}return TRUE;
}

传进来的参数

  • cseq——会话的序列号
  • sock——socket建立的网络通道
  • urlSuffix——解析出来的URL字符串
  • recvbuf——服务器接收到的整个信息

函数中,先获取了服务器本地的IP地址
接下来,就按照RTSP的DESCRIBE标准进行回复

RTP协议中,使用VLC进行播放,需要有一个SDP文件进行解析才能播放,RTP传的是裸流,没有控制信息,需要本地配置
而RTSP协议中,SDP包含在交互信息中,不需要用VLC打开SDP文件,通过解析交互信息,客户端就能够知道怎么去解析视频流,代码中的体现如下

char *pTemp2 = sdpMsg;
pTemp2 += sprintf(pTemp2,"v=0\r\n");
pTemp2 += sprintf(pTemp2,"o=StreamingServer 3331435948 1116907222000 IN IP4 %s\r\n",localip);
pTemp2 += sprintf(pTemp2,"s=H.264\r\n");
pTemp2 += sprintf(pTemp2,"c=IN IP4 0.0.0.0\r\n");
pTemp2 += sprintf(pTemp2,"t=0 0\r\n");
pTemp2 += sprintf(pTemp2,"a=control:*\r\n");

PLAY

int PlayAnswer(char *cseq, int sock,int SessionId,char* urlPre,char* recvbuf)
{if (sock != 0){char buf[1024];memset(buf,0,1024);char *pTemp = buf;char*localip;localip = GetLocalIP(sock);pTemp += sprintf(pTemp,"RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sRange: npt=0.000-\r\nSession: %d\r\nRTP-Info: url=rtsp://%s/%s;seq=0\r\n\r\n",cseq,dateHeader(),SessionId,localip,urlPre);free(localip);int reg = send(sock, buf,strlen(buf),0);if(reg <= 0){return FALSE;}else{printf(">>>>>%s",buf);udpfd = socket(AF_INET,SOCK_DGRAM,0);//UDPstruct sockaddr_in server;server.sin_family=AF_INET;server.sin_port=htons(g_rtspClients[0].rtpport[0]);          server.sin_addr.s_addr=inet_addr(g_rtspClients[0].IP);connect(udpfd,(struct sockaddr *)&server,sizeof(server));printf("udp up\n");}return TRUE;}return FALSE;
}

客户端play请求后,服务端开启一个UDP的SOCKET通道进行裸流的传输,命令走的是TCP通道。这就是RTSP的设计

因为考虑实时,所以用的是UDP

vdRTPSendThread

HI_VOID* vdRTPSendThread(HI_VOID *p)
{while(1){if(!list_empty(&RTPbuf_head)){RTPbuf_s *p = get_first_item(&RTPbuf_head,RTPbuf_s,list);VENC_Sent(p->buf,p->len);list_del(&(p->list));free(p->buf);free(p);p = NULL;count--;//printf("count = %d\n",count);}usleep(5000);}
}

在client连接后,server收到play指令,创建裸流的UDP通道

发送是在vdRTPSendThread线程中进行。每隔5微妙在RTPbuf_head链表里查找数据,有数据就进行发送

get_first_item用来取出链表里的第一个非空节点
然后将节点中的内容通过VENC_Sent发送出去,函数的内容主要是为了H264封RTP包添加包头
发送完就将它释放掉
这里的send是消费者,不断发送链表中的数据

有消费者就有生产者,生产在编码的地方

发送有两种方法,一种直接发送,一种是环形buffer发送

直接发送的方法是编码一帧,就发一帧。直接发送的方法不是在vdRTPSendThread线程中,而是在编码的地方发送,编完一帧就发一帧

HI_S32 SAMPLE_COMM_VENC_Sentjin(VENC_STREAM_S *pstStream)
{HI_S32 i,flag=0;for(i=0;i<MAX_RTSP_CLIENT;i++)//have atleast a connect{if(g_rtspClients[i].status == RTSP_SENDING){flag = 1;break;}}if(flag){for (i = 0; i < pstStream->u32PackCount; i++){HI_S32 lens=0,j,lastadd=0,newadd=0,showflap=0;char sendbuf[320*1024];//char tmp[640*1024];lens = pstStream->pstPack[i].u32Len-pstStream->pstPack[i].u32Offset;memcpy(&sendbuf[0],pstStream->pstPack[i].pu8Addr+pstStream->pstPack[i].u32Offset,lens);//printf("lens = %d, count= %d\n",lens,count++);VENC_Sent(sendbuf,lens);        lens = 0;}}return HI_SUCCESS;
}

直接发送的方式优势是简单,但实际应用中不会这么去做。实际情况中会比较复杂,编码和传输的速度不一定适配。
如果发送的速度比编码的速度块,那么发送就要等编码。时间上会有冗余

环形buffer就可以有一定的缓冲。如果发送比编码快,那么就阻塞,就像视频缓冲;如果编码比发送快,甚至套了发送一圈了,那么可以增大环形buffer,但是早晚会有崩溃的一天,编码快得多就要考虑系统的设计。
实际谁快谁慢是不确定的,比如网络波动会影响发送的速度。有时候发送慢了,那编码的帧排列准备;有时候发送快了,那么在排队的编码完的帧等待的数量少一点。

环形buffer发送的代码如下

HI_S32 saveStream(VENC_STREAM_S *pstStream)
{HI_S32 i,j,lens=0;for(j=0;j<MAX_RTSP_CLIENT;j++)//have atleast a connect{if(g_rtspClients[j].status == RTSP_SENDING){for (i = 0; i < pstStream->u32PackCount; i++){RTPbuf_s *p = (RTPbuf_s *)malloc(sizeof(RTPbuf_s));INIT_LIST_HEAD(&(p->list));lens = pstStream->pstPack[i].u32Len-pstStream->pstPack[i].u32Offset;p->buf = (char *)malloc(lens);p->len = lens;memcpy(p->buf,pstStream->pstPack[i].pu8Addr+pstStream->pstPack[i].u32Offset,lens);list_add_tail(&(p->list),&RTPbuf_head);count++;//printf("count = %d\n",count);}}}return HI_SUCCESS;
}

环形buffer是通过链表实现
count是用来记录剩余编码完的帧的数量,发送的话就去减减,到0说明编码完的帧用完了。
这里就是上面说的生产者,把编码完的帧送到全局的链表中,由vdRTPSendThread发送
发送完一个节点,就把这个节点拿掉,并且把内存释放

海思3518E开发笔记6.1——RTSP实时图传源码分析相关推荐

  1. 海思3518E开发笔记1.6——rootfs及其启动流程分析

    3518E的根文件系统 /etc/fstab fs-version group profile udev init rcS S00devs S01udev S80network S90hibernat ...

  2. 海思3518E开发笔记1.5——flash分区及uboot、kernel、rootfs烧写并部署

    目录 规划分区 烧写流程 tftp更新并重新烧写uboot的命令序列 tftp更新并重新烧写kernel的命令序列 tftp更新并重新烧写rootfs的命令序列: 裸机烧录uboot 什么是裸机 烧录 ...

  3. 海思3518E开发笔记1.1——HI3518E方案整体架构介绍

    目录 前言 硬件 软件 前言 在做一个项目之前,需要对这个项目在一个比较高的高度进行了解. 如,硬件构成.软件是裸机的还是带操作系统的,带操作系统的是怎么样一个开发流程. 得对项目由打到校进行了解,知 ...

  4. 海思3518E开发笔记2.5——海思VI(video input)模块详解

    目录 海思video input模块架构介绍 海思video input模块功能介绍 结构体说明 函数调用关系 流程分析 step 1: mipi configure step 2: configur ...

  5. 海思3518E开发笔记2.7——海思VENC(Video Encode)模块详解

    目录 海思Video Encode模块架构介绍 海思Video Encode相关概念 码率控制 海思Video Encode模块功能介绍 编码通道 ROI 裁剪编码 函数调用关系 相关数据结构 流程分 ...

  6. 海思3518E开发笔记4.2——sensor数据交互接口(MIPI、LVDS、DVP并口),以AR0130和OV9712为例

    目录 前言 DVP(并口) LVDS MIPI(MIPI-CSI2) 总结 前言 摄像头是一个独立的芯片,和主控芯片是弧线独立的,两者通过接口进行交互,交互的信息有控制信号和数据信号 控制信号常用I2 ...

  7. 小朱笔记之hadoop应用实战、源码分析-目录

    小朱笔记之hadoop应用实战.源码分析 1.1 背景目的 该笔记从宏观架构.安装配置.源码分析.使用案例四个方面剖析了Hadoop1.0.3,希望能对同学们提供帮助,赠人玫瑰,手留余香.能够把had ...

  8. glibc-2.23学习笔记(二)—— free部分源码分析

    glibc-2.23学习笔记(二)-- free部分源码分析 _libc_free _int_free 函数定义 局部变量 start fast bins部分 unsorted bins部分 mmap ...

  9. glibc-2.23学习笔记(一)—— malloc部分源码分析

    glibc-2.23学习笔记(一)-- malloc部分源码分析 搭建Glibc源码调试环境 1.下载并解压glibc源码 2.配置gdb 3.编译测试程序 第一次调用 源码分析 __libc_mal ...

  10. 基于比原链开发Dapp(三)-Dapp-demo前端源码分析

    # 简介 ​    本章内容会针对比原官方提供的dapp-demo,分析里面的前端源码,分析清楚整个demo的流程,然后针对里面开发过程遇到的坑,添加一下个人的见解还有解决的方案. ### 储蓄分红合 ...

最新文章

  1. opencv resize_树莓派监控摄像头python+picamera或openCV
  2. [置顶]       设计模式之六大原则——单一职责原则(SRP)
  3. SAP 电商云 Accelerator 和 Spartacus UI 的工作机制差异
  4. vue如何新建一个项目(超详细哦--转)
  5. 想学好数学,请收好这份宝典!
  6. python读写ini文件的库支持unicode_Python读写unicode文件的方法
  7. 图片hover且设置transform其父级border-radius失效
  8. linux 类似迅雷下载软件,推荐几个可以替代迅雷的下载软件
  9. chromedriver与chrome各版本及下载地址
  10. manjaro(linux)安装网易云音乐
  11. access 分组序号,使用Access SQL进行分组排名
  12. 小觅相机运行VINS-Fusion(三)——IMU的标定
  13. r730服务器安装系统蓝屏6,安装系统蓝屏解决解决方法
  14. 2022最新:8种常用DNA甲基化测序技术,你知道几个?|易基因
  15. type 与 interface 的区别
  16. AUTOCAD——打断命令、合并命令
  17. 在计算机病毒组成结构中,计算机病毒的结构中有哪三种机制组成?
  18. java switch语句_Java switch语句
  19. 什么是死锁?如何避免死锁?
  20. StarUML使用说明

热门文章

  1. 【老生谈算法】matlab实现数字图像复原算法源码——数字图像复原算法
  2. 西门子s7删除注册表,西门子S7200编程软件卸载步骤
  3. matlab 贝叶斯回归,贝叶斯向量自回归MATLAB代码 使用matlab实现贝叶斯向量自回归模型 - 下载 - 搜珍网...
  4. 最新O泡易支付系统源码-源码全解密,无后门,本地资源化
  5. 学号密码错误的计算机流程图,自学考试管理信息系统练习题及答案
  6. 免费的微信编辑器插件调用
  7. 空间点到空间直线的距离求解
  8. 福州发布《关于运用大数据加强对市场主体服务和监管实施方案》
  9. QCC512x QCC302x Earbud 工程增加三击事件
  10. 【学术相关】iccv、cvpr、eccv论文接收率及格式下载(附论文下载)