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

一、音视频的采集

音视频采集的核心流程:

音/视频采集

  • 用到的视频输出的类是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:编码规范。NULLvideoToolbox自己选择。
    参数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 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数据。

创建解码器:

- (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: 指定必须使用的特定视频解码器。NULLvideo 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回调出去用来展示。

音视频-视频编/解码 实战相关推荐

  1. 嵌入式Linux下基于FFmpeg的视频硬件编解码[图]

    转自:http://tech.c114.net/167/a674033.html 摘要: 对FFmpeg多媒体解决方案中的视频编解码流程进行研究.结合对S3C6410处理器视频硬件编解码方法的分析,阐 ...

  2. 嵌入式Linux下基于FFmpeg的视频硬件编解码

    嵌入式Linux下基于FFmpeg的视频硬件编解码[图] http://www.c114.net ( 2012/3/1 15:41 ) 摘要: 对FFmpeg多媒体解决方案中的视频编解码流程进行研究. ...

  3. ffmpeg / avconv是通用的视频/音频编解码命令行工具

    转载自:http://blog.openlg.net/index.php/archives/767 ffmpeg / avconv是通用的视频/音频编解码命令行工具. 通用是既指他们可以处理各种各样的 ...

  4. iOS8系统H264视频硬件编解码说明

    iOS8系统H264视频硬件编解码说明 转载自:http://www.tallmantech.com/archives/206#more-206 公司项目原因,接触了一下视频流H264的编解码知识,之 ...

  5. iOS系统H264视频硬件编解码说明

    公司项目原因,接触了一下视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会 ...

  6. 音频编解码·实战篇(1)PCM转至AAC(AAC编码)

    音频编解码·实战篇(1)PCM转至AAC(AAC编码) 作者:柳大·Poechant 博客:blog.csdn.net/poechant 邮箱:zhongchao.ustc@gmail.com 日期: ...

  7. 视频H264编解码知识整理

    简介 网络提取层(NAL network abstraction layer )和视频编码层(VCL video coding) 码率.帧率.分辨率 其它 总结 简介 视频编解码网上介绍很多,整理了不 ...

  8. 开发那些事儿:如何解决RK芯片视频处理编解码耗时很长的问题?

    流媒体视频直播包括以下几个步骤:采集->处理->编码和封装->推流到服务器->服务器流分发->播放器流播放. 在流媒体处理编码的过程中,会有硬解码和软解码两种播放方式.两 ...

  9. ffmpeg / avconv是通用的视频/音频编解码命令行工具【转】

    来自:http://blog.csdn.net/smilefyx/article/details/46793685 通用是既指他们可以处理各种各样的编码的视频和音频,转换成各种需要的格式,又指他们是跨 ...

最新文章

  1. 结对编程--基于android平台的黄金点游戏
  2. 【机器学习】知否?知否?广义线性模型
  3. [网络安全自学篇] 十五.Python攻防之多线程、C段扫描和数据库编程(二)
  4. lambda表达式python啥意思_Python中lambda表达式是什么
  5. 类5-类的继承、虚函数、纯虚函数、虚析构函数
  6. C#与NET实战 第5章 进程、线程与同步 节选
  7. 前端有未来吗?听我娓娓道来!
  8. php server(),php的$_SERVER参数详解(附实例)
  9. 单细胞分析实录(10): 消除细胞周期的影响
  10. 1000句最常用英语口语 (四)
  11. python、变量命名中字母不区分大小写_python变量名不区分大小写吗
  12. unbuntu 安装docker
  13. 透彻理解神经网络剪枝算法
  14. 2022牛客多校J题 Serval and Essay
  15. html横幅设置,如何控制HTML横幅的宽度和高度?
  16. Unity与Android通信交互
  17. 10大关键词解读中国互联网五年间创新飞跃在哪里
  18. VxWorks 任务使用的学习
  19. iPhone 如何不越狱安装越狱软件
  20. 《消费者行为学》读书笔记 第一章 消费者行为学导论

热门文章

  1. 实验1动态规划——小明打王者
  2. HTML5开源游戏,梦幻农场,连连看,开心消除类小游戏
  3. 【五】Socket函数详解
  4. 开发者分享 | AXI 基础第 2 讲-使用AXI VIP 对 AXI4-Lite 主 (Master) 接口进行仿真
  5. 元学习与小样本学习 | (2) Few-shot Learning 综述
  6. 向百度提交网站地图Sitemap
  7. 复制的数字无法计算机,拷完数据在退出U盘时电脑提示无法停止通用卷设备的解决方法...
  8. python能替代sql_SQL和Python 哪个更容易自学?
  9. 淘宝商城开放的B2C平台战略
  10. 技术派-关于最佳线程数的计算的准确理解