当前位置:   article > 正文

音视频-视频编/解码 实战_cmsamplebuffercreate

cmsamplebuffercreate

先来简单看下 音视频的采集

一、音视频的采集

 

 

音视频采集的核心流程:

音/视频采集

  • 用到的视频输出的类是AVCaptureVideoDataOutput,音频输出的类是AVCaptureAudioDataOutput
  • 采集成功后的代理方法输出的音视频对象为CMSampleBufferRef类型的sampleBuffer。这里我们可以使用AVCaptureConnection来判断是音频还是视频。

 

  1. - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
  2. if (connection == self.audioConnection) { //音频
  3. }else if (connection == self.videoConnection) { //视频
  4. }
  5. }

采集的核心流程跟 AVFoundation 拍照/录制视频AVFoundation 人脸识别 的采集流程基本一致,大家可以了解下。

二、视频的编解码

2.1 视频的编码

1.首先需要初始化编码器,看代码:

 

  1. - (instancetype)initWithConfigure:(CQVideoCoderConfigure *)configure {
  2. self = [super init];
  3. if (self) {
  4. self.configure = configure;
  5. self.encodeQueue = dispatch_queue_create("h264 hard encode queue", DISPATCH_QUEUE_SERIAL);
  6. self.callbackQueue = dispatch_queue_create("h264 hard encode callback queue", DISPATCH_QUEUE_SERIAL);
  7. //1.创建编码session
  8. OSStatus status = VTCompressionSessionCreate(kCFAllocatorDefault, (int32_t)self.configure.width, (int32_t)self.configure.height, kCMVideoCodecType_H264, NULL, NULL, NULL, compressionOutputCallback, (__bridge void * _Nullable)(self), &_encodeSesion);
  9. if (status != noErr) {
  10. NSLog(@"VTCompressionSessionCreate error status: %d", (int)status);
  11. return self;
  12. }
  13. //2、设置编码器参数
  14. //是否实时执行
  15. status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
  16. NSLog(@"VTSessionSetProperty RealTime status: %d", (int)status);
  17. //指定编码比特流的配置文件和级别。直播一般使用baseline,可减少由b帧减少带来的延迟。
  18. status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
  19. NSLog(@"VTSessionSetProperty ProfileLevel status: %d", (int)status);
  20. //设置比特率均值(比特率可以高于此。默认比特率为零,表示视频编码器。应该确定压缩数据的大小。)
  21. //注意:比特率设置只在定时的时候有效
  22. status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFNumberRef)@(self.configure.bitrate));
  23. NSLog(@"VTSessionSetProperty AverageBitRate status: %d", (int)status);
  24. //码率限制
  25. CFArrayRef limits = (__bridge CFArrayRef)@[@(self.configure.bitrate / 4),@(self.configure.bitrate * 4)];
  26. status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_DataRateLimits,limits);
  27. NSLog(@"VTSessionSetProperty DataRateLimits status: %d", (int)status);
  28. //设置关键帧间隔
  29. status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFNumberRef)@(self.configure.fps * 2));
  30. NSLog(@"VTSessionSetProperty MaxKeyFrameInterval status: %d", (int)status);
  31. //设置预期的fps
  32. CFNumberRef expectedFrameRate = (__bridge CFNumberRef)@(self.configure.fps);
  33. status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, expectedFrameRate);
  34. NSLog(@"VTSessionSetProperty ExpectedFrameRate status: %d", (int)status);
  35. //3、准备编码
  36. status = VTCompressionSessionPrepareToEncodeFrames(self.encodeSesion);
  37. NSLog(@"VTSessionSetProperty: set PrepareToEncodeFrames return: %d", (int)status);
  38. }
  39. return self;
  40. }
  • 1、VTCompressionSessionCreate:创建压缩会话,并且添加了编码成功后的回调函数compressionOutputCallback
    参数1:会话的分配器。传递NULL使用默认分配器。
    参数2:帧的宽度,以像素为单位。如果视频编码器不支持所提供的宽度和高度,系统可能会自动修改。
    参数3:帧的高度。
    参数4:编码类型。
    参数5:编码规范。NULLvideoToolbox自己选择。
    参数6:源像素缓冲区属性,NULL不让videToolbox创建,自己创建。
    参数7: 压缩数据分配器。NULL默认的分配。
    参数8:回调函数。异步调用。
    参数9:客户端为输出回调定义的引用值。这里传的事我们自定义的编码器,也就是self
    参数10: 要创建的编码会话对象。
  • 2、VTSessionSetProperty属性配置。
  • 3、VTCompressionSessionPrepareToEncodeFrames:准备编码。

