iOS视频硬编码技术

一.iOS视频采集硬编码

基本原理

硬编码 & 软编码

硬编码:通过系统自带的Camera录制视频,实际上调用的是底层的高清编码硬件模块,即显卡,不使用CPU,速度快

软编码:使用CPU进行编码,如常见C/C++代码,编译生成二进制文件,速度相对较慢。例如使用Android NDK编译H264生成so库,编写jni接口,再使用java调用so库。

硬编码过程和原理

过程:通过MediaRecoder采集视频,再将视频流映射到LocalSocket实现收发。

视频采集步骤

主要是通过< AVFoundation >框架进行采集

  • 1.创建会话
  • 2.设置视频的输入
  • 3.设置视频的输出
  • 获取摄像头方向
  • 切换摄像头
  • 实现代理 -- AVCaptureVideoDataOutputSampleBufferDelegat
  • 停止采集
·          AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];
·          captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
·          self.captureSession = captureSession;
·          // 2.1获取输入设备,摄像头,默认为后置
·          AVCaptureDevice *videoDevice = [self getVideoDevice:AVCaptureDevicePositionBack];
·         
·          //2.2创建对应视频输入对象
·          AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:nil];
·         
·          //2.3添加到会话中
·          // 注意:最好要判断是否能添加输入,会话不能添加空的
·          if ([captureSession canAddInput:videoDeviceInput])   {
·           [captureSession addInput:videoDeviceInput];
·          }
    // 3.1获取视频输出设备
    AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    //默认为YES,接收器会立即丢弃接收到的帧,在代理里面,NO的时候,将允许在丢弃之前有更多时间处理旧帧
   // [videoOutput setAlwaysDiscardsLateVideoFrames:NO];
    // 3.2 设置输出代理,捕获视频样品数据
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    [videoOutput setSampleBufferDelegate:self queue:queue];
    if ([captureSession canAddOutput:videoOutput]) {
        [captureSession addOutput:videoOutput];
    }
    
    //3.3 设置视频输出方向
    // 注意:设置方向,必须在videoOutput添加到captureSession之后,否则出错
    AVCaptureConnection *connection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];
    if (connection.isVideoOrientationSupported) {
        [connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
    }
 

4. 添加视频预览层

AVCaptureVideoPreviewLayer *layer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
self.preViewlayer = layer;
[layer setVideoGravity:AVLayerVideoGravityResizeAspect];
layer.frame = preview.bounds;
[preview.layer insertSublayer:layer atIndex:0];

5.开始采集

[captureSession startRunning];
//指定摄像头方向,获取摄像头
- (AVCaptureDevice *)getVideoDevice:(AVCaptureDevicePosition)position{
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices) {
        if (device.position == position) {
            return device;
        }
    }
    return nil;
}
// 切换采集摄像头
- (void)switchScene:(UIView *)preview{
    // 1.添加动画
    CATransition *rotaionAnim = [[CATransition alloc] init];
    rotaionAnim.type = @"oglFlip";
    rotaionAnim.subtype = @"fromLeft";
    rotaionAnim.duration = 0.5;
    [preview.layer addAnimation:rotaionAnim forKey:nil];
    
    // 2.获取当前镜头
    AVCaptureDevicePosition position = self.videoDeviceInput.device.position == AVCaptureDevicePositionBack ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
    
    // 3.创建新的input对象
    AVCaptureDevice *newDevice = [self getVideoDevice:position];
    AVCaptureDeviceInput *newDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:newDevice error:nil];
    
    // 4.移除旧输入,添加新输入
    [self.captureSession beginConfiguration];
    [self.captureSession removeInput:self.videoDeviceInput];
    [self.captureSession addInput:newDeviceInput];
    // 此处要重新设置视频输出方向,默认会旋转90度
    self.connection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
    if (_connection.isVideoOrientationSupported) {
        [_connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
    }
 
    [self.captureSession commitConfiguration];
    // 5.保存新输入
    self.videoDeviceInput = newDeviceInput;
}
 
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
    NSLog(@"获取到一帧数据");
    // 对获取到的数据进行编码,编码部分在下一篇继续讲
    dispatch_sync(mEncodeQueue, ^{
        [self.encoder encodeFrame:sampleBuffer];
    });
}
- (void)stopCapturing{
    // 停止扫描
    [self.captureSession stopRunning];
    // 移除预览图层
    [self.preViewlayer removeFromSuperlayer];
    // 将对象置为nil
    self.captureSession = nil;
}

