1.背景
最近需要在项目启动的时候添加一个启动视频动画,就是在启动的时候播放一个小视频。使用AVLayerPlayer很快播放视频就搞定。可是当我正在使用QQ音乐听歌的时候,打开我们的应用会发现音乐被自动中断了。后来一查发现是因为在启动应用的时候播放小视频,会自动audioSession。那么我们应该怎么做才能,在播放视频的时候,不会影响到我们听歌了。
2.解决方法
我首先想到的是,系统有没有这样的API,可以控制播放视频的时候不阻断别的音乐播放。不过很遗憾,至今没有找到。如果有找到的朋友可以私信我😁
既然如此,我决定自己来做这个视频的播放。也就是对视频做解码,拿到每一帧画面,自己播放。AVAssetReader
可以从原始数据里获取解码后的视频数据,再使用AVAssetReaderTrackOutPut
能够读取到每一帧 CMSampleBufferRef
.在这里需要注意的是整个视频解码的过程都要放到子线程,在获取到CGImageRef的时候然后在回到主线程传给UIView进行显示。
AVAsset *asset = [AVAsset assetWithURL:self.url];
NSError *error;
AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
BLOCK_EXEC(self.errorBlock, error);
});
return;
}
NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
AVAssetTrack *videoTrack = videoTracks[0];
NSDictionary* options = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:
(int)kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
AVAssetReaderTrackOutput* videoReaderOutput = [[AVAssetReaderTrackOutput alloc]
initWithTrack:videoTrack outputSettings:options];
[assetReader addOutput:videoReaderOutput];
[assetReader startReading];
// 要确保nominalFrameRate>0,之前出现过android拍的0帧视频
while ([assetReader status] == AVAssetReaderStatusReading && videoTrack.nominalFrameRate > 0) {
@autoreleasepool {
// 读取video sample
CMSampleBufferRef videoBuffer = [videoReaderOutput copyNextSampleBuffer];
CGImageRef imageRef = [self imageFromSampleBuffer:videoBuffer];
if (videoBuffer) CFRelease(videoBuffer);
if (imageRef) {
dispatch_async(dispatch_get_main_queue(), ^{
BLOCK_EXEC(self.imageRefBlock, imageRef);
});
}
// 根据需要休眠一段时间;比如上层播放视频时每帧之间是有间隔的
[NSThread sleepForTimeInterval:self.timeInterval];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
// 视频解码结束
BLOCK_EXEC(self.endBlock);
});
我们拿到CMSampleBuffer
后就可以通过以下方法转化为CGImageRef
,这里需要注意的是,在外面使用了CGImageRef
后要记得使用CFRelease()
方法释放。
- (CGImageRef) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// Get the number of bytes per row for the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a bitmap graphics context with the sample buffer data
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// Create a Quartz image from the pixel data in the bitmap graphics context
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return quartzImage;
}