iOS视频硬编码技术
一.iOS视频采集硬编码
基本原理
硬编码 & 软编码
硬编码:通过系统自带的Camera录制视频,实际上调用的是底层的高清编码硬件模块,即显卡,不使用CPU,速度快
软编码:使用CPU进行编码,如常见C/C // [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];
}
  1. 添加视频预览层
    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;
    }

• 实现代理 – AVCaptureVideoDataOutputSampleBufferDelegat

  • (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;
    }
  1. 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为默认分配器
width、height:视频帧像素的宽高,如果编码器不支持这个宽高的话可能会改变
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_MaxKeyFrameInterval、kVTCompressionPropertyKey_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数据加上开始码组成NALUNSData *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数据加上开始码组成NALUNSData *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. WebRTC IOS视频硬编码流程及其中传递的CVPixelBufferRef

    WebRTC中默认摄像头采集: RTCCameraVideoCapturer: src/sdk/objc/components/capturer/RTCCameraVideoCapturer.m- ( ...

  2. 香橙派Pi5基于Qt5视频硬编码编译

    香橙派Pi5视频通过Qt5硬编码编译 文章目录 香橙派Pi5视频通过Qt5硬编码编译 前言 一.RKMpp和RKMpi是什么? 二.编译RKMPP 1.下载地址 2.文件结构 3.开始编译 4.编译M ...

  3. iOS h264硬编码

    从这里抄过来的:https://github.com/LevyGG/iOS-H.264-hareware-encode-and-decode/blob/master/VTDemoOniPad/H264 ...

  4. 大厂卡你的学历,究竟是为了什么?,android开发视频硬编码

    因此Android工程师的养成,一部分依然靠纯自学,大部分则靠培训,无论「培训」背负了多少的骂名,但整个Android行业,二三十万的从业者,抛开少部分大学计算机科班和软件工程相关专业的,绝大多数都是 ...

  5. ios视频硬解异常总结,12911总结

    废话少说,直接上结果: VTDecompressionSessionCreate: -12911 原因总结: 创建session时,就是VTDecompressionSessionCreate函数: ...

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

    1    功能概况 *  实现音视频的数据的采集 *  实现音视频数据的编码,视频编码成h264,音频编码成aac *  实现音视频数据的发布,将编码好的音视频数据传输到服务器 2 视频和音频编码方案 ...

  7. 视频通信关键技术探索及实践

    导读:2021年10月21日,「QCon 全球软件开发大会」在上海举办,网易智企技术 VP 陈功作为出品人发起了「AI 时代下的融合通信技术」专场,邀请到多位技术专家与大家一起分享相关技术话题. 我们 ...

  8. LiveVideoStack线上分享第五季(十三):高性能视频硬件编码

    面向4G/5G场景下视频业务的爆发以及用户对于更高画质的要求(直播.短视频.视频点播等业务),更高清的画质意味着需要更大的计算资源以及网络带宽资源,而海量用户侧的画质提升/宽带降低,会导致前端以及视频 ...

  9. Android音频AAC硬编码

    Android音视频编码分为软编和硬编两种.所谓的硬编是用设备GPU去实现编解码,从而减轻CPU的压力,让程序更加的健壮,自然而然你就知道了软编其实就是让CPU编码(其实是在c层通过c/c++进行编码 ...

最新文章

  1. python语言入门编程猫-编程猫推出海龟编程器,打造Python教育产品矩阵
  2. linux之用户态和内核态
  3. 深入浅出python机器学习_9.1_数据预处理_sklearn.preprocessing.StandardScaler MinMaxScaler RobustScaler Normalizer
  4. mysql009模糊查询like.是否为null
  5. 嵌入式OS入门笔记-以RTX为案例:三.初探进程
  6. CSShack,CSS hack
  7. Linux-3.2.0.24中内核的Netlink测试使用
  8. python改错题重要的事情说三遍_5个很好的Python面试题问题答案及分析
  9. linux制作img镜像文件,制作img镜像文件的5种方法 .
  10. 物联那点事儿之自制网络温湿度计(arduino+点灯科技篇)
  11. TSINGSEE青犀视频开发人脸识别技术实现过程中的的难点汇总
  12. zigbee网关 CC2530 zstack用手机显示终端传来的lm75a温度传感器的值
  13. DB2的VALUE函数
  14. #C语言学习笔记#猴子偷桃问题
  15. 统计推断(一) Hypothesis Test
  16. 救救我吧,今年27岁,想转行学大数据开发
  17. 激光雷达在自动驾驶中的关键技术应用
  18. VSS2005安装指南
  19. 软件测试:Alpha测试与beta测试区别
  20. C/C++段错误问题排查和解决方法

热门文章

  1. mysql6.0设置时区_关于Mysql6.0+的时区错乱问题
  2. pyspark性能调优参数
  3. linux模拟器 cygwin源
  4. 文件读取输出-python
  5. 汇编语言中栈及栈操作的实现
  6. LeetCode简单题之换酒问题
  7. LeetCode简单题之词典中最长的单词
  8. [JavaScript] JavaScript 数组挖掘,不只是讲数组哟
  9. java处理中文字符_Java中文字符处理的四大迷题
  10. AtomicBoolean 的使用与介绍