6. iOS音视频采集硬编码

关于音视频采集硬编码,为方便项目的参考。用到AVCaptureSession来进行音视频数据采集,AVCaptureSession是用来管理视频与数据的捕获,采集到音频原始数据pcm(pcm是指未经过压缩处理的)压缩为aac格式,采集到yuv420格式的视频帧压缩成h.264格式。

demo:https://github.com/oopsr/AVDecode

二.iOS视频开发:视频H264硬编码

已经介绍了如何采集iOS摄像头的视频数据,采集到的原始视频数据量是比较大的,这么大的数据量不利于进行储存或网络传输。需要对视频数据进行压缩,就像你要向别人传文件时觉得文件太大了,打个rar压缩包再发给对方的道理一样。视频数据的压缩也叫做编码,H264是一种视频编码格式,iOS 8.0及以上苹果开放了VideoToolbox框架来实现H264硬编码,开发者可以利用VideoToolbox框架很方便地实现视频的硬编码。下面将分以下几部分内容来讲解H264硬编码在iOS中的实现:

1、介绍视频编码的基本概念

2、VideoToolbox实现硬编码原理及流程

3、代码实现硬编码

4、总结及Demo


基本概念

视频数据为什么可以压缩呢,因为视频数据存在冗余。通俗地理解,例如一个视频中,前一秒画面跟当前的画面内容相似度很高,那么这两秒的数据是不是可以不用全部保存,只保留一个完整的画面,下一个画面看有哪些地方有变化了记录下来,拿视频去播放的时候就按这个完整的画面和其他有变化的地方把其他画面也恢复出来。记录画面不同然后保存下来这个过程就是数据编码,根据不同的地方恢复画面的过程就是数据解码。

H264是一种视频编码标准,在H264协议里定义了三种帧:

  • I帧:完整编码的帧,也叫关键帧
  • P帧:参考之前的I帧生成的只包含差异部分编码的帧
  • B帧:参考前后的帧编码的帧叫B帧

H264采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法,帧间压缩是生成B帧和P帧的算法。

H264原始码流是由一个接一个的NALU(Nal Unit)组成的,NALU = 开始码 + NAL类型 +
视频数据

开始码用于标示这是一个NALU 单元的开始,必须是"00
00 00 01" 或"00 00 01"

NALU类型如下:

类型

说明

0

未规定

1

非IDR图像中不采用数据划分的片段

2

非IDR图像中A类数据划分片段

3

非IDR图像中B类数据划分片段

4

非IDR图像中C类数据划分片段

5

IDR图像的片段

6

补充增强信息(SEI)

7

序列参数集(SPS)

8

图像参数集(PPS)

9

分割符

10

序列结束符

11

流结束符

12

填充数据

13

序列参数集扩展

14

带前缀的NAL单元

15

子序列参数集

16 – 18

保留

19

不采用数据划分的辅助编码图像片段

20

编码片段扩展

21 – 23

保留

24 – 31

未规定

一般只用到了1、5、7、8这4个类型就够了。类型为5表示这是一个I帧,I帧前面必须有SPS和PPS数据,也就是类型为7和8,类型为1表示这是一个P帧或B帧。

帧率:单位为fps(frame pre second),视频画面每秒有多少帧画面,数值越大画面越流畅

码率:单位为bps(bit pre second),视频每秒输出的数据量,数值越大画面越清晰

