需求:已經(jīng)存在一個音頻播放器的的情況下,需要同時播放一個視頻(沒有聲音),做成替換視頻背景音的假象.
方案-:使用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.音頻不支持流方式,必須是已經(jīng)下載完成的文件
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);
根據(jù)幀間隔做一個定時器來替換繪制的圖片,每次計時獲取一張圖片
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];
});
獲取圖片函數(shù)
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問題還有很多,如播放快慢的時間校準(zhǔn),前后臺切換問題等