UWP 手绘视频创作工具技术分享系列 - 手绘视频导出

时间:2023-03-08 16:58:21

手绘视频最终的生成物是视频文件,前面几篇主要讲的是手绘视频的创作部分,今天讲一下手绘视频的导出问题。主要以 UWP 为例,另外会介绍一些 Web 端遇到的问题和解决方法。

如上所述,手绘视频在创作后,最终会导出为视频文件,如 MP4,WMV 等,我们目前的选择是 MP4,整个导出大致分为几个步骤:

UWP 手绘视频创作工具技术分享系列 - 手绘视频导出

1. 后台渲染手绘视频

后台渲染我们借助的还是 Win2D,前面几篇介绍过,创作绘制过程也是借助 Win2D 来完成动态渲染的。把需要渲染的元素和指定的时间等属性传递给 Win2D,其他的由 Win2D 去完成,这里不多作介绍。

2. 按帧率定制截取图片

这个步骤的实现方式较多,我们使用的是 CanvasBitmap.CreateFromBytes 和 MediaClip.CreateFromSurface 的方式截图,并把每部分的视频片段文件保存下来,看一段示例代码:

var img = CanvasBitmap.CreateFromBytes(device, screen.GetPixelBytes(), (int)screen.SizeInPixels.Width, (int)screen.SizeInPixels.Height, screen.Format);
var clip = MediaClip.CreateFromSurface(img, span);
layerTmp.Overlays.Add(CreateMediaOverlay(clip, size, s - start));
var composition = new MediaComposition();
composition.Clips.Add(MediaClip.CreateFromSurface(bkScreen, TimeSpan.FromMilliseconds(s - start)));
composition.OverlayLayers.Add(layerTmp);
var mediaPartFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync($"part_{mediafileList.Count}.mp4", CreationCollisionOption.ReplaceExisting);
await composition.RenderToFileAsync(mediaPartFile, MediaTrimmingPreference.Fast, MediaEncodingProfile.CreateMp4(quality));
mediafileList.Add(mediaPartFile);

3. 图片序列生成视频

这一步骤,普遍来讲都是通过 FFMpeg 来实现,FFMpeg 在 C# 语言方面也有很多封装版本可用。不过我们在 UWP 中并没有使用 FFMpeg,一方面代码库体积较大,另一方面我们有 MediaComposition 和 MediaClip 可用。

我们使用前面步骤保存下来的视频片段,使用 MediaComposition.RenderToFileAsync 方法保存到视频文件 ××.mp4 中:

foreach (var mediaPartFile in mediafileList)
{
var mediaPartClip = await MediaClip.CreateFromFileAsync(mediaPartFile);
bkComposition.Clips.Add(mediaPartClip);
}
var saveOperation = bkComposition.RenderToFileAsync(file, MediaTrimmingPreference.Fast,
MediaEncodingProfile.CreateMp4(quality));

4. 处理插入视频的音轨

这一步骤操作相对简单,因为 MediaOverlay 对声音的支持很方便,我们只需要把插入的视频,按照设定的开始时间和结束时间做裁剪,然后做好指定的旋转等变换,接下来设置 MediaOvelay.AudioEnabled = true; 就可以了,如果需要对视频静音,就设置为 false。

var overlay = CreateMediaOverlay(overlayClip, size, effect.StartAt);
overlay.AudioEnabled = videoGlygh.IsEnableAudio;
layer.Overlays.Add(overlay);
bkComposition.OverlayLayers.Add(layer);

5. 处理视频背景音乐

处理背景音乐也是使用 MediaComposition 的 BackgroundAudioTracks,通过音频文件来创建 BackgroundAudioTrack。它是一个 iList 类型,也就是说我们可以加入多个音轨。看一下简单的示例代码:

StorageFile music = await StorageFile.GetFileFromApplicationUriAsync(new Uri(DrawOption.Instance.DefaultMusic.url));
var backgroundTrack = await BackgroundAudioTrack.CreateFromFileAsync(music);
bkComposition.BackgroundAudioTracks.Add(backgroundTrack);

这里需要处理一些特殊情况,比如手绘视频中允许音频文件循环播放,这时我们需要对音频文件做一下拼接,简单的根据视频时间和音频时间做一下手动拼接:

int i = ;
while (DrawOption.Instance.MusicLoop && duration.TotalMilliseconds * i < total)
{
var track = await BackgroundAudioTrack.CreateFromFileAsync(music);
track.Delay = TimeSpan.FromMilliseconds(i * duration.TotalMilliseconds);
bkComposition.BackgroundAudioTracks.Add(track);
++i;
}

到这里我们就完成了在 UWP 中导出手绘视频的工作,而导出时间一般和视频分辨率,渲染元素的复杂度有很大关联,目前 720P 视频的导出时间大概是手绘视频时长的 2 倍左右。当视频很长,比如超过 10 分钟时,导出时间会变得比较长,之前我们也 fix 过一个 bug,就是图片大量保存到本地时,本地磁盘 IO 变成了瓶颈,磁盘占用量也很高,后面针对这个 bug 做了修改,把本地保存文件改为内存中持有,做好 GC 工作。

这样一来,视频导出的时间消耗就可以接受了,同时我们还有 Web 端平台,它同样也具备手绘视频创作和导出的功能,它的导出功能是在服务器端完成的,服务器是 Linux,它并没有 UWP 这么幸运,它的导出工作运行起来比较缓慢,基本会在视频长度的 5- 10倍左右,流程如下:

UWP 手绘视频创作工具技术分享系列 - 手绘视频导出

这里影响导出时间的主要是 PhantomJS 的截图,它的性能不好,每帧图片截图的时间很久,拖慢了整体速度。而目前我们想到了,除了使用 C++ 重新写一下截图的功能,没有其他好的办法,而即使重写,效率提升也不会太大。

基于这些问题,我们想到了另一个解决办法,在用户本地,使用浏览器插件或本地应用程序,来完成转换并同步到服务器。下面简单说说我们目前尝试的几种方案:

1. 传统的录屏方案

在我们考虑把 Web 端视频生成转移到本地的第一时间,就想到了这个方案。实现方面相对于用户直接使用一个 3rdParty 的录屏软件,不同点就在于我们可以获取用户选择了什么音频作为背景音乐,我们可以把它上传到服务器端,展示在‘我的作品’列表里。流程如下图:

UWP 手绘视频创作工具技术分享系列 - 手绘视频导出

这种方式实现相对简单,基本就是 FFMpeg 的使用,但是弊端也很明显。因为是录屏,所以录制过程中,用户的浏览器不能移动、不能最小化、也不能暂停,而且必须预览完整的一遍,不可控性非常多,所以很快就被否决了。

2. Web 端结合本地程序方案

这个方案需要 Web 端和本地程序各自做一些事情,简单来说就是本地程序在本机启动一个服务,Web 端按照帧率在后台渲染的 Canvas 里截取图片传给本地程序,本地程序生成视频,合成音轨,上传到服务器,流程如图:

UWP 手绘视频创作工具技术分享系列 - 手绘视频导出

本地程序是一个后台服务,没有界面,不需要用户配合,浏览器只要不关闭就可以完成,用户不需要进行预览,这些就是这个方案的优点。目前这个方案正在开发中,开发完成后,我们会就这个方案详细做分享,还是一种很脑洞的实现方式。

到这里我们就讲解完毕了,UWP 的视频导出,Web 端视频导出的问题,以及目前我们想到的解决方案,如果大家有更好的办法,欢迎反馈给我们,感谢!