Java中怎么通过调用jna实现语音识别功能

Java中怎么通过调用jna实现语音识别功能,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

创新互联建站凭借在网站建设、网站推广领域领先的技术能力和多年的行业经验,为客户提供超值的营销型网站建设服务,我们始终认为:好的营销型网站就是好的业务员。我们已成功为企业单位、个人等客户提供了成都网站设计、成都网站制作服务,以良好的商业信誉,完善的服务及深厚的技术力量处于同行领先地位。

JNA

java调用.dll获取.so一般通过JNI,但是JNI的使用比较复杂,需要用C另写一个共享库进行适配。而JNA是一个自动适配工具,通过它调用.dll只需要一个借口即可。

官网:https://github.com/twall/jna/。下载jna.jar即可。

编写接口

科大讯飞语音云主要提供语音合成和语音识别两个方面的东西,我主要使用语音识别这块的功能。

建立接口QTSR,继承Library。

将msc.dll等文件复制到项目根目录。

Java中怎么通过调用jna实现语音识别功能

加载msc.dll

QTSR INSTANCE = (QTSR) Native.loadLibrary("msc", QTSR.class);

然后来看一下msc.dll公开了哪些方法。首先是QISRInit,这是一个全局初始化函数。

Java中怎么通过调用jna实现语音识别功能

它的返回值为int,参数是const char*。int还是java的int,但是char*就对应的是java的String了。

所以在QTSR中添加方法:

public int QISRInit(String configs);

返回值在msp_errors.h中定义,等一下我们还是要弄在java里面去。

继续看QISRInit函数,在官方文档中有调用示例:

const char* configs=“server_url=dev.voicecloud.cn, timeout=10000, vad_enable=true”;     int   ret = QISRInit( configs );     if(MSP_SUCCESS != ret )     {             printf( “QISRInit failed, error code is: %d”, ret );     }

对应的在java中的调用代码如下:

String config = "server_url=dev.voicecloud.cn, timeout=10000, vad_enable=true";   int code = QTSR.INSTANCE.QISRInit(config);   if (code != 0) {     System.out.println("QISRInit failed, error code is:" + code);   }

我们在看一个函数:QISRSessionBegin,这个开始一路ISR会话。

Java中怎么通过调用jna实现语音识别功能

还是刚才的思路,char*对应java的String,但是注意一下int *errorCode。这个函数其实传入两个参数,传出两个参数。即本身返回的sessionId,还有errorCode。

这里的int*对应的是jna的IntByReference。所以添加方法:

public String QISRSessionBegin(String grammarList, String params,IntByReference errorCode);

同样看看官方示例:

/* vad_timeout和vad_speech_tail两个参数只有在打开VAD功能时才生效 */ const char*      params=     “ssm=1,sub=iat,aue=speex-wb;7,auf=audio/L16;rate=16000,ent=sms16k,rst=plain,vad_timeout=1000,vad_speech_tail=1000”;   int ret = MSP_SUCCESS;     const char*     session_id = QISRSessionBegin( NULL, params, &ret );     if(MSP_SUCCESS != ret )     {             printf( “QISRSessionBegin failed, error code is: %d”, ret );     }

在java这样写:

String params = "ssm=1,sub=iat,aue=speex-wb;7,auf=audio/L16;rate=16000,ent=sms16k,rst=plain,vad_timeout=1000,vad_speech_tail=1000";   IntByReference errorCode = new IntByReference();   String sessionId = QTSR.INSTANCE.QISRSessionBegin(null, params,errorCode);

运行效果:

Java中怎么通过调用jna实现语音识别功能

其他的函数处理方式大致相同,这里贴上一个c和java在jna中的类型对应表:

Java中怎么通过调用jna实现语音识别功能

其中Unsigned类型和signed在java中对应是一样的。

.h文件和常量处理

在SDK的include目录有4个.h文件,定义了一些常量,比如上面一节中的0其实是msp_errors.h中MSP_SUCCESS。

我以msp_errors.h为例,建立一个接口Msp_errors,继承StdCallLibrary。

照着msp_errors.h中的定义在Msp_errors中进行定义。

