歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~
本文作者,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),第一時間獲取更多海量技術實踐乾貨哦~
海量技術實踐經驗,盡在雲加社群!