【Win 10 应用开发】在后台进行多媒体转码

前面,老周给大伙儿讲了如何运用 MediaTranscoder 类来完成多媒体。然而,你懂的,要是多媒体文件比较大,转码时间会更长,有可能用户不会一眭停在当前应用界面上,或许会切换到其他应用程序,甚至关闭应用程序。

所以,在特定情形下,是得考虑在后台任务上进行转码,这样哪怕前台应用退出了,也不会使转码中止,处理工作可以在后台任务中继续耍。

不过,多媒体转码这玩意儿毕竟很耗性能,也很耗电,强烈建议大家不要搞什么批量转码,不然,我估计平板/手机的电池续航能力支撑不了。

 

示例我已经做好了,因而完整的代码大伙儿可以下载来慢慢参考,bug比较多,因为老周的编程水平实在太差,比不上支付婊的高手们,故bug如林。

 

下面老周就重点扯一下实现过程。其实也没什么,也是用到万能的 MediaTranscoder 类,只不是过放在后台任务中进行了而已。

1、实现后台任务。咱们先做后台吧,在新建项目时,一定要选“Windows 运行时组件”,以前有朋友错选了类库项目,结果一运行就呵呵了。所以,选错老婆是很痛苦的,记好了,是 windows 运行时组件

不要把后台类型声明在主启动项目中,不然激活后台时会把你的前台程序给挂掉,因为运行后台是要新的实例的,而RT应用为了防止人品不端正的开发者故意破坏人民群众的公共设施,RT一般同时只能有一个实例在运行,所以,后台类一定要扔到一个独立的 Windows 运行时组件中,并在主启动项目中引用之。

实现后台,我相信大家都会了,老周以前也写过相关的东东,而且老周的破书里面也有讲到。总的一句就是:实现IBackgroundTask接口,就完事了。

 

好了,上代码。

    public sealed class MPBackTask : IBackgroundTask
    {
        public async void Run(IBackgroundTaskInstance taskInstance)
        {

                    。。。。。。。。。。。。。。。。。。

        }

        private async Task TranscodeAsync(IBackgroundTaskInstance taskInst, MediaProcessingTriggerDetails d)
        {
                。。。。。。。。。。。。。。。。。。。。。。。。。
        }
    }

Run方法是必须实现的,因为接口中包含了该方法,而TranscodeAsync方法是我自定义的,负责完成转码工作。

 

先看Run方法,

        public async void Run(IBackgroundTaskInstance taskInstance)
        {
            var deferral = taskInstance.GetDeferral();
            var details = taskInstance.TriggerDetails as MediaProcessingTriggerDetails;

            try
            {
                // 向应用设置写入一个标记,表示后台正在处理转码
                // 数据内容随意
                ApplicationData.Current.LocalSettings.Values["running"] = "y";

                await TranscodeAsync(taskInstance, details);
                ShowNotification("后台转码成功。");
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"异常:{ex.Message}");
            }
            finally
            {
                // 删除设置中的标记
                var settingcont = ApplicationData.Current.LocalSettings;
                if (settingcont.Values.ContainsKey("running"))
                {
                    settingcont.Values.Remove("running");
                }
                deferral.Complete();
            }
        }

从taskInstance.TriggerDetails属性中你可以得到一个MediaProcessingTriggerDetails实例,有啥用呢,它可以接收从前台应用程序发送来的参数。比如输入文件,输出文件等,因为选择文件的操作肯定在前台完成的,选择文件后,在触发后台任务时传递进来。

为了让前台应用能够确认这个后台任务是否正在执行转码,我这里巧用一下应用程序设置容器,这些数据是保存在注册表中的,当你的应用被卸载时,系统会自动清理注册表,所以RT应用是很干净的,别担心。

我的做法是,在执行转码时,向设置项中加一个叫running的项,value随便,因为我不是判断值的,而是看key的,在执行转码前向设置容器加上这个key,当转码完成后再把这个key干掉。这样一来,在前台应用程序中,如果发现有这个key就说明转码还在后台干活,如果没有这个key,表明转码完成了。

 