2、进行编码,看代码:

 

  1. - (void)encoderSampleBuffers:(CMSampleBufferRef)sampleBuffer {
  2. CFRetain(sampleBuffer);
  3. dispatch_async(self.encodeQueue, ^{
  4. CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);//帧数据
  5. self->frameID++;
  6. CMTime timeStamp = CMTimeMake(self->frameID, 1000);//该帧的时间戳
  7. CMTime duration = kCMTimeInvalid;//持续时间
  8. VTEncodeInfoFlags flags;
  9. OSStatus status = VTCompressionSessionEncodeFrame(self.encodeSesion, imageBuffer, timeStamp, duration, NULL, NULL, &flags);//编码
  10. if (status != noErr) {
  11. NSLog(@"VTCompressionSessionEncodeFrame error status: %d",(int)status);
  12. }
  13. CFRelease(sampleBuffer);
  14. });
  15. }
  • 1、CMSampleBufferGetImageBuffer从采集到的视频CMSampleBufferRef中获取CVImageBufferRef
  • 2、VTCompressionSessionEncodeFrame压缩编码:
    参数1:编码会话encodeSesion
    参数2:CVImageBuffer对象,包含视频帧数据
    参数3:对应该帧的时间戳,每个示例的时间戳必须大于前一个。
    参数4:该演示帧的持续时间,没有可填kCMTimeInvalid
    参数5:编码该帧的键/值对属性信息。注意,某些会话属性也可能在帧之间更改。这种变化对随后编码的帧有影响。
    参数6:将要传递给回调函数的帧的引用值。
    参数7:VTEncodeInfoFlags接收有关编码操作的信息.