分辨率:视频画面像素密度,例如常见的720P、1080P等

关键帧间隔:每隔多久编码一个关键帧

软编码:使用CPU进行编码。性能较差

硬编码:不使用CPU进行编码,使用显卡GPU,专用的DSP、FPGA、ASIC芯片等硬件进行编码。性能较好

VideoToolbox实现H264硬编码

iOS8.0及以上可以通过VideoToolbox实现视频数据的硬编解码。VideoToolbox基本数据结构:

  • CVPixelBufferRef/CVImageBufferRef:存放编码前和解码后的图像数据,这俩货其实是同一个东西
  • CMTime:时间戳相关,时间以64-bit/32-bit的形式出现
  • CMBlockBufferRef:编码后输出的数据
  • CMFormatDescriptionRef/CMVideoFormatDescriptionRef:图像存储方式,编解码器等格式描述。这俩货也是同一个东西
  • CMSampleBufferRef:存放编解码前后的视频图像的容器数据
  • CMSampleBuffer编解码前后数据结构                                                                                                                                                                                                                                                                 
基本步骤

1、通过VTCompressionSessionCreate创建编码器

2、通过VTSessionSetProperty设置编码器属性

3、设置完属性调用VTCompressionSessionPrepareToEncodeFrames准备编码

4、输入采集到的视频数据,调用VTCompressionSessionEncodeFrame进行编码

5、获取到编码后的数据并进行处理

6、调用VTCompressionSessionCompleteFrames停止编码器

7、调用VTCompressionSessionInvalidate销毁编码器

1、创建编码器

VTCompressionSessionCreate用来创建视频编码会话,这个方法有10个参数,可以看一下苹果对这个API的注释

VTCompressionSessionCreate(
    CM_NULLABLE CFAllocatorRef                          allocator,
    int32_t                                             width,
    int32_t                                             height,
    CMVideoCodecType                                    codecType,
    CM_NULLABLE CFDictionaryRef                         encoderSpecification,
    CM_NULLABLE CFDictionaryRef                         sourceImageBufferAttributes,
    CM_NULLABLE CFAllocatorRef                          compressedDataAllocator,
    CM_NULLABLE VTCompressionOutputCallback             outputCallback,
    void * CM_NULLABLE                                  outputCallbackRefCon,
    CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTCompressionSessionRef * CM_NONNULL compressionSessionOut)

allocator:内存分配器,填NULL为默认分配器

widthheight:视频帧像素的宽高,如果编码器不支持这个宽高的话可能会改变

codecType:编码类型,枚举

encoderSpecification:指定特定的编码器,填NULL的话由VideoToolBox自动选择

sourceImageBufferAttributes:源像素缓冲区的属性,如果这个参数有值的话,VideoToolBox会创建一个缓冲池,不需要缓冲池可以设置为NULL

compressedDataAllocator:压缩后数据的内存分配器,填NULL使用默认分配器

outputCallback:视频编码后输出数据回调函数

outputCallbackRefCon:回调函数中的自定义指针,通常传self,在回调函数中就可以拿到当前类的方法和属性了

compressionSessionOut:编码器句柄,传入编码器的指针

OSStatus status = VTCompressionSessionCreate(NULL, 180, 320, kCMVideoCodecType_H264, NULL, NULL, NULL, encodeOutputDataCallback, (__bridge void *)(self), &_compressionSessionRef);
2、设置编码器属性 & 准备编码

编码器创建完了,所有给编码器设置属性都是调用VTSessionSetProperty方法来实现。

