当前位置: 首页 > 编程日记 > 正文

1小时学会:最简单的iOS直播推流(七)h264/aac 硬编码

最简单的iOS 推流代码,视频捕获,软编码(faac,x264),硬编码(aac,h264),美颜,flv编码,rtmp协议,陆续更新代码解析,你想学的知识这里都有,愿意懂直播技术的同学快来看!!

源代码:https://github.com/hardman/AWLive

前面已经介绍了如何从硬件设备获取到音视频数据(pcm,NV12)。

但是我们需要的视频格式是 aac和 h264。

现在就介绍一下如何将pcm编码aac,将NV12数据编码为h264。

编码分为软编码和硬编码。

硬编码是系统提供的,由系统专门嵌入的硬件设备处理音视频编码,主要计算操作在对应的硬件中。硬编码的特点是,速度快,cpu占用少,但是不够灵活,只能使用一些特定的功能。

软编码是指,通过代码计算进行数据编码,主要计算操作在cpu中。软编码的特点是,灵活,多样,功能丰富可扩展,但是cpu占用较多。

在代码中,编码器是通过AWEncoderManager获取的。

AWENcoderManager是一个工厂,通过audioEncoderType和videoEncoderType指定编码器类型。

编码器分为两类,音频编码器(AWAudioEncoder),视频编码器(AWVideoEncoder)。

音视频编码器又分别分为硬编码(在HW目录中)和软编码(在SW目录中)。

所以编码部分主要有4个文件:硬编码H264(AWHWH264Encoder),硬编码AAC(AWHWAACEncoder),软编码AAC(AWSWFaacEncoder),软编码H264(AWSWX264Encoder)

硬编码H264

第一步,开启硬编码器

-(void)open{//创建 video encode session// 创建 video encode session// 传入视频宽高,编码类型:kCMVideoCodecType_H264// 编码回调:vtCompressionSessionCallback,这个回调函数为编码结果回调,编码成功后,会将数据传入此回调中。// (__bridge void * _Nullable)(self):这个参数会被原封不动地传入vtCompressionSessionCallback中,此参数为编码回调同外界通信的唯一参数。// &_vEnSession,c语言可以给传入参数赋值。在函数内部会分配内存并初始化_vEnSession。OSStatus status = VTCompressionSessionCreate(NULL, (int32_t)(self.videoConfig.pushStreamWidth), (int32_t)self.videoConfig.pushStreamHeight, kCMVideoCodecType_H264, NULL, NULL, NULL, vtCompressionSessionCallback, (__bridge void * _Nullable)(self), &_vEnSession);if (status == noErr) {// 设置参数// ProfileLevel,h264的协议等级,不同的清晰度使用不同的ProfileLevel。VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);// 设置码率VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(self.videoConfig.bitrate));// 设置实时编码VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);// 关闭重排Frame,因为有了B帧(双向预测帧,根据前后的图像计算出本帧)后,编码顺序可能跟显示顺序不同。此参数可以关闭B帧。VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);// 关键帧最大间隔,关键帧也就是I帧。此处表示关键帧最大间隔为2s。VTSessionSetProperty(_vEnSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(self.videoConfig.fps * 2));// 关于B帧 P帧 和I帧,请参考:http://blog.csdn.net/abcjennifer/article/details/6577934//参数设置完毕,准备开始,至此初始化完成,随时来数据,随时编码status = VTCompressionSessionPrepareToEncodeFrames(_vEnSession);if (status != noErr) {[self onErrorWithCode:AWEncoderErrorCodeVTSessionPrepareFailed des:@"硬编码vtsession prepare失败"];}}else{[self onErrorWithCode:AWEncoderErrorCodeVTSessionCreateFailed des:@"硬编码vtsession创建失败"];}
}

第二步,向编码器丢数据:

