欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~

本文由视频咖 发表于云+社区专栏

本文作者,shengcui,腾讯云高级开发工程师,负责移动客户端开发

最近抖音最近又带了一波合唱的节奏,老板看到后果然又是要尽快跟进,希望隔壁公司加薪的时候他也能作出如此反应。

功能看起来不复杂,就是把一个视频播放出来放一边,另一边显示摄像头的画面和源视频一起录制。单独录制和播放都还比较简单,但是左右合成就有点头大。网上搜了一圈都是些直播相关的文章,看了下没什么头绪。无奈之余翻翻SDK碰运气。之前做本地视频上传的时候有一个叫Join的类是用来前后拼接视频的,没想到里面竟然还有个分屏的接口,研究了一番终于弄清楚了他的使用方法。在此记录方便回顾,也和大家一起分享下。

前期的准备

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

工欲善其事,必先利其器。前期的准备工作其实不多,主要是下载SDK和准备视频。

  1. 到 SDK 的官方网站 上注册个帐号
  2. SDK开发包 - 短视频 - 文档平台 - 腾讯云 这里下载SDK
  3. 准备一段视频,我是从抖音上随便下了一个, Airdrop到电脑上保存为demo.mp4

开工

大概的思路是这样的

  1. 在界面上放两个View, 一个用来播放,一个用来录制
  2. 再放一个按钮和进度条来开始录制和显示进度
  3. 录制与源视频相同的时长后停止
  4. 把录好的视频与源视频左右合成
  5. 预览合成好的视频

先来开始工程的创建,打开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参数需要到这里去申请,提交申请后一般很快就会审批下来。然后页面上就会有相关的信息。

  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. 响应异步操作进度的回调。一共只有百十来行代码,简直是唾手可得,再把界面修饰下明天就可以和老板报告了。老板肯定没有想到我能这么完成这个任务,这对他来说一定是一个惊喜。

问答

短视频都需要CDN的支持吗?

相关阅读

心随手动,驱动短视频热潮的引擎

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

MySQL 8.0 版本功能变更介绍

此文已由作者授权腾讯云+社区发布,原文链接:https://cloud.tencent.com/developer/article/1158911?fromSource=waitui

欢迎大家前往腾讯云+社区或关注云加社区微信公众号(QcloudCommunity),第一时间获取更多海量技术实践干货哦~

海量技术实践经验,尽在云加社区

100行代码搞定抖音短视频App,终于可以和美女合唱了。的更多相关文章

  1. 10行代码搞定移动web端自定义tap事件

    发发牢骚 移动web端里摸爬滚打这么久踩了不少坑,有一定移动web端经验的同学一定被click困扰过.我也不列外.一路走来被虐的不行,fastclick.touchend.iscroll什么的都用过, ...

  2. 30行代码搞定WCF并发性能测试

    [以下只是个人观点,欢迎交流] 30行代码搞定WCF并发性能 轻量级测试. 1. 调用并发测试接口 static void Main()         {               List< ...

  3. JAVA代码实现抖音短视频去水印功能

    今天有人找我帮他抖音视频去水印,发到朋友圈,然后就研究了一下.去水印功能代码如下: public class DouYinQushuiyin { public static void main(Str ...

  4. Python爬虫---爬取抖音短视频

    目录 前言 抖音爬虫制作 选定网页 分析网页 提取id构造网址 拼接数据包链接 获取视频地址 下载视频 全部代码 实现结果 待解决的问题 前言 最近一直想要写一个抖音爬虫来批量下载抖音的短视频,但是经 ...

  5. Python音视频开发:消除抖音短视频Logo的图形化工具实现

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 在<Python音视频开发:消除抖音短视频Logo和去电视台标的实现详解>节介绍了怎么通过Python+Moviepy+OpenCV实现 ...

  6. Python音视频开发:消除抖音短视频Logo和去电视台标

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 对于带Logo(如抖音Logo.电视台标)的视频,有三种方案进行Logo消除: 直接将对应区域用对应图像替换: 直接将对应区域模糊化: 通过变换将要 ...

  7. 视频剪辑软件调研:Adobe Premiere、会声会影、抖音短视频

    Adobe Premiere.会声会影.抖音短视频基本功能特点对比: 特点 Adobe Premiere 会声会影 抖音短视频 运行平台 Win7/Win8/Win10.macOS  Win7/Win ...

  8. 最新快手抖音短视频源码web+APP架设教程+完整数据

    最新更新快手抖音短视频源码web+APP架设教程+完整数据完美运行 视频直播源码,好东西,反正有人要就是了. 下载地址:https://pan.baidu.com/wap/init?surl=POU5 ...

  9. Tensorflow快餐教程(1) - 30行代码搞定手写识别

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/lusing/article/details ...

随机推荐

  1. log4net 入门使用

    log4net 是dotnet平台下的一个日记记录组件. 一  NuGet中安装log4net包: 二 配置log4net.config文件 配置文件内容: <?xml version=&quo ...

  2. tomcat 6.x + log4j日志配置并按天(或大小)生成文件

      tomcat日志,默认路径在${catalina.home}/logs目录下,默认使用的是tomcat自己封装的logging工具类,默认配置文件使用的${catalina.home}/conf/ ...

  3. C#Zxing.net生成条形码和二维码

    下载Zxing.net官网:https://archive.codeplex.com/?p=zxingnet 或者去VS程序包下载 封装好的代码: using System; using System ...

  4. C#多线程学习(二) 如何操纵一个线程

    在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就 ...

  5. mvc - Authorize授权

    from : http://www.cnblogs.com/asks/p/4372783.html http://www.cnblogs.com/myindex/p/5479428.html

  6. WPF 添加OCX控件

    1. 在计算机上安装OCX控件 2.创建WPF应用程序 3.打开工具箱 4.单击COM 组件选项卡上,选择安装的控件,,然后单击确定,将控件添加到工具箱 5.在解决方案资源管理器,右键单击UserCo ...

  7. bitcoin script

    P2PK P2PKH,MS,P2SH,OP_RETURN 等的区别 1.P2PK pay_to_public_key pubkey script: <pubkey> OP_CHECKSIG ...

  8. MongoDB集群方案介绍

    MongoDB集群方案介绍 一.什么是mongodb集群? 是指由多台mongodb服务器组成的服务器集群,称之为mongodb集群. 二.mongodb集群搭建的方式: 1.Replica Set  ...

  9. 「HNOI 2013」数列

    题目链接 戳我 \(Solution\) 这道题貌似并不难的样子\(QAQ\) 我们发现这个因为有首项的关系所以有点不太好弄.所以我们要将这个首项对答案的影响给去掉. 我们可以构建一个差分数组,我们令 ...

  10. 本地Windows环境下安装MySql

    Windows 上安装 MySQL Windows 上安装 MySQL 相对来说会较为简单,你需要在 MySQL 下载中下载 Windows 版本的 MySQL 安装包. Download Link: ...