kVTCompressionPropertyKey_AverageBitRate:设置编码的平均码率,单位是bps,这不是一个硬性指标,设置的码率会上下浮动。VideoToolBox框架只支持ABR模式。H264有4种码率控制方法:

  • CBR(Constant Bit Rate)是以恒定比特率方式进行编码,有Motion发生时,由于码率恒定,只能通过增大QP来减少码字大小,图像质量变差,当场景静止时,图像质量又变好,因此图像质量不稳定。这种算法优先考虑码率(带宽)。
  • VBR(Variable Bit Rate)动态比特率,其码率可以随着图像的复杂程度的不同而变化,因此其编码效率比较高,Motion发生时,马赛克很少。码率控制算法根据图像内容确定使用的比特率,图像内容比较简单则分配较少的码率(似乎码字更合适),图像内容复杂则分配较多的码字,这样既保证了质量,又兼顾带宽限制。这种算法优先考虑图像质量。

    *CVBR(Constrained VariableBit Rate),这样翻译成中文就比较难听了,它是VBR的一种改进方法。但是Constrained又体现在什么地方呢?这种算法对应的Maximum bitRate恒定或者Average BitRate恒定。这种方法的兼顾了以上两种方法的优点:在图像内容静止时,节省带宽,有Motion发生时,利用前期节省的带宽来尽可能的提高图像质量,达到同时兼顾带宽和图像质量的目的。
  • ABR (Average Bit Rate) 在一定的时间范围内达到设定的码率,但是局部码率峰值可以超过设定的码率,平均码率恒定。可以作为VBR和CBR的一种折中选择。

kVTCompressionPropertyKey_ProfileLevel:设置H264编码的画质,H264有4种Profile:BP、EP、MP、HP

BP(Baseline
Profile)
:基本画质。支持I/P 帧,只支持无交错(Progressive)和CAVLC;主要应用:可视电话,会议电视,和无线通讯等实时视频通讯领域

EP(Extended
profile)
:进阶画质。支持I/P/B/SP/SI 帧,只支持无交错(Progressive)和CAVLC;

MP(Main
profile)
:主流画质。提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),也支持CAVLC 和CABAC 的支持;主要应用:数字广播电视和数字视频存储

HP(High
profile)
:高级画质。在main Profile 的基础上增加了8×8内部预测、自定义量化、 无损视频编码和更多的YUV 格式;应用于广电和存储领域

Level就多了,这里不一一列举,可参考h264 profile
& level
,iPhone上常用的方案如下:

  • 实时直播:

    低清Baseline Level 1.3

    标清Baseline Level 3

    半高清Baseline Level 3.1

    全高清Baseline Level 4.1
  • 存储媒体:

    低清 Main Level 1.3

    标清 Main Level 3

    半高清 Main Level 3.1

    全高清 Main Level 4.1
  • 高清存储:

    半高清 High Level 3.1

    全高清 High Level 4.1

kVTCompressionPropertyKey_RealTime:设置是否实时编码输出

kVTCompressionPropertyKey_AllowFrameReordering:配置是否产生B帧,High profile 支持 B


kVTCompressionPropertyKey_MaxKeyFrameIntervalkVTCompressionPropertyKey_MaxKeyFrameIntervalDuration:配置I帧间隔

设置完编码器的属性后,调用VTCompressionSessionPrepareToEncodeFrames准备编码

// 设置码率 512kbps
OSStatus status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(512 * 1024));
// 设置ProfileLevel为BP3.1
status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_3_1);
// 设置实时编码输出(避免延迟)
status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
// 配置是否产生B帧
status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_AllowFrameReordering, self.videoEncodeParam.allowFrameReordering ? kCFBooleanTrue : kCFBooleanFalse);
// 配置最大I帧间隔  15帧 x 240秒 = 3600帧,也就是每隔3600帧编一个I帧
status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(self.videoEncodeParam.frameRate * self.videoEncodeParam.maxKeyFrameInterval));
// 配置I帧持续时间,240秒编一个I帧
status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@(self.videoEncodeParam.maxKeyFrameInterval));
// 编码器准备编码
status = VTCompressionSessionPrepareToEncodeFrames(_compressionSessionRef);
3、输入待编码的视频数据

向编码器输送待编码的视频数据,通过调用VTCompressionSessionEncodeFrame方法实现。