3、编码成功后回调处理:

 

  1. // startCode 长度 4
  2. const Byte startCode[] = "\x00\x00\x00\x01";
  3. void compressionOutputCallback(void * CM_NULLABLE outputCallbackRefCon, void * CM_NULLABLE sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CM_NULLABLE CMSampleBufferRef sampleBuffer ) {
  4. if (status != noErr) {
  5. NSLog(@"compressionOutputCallback error status: %d", (int)status);
  6. return;
  7. }
  8. if (!CMSampleBufferDataIsReady(sampleBuffer)) {
  9. NSLog(@"CMSampleBufferDataIsReady is not ready");
  10. return;
  11. }
  12. CQVideoEncoder *encoder = (__bridge CQVideoEncoder *)outputCallbackRefCon;
  13. BOOL keyFrame = NO;
  14. CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
  15. keyFrame = !CFDictionaryContainsKey(CFArrayGetValueAtIndex(attachmentsArray, 0), kCMSampleAttachmentKey_NotSync);
  16. //是否为关键帧,并且有没有获取过sps 和 pps 数据。
  17. if (keyFrame && !encoder->hasSpsPps) {
  18. size_t spsSize, spsCount, ppsSize, ppsCount;
  19. const uint8_t *spsData, *ppsData;
  20. //获取图像原格式
  21. CMFormatDescriptionRef formatDes = CMSampleBufferGetFormatDescription(sampleBuffer);
  22. OSStatus status1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 0, &spsData, &spsSize, &spsCount, 0);
  23. OSStatus status2 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 1, &ppsData, &ppsSize, &ppsCount, 0);
  24. if (status1 == noErr & status2 == noErr) {//sps/pps获取成功
  25. NSLog(@"Get sps and pps success!!!");
  26. //sps 和 pps 数据只需保存在H264文件开头即可。
  27. encoder->hasSpsPps = true;
  28. NSMutableData *spsDataM = [NSMutableData dataWithCapacity:4 + spsSize];
  29. [spsDataM appendBytes:startCode length:4];
  30. [spsDataM appendBytes:spsData length:spsSize];
  31. NSMutableData *ppsDataM = [NSMutableData dataWithCapacity:4 + ppsSize];
  32. [ppsDataM appendBytes:startCode length:4];
  33. [ppsDataM appendBytes:ppsData length:ppsSize];
  34. dispatch_async(encoder.encodeQueue, ^{
  35. if ([encoder.delegate respondsToSelector:@selector(encodeCallbackWithSps:pps:)]) {
  36. [encoder.delegate encodeCallbackWithSps:spsDataM pps:ppsDataM];
  37. }
  38. });
  39. } else {
  40. NSLog(@"Get sps and pps failed, spsStatus:%d, ppsStatus:%d", (int)status1, (int)status2);
  41. }
  42. }
  43. //获取NAL Unit数据
  44. size_t lengthAtOffset, totalLength;
  45. char *dataPoint;
  46. //将数据复制到dataPoint
  47. CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
  48. OSStatus error = CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &totalLength, &dataPoint);
  49. if (error != kCMBlockBufferNoErr) {
  50. NSLog(@"VideoEncodeCallback: get datapoint failed, status = %d", (int)error);
  51. return;
  52. }
  53. //循环获取NAL Unit数据
  54. size_t offet = 0;
  55. //返回的NAL Unit数据前四个字节不是系统端的startCode(0001)
  56. //而是大端模式的帧长度
  57. const int lengthStartCode = 4;
  58. const int lengthBigFrame = 4;
  59. while (offet < totalLength - lengthBigFrame) {
  60. //获取NAL Unit数据长度
  61. uint32_t lengthNALU = 0;
  62. memcpy(&lengthNALU, dataPointerOut + offet, lengthBigFrame);
  63. lengthNALU = CFSwapInt32BigToHost(lengthBigFrame);//大端转系统端
  64. //获取到编码好的视频startCode + NAL Uint
  65. NSMutableData *data = [NSMutableData dataWithCapacity:lengthStartCode + lengthNALU];
  66. [data appendBytes:startCode length:lengthStartCode];
  67. [data appendBytes:dataPointerOut + offet + lengthBigFrame length:lengthNALU];
  68. dispatch_async(encoder.encodeQueue, ^{
  69. if ([encoder.delegate respondsToSelector:@selector(encodeVideoCallback:)]) {
  70. [encoder.delegate encodeVideoCallback:data];
  71. }
  72. });
  73. offet += lengthStartCode + lengthNALU;
  74. }
  75. }
  • 1、通过编码成功后的CMSampleBufferRef获取到当前帧的相关属性,判断是否是关键帧。编码前后的视频数据都是CMSampleBufferRef类型。
  • 2、是关键帧并且没有设置过sps、pps
     2.1、获取图像原格式和图像的sps、pps
     2.2、将二进制格式的sps、pps拼接到NSData中,并且开头加上startCode(00 00 00 01)NAL Unit之间使用startCode(00 00 00 01)进行分割的。
     2.3、将NSData格式的sps、pps回调出去。
  • 3、将数据复制到dataPointerOut
     3.1、CMSampleBufferGetDataBuffer:从CMSampleBufferRef中获取CMBlockBufferRef
     3.2、CMBlockBufferGetDataPointer:将数据复制到dataPointerOut,获取到数据的总长度。
  • 4、循环获取NAL Unit数据。
     4.1、memcpy:获取NAL Unit数据长度。
     4.2、获取到编码好的视频,开头加上startCode(00 00 00 01)
     4.3、将视频数据回调出去。

