首先,需要特别感谢蓝斯老师的资料,我这里有很大一部分是参考蓝斯老师的,附上传送门:http://blog.csdn.net/lancees/article/details/9178385

一、DLNA相关设备说明
  DMS:Digital Media Server的缩写,把本设备内的多媒体文件(自己控制需要共享哪些文件)到DLNA服务端;
  DMP:Digital Media Player的缩写,可以搜索局域网内,所有DMS提供的多媒体内容,并在本设备播放;
  DMC:Digital MediaController的缩写,作为控制DMP搜索播放DMS的内容,与DMP不同,DMC可以控制局域网内的其他设备的DMS和DMP之间的交互,比如,pad作为DMS,机顶盒做DMP,手机用做DMC,三个设备在同一局域网内,手机可以直接控制让机顶盒播放pad里面共享出来的多媒体;
  DMR:Digital Render的缩写,作用:DMP+DMR。
 
  一般来说,播放器中的DLNA功能具有DMS+DMC功能,如PPTV等,也就是说播放器能够找到该局域网下的DMP服务端,同时能把自己的多媒体文件推送到DMP上
  而作为服务端一般会实现DMR功能,即可以播放DMS上的多媒体文件。
  DLNA技术是采用upnp协议进行通信传输。
 
二、UPnP设备工作过程
  UPnP定义了设备之间、设备和控制点、控制点之间通讯的协议。完整的UPnP由设备寻址、设备发现、设备描述、设备控制、事件通知和基于Html的描述界面几部分构成。
 
  1.设备寻址
    uPnP网络的基础就是TCP/IP协议族,UPnP设备能在TCP/IP协议下工作的关键就是正确的设备寻址。一个UPnP设备寻址的一般过程是:首先向 DHCP服务器发送DHCPDISCOVER消息,如果在指定的时间内,设备没有收到DHCPOFFERS回应消息,设备必须使用 Auto-IP完成IP地址的设置。使用Auto-IP时,设备在地址范围169.254/169.16范围中查找空闲的地址。在选中一个地址之后,设备测试此地址是否在使用。如果此地址被占用,则重复查找过程直到找到一个未被占用的地址,此过程的执行需要底层操作系统的支持,地址的选择过程应该是随机的以避免多个设备选择地址时发生多次冲突。为了测试选择的地址是否未被占用,设备必须使用地址分辨协议(ARP)。一个ARP查询请求设置发送者的硬件地址为设备的硬件地址,发送者的IP地址为全0。设备应该侦听ARP查询响应,或者是否存在具有相同IP地址的ARP查询请求。如果发现,设备必须尝试新的地址。
  2.设备发现
    一旦设备连接到网上并且分配了地址,就要进行发现的操作了。设备发现是UPnP网络实现的第一步。设备发现是由简单发现协议SSDP(Simple Service Discovery Protocol)来定义的。在设备发现操作之后,控制点可以发现感兴趣的设备,并使得控制点获得设备能力的描述,同时控制点也可以向设备发送命令,侦听设备状态的改变,并将设备展示给用户。
  3.设备描述
    uPnP网络结构的第二步是设备描述。在控制点发现了一个设备之后,控制点仍然对设备知之甚少,控制点可能仅仅知道设备或服务的uPnP类型,设备的UUID和设备描述的URL地址。为了让控制点更多的了解设备和它的功能或者与设备交互,控制点必须从发现消息中得到设备描述的URL,通过URL取回设备描述。
  4.设备控制
    设备控制是uPnP网络的第三步。在接收设备和服务描述之后,控制点可以向这些服务发出动作,同时控制点也可以轮询服务的状态变量值。发出动作实质上是一种远程过程调用,控制点将动作送到设备服务,在动作完成之后,服务返回相应的结果。
  5.事件通知
    设备事件是uPnP网络的第四步。一个服务的UPnP描述包括服务响应的动作列表和运行时模拟服务状态的变量列表。当这些变量改变时,服务就会发布更新,则控制点就会收到设备事件。
  6.展示
    只要得到了设备的ULR,就可以取得该设备表达页面的ULR,然后可以将此表达纳入用户的本地浏览器上。这部分还包括与用户对话的界面,以及与用户进行会话的处理。
 
