100行代碼搞定抖音短視頻App,終於能夠和美女合唱了。

歡迎你們前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~canvas

本文由 視頻咖 發表於 雲+社區專欄

img

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

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

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

前期的準備

以前的工程在上班以前同事就搭建好了,此次正好本身也試着搭建一遍。app

工欲善其事,必先利其器。前期的準備工做其實很少,主要是下載SDK和準備視頻。異步

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

開工

大概的思路是這樣的ide

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

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

img1建立工程ui

img4配置Framework編碼

由於要錄像,因此咱們須要相機和麥克風的權限,在Info中配置一下增長如下兩項atom

Privacy - Microphone Usage Description
Privacy - Camera Usage Description

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

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

img5放View

而後加上進度條,在ViewController.m中設置IBOutlet綁定界面,並設置好按鈕的IBAction。由於錄製好後咱們還要跳轉到預覽界面,還須要一個導航,點擊黃色VC圖標,在菜單欄依次進入 Editor - Embeded In 點擊 Navigation Controller 給ViewController套一層Navigation Controller。這樣界面基本就搭建好了。

img6綁定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/dev...

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

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

相關文章
相關標籤/搜索