2.2视频的解码
解析H264格式数据:

 

  1. - (void)decodeH264Data:(NSData *)frame {
  2. dispatch_async(self.decodeQueue, ^{
  3. uint8_t *frameNALU = (uint8_t *)frame.bytes;
  4. uint32_t lengthFrame = (uint32_t)frame.length;
  5. int type = (frameNALU[4] & 0x1F);
  6. //0 01 00111 & 39
  7. //0 00 11111 31
  8. //0 00 00111 7
  9. //NSLog(@"type: %hhu, %d", frame[4], type);
  10. //将NAL Unit开始码转为4字节大端NAL Unit的长度信息。
  11. uint32_t naluSize = lengthFrame - 4;
  12. uint8_t *pNaluSize = (uint8_t *)(&naluSize);
  13. frameNALU[0] = *(pNaluSize + 3);
  14. frameNALU[1] = *(pNaluSize + 2);
  15. frameNALU[2] = *(pNaluSize + 1);
  16. frameNALU[3] = *(pNaluSize);
  17. CVPixelBufferRef pixelBuffer = NULL;
  18. switch (type) {
  19. case 0x05://I帧(关键帧)
  20. if ([self createDecoder]) {
  21. pixelBuffer = [self decodeNALUFrame:frameNALU withFrameLength:lengthFrame];
  22. }
  23. break;
  24. case 0x06://增强信息
  25. break;
  26. case 0x07://sps
  27. self->_spsSize = naluSize;
  28. self->_sps = malloc(self->_spsSize);
  29. memcpy(self->_sps, &frameNALU[4], self->_spsSize);
  30. break;
  31. case 0x08://pps
  32. self->_ppsSize = naluSize;
  33. self->_pps = malloc(self->_ppsSize);
  34. memcpy(self->_pps, &frameNALU[4], self->_ppsSize);
  35. break;
  36. default://其他帧(0x010x05
  37. if ([self createDecoder]) {
  38. pixelBuffer = [self decodeNALUFrame:frameNALU withFrameLength:lengthFrame];
  39. }
  40. break;
  41. }
  42. });
  43. }
  • 1、获取帧的二进制数据,这里的NSData数据就是我门上面编码回调过来的编码后的视频数据。
  • 2、int type = (frameNALU[4] & 0x1F);:获取该数据类型。type为7是sps, 8是pps
    注意:因为前4个字节是NAL Unit数据的分割码也就是前面说的startCode(\x00\x00\x00\x01),所以这里取第5个字节的数据,frameNALU[4]也就是NAL Unitheader
     例如:
    0 01 00111(frameNALU[4]) & 0 00 11111(0x1F) = 0 00 00111 转为十进制就是7代表sps
     我们将header分为三部分0-01-00111
     第一部分占一位0代表 禁止位,用以检查传输过程中是否发生错误,0表示正常,1表示违反语法。
     第二部分占两位01 用来表示当前NAL单元的优先级。非0值表示参考字段/帧/图片数据,其他不那么重要的数据则为0。对于非0值,值越大表示NAL Unit重要性越高。
     第三部分占五位00111 指定NAL Unit类型,这就是为什么按位与运算用0 00 11111(0x1F)
  • 3、当type0x05时代表时I帧(关键帧)这时我们需要先创建解码器。0x07时创建sps数据。0x08时创建pps数据。