VTCompressionSessionEncodeFrame(
    CM_NONNULL VTCompressionSessionRef  session,
    CM_NONNULL CVImageBufferRef         imageBuffer,
    CMTime                              presentationTimeStamp,
    CMTime                              duration, // may be kCMTimeInvalid
    CM_NULLABLE CFDictionaryRef         frameProperties,
    void * CM_NULLABLE                  sourceFrameRefCon,
    VTEncodeInfoFlags * CM_NULLABLE     infoFlagsOut )

session:创建编码器时的句柄

imageBuffer:YUV数据,iOS通过摄像头采集出来的视频流数据类型是CMSampleBufferRef,要从里面拿到CVImageBufferRef来进行编码。通过CMSampleBufferGetImageBuffer方法可以从sampleBuffer中获得imageBuffer。

presentationTimeStamp:这一帧的时间戳,单位是毫秒

duration:这一帧的持续时间,如果没有持续时间,填kCMTimeInvalid

frameProperties:指定这一帧的属性,这里可以用来指定产生I帧

encodeParams:自定义指针

infoFlagsOut:用于接收编码操作的信息,不需要就置为NULL

// 获取CVImageBufferRef
CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
// 设置是否为I帧
NSDictionary *frameProperties = @{(__bridge NSString *)kVTEncodeFrameOptionKey_ForceKeyFrame: @(forceKeyFrame)};;
// 输入待编码数据
OSStatus status = VTCompressionSessionEncodeFrame(_compressionSessionRef, imageBuffer, kCMTimeInvalid, kCMTimeInvalid, (__bridge CFDictionaryRef)frameProperties, NULL, NULL);
4、获取编码后的数据并进行处理

编码后的数据通过VTCompressionSessionCreate方法设置的回调函数返回。编码后的数据以及这一帧的基本信息都在CMSampleBufferRef中。如果这一帧是个关键帧,那么需要获取SPS和PPS数据,然后给这些数据加个开始码返回出去。

VEVideoEncoder *encoder = (__bridge VEVideoEncoder *)outputCallbackRefCon;
// 开始码
const char header[] = "\x00\x00\x00\x01";
size_t headerLen = (sizeof header) - 1;
NSData *headerData = [NSData dataWithBytes:header length:headerLen];
 
// 判断是否是关键帧
bool isKeyFrame = !CFDictionaryContainsKey((CFDictionaryRef)CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0), (const void *)kCMSampleAttachmentKey_NotSync);
 
if (isKeyFrame)
{
    NSLog(@"VEVideoEncoder::编码了一个关键帧");
    CMFormatDescriptionRef formatDescriptionRef = CMSampleBufferGetFormatDescription(sampleBuffer);
    
    // 关键帧需要加上SPS、PPS信息
    size_t sParameterSetSize, sParameterSetCount;
    const uint8_t *sParameterSet;
    OSStatus spsStatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDescriptionRef, 0, &sParameterSet, &sParameterSetSize, &sParameterSetCount, 0);
    
    size_t pParameterSetSize, pParameterSetCount;
    const uint8_t *pParameterSet;
    OSStatus ppsStatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDescriptionRef, 1, &pParameterSet, &pParameterSetSize, &pParameterSetCount, 0);
    
    if (noErr == spsStatus && noErr == ppsStatus)
    {
        // sps数据加上开始码组成NALU
        NSData *sps = [NSData dataWithBytes:sParameterSet length:sParameterSetSize];
        NSMutableData *spsData = [NSMutableData data];
        [spsData appendData:headerData];
        [spsData appendData:sps];
        // 通过代理回调给上层
        if ([encoder.delegate respondsToSelector:@selector(videoEncodeOutputDataCallback:isKeyFrame:)])
        {
            [encoder.delegate videoEncodeOutputDataCallback:spsData isKeyFrame:isKeyFrame];
        }
        // pps数据加上开始码组成NALU
        NSData *pps = [NSData dataWithBytes:pParameterSet length:pParameterSetSize];
        NSMutableData *ppsData = [NSMutableData data];
        [ppsData appendData:headerData];
        [ppsData appendData:pps];
        
        if ([encoder.delegate respondsToSelector:@selector(videoEncodeOutputDataCallback:isKeyFrame:)])
        {
            [encoder.delegate videoEncodeOutputDataCallback:ppsData isKeyFrame:isKeyFrame];
        }
    }
}
// 获取帧数据
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length, totalLength;
char *dataPointer;
status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &dataPointer);
if (noErr != status)
{
    NSLog(@"VEVideoEncoder::CMBlockBufferGetDataPointer Error : %d!", (int)status);
    return;
}
 
