DLNA android关于Platinum库的dmr底层c++代码实现
来源:互联网 发布:数据传输线 编辑:程序博客网 时间:2024/05/21 21:38
最近公司正在做关于DLNA的项目,初期在网上大概了解了一些DLNA相关知识,可以用来开发的框架有Platinum,Cling和Cybergarage。同学们可以选择适合自己的框架进行学习开发,至于为什么选择Platinum库是因为他是一个跨平台的C++库,效率较高,稳定性也很强。
下载地址:http://sourceforge.net/projects/platinum/
网上的示例帖子很少,有一大部分都是蓝斯老师的,在这里要感谢他。下面开始讲解利用Platinum库开发步骤:
1.NDK下的降Platinum库编译成so文件
大家可以参考这篇博文http://blog.csdn.net/lancees/article/details/8789678,讲解的很详细了。
下载的Platinum为1-0-5-13_0ab854版本,博文中提到的将LOCAL_LDLIBS += -laxTls改为LOCAL_LDLIBS += -laxTLS新版本已经修复了。
2.Platinum库c++的实现
大家可以在目录Platinum\Source\Platform\Android\module\platinum\jni\下找到platinum-jni.cpp,这里边有示例代码:
/*****************************************************************|| Android JNI Interface|| (c) 2002-2012 Plutinosoft LLC| Author: Sylvain Rebaud (sylvain@plutinosoft.com)| ****************************************************************//*----------------------------------------------------------------------| includes+---------------------------------------------------------------------*/#include <assert.h>#include <jni.h>#include <string.h>#include <sys/types.h>#include "platinum-jni.h"#include "Platinum.h"#include <android/log.h>/*----------------------------------------------------------------------| logging+---------------------------------------------------------------------*/NPT_SET_LOCAL_LOGGER("platinum.android.jni")/*----------------------------------------------------------------------| functions+---------------------------------------------------------------------*/__attribute__((constructor)) static void onDlOpen(void){}/*----------------------------------------------------------------------| JNI_OnLoad+---------------------------------------------------------------------*/JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){ NPT_LogManager::GetDefault().Configure("plist:.level=FINE;.handlers=ConsoleHandler;.ConsoleHandler.outputs=2;.ConsoleHandler.colors=false;.ConsoleHandler.filter=59"); return JNI_VERSION_1_4;}/* * Class: com_plutinosoft_platinum_UPnP * Method: _init * Signature: ()J */JNIEXPORT jlong JNICALL Java_com_plutinosoft_platinum_UPnP__1init(JNIEnv *env, jclass){ NPT_LOG_INFO("init"); PLT_UPnP* self = new PLT_UPnP(); return (jlong)self;}/* * Class: com_plutinosoft_platinum_UPnP * Method: _start * Signature: (J)I */JNIEXPORT jint JNICALL Java_com_plutinosoft_platinum_UPnP__1start(JNIEnv *, jclass, jlong _self){ NPT_LOG_INFO("start"); PLT_UPnP* self = (PLT_UPnP*)_self; return self->Start();}/* * Class: com_plutinosoft_platinum_UPnP * Method: _stop * Signature: (J)I */JNIEXPORT jint JNICALL Java_com_plutinosoft_platinum_UPnP__1stop(JNIEnv *, jclass, jlong _self){ NPT_LOG_INFO("stop"); PLT_UPnP* self = (PLT_UPnP*)_self; return self->Stop();}
编译后你会发现没有办法开启一个设备,因为PLT_UPnP中并没有加入PLT_DeviceHostReference对象,所以不能开启设备。但是在\Platinum\Source\Tests\MediaRenderer目录下大家可以找到MediaRendererTest.cpp文件:
/*****************************************************************|| Platinum - Test UPnP A/V MediaRenderer|| Copyright (c) 2004-2010, Plutinosoft, LLC.| All rights reserved.| http://www.plutinosoft.com|| This program is free software; you can redistribute it and/or| modify it under the terms of the GNU General Public License| as published by the Free Software Foundation; either version 2| of the License, or (at your option) any later version.|| OEMs, ISVs, VARs and other distributors that combine and | distribute commercially licensed software with Platinum software| and do not wish to distribute the source code for the commercially| licensed software under version 2, or (at your option) any later| version, of the GNU General Public License (the "GPL") must enter| into a commercial license agreement with Plutinosoft, LLC.| licensing@plutinosoft.com| | This program is distributed in the hope that it will be useful,| but WITHOUT ANY WARRANTY; without even the implied warranty of| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the| GNU General Public License for more details.|| You should have received a copy of the GNU General Public License| along with this program; see the file LICENSE.txt. If not, write to| the Free Software Foundation, Inc., | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.| http://www.gnu.org/licenses/gpl-2.0.html| ****************************************************************//*----------------------------------------------------------------------| includes+---------------------------------------------------------------------*/#include "PltUPnP.h"#include "PltMediaRenderer.h"#include <stdlib.h>/*----------------------------------------------------------------------| globals+---------------------------------------------------------------------*/struct Options { const char* friendly_name;} Options;/*----------------------------------------------------------------------| PrintUsageAndExit+---------------------------------------------------------------------*/static voidPrintUsageAndExit(char** args){ fprintf(stderr, "usage: %s [-f <friendly_name>]\n", args[0]); fprintf(stderr, "-f : optional upnp server friendly name\n"); fprintf(stderr, "<path> : local path to serve\n"); exit(1);}/*----------------------------------------------------------------------| ParseCommandLine+---------------------------------------------------------------------*/static voidParseCommandLine(char** args){ const char* arg; char** tmp = args+1; /* default values */ Options.friendly_name = NULL; while ((arg = *tmp++)) { if (!strcmp(arg, "-f")) { Options.friendly_name = *tmp++; } else { fprintf(stderr, "ERROR: too many arguments\n"); PrintUsageAndExit(args); } }}/*----------------------------------------------------------------------| main+---------------------------------------------------------------------*/intmain(int /* argc */, char** argv){ PLT_UPnP upnp; /* parse command line */ ParseCommandLine(argv); PLT_DeviceHostReference device( new PLT_MediaRenderer(Options.friendly_name?Options.friendly_name:"Platinum Media Renderer", false, "e6572b54-f3c7-2d91-2fb5-b757f2537e21")); upnp.AddDevice(device); bool added = true; upnp.Start(); char buf[256]; while (gets(buf)) { if (*buf == 'q') break; if (*buf == 's') { if (added) { upnp.RemoveDevice(device); } else { upnp.AddDevice(device); } added = !added; } } upnp.Stop(); return 0;}
可以看到代码中构建了一个PLT_DeviceHostReference对象,然后用upnp.AddDevice(device)添加进去,就可以正常开启一个dmr设备了。(我在实现自己的platinum-jni时,编译会报undefined reference to `PLT_MediaRenderer::PLT_MediaRenderer(char const*, bool, char const*, unsigned int, bool)的错误,可以尝试在mk文件中添加LOCAL_C_INCLUDES += $(PLT_SRC_ROOT)/Devices/MediaRenderer,问题解决)。
接下来我们要做的事情就是将c++中接受到的action事件反射到java层进行处理:
此时我们就要去\Platinum\Source\Devices\MediaRenderer目录下找PltMediaRenderer.cpp文件:
/*----------------------------------------------------------------------| PLT_MediaRenderer::OnAction+---------------------------------------------------------------------*/NPT_ResultPLT_MediaRenderer::OnAction(PLT_ActionReference& action, const PLT_HttpRequestContext& context){ NPT_COMPILER_UNUSED(context); /* parse the action name */ NPT_String name = action->GetActionDesc().GetName(); // since all actions take an instance ID and we only support 1 instance // verify that the Instance ID is 0 and return an error here now if not NPT_String serviceType = action->GetActionDesc().GetService()->GetServiceType(); if (serviceType.Compare("urn:schemas-upnp-org:service:AVTransport:1", true) == 0) { if (NPT_FAILED(action->VerifyArgumentValue("InstanceID", "0"))) { action->SetError(718, "Not valid InstanceID"); return NPT_FAILURE; } }serviceType = action->GetActionDesc().GetService()->GetServiceType();if (serviceType.Compare("urn:schemas-upnp-org:service:RenderingControl:1", true) == 0) {if (NPT_FAILED(action->VerifyArgumentValue("InstanceID", "0"))) {action->SetError(702, "Not valid InstanceID");return NPT_FAILURE;}}/* Is it a ConnectionManager Service Action ? */if (name.Compare("GetCurrentConnectionInfo", true) == 0) {return OnGetCurrentConnectionInfo(action);} /* Is it a AVTransport Service Action ? */ if (name.Compare("Next", true) == 0) { return OnNext(action); } if (name.Compare("Pause", true) == 0) { return OnPause(action); } if (name.Compare("Play", true) == 0) { return OnPlay(action); } if (name.Compare("Previous", true) == 0) { return OnPrevious(action); } if (name.Compare("Seek", true) == 0) { return OnSeek(action); } if (name.Compare("Stop", true) == 0) { return OnStop(action); } if (name.Compare("SetAVTransportURI", true) == 0) { return OnSetAVTransportURI(action); } if (name.Compare("SetPlayMode", true) == 0) { return OnSetPlayMode(action); } /* Is it a RendererControl Service Action ? */ if (name.Compare("SetVolume", true) == 0) { return OnSetVolume(action); }if (name.Compare("SetVolumeDB", true) == 0) {return OnSetVolumeDB(action); }if (name.Compare("GetVolumeDBRange", true) == 0) {return OnGetVolumeDBRange(action);} if (name.Compare("SetMute", true) == 0) { return OnSetMute(action); } // other actions rely on state variables NPT_CHECK_LABEL_WARNING(action->SetArgumentsOutFromStateVariable(), failure); return NPT_SUCCESS;failure: action->SetError(401,"No Such Action."); return NPT_FAILURE;}
可以看到OnAction方法,此方法会在mediaRender设备接收到事件时调用
/*----------------------------------------------------------------------| PLT_MediaRenderer::OnNext+---------------------------------------------------------------------*/NPT_ResultPLT_MediaRenderer::OnNext(PLT_ActionReference& action){ if (m_Delegate) { return m_Delegate->OnNext(action); } return NPT_ERROR_NOT_IMPLEMENTED;}
接着会调PltMediaRenderer的各种OnXXX(PLT_ActionReference& action)方法,在这里我们可以看到把action事件交给了m_Delegate来处理,所以我们要手动设置一个m_Delegate。在\Platinum\Source\Devices\MediaRenderer目录下有PltMediaRenderer.h头文件,定义了一个方法:
// methods virtual void SetDelegate(PLT_MediaRendererDelegate* delegate) { m_Delegate = delegate; }PLT_MediaRenderDelegate* delegate是从这个方法里边设置进来的,所以我们需要继承PLT_MediaRenderDelagate来定义一个自己的子类,代码如下:
class IMediaActionReceiver : public PLT_MediaRendererDelegate{ public: static const int MEDIA_RENDER_CTL_MSG_BASE = 0x100; static const int MEDIA_RENDER_CTL_MSG_SET_AV_URL = (MEDIA_RENDER_CTL_MSG_BASE+0); static const int MEDIA_RENDER_CTL_MSG_STOP = (MEDIA_RENDER_CTL_MSG_BASE+1); static const int MEDIA_RENDER_CTL_MSG_PLAY = (MEDIA_RENDER_CTL_MSG_BASE+2); static const int MEDIA_RENDER_CTL_MSG_PAUSE = (MEDIA_RENDER_CTL_MSG_BASE+3); static const int MEDIA_RENDER_CTL_MSG_SEEK = (MEDIA_RENDER_CTL_MSG_BASE+4); static const int MEDIA_RENDER_CTL_MSG_SETVOLUME = (MEDIA_RENDER_CTL_MSG_BASE+5); static const int MEDIA_RENDER_CTL_MSG_SETMUTE = (MEDIA_RENDER_CTL_MSG_BASE+6); static const int MEDIA_RENDER_CTL_MSG_SETPLAYMODE = (MEDIA_RENDER_CTL_MSG_BASE+7); static const int MEDIA_RENDER_CTL_MSG_PRE = (MEDIA_RENDER_CTL_MSG_BASE+8); static const int MEDIA_RENDER_CTL_MSG_NEXT = (MEDIA_RENDER_CTL_MSG_BASE+9); public: virtual void ActionInflect(int cmd, const char* value, const char* data); virtual NPT_Result OnGetCurrentConnectionInfo(PLT_ActionReference& action); // AVTransport virtual NPT_Result OnNext(PLT_ActionReference& action); virtual NPT_Result OnPause(PLT_ActionReference& action); virtual NPT_Result OnPlay(PLT_ActionReference& action); virtual NPT_Result OnPrevious(PLT_ActionReference& action); virtual NPT_Result OnSeek(PLT_ActionReference& action); virtual NPT_Result OnStop(PLT_ActionReference& action); virtual NPT_Result OnSetAVTransportURI(PLT_ActionReference& action); virtual NPT_Result OnSetPlayMode(PLT_ActionReference& action); // RenderingControl virtual NPT_Result OnSetVolume(PLT_ActionReference& action); virtual NPT_Result OnSetVolumeDB(PLT_ActionReference& action); virtual NPT_Result OnGetVolumeDBRange(PLT_ActionReference& action); virtual NPT_Result OnSetMute(PLT_ActionReference& action);};
在相关代码中实现此类并且调用自己的ActionInflect方法来进行处理:
NPT_Result IMediaActionReceiver::OnNext(PLT_ActionReference& action){ NPT_String curURI; action->GetArgumentValue("CurrentURI", curURI); NPT_String metaData ; action->GetArgumentValue("CurrentURIMetaData", metaData); ActionInflect(MEDIA_RENDER_CTL_MSG_NEXT, curURI.GetChars(), metaData.GetChars()); return NPT_SUCCESS;}ActionInflect方法代码如下:
voidIMediaActionReceiver::ActionInflect(int cmd, const char* value, const char* data){NPT_LOG_INFO("------------------->@@@@@@@############.\n");int status;JNIEnv *env = NULL;bool isAttach = false;status = jvm->GetEnv((void**) &env, JNI_VERSION_1_4);if(status != JNI_OK) {status = jvm->AttachCurrentThread(&env, NULL);if(status < 0) {return;}isAttach = true;}jstring valueString = NULL;jstring dataString = NULL;jclass inflectClass = g_inflectClass;jmethodID inflectMethod = g_methodID;if (inflectClass == NULL || inflectMethod == NULL){ goto end;}valueString = env->NewStringUTF(value);dataString = env->NewStringUTF(data);env->CallStaticVoidMethod(inflectClass, inflectMethod,valueString, dataString,cmd);env->DeleteLocalRef(valueString);env->DeleteLocalRef(dataString);end:if (env->ExceptionOccurred()){env->ExceptionDescribe();env->ExceptionClear();}if (isAttach){jvm->DetachCurrentThread();}}其中jvm可以在JNI_OnLoad方法中得到,g_inflectClass跟g_methodID是你java层反射函数的Class跟方法ID(根据自身需求来定)。
下一步我们需要将事件状态值更新至所在服务列表
可以参考代码:
NPT_ResultPLT_MediaRenderer::SetupServices(){ PLT_Service* service; { /* AVTransport */ service = new PLT_Service( this, "urn:schemas-upnp-org:service:AVTransport:1", "urn:upnp-org:serviceId:AVTransport", "AVTransport", "urn:schemas-upnp-org:metadata-1-0/AVT/"); NPT_CHECK_FATAL(service->SetSCPDXML((const char*) RDR_AVTransportSCPD)); NPT_CHECK_FATAL(AddService(service)); service->SetStateVariableRate("LastChange", NPT_TimeInterval(0.2f)); service->SetStateVariable("A_ARG_TYPE_InstanceID", "0"); // GetCurrentTransportActions service->SetStateVariable("CurrentTransportActions", "Play,Pause,Stop,Seek,Next,Previous"); // GetDeviceCapabilities service->SetStateVariable("PossiblePlaybackStorageMedia", "NONE,NETWORK,HDD,CD-DA,UNKNOWN"); service->SetStateVariable("PossibleRecordStorageMedia", "NOT_IMPLEMENTED"); service->SetStateVariable("PossibleRecordQualityModes", "NOT_IMPLEMENTED"); // GetMediaInfo service->SetStateVariable("NumberOfTracks", "0"); service->SetStateVariable("CurrentMediaDuration", "00:00:00"); service->SetStateVariable("AVTransportURI", ""); service->SetStateVariable("AVTransportURIMetadata", "");; service->SetStateVariable("NextAVTransportURI", "NOT_IMPLEMENTED"); service->SetStateVariable("NextAVTransportURIMetadata", "NOT_IMPLEMENTED"); service->SetStateVariable("PlaybackStorageMedium", "NONE"); service->SetStateVariable("RecordStorageMedium", "NOT_IMPLEMENTED");service->SetStateVariable("RecordMediumWriteStatus", "NOT_IMPLEMENTED"); // GetPositionInfo service->SetStateVariable("CurrentTrack", "0"); NPT_Result durResult = service->SetStateVariable("CurrentTrackDuration", "00:00:00"); service->SetStateVariable("CurrentTrackMetadata", ""); service->SetStateVariable("CurrentTrackURI", ""); NPT_Result relTimeResult = service->SetStateVariable("RelativeTimePosition", "00:00:00"); service->SetStateVariable("AbsoluteTimePosition", "00:50:00"); service->SetStateVariable("RelativeCounterPosition", "2147483647"); // means NOT_IMPLEMENTED service->SetStateVariable("AbsoluteCounterPosition", "2147483647"); // means NOT_IMPLEMENTED // disable indirect eventing for certain state variables PLT_StateVariable* var; var = service->FindStateVariable("RelativeTimePosition"); if (var) var->DisableIndirectEventing(); var = service->FindStateVariable("AbsoluteTimePosition"); if (var) var->DisableIndirectEventing(); var = service->FindStateVariable("RelativeCounterPosition"); if (var) var->DisableIndirectEventing(); var = service->FindStateVariable("AbsoluteCounterPosition"); if (var) var->DisableIndirectEventing(); // GetTransportInfo service->SetStateVariable("TransportState", "NO_MEDIA_PRESENT"); service->SetStateVariable("TransportStatus", "OK"); service->SetStateVariable("TransportPlaySpeed", "1"); // GetTransportSettings service->SetStateVariable("CurrentPlayMode", "NORMAL"); service->SetStateVariable("CurrentRecordQualityMode", "NOT_IMPLEMENTED"); } { /* ConnectionManager */ service = new PLT_Service( this, "urn:schemas-upnp-org:service:ConnectionManager:1", "urn:upnp-org:serviceId:ConnectionManager", "ConnectionManager"); NPT_CHECK_FATAL(service->SetSCPDXML((const char*) RDR_ConnectionManagerSCPD)); NPT_CHECK_FATAL(AddService(service)); service->SetStateVariable("CurrentConnectionIDs", "0"); // put all supported mime types here instead service->SetStateVariable("SinkProtocolInfo", "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO,http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_SP_G726,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMDRM_WMABASE,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPLL_BASE,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC_XAC3,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMDRM_WMVSPLL_BASE,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_BASE,http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_ASP_L5_SO_G726,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL_XAC3,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAPRO,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN,http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_ASP_L4_SO_G726,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3X,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_MP3,http-get:*:video/x-ms-wmv:*,http-get:*:video/mp4:*"); service->SetStateVariable("SourceProtocolInfo", ""); } { /* RenderingControl */ service = new PLT_Service( this, "urn:schemas-upnp-org:service:RenderingControl:1", "urn:upnp-org:serviceId:RenderingControl", "RenderingControl", "urn:schemas-upnp-org:metadata-1-0/RCS/"); NPT_CHECK_FATAL(service->SetSCPDXML((const char*) RDR_RenderingControlSCPD)); NPT_CHECK_FATAL(AddService(service)); service->SetStateVariableRate("LastChange", NPT_TimeInterval(0.2f)); service->SetStateVariable("Mute", "0"); service->SetStateVariableExtraAttribute("Mute", "Channel", "Master"); service->SetStateVariable("Volume", "100"); service->SetStateVariableExtraAttribute("Volume", "Channel", "Master"); service->SetStateVariable("VolumeDB", "0"); service->SetStateVariableExtraAttribute("VolumeDB", "Channel", "Master"); service->SetStateVariable("PresetNameList", "FactoryDefaults"); } return NPT_SUCCESS;}
按照这些步骤,就能构建自己的dmr。说明一下自己在开发中遇到的一个问题,在构建自己的PLT_MediaRender时:
JNIEXPORT jint JNICALL Java_com_geniusgithub_mediarender_jni_PlatinumJniProxy__1startMediaRender(JNIEnv *env, jclass, jlong _self, jbyteArray _jbyteArrName,jbyteArray _jbyteArrayUUID){ PLT_UPnP* upnp = (PLT_UPnP*)_self;
char* dmr_name = (char*)env->GetByteArrayElements(_jbyteArrName, 0); char* dmr_uuid = (char*)env->GetByteArrayElements(_jbyteArrayUUID, 0); PLT_MediaRenderer* mediaRender = new PLT_MediaRenderer(dmr_name,false,dmr_uuid,36439,false); PLT_DeviceHostReference device(mediaRender); myDevice = device; upnp->AddDevice(myDevice); upnp->Start(); return 0; }
<span style="font-size:18px;">GetByteArrayElements有可能发生字符乱码情况,把方法中传入的jbyteArray改为jstring(相应的java层也应改变),代码改为:</span>
<pre name="code" class="cpp">char* dmr_name = (char *) env->GetStringUTFChars(name, 0);char* dmr_uuid = (char *) env->GetStringUTFChars(uuid, 0);
<span style="font-size:18px;">乱码问题得到解决。所有要讲解的就到这了,过程中遇到了很多问题,所以写了这篇博文供大家参考,再次谢谢蓝斯博主!</span>
0 0
- DLNA android关于Platinum库的dmr底层c++代码实现
- 基于Platinum库的DMR实现(android)
- 基于Platinum库的DMR实现(android)
- 基于Platinum库的DMR实现(android)
- 基于Platinum库的DMR实现(android)-MediaRender
- DLNA DMR实现
- 关于Platinum库的MediaRender具体C++代码实现探讨
- 关于Platinum库的MediaRender具体C++代码实现探讨
- 基于Platinum库的DLNA开发
- 基于Platinum库的DMS实现(android)
- 基于Platinum库的DMS实现(android)
- 基于Platinum库的DMS实现(android)--Server
- 转-iOS利用Platinum库开发DLNA功能
- iOS开发,如何利用Platinum库开发一个DLNA功能
- Platinum UPnP SDK(xbmc) DLNA
- Android底层调用C代码(JNI实现)
- 关于Object-C 底层实现self isa 的理解
- 关于Object-C 底层实现self isa 的理解
- JS用法(一)
- [2014-09-02]JAVA笔记_增强的for循环、自动拆箱装箱、可变参数
- 六 (6.7)C/C++运行库注意事项 6.8 了解自己的身份
- 我遇到的java笔试题->Swing组件JList的列表数据修改了,如何通知JList改变显示?
- BZOJ 3670 NOI 2014 动物园 变形KMP
- DLNA android关于Platinum库的dmr底层c++代码实现
- Hibernate入门基础
- Https的数据请求的证书设置 CFNetwork SSLHandshake failed (-9806)
- 信 标 b e a c o n 作用,beacon frames
- IE的缓存导致ajax不走后台的问题
- BZOJ 3672 NOI2014 购票 树的分治 NOI2014全AC达成!!!!
- SQL 巧妙的乱序排列
- Android - 时间 日期相关组件
- CSS3 主要知识点复习总结+HTML5 新增标签