创建解码器:

 

  1. - (BOOL)createDecoder {
  2. if (self.decodeSesion) return YES;
  3. const uint8_t * const parameterSetPointers[2] = {_sps, _pps};
  4. const size_t parameterSetSize[2] = {_spsSize, _ppsSize};
  5. int lengthStartCode = 4;
  6. OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSize, lengthStartCode, &_decoderDesc);
  7. if (status != noErr) {
  8. NSLog(@"CMVideoFormatDescriptionCreateFromH264ParameterSets error status: %d", (int)status);
  9. return NO;
  10. }
  11. NSDictionary *decoderAttachments =
  12. @{
  13. (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //摄像头的输出数据格式
  14. (id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:self.configure.width],
  15. (id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:self.configure.height],
  16. (id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:YES]
  17. };
  18. //解码回调设置
  19. VTDecompressionOutputCallbackRecord decompressionCallback;
  20. decompressionCallback.decompressionOutputCallback = decoderVideoOutputCallback;
  21. decompressionCallback.decompressionOutputRefCon = (__bridge void * _Nullable)self;
  22. VTDecompressionSessionCreate(kCFAllocatorDefault, _decoderDesc, NULL, (__bridge CFDictionaryRef _Nullable)decoderAttachments, &decompressionCallback, &_decodeSesion);
  23. if (status != noErr) {
  24. NSLog(@"VTDecompressionSessionCreate error status: %d", (int)status);
  25. return NO;
  26. }
  27. //实时编码
  28. status = VTSessionSetProperty(_decodeSesion, kVTDecompressionPropertyKey_RealTime,kCFBooleanTrue);
  29. if (status != noErr) {
  30. NSLog(@"VTSessionSetProperty RealTime error status:%d", (int)status);
  31. }
  32. return YES;
  33. }
  • 1、CMVideoFormatDescriptionCreateFromH264ParameterSets设置解码参数:
    参数1: kCFAllocatorDefault 使用默认的内存分配
    参数2: 参数个数
    参数3: 参数集指针
    参数4: 参数集大小
    参数5: startCode的长度 4
    参数6: 解码格式器描述对象
  • 2、VTDecompressionOutputCallbackRecord解码回调设置。
  • 3、VTDecompressionSessionCreate创建解码器。
    参数1: kCFAllocatorDefault 使用默认的内存分配。
    参数2: 解码格式器描述对象。
    参数3: 指定必须使用的特定视频解码器。NULLvideo toolbox选择解码器。
    参数4: 描述源像素缓冲区的要求, NULL无要求。
    参数5: 已解压缩的帧调用的回调函数。
    参数6: 指向一个变量以接收新的解压会话。
  • 4、VTSessionSetProperty设置解码会话属性。

解码:

 

  1. - (CVPixelBufferRef)decodeNALUFrame:(uint8_t *)frameNALU withFrameLength:(uint32_t)lengthFrame {
  2. CVPixelBufferRef outputPixelBuffer = NULL;
  3. CMBlockBufferRef blockBufferOut = NULL;
  4. CMBlockBufferFlags flag0 = 0;
  5. //1.
  6. OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frameNALU, lengthFrame, kCFAllocatorNull, NULL, 0, lengthFrame, flag0, &blockBufferOut);
  7. if (status != kCMBlockBufferNoErr) {
  8. NSLog(@"CMBlockBufferCreateWithMemoryBlock error status:%d", (int)status);
  9. return outputPixelBuffer;
  10. }
  11. CMSampleBufferRef sampleBuffer = NULL;
  12. const size_t sampleSizeArray[] = {lengthFrame};
  13. //2.创建sampleBuffer
  14. status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBufferOut, _decoderDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
  15. if (status != noErr || !sampleBuffer) {
  16. NSLog(@"CMSampleBufferCreateReady error status:%d", (int)status);
  17. CFRelease(blockBufferOut);
  18. return outputPixelBuffer;
  19. }
  20. //解码
  21. VTDecodeFrameFlags decodeFrameFlags = kVTDecodeFrame_1xRealTimePlayback;
  22. VTDecodeInfoFlags decodeInfoFlags = kVTDecodeInfo_Asynchronous; //异步解码
  23. status = VTDecompressionSessionDecodeFrame(_decodeSesion, sampleBuffer, decodeFrameFlags, &outputPixelBuffer, &decodeInfoFlags);
  24. if (status == kVTInvalidSessionErr) {
  25. NSLog(@"VTDecompressionSessionDecodeFrame InvalidSessionErr status:%d", (int)status);
  26. } else if (status == kVTVideoDecoderBadDataErr) {
  27. NSLog(@"VTDecompressionSessionDecodeFrame BadData status:%d", (int)status);
  28. } else if (status != noErr) {
  29. NSLog(@"VTDecompressionSessionDecodeFrame status:%d", (int)status);
  30. }
  31. CFRelease(sampleBuffer);
  32. CFRelease(blockBuffer);
  33. return outputPixelBuffer;
  34. }
  • 1、CMBlockBufferCreateWithMemoryBlock创建CMBlockBufferRef:
    参数1: kCFAllocatorDefault使用默认内存分配
    参数2: 帧的内存块,这里就用frameNALU
    参数3: 帧大小
    参数4: 管理 内存块 的分配器,参数2为NULL时用于分配内存块,参数2不为NULL时用于释放内存块,kCFAllocatorNull不需要释放内存块。
    参数5: 如果非空,则用于内存块的分配和释放(参数4blockAllocator会被忽略)。如果 参数2内存块 为NULL,则其Allocate()必须为非NULL。如果分配成功,将在分配内存块时调用一次Allocate。释放CMBlockBuffer时将调用Free()
    参数6: 数据偏移量
    参数7: 数据长度
    参数8: 功能和控制标志
    参数9: 接收新创建的CMBlockBuffer地址,不能为空。
  • 2、CMSampleBufferCreateReady创建CMSampleBufferRef
    参数1: kCFAllocatorDefault使用默认内存分配
    参数2:需要编码的数据blockBufferOut.不能为NULL
    参数3:视频输出格式
    参数4: CMSampleBuffer 个数.
    参数5: 必须为0、1 或numSamples
    参数6: 数组.为空
    参数7: 必须为0、1 或numSamples, 默认为1
    参数8: 帧大小的数组。
    参数9: 新的CMSampleBufferRef对象
  • 3、VTDecompressionSessionDecodeFrame解码:
    参数1: 解码会话对象。
    参数2: CMSampleBufferRef对象,包含一个或多个视频帧。
    参数3: 解码标志
    参数4: 解码后数据CVPixelBufferRef
    参数5: 同步还是异步解码。