//这里的参数yuvData就是从相机获取的NV12数据。
-(aw_flv_video_tag *)encodeYUVDataToFlvTag:(NSData *)yuvData{if (!_vEnSession) {return NULL;}//yuv 变成 转CVPixelBufferRefOSStatus status = noErr;//视频宽度size_t pixelWidth = self.videoConfig.pushStreamWidth;//视频高度size_t pixelHeight = self.videoConfig.pushStreamHeight;//现在要把NV12数据放入 CVPixelBufferRef中,因为 硬编码主要调用VTCompressionSessionEncodeFrame函数,此函数不接受yuv数据,但是接受CVPixelBufferRef类型。CVPixelBufferRef pixelBuf = NULL;//初始化pixelBuf,数据类型是kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,此类型数据格式同NV12格式相同。CVPixelBufferCreate(NULL, pixelWidth, pixelHeight, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBuf);// Lock address,锁定数据,应该是多线程防止重入操作。if(CVPixelBufferLockBaseAddress(pixelBuf, 0) != kCVReturnSuccess){[self onErrorWithCode:AWEncoderErrorCodeLockSampleBaseAddressFailed des:@"encode video lock base address failed"];return NULL;}//将yuv数据填充到CVPixelBufferRef中size_t y_size = pixelWidth * pixelHeight;size_t uv_size = y_size / 4;uint8_t *yuv_frame = (uint8_t *)yuvData.bytes;//处理y frameuint8_t *y_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuf, 0);memcpy(y_frame, yuv_frame, y_size);uint8_t *uv_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuf, 1);memcpy(uv_frame, yuv_frame + y_size, uv_size * 2);//硬编码 CmSampleBufRef//时间戳uint32_t ptsMs = self.manager.timestamp + 1; //self.vFrameCount++ * 1000.f / self.videoConfig.fps;CMTime pts = CMTimeMake(ptsMs, 1000);//硬编码主要其实就这一句。将携带NV12数据的PixelBuf送到硬编码器中,进行编码。status = VTCompressionSessionEncodeFrame(_vEnSession, pixelBuf, pts, kCMTimeInvalid, NULL, pixelBuf, NULL);... ...
}

第三步,通过硬编码回调获取h264数据

static void vtCompressionSessionCallback (void * CM_NULLABLE outputCallbackRefCon,void * CM_NULLABLE sourceFrameRefCon,OSStatus status,VTEncodeInfoFlags infoFlags,CM_NULLABLE CMSampleBufferRef sampleBuffer ){//通过outputCallbackRefCon获取AWHWH264Encoder的对象指针,将编码好的h264数据传出去。AWHWH264Encoder *encoder = (__bridge AWHWH264Encoder *)(outputCallbackRefCon);//判断是否编码成功if (status != noErr) {dispatch_semaphore_signal(encoder.vSemaphore);[encoder onErrorWithCode:AWEncoderErrorCodeEncodeVideoFrameFailed des:@"encode video frame error 1"];return;}//是否数据是完整的if (!CMSampleBufferDataIsReady(sampleBuffer)) {dispatch_semaphore_signal(encoder.vSemaphore);[encoder onErrorWithCode:AWEncoderErrorCodeEncodeVideoFrameFailed des:@"encode video frame error 2"];return;}//是否是关键帧,关键帧和非关键帧要区分清楚。推流时也要注明。 BOOL isKeyFrame = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync);//首先获取sps 和pps//sps pss 也是h264的一部分,可以认为它们是特别的h264视频帧,保存了h264视频的一些必要信息。//没有这部分数据h264视频很难解析出来。//数据处理时,sps pps 数据可以作为一个普通h264帧,放在h264视频流的最前面。BOOL needSpsPps = NO;if (!encoder.spsPpsData) {if (isKeyFrame) {//获取avcC,这就是我们想要的sps和pps数据。//如果保存到文件中,需要将此数据前加上 [0 0 0 1] 4个字节,写入到h264文件的最前面。//如果推流,将此数据放入flv数据区即可。CMFormatDescriptionRef sampleBufFormat = CMSampleBufferGetFormatDescription(sampleBuffer);NSDictionary *dict = (__bridge NSDictionary *)CMFormatDescriptionGetExtensions(sampleBufFormat);encoder.spsPpsData = dict[@"SampleDescriptionExtensionAtoms"][@"avcC"];}needSpsPps = YES;}//获取真正的视频帧数据CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);size_t blockDataLen;uint8_t *blockData;status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &blockDataLen, (char **)&blockData);if (status == noErr) {size_t currReadPos = 0;//一般情况下都是只有1帧,在最开始编码的时候有2帧,取最后一帧while (currReadPos < blockDataLen - 4) {uint32_t naluLen = 0;memcpy(&naluLen, blockData + currReadPos, 4);naluLen = CFSwapInt32BigToHost(naluLen);//naluData 即为一帧h264数据。//如果保存到文件中,需要将此数据前加上 [0 0 0 1] 4个字节,按顺序写入到h264文件中。//如果推流,需要将此数据前加上4个字节表示数据长度的数字,此数据需转为大端字节序。//关于大端和小端模式,请参考此网址:http://blog.csdn.net/hackbuteer1/article/details/7722667encoder.naluData = [NSData dataWithBytes:blockData + currReadPos + 4 length:naluLen];currReadPos += 4 + naluLen;encoder.isKeyFrame = isKeyFrame;}}else{[encoder onErrorWithCode:AWEncoderErrorCodeEncodeGetH264DataFailed des:@"got h264 data failed"];}... ...
}