三、开发步骤 
  1.下载PlatinumKit开源库
  2.搭建编译环境
    1.编译PlatinumKit库(在PlatinumKit-1-0-4-2/Platinum路径下)
      命令:scons target=xxx build_config=Release
      target可参考Platinum/Build/Targets下的文件夹名,如图1所示。

图1

      例:目标为arm架构的linux平台,则target=arm-unkonwn-linux
        目标为Android平台,则target=arm-android-linux
 
      编译完成会在Platinum\Build\Targets\arm-android-linux\Release目录下生成相关*.a库
      注:具体平台的编译命令可查看该目录下README.txt文档,同时请确保scons工具已安装
    2.编译jni生成so库
      1.下载ndk工具与配置
        1.解压tar
        2.vi etc/profile
        3.增加如下设置
          ANDROID_NDK_ROOT=ndk根目录绝对路径
          export ANDROID_NDK_ROOT
        4.source etc/profile
        5.cd Platinum/Source/Platform/Android/module/platinum/jni
        6.$ANDROID_NDK_ROOT/ndk-build NDK_DEBUG=0
        至此,编译环境的搭建已完成。
    3.基于PlatinumKit实现DMR功能
      1.开启upnp服务,即让DMC设备能够搜索到本设备
      Platinum\Source\Platform\Android\module\platinum\jni\platinum-jni.cpp
 PltMediaRendererDelegate *mediaRenderDelegate;
 PLT_MediaRenderer *mediaRenderer;
 PLT_DeviceHostReference device;
 PLT_UPnP upnp;
 JNIEXPORT jint JNICALL Java_com_pngcui_skyworth_dlna_jni_PlatinumJniProxy_startDlnaMediaRender
         (JNIEnv *env, jclass, jbyteArray name, jbyteArray uuid)
 {
     ;
     jbyte *bytes;
     char *c_name = ConvertJByteaArrayToChars(env, name, bytes);
     jbyte *bytes2;
     char *c_uuid = ConvertJByteaArrayToChars(env, uuid, bytes2); 

     LOGI("name =  %s,uuid = %s",c_name,c_uuid);

     if(mediaRenderDelegate == NULL)
         mediaRenderDelegate = new PltMediaRendererDelegate();

     mediaRenderer= new PLT_MediaRenderer(c_name,false,c_uuid);
     mediaRenderer->SetDelegate(mediaRenderDelegate);

     PLT_DeviceHostReference device1(mediaRenderer);
     device = device1;

     upnp.AddDevice(device);
     ret = upnp.Start();
     env->ReleaseByteArrayElements(name, bytes, JNI_ABORT);
     env->ReleaseByteArrayElements(uuid, bytes2, JNI_ABORT);
     delete c_name;
     delete c_uuid;

         return ret;
 }
      2.获取DMS传递过来的Action请求,并将Action事件反射到java中,即把DMS推送过来的事件传递给APP进行处理,以播放为例
      Platinum\Source\Devices\MediaRenderer\PltMediaRenderer.cpp
 NPT_Result
 PLT_MediaRenderer::OnAction(PLT_ActionReference&          action,
                             const PLT_HttpRequestContext& context)
 {
     NPT_COMPILER_UNUSED(context);

     /* parse the action name */
     NPT_String name = action->GetActionDesc().GetName();
     LOGI("OnAction -> %s",name.GetChars());
     ...
     ) {
         return OnPlay(action);
     }
     ...
     return NPT_FAILURE;
 }
 NPT_Result
 PLT_MediaRenderer::OnPlay(PLT_ActionReference& action)
 {
     if (m_Delegate) {
         return m_Delegate->OnPlay(action);
     }
     return NPT_ERROR_NOT_IMPLEMENTED;
 }
      Platform\Android\module\platinum\jni\PltMediaRendererDelegate.cpp
 NPT_Result
 PltMediaRendererDelegate::OnPlay(PLT_ActionReference& action)
 {
     LOGI("OnPlay");

     NPT_String curURI;
     action->GetArgumentValue("CurrentURI", curURI);
     NPT_String metaData ;
     action->GetArgumentValue("CurrentURIMetaData", metaData);
     ActionInflect(MEDIA_RENDER_CTL_MSG_PLAY, curURI.GetChars(),metaData.GetChars());

     return NPT_SUCCESS;
 }

 void PltMediaRendererDelegate::ActionInflect(int cmd, const char* value, const char* data)
 {
     int status;
     JNIEnv *env = NULL;
     bool isAttach = false;
     if(g_vm == NULL)
         LOGI("g_vm == NULL");
     status = g_vm->GetEnv((void **) &env, JNI_VERSION_1_4);
     )
     {
         status = g_vm->AttachCurrentThread(&env, NULL);
         LOGI("status = %d",status);
         ) {
             LOGI("callback_handler: failed to attach , current thread, status = %d", status);
             return;
         }
         isAttach = true;
     }

     jstring valueString = NULL;
     jstring dataString = NULL; 

     jclass inflectClass = g_inflectClass;
     if (inflectClass == NULL)
     {
         LOGI("inflectClass == NULL return");
     }
     jmethodID inflectMethod = g_methodID;
     if (inflectMethod == NULL)
     {
         LOGI("inflectMethod == NULL return");
     }

     if (inflectClass == NULL || inflectMethod == NULL)
     {
         LOGI("goto end");
         goto end;
     }
     LOGI("CMD = %d VALUE = %s DATA = %s",cmd, value, data);  

     valueString = env->NewStringUTF(value);
     dataString = env->NewStringUTF(data);  

     env->CallStaticVoidMethod(inflectClass, inflectMethod, cmd, valueString, dataString);  

     env->DeleteLocalRef(valueString);
     env->DeleteLocalRef(dataString);  

 end:
     if (env->ExceptionOccurred())
     {
         LOGI("clear...");
         env->ExceptionDescribe();
         env->ExceptionClear();
     }
     if (isAttach)
     {
         g_vm->DetachCurrentThread();
     }
 }  

      注:Android6.0以上版本无法使用 env->CallStaticVoidMethod进行反射,因为google官方对jvm进行了一些列的优化

      然后把获取到的播放路径等信息通过java反射机制传递到APP层中,而其中的反射类与方法需要在主线程中获取,在jni子线程中无法获取,即
      jclass inflectClass = g_inflectClass;与 jmethodID inflectMethod = g_methodID;
      所以需要在platinum-jni.cpp文件的JNI_OnLoad()方法中获取反射类
 void InitInflectClass(JavaVM* vm)
 {
       g_vm = vm;

     JNIEnv *env = NULL;
     int status = g_vm->GetEnv((void **) &env, JNI_VERSION_1_4);
     )
     {
         return ;
     }
     jclass inflectClass = env->FindClass("com/pngcui/skyworth/dlna/jni/PlatinumReflection");
     if (inflectClass == NULL)
     {
         LOGI("inflectClass == NULL return");
         return ;
     }
     g_inflectClass = (jclass)env->NewGlobalRef(inflectClass);
     jmethodID methodID = env->GetStaticMethodID(inflectClass, "onActionReflection", "(ILjava/lang/String;Ljava/lang/String;)V");
     if (methodID == NULL)
     {
         LOGI("methodID == NULL return");
         return ;
     }
     g_methodID = methodID;
     return ;
 }
      3.更新事件服务列表(如播放位置信息,暂停等)
        当DMR开始播放了,每隔1秒,会调用jni中的responseGenaEvent方法更新播放位置
      Platform\Android\module\platinum\jni\platinum-jni.cpp
 /*
  * Class:     com_pngcui_skyworth_dlna_jni_PlatinumJniProxy
  * Method:    responseGenaEvent
  * Signature: (J)I
  */
 JNIEXPORT jboolean JNICALL Java_com_pngcui_skyworth_dlna_jni_PlatinumJniProxy_responseGenaEvent
         (JNIEnv *env, jclass, jint cmd, jbyteArray value, jbyteArray data)
 {
 //    NPT_LOG_INFO("response");
     jbyte *bytes;
     char *c_value = ConvertJByteaArrayToChars(env, value, bytes);
     jbyte *bytes2;
     char *c_data = ConvertJByteaArrayToChars(env, data, bytes2);

     LOGI("ResponseGenaEvent value = %s data = %s",c_value,c_data);

     mediaRenderer->UpdateServices(c_value,c_data);

     env->ReleaseByteArrayElements(value, bytes, JNI_ABORT);
     env->ReleaseByteArrayElements(data, bytes2, JNI_ABORT);
     delete c_value;
     delete c_data;

     return true;
 }
      Platinum\Source\Devices\MediaRenderer\PltMediaRenderer.cpp
 NPT_Result
 PLT_MediaRenderer::UpdateServices(const char* value,const char* data)
 {

     PLT_Service* serviceUpdate;
     if(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", serviceUpdate) == NPT_ERROR_NO_SUCH_ITEM){
         LOGI("cant find PLT_Service.....");
         return NPT_FAILURE;
     }
     ) == ) == ':')
     {
         ) == 'd')
         {
             serviceUpdate->SetStateVariable("CurrentTrackDuration", value);
             serviceUpdate->SetStateVariable("CurrentMediaDuration", value);
         }
         else
         {
             serviceUpdate->SetStateVariable("RelativeTimePosition", value);
             serviceUpdate->SetStateVariable("AbsoluteTimePosition", value);
         }
     }
     else
     {
         serviceUpdate->SetStateVariable("TransportState", value);
     }

     return NPT_SUCCESS;
 }
      注:DMS与DMR在连接时,会向DMR订阅一些事件,当订阅的事件发生改变,DMR会主动向DMS发送一条单播,比如DMR开始播放了会发送包含下列信息
        <Event xmlns="urn:schemas-upnp-org:metadata-1-0/AVT/">
        <InstanceID val="0">
        <TransportState val="PLAYING"/>
        </InstanceID></Event>
      而对于播放的位置信息等经常变动的事件,DMR不会主动向DMS发送广播,需要DMS主动获取,而DMS每隔1秒会向DMR发送getpositioninfo的Action动作
      所以我们只需要更新getpositioninfo这个域内的事件即可。

        而DMR获取到Action后,会调用action->SetArgumentsOutFromStateVariable()方法去获取这些事件的值,继而更新了播放器端的进度等信息。
       Platinum\Source\Devices\MediaRenderer\PltMediaRenderer.cpp
 NPT_Result
 PLT_MediaRenderer::OnAction(PLT_ActionReference&          action,
                             const PLT_HttpRequestContext& context)
 {
 ...
     // other actions rely on state variables
     NPT_CHECK_LABEL_WARNING(action->SetArgumentsOutFromStateVariable(), failure);
     return NPT_SUCCESS;

 failure:
     action->SetError(,"No Such Action.");
     return NPT_FAILURE;
 }

    接下来就是APP播放的实现了,可参考蓝斯老师的博客:http://blog.csdn.net/lancees/article/details/8951679

