需求:已经存在一个音频播放器的的情况下,需要同时播放一个视频(没有声音),做成替换视频背景音的假象.
方案-:使用AVPlayer播放AVMutableComposition合成的资源
主要代码如下:
AVAsset *videoAsset = [AVAsset assetWithURL:baseVideoUrl];
AVAssetTrack *videoTrack = [videoAsset tracksWithMediaType:AVMediaTypeVideo].firstObject;
AVMutableComposition *mix_composition = [AVMutableComposition composition];
AVMutableCompositionTrack *mul_v_track = [mix_composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
BOOL success = [mul_v_track insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil];
if (!success) {
NSLog(@"视频插入失败");
return;
}
AVAsset *a_asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:audioPath]];
NSArray *a_tracks = [a_asset tracksWithMediaType:AVMediaTypeAudio];
for (AVAssetTrack *a_track in a_tracks) {
AVMutableCompositionTrack *mul_audio_track = [mix_composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
BOOL success = [mul_audio_track insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:a_track atTime:kCMTimeZero error:nil];
if (!success) {
NSLog(@"插入音频失败");
return;
}
}
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:mix_composition];
self.player = [AVPlayer playerWithPlayerItem:item];
该方案缺点:
1.音频不支持流方式,必须是已经下载完成的文件
2.切换音频时会有一定的延迟
方案二:使用AVAssetReader读取一帧一帧的图片并渲染,音频使用单独的播放器去播放
AVURLAsset *vas = [AVURLAsset URLAssetWithURL:self.fileUrl options:@{AVURLAssetPreferPreciseDurationAndTimingKey:@(YES)}];
AVAssetReader *videoReader = [AVAssetReader assetReaderWithAsset:vas error:nil];
AVAssetTrack *videoTrack = [vas tracksWithMediaType:AVMediaTypeVideo].firstObject;
NSMutableDictionary *outPutSetting = [NSMutableDictionary dictionary];
[outPutSetting setObject:@(kCVPixelFormatType_32BGRA) forKey:(id)kCVPixelBufferPixelFormatTypeKey];
AVAssetReaderTrackOutput *outPut = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:outPutSetting];
[videoReader addOutput:outPut];
outPut.supportsRandomAccess = YES;
CGFloat frameDuration = CMTimeGetSeconds(videoTrack.minFrameDuration);
self.duration = self.asset.duration;
CMTimeShow(self.asset.duration);
根据帧间隔做一个定时器来替换绘制的图片,每次计时获取一张图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer timerWithTimeInterval:frameDuration target:self selector:@selector(readNextFrame) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
self.decodeTimer = timer;
[timer fire];
});
获取图片函数
CMSampleBufferRef sampleBuffer = [self.output copyNextSampleBuffer];
_currentPlayTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer);
CMTimeShow(_currentPlayTime);
CVImageBufferRef cvimagebuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(cvimagebuffer, 0);
void *baseAddre = CVPixelBufferGetBaseAddress(cvimagebuffer);
size_t cvwidth = CVPixelBufferGetWidth(cvimagebuffer);
size_t cvheight = CVPixelBufferGetHeight(cvimagebuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(cvimagebuffer);
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(baseAddre, cvwidth, cvheight, 8, bytesPerRow, space, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGImageRef resImage = CGBitmapContextCreateImage(context);
CVPixelBufferUnlockBaseAddress(cvimagebuffer, 0);
CGContextRelease(context);
CGColorSpaceRelease(space);
_cgimage = resImage;
封装播放器,播放,暂停,滑动指定时间播放
//暂停就是将计时器停止就可以了
- (void)pause {
self.status = KGPreviewPlayerStatusPause;
//停止计时器,即停止了解码操作
[self.decodeTimer setFireDate:[NSDate distantFuture]];
}
//seekTime需要重新指定reader的timerange
- (void)seekTime:(CMTime)time {
if (self.status == KGPreviewPlayerStatusEnd) {
CMTimeRange range = CMTimeRangeMake(time, CMTimeSubtract(self.asset.duration, time));
NSValue *value = [NSValue valueWithCMTimeRange:range];
[self.output resetForReadingTimeRanges:@[value]];
} else {
[self.decodeTimer setFireDate:[NSDate distantPast]];
[self.reader cancelReading];
if (self.status != KGPreviewPlayerStatusPause) {
[self.clock lockWhenCondition:100];
}
_currentPlayTime = time;
[self reConfigReader];
[self startDecode];
}
}
方案二demo地址:https://github.com/taozaizai/AVAssertReaderDemo.git
demo问题还有很多,如播放快慢的时间校准,前后台切换问题等