- (void)createNewVideoWithWaterMark:(NSURL *)videoPath { NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES); NSString *ourDocumentPath =[documentPaths objectAtIndex:0]; NSString *resultPath = [ourDocumentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4",@"resultVideo"]]; // Delete old videos [self removeFileWithUrl:resultPath]; NSError *error; AVURLAsset *asset = [AVURLAsset URLAssetWithURL:videoPath options:nil]; AVAssetReader *readerAsset = [AVAssetReader assetReaderWithAsset:asset error:&error]; AVAssetWriter *writerAsset = [AVAssetWriter assetWriterWithURL:[NSURL fileURLWithPath:resultPath] fileType:AVFileTypeMPEG4 error:&error]; NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo]; NSDictionary *videoSetting = @{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], (id)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary]}; AVAssetReaderVideoCompositionOutput *videoOutput = [AVAssetReaderVideoCompositionOutput assetReaderVideoCompositionOutputWithVideoTracks:videoTracks videoSettings:videoSetting]; AVAssetWriterInput *videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:[self videoCompressSettings]]; if ([readerAsset canAddOutput:videoOutput]) { NSDateFormatter *formater = [[NSDateFormatter alloc] init]; [formater setDateFormat:@" yyyy-MM-dd HH:mm:ss "]; NSString *waterMarkTime = [formater stringFromDate:[NSDate date]]; videoOutput.videoComposition = [self fixedCompositionWithAsset:asset]; [readerAsset addOutput:videoOutput]; } if ([writerAsset canAddInput:videoInput]) { [writerAsset addInput:videoInput]; } // audio NSArray *audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio]; NSDictionary *audioSetting = @{AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM]}; AVAssetReaderAudioMixOutput *audioOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:audioTracks audioSettings:audioSetting]; AVAssetWriterInput *audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[self audioCompressSettings]]; if ([readerAsset canAddOutput:audioOutput]) { [readerAsset addOutput:audioOutput]; } if ([writerAsset canAddInput:audioInput]) { [writerAsset addInput:audioInput]; } [writerAsset startWriting]; [writerAsset startSessionAtSourceTime:kCMTimeZero]; [readerAsset startReading]; dispatch_queue_t videoQueue = dispatch_queue_create("Video Queue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t audioQueue = dispatch_queue_create("Audio Queue", DISPATCH_QUEUE_SERIAL); dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); [videoInput requestMediaDataWhenReadyOnQueue:videoQueue usingBlock:^{ while ([videoInput isReadyForMoreMediaData]) { CMSampleBufferRef sampleBuffer; if([readerAsset status] == AVAssetReaderStatusReading && (sampleBuffer = [videoOutput copyNextSampleBuffer])){ BOOL result = [videoInput appendSampleBuffer:sampleBuffer]; CFRelease(sampleBuffer); if(!result){ [readerAsset cancelReading]; break; } }else{ [videoInput markAsFinished]; dispatch_group_leave(group); break; } } }]; dispatch_group_enter(group); [audioInput requestMediaDataWhenReadyOnQueue:audioQueue usingBlock:^{ while ([audioInput isReadyForMoreMediaData]) { CMSampleBufferRef sampleBuffer; if ([readerAsset status] == AVAssetReaderStatusReading && (sampleBuffer = [audioOutput copyNextSampleBuffer])) { // AVAssetWriterStatusFailed (reason: Error Domain=AVFoundationErrorDomain Code=-11800 "这项操作无法完成" UserInfo={NSLocalizedFailureReason=发生未知错误(-12780), NSLocalizedDescription=这项操作无法完成, NSUnderlyingError=0x302399a70 {Error Domain=NSOSStatusErrorDomain Code=-12780 "(null)"}}) BOOL result = [audioInput appendSampleBuffer:sampleBuffer]; CFRelease(sampleBuffer); if(!result){ [readerAsset cancelReading]; break; } } else { [audioInput markAsFinished]; dispatch_group_leave(group); break; } } }]; dispatch_group_notify(group, dispatch_get_main_queue(), ^{ }); } - (AVMutableVideoComposition *)fixedCompositionWithAsset:(AVAsset *)videoAsset { AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; NSUInteger degress = 0; if (videoAssetTrack) { CGAffineTransform t = videoAssetTrack.preferredTransform; if (t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0) { // Portrait degress = 90; } else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0) { // PortraitUpsideDown degress = 270; } else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0) { // LandscapeRight degress = 0; } else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0) { // LandscapeLeft degress = 180; } } CGAffineTransform translateToCenter; CGAffineTransform mixedTransform; if (degress == 90) { translateToCenter = CGAffineTransformMakeTranslation(videoAssetTrack.naturalSize.height, 0.0); mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2); } else if(degress == 180) { translateToCenter = CGAffineTransformMakeTranslation(videoAssetTrack.naturalSize.width, videoAssetTrack.naturalSize.height); mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI); } else if(degress == 270) { translateToCenter = CGAffineTransformMakeTranslation(0.0, videoAssetTrack.naturalSize.width); mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2*3.0); } else { translateToCenter = CGAffineTransformMakeTranslation(0.0, videoAssetTrack.naturalSize.width); mixedTransform = CGAffineTransformRotate(translateToCenter,0); } AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration); AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoAssetTrack]; [videolayerInstruction setOpacity:0.0 atTime:videoAsset.duration]; [videolayerInstruction setTransform:mixedTransform atTime:kCMTimeZero]; mainInstruction.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil]; AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition]; CGSize naturalSize = videoAssetTrack.naturalSize; float renderWidth, renderHeight; renderWidth = naturalSize.width; renderHeight = naturalSize.height; if (isnan(renderWidth) || renderWidth <= 0) { if (degress == 90 || degress == 270) { renderWidth = 1080.0; } else { renderWidth = 1920.0; } } else { if (degress == 90 || degress == 270) { renderWidth = naturalSize.height; } } if (isnan(renderHeight) || renderHeight <= 0) { if (degress == 90 || degress == 270) { renderHeight = 1920.0; } else { renderHeight = 1080.0; } } else { if (degress == 90 || degress == 270) { renderHeight = naturalSize.width; } } mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight); mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction]; mainCompositionInst.frameDuration = CMTimeMake(1, 30); CATextLayer *subtitleText = [[CATextLayer alloc] init]; [subtitleText setFontSize:35]; [subtitleText setFrame:CGRectMake(10, renderHeight-50, renderWidth, 50)]; [subtitleText setString:@"content 12345"]; subtitleText.contentsScale = [UIScreen mainScreen].scale; [subtitleText setForegroundColor:[[[UIColor whiteColor] colorWithAlphaComponent:0.8] CGColor]]; CALayer *overlayLayer = [CALayer layer]; [overlayLayer addSublayer:subtitleText]; overlayLayer.frame = CGRectMake(0, 0, renderWidth, renderHeight); [overlayLayer setMasksToBounds:YES]; CALayer *parentLayer = [CALayer layer]; CALayer *videoLayer = [CALayer layer]; parentLayer.frame = CGRectMake(0, 0, renderWidth, renderHeight); videoLayer.frame = CGRectMake(0, 0, renderWidth, renderHeight); [parentLayer addSublayer:videoLayer]; [parentLayer addSublayer:overlayLayer]; mainCompositionInst.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer]; return mainCompositionInst; } - (NSDictionary *)audioCompressSettings { AudioChannelLayout stereoChannelLayout = { .mChannelLayoutTag = kAudioChannelLayoutTag_Stereo, .mChannelBitmap = 0, .mNumberChannelDescriptions = 0}; NSData *channelLayoutAsData = [NSData dataWithBytes:&stereoChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)]; NSDictionary *audioCompressSettings = @{AVFormatIDKey:@(kAudioFormatMPEG4AAC), AVEncoderBitRateKey:@96000, AVSampleRateKey:@44100, AVChannelLayoutKey:channelLayoutAsData, AVNumberOfChannelsKey:@2}; return audioCompressSettings; }