zoukankan      html  css  js  c++  java
  • 100行代码搞定抖音短视频App,终于可以和美女合唱了。

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

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

    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好了。

    img1创建工程

    img4配置Framework

    因为要录像,所以我们需要相机和麦克风的权限,在Info中配置一下增加以下两项

    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/developer/article/1158911?fromSource=waitui

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

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

  • 相关阅读:
    win8 64下启动Apache失败:443端口被占用的解决方法
    JavaScript初学者应注意的七个细节
    再说SQL Server数据库优化
    2010.Net程序员年终随笔
    基于Siverlight 3.0的超炫图表工具Visifire 最后一个免费版本,你还等什么?
    苦修六年 终成正果 幸福之路 从此开始
    Asp.net中服务端控件事件是如何触发的(笔记)
    我的缓存实例—工作记录
    坚持观点:决不为了用Linq而用Linq!!
    ASP.NET 之 常用类、方法的超级总结,并包含动态的EXCEL导入导出功能,奉上类库源码
  • 原文地址:https://www.cnblogs.com/qcloud1001/p/9335185.html
Copyright © 2011-2022 走看看