-
Notifications
You must be signed in to change notification settings - Fork 146
playerRecord
短视频录制功能也可以理解为录屏功能,可以将当前正在播放的视频(可以包含有UI界面)录制为mp4或flv格式的文件保存到本地。
如果不需要录制UI,可以直接获取播放器返回的原始音视频数据写入文件。本文则重点介绍如何完成含有UI的播放录制功能。
因为视频录制功能需要音视频的编码和复用功能,而这些功能对于独立的播放SDK并不存在也不需要,所以要使用短视频录制功能,需要集成的SDK为融合版SDK,使用其中的KSYGPUPicMixer、KSYAudioMixer、KSYGPUPicOutput等模块
v1.9.5及以上版本支持
播放时通过播放器中的textureBlock获取渲染的TextureId,与UIElement叠加后得到视频数据送入KSYStreamerBase模块;原始音频数据则从播放器的audioDataBlock中获取,经过KSYAudioMixer后送入KSYStreamerBase模块,示意图如下:
- 支持自定义录制UI层级
- 能够按照视频帧率录制
- 在某些设备上及资源占用严重
- 不支持视频播放过程中的缩放、旋转
v2.1.1及以上版本支持
音频数据的获取与处理方式同方案一,不同之处是视频和UI数据的获取,方案二通过直接截取UIView上内容来获取当前的显示内容,转换为CVPixerBuffer后直接送入KSYStreamerBase模块,示意图如下:
- 支持自定义录制UI层级
- 可自定义录制帧率
- 资源占用相对较少
- 可以把视频播放过程中的缩放、旋转原样录下来
- 无法按照视频播放的帧率进行录制
KSYLive_iOS的Demo中提供了KSYUIRecorderKit类实现录屏功能,KSYRecordVC类中演示了如何使用KSYUIRecorderKit进行短视频录制 录制功能的具体实现请参见KSYUIRecorderKit.m文件源码,此处简单介绍直接使用该类的方法。
#import "KSYUIRecorderKit.h"
#import <GPUImage/GPUImage.h>
#import <libksygpulive/libksygpulive.h>
@property (strong, nonatomic) KSYMoviePlayerController *player;
@property (strong, nonatomic) KSYUIRecorderKit* kit;
_kit = [[KSYUIRecorderKit alloc]initWithScheme:KSYPlayerRecord_PicMix_Scheme];
或
_kit = [[KSYUIRecorderKit alloc]init];
_kit = [[KSYUIRecorderKit alloc]initWithScheme:KSYPlayerRecord_ScreenShot_Scheme];
此处对播放器的初始化使用initWithContentURL:sharegroup:进行初始化操作
self.player = [[KSYMoviePlayerController alloc] initWithContentURL:_url sharegroup:[[[GPUImageContext sharedImageProcessingContext] context] sharegroup]];
//player视频数据输入
__weak KSYUIRecorderKit* weakKit = _kit;
_player.textureBlock = ^(GLuint textureId, int width, int height, double pts){
CGSize size = CGSizeMake(width, height);
CMTime _pts = CMTimeMake((int64_t)(pts * 1000), 1000);
[weakKit processWithTextureId:textureId TextureSize:size Time:_pts];
};
//player音频数据输入
_player.audioDataBlock = ^(CMSampleBufferRef buf){
CMTime pts = CMSampleBufferGetPresentationTimeStamp(buf);
if(pts.value < 0)
{
return;
}
[weakKit processAudioSampleBuffer:buf];
};
self.player = [[KSYMoviePlayerController alloc] initWithContentURL:_url sharegroup:nil];
@WeakObj(_kit);
_player.audioDataBlock = ^(CMSampleBufferRef buf){
CMTime pts = CMSampleBufferGetPresentationTimeStamp(buf);
if(pts.value < 0)
{
return;
}
[weakKit processAudioSampleBuffer:buf];
};
//addUIToKit将需要录制的界面元素添加到_kit.contentView,未添加的元素将不能被录制下来
-(void)addUIToKit{
[_kit.contentView addSubview:labelVolume];
[_kit.contentView addSubview:sliderVolume];
[_kit.contentView addSubview:lableHWCodec];
[_kit.contentView addSubview:switchHwCodec];
[_kit.contentView addSubview:btnPlay];
[_kit.contentView addSubview:btnPause];
[_kit.contentView addSubview:btnResume];
[_kit.contentView addSubview:btnStop];
[_kit.contentView addSubview:btnQuit];
[_kit.contentView addSubview:btnStartRecord];
[_kit.contentView addSubview:btnStopRecord];
[_kit.contentView addSubview:stat];
[self.view addSubview:_kit.contentView];
[_kit.contentView sendSubviewToBack:videoView];
}
- 截取指定UI上的内容
UIImage *screenshot = NULL;
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0);
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:NO];
screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
- UIImage转换为CVPixelBuffer
- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image
{
CVPixelBufferRef pixelBuffer = NULL;
CGFloat imageWidth = 0;
CGFloat imageHeight = 0;
CVReturn status = kCVReturnError;
if(!image)
return nil;
NSMutableDictionary *options = [[NSMutableDictionary alloc]init];
[options setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCVPixelBufferCGImageCompatibilityKey];
[options setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey];
if(SYSTEM_VERSION_LESS_THAN(@"9.0"))
[options setObject:@{} forKey:(NSString *)kCVPixelBufferIOSurfacePropertiesKey];
imageWidth = CGImageGetWidth(image);
imageHeight = CGImageGetHeight(image);
status = CVPixelBufferCreate(kCFAllocatorDefault,
imageWidth,
imageHeight,
kCVPixelFormatType_32BGRA,
(__bridge CFDictionaryRef) options,
&pixelBuffer);
if(status != kCVReturnSuccess || !pixelBuffer)
return nil;
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pixelData,
imageWidth,
imageHeight,
8,
CVPixelBufferGetBytesPerRow(pixelBuffer),
rgbColorSpace,
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little);
CGContextConcatCTM(context, CGAffineTransformIdentity);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
return pixelBuffer;
}
更详细的处理逻辑,请参考KSYRecordVC.m文件
path为录制文件的保存路径,目前支持mp4和flv格式,依靠后缀名区分
NSString* recordFilePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/RecordAv.mp4"];
NSURL * path =[[NSURL alloc] initWithString:recordFilePath];
[_kit startRecord:path];
[_kit stopRecord];
因为stopRecord操作为异步操作,所以stopRecord调用结束后并不表示文件已经写完,需要用以下方式处理
- 启动录制前注册KSYStreamStateDidChangeNotification消息
[[NSNotificationCenter defaultCenter]addObserver:self
selector:@selector(onStreamStateChange:)
name:(KSYStreamStateDidChangeNotification)
object:nil];
- 当收到KSYStreamStateDidChangeNotification消息,且_kit.writer.streamState的值为KSYStreamStateIdle时才表示文件已经写完,此时可以进一步处理文件,否则有可能因为文件被占用导致操作失败
- (void) onStreamStateChange :(NSNotification *)notification{
if (_kit.writer.streamState == KSYStreamStateIdle && _kit.bPlayRecord == NO){
[self saveVideoToAlbum: recordFilePath];
}
}