歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐干貨哦~

本文作者,shengcui,騰訊雲高級開發工程師,負責移動客戶端開發
最近抖音最近又帶了一波合唱的節奏,老板看到后果然又是要盡快跟進,希望隔壁公司加薪的時候他也能作出如此反應。
功能看起來不復雜,就是把一個視頻播放出來放一邊,另一邊顯示攝像頭的畫面和源視頻一起錄制。單獨錄制和播放都還比較簡單,但是左右合成就有點頭大。網上搜了一圈都是些直播相關的文章,看了下沒什么頭緒。無奈之余翻翻SDK碰運氣。之前做本地視頻上傳的時候有一個叫Join的類是用來前后拼接視頻的,沒想到里面竟然還有個分屏的接口,研究了一番終於弄清楚了他的使用方法。在此記錄方便回顧,也和大家一起分享下。
前期的准備
之前的工程在上班之前同事就搭建好了,這次正好自己也試着搭建一遍。
工欲善其事,必先利其器。前期的准備工作其實不多,主要是下載SDK和准備視頻。
- 到 SDK 的官方網站 上注冊個帳號
- 在 SDK開發包 - 短視頻 - 文檔平台 - 騰訊雲 這里下載SDK
- 准備一段視頻,我是從抖音上隨便下了一個, Airdrop到電腦上保存為demo.mp4
開工
大概的思路是這樣的
- 在界面上放兩個View, 一個用來播放,一個用來錄制
- 再放一個按鈕和進度條來開始錄制和顯示進度
- 錄制與源視頻相同的時長后停止
- 把錄好的視頻與源視頻左右合成
- 預覽合成好的視頻
先來開始工程的創建,打開Xcode, File - New - Project, 起個好名字,這里就叫Demo好了。
1創建工程
4配置Framework
因為要錄像,所以我們需要相機和麥克風的權限,在Info中配置一下增加以下兩項
Privacy - Microphone Usage Description
Privacy - Camera Usage Description
值的內容隨便寫,我填了"錄像"
接下來我們配置一個簡單的錄制界面,打開Main.storyboard, 拖進去兩個UIView, 配置寬度為superview的0.5倍,長寬比16:9
5放View
然后加上進度條,在ViewController.m中設置IBOutlet綁定界面,並設置好按鈕的IBAction。因為錄制好后我們還要跳轉到預覽界面,還需要一個導航,點擊黃色VC圖標,在菜單欄依次進入 Editor - Embeded In 點擊 Navigation Controller 給ViewController套一層Navigation Controller。這樣界面基本就搭建好了。
6綁定View
然后我們就可以愉快的編碼了。
代碼部分
前面提到過開發的思路,關鍵點只有三個部分,播放、錄制、以及錄制后和原視頻進行合成,這對應到SDK的就是TXVideoEditer、TXUGCRecord、TXVideoJoiner這三個類。只要用好這三個類就能完成合唱功能了。
在使用前要配置SDK的Licence, 打開AppDelegate.m在里面添加以下代碼:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[TXUGCBase setLicenceURL:@"<Licence的URL>" key:@"<Licence的Key>"];
return YES;
}
這里的Licence參數需要到這里去申請,提交申請后一般很快就會審批下來。然后頁面上就會有相關的信息。
- 首先是聲明與初始化。
打開ViewContorller.m,引用SDK並聲明上述三個類的實例。另外這里播放、錄制和合成視頻都是異步操作,需要監聽他們的事件,所以要加上實現TXVideoJoinerListener, TXUGCRecordListener, TXVideoPreviewListener這三個協議的聲明。加好后如下所示。
#import "ViewController.h"
@import TXLiteAVSDK_UGC;
@interface ViewController () <TXVideoJoinerListener, TXUGCRecordListener, TXVideoPreviewListener>
{
TXVideoEditer *_editor;
TXUGCRecord *_recorder;
TXVideoJoiner *_joiner;
TXVideoInfo *_videoInfo;
NSString *_recordPath;
NSString *_resultPath;
}
@property (weak, nonatomic) IBOutlet UIView *cameraView;
@property (weak, nonatomic) IBOutlet UIView *movieView;
@property (weak, nonatomic) IBOutlet UIButton *recordButton;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
- (IBAction)onTapButton:(UIButton *)sender;
@end
准備好成員變量和接口實現聲明后,我們在viewDidLoad中對上面的成員變量進行初始化。
- (void)viewDidLoad {
[super viewDidLoad];
// 這里隨便找了段視頻放到了工程里
NSString *mp4Path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"mp4"];
_videoInfo = [TXVideoInfoReader getVideoInfo:mp4Path];
TXAudioSampleRate audioSampleRate = AUDIO_SAMPLERATE_48000;
if (_videoInfo.audioSampleRate == 8000) {
audioSampleRate = AUDIO_SAMPLERATE_8000;
}else if (_videoInfo.audioSampleRate == 16000){
audioSampleRate = AUDIO_SAMPLERATE_16000;
}else if (_videoInfo.audioSampleRate == 32000){
audioSampleRate = AUDIO_SAMPLERATE_32000;
}else if (_videoInfo.audioSampleRate == 44100){
audioSampleRate = AUDIO_SAMPLERATE_44100;
}else if (_videoInfo.audioSampleRate == 48000){
audioSampleRate = AUDIO_SAMPLERATE_48000;
}
// 設置錄像的保存路徑
_recordPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"record.mp4"];
_resultPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"result.mp4"];
// 播放器初始化
TXPreviewParam *param = [[TXPreviewParam alloc] init];
param.videoView = self.movieView;
param.renderMode = RENDER_MODE_FILL_EDGE;
_editor = [[TXVideoEditer alloc] initWithPreview:param];
[_editor setVideoPath:mp4Path];
_editor.previewDelegate = self;
// 錄像參數初始化
_recorder = [TXUGCRecord shareInstance];
TXUGCCustomConfig *recordConfig = [[TXUGCCustomConfig alloc] init];
recordConfig.videoResolution = VIDEO_RESOLUTION_720_1280;
recordConfig.videoFPS = _videoInfo.fps;
recordConfig.audioSampleRate = audioSampleRate;
recordConfig.videoBitratePIN = 9600;
recordConfig.maxDuration = _videoInfo.duration;
_recorder.recordDelegate = self;
// 啟動相機預覽
[_recorder startCameraCustom:recordConfig preview:self.cameraView];
// 視頻拼接
_joiner = [[TXVideoJoiner alloc] initWithPreview:nil];
_joiner.joinerDelegate = self;
[_joiner setVideoPathList:@[_recordPath, mp4Path]];
}
- 接下來是錄制部分,只要響應用戶點擊按鈕調用SDK方法就可以了,為了方便起見,這里復用了這個按鈕來顯示當前狀態。另外加上在進度條上顯示進度的邏輯。
- (IBAction)onTapButton:(UIButton *)sender {
[_editor startPlayFromTime:0 toTime:_videoInfo.duration];
if ([_recorder startRecord:_recordPath coverPath:[_recordPath stringByAppendingString:@".png"]] != 0) {
NSLog(@"相機啟動失敗");
}
[sender setTitle:@"錄像中" forState:UIControlStateNormal];
sender.enabled = NO;
}
#pragma mark TXVideoPreviewListener
-(void) onPreviewProgress:(CGFloat)time
{
self.progressView.progress = time / _videoInfo.duration;
}
- 錄制好后開始完成拼接部分, 這里需要指定兩個視頻在結果中的位置,這里設置一左一右。
-(void)onRecordComplete:(TXUGCRecordResult*)result;
{
NSLog(@"錄制完成,開始合成");
[self.recordButton setTitle:@"合成中..." forState:UIControlStateNormal];
//獲取錄制視頻的寬高
TXVideoInfo *videoInfo = [TXVideoInfoReader getVideoInfo:_recordPath];
CGFloat width = videoInfo.width;
CGFloat height = videoInfo.height;
//錄制視頻和原視頻左右排列
CGRect recordScreen = CGRectMake(0, 0, width, height);
CGRect playScreen = CGRectMake(width, 0, width, height);
[_joiner setSplitScreenList:@[[NSValue valueWithCGRect:recordScreen],[NSValue valueWithCGRect:playScreen]] canvasWidth:width * 2 canvasHeight:height];
[_joiner splitJoinVideo:VIDEO_COMPRESSED_720P videoOutputPath:_resultPath];
}
- 監聽合成進度,讓子彈飛一會-(void) onJoinProgress:(float)progress { NSLog(@"視頻合成中%d%%",(int)(progress * 100)); self.progressView.progress = progress; }
- 大工告成#pragma mark TXVideoJoinerListener -(void) onJoinComplete:(TXJoinerResult *)result { NSLog(@"視頻合成完畢"); VideoPreviewController *controller = [[VideoPreviewController alloc] initWithVideoPath:_resultPath]; [self.navigationController pushViewController:controller animated:YES]; }
至此就制作完成了,上面提到了一個視頻預覽的ViewController,代碼也很簡單
@import TXLiteAVSDK_UGC;
@interface VideoPreviewController () <TXVideoPreviewListener>
{
TXVideoEditer *_editor;
}
@property (strong, nonatomic) NSString *videoPath;
@end
@implementation VideoPreviewController
- (instancetype)initWithVideoPath:(NSString *)path {
if (self = [super initWithNibName:nil bundle:nil]) {
self.videoPath = path;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
TXPreviewParam *param = [[TXPreviewParam alloc] init];
param.videoView = self.view;
param.renderMode = RENDER_MODE_FILL_EDGE;
_editor = [[TXVideoEditer alloc] initWithPreview:param];
_editor.previewDelegate = self;
[_editor setVideoPath:self.videoPath];
[_editor startPlayFromTime:0 toTime:[TXVideoInfoReader getVideoInfo:self.videoPath].duration];
}
-(void) onPreviewFinished
{
[_editor startPlayFromTime:0 toTime:[TXVideoInfoReader getVideoInfo:self.videoPath].duration];
}
@end
以上既是所有的代碼,這里回顧一下前面的完整流程: 1.新建與配置工程 2.添加錄像、播放與狀態顯示的視圖 3. 響應用戶事件來調用SDK相關方法 4. 響應異步操作進度的回調。一共只有百十來行代碼,簡直是唾手可得,再把界面修飾下明天就可以和老板報告了。老板肯定沒有想到我能這么完成這個任務,這對他來說一定是一個驚喜。

問答
相關閱讀
此文已由作者授權騰訊雲+社區發布,原文鏈接:https://cloud.tencent.com/developer/article/1158911?fromSource=waitui
歡迎大家前往騰訊雲+社區或關注雲加社區微信公眾號(QcloudCommunity),第一時間獲取更多海量技術實踐干貨哦~
海量技術實踐經驗,盡在雲加社區!
