目前最好的,以rtmp发送264到red5服务器的代码,darkdragonking亲测可用--《到H264视频通过RTMP直播》
前面的文章中提到了通过RTSP(Real Time Streaming Protocol)的方式来实现视频的直播,但RTSP方式的一个弊端是如果需要支持客户端通过网页来访问,就需要在在页面中嵌入一个ActiveX控件,而ActiveX一般都需要签名才能正常使用,否则用户在使用时还需要更改浏览器设置,并且ActiveX还只支持IE内核的浏览器,Chrome、FireFox需要IE插件才能运行,因此会特别影响用户体验。而RTMP(Real Time Messaging Protocol)很好的解决了这一个问题。由于RTMP是针对FLASH的流媒体协议,视频通过RTMP直播后,只需要在WEB上嵌入一个Web Player(如Jwplayer)即可观看,而且对平台也没什么限制,还可以方便的通过手机观看。
视频通过RTMP方式发布需要一个RTMP Server(常见的有FMS、Wowza Media Server, 开源的有CRtmpServer、Red5等),原始视频只要按照RTMP协议发送给RTMP Server就可以RTMP视频流的发布了。为了便于视频的打包发布,封装了一个RTMPStream,目前只支持发送H264的视频文件。可以直接发送H264数据帧或H264文件,RTMPStream提供的接口如下。
- classCRTMPStream
- {
- public:
- CRTMPStream(void);
- ~CRTMPStream(void);
- public:
- // 连接到RTMP Server
- boolConnect(constchar* url);
- // 断开连接
- voidClose();
- // 发送MetaData
- boolSendMetadata(LPRTMPMetadata lpMetaData);
- // 发送H264数据帧
- boolSendH264Packet(unsignedchar*data,unsignedintsize,boolbIsKeyFrame,unsignedintnTimeStamp);
- // 发送H264文件
- boolSendH264File(constchar*pFileName);
- //...
- }
调用示例:
- #include <stdio.h>
- #include "RTMPStream\RTMPStream.h"
- intmain(intargc,char* argv[])
- {
- CRTMPStream rtmpSender;
- boolbRet = rtmpSender.Connect("rtmp://192.168.1.104/live/test");
- rtmpSender.SendH264File("E:\\video\\test.264");
- rtmpSender.Close();
- }
通过JwPlayer播放效果如下:
最后附上RTMPStream完整的代码:
- /********************************************************************
- filename: RTMPStream.h
- created: 2013-04-3
- author: firehood
- purpose: 发送H264视频到RTMP Server,使用libRtmp库
- *********************************************************************/
- #pragma once
- #include "rtmp.h"
- #include "rtmp_sys.h"
- #include "amf.h"
- #include <stdio.h>
- #define FILEBUFSIZE (1024 * 1024 * 10) // 10M
- // NALU单元
- typedefstruct_NaluUnit
- {
- inttype;
- intsize;
- unsigned char*data;
- }NaluUnit;
- typedefstruct_RTMPMetadata
- {
- // video, must be h264 type
- unsigned intnWidth;
- unsigned intnHeight;
- unsigned intnFrameRate;// fps
- unsigned intnVideoDataRate;// bps
- unsigned intnSpsLen;
- unsigned charSps[1024];
- unsigned intnPpsLen;
- unsigned charPps[1024];
- // audio, must be aac type
- boolbHasAudio;
- unsigned intnAudioSampleRate;
- unsigned intnAudioSampleSize;
- unsigned intnAudioChannels;
- charpAudioSpecCfg;
- unsigned intnAudioSpecCfgLen;
- } RTMPMetadata,*LPRTMPMetadata;
- classCRTMPStream
- {
- public:
- CRTMPStream(void);
- ~CRTMPStream(void);
- public:
- // 连接到RTMP Server
- boolConnect(constchar* url);
- // 断开连接
- voidClose();
- // 发送MetaData
- boolSendMetadata(LPRTMPMetadata lpMetaData);
- // 发送H264数据帧
- boolSendH264Packet(unsignedchar*data,unsignedintsize,boolbIsKeyFrame,unsignedintnTimeStamp);
- // 发送H264文件
- boolSendH264File(constchar*pFileName);
- private:
- // 送缓存中读取一个NALU包
- boolReadOneNaluFromBuf(NaluUnit &nalu);
- // 发送数据
- intSendPacket(unsignedintnPacketType,unsignedchar*data,unsignedintsize,unsignedintnTimestamp);
- private:
- RTMP* m_pRtmp;
- unsigned char* m_pFileBuf;
- unsigned intm_nFileBufSize;
- unsigned intm_nCurPos;
- };
- /********************************************************************
- filename: RTMPStream.cpp
- created: 2013-04-3
- author: firehood
- purpose: 发送H264视频到RTMP Server,使用libRtmp库
- *********************************************************************/
- #include "RTMPStream.h"
- #include "SpsDecode.h"
- #ifdef WIN32
- #include <windows.h>
- #endif
- #ifdef WIN32
- #pragma comment(lib,"WS2_32.lib")
- #pragma comment(lib,"winmm.lib")
- #endif
- enum
- {
- FLV_CODECID_H264 = 7,
- };
- intInitSockets()
- {
- #ifdef WIN32
- WORDversion;
- WSADATA wsaData;
- version = MAKEWORD(1, 1);
- return(WSAStartup(version, &wsaData) == 0);
- #else
- returnTRUE;
- #endif
- }
- inlinevoidCleanupSockets()
- {
- #ifdef WIN32
- WSACleanup();
- #endif
- }
- char* put_byte(char*output, uint8_t nVal )
- {
- output[0] = nVal;
- returnoutput+1;
- }
- char* put_be16(char*output, uint16_t nVal )
- {
- output[1] = nVal & 0xff;
- output[0] = nVal >> 8;
- returnoutput+2;
- }
- char* put_be24(char*output,uint32_t nVal )
- {
- output[2] = nVal & 0xff;
- output[1] = nVal >> 8;
- output[0] = nVal >> 16;
- returnoutput+3;
- }
- char* put_be32(char*output, uint32_t nVal )
- {
- output[3] = nVal & 0xff;
- output[2] = nVal >> 8;
- output[1] = nVal >> 16;
- output[0] = nVal >> 24;
- returnoutput+4;
- }
- char* put_be64(char*output, uint64_t nVal )
- {
- output=put_be32( output, nVal >> 32 );
- output=put_be32( output, nVal );
- returnoutput;
- }
- char* put_amf_string(char*c,constchar*str )
- {
- uint16_t len = strlen( str );
- c=put_be16( c, len );
- memcpy(c,str,len);
- returnc+len;
- }
- char* put_amf_double(char*c,doubled )
- {
- *c++ = AMF_NUMBER; /* type: Number */
- {
- unsigned char*ci, *co;
- ci = (unsigned char*)&d;
- co = (unsigned char*)c;
- co[0] = ci[7];
- co[1] = ci[6];
- co[2] = ci[5];
- co[3] = ci[4];
- co[4] = ci[3];
- co[5] = ci[2];
- co[6] = ci[1];
- co[7] = ci[0];
- }
- returnc+8;
- }
- CRTMPStream::CRTMPStream(void):
- m_pRtmp(NULL),
- m_nFileBufSize(0),
- m_nCurPos(0)
- {
- m_pFileBuf = newunsignedchar[FILEBUFSIZE];
- memset(m_pFileBuf,0,FILEBUFSIZE);
- InitSockets();
- m_pRtmp = RTMP_Alloc();
- RTMP_Init(m_pRtmp);
- }
- CRTMPStream::~CRTMPStream(void)
- {
- Close();
- WSACleanup();
- delete[] m_pFileBuf;
- }
- boolCRTMPStream::Connect(constchar* url)
- {
- if(RTMP_SetupURL(m_pRtmp, (char*)url)<0)
- {
- returnFALSE;
- }
- RTMP_EnableWrite(m_pRtmp);
- if(RTMP_Connect(m_pRtmp, NULL)<0)
- {
- returnFALSE;
- }
- if(RTMP_ConnectStream(m_pRtmp,0)<0)
- {
- returnFALSE;
- }
- returnTRUE;
- }
- voidCRTMPStream::Close()
- {
- if(m_pRtmp)
- {
- RTMP_Close(m_pRtmp);
- RTMP_Free(m_pRtmp);
- m_pRtmp = NULL;
- }
- }
- intCRTMPStream::SendPacket(unsignedintnPacketType,unsignedchar*data,unsignedintsize,unsignedintnTimestamp)
- {
- if(m_pRtmp == NULL)
- {
- returnFALSE;
- }
- RTMPPacket packet;
- RTMPPacket_Reset(&packet);
- RTMPPacket_Alloc(&packet,size);
- packet.m_packetType = nPacketType;
- packet.m_nChannel = 0x04;
- packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
- packet.m_nTimeStamp = nTimestamp;
- packet.m_nInfoField2 = m_pRtmp->m_stream_id;
- packet.m_nBodySize = size;
- memcpy(packet.m_body,data,size);
- intnRet = RTMP_SendPacket(m_pRtmp,&packet,0);
- RTMPPacket_Free(&packet);
- returnnRet;
- }
- boolCRTMPStream::SendMetadata(LPRTMPMetadata lpMetaData)
- {
- if(lpMetaData == NULL)
- {
- returnfalse;
- }
- charbody[1024] = {0};;
- char* p = (char*)body;
- p = put_byte(p, AMF_STRING );
- p = put_amf_string(p , "@setDataFrame");
- p = put_byte( p, AMF_STRING );
- p = put_amf_string( p, "onMetaData");
- p = put_byte(p, AMF_OBJECT );
- p = put_amf_string( p, "copyright");
- p = put_byte(p, AMF_STRING );
- p = put_amf_string( p, "firehood");
- p =put_amf_string( p, "width");
- p =put_amf_double( p, lpMetaData->nWidth);
- p =put_amf_string( p, "height");
- p =put_amf_double( p, lpMetaData->nHeight);
- p =put_amf_string( p, "framerate");
- p =put_amf_double( p, lpMetaData->nFrameRate);
- p =put_amf_string( p, "videocodecid");
- p =put_amf_double( p, FLV_CODECID_H264 );
- p =put_amf_string( p, "");
- p =put_byte( p, AMF_OBJECT_END );
- intindex = p-body;
- SendPacket(RTMP_PACKET_TYPE_INFO,(unsigned char*)body,p-body,0);
- inti = 0;
- body[i++] = 0x17; // 1:keyframe 7:AVC
- body[i++] = 0x00; // AVC sequence header
- body[i++] = 0x00;
- body[i++] = 0x00;
- body[i++] = 0x00; // fill in 0;
- // AVCDecoderConfigurationRecord.
- body[i++] = 0x01; // configurationVersion
- body[i++] = lpMetaData->Sps[1]; // AVCProfileIndication
- body[i++] = lpMetaData->Sps[2]; // profile_compatibility
- body[i++] = lpMetaData->Sps[3]; // AVCLevelIndication
- body[i++] = 0xff; // lengthSizeMinusOne
- // sps nums
- body[i++] = 0xE1; //&0x1f
- // sps data length
- body[i++] = lpMetaData->nSpsLen>>8;
- body[i++] = lpMetaData->nSpsLen&0xff;
- // sps data
- memcpy(&body[i],lpMetaData->Sps,lpMetaData->nSpsLen);
- i= i+lpMetaData->nSpsLen;
- // pps nums
- body[i++] = 0x01; //&0x1f
- // pps data length
- body[i++] = lpMetaData->nPpsLen>>8;
- body[i++] = lpMetaData->nPpsLen&0xff;
- // sps data
- memcpy(&body[i],lpMetaData->Pps,lpMetaData->nPpsLen);
- i= i+lpMetaData->nPpsLen;
- returnSendPacket(RTMP_PACKET_TYPE_VIDEO,(unsignedchar*)body,i,0);
- }
- boolCRTMPStream::SendH264Packet(unsignedchar*data,unsignedintsize,boolbIsKeyFrame,unsignedintnTimeStamp)
- {
- if(data == NULL && size<11)
- {
- returnfalse;
- }
- unsigned char*body =newunsignedchar[size+9];
- inti = 0;
- if(bIsKeyFrame)
- {
- body[i++] = 0x17;// 1:Iframe 7:AVC
- }
- else
- {
- body[i++] = 0x27;// 2:Pframe 7:AVC
- }
- body[i++] = 0x01;// AVC NALU
- body[i++] = 0x00;
- body[i++] = 0x00;
- body[i++] = 0x00;
- // NALU size
- body[i++] = size>>24;
- body[i++] = size>>16;
- body[i++] = size>>8;
- body[i++] = size&0xff;;
- // NALU data
- memcpy(&body[i],data,size);
- boolbRet = SendPacket(RTMP_PACKET_TYPE_VIDEO,body,i+size,nTimeStamp);
- delete[] body;
- returnbRet;
- }
- boolCRTMPStream::SendH264File(constchar*pFileName)
- {
- if(pFileName == NULL)
- {
- returnFALSE;
- }
- FILE*fp = fopen(pFileName,"rb");
- if(!fp)
- {
- printf("ERROR:open file %s failed!",pFileName);
- }
- fseek(fp, 0, SEEK_SET);
- m_nFileBufSize = fread(m_pFileBuf, sizeof(unsignedchar), FILEBUFSIZE, fp);
- if(m_nFileBufSize >= FILEBUFSIZE)
- {
- printf("warning : File size is larger than BUFSIZE\n");
- }
- fclose(fp);
- RTMPMetadata metaData;
- memset(&metaData,0,sizeof(RTMPMetadata));
- NaluUnit naluUnit;
- // 读取SPS帧
- ReadOneNaluFromBuf(naluUnit);
- metaData.nSpsLen = naluUnit.size;
- memcpy(metaData.Sps,naluUnit.data,naluUnit.size);
- // 读取PPS帧
- ReadOneNaluFromBuf(naluUnit);
- metaData.nPpsLen = naluUnit.size;
- memcpy(metaData.Pps,naluUnit.data,naluUnit.size);
- // 解码SPS,获取视频图像宽、高信息
- intwidth = 0,height = 0;
- h264_decode_sps(metaData.Sps,metaData.nSpsLen,width,height);
- metaData.nWidth = width;
- metaData.nHeight = height;
- metaData.nFrameRate = 25;
- // 发送MetaData
- SendMetadata(&metaData);
- unsigned inttick = 0;
- while(ReadOneNaluFromBuf(naluUnit))
- {
- boolbKeyframe = (naluUnit.type == 0x05) ? TRUE : FALSE;
- // 发送H264数据帧
- SendH264Packet(naluUnit.data,naluUnit.size,bKeyframe,tick);
- msleep(40);
- tick +=40;
- }
- returnTRUE;
- }
- boolCRTMPStream::ReadOneNaluFromBuf(NaluUnit &nalu)
- {
- inti = m_nCurPos;
- while(i<m_nFileBufSize)
- {
- if(m_pFileBuf[i++] == 0x00 &&
- m_pFileBuf[i++] == 0x00 &&
- m_pFileBuf[i++] == 0x00 &&
- m_pFileBuf[i++] == 0x01
- )
- {
- intpos = i;
- while(pos<m_nFileBufSize)
- {
- if(m_pFileBuf[pos++] == 0x00 &&
- m_pFileBuf[pos++] == 0x00 &&
- m_pFileBuf[pos++] == 0x00 &&
- m_pFileBuf[pos++] == 0x01
- )
- {
- break;
- }
- }
- if(pos == nBufferSize)
- {
- nalu.size = pos-i;
- }
- else
- {
- nalu.size = (pos-4)-i;
- }
- nalu.type = m_pFileBuf[i]&0x1f;
- nalu.data = &m_pFileBuf[i];
- m_nCurPos = pos-4;
- returnTRUE;
- }
- }
- returnFALSE;
- }
其中,在编译过程中,需要导入rtmp对应的包和头文件,这个要自己从网上搜索下,可以参考这里http://download.csdn.net/detail/zhadenianqu/7462751
附上SpsDecode.h文件:
- #include <stdio.h>
- #include <math.h>
- UINTUe(BYTE*pBuff,UINTnLen,UINT&nStartBit)
- {
- //计算0bit的个数
- UINTnZeroNum = 0;
- while(nStartBit < nLen * 8)
- {
- if(pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8)))//&:按位与,%取余
- {
- break;
- }
- nZeroNum++;
- nStartBit++;
- }
- nStartBit ++;
- //计算结果
- DWORDdwRet = 0;
- for(UINTi=0; i<nZeroNum; i++)
- {
- dwRet <<= 1;
- if(pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
- {
- dwRet += 1;
- }
- nStartBit++;
- }
- return(1 << nZeroNum) - 1 + dwRet;
- }
- intSe(BYTE*pBuff,UINTnLen,UINT&nStartBit)
- {
- intUeVal=Ue(pBuff,nLen,nStartBit);
- doublek=UeVal;
- intnValue=ceil(k/2);//ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00
- if(UeVal % 2==0)
- nValue=-nValue;
- returnnValue;
- }
- DWORDu(UINTBitCount,BYTE* buf,UINT&nStartBit)
- {
- DWORDdwRet = 0;
- for(UINTi=0; i<BitCount; i++)
- {
- dwRet <<= 1;
- if(buf[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
- {
- dwRet += 1;
- }
- nStartBit++;
- }
- returndwRet;
- }
- boolh264_decode_sps(BYTE* buf,unsignedintnLen,int&width,int&height)
- {
- UINTStartBit=0;
- intforbidden_zero_bit=u(1,buf,StartBit);
- intnal_ref_idc=u(2,buf,StartBit);
- intnal_unit_type=u(5,buf,StartBit);
- if(nal_unit_type==7)
- {
- intprofile_idc=u(8,buf,StartBit);
- intconstraint_set0_flag=u(1,buf,StartBit);//(buf[1] & 0x80)>>7;
- intconstraint_set1_flag=u(1,buf,StartBit);//(buf[1] & 0x40)>>6;
- intconstraint_set2_flag=u(1,buf,StartBit);//(buf[1] & 0x20)>>5;
- intconstraint_set3_flag=u(1,buf,StartBit);//(buf[1] & 0x10)>>4;
- intreserved_zero_4bits=u(4,buf,StartBit);
- intlevel_idc=u(8,buf,StartBit);
- intseq_parameter_set_id=Ue(buf,nLen,StartBit);
- if( profile_idc == 100 || profile_idc == 110 ||
- profile_idc == 122 || profile_idc == 144 )
- {
- intchroma_format_idc=Ue(buf,nLen,StartBit);
- if( chroma_format_idc == 3 )
- intresidual_colour_transform_flag=u(1,buf,StartBit);
- intbit_depth_luma_minus8=Ue(buf,nLen,StartBit);
- intbit_depth_chroma_minus8=Ue(buf,nLen,StartBit);
- intqpprime_y_zero_transform_bypass_flag=u(1,buf,StartBit);
- intseq_scaling_matrix_present_flag=u(1,buf,StartBit);
- intseq_scaling_list_present_flag[8];
- if( seq_scaling_matrix_present_flag )
- {
- for(inti = 0; i < 8; i++ ) {
- seq_scaling_list_present_flag[i]=u(1,buf,StartBit);
- }
- }
- }
- intlog2_max_frame_num_minus4=Ue(buf,nLen,StartBit);
- intpic_order_cnt_type=Ue(buf,nLen,StartBit);
- if( pic_order_cnt_type == 0 )
- intlog2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);
- elseif( pic_order_cnt_type == 1 )
- {
- intdelta_pic_order_always_zero_flag=u(1,buf,StartBit);
- intoffset_for_non_ref_pic=Se(buf,nLen,StartBit);
- intoffset_for_top_to_bottom_field=Se(buf,nLen,StartBit);
- intnum_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit);
- int*offset_for_ref_frame=newint[num_ref_frames_in_pic_order_cnt_cycle];
- for(inti = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
- offset_for_ref_frame[i]=Se(buf,nLen,StartBit);
- delete[] offset_for_ref_frame;
- }
- intnum_ref_frames=Ue(buf,nLen,StartBit);
- intgaps_in_frame_num_value_allowed_flag=u(1,buf,StartBit);
- intpic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);
- intpic_height_in_map_units_minus1=Ue(buf,nLen,StartBit);
- width=(pic_width_in_mbs_minus1+1)*16;
- height=(pic_height_in_map_units_minus1+1)*16;
- returntrue;
- }
- else
- returnfalse;
- }
目前最好的,以rtmp发送264到red5服务器的代码,darkdragonking亲测可用--《到H264视频通过RTMP直播》相关推荐
- 如何用python发送SMTP邮件(基于QQ邮箱)亲测可用
第一步 首先我们要在QQ邮箱开通SMTP邮件,并生成授权码 设置->账户->生成授权码 通过绑定手机发送短信生成授权码,并保存授权码,一系列英文字母(等会要用) 第二步 确保自己的计算机名 ...
- H264视频通过RTMP直播 .
前面的文章中提到了通过RTSP(Real Time Streaming Protocol)的方式来实现视频的直播,但RTSP方式的一个弊端是如果需要支持客户端通过网页来访问,就需要在在页面中嵌入一个A ...
- vue2.x 播放rtmp,hls,m3u8直播流教程,亲测可用
网上教程挺多,但实际用起来会各种报错 大部分原因是依赖的包的版本问题,因此我这里附上亲测可用的版本; 跟着步骤一步一来就行了! step1: 在package.json文件下添加所有关于直播的依赖包, ...
- H264视频通过RTMP直播
http://blog.csdn.net/firehood_/article/details/8783589 前面的文章中提到了通过RTSP(Real Time Streaming Protocol) ...
- rtmp/rtsp测试地址 2019/11/22日亲测可用
今天找rtsp的测试地址发现很多都是过期的,在一堆网址里面只找到这几个可用的,需要的自取 rtmp://58.200.131.2:1935/livetv/gxtv 广西卫视,可用,但是比较卡 rt ...
- python定时导出已发送文件_Python链接数据库查询导出查询结果到Excel并定时发送邮件到指定邮箱,实现巡检功能(亲测可用!!!)...
#python版本3.7 # -*- coding: utf-8 -*- """ Created on Wed Aug 1 11:35:17 2018 @author: ...
- Java实现的一个发送手机短信(亲测可用)
原文地址:http://sms.webchinese.cn/api.shtml JAVA发送手机短信,流传有几种方法:(1)使用webservice接口发送手机短信,这个可以使用sina提供的webs ...
- SpringBoot集成Kafka集群并实现接收_发送消息操作_以及常见错误_亲测---Kafka工作笔记005
1.注意这个过程中,很重要的是:版本,springboot的版本和spring-kafka的版本要对应起来. 2.我现在发现两个版本是没问题的,一会说明 3.还要注意yml资源文件,或者propert ...
- H5视频之RTMP、RTSP、HTTP协议流直播流测试地址
转自https://blog.csdn.net/u014162133/article/details/81188410 一.RTMP.RTSP.HTTP协议 这三个协议都属于互联网 TCP/IP 五层 ...
最新文章
- 【复盘】小朋友的奇思妙想
- 分享Kali Linux 2016.2第48周镜像文件
- JavaScript页面校验
- Aroma's Search(暴力)
- 【SSM面向CRUD编程专栏 2】Spring相关API 数据源(连接池)的配置 注解开发 整合junit
- 有关asp.net技术的外文文献_医学科技论文写作中参考文献的标准格式及常见问题...
- python---之suplot和suplots的区别
- Oracle查询 rownum和rowid的区别
- java考试题精选30道(附答案)
- 工作流:一文让你学会使用flowable工作流
- EasyPlayer播放海康大华RTSP流时RTSPClient客户端连接兼容问题的解决
- 纯前端实现—用户注册登录界面
- Android异常 Eclipse编译应用时出现 com.android.dx.cf.iface.parseexception
- 不限专业和工作年限就能报考的证书有哪些?
- 管理QQ群,什么情况下适合用软件
- java:如何解决汉字在记事本中编译翻译后出现乱码
- 哲理短文-人生什么最重要
- 关于HML要玩物联网这件事 之 CC3200 TCP Client
- 大公司高级Android工程师技能要求
- VMware-ESXi、vCenter、vSphere Client、Datastorage部署
热门文章
- Java将字符串按照指定长度分割成字符串数组
- 华为nova5 pro鸿蒙,华为nova5 Pro简单上手:一款“不偏科”的真旗舰!
- python画黑白线条_Python实现手绘图效果实例分享
- navicat连不上mysql client does not support authentication protocol requested by server; consider upgrad
- java数组 插入,Java数组添加元素
- Arduino简单实例之四_PS2游戏摇杆
- Linux环境搭建:软件包的几种安装方式,环境变量的设置,防火墙,PHPWIND
- 0基础想入坑3d建模的小白,你真的了解建模了吗?
- Ubuntu用命令行打开网页的三种方法
- JavaScript 三目运算符 return不合语法