好,现在来看看 TranscodeAsync 方法,我让它返回Task,表示它可以异步等待,因为要接收从前台发来的参数,所以,用一个d参数来引用MediaProcessingTriggerDetails实例。那为什么还要一个参数来引用IBackgroundTaskInstance呢,这是为了报告处理进度,如果前台程序觉得有必要监视进度时,可以向前台应用报告进度。

代码如下。

        private async Task TranscodeAsync(IBackgroundTaskInstance taskInst, MediaProcessingTriggerDetails d)
        {
            // 取出传进来的参数
            string inputpath = d.Arguments["input_path"] as string;
            string outputpath = d.Arguments["output_path"] as string;

            if (inputpath == null || outputpath == null)
            {
                return;
            }

            StorageFile inputfile = await StorageFile.GetFileFromPathAsync(inputpath);
            StorageFile outputfile = await StorageFile.GetFileFromPathAsync(outputpath);

            MediaTranscoder transcoder = new MediaTranscoder();
            MediaEncodingProfile profile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High);
            // 准备转码
            PrepareTranscodeResult result = await transcoder.PrepareFileTranscodeAsync(inputfile, outputfile, profile);

            if (result.CanTranscode)
            {
                Progress<double> progress = new Progress<double>(p =>
                 {
                     taskInst.Progress = Convert.ToUInt32(p);
                 });
                // 转码
                await result.TranscodeAsync().AsTask(progress);
            }
        }

转码的过程我就不多说了,一样的,这里我把wav格式转为mp3格式,注意你输入的wav文件不能是DTS,因为有些DTS音频文件也是把后缀弄成wav,其实与wav不同,所以要用正常的wav文件,如果是DTS,转码后的mp3文件,你只能听到“沙沙沙”的声音。

关键还是介绍一下如何获取参数,MediaProcessingTriggerDetails有一个Arguments属性,其实是一个字典数据结构的东东,key是字符串,value是object类型,因为我在前台已经把输入和输出文件作为参数传过来,所以这里可以直接从参数中得到输入和输出的文件。

            string inputpath = d.Arguments["input_path"] as string;
            string outputpath = d.Arguments["output_path"] as string;

然后用GetFileFromPathAsync静态方法从路径生成StorageFile实例,就可以读写了。

 

顺便说说,官方的SDK示例中,它是用应用设置容器来传递的,就是用户选好文件后,把文件路径存到设置项中,然后在后台中读取。而我这里用的是参数传递的方法,达到的效果一样。

 

后台完成后,在主启动项目中引用后台项目,记得了,别忘了引用。然后打开清单文件,在[声明]选项卡中添加一个后台任务,类型勾选“媒体处理”,别勾错了,常规和系统事件都会发生异常。XML代码如下:

        <Extension Category="windows.backgroundTasks" EntryPoint="BTM.MPBackTask">
          <BackgroundTasks>
            <uap:Task Type="mediaProcessing" />
          </BackgroundTasks>
        </Extension>

 

最后,就是在前台代码中注册后台了。因为这个后台任务只是为了完成转码工作的,并不需要长期存在,我的想法是,最好在需要转码时注册,不用时直接取消注册。另外一个原因就是,用来触发这个后台的类只能使用一次。当开始新的转码任务时,也要重新实例化一个的。

后台转码用的是 MediaProcessingTrigger 触发器类,而且,触发后台时也是要用这个类的RequestAsync方法,只要这个方法被调用,后台任务就会触发执行。可是,你懂的,Trigger是在注册后台任务时,通过BackgroundTaskBuilder类的SetTrigger方法来设置的。正因为如此,当你每次实例化一个MediaProcessingTrigger时,都必须注册一次后台任务,否则无法与后台任务关联。但是,已经注册的后台任务是不能重复注册的,所以唯一的做法就是每用一次注册一次。

        private MediaProcessingTrigger SetBackgroundTaskForTranscode()
        {
            BackgroundTaskBuilder tb = new BackgroundTaskBuilder();
            tb.Name = "btc";
            tb.TaskEntryPoint = $"{nameof(BTM)}.{nameof(BTM.MPBackTask)}";
            MediaProcessingTrigger trigger = new MediaProcessingTrigger();
            tb.SetTrigger(trigger);
            foreach (var bt in BackgroundTaskRegistration.AllTasks)
            {
                if (bt.Value.Name == "btc")
                {
                    bt.Value.Unregister(true);
                    break;
                }
            }
            var r = tb.Register();
            r.Progress += onProgress;
            r.Completed += onCompleted;
            return trigger;
        }

