VideoToolbox를 사용하여 H.264 비디오 스트림의 압축을 푸는 방법
H.264 비디오 스트림의 압축을 풀기 위해 Apple의 하드웨어 가속 비디오 프레임 워크를 사용하는 방법을 알아내는 데 많은 어려움이있었습니다. 몇 주 후에 나는 그것을 알아 냈고 하나를 찾을 수 없었기 때문에 광범위한 예를 공유하고 싶었습니다.
제 목표는 WWDC '14 세션 513 에서 소개 된 Video Toolbox의 철저하고 유익한 예제를 제공하는 것 입니다. 내 코드는 기본 H.264 스트림 (파일에서 읽은 비디오 또는 온라인에서 스트리밍되는 비디오 등)과 통합되어야하기 때문에 컴파일 또는 실행되지 않으며 특정 경우에 따라 조정해야합니다.
주제를 검색하면서 배운 것을 제외하고는 비디오 인코딩 / 디코딩에 대한 경험이 거의 없다는 것을 언급해야합니다. 비디오 형식, 매개 변수 구조 등에 대한 모든 세부 사항을 알지 못하기 때문에 알아야 할 사항 만 포함했습니다.
XCode 6.2를 사용하고 있으며 iOS 8.1 및 8.2를 실행하는 iOS 장치에 배포했습니다.
개념 :
NALU : NALU는 NALU 시작 코드 헤더가있는 다양한 길이의 데이터 청크입니다. 0x00 00 00 01 YY
여기에서 처음 5 비트 YY
는 NALU의 유형과 헤더 뒤에 오는 데이터 유형을 알려줍니다. (처음 5 비트 만 필요하므로 YY & 0x1F
관련 비트를 가져 오는 데 사용 합니다.)이 모든 유형이 메서드 NSString * const naluTypesStrings[]
에 포함되어 있지만 모두가 무엇인지 알 필요는 없습니다.
매개 변수 : 디코더는 H.264 비디오 데이터가 저장되는 방법을 알 수 있도록 매개 변수가 필요합니다. 설정해야하는 2 개는 SPS (Sequence Parameter Set) 및 PPS (Picture Parameter Set) 이며 각각 고유 한 NALU 유형 번호가 있습니다. 매개 변수가 의미하는 바를 알 필요가 없습니다. 디코더는 매개 변수로 무엇을해야하는지 알고 있습니다.
H.264 스트림 형식 : 대부분의 H.264 스트림에서 초기 PPS 및 SPS 매개 변수 세트와 i 프레임 (일명 IDR 프레임 또는 플러시 프레임) NALU를 받게됩니다. 그런 다음 여러 개의 P 프레임 NALU (아마도 수십 개 정도)를 수신 한 다음 다른 매개 변수 세트 (초기 매개 변수와 동일 할 수 있음) 및 i 프레임, 더 많은 P 프레임 등을 받게됩니다. i 프레임은 다음보다 훨씬 큽니다. P 프레임. 개념적으로 i 프레임은 비디오의 전체 이미지로 생각할 수 있으며 P 프레임은 다음 i 프레임을받을 때까지 해당 i 프레임에 적용된 변경 사항입니다.
순서:
H.264 스트림에서 개별 NALU를 생성합니다. 사용중인 비디오 소스에 따라 다르기 때문에이 단계에 대한 코드를 표시 할 수 없습니다. 이 그래픽은 내가 작업중인 것을 보여주기 위해 만들었지 만 (그래픽의 "데이터"는 다음 코드에서 "프레임"임) 귀하의 경우는 다를 수 있습니다. 두 가지 유형 중 하나 인 프레임 ( )을 받을 때마다 내 메서드
receivedRawVideoFrame:
가 호출됩니다uint8_t *frame
. 다이어그램에서 두 프레임 유형은 두 개의 큰 보라색 상자입니다.CMVideoFormatDescriptionCreateFromH264ParameterSets ()를 사용하여 SPS 및 PPS NALU에서 CMVideoFormatDescriptionRef를 만듭니다 . 이 작업을 먼저 수행하지 않으면 프레임을 표시 할 수 없습니다. SPS와 PPS는 숫자가 뒤죽박죽처럼 보일 수 있지만 VTD는 이들로 무엇을해야하는지 알고 있습니다. 당신이 알아야 할 모든 즉
CMVideoFormatDescriptionRef
비디오 데이터의 설명이다., 너비 / 높이, 형식 유형 (같은kCMPixelFormat_32BGRA
,kCMVideoCodecType_H264
등), 화면 비율, 색 공간 등 새로운 세트가 도착할 때까지 디코더가 매개 변수에 개최한다 (때로는 매개 변수 변경되지 않은 경우에도 정기적으로 분개합니다)."AVCC"형식에 따라 IDR 및 비 IDR 프레임 NALU를 다시 패키징하십시오. 이것은 NALU 시작 코드를 제거하고 NALU의 길이를 나타내는 4 바이트 헤더로 대체하는 것을 의미합니다. SPS 및 PPS NALU에 대해서는이 작업을 수행 할 필요가 없습니다. (4 바이트 NALU 길이 헤더는 big-endian이므로
UInt32
값이 있으면CMBlockBuffer
using에 복사하기 전에 바이트 스왑해야합니다CFSwapInt32
.htonl
함수 호출을 사용하여 코드에서이 작업을 수행합니다 .)IDR 및 비 IDR NALU 프레임을 CMBlockBuffer로 패키징합니다. SPS PPS 매개 변수 NALU와 함께이 작업을 수행하지 마십시오. 당신이 알아야
CMBlockBuffers
할 것은 이것이 핵심 미디어에서 임의의 데이터 블록을 래핑하는 방법이라는 것입니다. (비디오 파이프 라인의 모든 압축 된 비디오 데이터가 여기에 포함됩니다.)CMBlockBuffer를 CMSampleBuffer로 패키징합니다. 당신이 알아야 할 모든
CMSampleBuffers
것은 그들이 우리CMBlockBuffers
를 다른 정보로 마무리 한다는 것입니다 (여기서 사용 된다면 그것은CMVideoFormatDescription
and 입니다).CMTime
CMTime
VTDecompressionSessionRef를 생성하고 샘플 버퍼를 VTDecompressionSessionDecodeFrame ()에 공급합니다. 또는
AVSampleBufferDisplayLayer
및 해당enqueueSampleBuffer:
방법을 사용할 수 있으며 VTDecompSession을 사용할 필요가 없습니다. 설정하는 것이 더 간단하지만 VTD처럼 문제가 발생하더라도 오류가 발생하지 않습니다.VTDecompSession 콜백에서 결과 CVImageBufferRef를 사용하여 비디오 프레임을 표시합니다. 당신이 당신을 변환해야하는 경우
CVImageBuffer
A를UIImage
, 내 StackOverflow의 답변을 참조 여기에 .
기타 참고 사항 :
H.264 스트림은 많이 다를 수 있습니다. 내가 배운 것에서 NALU 시작 코드 헤더는 때때로 3 바이트 (
0x00 00 01
) 이고 때로는 4 (0x00 00 00 01
)입니다. 내 코드는 4 바이트로 작동합니다. 3으로 작업하는 경우 몇 가지 사항을 변경해야합니다.NALU 에 대해 더 알고 싶다면 이 답변 이 매우 유용 하다는 것을 알았습니다 . 필자의 경우 설명한대로 "에뮬레이션 방지"바이트를 무시할 필요가 없다는 것을 알았으므로 개인적으로이 단계를 건너 뛰었지만 이에 대해 알아야 할 수도 있습니다.
귀하의 경우 VTDecompressionSession이 (-12909 같은) 오류 번호를 출력 Xcode 프로젝트에서 오류 코드를 찾아보십시오. 프로젝트 네비게이터에서 VideoToolbox 프레임 워크를 찾아서 열고 VTErrors.h 헤더를 찾으십시오. 찾을 수 없다면 다른 답변에 아래의 모든 오류 코드도 포함했습니다.
코드 예 :
따라서 일부 전역 변수를 선언하고 VT 프레임 워크 (VT = Video Toolbox)를 포함하여 시작하겠습니다.
#import <VideoToolbox/VideoToolbox.h>
@property (nonatomic, assign) CMVideoFormatDescriptionRef formatDesc;
@property (nonatomic, assign) VTDecompressionSessionRef decompressionSession;
@property (nonatomic, retain) AVSampleBufferDisplayLayer *videoLayer;
@property (nonatomic, assign) int spsSize;
@property (nonatomic, assign) int ppsSize;
다음 배열은 수신중인 NALU 프레임 유형을 인쇄 할 수 있도록 만 사용됩니다. 이 모든 유형이 의미하는 바를 알고 있다면 나보다 H.264에 대해 더 많이 알고 있습니다. :) 내 코드는 유형 1, 5, 7 및 8 만 처리합니다.
NSString * const naluTypesStrings[] =
{
@"0: Unspecified (non-VCL)",
@"1: Coded slice of a non-IDR picture (VCL)", // P frame
@"2: Coded slice data partition A (VCL)",
@"3: Coded slice data partition B (VCL)",
@"4: Coded slice data partition C (VCL)",
@"5: Coded slice of an IDR picture (VCL)", // I frame
@"6: Supplemental enhancement information (SEI) (non-VCL)",
@"7: Sequence parameter set (non-VCL)", // SPS parameter
@"8: Picture parameter set (non-VCL)", // PPS parameter
@"9: Access unit delimiter (non-VCL)",
@"10: End of sequence (non-VCL)",
@"11: End of stream (non-VCL)",
@"12: Filler data (non-VCL)",
@"13: Sequence parameter set extension (non-VCL)",
@"14: Prefix NAL unit (non-VCL)",
@"15: Subset sequence parameter set (non-VCL)",
@"16: Reserved (non-VCL)",
@"17: Reserved (non-VCL)",
@"18: Reserved (non-VCL)",
@"19: Coded slice of an auxiliary coded picture without partitioning (non-VCL)",
@"20: Coded slice extension (non-VCL)",
@"21: Coded slice extension for depth view components (non-VCL)",
@"22: Reserved (non-VCL)",
@"23: Reserved (non-VCL)",
@"24: STAP-A Single-time aggregation packet (non-VCL)",
@"25: STAP-B Single-time aggregation packet (non-VCL)",
@"26: MTAP16 Multi-time aggregation packet (non-VCL)",
@"27: MTAP24 Multi-time aggregation packet (non-VCL)",
@"28: FU-A Fragmentation unit (non-VCL)",
@"29: FU-B Fragmentation unit (non-VCL)",
@"30: Unspecified (non-VCL)",
@"31: Unspecified (non-VCL)",
};
이제 모든 마법이 일어나는 곳입니다.
-(void) receivedRawVideoFrame:(uint8_t *)frame withSize:(uint32_t)frameSize isIFrame:(int)isIFrame
{
OSStatus status;
uint8_t *data = NULL;
uint8_t *pps = NULL;
uint8_t *sps = NULL;
// I know what my H.264 data source's NALUs look like so I know start code index is always 0.
// if you don't know where it starts, you can use a for loop similar to how i find the 2nd and 3rd start codes
int startCodeIndex = 0;
int secondStartCodeIndex = 0;
int thirdStartCodeIndex = 0;
long blockLength = 0;
CMSampleBufferRef sampleBuffer = NULL;
CMBlockBufferRef blockBuffer = NULL;
int nalu_type = (frame[startCodeIndex + 4] & 0x1F);
NSLog(@"~~~~~~~ Received NALU Type \"%@\" ~~~~~~~~", naluTypesStrings[nalu_type]);
// if we havent already set up our format description with our SPS PPS parameters, we
// can't process any frames except type 7 that has our parameters
if (nalu_type != 7 && _formatDesc == NULL)
{
NSLog(@"Video error: Frame is not an I Frame and format description is null");
return;
}
// NALU type 7 is the SPS parameter NALU
if (nalu_type == 7)
{
// find where the second PPS start code begins, (the 0x00 00 00 01 code)
// from which we also get the length of the first SPS code
for (int i = startCodeIndex + 4; i < startCodeIndex + 40; i++)
{
if (frame[i] == 0x00 && frame[i+1] == 0x00 && frame[i+2] == 0x00 && frame[i+3] == 0x01)
{
secondStartCodeIndex = i;
_spsSize = secondStartCodeIndex; // includes the header in the size
break;
}
}
// find what the second NALU type is
nalu_type = (frame[secondStartCodeIndex + 4] & 0x1F);
NSLog(@"~~~~~~~ Received NALU Type \"%@\" ~~~~~~~~", naluTypesStrings[nalu_type]);
}
// type 8 is the PPS parameter NALU
if(nalu_type == 8)
{
// find where the NALU after this one starts so we know how long the PPS parameter is
for (int i = _spsSize + 4; i < _spsSize + 30; i++)
{
if (frame[i] == 0x00 && frame[i+1] == 0x00 && frame[i+2] == 0x00 && frame[i+3] == 0x01)
{
thirdStartCodeIndex = i;
_ppsSize = thirdStartCodeIndex - _spsSize;
break;
}
}
// allocate enough data to fit the SPS and PPS parameters into our data objects.
// VTD doesn't want you to include the start code header (4 bytes long) so we add the - 4 here
sps = malloc(_spsSize - 4);
pps = malloc(_ppsSize - 4);
// copy in the actual sps and pps values, again ignoring the 4 byte header
memcpy (sps, &frame[4], _spsSize-4);
memcpy (pps, &frame[_spsSize+4], _ppsSize-4);
// now we set our H264 parameters
uint8_t* parameterSetPointers[2] = {sps, pps};
size_t parameterSetSizes[2] = {_spsSize-4, _ppsSize-4};
// suggestion from @Kris Dude's answer below
if (_formatDesc)
{
CFRelease(_formatDesc);
_formatDesc = NULL;
}
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2,
(const uint8_t *const*)parameterSetPointers,
parameterSetSizes, 4,
&_formatDesc);
NSLog(@"\t\t Creation of CMVideoFormatDescription: %@", (status == noErr) ? @"successful!" : @"failed...");
if(status != noErr) NSLog(@"\t\t Format Description ERROR type: %d", (int)status);
// See if decomp session can convert from previous format description
// to the new one, if not we need to remake the decomp session.
// This snippet was not necessary for my applications but it could be for yours
/*BOOL needNewDecompSession = (VTDecompressionSessionCanAcceptFormatDescription(_decompressionSession, _formatDesc) == NO);
if(needNewDecompSession)
{
[self createDecompSession];
}*/
// now lets handle the IDR frame that (should) come after the parameter sets
// I say "should" because that's how I expect my H264 stream to work, YMMV
nalu_type = (frame[thirdStartCodeIndex + 4] & 0x1F);
NSLog(@"~~~~~~~ Received NALU Type \"%@\" ~~~~~~~~", naluTypesStrings[nalu_type]);
}
// create our VTDecompressionSession. This isnt neccessary if you choose to use AVSampleBufferDisplayLayer
if((status == noErr) && (_decompressionSession == NULL))
{
[self createDecompSession];
}
// type 5 is an IDR frame NALU. The SPS and PPS NALUs should always be followed by an IDR (or IFrame) NALU, as far as I know
if(nalu_type == 5)
{
// find the offset, or where the SPS and PPS NALUs end and the IDR frame NALU begins
int offset = _spsSize + _ppsSize;
blockLength = frameSize - offset;
data = malloc(blockLength);
data = memcpy(data, &frame[offset], blockLength);
// replace the start code header on this NALU with its size.
// AVCC format requires that you do this.
// htonl converts the unsigned int from host to network byte order
uint32_t dataLength32 = htonl (blockLength - 4);
memcpy (data, &dataLength32, sizeof (uint32_t));
// create a block buffer from the IDR NALU
status = CMBlockBufferCreateWithMemoryBlock(NULL, data, // memoryBlock to hold buffered data
blockLength, // block length of the mem block in bytes.
kCFAllocatorNull, NULL,
0, // offsetToData
blockLength, // dataLength of relevant bytes, starting at offsetToData
0, &blockBuffer);
NSLog(@"\t\t BlockBufferCreation: \t %@", (status == kCMBlockBufferNoErr) ? @"successful!" : @"failed...");
}
// NALU type 1 is non-IDR (or PFrame) picture
if (nalu_type == 1)
{
// non-IDR frames do not have an offset due to SPS and PSS, so the approach
// is similar to the IDR frames just without the offset
blockLength = frameSize;
data = malloc(blockLength);
data = memcpy(data, &frame[0], blockLength);
// again, replace the start header with the size of the NALU
uint32_t dataLength32 = htonl (blockLength - 4);
memcpy (data, &dataLength32, sizeof (uint32_t));
status = CMBlockBufferCreateWithMemoryBlock(NULL, data, // memoryBlock to hold data. If NULL, block will be alloc when needed
blockLength, // overall length of the mem block in bytes
kCFAllocatorNull, NULL,
0, // offsetToData
blockLength, // dataLength of relevant data bytes, starting at offsetToData
0, &blockBuffer);
NSLog(@"\t\t BlockBufferCreation: \t %@", (status == kCMBlockBufferNoErr) ? @"successful!" : @"failed...");
}
// now create our sample buffer from the block buffer,
if(status == noErr)
{
// here I'm not bothering with any timing specifics since in my case we displayed all frames immediately
const size_t sampleSize = blockLength;
status = CMSampleBufferCreate(kCFAllocatorDefault,
blockBuffer, true, NULL, NULL,
_formatDesc, 1, 0, NULL, 1,
&sampleSize, &sampleBuffer);
NSLog(@"\t\t SampleBufferCreate: \t %@", (status == noErr) ? @"successful!" : @"failed...");
}
if(status == noErr)
{
// set some values of the sample buffer's attachments
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
// either send the samplebuffer to a VTDecompressionSession or to an AVSampleBufferDisplayLayer
[self render:sampleBuffer];
}
// free memory to avoid a memory leak, do the same for sps, pps and blockbuffer
if (NULL != data)
{
free (data);
data = NULL;
}
}
다음 방법은 VTD 세션을 생성합니다. 새 매개 변수 를받을 때마다 다시 작성하십시오 . ( 매개 변수를받을 때마다 다시 만들 필요는 없습니다 .)
당신은 대상에 대한 일련의 속성에 원하는 경우 CVPixelBuffer
에 읽어 CoreVideo PixelBufferAttributes 값 과에 넣어 NSDictionary *destinationImageBufferAttributes
.
-(void) createDecompSession
{
// make sure to destroy the old VTD session
_decompressionSession = NULL;
VTDecompressionOutputCallbackRecord callBackRecord;
callBackRecord.decompressionOutputCallback = decompressionSessionDecodeFrameCallback;
// this is necessary if you need to make calls to Objective C "self" from within in the callback method.
callBackRecord.decompressionOutputRefCon = (__bridge void *)self;
// you can set some desired attributes for the destination pixel buffer. I didn't use this but you may
// if you need to set some attributes, be sure to uncomment the dictionary in VTDecompressionSessionCreate
NSDictionary *destinationImageBufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES],
(id)kCVPixelBufferOpenGLESCompatibilityKey,
nil];
OSStatus status = VTDecompressionSessionCreate(NULL, _formatDesc, NULL,
NULL, // (__bridge CFDictionaryRef)(destinationImageBufferAttributes)
&callBackRecord, &_decompressionSession);
NSLog(@"Video Decompression Session Create: \t %@", (status == noErr) ? @"successful!" : @"failed...");
if(status != noErr) NSLog(@"\t\t VTD ERROR type: %d", (int)status);
}
이제이 메서드는 VTD가 보낸 프레임의 압축을 풀 때마다 호출됩니다. 이 메서드는 오류가 있거나 프레임이 삭제 된 경우에도 호출됩니다.
void decompressionSessionDecodeFrameCallback(void *decompressionOutputRefCon,
void *sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration)
{
THISCLASSNAME *streamManager = (__bridge THISCLASSNAME *)decompressionOutputRefCon;
if (status != noErr)
{
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
NSLog(@"Decompressed error: %@", error);
}
else
{
NSLog(@"Decompressed sucessfully");
// do something with your resulting CVImageBufferRef that is your decompressed frame
[streamManager displayDecodedFrame:imageBuffer];
}
}
이것은 우리가 실제로 sampleBuffer를 VTD로 보내서 디코딩하는 곳입니다.
- (void) render:(CMSampleBufferRef)sampleBuffer
{
VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression;
VTDecodeInfoFlags flagOut;
NSDate* currentTime = [NSDate date];
VTDecompressionSessionDecodeFrame(_decompressionSession, sampleBuffer, flags,
(void*)CFBridgingRetain(currentTime), &flagOut);
CFRelease(sampleBuffer);
// if you're using AVSampleBufferDisplayLayer, you only need to use this line of code
// [videoLayer enqueueSampleBuffer:sampleBuffer];
}
을 사용하는 경우 AVSampleBufferDisplayLayer
viewDidLoad 또는 다른 init 메서드 내부에서 이와 같이 레이어를 초기화해야합니다.
-(void) viewDidLoad
{
// create our AVSampleBufferDisplayLayer and add it to the view
videoLayer = [[AVSampleBufferDisplayLayer alloc] init];
videoLayer.frame = self.view.frame;
videoLayer.bounds = self.view.bounds;
videoLayer.videoGravity = AVLayerVideoGravityResizeAspect;
// set Timebase, you may need this if you need to display frames at specific times
// I didn't need it so I haven't verified that the timebase is working
CMTimebaseRef controlTimebase;
CMTimebaseCreateWithMasterClock(CFAllocatorGetDefault(), CMClockGetHostTimeClock(), &controlTimebase);
//videoLayer.controlTimebase = controlTimebase;
CMTimebaseSetTime(self.videoLayer.controlTimebase, kCMTimeZero);
CMTimebaseSetRate(self.videoLayer.controlTimebase, 1.0);
[[self.view layer] addSublayer:videoLayer];
}
If you can't find the VTD error codes in the framework, I decided to just include them here. (Again, all these errors and more can be found inside the VideoToolbox.framework
itself in the project navigator, in the file VTErrors.h
.)
You will get one of these error codes either in the the VTD decode frame callback or when you create your VTD session if you did something incorrectly.
kVTPropertyNotSupportedErr = -12900,
kVTPropertyReadOnlyErr = -12901,
kVTParameterErr = -12902,
kVTInvalidSessionErr = -12903,
kVTAllocationFailedErr = -12904,
kVTPixelTransferNotSupportedErr = -12905, // c.f. -8961
kVTCouldNotFindVideoDecoderErr = -12906,
kVTCouldNotCreateInstanceErr = -12907,
kVTCouldNotFindVideoEncoderErr = -12908,
kVTVideoDecoderBadDataErr = -12909, // c.f. -8969
kVTVideoDecoderUnsupportedDataFormatErr = -12910, // c.f. -8970
kVTVideoDecoderMalfunctionErr = -12911, // c.f. -8960
kVTVideoEncoderMalfunctionErr = -12912,
kVTVideoDecoderNotAvailableNowErr = -12913,
kVTImageRotationNotSupportedErr = -12914,
kVTVideoEncoderNotAvailableNowErr = -12915,
kVTFormatDescriptionChangeNotSupportedErr = -12916,
kVTInsufficientSourceColorDataErr = -12917,
kVTCouldNotCreateColorCorrectionDataErr = -12918,
kVTColorSyncTransformConvertFailedErr = -12919,
kVTVideoDecoderAuthorizationErr = -12210,
kVTVideoEncoderAuthorizationErr = -12211,
kVTColorCorrectionPixelTransferFailedErr = -12212,
kVTMultiPassStorageIdentifierMismatchErr = -12213,
kVTMultiPassStorageInvalidErr = -12214,
kVTFrameSiloInvalidTimeStampErr = -12215,
kVTFrameSiloInvalidTimeRangeErr = -12216,
kVTCouldNotFindTemporalFilterErr = -12217,
kVTPixelTransferNotPermittedErr = -12218,
A good Swift example of much of this can be found in Josh Baker's Avios library: https://github.com/tidwall/Avios
Note that Avios currently expects the user to handle chunking data at NAL start codes, but does handle decoding the data from that point forward.
또한 살펴볼 가치가있는 것은 Swift 기반 RTMP 라이브러리 HaishinKit (이전의 "LF")입니다.이 라이브러리는보다 강력한 NALU 구문 분석을 포함하여 자체 디코딩 구현이 있습니다. https://github.com/shogo4405/lf.swift
위의 VTErrors 외에도 Livy의 예제를 시도하는 동안 발생할 수있는 CMFormatDescription, CMBlockBuffer, CMSampleBuffer 오류를 추가 할 가치가 있다고 생각했습니다.
kCMFormatDescriptionError_InvalidParameter = -12710,
kCMFormatDescriptionError_AllocationFailed = -12711,
kCMFormatDescriptionError_ValueNotAvailable = -12718,
kCMBlockBufferNoErr = 0,
kCMBlockBufferStructureAllocationFailedErr = -12700,
kCMBlockBufferBlockAllocationFailedErr = -12701,
kCMBlockBufferBadCustomBlockSourceErr = -12702,
kCMBlockBufferBadOffsetParameterErr = -12703,
kCMBlockBufferBadLengthParameterErr = -12704,
kCMBlockBufferBadPointerParameterErr = -12705,
kCMBlockBufferEmptyBBufErr = -12706,
kCMBlockBufferUnallocatedBlockErr = -12707,
kCMBlockBufferInsufficientSpaceErr = -12708,
kCMSampleBufferError_AllocationFailed = -12730,
kCMSampleBufferError_RequiredParameterMissing = -12731,
kCMSampleBufferError_AlreadyHasDataBuffer = -12732,
kCMSampleBufferError_BufferNotReady = -12733,
kCMSampleBufferError_SampleIndexOutOfRange = -12734,
kCMSampleBufferError_BufferHasNoSampleSizes = -12735,
kCMSampleBufferError_BufferHasNoSampleTimingInfo = -12736,
kCMSampleBufferError_ArrayTooSmall = -12737,
kCMSampleBufferError_InvalidEntryCount = -12738,
kCMSampleBufferError_CannotSubdivide = -12739,
kCMSampleBufferError_SampleTimingInfoInvalid = -12740,
kCMSampleBufferError_InvalidMediaTypeForOperation = -12741,
kCMSampleBufferError_InvalidSampleData = -12742,
kCMSampleBufferError_InvalidMediaFormat = -12743,
kCMSampleBufferError_Invalidated = -12744,
kCMSampleBufferError_DataFailed = -16750,
kCMSampleBufferError_DataCanceled = -16751,
@Livy CMVideoFormatDescriptionCreateFromH264ParameterSets
는 다음을 추가 하기 전에 메모리 누수를 제거합니다 .
if (_formatDesc) {
CFRelease(_formatDesc);
_formatDesc = NULL;
}
'programing' 카테고리의 다른 글
git, don't show me *.pyc in the list of untracked files! (0) | 2020.11.29 |
---|---|
순수 C / C ++ (cout / printf)에서 진행률 표시기를 표시하는 방법은 무엇입니까? (0) | 2020.11.29 |
Google API : 클라이언트의 유효한 출처가 아닙니다. URL이 클라이언트 ID 'ID'에 허용되지 않았습니다. (0) | 2020.11.29 |
.htaccess로 디렉토리 (모든 하위 디렉토리 포함)에서 PHP를 비활성화합니다. (0) | 2020.11.29 |
보기에서 C # 면도기 URL 매개 변수 (0) | 2020.11.29 |