附DLNA支持的多媒体格式:
  1.video支持格式:
     mp4 mov 3gp 3gpp mkv
  2.audio支持格式
    mp3 flv wav
  3.picture支持格式
    jpg jpeg png bmp
 

基于PlatinumKit库的DLNA服务端开发的更多相关文章

  1. 基于JAX-WS的Web Service服务端/客户端 ;JAX-WS + Spring 开发webservice

    一.基于JAX-WS的Web Service服务端/客户端 下面描述的是在main函数中使用JAX-WS的Web Service的方法,不是在web工程里访问,在web工程里访问,参加第二节. JAX ...

  2. Day01_搭建环境&CMS服务端开发

    学成在线 第1天 讲义-项目概述 CMS接口开发 1 项目的功能构架 1.1 项目背景 受互联网+概念的催化,当今中国在线教育市场的发展可谓是百花齐放.如火如荼. 按照市场领域细分为:学前教育.K12 ...

  3. Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)

    本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...

  4. Swift3.0服务端开发(三) Mustache页面模板与日志记录

    本篇博客主要介绍如果在Perfect工程中引入和使用Mustache页面模板与日志记录系统.Mustache页面模板类似于PHP中的smarty模板引擎或者Java中的JSTL标签.当然Mustach ...

  5. Swift3.0服务端开发(五) 记事本的开发(iOS端+服务端)

    前边以及陆陆续续的介绍了使用Swift3.0开发的服务端应用程序的Perfect框架.本篇博客就做一个阶段性的总结,做一个完整的实例,其实这个实例在<Swift3.0服务端开发(一)>这篇 ...

  6. WCF服务端开发和客户端引用小结

    1.服务端开发 1.1 WCF服务创建方式 创建一个WCF服务,总是会创建一个服务接口和一个服务接口实现.通常根据服务宿主的不同,有两种创建方式. (1)创建WCF应用程序 通过创建WCF服务应用程序 ...

  7. 从架构师视角看是否该用Kotlin做服务端开发?

    前言 自从Oracle收购Sun之后,对Java收费或加强控制的尝试从未间断,谷歌与Oracle围绕Java API的官司也跌宕起伏.虽然Oracle只是针对Oracle JDK8的升级收费,并释放了 ...

  8. 微服务项目开发学成在线_day01_CMS服务端开发

    05-CMS需求分析-什么是CMS 什么是CMS?CMS (Content Management System)即内容管理系统,不同的项目对CMS的定位不同.CMS有哪些类型? 每个公司对每个项目的C ...

  9. 俯瞰 Java 服务端开发

    原文首发于 github ,欢迎 star . Java 服务端开发是一个非常宽广的领域,要概括其全貌,即使是几本书也讲不完,该文将会提到许多的技术及工具,但不会深入去讲解,旨在以一个俯瞰的视角去探寻 ...