size_t bufferOffset = 0;
static const int avcHeaderLength = 4;
while (bufferOffset < totalLength - avcHeaderLength)
{
    // 读取 NAL 单元长度
    uint32_t nalUnitLength = 0;
    memcpy(&nalUnitLength, dataPointer + bufferOffset, avcHeaderLength);
    
    // 大端转小端
    nalUnitLength = CFSwapInt32BigToHost(nalUnitLength);
    
    NSData *frameData = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + avcHeaderLength) length:nalUnitLength];
    
    NSMutableData *outputFrameData = [NSMutableData data];
    [outputFrameData appendData:headerData];
    [outputFrameData appendData:frameData];
    
    bufferOffset += avcHeaderLength + nalUnitLength;
    
    if ([encoder.delegate respondsToSelector:@selector(videoEncodeOutputDataCallback:isKeyFrame:)])
    {
        [encoder.delegate videoEncodeOutputDataCallback:outputFrameData isKeyFrame:isKeyFrame];
    }
}
5、停止编码
OSStatus status = VTCompressionSessionCompleteFrames(_compressionSessionRef, kCMTimeInvalid);
6、释放编码器
VTCompressionSessionInvalidate(_compressionSessionRef);
CFRelease(_compressionSessionRef);
_compressionSessionRef = NULL;

踩坑及总结

在弄视频编解码的时候,发现720P的分辨率,码率1Mbps,在画面晃动的时候马赛克很严重,码率设置的再低一点更严重。一开始以为是编码器的某些属性漏了设置了,或者是参数设置错了。查阅了很多资料都找不到原因。后来怀疑是ABR模式当画面从静止到晃动码率一下子上不去,导致马赛克,这个假设似乎成立,结果去打印编码出来的码率,画面晃动的时候码率是有上去的,说明这个思路还是不对。后来,发现,摄像头采集的数据是720P,也就是1280x720的分辨率,给编码器设置编码宽高的时候也是按1280x720的宽高设给编码器的,但实际上解码、播放是展示的画面尺寸(像素)只有320x180,于是尝试了一下把编码的宽高设置为320x180,马赛克问题解决了!