解码成功后回调函数:

 

  1. void decoderVideoOutputCallback(void * CM_NULLABLE decompressionOutputRefCon,
  2. void * CM_NULLABLE sourceFrameRefCon,
  3. OSStatus status,
  4. VTDecodeInfoFlags infoFlags,
  5. CM_NULLABLE CVImageBufferRef imageBuffer,
  6. CMTime presentationTimeStamp,
  7. CMTime presentationDuration ) {
  8. if (status != noErr) {
  9. NSLog(@"decoderVideoOutputCallback error status:%d", (int)status);
  10. return;
  11. }
  12. CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
  13. *outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
  14. CQVideoDecoder *decoder = (__bridge CQVideoDecoder *)decompressionOutputRefCon;
  15. dispatch_async(decoder.callbackQueue, ^{
  16. if ([decoder.delegate respondsToSelector:@selector(videoDecodeCallback:)]) {
  17. [decoder.delegate videoDecodeCallback:imageBuffer];
  18. }
  19. //释放数据
  20. CVPixelBufferRelease(imageBuffer);
  21. });
  22. }
  • 1、回调函数的参数:
    参数1:回调的引用值。
    参数2:帧的引用值。
    参数3:压缩失败/成功的状态码。
    参数4:如果设置了kVTDecodeInfo_Asynchronous表示异步解码,
    如果设置了kVTDecodeInfo_FrameDropped可以丢帧,
    如果设置了kVTDecodeInfo_ImageBufferModifiable可以安全地修改imageBuffer(实际图像的缓冲).
    参数5:实际图像的缓冲。如果未设置kVTDecodeInfo_ImageBufferModifiable标志,则视频解压缩器可能仍在引用此回调中返回的imageBuffer,此时修改返回的imageBuffer是不安全的。
    参数6:帧的时间戳。
    参数7:帧的持续时间。

  • 2、将指针*outputPixelBuffer指向实际图像缓冲区imageBuffer

  • 3、将图像缓冲区imageBuffer回调出去用来展示。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/229427?site
推荐阅读
相关标签
  

闽ICP备14008679号