SLAvWriterInput.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. //
  2. // SLAvWriterInput.m
  3. // DarkMode
  4. //
  5. // Created by wsl on 2019/11/7.
  6. // Copyright © 2019 https://github.com/wsl2ls ----- All rights reserved.
  7. //
  8. #import "SLAvWriterInput.h"
  9. @interface SLAvWriterInput ()
  10. @property (nonatomic, strong) AVAssetWriter *assetWriter; //音视频数据流文件写入
  11. @property (nonatomic, strong) AVAssetWriterInput *assetWriterVideoInput; //写入视频文件
  12. @property (nonatomic, strong) AVAssetWriterInput *assetWriterAudioInput; //写入音频文件
  13. @property (nonatomic, strong) NSDictionary *videoCompressionSettings; //视频写入配置
  14. @property (nonatomic, strong) NSDictionary *audioCompressionSettings; //音频写入配置
  15. @property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor* inputPixelBufferAdptor; //输入像素缓冲器 把缓冲样本滤镜处理之后再写入
  16. @property (nonatomic, assign) CMTime currentSampleTime; // 当前采样时间
  17. @property (nonatomic, assign) CMVideoDimensions currentVideoDimensions; //尺寸
  18. @property (nonatomic, strong) CIContext* context;
  19. @property (nonatomic, assign) BOOL isStartWriting; //是否开始写入
  20. @property (nonatomic, copy) NSURL *outputFileURL; //音视频文件输出路径
  21. @property (nonatomic, assign) SLAvWriterFileType outputFileType; //写入输出文件类型 默认 SLAvWriterFileTypeVideo
  22. @end
  23. @implementation SLAvWriterInput
  24. - (instancetype)init {
  25. self = [super init];
  26. if (self) {
  27. self.videoSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
  28. }
  29. return self;
  30. }
  31. #pragma mark - Setter
  32. - (void)setOutputFileURL:(NSURL *)outputFileURL {
  33. _outputFileURL = outputFileURL;
  34. if (self.outputFileType == SLAvWriterFileTypeAudio) {
  35. _assetWriter = [AVAssetWriter assetWriterWithURL:outputFileURL fileType:AVFileTypeAC3 error:nil];
  36. } else if (self.outputFileType == SLAvWriterFileTypeVideo || self.outputFileType == SLAvWriterFileTypeSilentVideo) {
  37. _assetWriter = [AVAssetWriter assetWriterWithURL:outputFileURL fileType:AVFileTypeMPEG4 error:nil];
  38. }
  39. }
  40. #pragma mark - Getter
  41. - (AVAssetWriterInput *)assetWriterVideoInput {
  42. if (!_assetWriterVideoInput) {
  43. //写入视频大小
  44. NSInteger numPixels = self.videoSize.width * [UIScreen mainScreen].scale * self.videoSize.height * [UIScreen mainScreen].scale;
  45. //每像素比特
  46. CGFloat bitsPerPixel = 24.0;
  47. NSInteger bitsPerSecond = numPixels * bitsPerPixel;
  48. // 码率和帧率设置
  49. NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(bitsPerSecond),
  50. AVVideoExpectedSourceFrameRateKey : @(15),
  51. AVVideoMaxKeyFrameIntervalKey : @(15),
  52. AVVideoProfileLevelKey : AVVideoProfileLevelH264High40 };
  53. CGFloat width = self.videoSize.width * [UIScreen mainScreen].scale;
  54. CGFloat height = self.videoSize.height * [UIScreen mainScreen].scale;
  55. //视频属性
  56. self.videoCompressionSettings = @{ AVVideoCodecKey : AVVideoCodecH264,
  57. AVVideoWidthKey : @(width),
  58. AVVideoHeightKey : @(height),
  59. AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill,
  60. AVVideoCompressionPropertiesKey : compressionProperties };
  61. _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoCompressionSettings];
  62. //expectsMediaDataInRealTime 必须设为yes,需要从capture session 实时获取数据
  63. _assetWriterVideoInput.expectsMediaDataInRealTime = YES;
  64. }
  65. return _assetWriterVideoInput;
  66. }
  67. - (AVAssetWriterInput *)assetWriterAudioInput {
  68. if (_assetWriterAudioInput == nil) {
  69. /* 注:
  70. <1>AVNumberOfChannelsKey 通道数 1为单通道 2为立体通道
  71. <2>AVSampleRateKey 采样率 取值为 8000/44100/96000 影响音频采集的质量
  72. <3>d 比特率(音频码率) 取值为 8 16 24 32
  73. <4>AVEncoderAudioQualityKey 质量 (需要iphone8以上手机)
  74. <5>AVEncoderBitRateKey 比特采样率 一般是128000
  75. */
  76. /*另注:aac的音频采样率不支持96000,当我设置成8000时,assetWriter也是报错*/
  77. // 音频设置
  78. _audioCompressionSettings = @{ AVEncoderBitRatePerChannelKey : @(28000),
  79. AVFormatIDKey : @(kAudioFormatMPEG4AAC),
  80. AVNumberOfChannelsKey : @(1),
  81. AVSampleRateKey : @(22050) };
  82. _assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioCompressionSettings];
  83. _assetWriterAudioInput.expectsMediaDataInRealTime = YES;
  84. }
  85. return _assetWriterAudioInput;
  86. }
  87. - (AVAssetWriterInputPixelBufferAdaptor *)inputPixelBufferAdptor {
  88. if (!_inputPixelBufferAdptor) {
  89. NSDictionary* sourcePixelBufferAttributesDictionary =
  90. @{
  91. (NSString*)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_32BGRA),
  92. (NSString*)kCVPixelBufferWidthKey:@(self.currentVideoDimensions.width),
  93. (NSString*)kCVPixelBufferHeightKey:@(self.currentVideoDimensions.height),
  94. (NSString*)kCVPixelFormatOpenGLESCompatibility:@(1)
  95. };
  96. _inputPixelBufferAdptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.assetWriterVideoInput sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];
  97. }
  98. return _inputPixelBufferAdptor;
  99. }
  100. -(CIContext *)context{
  101. // default creates a context based on GPU
  102. if (_context == nil) {
  103. _context = [CIContext contextWithOptions:nil];
  104. }
  105. return _context;
  106. }
  107. #pragma mark - HelpMethods
  108. // 开始准备写入 配置写入的输出文件地址和格式 每次开始写入之前都要调用一次
  109. - (void)startWritingToOutputFileAtPath:(NSString *)path fileType:(SLAvWriterFileType)fileType deviceOrientation:(UIDeviceOrientation)deviceOrientation{
  110. self.outputFileType = fileType;
  111. //移除相同文件,否则无法写入
  112. if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
  113. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  114. }
  115. self.outputFileURL = [NSURL fileURLWithPath:path];
  116. //调整写入时的方向
  117. if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
  118. self.assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI/2);
  119. } else if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
  120. self.assetWriterVideoInput.transform = CGAffineTransformMakeRotation(-M_PI/2);
  121. } else if (deviceOrientation == UIDeviceOrientationPortraitUpsideDown) {
  122. self.assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI);
  123. } else {
  124. self.assetWriterVideoInput.transform = CGAffineTransformMakeRotation(0);
  125. }
  126. if (self.outputFileType == SLAvWriterFileTypeSilentVideo || self.outputFileType == SLAvWriterFileTypeVideo) {
  127. if ([self.assetWriter canAddInput:self.assetWriterVideoInput]) {
  128. [self.assetWriter addInput:self.assetWriterVideoInput];
  129. } else {
  130. NSLog(@"视频写入失败");
  131. }
  132. }
  133. if (self.outputFileType == SLAvWriterFileTypeAudio || self.outputFileType == SLAvWriterFileTypeVideo){
  134. if ([self.assetWriter canAddInput:self.assetWriterAudioInput] ) {
  135. [self.assetWriter addInput:self.assetWriterAudioInput];
  136. } else {
  137. NSLog(@"音频写入失败");
  138. }
  139. }
  140. }
  141. /// 实时写入视频样本 如果需要给视频加滤镜,就传入filterImage ,如果filterImage == nil,就表示不需要加滤镜
  142. - (void)writingVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection filterImage:(CIImage *)filterImage {
  143. if (filterImage) {
  144. //如果需要滤镜处理
  145. @autoreleasepool {
  146. @synchronized(self) {
  147. CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
  148. //样本尺寸
  149. self.currentVideoDimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
  150. // 当前采样时间
  151. self.currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer);
  152. //开始写入之前必须初始化
  153. _inputPixelBufferAdptor = self.inputPixelBufferAdptor;
  154. if (!self.isStartWriting && (self.outputFileType == SLAvWriterFileTypeVideo || self.outputFileType == SLAvWriterFileTypeSilentVideo)) {
  155. //一个写入周期内只能执行一次
  156. [self.assetWriter startWriting];
  157. [self.assetWriter startSessionAtSourceTime:self.currentSampleTime];
  158. self.isStartWriting = YES;
  159. }
  160. if (self.inputPixelBufferAdptor.assetWriterInput.isReadyForMoreMediaData && self.isStartWriting) {
  161. CVPixelBufferRef newPixelBuffer = NULL;
  162. CVPixelBufferPoolCreatePixelBuffer(NULL, self.inputPixelBufferAdptor.pixelBufferPool, &newPixelBuffer);
  163. [self.context render:filterImage toCVPixelBuffer:newPixelBuffer bounds:filterImage.extent colorSpace:filterImage.colorSpace];
  164. if (newPixelBuffer) {
  165. if (self.assetWriter.status == AVAssetWriterStatusWriting) {
  166. BOOL success = [self.inputPixelBufferAdptor appendPixelBuffer:newPixelBuffer withPresentationTime:self.currentSampleTime];
  167. if (!success) {
  168. NSLog(@"视频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription);
  169. [self finishWriting];
  170. }
  171. }
  172. CFRelease(newPixelBuffer);
  173. }else{
  174. NSLog(@"newPixelBuffer is nil");
  175. }
  176. }
  177. }
  178. }
  179. }else {
  180. @autoreleasepool {
  181. @synchronized(self) {
  182. if (!self.isStartWriting && (self.outputFileType == SLAvWriterFileTypeVideo || self.outputFileType == SLAvWriterFileTypeSilentVideo)) {
  183. //一个写入周期内只能执行一次
  184. [self.assetWriter startWriting];
  185. [self.assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
  186. self.isStartWriting = YES;
  187. }
  188. //写入视频数据
  189. if (self.assetWriterVideoInput.readyForMoreMediaData && self.isStartWriting) {
  190. BOOL success = [self.assetWriterVideoInput appendSampleBuffer:sampleBuffer];
  191. if (!success) {
  192. @synchronized (self) {
  193. NSLog(@"视频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription);
  194. [self finishWriting];
  195. }
  196. }
  197. }
  198. }
  199. }
  200. }
  201. }
  202. //实时写入输出的音频
  203. - (void)writingAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
  204. @autoreleasepool {
  205. @synchronized(self) {
  206. if (!self.isStartWriting && self.outputFileType == SLAvWriterFileTypeAudio) {
  207. [self.assetWriter startWriting];
  208. [self.assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
  209. self.isStartWriting = YES;
  210. }
  211. if (self.assetWriterAudioInput.readyForMoreMediaData && self.isStartWriting == YES) {
  212. //写入音频数据
  213. BOOL success = [self.assetWriterAudioInput appendSampleBuffer:sampleBuffer];
  214. if (!success) {
  215. @synchronized (self) {
  216. NSLog(@"音频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription);
  217. [self finishWriting];
  218. }
  219. }
  220. }
  221. }
  222. }
  223. }
  224. /// 完成写入
  225. - (void)finishWriting {
  226. __weak typeof(self) weakSelf = self;
  227. if(_assetWriter && self.isStartWriting && self.assetWriter.status != AVAssetWriterStatusUnknown) {
  228. [_assetWriter finishWritingWithCompletionHandler:^{
  229. if ([weakSelf.delegate respondsToSelector:@selector(writerInput:didFinishRecordingToOutputFileAtURL:error:)]) {
  230. SL_DISPATCH_ON_MAIN_THREAD(^{
  231. [weakSelf.delegate writerInput:weakSelf didFinishRecordingToOutputFileAtURL:weakSelf.outputFileURL error:weakSelf.assetWriter.error];
  232. });
  233. }
  234. weakSelf.isStartWriting = NO;
  235. weakSelf.inputPixelBufferAdptor = nil;
  236. weakSelf.context = nil;
  237. weakSelf.assetWriter = nil;
  238. weakSelf.assetWriterAudioInput = nil;
  239. weakSelf.assetWriterVideoInput = nil;
  240. }];
  241. }
  242. }
  243. @end