视频硬编解码初级篇
来源:互联网 发布:卖域名赚钱吗 编辑:程序博客网 时间:2024/05/16 05:30
一:硬编码
1,初始化设备
a: 创建一个会话AVCaptureSession,
b: 添加输入(AVCaptureDeviceInput )
c: 输出源(AVCaptureVideoDataOutput)
d: 添加预览(AVCaptureVideoPreviewLayer)
- (void)initVideoCapture{ _captureSession =[[AVCaptureSession alloc]init]; [_captureSession setSessionPreset:AVCaptureSessionPreset640x480]; AVCaptureDevice * captureDevice =[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; if (captureDevice == nil) { NSLog(@"captureDevice is nil !"); return;} NSError * error; // input device for session AVCaptureDeviceInput *inputDevice =[AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; if (error) { NSLog(@"AVCaptureDeviceInput is error %@",error); return;} if ([_captureSession canAddInput:inputDevice]) { [_captureSession addInput:inputDevice]; } // output device for session 细化为 视频数据输出, AVCaptureVideoDataOutput * videoDataOutput =[[AVCaptureVideoDataOutput alloc]init]; /* ONLY support pixel format : 420v, 420f, BGRA */ videoDataOutput.videoSettings =[NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]forKey:(NSString *)kCVPixelBufferPixelFormatTypeKey]; //指定接收方是否应该抛弃任何视频帧之前不是处理下一个帧捕获 [videoDataOutput setAlwaysDiscardsLateVideoFrames:YES]; // [videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_global_queue(0, 0)]; if ([_captureSession canAddOutput:videoDataOutput]) { [_captureSession addOutput:videoDataOutput]; }else{NSLog(@"not canAddOutput ! ");} //添加预览 AVCaptureVideoPreviewLayer * videoLayer =[AVCaptureVideoPreviewLayer layerWithSession:_captureSession]; videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; videoLayer.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height-100); [self.view.layer addSublayer:videoLayer];}
2,创建压缩会话
#pragma mark - 图像送入编码器前 要先设置 - VTCompressionSessionRef- (int)startEncodeSessionWith:(int)width height:(int)height frmaeRate:(int)fps bitRate:(int)bt{ OSStatus osstatus; _frameCount = 0; // compressionframe 这是执行 函数块方法 的地址 VTCompressionOutputCallback outputCallBack = compressionframe; //为压缩视频帧创建一个会话 osstatus = VTCompressionSessionCreate(kCFAllocatorDefault, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, outputCallBack, (__bridge void * _Nullable)(self), &_compressionSession); if (osstatus != noErr) { NSLog(@"VTCompressionSessionCreate failed. ret=%d", osstatus);return -1;} // 设置实时编码输出,降低编码延迟 osstatus=VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); NSLog(@"kVTCompressionPropertyKey_RealTime - %d",osstatus); // 准备编码 资源分配,然后再开始编码帧。 osstatus = VTCompressionSessionPrepareToEncodeFrames(_compressionSession); return 0;}static int i = 0;static ViewController * selfCalss = nil;// 将该方法的地址 传入 VTCompressionSessionCreate 方法中回调 // 可以在这里生产 h264流, 用于传输void compressionframe(void *useData, void *sourceFrameRefCon, OSStatus status,VTEncodeInfoFlags infoFlags,CMSampleBufferRef sampleBuffer){// NSLog(@"sampleBuffer = %@", sampleBuffer); OSStatus err; CMFormatDescriptionRef formatDes; const uint8_t * sps ; size_t spsSize; size_t spsCount;// CMVideoFormatDescriptionCreateFromH264ParameterSets formatDes = CMSampleBufferGetFormatDescription(sampleBuffer); NSLog(@"formatDes = [%@]",formatDes); err = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 0, &sps, &spsSize, &spsCount, nil); NSLog(@"sps = %s spsSize = %zu spsCount = %zu",sps, spsSize, spsCount); const uint8_t * pps ; size_t ppsSize; size_t ppsLen; err = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 1, &pps, &ppsSize, &ppsLen, nil); NSLog(@"pps = %s ppsSize = %zu ppsLen = %zu",pps, ppsSize, ppsLen); NSLog(@"code = %d",err); if (i == 0) { i = 1; [selfCalss writeH264Data:(void*)sps length:spsSize addStartCode:YES]; [selfCalss writeH264Data:(void*)pps length:ppsSize addStartCode:YES]; } size_t offset,len; char * dataPointer; CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); err = CMBlockBufferGetDataPointer(blockBuffer, 0, &offset, &len, &dataPointer); NSLog(@"code = %d",err); if (err == noErr) { size_t offset = 0; const int lengthInfoSize = 4; // 返回的nalu数据前四个字节不是0001的startcode,而是大端模式的帧长度length // 循环获取nalu数据 while (offset < len - lengthInfoSize) { uint32_t naluLength = 0; memcpy(&naluLength, dataPointer + offset, lengthInfoSize); // 获取nalu的长度, // 大端模式转化为系统端模式 naluLength = CFSwapInt32BigToHost(naluLength); NSLog(@"got nalu data, length=%d, totalLength=%zu", naluLength, len); // 保存nalu数据到文件 [selfCalss writeH264Data:dataPointer+offset+lengthInfoSize length:naluLength addStartCode:YES]; // 读取下一个nalu,一次回调可能包含多个nalu offset += lengthInfoSize + naluLength; } }// [selfCalss showH264DataWith:sampleBuffer];// [selfCalss decode];// [selfCalss decompressionSession];}
3,获取输入源
在输出源的代理方法中 可以得到原始的 CMSampleBufferRef 数据。在用它编码前,还需要创建一个压缩会话 VTCompressionSessionRef
#pragma mark - 输出视频数据deta 代理- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{ // 得到原始的 CMSampleBufferRef 数据 [self encodeFrame:sampleBuffer];}// 编码每一帧图像 注意异步,- (void)encodeFrame:(CMSampleBufferRef)sampleBuf{ // CMSampleBuffer = CMTime + FormatDesc + CMBlockBuffer // 编码顺序,同步线程 试试异步会怎么样 dispatch_sync(dispatch_get_global_queue(0, 0), ^{ CVImageBufferRef imgBuf= CMSampleBufferGetImageBuffer(sampleBuf); // pts,必须设置,否则会导致编码出来的数据非常大,原因未知 --->> 是显示的时间 CMTime pts = CMTimeMake(_frameCount, 1000); // 使用 kCMTimeInvalid 初始化一个无效的CMTime CMTime duration = kCMTimeInvalid; VTEncodeInfoFlags flags; // 送入编码器编码 OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession, imgBuf, pts, duration, NULL, NULL, &flags); if (status != noErr) { NSLog(@"H264: VTCompressionSessionEncodeFrame failed with %d", (int)status); return; } });}
4,最后保存到文件 或者 网络传输
- (void) writeH264Data:(void*)data length:(size_t)length addStartCode:(BOOL)b{ // 添加4字节的 h264 协议 start code const Byte bytes[] = "\x00\x00\x00\x01"; if (_h264File) { if(b) fwrite(bytes, 1, 4, _h264File); fwrite(data, 1, length, _h264File); } else { NSLog(@"_h264File null error, check if it open successed"); }}// 不要忘记开始- (IBAction)startCLick:(UIButton *)sender { _h264File = fopen([[NSString stringWithFormat:@"%@/Documents/vt_encode.h264", NSHomeDirectory()] UTF8String], "wb"); [self startEncodeSessionWith:480 height:640 frmaeRate:25 bitRate:640*1000]; [_captureSession startRunning];}
。
。
。
。
。
二:硬解码
// 如果编码没有问题的话。这个方法可以直接拿到 compressionframe 方法中使用,可以播放原始视频了// 这里没有使用OpenGL 渲染。而是使用 系统的 AVSampleBufferDisplayLayer API。- (void)showH264DataWith:(CMSampleBufferRef)sampleBuffer{ if (_sampleBufLayer == nil) { _sampleBufLayer =[[AVSampleBufferDisplayLayer alloc]init]; _sampleBufLayer.videoGravity = AVLayerVideoGravityResizeAspect; _sampleBufLayer.opaque = YES; _sampleBufLayer.frame = CGRectMake(100, 200, 240, 320); dispatch_async(dispatch_get_main_queue(), ^{ [self.view.layer addSublayer:_sampleBufLayer]; }); } if([_sampleBufLayer isReadyForMoreMediaData]){ dispatch_sync(dispatch_get_main_queue(),^{ [_sampleBufLayer enqueueSampleBuffer:sampleBuffer]; [_sampleBufLayer setNeedsDisplay]; [_sampleBufLayer flush]; }); //理论上要释放,但是这里线不能释放。// CFRelease(sampleBuffer); }}
解码。就是 得到数据的 sps。pps。以及 blockBuffer .并将他们组合成为解码后的CMSampleBuffer.用AVSampleBufferDisplayLayer 播放出来,当然没有任何的渲染(美白渲染)
1, 创建一个 格式的描述 CMVideoFormatDescriptionRef,由 CMVideoFormatDescriptionRef,生产 解码会话 VTDecompressionSessionRef
/// 解码 AVSampleBufferDisplayLayer * _sampleBufLayer; VTDecompressionSessionRef _decompressionSession; CMVideoFormatDescriptionRef _decodeVideoFormatDes; const uint8_t * const _sps; const uint8_t * const _pps; const size_t _spsSize, _ppsSize;// 创建一个 格式的描述 _decodeVideoFormatDes- (void)decompressionSession{ const uint8_t* const parameterSetPointers[2] = { _sps, _pps }; const size_t parameterSetSizes[2] = { _spsSize, _ppsSize }; OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, //param count parameterSetPointers, parameterSetSizes, 4, //nal start code size &_decodeVideoFormatDes); NSLog(@"_decodeVideoFormatDes = %@",_decodeVideoFormatDes); VTDecompressionOutputCallbackRecord outputCBRecord; outputCBRecord.decompressionOutputCallback = NULL; outputCBRecord.decompressionOutputRefCon = NULL; status = VTDecompressionSessionCreate(kCFAllocatorDefault, _decodeVideoFormatDes, NULL, NULL, &outputCBRecord, &_decompressionSession);}
2, 使用 VTDecompressionSessionRef 解码会话 进行 硬解码
- (CVPixelBufferRef)decode{ /*VTDecompressionSessionCreate 创建解码 session VTDecompressionSessionDecodeFrame 解码一个frame VTDecompressionSessionInvalidate 销毁解码 session*/ OSStatus status; //其中 sampleBuffer是输入的H.264视频数据,每次输入一个frame。 //先用CMBlockBufferCreateWithMemoryBlock 从H.264数据创建一个CMBlockBufferRef实例。 //然后用 CMSampleBufferCreateReady创建CMSampleBufferRef实例。 uint8_t * buffer; size_t blockLen = 0; CMBlockBufferRef blobkBuffer; // 由解码会话生产 blockBuffer 图像data status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, &buffer, blockLen, kCFAllocatorNull, NULL, 0, blockLen, 0, &blobkBuffer); if (status != noErr) { NSLog(@"CMBlockBufferCreateWithMemoryBlock error ! %d",status); return nil;} // size_t是无符号的,并且是平台无关的,表示0-MAXINT的范围 const size_t sampleSizeArray[] = {}; CMSampleBufferRef decodeSampleBuffer = NULL; // 用生产的 blockBuffer图像资源 去生产 decodeSampleBuffer // 到这里理论上可以 用 AVSampleBufferDisplayLayer 播放 decodeSampleBuffer 了 status = CMSampleBufferCreateReady(kCFAllocatorDefault, blobkBuffer, _decodeVideoFormatDes, 1, 0, NULL, 1, sampleSizeArray, &decodeSampleBuffer); if (status != noErr) { NSLog(@"CMSampleBufferCreateReady error ! %d",status); return nil;} //3, 这里是生成 CVPixelBufferRef,用于图片渲染的 VTDecodeInfoFlags outputFlag = 0; VTDecodeFrameFlags flags = 0; CVPixelBufferRef pixelBuffer; status = VTDecompressionSessionDecodeFrame(_decompressionSession, decodeSampleBuffer, flags, &pixelBuffer, &outputFlag); if (status != noErr) { NSLog(@"VTDecompressionSessionDecodeFrame error ! %d",status); return nil;} return pixelBuffer;}
注意:这个版本没有进行任何优化。
阅读全文
0 0
- 视频硬编解码初级篇
- 【视频开发】GPU编解码:GPU硬解码---DXVA
- IOS 视频硬解码
- GPU编解码 - 硬编码
- 视频解码和硬解码
- 视频解码和硬解码
- GPU编解码 - 硬解码---CUVID
- GPU编解码 - 硬解码 - CUVID
- h264视频编解码
- 视频编解码博客
- 视频编解码概述
- 视频编解码基础
- 视频编解码概述
- 视频编解码基本概念
- 视频编解码学习
- 视频编解码原理
- 理解视频编解码
- 视频硬件编解码
- 云采审批接口——撤回单据
- leetcode 566 Reshape the Matrix
- POJ1852 UVa10714 Ants【水题】
- Web前端面试题目汇总
- 用两个栈来实现一个队列
- 视频硬编解码初级篇
- 单例模式
- 5.3矩阵压缩与存储
- WPF
- 有关可变形部件模型(Deformable Part Model)的一些说明
- idea本地运行hdfs程序
- HDU4738Caocao's Bridges(无向图求桥)
- 做网站专题测试时出现背景显示不全问题
- yolov2论文翻译