public static final int MSP_SUCCESS = 0;   public static final int ERROR_FAIL  = -1;   public static final int ERROR_EXCEPTION= -2;   public static final int ERROR_GENERAL= 10100;      public static final int ERROR_OUT_OF_MEMORY= 10101;        public static final int ERROR_FILE_NOT_FOUND= 10102;       public static final int ERROR_NOT_SUPPORT= 10103;

使用很简单的,比如MSP_SUCCESS 就是Msp_errors.MSP_SUCCESS。

完整代码和文件

这个只是语音识别部分的,语音合成的话我记得有人做过jni接口的。

*QTSR.java

package com.cnblogs.htynkn;     import com.sun.jna.Library;  import com.sun.jna.Native;  import com.sun.jna.Pointer;  import com.sun.jna.ptr.IntByReference;     /**   * @author 夜明的孤行灯   * @date 2012-7-5   */    public interface QTSR extends Library {      QTSR INSTANCE = (QTSR) Native.loadLibrary("msc", QTSR.class);         /**       * 初始化MSC的ISR部分       *       * @param configs       *            初始化时传入的字符串,以指定合成用到的一些配置参数,各个参数以“参数名=参数值”的形式出现,大小写不敏感,不同的参数之间以“       *            ,”或“\n”隔开,不设置任何值时可以传入NULL或空串:       * @return 如果函数调用成功返回MSP_SUCCESS,否则返回错误代码,错误代码参见msp_errors       */     public int QISRInit(String configs);         /**       * 开始一个ISR会话       *       * @param grammarList       *            uri-list格式的语法,可以是一个语法文件的URL或者一个引擎内置语法列表。可以同时指定多个语法,不同的语法之间以“,”       *            隔开。进行语音听写时不需要语法,此参数设定为NULL或空串即可;进行语音识别时则需要语法,语法可以在此参数中指定,       *            也可以随后调用QISRGrammarActivate指定识别所用的语法。       * @param params       *            本路ISR会话使用的参数,可设置的参数及其取值范围请参考《可设置参数列表_MSP20.xls》,各个参数以“参数名=参数值”       *            的形式出现,不同的参数之间以“,”或者“\n”隔开。       * @param errorCode       *            如果函数调用成功则其值为MSP_SUCCESS,否则返回错误代码,错误代码参见msp_errors。几个主要的返回值:       *            MSP_ERROR_NOT_INIT 未初始化 MSP_ERROR_INVALID_PARA 无效的参数       *            MSP_ERROR_NO_LICENSE 开始一路会话失败       * @return MSC为本路会话建立的ID,用来唯一的标识本路会话,供以后调用其他函数时使用。函数调用失败则会返回NULL。       */     public String QISRSessionBegin(String grammarList, String params,              IntByReference errorCode);         /**       * 传入语法       *       * @param sessionID       *            由QISRSessionBegin返回过来的会话ID。       * @param grammar       *            语法字符串       * @param type       *            语法类型,可以是uri-list、abnf、xml等       * @param weight       *            本次传入语法的权重,本参数在MSP 2.0中会被忽略。       * @return 如果函数调用成功返回MSP_SUCCESS,否则返回错误代码,错误代码参见msp_errors       */     public int QISRGrammarActivate(String sessionID, String grammar,              String type, int weight);         /**       * 写入用来识别的语音       *       * @param sessionID       *            由QISRSessionBegin返回过来的会话ID。       * @param waveData       *            音频数据缓冲区起始地址       * @param waveLen       *            音频数据长度,其大小不能超过设定的max_audio_size       * @param audioStatus       *            用来指明用户本次识别的音频是否发送完毕,可能值如下:       *            MSP_AUDIO_SAMPLE_FIRST = 1 第一块音频       *            MSP_AUDIO_SAMPLE_CONTINUE = 2 还有后继音频       *            MSP_AUDIO_SAMPLE_LAST = 4 最后一块音频       * @param epStatus       *            端点检测(End-point detected)器所处的状态,可能的值如下:       *            MSP _EP_LOOKING_FOR_SPEECH = 0 还没有检测到音频的前端点。       *            MSP _EP_IN_SPEECH = 1 已经检测到了音频前端点,正在进行正常的音频处理。       *            MSP _EP_AFTER_SPEECH = 3 检测到音频的后端点,后继的音频会被MSC忽略。       *            MSP _EP_TIMEOUT = 4 超时。       *            MSP _EP_ERROR= 5 出现错误。       *            MSP _EP_MAX_SPEECH = 6 音频过大。       * @param recogStatus       *            识别器所处的状态       * @return       */     public int QISRAudioWrite(String sessionID, Pointer waveData, int waveLen,              int audioStatus, IntByReference epStatus, IntByReference recogStatus);         /**       * 获取识别结果       *       * @param sessionID 由QISRSessionBegin返回过来的会话ID。       * @param rsltStatus 识别结果的状态,其取值范围和含义请参考QISRAudioWrite的参数recogStatus       * @param waitTime 与服务器交互的间隔时间,可以控制和服务器的交互频度。单位为ms,建议取值为5000。       * @param errorCode 如果函数调用成功返回MSP_SUCCESS,否则返回错误代码,错误代码参见msp_errors       * @return 函数执行成功并且获取到识别结果时返回识别结果,函数执行成功没有获取到识别结果时返回NULL       */     public String QISRGetResult(String sessionID, IntByReference rsltStatus,              int waitTime, IntByReference errorCode);         /**       * 结束一路会话       *       * @param sessionID 由QISRSessionBegin返回过来的会话ID。       * @param hints 结束本次会话的原因描述,用于记录日志,便于用户查阅或者跟踪某些问题。       * @return       */     public int QISRSessionEnd(String sessionID, String hints);         /**       * 获取与识别交互相关的参数       *       * @param sessionID 由QISRSessionBegin返回过来的会话ID。       * @param paramName 要获取的参数名称;支持同时查询多个参数,查询多个参数时,参数名称按“,” 或“\n”分隔开来。       * @param paramValue 获取的参数值,以字符串形式返回;查询多个参数时,参数值之间以“;”分开,不支持的参数将返回空的值。       * @param valueLen 参数值的长度。       * @return       */     public int QISRGetParam(String sessionID, String paramName,              String paramValue, IntByReference valueLen);         /**       * 逆初始化MSC的ISR部分       *       * @return       */     public int QISRFini();  }   *Msp_errors  ?  package com.cnblogs.htynkn;     import com.sun.jna.win32.StdCallLibrary;     /**   * @author 夜明的孤行灯   * @date 2012-7-5   */    public interface Msp_errors extends StdCallLibrary {      public static final int MSP_SUCCESS = 0;      public static final int ERROR_FAIL  = -1;      public static final int ERROR_EXCEPTION= -2;      public static final int ERROR_GENERAL= 10100;   /* 0x2774 */     public static final int ERROR_OUT_OF_MEMORY= 10101;     /* 0x2775 */     public static final int ERROR_FILE_NOT_FOUND= 10102;    /* 0x2776 */     public static final int ERROR_NOT_SUPPORT= 10103;   /* 0x2777 */     public static final int ERROR_NOT_IMPLEMENT= 10104;     /* 0x2778 */     public static final int ERROR_ACCESS= 10105;    /* 0x2779 */     public static final int ERROR_INVALID_PARA= 10106;  /* 0x277A */     public static final int ERROR_INVALID_PARA_VALUE= 10107;    /* 0x277B */     public static final int ERROR_INVALID_HANDLE= 10108;    /* 0x277C */     public static final int ERROR_INVALID_DATA= 10109;  /* 0x277D */     public static final int ERROR_NO_LICENSE= 10110;    /* 0x277E */     public static final int ERROR_NOT_INIT= 10111;  /* 0x277F */     public static final int ERROR_NULL_HANDLE= 10112;   /* 0x2780 */     public static final int ERROR_OVERFLOW= 10113;  /* 0x2781 */     public static final int ERROR_TIME_OUT= 10114;  /* 0x2782 */     public static final int ERROR_OPEN_FILE= 10115;     /* 0x2783 */     public static final int ERROR_NOT_FOUND= 10116;     /* 0x2784 */     public static final int ERROR_NO_ENOUGH_BUFFER= 10117;  /* 0x2785 */     public static final int ERROR_NO_DATA= 10118;   /* 0x2786 */     public static final int ERROR_NO_MORE_DATA= 10119;  /* 0x2787 */     public static final int ERROR_NO_RESPONSE_DATA= 10120;  /* 0x2788 */     public static final int ERROR_ALREADY_EXIST= 10121;     /* 0x2789 */     public static final int ERROR_LOAD_MODULE= 10122;   /* 0x278A */     public static final int ERROR_BUSY  = 10123;    /* 0x278B */     public static final int ERROR_INVALID_CONFIG= 10124;    /* 0x278C */     public static final int ERROR_VERSION_CHECK= 10125;     /* 0x278D */     public static final int ERROR_CANCELED= 10126;  /* 0x278E */     public static final int ERROR_INVALID_MEDIA_TYPE= 10127;    /* 0x278F */     public static final int ERROR_CONFIG_INITIALIZE= 10128;     /* 0x2790 */     public static final int ERROR_CREATE_HANDLE= 10129;     /* 0x2791 */     public static final int ERROR_CODING_LIB_NOT_LOAD= 10130;   /* 0x2792 */    /* Error codes of network 10200(0x27D8)*/     public static final int ERROR_NET_GENERAL= 10200;   /* 0x27D8 */     public static final int ERROR_NET_OPENSOCK= 10201;  /* 0x27D9 */   /* Open socket */     public static final int ERROR_NET_CONNECTSOCK= 10202;   /* 0x27DA */   /* Connect socket */     public static final int ERROR_NET_ACCEPTSOCK = 10203;   /* 0x27DB */   /* Accept socket */     public static final int ERROR_NET_SENDSOCK= 10204;  /* 0x27DC */   /* Send socket data */     public static final int ERROR_NET_RECVSOCK= 10205;  /* 0x27DD */   /* Recv socket data */     public static final int ERROR_NET_INVALIDSOCK= 10206;   /* 0x27DE */   /* Invalid socket handle */     public static final int ERROR_NET_BADADDRESS = 10207;   /* 0x27EF */   /* Bad network address */     public static final int ERROR_NET_BINDSEQUENCE= 10208;  /* 0x27E0 */   /* Bind after listen/connect */     public static final int ERROR_NET_NOTOPENSOCK= 10209;   /* 0x27E1 */   /* Socket is not opened */     public static final int ERROR_NET_NOTBIND= 10210;   /* 0x27E2 */   /* Socket is not bind to an address */     public static final int ERROR_NET_NOTLISTEN  = 10211;   /* 0x27E3 */   /* Socket is not listening */     public static final int ERROR_NET_CONNECTCLOSE= 10212;  /* 0x27E4 */   /* The other side of connection is closed */     public static final int ERROR_NET_NOTDGRAMSOCK= 10213;  /* 0x27E5 */   /* The socket is not datagram type */     public static final int ERROR_NET_DNS= 10214;   /* 0x27E6 */   /* domain name is invalid or dns server does not function well */    /* Error codes of mssp message 10300(0x283C) */     public static final int ERROR_MSG_GENERAL= 10300;   /* 0x283C */     public static final int ERROR_MSG_PARSE_ERROR= 10301;   /* 0x283D */     public static final int ERROR_MSG_BUILD_ERROR= 10302;   /* 0x283E */     public static final int ERROR_MSG_PARAM_ERROR= 10303;   /* 0x283F */     public static final int ERROR_MSG_CONTENT_EMPTY= 10304;     /* 0x2840 */     public static final int ERROR_MSG_INVALID_CONTENT_TYPE      = 10305;    /* 0x2841 */     public static final int ERROR_MSG_INVALID_CONTENT_LENGTH    = 10306;    /* 0x2842 */     public static final int ERROR_MSG_INVALID_CONTENT_ENCODE    = 10307;    /* 0x2843 */     public static final int ERROR_MSG_INVALID_KEY= 10308;   /* 0x2844 */     public static final int ERROR_MSG_KEY_EMPTY= 10309;     /* 0x2845 */     public static final int ERROR_MSG_SESSION_ID_EMPTY= 10310;  /* 0x2846 */     public static final int ERROR_MSG_LOGIN_ID_EMPTY= 10311;    /* 0x2847 */     public static final int ERROR_MSG_SYNC_ID_EMPTY= 10312;     /* 0x2848 */     public static final int ERROR_MSG_APP_ID_EMPTY= 10313;  /* 0x2849 */     public static final int ERROR_MSG_EXTERN_ID_EMPTY= 10314;   /* 0x284A */     public static final int ERROR_MSG_INVALID_CMD= 10315;   /* 0x284B */     public static final int ERROR_MSG_INVALID_SUBJECT= 10316;   /* 0x284C */     public static final int ERROR_MSG_INVALID_VERSION= 10317;   /* 0x284D */     public static final int ERROR_MSG_NO_CMD= 10318;    /* 0x284E */     public static final int ERROR_MSG_NO_SUBJECT= 10319;    /* 0x284F */     public static final int ERROR_MSG_NO_VERSION= 10320;    /* 0x2850 */     public static final int ERROR_MSG_MSSP_EMPTY= 10321;    /* 0x2851 */     public static final int ERROR_MSG_NEW_RESPONSE= 10322;  /* 0x2852 */     public static final int ERROR_MSG_NEW_CONTENT= 10323;   /* 0x2853 */     public static final int ERROR_MSG_INVALID_SESSION_ID        = 10324;    /* 0x2854 */    /* Error codes of DataBase 10400(0x28A0)*/     public static final int ERROR_DB_GENERAL= 10400;    /* 0x28A0 */     public static final int ERROR_DB_EXCEPTION= 10401;  /* 0x28A1 */     public static final int ERROR_DB_NO_RESULT= 10402;  /* 0x28A2 */     public static final int ERROR_DB_INVALID_USER= 10403;   /* 0x28A3 */     public static final int ERROR_DB_INVALID_PWD= 10404;    /* 0x28A4 */     public static final int ERROR_DB_CONNECT= 10405;    /* 0x28A5 */     public static final int ERROR_DB_INVALID_SQL= 10406;    /* 0x28A6 */     public static final int ERROR_DB_INVALID_APPID= 10407;  /* 0x28A7 */    /* Error codes of Resource 10500(0x2904)*/     public static final int ERROR_RES_GENERAL= 10500;   /* 0x2904 */     public static final int ERROR_RES_LOAD = 10501;     /* 0x2905 */   /* Load resource */     public static final int ERROR_RES_FREE = 10502;     /* 0x2906 */   /* Free resource */     public static final int ERROR_RES_MISSING = 10503;  /* 0x2907 */   /* Resource File Missing */     public static final int ERROR_RES_INVALID_NAME  = 10504;    /* 0x2908 */   /* Invalid resource file name */     public static final int ERROR_RES_INVALID_ID    = 10505;    /* 0x2909 */   /* Invalid resource ID */     public static final int ERROR_RES_INVALID_IMG   = 10506;    /* 0x290A */   /* Invalid resource image pointer */     public static final int ERROR_RES_WRITE= 10507;     /* 0x290B */   /* Write read-only resource */     public static final int ERROR_RES_LEAK = 10508;     /* 0x290C */   /* Resource leak out */     public static final int ERROR_RES_HEAD = 10509;     /* 0x290D */   /* Resource head currupt */     public static final int ERROR_RES_DATA = 10510;     /* 0x290E */   /* Resource data currupt */     public static final int ERROR_RES_SKIP = 10511;     /* 0x290F */   /* Resource file skipped */    /* Error codes of TTS 10600(0x2968)*/     public static final int ERROR_TTS_GENERAL= 10600;   /* 0x2968 */     public static final int ERROR_TTS_TEXTEND = 10601;  /* 0x2969 */  /* Meet text end */     public static final int ERROR_TTS_TEXT_EMPTY= 10602;    /* 0x296A */  /* no synth text */    /* Error codes of Recognizer 10700(0x29CC) */     public static final int ERROR_REC_GENERAL= 10700;   /* 0x29CC */     public static final int ERROR_REC_INACTIVE= 10701;  /* 0x29CD */     public static final int ERROR_REC_GRAMMAR_ERROR= 10702;     /* 0x29CE */     public static final int ERROR_REC_NO_ACTIVE_GRAMMARS        = 10703;    /* 0x29CF */     public static final int ERROR_REC_DUPLICATE_GRAMMAR= 10704;     /* 0x29D0 */     public static final int ERROR_REC_INVALID_MEDIA_TYPE        = 10705;    /* 0x29D1 */     public static final int ERROR_REC_INVALID_LANGUAGE= 10706;  /* 0x29D2 */     public static final int ERROR_REC_URI_NOT_FOUND= 10707;     /* 0x29D3 */     public static final int ERROR_REC_URI_TIMEOUT= 10708;   /* 0x29D4 */     public static final int ERROR_REC_URI_FETCH_ERROR= 10709;   /* 0x29D5 */    /* Error codes of Speech Detector 10800(0x2A30) */     public static final int ERROR_EP_GENERAL= 10800;    /* 0x2A30 */     public static final int ERROR_EP_NO_SESSION_NAME= 10801;    /* 0x2A31 */     public static final int ERROR_EP_INACTIVE  = 10802;     /* 0x2A32 */     public static final int ERROR_EP_INITIALIZED    = 10803;    /* 0x2A33 */    /* Error codes of TUV */       public static final int ERROR_TUV_GENERAL= 10900;   /* 0x2A94 */     public static final int ERROR_TUV_GETHIDPARAM       = 10901;    /* 0x2A95 */   /* Get Busin Param huanid*/     public static final int ERROR_TUV_TOKEN= 10902;     /* 0x2A96 */   /* Get Token */     public static final int ERROR_TUV_CFGFILE= 10903;   /* 0x2A97 */   /* Open cfg file */     public static final int ERROR_TUV_RECV_CONTENT  = 10904;    /* 0x2A98 */   /* received content is error */     public static final int ERROR_TUV_VERFAIL    = 10905;   /* 0x2A99 */   /* Verify failure */    /* Error codes of IMTV */     public static final int ERROR_LOGIN_SUCCESS= 11000;     /* 0x2AF8 */   /* 成功 */     public static final int ERROR_LOGIN_NO_LICENSE          = 11001;    /* 0x2AF9 */   /* 试用次数结束,用户需要付费 */     public static final int ERROR_LOGIN_SESSIONID_INVALID       = 11002;    /* 0x2AFA */   /* SessionId失效,需要重新登录通行证 */     public static final int ERROR_LOGIN_SESSIONID_ERROR= 11003;     /* 0x2AFB */   /* SessionId为空,或者非法 */     public static final int ERROR_LOGIN_UNLOGIN       = 11004;  /* 0x2AFC */   /* 未登录通行证 */     public static final int ERROR_LOGIN_INVALID_USER            = 11005;    /* 0x2AFD */   /* 用户ID无效 */     public static final int ERROR_LOGIN_INVALID_PWD             = 11006;    /* 0x2AFE */   /* 用户密码无效 */     public static final int ERROR_LOGIN_SYSTEM_ERROR= 11099;    /* 0x2B5B */   /* 系统错误 */    /* Error codes of HCR */     public static final int ERROR_HCR_GENERAL= 11100;      public static final int ERROR_HCR_RESOURCE_NOT_EXIST        = 11101;      public static final int ERROR_HCR_CREATE= 11102;      public static final int ERROR_HCR_DESTROY= 11103;      public static final int ERROR_HCR_START= 11104;      public static final int ERROR_HCR_APPEND_STROKES= 11105;      public static final int ERROR_HCR_GET_RESULT= 11106;      public static final int ERROR_HCR_SET_PREDICT_DATA= 11107;      public static final int ERROR_HCR_GET_PREDICT_RESULT        = 11108;        /* Error codes of http 12000(0x2EE0) */     public static final int ERROR_HTTP_BASE= 12000; /* 0x2EE0 */    /*Error codes of ISV */     public static final int ERROR_ISV_NO_USER  = 13000; /* 32C8 */    /* the user doesn't exist */    /* Error codes of Lua scripts */     public static final int ERROR_LUA_BASE= 14000;    /* 0x36B0 */     public static final int ERROR_LUA_YIELD= 14001; /* 0x36B1 */     public static final int ERROR_LUA_ERRRUN= 14002;    /* 0x36B2 */     public static final int ERROR_LUA_ERRSYNTAX= 14003; /* 0x36B3 */     public static final int ERROR_LUA_ERRMEM= 14004;    /* 0x36B4 */     public static final int ERROR_LUA_ERRERR= 14005;    /* 0x36B5 */ }

看完上述内容,你们掌握Java中怎么通过调用jna实现语音识别功能的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读!


新闻名称:Java中怎么通过调用jna实现语音识别功能
文章出自:http://azwzsj.com/article/jcjhcg.html