随机推荐

  1. THREE笛卡尔右手坐标系详解

    1,正常的笛卡尔右手坐标系,以屏幕右方为+X轴,屏幕上方为+Y轴,垂直屏幕向外为+Z轴,如下图,xy轴组成的平面为屏幕面 但由于THREE里的相机并不总是从屏幕正前方视角,还可以设置坐标系任意一个轴为 ...

  2. File 常用方法

    1.判断当前文件是否封装的文件夹目录 //返回true--是,false--不是 File file =new File("C:\\Users\\mac\\Desktop\\复习.txt&q ...

  3. java 与操作系统同步问题(三)————父亲儿子女儿水果问题

    问题描述:父亲每次都会放一个水果在桌子上,女儿喜欢吃香蕉(只吃香蕉), 儿子喜欢吃苹果(只吃苹果).父亲每次只会随机往桌子上放一个水果(苹果或香蕉),儿子,女儿会来取.使用p.v操作来完成父亲.儿子. ...

  4. Linux常用命令-jdk和Tomcat

    一.JDK的安装和配置 1.下载jdk文件 去官方网站下载Linux 64位  jdk-8u131-linux-x64.tar.gz 2.使用Ftp工具上传到/usr/local 下. 使用命令:ta ...

  5. 玩转Storage Table 的PartitionKey,RowKey设计

    参阅的文章 l  https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/designing-a-scalable ...

  6. javascript基础-闭包

    原理 函数里包含函数,即闭包. 包含函数的结果是,子函数会挟持父函数的活动对象.子函数在访问一个变量时,先读自身的活动对象,是否包含此变量,没有从父函数里找,还没有,去祖函数,层层回溯,直到windo ...

  7. 一天搞定CSS:盒模型content、padding、border、margin--06

    1.盒模型 网页设计中常听的属性名:内容(content).填充(padding).边框(border).边界(margin), CSS盒子模式都具备这些属性. 这些属性我们可以用日常生活中的常见事物 ...

  8. java中的中文参数存到数据库乱码问题

    关于java中的中文参数乱码问题,遇见过很多,若开发工具的字符集环境和数据库的字符集环境都一样,存到数据库中还是乱码的话,可以通过以下方法解决: 用数据库客户端检查每个字段的字符集和字符集校对和这个表 ...

  9. Dojo初探之1:AMD规范,编写符合AMD规范(异步模块加载机制)的模块化JS(其中dojo采用1.11.2版本)

    一.AMD规范探索 1.AMD规范(即异步模块加载机制) 我们在接触js的时候,一般都是通过各种function来定义一些方法,让它们帮我们做一些事情,一个js可以包含很多个js,而这些functio ...

  10. WPF Dashboard仪表盘控件的实现

    1.确定控件应该继承的基类 从表面上看,目前WPF自带常用控件中,没有一个是接近这个表盘控件的,但将该控件拆分就能够发现,该控件的每个子部分都是在WPF中存在的,因此我们需要将各个子控件组合才能形成这 ...