以上方法就是用于注册后台任务的,注册后,并把关联的 MediaProcessingTrigger实例返回,以供其他代码来触发。

 

下面代码将触发后台任务,执行转码。

            if (ApplicationData.Current.LocalSettings.Values.ContainsKey("running"))
            {
                tbmsg.Text = "有转码任务正在运行,请稍等。";
                return;
            }

            btnStart.IsEnabled = false;
            MediaProcessingTrigger mdproctrigger = SetBackgroundTaskForTranscode();

            MediaProcessingTriggerResult res = await mdproctrigger.RequestAsync(argstobtproc);
            switch (res)
            {
                case MediaProcessingTriggerResult.Allowed:
                    tbmsg.Text = "已启动后台转码。";
                    break;
                case MediaProcessingTriggerResult.DisabledByPolicy:
                    tbmsg.Text = "你的山寨机禁止后台运行。";
                    break;
                default:
                    tbmsg.Text = "对不起,发生灵异事件。";
                    break;
            }

还记得吧,前面在实现后台任务时,我向设置容器中写了个running的项, 这里可以检查一下,如果running存在,说明后台转码还在进行,就不要重开启操作。

RequestAsync方法调用时,可以把要传给后台的参数放进去,即一个ValueSet实例,刚才说了,其实它是一个字典。

 

最后,还有一个很关键的,就是访问文件的权限。RT应用不像传统桌面应用那样你可以毫不顾忌地访问各种文件。RT库对文件的访问权限有严格的控制。用户通过picker选择了一个文件,但是,这个StorageFile无法直接传给后台,因此只能把这个文件的path传到后台,再在后台中用GetFileFromPathAsync方法再获取StorageFile实例。然而,问题来了,如何让后台任务代码具备访问权限呢?

没事,SDK既然想到严格的安全控制,当然就会有解决方案。在Windows.Storage.AccessCache命名空间下,有专门的类,可以用来授权。

用户通过操作选择了文件后,会得到一个StorageFile实例,我们只需要把这个StorageFile实例 Add 到StorageApplicationPermissions类的任何一个属性的集合中就可以了。

其实,StorageApplicationPermissions类就两个属性——FutureAccessList 和 MostRecentlyUsedList,这两个属性用法是一样的,只是意思不同罢了。MostRecentlyUsedList 是过去式,表示文件访问历史;FutureAccessList 是未来式,表示即将要用到的列表。

所以,无论你把文件添加到哪个属性中,应用程序就具备该文件的访问权了。

有人会说,这不是多此一举吗,还要用这个来授权。非也,表面上看好像是多余的,但你想想,文件是如何得到的?就是通过Picker让用户自愿选择的,是吧,这就对了,如果用户不想让你访问那个文件,他就不选;只有当用户自己选了,你的代码才能访问到那个文件,才能把这个文件加入到FutureAccessList列表中。所以嘛,这样的处理不流氓,很有人品,很有德行。

下面代码把输入文件加入授权列表,输出文件的原理一样。

            FileOpenPicker picker = new FileOpenPicker();
            picker.FileTypeFilter.Add(".wav");
            StorageFile wavefile = await picker.PickSingleFileAsync();

            if (wavefile != null)
            {
                // 显示路径
                tbInputfile.Text = wavefile.Path;
                // 把该文件放入文件访问列表中,以便后台使用
                // 授权,否则后台访问不了
                StorageApplicationPermissions.FutureAccessList.Add(wavefile);
                // 写入参数列表
                argstobtproc["input_path"] = wavefile.Path;
            }

 

好了,大事做完了,看看效果吧。

 

===============================

本示例源码下载

 

posted @ 2016-03-09 18:25  东邪独孤  阅读(1031)  评论(2编辑  收藏  举报