CC_VideoToolBoxLearning_h264
有部分是看视频写的。。。。。
//
// ViewController.m
// CC_VideoToolBoxLearning_1
//
// Created by CC老师 on 2017/6/26.
// Copyright © 2017年 Miss CC. All rights reserved.
//
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>
@interface ViewController ()<AVCaptureVideoDataOutputSampleBufferDelegate>
@property (nonatomic,strong)UILabel *cLabel;
@property (nonatomic, strong)AVCaptureSession *cCapturesession;// 捕捉会话
@property (nonatomic, strong)AVCaptureDeviceInput *cCaptureDeviceInput; // 捕捉输入
@property (nonatomic, strong)AVCaptureDeviceInput *activeVideoInput; // 捕捉输入
@property (nonatomic, strong)AVCaptureVideoDataOutput *cCaptureDataOutput; // 捕捉输出
@property (nonatomic, strong)AVCaptureVideoPreviewLayer *cPreviewLayer;
@end
@implementation ViewController
{
int frameID;//帧id,每张图片id
dispatch_queue_t cCaptureQueue; //捕捉队列
dispatch_queue_t cEncodeQueue;//编码队列
VTCompressionSessionRef cEncodeingSession;
CMFormatDescriptionRef format;//编码格式
NSFileHandle *fileHandele; //文件句柄 追加 删除
// NSFileManager // 文件创建,管理,移动,复制,删除
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//基础UI实现
_cLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 100)];
_cLabel.text = @"H.264硬编码";
_cLabel.textColor = [UIColor redColor];
[self.view addSubview:_cLabel];
UIButton *cButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 20, 100, 100)];
[cButton setTitle:@"play" forState:UIControlStateNormal];
[cButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[cButton setBackgroundColor:[UIColor orangeColor]];
[cButton addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:cButton];
}
-(void)buttonClick:(UIButton *)button
{
if (!_cCapturesession || !_cCapturesession.isRunning)
{
[button setTitle:@"Stop" forState:UIControlStateNormal];
[self startCapture];
}else{
[button setTitle:@"Play" forState:UIControlStateNormal];
[self stopCapture];
}
}
//开始捕捉
- (void)startCapture
{
//配置捕捉会话
self.cCapturesession = [[AVCaptureSession alloc] init];
//设置捕捉分辨率
self.cCapturesession.sessionPreset = AVCaptureSessionPreset640x480;
cCaptureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
cEncodeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
AVCaptureDevice *inputCamera = nil;
// 拿到默认的捕捉设备
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices)
{
if ([device position] == AVCaptureDevicePositionBack)
{
inputCamera = device;
}
}
self.cCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:nil];
if ([self.cCapturesession canAddInput:self.cCaptureDeviceInput])
{
[self.cCapturesession addInput:self.cCaptureDeviceInput];
self.activeVideoInput = self.cCaptureDeviceInput;
}
// AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
self.cCaptureDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[self.cCaptureDataOutput setAlwaysDiscardsLateVideoFrames:NO];
[self.cCaptureDataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
[self.cCaptureDataOutput setSampleBufferDelegate:self queue:cCaptureQueue];
if ([self.cCapturesession canAddOutput:self.cCaptureDataOutput])
{
[self.cCapturesession addOutput:self.cCaptureDataOutput];
}
AVCaptureConnection *connection = [self.cCaptureDataOutput connectionWithMediaType:AVMediaTypeVideo];
[connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
self.cPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.cCapturesession];
[self.cPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
[self.cPreviewLayer setFrame:CGRectMake(100, 80, 200, 200)];
[self.view.layer addSublayer:self.cPreviewLayer];
NSString *filePath = [NSHomeDirectory() stringByAppendingString:@"/Documents/cc_video.h264"];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
BOOL createFile = [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
if (!createFile)
{
NSLog(@"create file failed");
}else{
NSLog(@"create file success");
}
NSLog(@"filePaht = %@",filePath);
fileHandele = [NSFileHandle fileHandleForWritingAtPath:filePath];
[self initVideoToolBox];
[self.cCapturesession startRunning];
}
//停止捕捉
- (void)stopCapture
{
[self.cCapturesession stopRunning];
[self.cPreviewLayer removeFromSuperlayer];
[self endVideoToolBox];
[fileHandele closeFile];
fileHandele = NULL;
}
#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate //音频 视频 捕捉数据
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
dispatch_sync(cEncodeQueue, ^{
[self encode:sampleBuffer];
});
}
//初始化videoToolBox
-(void)initVideoToolBox
{
dispatch_sync(cEncodeQueue, ^{
frameID = 0;
int width = 480,height = 640;
OSStatus status = VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)(self), &cEncodeingSession);
NSLog(@"h264:VTCompressionSessionCreate:%d",(int)status);
if (status != 0)
{
NSLog(@"h264:unable to create a h264 session");
return;
}
//设置实时编码输出
VTSessionSetProperty(cEncodeingSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
VTSessionSetProperty(cEncodeingSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
//设置关键帧
int frameInterval = 10;
CFNumberRef frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval);
VTSessionSetProperty(cEncodeingSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, frameIntervalRef);
//设置期望帧率 不是实际帧率
int fps = 10;
CFNumberRef fpsref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fps);
VTSessionSetProperty(cEncodeingSession, kVTCompressionPropertyKey_ExpectedFrameRate, fpsref);
//设置码率,上限,单位是bps
int bitrate = width*height*3*4*8;
CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitrate);
VTSessionSetProperty(cEncodeingSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
//设置码率,均值,单位是byte
int bigRateLimit = width*height*3*4;
CFNumberRef bitRateLimitRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bigRateLimit);
VTSessionSetProperty(cEncodeingSession, kVTCompressionPropertyKey_DataRateLimits, bitRateLimitRef);
// 开始编码
VTCompressionSessionPrepareToEncodeFrames(cEncodeingSession);
});
}
//编码
- (void) encode:(CMSampleBufferRef )sampleBuffer
{
//拿到第一帧未编码数据
CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
CMTime pTime = CMTimeMake(frameID++, 1000);
VTEncodeInfoFlags flags = 0;
OSStatus statusCode = VTCompressionSessionEncodeFrame(cEncodeingSession, imageBuffer, pTime, kCMTimeInvalid, NULL, NULL, &flags);
if (statusCode != noErr)
{
VTCompressionSessionInvalidate(cEncodeingSession);
CFRelease(cEncodeingSession);
cEncodeingSession = NULL;
return;
}
}
//编码完成回调
void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer)
{
NSLog(@"didCompressH264 called with status %d infoFlags %d",(int)status,(int)(infoFlags));
//状态错误
if (status != 0)
{
return;
}
//没准备好
if (!CMSampleBufferDataIsReady(sampleBuffer))
{
NSLog(@"didCompressh264 data is not ready");
return;
}
ViewController *encoder = (__bridge ViewController *)outputCallbackRefCon;
//判断当前帧是否关键帧
bool keyFrame = !CFDictionaryContainsKey((CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true),0)), kCMSampleAttachmentKey_NotSync);
if (keyFrame)
{
CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
size_t spsSize,spsCount;
const uint8_t *spsSet;
OSStatus status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &spsSet, &spsSize, &spsCount, 0);
if (status == noErr)
{
size_t ppsSize,ppsCount;
const uint8_t *ppsSet;
OSStatus ppsstatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &ppsSet, &ppsSize, &ppsCount, 0);
if (ppsstatus == noErr)
{
NSData *sps = [NSData dataWithBytes:spsSet length:spsSize];
NSData *pps = [NSData dataWithBytes:ppsSet length:ppsSize];
if (encoder)
{
[encoder gotSpsPps:sps pps:pps];
}
}
}
}
CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length,totoalLength ;
char *dataPointer;
OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totoalLength, &dataPointer);
if (statusCodeRet == noErr) {
size_t bufferOffset = 0;
static const int AVL = 4;
while (bufferOffset < totoalLength - AVL) {
uint32_t NAL = 0;
memcpy(&NAL, dataPointer + bufferOffset, AVL);
NAL = CFSwapInt32BigToHost(NAL);
NSData *data = [[NSData alloc] initWithBytes:dataPointer + bufferOffset +AVL length:NAL];
[encoder gotEncodedData:data isKeyFrame:keyFrame];
bufferOffset += AVL +NAL;
}
}
}
- (void)gotSpsPps:(NSData*)sps pps:(NSData*)pps
{
const char bytes[] = "\x00\x00\x00\x01";
//长度
size_t length = (sizeof bytes) -1;
//头字节
NSData *byteHeader = [NSData dataWithBytes:bytes length:length];
//写入文件
[fileHandele writeData:byteHeader];
//写入h264数据
[fileHandele writeData:sps];
//写入文件
[fileHandele writeData:byteHeader];
//写入h264数据
[fileHandele writeData:pps];
}
- (void)gotEncodedData:(NSData*)data isKeyFrame:(BOOL)isKeyFrame
{
NSLog(@"gotEncodeData:%d",(int)[data length]);
if (fileHandele != NULL)
{
const char bytes[] = "\x00\x00\x00\x01";
//长度
size_t length = (sizeof bytes) -1;
//头字节
NSData *byteHeader = [NSData dataWithBytes:bytes length:length];
//写入文件
[fileHandele writeData:byteHeader];
//写入h264数据
[fileHandele writeData:data];
}
}
//结束VideoToolBox
-(void)endVideoToolBox
{
VTCompressionSessionCompleteFrames(cEncodeingSession, kCMTimeInvalid);
VTCompressionSessionInvalidate(cEncodeingSession);
CFRelease(cEncodeingSession);
cEncodeingSession = NULL;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end