123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- //
- // SLAvWriterInput.m
- // DarkMode
- //
- // Created by wsl on 2019/11/7.
- // Copyright © 2019 https://github.com/wsl2ls ----- All rights reserved.
- //
- #import "SLAvWriterInput.h"
- @interface SLAvWriterInput ()
- @property (nonatomic, strong) AVAssetWriter *assetWriter; //音视频数据流文件写入
- @property (nonatomic, strong) AVAssetWriterInput *assetWriterVideoInput; //写入视频文件
- @property (nonatomic, strong) AVAssetWriterInput *assetWriterAudioInput; //写入音频文件
- @property (nonatomic, strong) NSDictionary *videoCompressionSettings; //视频写入配置
- @property (nonatomic, strong) NSDictionary *audioCompressionSettings; //音频写入配置
- @property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor* inputPixelBufferAdptor; //输入像素缓冲器 把缓冲样本滤镜处理之后再写入
- @property (nonatomic, assign) CMTime currentSampleTime; // 当前采样时间
- @property (nonatomic, assign) CMVideoDimensions currentVideoDimensions; //尺寸
- @property (nonatomic, strong) CIContext* context;
- @property (nonatomic, assign) BOOL isStartWriting; //是否开始写入
- @property (nonatomic, copy) NSURL *outputFileURL; //音视频文件输出路径
- @property (nonatomic, assign) SLAvWriterFileType outputFileType; //写入输出文件类型 默认 SLAvWriterFileTypeVideo
- @end
- @implementation SLAvWriterInput
- - (instancetype)init {
- self = [super init];
- if (self) {
- self.videoSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
- }
- return self;
- }
- #pragma mark - Setter
- - (void)setOutputFileURL:(NSURL *)outputFileURL {
- _outputFileURL = outputFileURL;
- if (self.outputFileType == SLAvWriterFileTypeAudio) {
- _assetWriter = [AVAssetWriter assetWriterWithURL:outputFileURL fileType:AVFileTypeAC3 error:nil];
- } else if (self.outputFileType == SLAvWriterFileTypeVideo || self.outputFileType == SLAvWriterFileTypeSilentVideo) {
- _assetWriter = [AVAssetWriter assetWriterWithURL:outputFileURL fileType:AVFileTypeMPEG4 error:nil];
- }
- }
- #pragma mark - Getter
- - (AVAssetWriterInput *)assetWriterVideoInput {
- if (!_assetWriterVideoInput) {
- //写入视频大小
- NSInteger numPixels = self.videoSize.width * [UIScreen mainScreen].scale * self.videoSize.height * [UIScreen mainScreen].scale;
- //每像素比特
- CGFloat bitsPerPixel = 24.0;
- NSInteger bitsPerSecond = numPixels * bitsPerPixel;
- // 码率和帧率设置
- NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(bitsPerSecond),
- AVVideoExpectedSourceFrameRateKey : @(15),
- AVVideoMaxKeyFrameIntervalKey : @(15),
- AVVideoProfileLevelKey : AVVideoProfileLevelH264High40 };
- CGFloat width = self.videoSize.width * [UIScreen mainScreen].scale;
- CGFloat height = self.videoSize.height * [UIScreen mainScreen].scale;
- //视频属性
- self.videoCompressionSettings = @{ AVVideoCodecKey : AVVideoCodecH264,
- AVVideoWidthKey : @(width),
- AVVideoHeightKey : @(height),
- AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill,
- AVVideoCompressionPropertiesKey : compressionProperties };
-
- _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoCompressionSettings];
- //expectsMediaDataInRealTime 必须设为yes,需要从capture session 实时获取数据
- _assetWriterVideoInput.expectsMediaDataInRealTime = YES;
- }
- return _assetWriterVideoInput;
- }
- - (AVAssetWriterInput *)assetWriterAudioInput {
- if (_assetWriterAudioInput == nil) {
- /* 注:
- <1>AVNumberOfChannelsKey 通道数 1为单通道 2为立体通道
- <2>AVSampleRateKey 采样率 取值为 8000/44100/96000 影响音频采集的质量
- <3>d 比特率(音频码率) 取值为 8 16 24 32
- <4>AVEncoderAudioQualityKey 质量 (需要iphone8以上手机)
- <5>AVEncoderBitRateKey 比特采样率 一般是128000
- */
-
- /*另注:aac的音频采样率不支持96000,当我设置成8000时,assetWriter也是报错*/
- // 音频设置
- _audioCompressionSettings = @{ AVEncoderBitRatePerChannelKey : @(28000),
- AVFormatIDKey : @(kAudioFormatMPEG4AAC),
- AVNumberOfChannelsKey : @(1),
- AVSampleRateKey : @(22050) };
-
- _assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioCompressionSettings];
- _assetWriterAudioInput.expectsMediaDataInRealTime = YES;
- }
- return _assetWriterAudioInput;
- }
- - (AVAssetWriterInputPixelBufferAdaptor *)inputPixelBufferAdptor {
- if (!_inputPixelBufferAdptor) {
- NSDictionary* sourcePixelBufferAttributesDictionary =
- @{
- (NSString*)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_32BGRA),
- (NSString*)kCVPixelBufferWidthKey:@(self.currentVideoDimensions.width),
- (NSString*)kCVPixelBufferHeightKey:@(self.currentVideoDimensions.height),
- (NSString*)kCVPixelFormatOpenGLESCompatibility:@(1)
- };
- _inputPixelBufferAdptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.assetWriterVideoInput sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];
- }
- return _inputPixelBufferAdptor;
- }
- -(CIContext *)context{
- // default creates a context based on GPU
- if (_context == nil) {
- _context = [CIContext contextWithOptions:nil];
- }
- return _context;
- }
- #pragma mark - HelpMethods
- // 开始准备写入 配置写入的输出文件地址和格式 每次开始写入之前都要调用一次
- - (void)startWritingToOutputFileAtPath:(NSString *)path fileType:(SLAvWriterFileType)fileType deviceOrientation:(UIDeviceOrientation)deviceOrientation{
- self.outputFileType = fileType;
- //移除相同文件,否则无法写入
- if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
- [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
- }
- self.outputFileURL = [NSURL fileURLWithPath:path];
-
- //调整写入时的方向
- if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
- self.assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI/2);
- } else if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
- self.assetWriterVideoInput.transform = CGAffineTransformMakeRotation(-M_PI/2);
- } else if (deviceOrientation == UIDeviceOrientationPortraitUpsideDown) {
- self.assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI);
- } else {
- self.assetWriterVideoInput.transform = CGAffineTransformMakeRotation(0);
- }
-
- if (self.outputFileType == SLAvWriterFileTypeSilentVideo || self.outputFileType == SLAvWriterFileTypeVideo) {
- if ([self.assetWriter canAddInput:self.assetWriterVideoInput]) {
- [self.assetWriter addInput:self.assetWriterVideoInput];
- } else {
- NSLog(@"视频写入失败");
- }
- }
- if (self.outputFileType == SLAvWriterFileTypeAudio || self.outputFileType == SLAvWriterFileTypeVideo){
- if ([self.assetWriter canAddInput:self.assetWriterAudioInput] ) {
- [self.assetWriter addInput:self.assetWriterAudioInput];
- } else {
- NSLog(@"音频写入失败");
- }
- }
- }
- /// 实时写入视频样本 如果需要给视频加滤镜,就传入filterImage ,如果filterImage == nil,就表示不需要加滤镜
- - (void)writingVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection filterImage:(CIImage *)filterImage {
- if (filterImage) {
- //如果需要滤镜处理
- @autoreleasepool {
- @synchronized(self) {
- CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
- //样本尺寸
- self.currentVideoDimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
- // 当前采样时间
- self.currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer);
- //开始写入之前必须初始化
- _inputPixelBufferAdptor = self.inputPixelBufferAdptor;
- if (!self.isStartWriting && (self.outputFileType == SLAvWriterFileTypeVideo || self.outputFileType == SLAvWriterFileTypeSilentVideo)) {
- //一个写入周期内只能执行一次
- [self.assetWriter startWriting];
- [self.assetWriter startSessionAtSourceTime:self.currentSampleTime];
- self.isStartWriting = YES;
- }
- if (self.inputPixelBufferAdptor.assetWriterInput.isReadyForMoreMediaData && self.isStartWriting) {
- CVPixelBufferRef newPixelBuffer = NULL;
- CVPixelBufferPoolCreatePixelBuffer(NULL, self.inputPixelBufferAdptor.pixelBufferPool, &newPixelBuffer);
- [self.context render:filterImage toCVPixelBuffer:newPixelBuffer bounds:filterImage.extent colorSpace:filterImage.colorSpace];
- if (newPixelBuffer) {
- if (self.assetWriter.status == AVAssetWriterStatusWriting) {
- BOOL success = [self.inputPixelBufferAdptor appendPixelBuffer:newPixelBuffer withPresentationTime:self.currentSampleTime];
- if (!success) {
- NSLog(@"视频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription);
- [self finishWriting];
- }
- }
- CFRelease(newPixelBuffer);
- }else{
- NSLog(@"newPixelBuffer is nil");
- }
- }
- }
- }
-
- }else {
- @autoreleasepool {
- @synchronized(self) {
- if (!self.isStartWriting && (self.outputFileType == SLAvWriterFileTypeVideo || self.outputFileType == SLAvWriterFileTypeSilentVideo)) {
- //一个写入周期内只能执行一次
- [self.assetWriter startWriting];
- [self.assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
- self.isStartWriting = YES;
- }
- //写入视频数据
- if (self.assetWriterVideoInput.readyForMoreMediaData && self.isStartWriting) {
- BOOL success = [self.assetWriterVideoInput appendSampleBuffer:sampleBuffer];
- if (!success) {
- @synchronized (self) {
- NSLog(@"视频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription);
- [self finishWriting];
- }
- }
- }
- }
- }
- }
-
- }
- //实时写入输出的音频
- - (void)writingAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
- @autoreleasepool {
- @synchronized(self) {
- if (!self.isStartWriting && self.outputFileType == SLAvWriterFileTypeAudio) {
- [self.assetWriter startWriting];
- [self.assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
- self.isStartWriting = YES;
- }
- if (self.assetWriterAudioInput.readyForMoreMediaData && self.isStartWriting == YES) {
- //写入音频数据
- BOOL success = [self.assetWriterAudioInput appendSampleBuffer:sampleBuffer];
- if (!success) {
- @synchronized (self) {
- NSLog(@"音频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription);
- [self finishWriting];
- }
- }
- }
- }
- }
- }
- /// 完成写入
- - (void)finishWriting {
- __weak typeof(self) weakSelf = self;
- if(_assetWriter && self.isStartWriting && self.assetWriter.status != AVAssetWriterStatusUnknown) {
- [_assetWriter finishWritingWithCompletionHandler:^{
- if ([weakSelf.delegate respondsToSelector:@selector(writerInput:didFinishRecordingToOutputFileAtURL:error:)]) {
- SL_DISPATCH_ON_MAIN_THREAD(^{
- [weakSelf.delegate writerInput:weakSelf didFinishRecordingToOutputFileAtURL:weakSelf.outputFileURL error:weakSelf.assetWriter.error];
- });
- }
- weakSelf.isStartWriting = NO;
- weakSelf.inputPixelBufferAdptor = nil;
- weakSelf.context = nil;
- weakSelf.assetWriter = nil;
- weakSelf.assetWriterAudioInput = nil;
- weakSelf.assetWriterVideoInput = nil;
- }];
- }
- }
- @end
|