最近公司正在做关于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 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;
}
可以看到代码中构建了一个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_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;
}
可以看到OnAction方法,此方法会在mediaRender设备接收到事件时调用
/*----------------------------------------------------------------------
| PLT_MediaRenderer::OnNext
+---------------------------------------------------------------------*/
NPT_Result
PLT_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方法代码如下:
void
IMediaActionReceiver::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_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;
}
按照这些步骤,就能构建自己的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>