第四步,其实,此时硬编码已结束,这一步跟编码无关,将取得的h264数据,送到推流器中。

-(aw_flv_video_tag *)encodeYUVDataToFlvTag:(NSData *)yuvData{... ...if (status == noErr) {dispatch_semaphore_wait(self.vSemaphore, DISPATCH_TIME_FOREVER);if (_naluData) {//此处 硬编码成功,_naluData内的数据即为h264视频帧。//我们是推流,所以获取帧长度,转成大端字节序,放到数据的最前面uint32_t naluLen = (uint32_t)_naluData.length;//小端转大端。计算机内一般都是小端,而网络和文件中一般都是大端。大端转小端和小端转大端算法一样,就是字节序反转就行了。uint8_t naluLenArr[4] = {naluLen >> 24 & 0xff, naluLen >> 16 & 0xff, naluLen >> 8 & 0xff, naluLen & 0xff};//将数据拼在一起NSMutableData *mutableData = [NSMutableData dataWithBytes:naluLenArr length:4];[mutableData appendData:_naluData];//将h264数据合成flv tag,合成flvtag之后就可以直接发送到服务端了。后续会介绍aw_flv_video_tag *video_tag = aw_encoder_create_video_tag((int8_t *)mutableData.bytes, mutableData.length, ptsMs, 0, self.isKeyFrame);//到此,编码工作完成,清除状态。_naluData = nil;_isKeyFrame = NO;CVPixelBufferUnlockBaseAddress(pixelBuf, 0);CFRelease(pixelBuf);return video_tag;}}else{[self onErrorWithCode:AWEncoderErrorCodeEncodeVideoFrameFailed des:@"encode video frame error"];}CVPixelBufferUnlockBaseAddress(pixelBuf, 0);CFRelease(pixelBuf);return NULL;

第五步,关闭编码器

//永远不忘记关闭释放资源。
-(void)close{dispatch_semaphore_signal(self.vSemaphore);VTCompressionSessionInvalidate(_vEnSession);_vEnSession = nil;self.naluData = nil;self.isKeyFrame = NO;self.spsPpsData = nil;
}

硬编码AAC

硬编码AAC逻辑同H264差不多。

第一步,打开编码器

-(void)open{//创建audio encode converter也就是AAC编码器//初始化一系列参数AudioStreamBasicDescription inputAudioDes = {.mFormatID = kAudioFormatLinearPCM,.mSampleRate = self.audioConfig.sampleRate,.mBitsPerChannel = (uint32_t)self.audioConfig.sampleSize,.mFramesPerPacket = 1,//每个包1帧.mBytesPerFrame = 2,//每帧2字节.mBytesPerPacket = 2,//每个包1帧也是2字节.mChannelsPerFrame = (uint32_t)self.audioConfig.channelCount,//声道数,推流一般使用单声道//下面这个flags的设置参照此文:http://www.mamicode.com/info-detail-986202.html.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsNonInterleaved,.mReserved = 0};//设置输出格式,声道数AudioStreamBasicDescription outputAudioDes = {.mChannelsPerFrame = (uint32_t)self.audioConfig.channelCount,.mFormatID = kAudioFormatMPEG4AAC,0};//初始化_aConverteruint32_t outDesSize = sizeof(outputAudioDes);AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outDesSize, &outputAudioDes);OSStatus status = AudioConverterNew(&inputAudioDes, &outputAudioDes, &_aConverter);if (status != noErr) {[self onErrorWithCode:AWEncoderErrorCodeCreateAudioConverterFailed des:@"硬编码AAC创建失败"];}//设置码率uint32_t aBitrate = (uint32_t)self.audioConfig.bitrate;uint32_t aBitrateSize = sizeof(aBitrate);status = AudioConverterSetProperty(_aConverter, kAudioConverterEncodeBitRate, aBitrateSize, &aBitrate);//查询最大输出uint32_t aMaxOutput = 0;uint32_t aMaxOutputSize = sizeof(aMaxOutput);AudioConverterGetProperty(_aConverter, kAudioConverterPropertyMaximumOutputPacketSize, &aMaxOutputSize, &aMaxOutput);self.aMaxOutputFrameSize = aMaxOutput;if (aMaxOutput == 0) {[self onErrorWithCode:AWEncoderErrorCodeAudioConverterGetMaxFrameSizeFailed des:@"AAC 获取最大frame size失败"];}
}

第二步,获取audio specific config,这是一个特别的flv tag,存储了使用的aac的一些关键数据,作为解析音频帧的基础。
在rtmp中,必须将此帧在所有音频帧之前发送。

-(aw_flv_audio_tag *)createAudioSpecificConfigFlvTag{//profile,表示使用的协议uint8_t profile = kMPEG4Object_AAC_LC;//采样率uint8_t sampleRate = 4;//channel信息uint8_t chanCfg = 1;//将上面3个信息拼在一起,成为2字节uint8_t config1 = (profile << 3) | ((sampleRate & 0xe) >> 1);uint8_t config2 = ((sampleRate & 0x1) << 7) | (chanCfg << 3);//将数据转成aw_dataaw_data *config_data = NULL;data_writer.write_uint8(&config_data, config1);data_writer.write_uint8(&config_data, config2);//转成flv tagaw_flv_audio_tag *audio_specific_config_tag = aw_encoder_create_audio_specific_config_tag(config_data, &_faacConfig);free_aw_data(&config_data);//返回给调用方,准备发送return audio_specific_config_tag;
}

第三步:当从麦克风获取到音频数据时,将数据交给AAC编码器编码。

-(aw_flv_audio_tag *)encodePCMDataToFlvTag:(NSData *)pcmData{self.curFramePcmData = pcmData;//构造输出结构体,编码器需要AudioBufferList outAudioBufferList = {0};outAudioBufferList.mNumberBuffers = 1;outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)self.audioConfig.channelCount;outAudioBufferList.mBuffers[0].mDataByteSize = self.aMaxOutputFrameSize;outAudioBufferList.mBuffers[0].mData = malloc(self.aMaxOutputFrameSize);uint32_t outputDataPacketSize = 1;//执行编码,此处需要传一个回调函数aacEncodeInputDataProc,以同步的方式,在回调中填充pcm数据。OSStatus status = AudioConverterFillComplexBuffer(_aConverter, aacEncodeInputDataProc, (__bridge void * _Nullable)(self), &outputDataPacketSize, &outAudioBufferList, NULL);if (status == noErr) {//编码成功,获取数据NSData *rawAAC = [NSData dataWithBytes: outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];//时间戳(ms) = 1000 * 每秒采样数 / 采样率;self.manager.timestamp += 1024 * 1000 / self.audioConfig.sampleRate;//获取到aac数据,转成flv audio tag,发送给服务端。return aw_encoder_create_audio_tag((int8_t *)rawAAC.bytes, rawAAC.length, (uint32_t)self.manager.timestamp, &_faacConfig);}else{//编码错误[self onErrorWithCode:AWEncoderErrorCodeAudioEncoderFailed des:@"aac 编码错误"];}return NULL;
}//回调函数,系统指定格式
static OSStatus aacEncodeInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData){AWHWAACEncoder *hwAacEncoder = (__bridge AWHWAACEncoder *)inUserData;//将pcm数据交给编码器if (hwAacEncoder.curFramePcmData) {ioData->mBuffers[0].mData = (void *)hwAacEncoder.curFramePcmData.bytes;ioData->mBuffers[0].mDataByteSize = (uint32_t)hwAacEncoder.curFramePcmData.length;ioData->mNumberBuffers = 1;ioData->mBuffers[0].mNumberChannels = (uint32_t)hwAacEncoder.audioConfig.channelCount;return noErr;}return -1;
}

第四步:关闭编码器释放资源

-(void)close{AudioConverterDispose(_aConverter);_aConverter = nil;self.curFramePcmData = nil;self.aMaxOutputFrameSize = 0;
}

文章列表

  1. 1小时学会:最简单的iOS直播推流(一)项目介绍
  2. 1小时学会:最简单的iOS直播推流(二)代码架构概述
  3. 1小时学会:最简单的iOS直播推流(三)使用系统接口捕获音视频
  4. 1小时学会:最简单的iOS直播推流(四)如何使用GPUImage,如何美颜
  5. 1小时学会:最简单的iOS直播推流(五)yuv、pcm数据的介绍和获取
  6. 1小时学会:最简单的iOS直播推流(六)h264、aac、flv介绍
  7. 1小时学会:最简单的iOS直播推流(七)h264/aac 硬编码
  8. 1小时学会:最简单的iOS直播推流(八)h264/aac 软编码
  9. 1小时学会:最简单的iOS直播推流(九)flv 编码与音视频时间戳同步
  10. 1小时学会:最简单的iOS直播推流(十)librtmp使用介绍
  11. 1小时学会:最简单的iOS直播推流(十一)sps&pps和AudioSpecificConfig介绍(完结)

相关文章:

Linux日常命令记录

1、查找进程 ps -ef | grep javajps 2、杀死进程 kill -9 1827 3、进入tomcat中的日志文件夹 cd logs 4、查看日志 tail -f catalina.outtail -n 10000 catalina.out 5、查看tomcat的连接数 ss -nat|grep -i "8081"|wc -lnetstat -nat | grep -i "8081" | …

【特效】移入显示移出隐藏

移入显示移出隐藏的效果也是很常见的&#xff0c;例如&#xff1a; 如果页面有有多处地方有此效果&#xff0c;那么也可以合并到一块&#xff0c;只写一段js代码&#xff0c;只要注意控制样式和class名字和用于js获取元素的class名字分开设置就可以了。代码很简单&#xff0c;用…

web前端开发最佳实践_学习前端Web开发的最佳方法

web前端开发最佳实践为什么要进行网站开发&#xff1f; (Why web development?) Web development is a field that is not going anywhere anytime soon. The web is moving quickly, and there are regular improvements to the devices many people use daily. Web开发是一个…

使用C#的HttpWebRequest模拟登陆网站

很久没有写新的东西了&#xff0c;今天在工作中遇到的一个问题&#xff0c;感觉很有用&#xff0c;有种想记下来的冲动。 这篇文章是有关模拟登录网站方面的。 实现步骤&#xff1b; 启用一个web会话发送模拟数据请求&#xff08;POST或者GET&#xff09;获取会话的CooKie 并根…

1小时学会:最简单的iOS直播推流(番外)运行不起AWLive的demo的同学请看这里

最简单的iOS 推流代码&#xff0c;视频捕获&#xff0c;软编码(faac&#xff0c;x264)&#xff0c;硬编码&#xff08;aac&#xff0c;h264&#xff09;&#xff0c;美颜&#xff0c;flv编码&#xff0c;rtmp协议&#xff0c;陆续更新代码解析&#xff0c;你想学的知识这里都有…

学习css布局

非常经典 http://zh.learnlayout.com/ float和position:absolute都是inline-block&#xff0c;破坏性的。absolute根据父元素定位&#xff08;static父元素除外&#xff09;。div也将不再是一行的块了。 position:relative自身定位。top&#xff0c;left是根据自己原本位置&…

csv文件示例_如何在R中使用数据框和CSV文件-带有示例的详细介绍

csv文件示例Welcome! If you want to start diving into data science and statistics, then data frames, CSV files, and R will be essential tools for you. Lets see how you can use their amazing capabilities.欢迎&#xff01; 如果您想开始研究数据科学和统计学&…

1小时学会:最简单的iOS直播推流(八)h264/aac 软编码

最简单的iOS 推流代码&#xff0c;视频捕获&#xff0c;软编码(faac&#xff0c;x264)&#xff0c;硬编码&#xff08;aac&#xff0c;h264&#xff09;&#xff0c;美颜&#xff0c;flv编码&#xff0c;rtmp协议&#xff0c;陆续更新代码解析&#xff0c;你想学的知识这里都有…

003小插曲之变量和字符串

变量&#xff1a;赋值&#xff08;名字值&#xff09;&#xff1b;变量名&#xff1a;字母分大小写/数字/下划线&#xff0c;不能以数字开头&#xff1b;拼接&#xff1b;原始字符串r&#xff1b; 专业优秀的名称&#xff1a;teacher/num/name/test/temp >>> teacher小…

mysql插入大量数据

创建实验表&#xff1a; CREATE TABLE a ( id int(11) NOT NULL AUTO_INCREMENT, name char(50) NOT NULL, type char(20) NOT NULL, PRIMARY KEY (id)) ENGINEInnoDB&#xff1b; 创建存储语句&#xff1a; delimiter // create procedure insertdata() begin declare i int …

十六进制190的2进制数_十六进制数系统解释

十六进制190的2进制数Hexadecimal numbers, often shortened to “hex numbers” or “hex”, are numbers represented in base 16 as opposed to base 10 that we use for everyday arithmetic and counting.十六进制数字(通常缩写为“十六进制数字”或“十六进制”)是以16为…

初学ssm框架的信息

ssm框架&#xff0c;就是Spring ,SpringMVC ,mybstis 的简称&#xff0c;我们是从mybstis 开始学起的&#xff0c;mybatis的作用作为一个连接数据库的框架&#xff0c;可以很好配置连接好数据库&#xff0c; 有mybatis,我们对数据库增删改查的操作更为简便了。SSM框架&#xff…

转:YUV RGB 常见视频格式解析

转&#xff1a; http://www.cnblogs.com/qinjunni/archive/2012/02/23/2364446.html YUV RGB 常见视频格式解析 I420是YUV格式的一种&#xff0c;而YUV有packed format和planar format两种&#xff0c;而I420属于planar format的一种。  同时I420表示了YUV的采样比例4:2:0。4…

1小时学会:最简单的iOS直播推流(十)librtmp使用介绍

最简单的iOS 推流代码&#xff0c;视频捕获&#xff0c;软编码(faac&#xff0c;x264)&#xff0c;硬编码&#xff08;aac&#xff0c;h264&#xff09;&#xff0c;美颜&#xff0c;flv编码&#xff0c;rtmp协议&#xff0c;陆续更新代码解析&#xff0c;你想学的知识这里都有…

导入语句 python_Python导入语句说明

导入语句 pythonWhile learning programming and reading some resources you’d have come across this word ‘abstraction’ which simply means to reduce and reuse the code as much as possible.在学习编程和阅读一些资源时&#xff0c;您会遇到“抽象”一词&#xff0c…

网页性能测试---webpagetest

http://www.webpagetest.org/转载于:https://www.cnblogs.com/cai-yu-candice/p/8194866.html

1小时学会:最简单的iOS直播推流(十一)spspps和AudioSpecificConfig介绍(完结)

最简单的iOS 推流代码&#xff0c;视频捕获&#xff0c;软编码(faac&#xff0c;x264)&#xff0c;硬编码&#xff08;aac&#xff0c;h264&#xff09;&#xff0c;美颜&#xff0c;flv编码&#xff0c;rtmp协议&#xff0c;陆续更新代码解析&#xff0c;你想学的知识这里都有…

ES5 数组方法forEach

ES6已经到了非学不可的地步了&#xff0c;对于ES5都不太熟的我决定是时候学习ES5了。 1. js 数组循环遍历。 数组循环变量&#xff0c;最先想到的就是 for(var i0;i<count;i)这样的方式了。 除此之外&#xff0c;也可以使用较简便的forEach 方式 2. forEach 函数。 使用如…

pytorch深度学习_了解如何使用PyTorch进行深度学习

pytorch深度学习PyTorch is an open source machine learning library for Python that facilitates building deep learning projects. Weve published a 10-hour course that will take you from being complete beginner in PyTorch to using it to code your own GANs (gen…

LwIP Application Developers Manual12---Configuring lwIP

1.前言 2.LwIP makefiles With minimal featuresC_SOURCES \ src/api/err.c \ src/core/init.c \ src/core/mem.c \ src/core/memp.c \ src/core/netif.c \ src/core/pbuf.c \ src/core/stats.c \ src/core/udp.c \ src/core/ipv4/icmp.c \ src/core/ipv4/inet.c \ src/core/i…

仿斗鱼聊天:基于CoreText的面向对象图文排版工具AWRichText

AWRichText 基于CoreText&#xff0c;面向对象&#xff0c;极简&#xff0c;易用&#xff0c;高效&#xff0c;支持精确点击&#xff0c;UIView混排&#xff0c;GIF动图&#xff0c;并不仅仅局限于图文混排的富文本排版神器。 代码地址&#xff1a;https://github.com/hardman/…

搭建nexus后,进入首页的时候出现warning: Could not connect to Nexus.错误

nexus出现这种问题&#xff0c;一般是版本太旧&#xff0c;换一个高版本的nexus就能解决了。 转载于:https://www.cnblogs.com/tietazhan/p/5459393.html

微软hackathon_武汉Hackathon的黑客之路–开发人员如何抗击COVID-19

微软hackathonThe Chinese New Year in 2020 was one of the saddest Chinese New Years in recent memory. After the sudden outbreak of the COVID-19 virus, the city pressed pause on all celebrations.2020年的农历新年是最近记忆中最可悲的农历新年之一。 在COVID-19病…

SVN版本控制系统使用

一.版本控制系统安装&#xff1a; 软件下载地址&#xff1a;https://www.visualsvn.com/downloads/ 二.安装版本控制系统以后&#xff0c;在window下&#xff0c;设置环境变量。 三.在命令提示符控制台查看服务器版本&#xff1a;svn --version 四.创建仓库&#xff1a;F:\DevR…

iOS的KVO实现剖析

KVO原理 对于KVO的原理&#xff0c;很多人都比较清楚了。大概是这样子的&#xff1a; 假定我们自己的类是Object和它的对象 obj&#xff0c; 当obj发送addObserverForKeypath:keypath消息后&#xff0c;系统会做3件事情&#xff1a; 动态创建一个Object的子类&#xff0c;名…

你真的以为了解java.io吗 呕心沥血 绝对干货 别把我移出首页了

文章结构1 flush的使用场景2 一个java字节流&#xff0c;inputstream 和 outputstream的简单例子3 分别测试了可能抛出java.io.FileNotFoundException&#xff0c;java.io.FileNotFoundException: test (拒绝访问。)&#xff0c;java.io.FileNotFoundException: test.txt (系统…

GitHub为所有人免费提供了所有核心功能-这就是您应该关心的原因

Just a couple of days ago, GitHub wrote a blog article stating that it is now free for teams. Heres the official blog article if youre interested. 就在几天前&#xff0c;GitHub写了一篇博客文章&#xff0c;指出它现在对团队免费。 如果您有兴趣&#xff0c;这是官…

什么是ObjCTypes?

先看一下消息转发流程: 在forwardInvocation这一步&#xff0c;你必须要实现一个方法: - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); 该方法用于说明消息的返回值和参数类型。NSMethodSignature是方法签名&#x…

0基础JavaScript入门教程(一)认识代码

1. 环境&#xff1a; JavaScript简称js&#xff0c;后续我们将使用js来代替JavaScript。 认识代码前&#xff0c;需要安装js代码运行环境。 安装nodejs&#xff1a;在https://nodejs.org/zh-cn/ 下载LTS版本&#xff0c;然后安装安装visual studio code&#xff1a;https://…

junit、hamcrest、eclemma的安装与使用

1、junit的安装与使用 1.1 安装步骤 1&#xff09;从http://www.junit.org/ 下载junit相应的jar包&#xff1b; 2&#xff09; 在CLASSPATH中加入JAR包所在的路径&#xff0c;如E:\Java\jar\junit\junit-4.10.jar&#xff1b; 3&#xff09; 将junit-4.10.jar加入到项目的lib文…