视频硬编解码初级篇

来源:互联网 发布:卖域名赚钱吗 编辑:程序博客网 时间: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;}

注意:这个版本没有进行任何优化。

原创粉丝点击