zoukankan      html  css  js  c++  java
  • 【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;
                }

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

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

    本示例源码下载

  • 相关阅读:
    Loadrunner:场景运行较长时间后报错:Message id [-17999] was not saved
    JMeter学习(三十二)属性和变量
    LoadRunner参数化MySQL
    Linux下安装使用NMON监控、分析系统性能
    使用Loadrunner进行http接口压力测试
    Windows远程桌面连接Ubuntu 14.04
    oracle转Mysql中,varchar2(10)和number应该转换为什么类型?
    centos下pip安装mysql_python
    JMeter中返回Json数据的处理方法
    如何使用AutoIT完成单机测试
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/5259169.html
Copyright © 2011-2022 走看看