音视频-视频编/解码 实战
先来简单看下 音视频的采集 。
一、音视频的采集
音视频采集的核心流程:
音/视频采集
- 用到的视频输出的类是
AVCaptureVideoDataOutput
,音频输出的类是AVCaptureAudioDataOutput
。 - 采集成功后的代理方法输出的音视频对象为
CMSampleBufferRef
类型的sampleBuffer
。这里我们可以使用AVCaptureConnection
来判断是音频还是视频。
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {if (connection == self.audioConnection) { //音频}else if (connection == self.videoConnection) { //视频}
}
采集的核心流程跟 AVFoundation 拍照/录制视频 和 AVFoundation 人脸识别 的采集流程基本一致,大家可以了解下。
二、视频的编解码
2.1 视频的编码
1.首先需要初始化编码器,看代码:
- (instancetype)initWithConfigure:(CQVideoCoderConfigure *)configure {self = [super init];if (self) {self.configure = configure;self.encodeQueue = dispatch_queue_create("h264 hard encode queue", DISPATCH_QUEUE_SERIAL);self.callbackQueue = dispatch_queue_create("h264 hard encode callback queue", DISPATCH_QUEUE_SERIAL);//1.创建编码session 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);if (status != noErr) {NSLog(@"VTCompressionSessionCreate error status: %d", (int)status);return self;}//2、设置编码器参数//是否实时执行status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);NSLog(@"VTSessionSetProperty RealTime status: %d", (int)status);//指定编码比特流的配置文件和级别。直播一般使用baseline,可减少由b帧减少带来的延迟。status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
NSLog(@"VTSessionSetProperty ProfileLevel status: %d", (int)status);//设置比特率均值(比特率可以高于此。默认比特率为零,表示视频编码器。应该确定压缩数据的大小。)//注意:比特率设置只在定时的时候有效status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFNumberRef)@(self.configure.bitrate));NSLog(@"VTSessionSetProperty AverageBitRate status: %d", (int)status);//码率限制CFArrayRef limits = (__bridge CFArrayRef)@[@(self.configure.bitrate / 4),@(self.configure.bitrate * 4)];status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_DataRateLimits,limits);NSLog(@"VTSessionSetProperty DataRateLimits status: %d", (int)status);//设置关键帧间隔status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFNumberRef)@(self.configure.fps * 2));NSLog(@"VTSessionSetProperty MaxKeyFrameInterval status: %d", (int)status);//设置预期的fpsCFNumberRef expectedFrameRate = (__bridge CFNumberRef)@(self.configure.fps);status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, expectedFrameRate);NSLog(@"VTSessionSetProperty ExpectedFrameRate status: %d", (int)status);//3、准备编码status = VTCompressionSessionPrepareToEncodeFrames(self.encodeSesion);NSLog(@"VTSessionSetProperty: set PrepareToEncodeFrames return: %d", (int)status);}return self;
}
- 1、
VTCompressionSessionCreate
:创建压缩会话,并且添加了编码成功后的回调函数compressionOutputCallback
。
参数1:会话的分配器。传递NULL
使用默认分配器。
参数2:帧的宽度,以像素为单位。如果视频编码器不支持所提供的宽度和高度,系统可能会自动修改。
参数3:帧的高度。
参数4:编码类型。
参数5:编码规范。NULL
由videoToolbox
自己选择。
参数6:源像素缓冲区属性,NULL
不让videToolbox
创建,自己创建。
参数7: 压缩数据分配器。NULL
默认的分配。
参数8:回调函数。异步调用。
参数9:客户端为输出回调定义的引用值。这里传的事我们自定义的编码器,也就是self
。
参数10: 要创建的编码会话对象。 - 2、
VTSessionSetProperty
属性配置。 - 3、
VTCompressionSessionPrepareToEncodeFrames
:准备编码。
2、进行编码,看代码:
- (void)encoderSampleBuffers:(CMSampleBufferRef)sampleBuffer {CFRetain(sampleBuffer);dispatch_async(self.encodeQueue, ^{CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);//帧数据self->frameID++;CMTime timeStamp = CMTimeMake(self->frameID, 1000);//该帧的时间戳CMTime duration = kCMTimeInvalid;//持续时间VTEncodeInfoFlags flags;OSStatus status = VTCompressionSessionEncodeFrame(self.encodeSesion, imageBuffer, timeStamp, duration, NULL, NULL, &flags);//编码if (status != noErr) {NSLog(@"VTCompressionSessionEncodeFrame error status: %d",(int)status);}CFRelease(sampleBuffer);});
}
- 1、
CMSampleBufferGetImageBuffer
从采集到的视频CMSampleBufferRef
中获取CVImageBufferRef
。 - 2、
VTCompressionSessionEncodeFrame
压缩编码:
参数1:编码会话encodeSesion
参数2:CVImageBuffer
对象,包含视频帧数据
参数3:对应该帧的时间戳,每个示例的时间戳必须大于前一个。
参数4:该演示帧的持续时间,没有可填kCMTimeInvalid
参数5:编码该帧的键/值对属性信息。注意,某些会话属性也可能在帧之间更改。这种变化对随后编码的帧有影响。
参数6:将要传递给回调函数的帧的引用值。
参数7:VTEncodeInfoFlags
接收有关编码操作的信息.
3、编码成功后回调处理:
// startCode 长度 4
const Byte startCode[] = "\x00\x00\x00\x01";
void compressionOutputCallback(void * CM_NULLABLE outputCallbackRefCon, void * CM_NULLABLE sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CM_NULLABLE CMSampleBufferRef sampleBuffer ) {if (status != noErr) {NSLog(@"compressionOutputCallback error status: %d", (int)status);return;}if (!CMSampleBufferDataIsReady(sampleBuffer)) {NSLog(@"CMSampleBufferDataIsReady is not ready");return;}CQVideoEncoder *encoder = (__bridge CQVideoEncoder *)outputCallbackRefCon;BOOL keyFrame = NO;CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);keyFrame = !CFDictionaryContainsKey(CFArrayGetValueAtIndex(attachmentsArray, 0), kCMSampleAttachmentKey_NotSync);//是否为关键帧,并且有没有获取过sps 和 pps 数据。if (keyFrame && !encoder->hasSpsPps) {size_t spsSize, spsCount, ppsSize, ppsCount;const uint8_t *spsData, *ppsData;//获取图像原格式CMFormatDescriptionRef formatDes = CMSampleBufferGetFormatDescription(sampleBuffer);OSStatus status1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 0, &spsData, &spsSize, &spsCount, 0);OSStatus status2 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 1, &ppsData, &ppsSize, &ppsCount, 0);if (status1 == noErr & status2 == noErr) {//sps/pps获取成功NSLog(@"Get sps and pps success!!!");//sps 和 pps 数据只需保存在H264文件开头即可。encoder->hasSpsPps = true;NSMutableData *spsDataM = [NSMutableData dataWithCapacity:4 + spsSize];[spsDataM appendBytes:startCode length:4];[spsDataM appendBytes:spsData length:spsSize];NSMutableData *ppsDataM = [NSMutableData dataWithCapacity:4 + ppsSize];[ppsDataM appendBytes:startCode length:4];[ppsDataM appendBytes:ppsData length:ppsSize];dispatch_async(encoder.encodeQueue, ^{if ([encoder.delegate respondsToSelector:@selector(encodeCallbackWithSps:pps:)]) {[encoder.delegate encodeCallbackWithSps:spsDataM pps:ppsDataM];}});} else {NSLog(@"Get sps and pps failed, spsStatus:%d, ppsStatus:%d", (int)status1, (int)status2);}}//获取NAL Unit数据size_t lengthAtOffset, totalLength;char *dataPoint;//将数据复制到dataPointCMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);OSStatus error = CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &totalLength, &dataPoint);if (error != kCMBlockBufferNoErr) {NSLog(@"VideoEncodeCallback: get datapoint failed, status = %d", (int)error);return;}//循环获取NAL Unit数据size_t offet = 0;//返回的NAL Unit数据前四个字节不是系统端的startCode(0001)//而是大端模式的帧长度const int lengthStartCode = 4;const int lengthBigFrame = 4;while (offet < totalLength - lengthBigFrame) {//获取NAL Unit数据长度uint32_t lengthNALU = 0;memcpy(&lengthNALU, dataPointerOut + offet, lengthBigFrame);lengthNALU = CFSwapInt32BigToHost(lengthBigFrame);//大端转系统端//获取到编码好的视频startCode + NAL UintNSMutableData *data = [NSMutableData dataWithCapacity:lengthStartCode + lengthNALU];[data appendBytes:startCode length:lengthStartCode];[data appendBytes:dataPointerOut + offet + lengthBigFrame length:lengthNALU];dispatch_async(encoder.encodeQueue, ^{if ([encoder.delegate respondsToSelector:@selector(encodeVideoCallback:)]) {[encoder.delegate encodeVideoCallback:data];}});offet += lengthStartCode + lengthNALU;}
}
- 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格式数据:
- (void)decodeH264Data:(NSData *)frame {dispatch_async(self.decodeQueue, ^{uint8_t *frameNALU = (uint8_t *)frame.bytes;uint32_t lengthFrame = (uint32_t)frame.length;int type = (frameNALU[4] & 0x1F);//0 01 00111 & 39//0 00 11111 31//0 00 00111 7//NSLog(@"type: %hhu, %d", frame[4], type);//将NAL Unit开始码转为4字节大端NAL Unit的长度信息。uint32_t naluSize = lengthFrame - 4;uint8_t *pNaluSize = (uint8_t *)(&naluSize);frameNALU[0] = *(pNaluSize + 3);frameNALU[1] = *(pNaluSize + 2);frameNALU[2] = *(pNaluSize + 1);frameNALU[3] = *(pNaluSize);CVPixelBufferRef pixelBuffer = NULL;switch (type) {case 0x05://I帧(关键帧)if ([self createDecoder]) {pixelBuffer = [self decodeNALUFrame:frameNALU withFrameLength:lengthFrame];}break;case 0x06://增强信息break;case 0x07://spsself->_spsSize = naluSize;self->_sps = malloc(self->_spsSize);memcpy(self->_sps, &frameNALU[4], self->_spsSize);break;case 0x08://ppsself->_ppsSize = naluSize;self->_pps = malloc(self->_ppsSize);memcpy(self->_pps, &frameNALU[4], self->_ppsSize);break;default://其他帧(0x01到 0x05)if ([self createDecoder]) {pixelBuffer = [self decodeNALUFrame:frameNALU withFrameLength:lengthFrame];}break;}});
}
- 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 Unit
的header
。
例如:
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、当
type
为0x05
时代表时I
帧(关键帧)这时我们需要先创建解码器。0x07
时创建sps
数据。0x08
时创建pps
数据。
创建解码器:
- (BOOL)createDecoder {if (self.decodeSesion) return YES;const uint8_t * const parameterSetPointers[2] = {_sps, _pps};const size_t parameterSetSize[2] = {_spsSize, _ppsSize};int lengthStartCode = 4;OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSize, lengthStartCode, &_decoderDesc);if (status != noErr) {NSLog(@"CMVideoFormatDescriptionCreateFromH264ParameterSets error status: %d", (int)status);return NO;}NSDictionary *decoderAttachments =@{(id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //摄像头的输出数据格式(id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:self.configure.width],(id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:self.configure.height],(id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:YES]};//解码回调设置VTDecompressionOutputCallbackRecord decompressionCallback;decompressionCallback.decompressionOutputCallback = decoderVideoOutputCallback;decompressionCallback.decompressionOutputRefCon = (__bridge void * _Nullable)self;VTDecompressionSessionCreate(kCFAllocatorDefault, _decoderDesc, NULL, (__bridge CFDictionaryRef _Nullable)decoderAttachments, &decompressionCallback, &_decodeSesion);if (status != noErr) {NSLog(@"VTDecompressionSessionCreate error status: %d", (int)status);return NO;}//实时编码status = VTSessionSetProperty(_decodeSesion, kVTDecompressionPropertyKey_RealTime,kCFBooleanTrue); if (status != noErr) {NSLog(@"VTSessionSetProperty RealTime error status:%d", (int)status);}return YES;
}
- 1、
CMVideoFormatDescriptionCreateFromH264ParameterSets
设置解码参数:
参数1:kCFAllocatorDefault
使用默认的内存分配
参数2: 参数个数
参数3: 参数集指针
参数4: 参数集大小
参数5:startCode
的长度 4
参数6: 解码格式器描述对象 - 2、
VTDecompressionOutputCallbackRecord
解码回调设置。 - 3、
VTDecompressionSessionCreate
创建解码器。
参数1:kCFAllocatorDefault
使用默认的内存分配。
参数2: 解码格式器描述对象。
参数3: 指定必须使用的特定视频解码器。NULL
让video toolbox
选择解码器。
参数4: 描述源像素缓冲区的要求,NULL
无要求。
参数5: 已解压缩的帧调用的回调函数。
参数6: 指向一个变量以接收新的解压会话。 - 4、
VTSessionSetProperty
设置解码会话属性。
解码:
- (CVPixelBufferRef)decodeNALUFrame:(uint8_t *)frameNALU withFrameLength:(uint32_t)lengthFrame {CVPixelBufferRef outputPixelBuffer = NULL;CMBlockBufferRef blockBufferOut = NULL;CMBlockBufferFlags flag0 = 0;//1.OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frameNALU, lengthFrame, kCFAllocatorNull, NULL, 0, lengthFrame, flag0, &blockBufferOut);if (status != kCMBlockBufferNoErr) {NSLog(@"CMBlockBufferCreateWithMemoryBlock error status:%d", (int)status);return outputPixelBuffer;}CMSampleBufferRef sampleBuffer = NULL;const size_t sampleSizeArray[] = {lengthFrame};//2.创建sampleBufferstatus = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBufferOut, _decoderDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);if (status != noErr || !sampleBuffer) {NSLog(@"CMSampleBufferCreateReady error status:%d", (int)status);CFRelease(blockBufferOut);return outputPixelBuffer;}//解码VTDecodeFrameFlags decodeFrameFlags = kVTDecodeFrame_1xRealTimePlayback;VTDecodeInfoFlags decodeInfoFlags = kVTDecodeInfo_Asynchronous; //异步解码status = VTDecompressionSessionDecodeFrame(_decodeSesion, sampleBuffer, decodeFrameFlags, &outputPixelBuffer, &decodeInfoFlags);if (status == kVTInvalidSessionErr) {NSLog(@"VTDecompressionSessionDecodeFrame InvalidSessionErr status:%d", (int)status);} else if (status == kVTVideoDecoderBadDataErr) {NSLog(@"VTDecompressionSessionDecodeFrame BadData status:%d", (int)status);} else if (status != noErr) {NSLog(@"VTDecompressionSessionDecodeFrame status:%d", (int)status);}CFRelease(sampleBuffer);CFRelease(blockBuffer);return outputPixelBuffer;
}
- 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: 同步还是异步解码。
解码成功后回调函数:
void decoderVideoOutputCallback(void * CM_NULLABLE decompressionOutputRefCon,void * CM_NULLABLE sourceFrameRefCon,OSStatus status,VTDecodeInfoFlags infoFlags,CM_NULLABLE CVImageBufferRef imageBuffer,CMTime presentationTimeStamp,CMTime presentationDuration ) {if (status != noErr) {NSLog(@"decoderVideoOutputCallback error status:%d", (int)status);return;}CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;*outputPixelBuffer = CVPixelBufferRetain(imageBuffer);CQVideoDecoder *decoder = (__bridge CQVideoDecoder *)decompressionOutputRefCon;dispatch_async(decoder.callbackQueue, ^{if ([decoder.delegate respondsToSelector:@selector(videoDecodeCallback:)]) {[decoder.delegate videoDecodeCallback:imageBuffer];}//释放数据CVPixelBufferRelease(imageBuffer);});}
1、回调函数的参数:
参数1:回调的引用值。
参数2:帧的引用值。
参数3:压缩失败/成功的状态码。
参数4:如果设置了kVTDecodeInfo_Asynchronous
表示异步解码,
如果设置了kVTDecodeInfo_FrameDropped
可以丢帧,
如果设置了kVTDecodeInfo_ImageBufferModifiable
可以安全地修改imageBuffer
(实际图像的缓冲).
参数5:实际图像的缓冲。如果未设置kVTDecodeInfo_ImageBufferModifiable
标志,则视频解压缩器可能仍在引用此回调中返回的imageBuffer
,此时修改返回的imageBuffer
是不安全的。
参数6:帧的时间戳。
参数7:帧的持续时间。2、将指针
*outputPixelBuffer
指向实际图像缓冲区imageBuffer
3、将图像缓冲区
imageBuffer
回调出去用来展示。
音视频-视频编/解码 实战相关推荐
- 嵌入式Linux下基于FFmpeg的视频硬件编解码[图]
转自:http://tech.c114.net/167/a674033.html 摘要: 对FFmpeg多媒体解决方案中的视频编解码流程进行研究.结合对S3C6410处理器视频硬件编解码方法的分析,阐 ...
- 嵌入式Linux下基于FFmpeg的视频硬件编解码
嵌入式Linux下基于FFmpeg的视频硬件编解码[图] http://www.c114.net ( 2012/3/1 15:41 ) 摘要: 对FFmpeg多媒体解决方案中的视频编解码流程进行研究. ...
- ffmpeg / avconv是通用的视频/音频编解码命令行工具
转载自:http://blog.openlg.net/index.php/archives/767 ffmpeg / avconv是通用的视频/音频编解码命令行工具. 通用是既指他们可以处理各种各样的 ...
- iOS8系统H264视频硬件编解码说明
iOS8系统H264视频硬件编解码说明 转载自:http://www.tallmantech.com/archives/206#more-206 公司项目原因,接触了一下视频流H264的编解码知识,之 ...
- iOS系统H264视频硬件编解码说明
公司项目原因,接触了一下视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会 ...
- 音频编解码·实战篇(1)PCM转至AAC(AAC编码)
音频编解码·实战篇(1)PCM转至AAC(AAC编码) 作者:柳大·Poechant 博客:blog.csdn.net/poechant 邮箱:zhongchao.ustc@gmail.com 日期: ...
- 视频H264编解码知识整理
简介 网络提取层(NAL network abstraction layer )和视频编码层(VCL video coding) 码率.帧率.分辨率 其它 总结 简介 视频编解码网上介绍很多,整理了不 ...
- 开发那些事儿:如何解决RK芯片视频处理编解码耗时很长的问题?
流媒体视频直播包括以下几个步骤:采集->处理->编码和封装->推流到服务器->服务器流分发->播放器流播放. 在流媒体处理编码的过程中,会有硬解码和软解码两种播放方式.两 ...
- ffmpeg / avconv是通用的视频/音频编解码命令行工具【转】
来自:http://blog.csdn.net/smilefyx/article/details/46793685 通用是既指他们可以处理各种各样的编码的视频和音频,转换成各种需要的格式,又指他们是跨 ...
最新文章
- 结对编程--基于android平台的黄金点游戏
- 【机器学习】知否?知否?广义线性模型
- [网络安全自学篇] 十五.Python攻防之多线程、C段扫描和数据库编程(二)
- lambda表达式python啥意思_Python中lambda表达式是什么
- 类5-类的继承、虚函数、纯虚函数、虚析构函数
- C#与NET实战 第5章 进程、线程与同步 节选
- 前端有未来吗?听我娓娓道来!
- php server(),php的$_SERVER参数详解(附实例)
- 单细胞分析实录(10): 消除细胞周期的影响
- 1000句最常用英语口语 (四)
- python、变量命名中字母不区分大小写_python变量名不区分大小写吗
- unbuntu 安装docker
- 透彻理解神经网络剪枝算法
- 2022牛客多校J题 Serval and Essay
- html横幅设置,如何控制HTML横幅的宽度和高度?
- Unity与Android通信交互
- 10大关键词解读中国互联网五年间创新飞跃在哪里
- VxWorks 任务使用的学习
- iPhone 如何不越狱安装越狱软件
- 《消费者行为学》读书笔记 第一章 消费者行为学导论
热门文章
- 实验1动态规划——小明打王者
- HTML5开源游戏,梦幻农场,连连看,开心消除类小游戏
- 【五】Socket函数详解
- 开发者分享 | AXI 基础第 2 讲-使用AXI VIP 对 AXI4-Lite 主 (Master) 接口进行仿真
- 元学习与小样本学习 | (2) Few-shot Learning 综述
- 向百度提交网站地图Sitemap
- 复制的数字无法计算机,拷完数据在退出U盘时电脑提示无法停止通用卷设备的解决方法...
- python能替代sql_SQL和Python 哪个更容易自学?
- 淘宝商城开放的B2C平台战略
- 技术派-关于最佳线程数的计算的准确理解