iOS视频硬编码技术的更多相关文章

  1. 直播二:iOS中硬编码(VideoToolBox)

    硬编码相对于软编码来说,使用非CPU进行编码,如显卡GPU.专用的DSP.FPGA.ASIC芯片等,性能高,对CPU没有压力,但是对其他硬件要求较高(如GPU等). 在iOS8之后,苹果开放了接口,并 ...

  2. WebRTC 源码分析(三):安卓视频硬编码

    数据怎么送进编码器? 怎么从编码器取数据? 如何做流控? 在开始之前,我们先了解一下 MediaCodec 的基本知识. MediaCodec 基础 Developer 官网 上的描述已经很清楚了,下 ...

  3. iOS音频AAC视频H264编码 推流最佳方案

    iOS音频AAC视频H264编码 推流最佳方案 项目都是个人的调研与实验,可能很多不好或者不对的地方请多包涵. 1    功能概况 *  实现音视频的数据的采集 *  实现音视频数据的编码,视频编码成 ...

  4. 【Android 直播软件开发:音视频硬解码篇】

    开篇 炙手可热,望而生畏的音视频开发 时至今日,短视频App可谓是如日中天,一片兴兴向荣.随着短视频的兴起,音视频开发也越来越受到重视,但是由于音视频开发涉及知识面比较广,入门门槛相对较高,让许许多多 ...

  5. 【GPU编解码】GPU硬编码

    一.OpenCV中的硬编码 OpenCV2.4.6中,已实现利用GPU进行写视频,编码过程由cv::gpu::VideoWriter_GPU完成,其示例程序如下. int main(int argc, ...

  6. 【GPU编解码】GPU硬编码 (转)

    一.OpenCV中的硬编码 OpenCV2.4.6中,已实现利用GPU进行写视频,编码过程由cv::gpu::VideoWriter_GPU完成,其示例程序如下. 1 int main(int arg ...

  7. 【计算机视觉】【并行计算与CUDA开发】GPU硬编码

    一.OpenCV中的硬编码 OpenCV2.4.6中,已实现利用GPU进行写视频,编码过程由cv::gpu::VideoWriter_GPU完成,其示例程序如下. 1 int main(int arg ...

  8. Android硬编码——音频编码、视频编码及音视频混合

    视频编解码对许多Android程序员来说都是Android中比较难的一个知识点.在Android 4.1以前,Android并没有提供硬编硬解的API,所以之前基本上都是采用FFMpeg来做视频软件编 ...

  9. 直播平台搭建之音视频开发:认识主流视频编码技术H.264

    H.264简介 什么是H.264?H.264是一种高性能的视频编解码技术.目前国际上制定视频编解码技术的组织有两个,一个是"国际电联",它制定的标准有H.261.H.263.H.2 ...

随机推荐

  1. ubuntu 缺少动态依赖库

    起因 困扰我好久的一个报错,终于解决了 之前我一直以为是 python代码的问题,以为是模块相互调引起的报错,忽略了最后一行这个错误 OSError: libGCBase_gcc421_v3_0.so ...

  2. 编译Android内核 For nexus 5 以及绕过Android的反调试

    本文博客链接:http://blog.csdn.net/qq1084283172/article/details/54880488 前面的博客中已经记录了Nexus 5手机的Android 4.4.4 ...

  3. Win64 驱动内核编程-5.内核里操作文件

    内核里操作文件 RING0 操作文件和 RING3 操作文件在流程上没什么大的区别,也是"获得文件句柄->读/写/删/改->关闭文件句柄"的模式.当然了,只能用内核 A ...

  4. 16.PHP_Ajax模拟服务器登录验证

    Ajax模拟登陆验证 index.php <script language="javascript">    var http_request = false;     ...

  5. 『动善时』JMeter基础 — 8、JMeter主要元件介绍

    目录 1.测试计划(Test Plan) 2.线程组 3.取样器(sampler) 4.逻辑控制器(Logic Controller) 5.配置元件(Config Element) 6.定时器(Tim ...

  6. springmvc Date类型转换

    有时候我们会碰到这么一个问题,有一个实体类,里面有一个Date类型的数据,jsp页面传递的时间参数是String的,这就导致无法对应,springmvc无法帮我们自动封装参数到实体类中了,这里我解决的 ...

  7. 容器随Docker启动而启动

    在容器开启状态下 docker container update --restart=always 容器名

  8. @shiro.hasPermission 使用

    在页面上加上@shiro.hasPermission 如下用.ftl为例子: 当加上shiro标签后,会与后台代码结合使用: 需要继承AuthorizingRealm  下的 protected Au ...

  9. HashMap方法源码分析

    本文将分析put(),resize(),get()和remove()方法的源码 putval()方法 大致步骤:计算key的hash值:根据hash值计算数组下标:判断下标处是否有节点,无节点则直接插 ...

  10. [bug] Hive启动报错:hive Name node is in safe mode.

    关闭HDFS安全模式 hadoop dfsadmin -safemode leave