-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 33.7 KB
/
content.json
1
[{"title":"基本算法练习","date":"2016-08-26T14:50:32.000Z","path":"2016/08/26/algorithm/","text":"平时在项目中基本很少用到算法,以至于平时看到一个有趣的算法题或者算法面试题都要懵逼大半天,以后会经常记录生活中遇到的算法,虽然对现有的工作帮助不大,但可以提升逻辑思维。 排序算法1234567891011121314151617181920212223242526 将一组数降序排序 列如:“13,25,9,56,5,45,89,79”1、冒泡排序(相邻对比,第一个比第二个小就交换位置,从头到尾,最后元素会最小,下一轮最后一个数退出对比,以此类推)int main (int argc, const char *argv[]) { int array[8]= {13,25,9,56,5,45,89,79}; int num = sizeof(array)/sizeof(int); for (int i = 0 ; i < num - 1; i ++ { for (int j = 0; j < num-1-i; j ++) { if (array[j] < array[j+1]) { int tem = array[j+1]; array[j] = array[j=1]; array[j] = tem; } } } for (int i = 0; i <num - 1; i ++) { printf(\"%d \",array[i]); } printf(\"\\n\");}2、 链接: Writing 第二点1$ 内容 链接: Server 第二点1$ 内容 链接: Generating 第四点1$ 内容 链接: Deployment","tags":[{"name":"算法","slug":"算法","permalink":"http://yoursite.com/tags/算法/"},{"name":"时常更新","slug":"时常更新","permalink":"http://yoursite.com/tags/时常更新/"}]},{"title":"视频硬解码H.264(VideoToolBox)","date":"2016-07-26T14:50:32.000Z","path":"2016/07/26/VideoToolbox/","text":"VideoToolbox框架具备iOS和macOS的硬件加速视频编码和解码功能。查看官方文档,VideoToolbox可以压缩视频、解压缩视频数据、转换视频数据、配置会话、多径压缩等等功能,自己对这个框架了解的程度还很浅,平时用到的也比较少,以后还得多发时间研究研究。另外iOS8开始,苹果才开放了硬解码和硬编码API,也难怪目前很多直播都只支持iOS8以上。 创建压缩视频帧的会话1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253/** 创建压缩视频帧的会话 @param width 宽度(以像素为单位)。如果视频编码器不能支持所提供的宽度和高度,则可以更改它们。 @param height 高度 @param fps 帧速率 @param bt 比特率,表示视频编码器。应该确定压缩数据的大小。 @return 会话创建是否成功,成功返回YES。 */- (BOOL)createEncodeSession:(int)width height:(int)height fps:(int)fps bite:(int)bt { OSStatus status; //帧压缩完成时调用的回调原型。 VTCompressionOutputCallback cb = encodeOutputCallback; //创建压缩视频帧的会话。 status = VTCompressionSessionCreate(kCFAllocatorDefault, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, cb, (__bridge void *)(self), &encodeSesion); if (status != noErr) { NSLog(@\"VTCompressionSessionCreate failed. ret=%d\", (int)status); return NO; } //******设置会话的属性****** //提示视频编码器,压缩是否实时执行。 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); NSLog(@\"set realtime return: %d\", (int)status); //指定编码比特流的配置文件和级别。直播一般使用baseline,可减少由于b帧带来的延时 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel); NSLog(@\"set profile return: %d\", (int)status); //设置比特率。 比特率可以高于此。默认比特率为零,表示视频编码器。应该确定压缩数据的大小。注意,比特率设置只在定时时有效,为源帧提供信息,并且一些编解码器提供不支持限制到指定的比特率。 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(bt)); //速率的限制 status += VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)@[@(bt*2/8), @1]); // Bps NSLog(@\"set bitrate return: %d\", (int)status); // 设置关键帧速率。 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(fps*2)); // 设置预期的帧速率。 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef)@(fps)); NSLog(@\"set framerate return: %d\", (int)status); // 开始编码 status = VTCompressionSessionPrepareToEncodeFrames(encodeSesion); NSLog(@\"start encode return: %d\", (int)status); return YES;} 编码回调12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576/** 编码回调 @param userData 回调参考值 @param sourceFrameRefCon 帧的引用值 @param status noErr代表压缩成功; 如果压缩不成功,则为错误代码。 @param infoFlags 编码操作的信息 @param sampleBuffer 包含压缩帧,如果压缩成功并且帧未被删除; 否则为NULL。 */void encodeOutputCallback(void *userData, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer ){ if (status != noErr) { NSLog(@\"didCompressH264 error: with status %d, infoFlags %d\", (int)status, (int)infoFlags); return; } if (!CMSampleBufferDataIsReady(sampleBuffer)) { NSLog(@\"didCompressH264 data is not ready \"); return; } EncodeH264 *h264 = (__bridge EncodeH264*)userData; // 判断当前帧是否为关键帧 bool keyframe = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync); // 获取sps & pps数据. sps pps只需获取一次,保存在h264文件开头即可 if (keyframe && !h264.isObtainspspps) { size_t spsSize, spsCount; size_t ppsSize, ppsCount; const uint8_t *spsData, *ppsData; CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer); OSStatus err0 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 0, &spsData, &spsSize, &spsCount, 0 ); OSStatus err1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 1, &ppsData, &ppsSize, &ppsCount, 0 ); if (err0==noErr && err1==noErr) { h264.isObtainspspps = YES; [h264 writeH264Data:(void *)spsData length:spsSize addStartCode:YES]; [h264 writeH264Data:(void *)ppsData length:ppsSize addStartCode:YES]; NSLog(@\"got sps/pps data. Length: sps=%zu, pps=%zu\", spsSize, ppsSize); } } size_t lengthAtOffset, totalLength; char *data; CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); OSStatus error = CMBlockBufferGetDataPointer(dataBuffer, 0, &lengthAtOffset, &totalLength, &data); if (error == noErr) { size_t offset = 0; const int lengthInfoSize = 4; // 返回的nalu数据前四个字节不是0001的startcode,而是大端模式的帧长度length // 循环获取nalu数据 while (offset < totalLength - lengthInfoSize) { uint32_t naluLength = 0; memcpy(&naluLength, data + offset, lengthInfoSize); // 获取nalu的长度, // 大端模式转化为系统端模式 naluLength = CFSwapInt32BigToHost(naluLength); NSLog(@\"got nalu data, length=%d, totalLength=%zu\", naluLength, totalLength); // 保存nalu数据到文件 [h264 writeH264Data:data+offset+lengthInfoSize length:naluLength addStartCode:YES]; // 读取下一个nalu,一次回调可能包含多个nalu offset += lengthInfoSize + naluLength; } }} 开启编码12345678910111213141516171819202122232425262728293031323334353637383940414243- (BOOL)createEncodeSession:(int)width height:(int)height fps:(int)fps bite:(int)bt { OSStatus status; //帧压缩完成时调用的回调原型。 VTCompressionOutputCallback cb = encodeOutputCallback; //创建压缩视频帧的会话。 status = VTCompressionSessionCreate(kCFAllocatorDefault, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, cb, (__bridge void *)(self), &encodeSesion); if (status != noErr) { NSLog(@\"VTCompressionSessionCreate failed. ret=%d\", (int)status); return NO; } //******设置会话的属性****** //提示视频编码器,压缩是否实时执行。 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); NSLog(@\"set realtime return: %d\", (int)status); //指定编码比特流的配置文件和级别。直播一般使用baseline,可减少由于b帧带来的延时 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel); NSLog(@\"set profile return: %d\", (int)status); //设置比特率。 比特率可以高于此。默认比特率为零,表示视频编码器。应该确定压缩数据的大小。注意,比特率设置只在定时时有效,为源帧提供信息,并且一些编解码器提供不支持限制到指定的比特率。 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(bt)); //速率的限制 status += VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)@[@(bt*2/8), @1]); // Bps NSLog(@\"set bitrate return: %d\", (int)status); // 设置关键帧速率。 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(fps*2)); // 设置预期的帧速率。 status = VTSessionSetProperty(encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef)@(fps)); NSLog(@\"set framerate return: %d\", (int)status); // 开始编码 status = VTCompressionSessionPrepareToEncodeFrames(encodeSesion); NSLog(@\"start encode return: %d\", (int)status); return YES;} 链接: demo链接","tags":[{"name":"VideoToolBox","slug":"VideoToolBox","permalink":"http://yoursite.com/tags/VideoToolBox/"}]},{"title":"音频硬编码AAC(AudioToolBox)","date":"2016-07-25T15:50:32.000Z","path":"2016/07/25/AudioToolbox/","text":"AudioToolbox库作用是录制或播放音频,转换格式,解析音频流以及配置音频会话。AudioToolbox这个库是c的接口,考官提供了音频编解码的列子:音频编解码swift Objective-C演示123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192$ 创建解码器- (void)setUpConverter:(CMSampleBufferRef)sampleBuffer{ // 获取audioformat的描述信息 CMAudioFormatDescriptionRef audioFormatDes = (CMAudioFormatDescriptionRef)CMSampleBufferGetFormatDescription(sampleBuffer); // 获取输入的asbd的信息 AudioStreamBasicDescription inAudioStreamBasicDescription = *(CMAudioFormatDescriptionGetStreamBasicDescription(audioFormatDes)); // 开始构造输出的asbd AudioStreamBasicDescription outAudioStreamBasicDescription = {0}; // 对于压缩格式必须设置为0 outAudioStreamBasicDescription.mBitsPerChannel = 0; outAudioStreamBasicDescription.mBytesPerFrame = 0; // 设定声道数为1 outAudioStreamBasicDescription.mChannelsPerFrame = 1; // 设定输出音频的格式 outAudioStreamBasicDescription.mFormatID = kAudioFormatMPEG4AAC; outAudioStreamBasicDescription.mFormatFlags = kMPEG4Object_AAC_LC; // 填充输出的音频格式 UInt32 size = sizeof(outAudioStreamBasicDescription); AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &outAudioStreamBasicDescription); // 选择aac的编码器(用来描述一个已经安装的编解码器) AudioClassDescription audioClassDes; // 初始化为0 memset(&audioClassDes, 0, sizeof(audioClassDes)); // 获取满足要求的aac编码起的总大小 UInt32 countSize = 0; AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(outAudioStreamBasicDescription.mFormatID), &outAudioStreamBasicDescription.mFormatID, &countSize); // 用来计算aac的编解码器的个数 int cout = countSize / sizeof(audioClassDes); // 创建一个包含有cout个数的编码器数组 AudioClassDescription descriptions[cout]; // 将编码起数组信息写入到descriptions中。 AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(outAudioStreamBasicDescription.mFormatID), &outAudioStreamBasicDescription.mFormatID, &countSize, descriptions); for (int i = 0; i < cout; cout++) { AudioClassDescription temp = descriptions[i]; if (temp.mManufacturer == kAppleSoftwareAudioCodecManufacturer && temp.mSubType ==outAudioStreamBasicDescription.mFormatID) { audioClassDes = temp; break; } } // 创建convertcontext用来保存converter的信息 ConverterContext *context = malloc(sizeof(ConverterContext)); self->convertContext = context; OSStatus result = AudioConverterNewSpecific(&inAudioStreamBasicDescription, &outAudioStreamBasicDescription, 1, &audioClassDes, &(context->converter)); if (result == noErr) { // 创建编解码器成功 AudioConverterRef converter = context->converter; // 设置编码起属性 UInt32 temp = kAudioConverterQuality_High; AudioConverterSetProperty(converter, kAudioConverterCodecQuality, sizeof(temp), &temp); // 设置比特率 UInt32 bitRate = 96000; result = AudioConverterSetProperty(converter, kAudioConverterEncodeBitRate, sizeof(bitRate), &bitRate); if (result != noErr) { NSLog(@\"设置比特率失败\"); } }else{ // 创建编解码器失败 free(context); context = NULL; NSLog(@\"创建编解码器失败\"); }}// AudioConverter的提供数据的回调函数OSStatus audioConverterComplexInputDataProc(AudioConverterRef inAudioConverter,UInt32 * ioNumberDataPacket,AudioBufferList *ioData,AudioStreamPacketDescription ** outDataPacketDescription,void *inUserData){ // ioData用来接受需要转换的pcm数据給converter进行编码 FillComplexInputParm *param = (FillComplexInputParm *)inUserData; if (param->sourceSize <= 0) { *ioNumberDataPacket = 0; return - 1; } ioData->mBuffers[0].mData = param->source; ioData->mBuffers[0].mDataByteSize = param->sourceSize; ioData->mBuffers[0].mNumberChannels = param->channelCount; *ioNumberDataPacket = 1; param->sourceSize = 0; return noErr;}$ 编码samplebuffer数据-(void)encodeSmapleBuffer:(CMSampleBufferRef)sampleBuffer{ if (!self->convertContext) { [self setUpConverter:sampleBuffer]; } ConverterContext *cxt = self->convertContext; if (cxt && cxt->converter) { // 从samplebuffer中提取数据 CFRetain(sampleBuffer); dispatch_async(encodeQueue, ^{ // 从samplebuffer众获取blockbuffer CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); size_t pcmLength = 0; char * pcmData = NULL; // 获取blockbuffer中的pcm数据的指针和长度 OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &pcmLength, &pcmData); if (status != noErr) { NSLog(@\"从block众获取pcm数据失败\"); CFRelease(sampleBuffer); return ; }else{ // 在堆区分配内存用来保存编码后的aac数据 char *outputBuffer = malloc(pcmLength); memset(outputBuffer, 0, pcmLength); UInt32 packetSize = 1; AudioStreamPacketDescription *outputPacketDes = (AudioStreamPacketDescription *)malloc(sizeof(AudioStreamPacketDescription) * packetSize); // 使用fillcomplexinputparm来保存pcm数据 FillComplexInputParm userParam; userParam.source = pcmData; userParam.sourceSize = (UInt32)pcmLength; userParam.channelCount = 1; userParam.packetDescription = NULL; // 在堆区创建audiobufferlist // AudioBufferList *bufferList = malloc(sizeof(AudioBufferList)); AudioBufferList outputBufferList; outputBufferList.mNumberBuffers = 1; outputBufferList.mBuffers[0].mData = outputBuffer; outputBufferList.mBuffers[0].mDataByteSize = pcmLength; outputBufferList.mBuffers[0].mNumberChannels = 1; status = AudioConverterFillComplexBuffer(convertContext->converter, audioConverterComplexInputDataProc, &userParam, &packetSize, &outputBufferList, outputPacketDes); free(outputPacketDes); outputPacketDes = NULL; if (status == noErr) { static int64_t totoalLength = 0; if (totoalLength >= 1024 * 1024 * 1) { return; } NSLog(@\"编码成功\"); // 获取原始的aac数据 NSData *rawAAC = [NSData dataWithBytes:outputBufferList.mBuffers[0].mData length:outputBufferList.mBuffers[0].mDataByteSize]; free(outputBuffer); outputBuffer = NULL; // 设置adts头 int headerLength = 0; char *packetHeader = newAdtsDataForPacketLength(rawAAC.length, 44100, 1, &headerLength); NSData *adtsHeader = [NSData dataWithBytes:packetHeader length:headerLength]; free(packetHeader); packetHeader = NULL; NSMutableData *fullData = [NSMutableData dataWithData:adtsHeader]; [fullData appendData:rawAAC]; [_handle seekToEndOfFile]; [_handle writeData:fullData]; fullData = nil; rawAAC = nil; totoalLength+=fullData.length; if (totoalLength >= 1024 * 1024 *1) { [_handle closeFile]; } } CFRelease(sampleBuffer); } }); }}// 給aac加上adts头, packetLength 为rewaac的长度,char *newAdtsDataForPacketLength(int packetLength,int sampleRate,int channelCout, int *ioHeaderLen){ // adts头的长度为固定的7个字节 int adtsLen = 7; // 在堆区分配7个字节的内存 char *packet = malloc(sizeof(char) * adtsLen); // 选择AAC LC int profile = 2; // 选择采样率对应的下标 int freqIdx = 4; // 选择声道数所对应的下标 int chanCfg = 1; // 获取adts头和raw aac的总长度 NSUInteger fullLength = adtsLen + packetLength; // 设置syncword packet[0] = 0xFF; packet[1] = 0xF9; packet[2] = (char)(((profile - 1)<<6) + (freqIdx<<2)+(chanCfg>>2)); packet[3] = (char)(((chanCfg&3)<<6)+(fullLength>>11)); packet[4] = (char)((fullLength&0x7FF)>>3); packet[5] = (char)(((fullLength&7)<<5)+0x1F); packet[6] = (char)0xFC; *ioHeaderLen =adtsLen; return packet;} 链接: demo链接","tags":[{"name":"AudioToolBox","slug":"AudioToolBox","permalink":"http://yoursite.com/tags/AudioToolBox/"}]},{"title":"iOS音视频采集笔记","date":"2016-07-22T15:30:32.000Z","path":"2016/07/22/AVCollection/","text":"最近利用空余时间研究了一下音视频采集,为方便项目的参考,特意做个笔记记录。用到AVCaptureSession来进行音视频数据采集,AVCaptureSession是用来管理视频与数据的捕获,采集到音频原始数据pcm(pcm是指未经过压缩处理的)压缩为aac格式,采集到yuv420格式的视频帧压缩成h.264格式。链接: demo链接 音视频数据采集123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990//初始化AVCaptureSession AVCaptureSession *session = [[AVCaptureSession alloc] init];//设置录像分辨率 [session canSetSessionPreset:AVCaptureSessionPreset640x480];//开始配置 [session beginConfiguration];//获取视频设备对象 AVCaptureDevice *videoDevice; NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for(AVCaptureDevice *device in devices) { if (device.position == AVCaptureDevicePositionFront) { videoDevice = device;//前置摄像头 } }//初始化视频捕获输入对象 NSError *error; AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.videoDevice error:&error]; if (error) { NSLog(@\"摄像头错误\"); return; }//输入对象添加到Session if ([session canAddInput:videoInput]) { [session addInput:videoInput]; }//输出对象 AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];//是否允许卡顿时丢帧 videoOutput.alwaysDiscardsLateVideoFrames = NO;// 设置像素格式 [videoOutput setVideoSettings:@{ (__bridge NSString *)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) }];//将输出对象添加到队列、并设置代理 dispatch_queue_t captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); [videoOutput setSampleBufferDelegate:self queue:captureQueue];//session添加输出对象 if ([session canAddOutput:self.videoOutput]) { [session addOutput:self.videoOutput]; }//创建连接 AVCaptureConnection输入对像和捕获输出对象之间建立连接。 AVCaptureConnection *connection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];//视频的方向 connection.videoOrientation = AVCaptureVideoOrientationPortrait;//设置稳定性,判断connection连接对象是否支持视频稳定 if ([connection isVideoStabilizationSupported]) {//这个稳定模式最适合连接 connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto; }//缩放裁剪系数 connection.videoScaleAndCropFactor = connection.videoMaxScaleAndCropFactor;//***音频设置 NSError *error;//获取音频设备对象 AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];//初始化捕获输入对象 AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.audioDevice error:&error]; if (error) { NSLog(@\"== 录音设备出错\"); }// 添加音频输入对象到session if ([session canAddInput:audioInput]) { [session addInput:audioInput]; }//初始化输出捕获对象 AVCaptureAudioDataOutput *audioOutput = [[AVCaptureAudioDataOutput alloc] init];// 添加音频输出对象到session if ([self.session canAddOutput:self.audioOutput]) { [self.session addOutput:self.audioOutput]; }// 创建设置音频输出代理所需要的线程队列 dispatch_queue_t audioQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); [self.audioOutput setSampleBufferDelegate:self queue:audioQueue];//提交配置 [session commitConfiguration];//调用startRunning [session startRunning] ******最后在回调中获取到pcm和yuv数据******#pragma mark - AVCaptureVideoDataAndAudioDataOutputSampleBufferDelegate- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer: (CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { if (captureOutput == audioOutput) { // 在此方法进行 AAC 软硬编码 }else { // 在此方法中进行 H.264 硬软编码 }}其中如果要切换摄像头需要先要锁定防止多处修改,最后解锁,重新提交配置 音视频采集到此结束,下一篇介绍VideoToollox和AudioToolbox对音视频进行硬编码。","tags":[{"name":"AVFoundation","slug":"AVFoundation","permalink":"http://yoursite.com/tags/AVFoundation/"}]},{"title":"音视频编解码基础知识杂记","date":"2016-07-20T15:40:32.000Z","path":"2016/07/20/AVjichu/","text":"最近直播很火,抽时间研究了一下音视频编解码和推流拉流的知识,特此做一些记录,起到巩固知识的作用,更方便以后查阅。 基础知识123456789101112131415$ 封装格式:是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起,封装格式种类很多,例如MP4,MKV,RMVB,TS,FLV,AVI等等$ 解封装: 就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据,例如,FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。$解码: 将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含AAC,MP3,AC-3等等,视频的压缩编码标准则包含H.264,MPEG2,VC-1等等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV420P,RGB等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如PCM数据。$ 流媒体协议:服务器与客户端之间通信遵循的规定。当前网络上主要的流媒体协议RTSP+RTP、RTMP、RTMFP、MMS、HTTP。其中传输层协议有UDP也有TCP,或者TCP+UDP;$视频编码:视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。视频编码是视音频技术中最重要的技术之一。视频码流的数据量占了视音频总数据量的绝大部分。高效率的视频编码在同等的码率下,可以获得更高的视频质量。$ 视频分辨率 :各种电视规格分辨率比较视 频的画面大小称为“分辨率”。数位视频以像素为度量单位,而类比视频以水平扫瞄线数量为度量单位。标清电视频号分辨率为 720/704/640x480i60(NTSC)或768/720x576i50(PAL/SECAM)。新的高清电视(HDTV)分辨率可达 1920x1080p60,即每条水平扫瞄线有1920个像素,每个画面有1080条扫瞄线,以每秒钟60张画面的速度播放。$画面更新率fps:是指视频格式每秒钟播放的静态画面数量$有损压缩和无损压缩 : 在视频压缩中有损(Lossy )和无(Lossless)的概念与静态图像中基本类似。无损压缩也即压缩前和解压缩后的数据完全一致。多数的无损压缩都采用RLE行程编码算法。有损 压缩意味着解压缩后的数据与压缩前的数据不一致。在压缩的过程中要丢失一些人眼和人耳所不敏感的图像或音频信息,而且丢失的信息不可恢复。几乎所有高压缩的算法都采用有损压缩,这样才能达到低数据率的目标。丢失的数据率与压缩比有关,压缩比越小,丢失的数据越多,解压缩后的效果一般越差。此外,某些有损压 缩算法采用多次重复压缩的方式,这样还会引起额外的数据丢失。 主流的编码标准1234567$ H.264编码标准,编码器JM速度慢,无法用于实际,实际用x264编码器。 普通用户通常两种码率控制模式:crf 和 Two pass ABR 。码率控制决定每帧分配多少比特数。决定文件大小和质量。 编译安装libx264:http://ffmpeg.org/trac/ffmpeg/wiki/CompilationGuide 1、CRF值:0~51。0无损模式,23缺省值,51最差。越小图像质量越好,18~28合理范围。 2、预设:编码速度和压缩率之间权衡,按照编码速度降序排列为: ultrafast,superfast,veryfast,faster,fast,medium,slow,slower,veryslow,placebo 缺省为medium, 音频编码 AAC MP3 WMA直播服务普遍采用了RTMP作为流媒体协议,FLV作为封装格式,H.264作为视频编码格式,AAC作为音频编码格式","tags":[{"name":"解码杂记","slug":"解码杂记","permalink":"http://yoursite.com/tags/解码杂记/"}]},{"title":"iOS利用voip push实现类似微信(QQ)电话连续响铃效果","date":"2016-06-20T09:04:10.000Z","path":"2016/06/20/voip/","text":"由于项目中添加了视频语音呼叫功能,某一天老总说要做保活,并拿出手机演示微信的音视频呼叫功能,微信在APP被杀死和黑屏的情况下都能收到呼叫并且能连续响铃和振动,说是要达到这种效果才行。当时脑子第一个想法是APNs去实现,但是APNs根本实现不了连续通知,就算连续推送也不可能只显示一个通知栏,然后又想到本地通知是可以实现连续响铃效果,但是当APP被杀死或者至于后台根本没办法启动本地通知,所以这个问题的切入点就是APP死了,怎么去激活?后面通过翻墙搜索发现VoIP可以实现激活应用,当时国内几乎搜索不到关于VoIP 推送的介绍,只好对着开发手册去慢慢摸索了。好了,直入正题了。 1、关于voip VoIP 推送基于pushKit框架。以前应用保活是通过与服务器发送周期性的心跳来维持,这样会导致设备被频繁唤醒,消耗电量,类似现在很多android系统的保活。为了代替永久连接机制,开发人员应该使用Pushkit框架。使用PushKit接收VoIP推送有很多好处: 12345只有当VoIP发生推送时,设备才会唤醒,从而节省能源。VoIP推送被认为是高优先级通知,并且毫无延迟地传送。VoIP推送可以包括比标准推送通知提供的数据更多的数据。如果收到VoIP推送时,您的应用程序未运行,则会自动重新启动。即使您的应用在后台运行,您的应用也会在运行时处理推送。 最后两点正是我们想要的。另外很多人都认为APNs可以激活应用,经过我多种测试,APNS只能在应用没被杀死的情况下(没有双击划掉)才能激活应用,比如静默推送。但是当app被杀死的情况下,APNs不管怎么更换推送payload的格式,都无法激活应用,要是能也不至于多此一举弄个VoIP推送了。还有很多人担心自己的APP使用VOIP推送会被拒,官方上解释说,必须是VoIP应用才能集成,经过实践证明其实不一定要是VoIP应用,只要你的应用中有类似视频呼叫或者语音呼叫的功能都能通过审核,后面我也会提到怎么样才能通过审核,要是没有相关功能就不用去考虑了。 2、VoIP的集成(最后附上详细代码提供下载) 在Xcode中开启VoIP推送 在Apple Developer创建VoIP证书跟APNs证书不同,VoIP证书不区分开发和生产环境,VoIP证书只有一个,生产和开发都可用同一个证书。另外有自己做过推送的应该都知道服务器一般集成的.pem格式的证书,所以还需将证书转成.pem格式,后面会介绍怎么转换.pem证书。导入frameworkObjective-C代码集成,导入头文件1#import <PushKit/PushKit.h> 设置代理123PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];pushRegistry.delegate = self;pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; 代理方法123//应用启动此代理方法会返回设备Token 、一般在此将token上传服务器- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{} 1234//当VoIP推送过来会调用此方法,一般在这里调起本地通知实现连续响铃、接收视频呼叫请求等操作- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { } 3. 建立本地测试环境集成VoIP推送看似是简单,但其中会遇到各种坑,如果每次出现问题都要服务器配合测试的话,很难找到问题的根源,而且还浪费时间,所以最好是自己搭建一个简单的测试环境先测试通,然后再与服务器对接。 1、制作.pem格式证书 123456789101、将之前生成的voip.cer SSL证书双击导入钥匙串2、打开钥匙串访问,在证书中找到对应voip.cer生成的证书,右键导出并选择.p12格式,这里我们命名为voippush.p12,这里导出需要输入密码(随意输入,别忘记了)。3、目前我们有两个文件,voip.cer SSL证书和voippush.p12私钥,新建文件夹命名为VoIP、并保存两个文件到VoIP文件夹。4、把.cer的SSL证书转换为.pem文件,打开终端命令行cd到VoIP文件夹、执行以下命令openssl x509 -in voip.cer -inform der -out VoiPCert.pem5、把.p12私钥转换成.pem文件,执行以下命令(这里需要输入之前导出设置的密码)openssl pkcs12 -nocerts -out VoIPKey.pem -in voippush.p126、再把生成的两个.pem整合到一个.pem文件中cat VoiPCert.pem VoIPKey.pem > ck.pem最终生成的ck.pem文件一般就是服务器用来推送的。 2、下载服务端推送代码并修改配置信息 链接: demo链接,里面包含.php代码 下载后一并放入VoIP文件夹中,并配置好相关信息,注意下面图片中提到的推送环境,请填写对应的环境地址,一般测试VoIP推送的稳定性最好是通过Hoc证书打包在生产环境中测试。 开发环境地址:gateway.sandbox.push.apple.com:2195 生产环境地址: gateway.push.apple.com:2195 3、推送测试 用终端命令行cd到我们的VoIP文件夹中,输入: php php文件名.php 就可以收到推送了,haha~。VoIP文件夹推送终端命令最终效果图 4、补充1、当app要上传App Store时,请在iTunes connect上传页面右下角备注中填写你用到VoIP推送的原因,附加上音视频呼叫用到VoIP推送功能的demo演示链接,演示demo必须提供呼出和呼入功能,demo我一般上传到优酷。2、经过大量测试,VoIP当应用被杀死(双击划掉)并且黑屏大部分情况都能收到推送,很小的情况会收不到推送消息,经测试可能跟手机电量消耗还有信号强弱有关。 再强调一遍,测试稳定性请在生产环境测试。3、如果不足和错误的地方,欢迎补充和改正,谢谢。 链接: 推送demo下载","tags":[{"name":"iOS","slug":"iOS","permalink":"http://yoursite.com/tags/iOS/"},{"name":"pushkit","slug":"pushkit","permalink":"http://yoursite.com/tags/pushkit/"},{"name":"voip Push","slug":"voip-Push","permalink":"http://yoursite.com/tags/voip-Push/"},{"name":"激活应用","slug":"激活应用","permalink":"http://yoursite.com/tags/激活应用/"}]},{"title":"Hello World","date":"2016-04-22T07:08:32.000Z","path":"2016/04/22/hello-world/","text":"周末利用空闲时间搭建了属于自己的个人博客,其实两年前就有这个想法了,后面因为各种原因就把这个事情给落下了,以前的事情就不提了,接下来让我把落下的慢慢的补上吧,毕竟出来混,终究是要还的… 标题第一点1$ 内容1 链接: Writing 第二点1$ 内容 链接: Server 第二点1$ 内容 链接: Generating 第四点1$ 内容 链接: Deployment","tags":[{"name":"第一篇","slug":"第一篇","permalink":"http://yoursite.com/tags/第一篇/"},{"name":"开心","slug":"开心","permalink":"http://yoursite.com/tags/开心/"}]}]