100行程式碼搞定抖音短視訊App,終於可以和美女合唱了。

騰訊雲加社群發表於2019-03-01

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

本文由視訊咖 發表於雲+社群專欄

img

本文作者,shengcui,騰訊雲高階開發工程師,負責移動客戶端開發

最近抖音最近又帶了一波合唱的節奏,老闆看到後果然又是要儘快跟進,希望隔壁公司加薪的時候他也能作出如此反應。

功能看起來不復雜,就是把一個視訊播放出來放一邊,另一邊顯示攝像頭的畫面和源視訊一起錄製。單獨錄製和播放都還比較簡單,但是左右合成就有點頭大。網上搜了一圈都是些直播相關的文章,看了下沒什麼頭緒。無奈之餘翻翻SDK碰運氣。之前做本地視訊上傳的時候有一個叫Join的類是用來前後拼接視訊的,沒想到裡面竟然還有個分屏的介面,研究了一番終於弄清楚了他的使用方法。在此記錄方便回顧,也和大家一起分享下。

前期的準備

之前的工程在上班之前同事就搭建好了,這次正好自己也試著搭建一遍。

工欲善其事,必先利其器。前期的準備工作其實不多,主要是下載SDK和準備視訊。

  1. 到 SDK 的官方網站 上註冊個帳號
  2. SDK開發包 - 短視訊 - 文件平臺 - 騰訊雲 這裡下載SDK
  3. 準備一段視訊,我是從抖音上隨便下了一個, Airdrop到電腦上儲存為demo.mp4

開工

大概的思路是這樣的

  1. 在介面上放兩個View, 一個用來播放,一個用來錄製
  2. 再放一個按鈕和進度條來開始錄製和顯示進度
  3. 錄製與源視訊相同的時長後停止
  4. 把錄好的視訊與源視訊左右合成
  5. 預覽合成好的視訊

先來開始工程的建立,開啟Xcode, File - New - Project, 起個好名字,這裡就叫Demo好了。

img
1建立工程

img
4配置Framework

因為要錄影,所以我們需要相機和麥克風的許可權,在Info中配置一下增加以下兩項

Privacy - Microphone Usage Description
Privacy - Camera Usage Description
複製程式碼

值的內容隨便寫,我填了"錄影"

接下來我們配置一個簡單的錄製介面,開啟Main.storyboard, 拖進去兩個UIView, 配置寬度為superview的0.5倍,長寬比16:9

img
5放View

然後加上進度條,在ViewController.m中設定IBOutlet繫結介面,並設定好按鈕的IBAction。因為錄製好後我們還要跳轉到預覽介面,還需要一個導航,點選黃色VC圖示,在選單欄依次進入 Editor - Embeded In 點選 Navigation Controller 給ViewController套一層Navigation Controller。這樣介面基本就搭建好了。

img
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引數需要到這裡去申請,提交申請後一般很快就會審批下來。然後頁面上就會有相關的資訊。

  1. 首先是宣告與初始化。

開啟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]];
}
複製程式碼
  1. 接下來是錄製部分,只要響應使用者點選按鈕呼叫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;    
}
複製程式碼
  1. 錄製好後開始完成拼接部分, 這裡需要指定兩個視訊在結果中的位置,這裡設定一左一右。
-(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];
}
複製程式碼
  1. 監聽合成進度,讓子彈飛一會-(void) onJoinProgress:(float)progress { NSLog(@"視訊合成中%d%%",(int)(progress * 100)); self.progressView.progress = progress; }
  2. 大工告成#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. 響應非同步操作進度的回撥。一共只有百十來行程式碼,簡直是唾手可得,再把介面修飾下明天就可以和老闆報告了。老闆肯定沒有想到我能這麼完成這個任務,這對他來說一定是一個驚喜。

img

問答

短視訊都需要CDN的支援嗎?

相關閱讀

心隨手動,驅動短視訊熱潮的引擎

教你1天搭建自己的“微視”

MySQL 8.0 版本功能變更介紹

此文已由作者授權騰訊雲+社群釋出,原文連結:https://cloud.tencent.com/developer/article/1158911?fromSource=waitui

歡迎大家前往騰訊雲+社群或關注雲加社群微信公眾號(QcloudCommunity),第一時間獲取更多海量技術實踐乾貨哦~

海量技術實踐經驗,盡在雲加社群

相關文章