jar 下载路径:https://download.youkuaiyun.com/download/xsf1840/10318356
最近项目中需要用到手机H5页面中录制 音频 视频 的上传回显功能,在开发中发现ios系统对input标签存在兼容性问题导致在页面直接实现音频录制存在困难,包括网上的插件都是基于js实现,所以也都存在兼容性问题。
<!--调用手机的录像功能和直接打开系统文件目录。苹果手机调出来的是拍照和录像安卓手机调出来的录音功能-->
<input type="file" accept="audio/*" capture="microphone">
后初步准备使用微信提供的js_sdk 实现该功能
视频直接使用ajax FormDate对象实现
由于token每日有请求上限,建议放到memcache/redis 或者根据定义的全局时间判断 我根据全局时间判断
第一步:获得access_token代码
第二步:根据token获得js临时票据(有效时间7200秒,可以放到memcache)
第四步:进行配置(页面)
public class PastUtil { public static String token = null; public static String time = null; public static String jsapi_ticket = null; private static final Logger logger = LoggerFactory.getLogger(PastUtil.class); /** * * @param appId 公账号appId * @param appSecret * @param request 当前网页的URL不包含#及其后面部分 * @return */ public static Map<String,String> getParam(HttpServletRequest request,String appId,String appSecret){ if(token == null){ token = getAccess_token(appId, appSecret); jsapi_ticket = getJsApiTicket(token); time = getTime(); }else{ if(!time.substring(0, 13).equals(getTime().substring(0, 13))){ //每小时刷新一次 token = null; token = getAccess_token(appId, appSecret); jsapi_ticket = getJsApiTicket(token); time = getTime(); } } String url = getUrl(request); //获取签名并SHA-1加密 Map<String, String> params = sign(jsapi_ticket, url); params.put("appid", appId); System.out.println(params); return params; } /** * 获取接口访问凭证 * * @param appid 凭证 * @param appsecret 密钥 * @return */ public static String getAccess_token(String appid, String appsecret) { //凭证获取(GET) String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret); // 发起GET请求获取凭证 Map<String,Object> jsonObject = HttpUtil.doGet(requestUrl,null); String access_token = null; if (null != jsonObject) { try { Object object= jsonObject.get("access_token"); if(object !=null){ access_token = object.toString(); } } catch (JSONException e) { // 获取token失败 logger.error("获取token失败 errcode:{} errmsg:{}", jsonObject.get("errcode"), jsonObject.get("errmsg")); } } return access_token; } /** * 调用微信JS接口的临时票据 * * @param access_token 接口访问凭证 * @return */ public static String getJsApiTicket(String access_token) { String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; String requestUrl = url.replace("ACCESS_TOKEN", access_token); // 发起GET请求获取凭证 Map<String,Object> jsonObject = HttpUtil.doGet(requestUrl,null); String ticket = null; if (null != jsonObject) { try { Object object = jsonObject.get("ticket"); if(object !=null){ ticket = object.toString(); } } catch (JSONException e) { // 获取token失败 logger.error("获取票据失败 errcode:{} errmsg:{}", jsonObject.get("errcode"), jsonObject.get("errmsg")); } } return ticket; } private static String getUrl(HttpServletRequest request){ StringBuffer requestUrl = request.getRequestURL(); String queryString = request.getQueryString(); String url = requestUrl +"?"+queryString; return url; } public static Map<String, String> sign(String jsapi_ticket, String url) { Map<String, String> ret = new HashMap<String, String>(); String nonce_str = create_nonce_str(); String timestamp = create_timestamp(); String str; String signature = ""; //注意这里参数名必须全部小写,且必须有序 str = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + url; try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(str.getBytes("UTF-8")); signature = byteToHex(crypt.digest()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } ret.put("url", url); ret.put("jsapi_ticket", jsapi_ticket); ret.put("nonceStr", nonce_str); ret.put("timestamp", timestamp); ret.put("signature", signature); return ret; } private static String byteToHex(final byte[] hash) { Formatter formatter = new Formatter(); for (byte b : hash) { formatter.format("%02x", b); } String result = formatter.toString(); formatter.close(); return result; } private static String create_nonce_str() { return UUID.randomUUID().toString(); } private static String create_timestamp() { return Long.toString(System.currentTimeMillis() / 1000); } //获取当前系统时间 用来判断access_token是否过期 public static String getTime(){ Date dt=new Date(); SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(dt); } }
页面js 部分需要先到微信公众号注册接口回调域名
<script> /* * 注意: * 1. 所有的JS接口只能在公众号绑定的域名下调用,公众号开发者需要先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。 * 2. 如果发现在 Android 不能分享自定义内容,请到官网下载最新的包覆盖安装,Android 自定义分享接口需升级至 6.0.2.58 版本及以上。 * 3. 完整 JS-SDK 文档地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html * */ wx.config({ debug: true, appId: '${weixinMap.appid}', timestamp: '${weixinMap.timestamp}', nonceStr: '${weixinMap.nonceStr}', signature: '${weixinMap.signature}', jsApiList: [ 'checkJsApi', 'startRecord', 'stopRecord', 'onVoiceRecordEnd', 'playVoice', 'pauseVoice', 'stopVoice', 'uploadVoice', 'downloadVoice' ] }); </script> <script src="/static/common/weixin_js_sdk/weixin_js_sdk.js?v=${v}"></script>引入weixin_js_dk.js 是自己的 录音 语音识别等事件逻辑
需要注意的是,微信存储的音频格式amr格式(压缩比大) ,且只保留3天 H5也 无法播放 需要转码MP3
所以录制音频上传微信服务器,在根据微信提供的多媒体接口 下载到自己的服务器,转码保存即可(根据ffmpeg实现)
转码在后台Java实现
知道原因之后,解决思路有两种。
- 不使用jave ,将jave一些核心的代码抽取出来,自己调用系统外部。
- 下载最新的ffmpeg,替换掉原先的ffmpeg。
第二种比较简单
网上一般都是利用引入jave-1.0.2集成了ffmpeg的jar包实现该功能,避免了在linux上搭建程序的麻烦
jave的能转换的原理其实就是调用外部的二进制可执行文件 ffmpeg
,打开它的jar包就可以发现,它里面内置了ffmpeg.exe(window)或ffmpeg二进制可执行文件(linux)
所以实际上,jave就是封装了一层对外部ffmpeg
的调用。
而windows上能转换是因为:ffmpeg.exe 这个程序没问题。再linux环境只需要到网站下载相应二进制可执行文件替换相应的。exe即可
代码如下
private Object o = new Object();//全局锁对象
** * 获取上传音频并转码保存 * @param request * @param serverId * @return */ @RequestMapping("/mobile/add/audio") @ResponseBody public Map<String,Object> addAudio(HttpServletRequest request,@RequestParam("serverId")String serverId){ Map<String,Object> json =null; try { String path = saveImageToDisk(serverId,request); json = getJsonMap(true,path,""); }catch (Exception e){ logger.error("addAudio",e); json = getJsonMap(false,"",""); return json; } return json; } /** * 获取临时素材 */ private InputStream getMedia(String mediaId) { String url = "https://api.weixin.qq.com/cgi-bin/media/get"; String access_token = PastUtil.token; String params = "access_token=" + access_token + "&media_id=" + mediaId; InputStream is = null; try { String urlNameString = url + "?" + params; URL urlGet = new URL(urlNameString); HttpURLConnection http = (HttpURLConnection) urlGet.openConnection(); http.setRequestMethod("GET"); // 必须是get方式请求 //文本 application/x-www-form-urlencoded // 视频:video/mpeg4 // 音频:audio/mp3 http.setRequestProperty("Content-Type","audio/mp3"); http.setDoOutput(true); http.setDoInput(true); http.connect(); // 获取文件转化为byte流 is = http.getInputStream(); } catch (Exception e) { e.printStackTrace(); } return is; } /** * 保存图片至服务器 * @param mediaId * @return 文件名 */ public String saveImageToDisk(String mediaId,HttpServletRequest request){ String filename = ""; String newFileName = ""; //从微信服务器 下载流文件 InputStream inputStream = getMedia(mediaId); //将流文件写入本地服务器 byte[] data = new byte[1024]; int len = 0; FileOutputStream fileOutputStream = null; try { //服务器保存路径 filename = path+System.currentTimeMillis()+mediaId + ".amr"; newFileName = path+System.currentTimeMillis()+mediaId+"1"+".mp3"; String allAmrUrl = s+filename; //下载amr到自己服务器的文件全路径 String allMap3Url = s+newFileName; //转码后全路径 String savePath = s+url+dateStr; //保存路径 //目录 File isD = new File(savePath); // 校验如果目录不存在,则创建目录 if (!isD.isDirectory()) { isD.mkdirs(); } if (!isD.exists()) { synchronized (o) { isD.mkdirs(); } } fileOutputStream = new FileOutputStream(allAmrUrl); while ((len = inputStream.read(data)) != -1) { fileOutputStream.write(data, 0, len); } //转码 changeToMp3(allAmrUrl,allMap3Url); } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (fileOutputStream != null) { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return newFileName; } /** * 把amr格式的语音转换成MP3 * @Title: changeToMp3 * @Description: (把amr格式的语音转换成MP3) * @author pll * @param @param sourcePath amr格式文件路径 * @param @param targetPath 存放mp3格式文件路径 * @return void 返回类型 * @throws */ public static void changeToMp3(String sourcePath, String targetPath) { File source = new File(sourcePath); File target = new File(targetPath); AudioAttributes audio = new AudioAttributes(); Encoder encoder = new Encoder(); audio.setCodec("libmp3lame"); EncodingAttributes attrs = new EncodingAttributes(); attrs.setFormat("mp3"); attrs.setAudioAttributes(audio); try { encoder.encode(source, target, attrs); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InputFormatException e) { e.printStackTrace(); } catch (EncoderException e) { e.printStackTrace(); } }
不得不吐槽:微信开发文档写的很恶心!说明说一半,注意事项还与接口说明不放一起,得自己到犄角旮旯的地方去找
例如:这个js_sdk 需要服务器ip白名单设置 微信转账银行卡获取公钥加密方式,参数回调 甚是烦人