关于Platinum库的MediaRender具体C++代码实现探讨
接上篇博文 NDK下 将Platinum SDK 编译成so库 (android - upnp)
讲述了如何利用该代码库编译给android程序调用的so库,其中也提到了,在使用sample-upnp工程来测试生成的so库是无效的
大家比对一下Platinum开发库的Platinum\Source\Platform\Android\module\platinum\jni\platinum-jni.cpp和
Platinum\Source\Tests\MediaRenderer\MediaRendererTest.cpp
platinum-jni.cpp
#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(); }
MediaRendererTest.cpp
#include "PltUPnP.h" #include "PltMediaRenderer.h" #include <stdlib.h> /*---------------------------------------------------------------------- | globals +---------------------------------------------------------------------*/ struct Options { const char* friendly_name; } Options; /*---------------------------------------------------------------------- | PrintUsageAndExit +---------------------------------------------------------------------*/ static void PrintUsageAndExit(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 void ParseCommandLine(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 +---------------------------------------------------------------------*/ int main(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; }
可以看出JNI接口中的PLT_UPnP对象并没有加入PLT_DeviceHostReference对象,所以无法启动设备,只要参照MediaRenderTest.cpp的做法我们就可以实现一个dmr设备的开启与关闭了,童鞋们可以仿照博文 基于Platinum库的DMR实现(android)当中的jni类来尝试实现一下这两个接口
public static native int startMediaRender(byte[] friendname ,byte[] uuid);
public static native int stopMediaRender();
完成这一步,DMR的实现就已经成功一半了
接下来我们要做的两件事就是
1.将外部的action事件通过反射机制抛给java层处理
2.通过jni将一些事件状态值更新至所在服务列表
先看第一点,每当有action事件到来时,PLT_MediaRenderer的
virtual NPT_Result OnAction(PLT_ActionReference&action,const PLT_HttpRequestContext& context);方法就会被调用
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(); // 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; }
部分事件会由m_Delegate成员变量来处理,也就是 virtual void SetDelegate(PLT_MediaRendererDelegate* delegate) { m_Delegate = delegate; }传进来的
所以我们只需要从PLT_MediaRendererDelegate类派生一个子类,然后将它设进PLT_MediaRenderer并实现相应的action方法并把一些需要的值反射出来即可
class PLT_MediaRendererDelegate { public: virtual ~PLT_MediaRendererDelegate() {} // ConnectionManager virtual NPT_Result OnGetCurrentConnectionInfo(PLT_ActionReference& action) = 0; // AVTransport virtual NPT_Result OnNext(PLT_ActionReference& action) = 0; virtual NPT_Result OnPause(PLT_ActionReference& action) = 0; virtual NPT_Result OnPlay(PLT_ActionReference& action) = 0; virtual NPT_Result OnPrevious(PLT_ActionReference& action) = 0; virtual NPT_Result OnSeek(PLT_ActionReference& action) = 0; virtual NPT_Result OnStop(PLT_ActionReference& action) = 0; virtual NPT_Result OnSetAVTransportURI(PLT_ActionReference& action) = 0; virtual NPT_Result OnSetPlayMode(PLT_ActionReference& action) = 0; // RenderingControl virtual NPT_Result OnSetVolume(PLT_ActionReference& action) = 0; virtual NPT_Result OnSetVolumeDB(PLT_ActionReference& action) = 0; virtual NPT_Result OnGetVolumeDBRange(PLT_ActionReference& action) = 0; virtual NPT_Result OnSetMute(PLT_ActionReference& action) = 0; };
void ActionInflect(int cmd, const char* value, const char* data) { if (g_vm == NULL) { UpnpPrintInfo("g_vm = NULL!!!"); return ; } int status; JNIEnv *env = NULL; bool isAttach = false; status = g_vm->GetEnv((void **) &env, JNI_VERSION_1_6); if(status != JNI_OK) { status = g_vm->AttachCurrentThread(&env, NULL); if(status < 0) { UpnpPrintInfo("callback_handler: failed to attach , current thread, status = %d", status); return; } isAttach = true; } jstring valueString = NULL; jstring dataString = NULL; jclass inflectClass = g_inflectClass; jmethodID inflectMethod = g_methodID; if (inflectClass == NULL || inflectMethod == NULL) { goto end; } // UpnpPrintInfo("CMD = %d\nVALUE = %s\nDATA = %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()) { env->ExceptionDescribe(); env->ExceptionClear(); } if (isAttach) { g_vm->DetachCurrentThread(); } }
再看第二点,如何将事件变量值更新至服务列表
首先通过FindServiceByType方法找到urn:schemas-upnp-org:service:AVTransport:1服务,
然后通过SetStateVariable更新值即可
变量名与值对应关系大家参照dlna文档开发即可
http://download.csdn.net/detail/geniuseoe2012/4969961
重点看standardizeddcps\MediaServer_4 and MediaRenderer_3下的UPnP-av-AVTransport-v3-Service-20101231.pdf文档
代码实现可以参照PLT_MediaRenderer::SetupServices()方法
NPT_Result PLT_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; }
至此代码实现步骤已全部讲解完毕,当然在具体实现过程中多多少少会遇到些问题
根据log信息多问问谷哥度娘,一般都能找到解决方案的
同时在jni层转换C++类型与java类型时一定要注意及时释放资源,不要造成内存泄漏了
另外不要再向楼主索要源码了,编程最重要的是思维
只有自己动手实践了,摸索了,思考了,解决了,自身水平才会提高
否则你永远都只能是code-farmer而无法成为code-designer
授之以鱼不如授之予渔也是蓝老师一贯的教学宗旨,伸手党请自觉绕道
最后感谢大家的支持,我们下节课再见!
more brilliant,Please pay attention to my CSDN blog -->http://blog.csdn.net/geniuseoe2012