一年的时间里,前前后后都在搞微信开发的相关模块,这不前一阵子,公司又开了个新项目,其中有一个就是类似于微信朋友圈的功能(我也不知道为啥要开发微信已有的功能啊,泪奔...),其中包含上传图片、录音、视频等,由于微信端上传图片和视频这块也是头一遭做,图片采用了微信的相关插件,视频嘛用的是百度的webupload插件,感觉也相当不错,采用了分片上传技术。今天这篇就主要介绍一下,录音的相关功能。

简单说明

微信录音这块,其实面对这项功能的时候,不用我多说,都知道要先去看开发文档,查阅相关资料等准备工作;我先贴个地址,免得看我这篇文档的时候再去查找网页:微信公众号开发-微信JS-SDK说明文档。进入到这个页面找到第5小节,**音频接口**这里,就是本篇要说的东西了。

微信录音分为如下几个**接口**(这里归纳一下,文档里有,详细内容自己去看吧):

  1. 开始录音接口
  2. 停止录音接口
  3. 监听录音自动停止接口
  4. 播放语音接口
  5. 暂停播放接口
  6. 停止播放接口
  7. 监听语音播放完毕接口
  8. 上传语音接口
  9. 下载语音接口

看到以上,是不是觉得蛮多的,配合起来使用才提供了这么一个完整(好像也并不怎么完整,没有提供方便的转码及下载本地机制)的录音功能。好话说在前头:你弄完了当你再去看这块代码的时候,发现还真他娘的乱啊。。。所以,我写这篇记录的原因就在这里了,狗头.png

还有一个就是**接口权限**(一张图片来表述,图片来源微信文档...):


上传代码梳理

本章会以文字和代码的形式进行梳理,过程应该会蛮多的,不过看完之后直接拿去使用,问题也应该不大,如是说道。

JS-SDK库的使用步骤,这里就不过多介绍了,文档里也有,也是简单整理下几个所需步骤吧:

  1. 绑定域名
  2. 引入JS文件
  3. config接口注入权限验证配置
  4. ready接口处理成功验证
  5. error接口处理失败验证

config事件

我们直接从注入API列表说起,config接口有如下几个属性(不知道属性是否合适,姑且叫为属性吧),

  1. /**
  2. * 微信jssdk调用接口初始化
  3. */
  4. wx.config({
  5. debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
  6. appId: '', // 必填,公众号的唯一标识
  7. timestamp: , // 必填,生成签名的时间戳
  8. nonceStr: '', // 必填,生成签名的随机串
  9. signature: '',// 必填,签名
  10. jsApiList: [
  11. 'startRecord', // 录音开始api
  12. 'stopRecord', // 录音结束api
  13. 'uploadVoice', // 上传录音api
  14. 'onVoiceRecordEnd', // 超过一分钟自动停止api
  15. 'playVoice', // 播放录音api
  16. 'pauseVoice', // 暂停录音api
  17. 'onVoicePlayEnd', // 监听语音播放完毕api
  18. ]
  19. });

代码中的jsApiList就是录音功能所用到的所有接口了。其他几个属性的值,就不介绍如何生成的了,调用jssdk的类库,通过公众号的appid和secret就能获取到。嗯...还是贴下这部分的代码吧:

  1. // 引入jssdk库
  2. require_once APPPATH . 'libraries/weixin/jssdk.php';
  3. // 传入appid和secret实例化jssdk类
  4. $JsSdk = new JSSDK('wxa318c6979e231ffa', '301d8f04a0f2ba3098135a162165c991');
  5. // 得到相关时间戳和随机字符串
  6. $data['signPackage'] = $JsSdk->getSignPackage();
  7. // 获取当前页面url
  8. $data['signPackage']['url'] = explode('?', $data['signPackage']['url'])[0];

注意:调用JSSDK类的时候,会在项目根目录下生成两个文件access_token.phpjsapi_ticket.php,里面放的是过期时间和token、ticket值。

ready事件

ready注册录音播放结束监听事件:

  1. wx.ready(function(){
  2. // 监听事件一开始就加载
  3. wx.onVoicePlayEnd({
  4. success: function (res) {
  5. layer.msg('播放完毕'); // 这里用了layer弹框
  6. }
  7. });
  8. });

录音事件

本小节主要是关于录音事件的介绍,代码行中的注释应该写的很清楚,不清楚的留言问吧:

  1. // 声明一个录音数组 以存放录音临时ID
  2. var voice = {
  3. localId: []
  4. };
  5. // 手指按下录音键
  6. $('#micb').on('touchstart', function(event){
  7. // 取消事件的默认动作
  8. event.preventDefault();
  9. // 赋值当前的录音开始时间戳到全局变量
  10. START = new Date().getTime();
  11. recordTimer = setTimeout(function(){
  12. // 录音开始
  13. wx.startRecord({
  14. success: function(){
  15. // 录音不能超过一分钟 超过一分钟自动停止 并触发该事件
  16. wx.onVoiceRecordEnd({
  17. // 录音时间超过一分钟没有停止的时候会执行 complete 回调
  18. complete: function (res) {
  19. // 给出提示
  20. layer.msg('最多只能录制一分钟', {icon:2, time:1000});
  21. // 记录录音的临时ID
  22. voice.localId = res.localId;
  23. uploadVoice();
  24. }
  25. });
  26. },
  27. cancel: function () {
  28. alert('用户拒绝授权录音');
  29. }
  30. });
  31. },300);
  32. });
  33. // 松手结束录音
  34. $('#micb').on('touchend', function(event){
  35. event.preventDefault();
  36. // 获取录音停止时间戳
  37. END = new Date().getTime();
  38. // 获取录音总时长
  39. duration = END - START;
  40. // 如果小于300ms则视为时间太短 抛出提示
  41. if(duration < 300){
  42. END = 0;
  43. START = 0;
  44. layer.msg('时间太短', {icon:2, time:1000});
  45. //小于300ms,不录音
  46. clearTimeout(recordTimer);
  47. }else{
  48. wx.stopRecord({
  49. success: function (res) {
  50. voice.localId = res.localId;
  51. // 上传录音
  52. uploadVoice();
  53. },
  54. fail: function (res) {
  55. alert(JSON.stringify(res));
  56. }
  57. });
  58. }
  59. });
  60. // 上传录音
  61. function uploadVoice() {
  62. // 调用微信的上传录音接口把本地录音先上传到微信的服务器
  63. // 不过,微信只保留3天,而我们需要长期保存,我们需要把资源从微信服务器下载到自己的服务器
  64. wx.uploadVoice({
  65. localId: voice.localId, // 需要上传的音频的本地ID,由stopRecord接口获得
  66. isShowProgressTips: 1, // 默认为1,显示进度提示
  67. success: function (res) {
  68. // 把录音在微信服务器上的id(res.serverId)发送到自己的服务器供下载。
  69. $.ajax({
  70. url: 'down_file.php',
  71. type: 'post',
  72. data: {serverId: res.serverId},
  73. dataType: "json",
  74. success: function (data) {
  75. if(data.status == 200) {
  76. layer.msg('语音保存成功', {icon:1, time:2000});
  77. }
  78. },
  79. error: function (xhr, errorType, error) {
  80. console.log(error);
  81. }
  82. });
  83. }
  84. });
  85. }
  86. /**
  87. * 播放音频
  88. */
  89. function playVoice()
  90. {
  91. wx.playVoice({
  92. localId: voice.localId // 需要播放的音频的本地ID,由stopRecord接口获得
  93. })
  94. }

以上就是微信录音在前端的JS代码,接下来说明服务器(PHP)端下载录音到本地的代码,下载代码具体在download_media()方法中,其他都是一些辅助方法:

下载

  1. <?php
  2. class Wechat
  3. {
  4. /**
  5. * 获取微信access_token
  6. * @return string access_token
  7. */
  8. public function getWechatAccessToken()
  9. {
  10. $token_url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential';
  11. $appid = 'appid';
  12. $secret = 'secret';
  13. $get_token_url = $token_url . '&appid=' . $appid . '&secret=' . $secret;
  14. $res = file_get_contents ( $get_token_url );
  15. $res_arr = json_decode ( $res, true );
  16. if($res_arr['access_token']) return $res_arr['access_token'];
  17. return false;
  18. }
  19. /**
  20. * 生成毫秒级时间戳
  21. */
  22. public function msectime()
  23. {
  24. list($msec, $sec) = explode(' ', microtime());
  25. return (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
  26. }
  27. /**
  28. * 随机取出字符串
  29. * @param int $strlen 字符串位数
  30. * @return string
  31. */
  32. public function salt($strlen)
  33. {
  34. $str = "abcdefghkmnprstuvwxyzABCDEFGHKMNPRSTUVWXYZ23456789";
  35. $salt = '';
  36. $_len = strlen($str)-1;
  37. for ($i = 0; $i < $strlen; $i++) {
  38. $salt .= $str[mt_rand(0,$_len)];
  39. }
  40. return $salt;
  41. }
  42. /**
  43. * 下载微信素材资源到本地
  44. * @param url $url 素材地址
  45. * @return json
  46. */
  47. public function download_media()
  48. {
  49. // 获取微信服务器的录音ID
  50. $media_id = $this->input->post('serverId');
  51. if ($media_id) {
  52. // 获取access_token
  53. $access_tokens = $this->getWechatAccessToken();
  54. // 下载素材接口
  55. $down_media_url = 'https://api.weixin.qq.com/cgi-bin/media/get';
  56. /**
  57. * 根据access_tokens获取素材
  58. */
  59. $get_media_url = $down_media_url . '?access_token=' . $access_tokens . '&media_id=' . $media_id;
  60. // 获取文件流
  61. $file_flow = file_get_contents($get_media_url);
  62. // 本地保存目录
  63. $save_path = "resource/uploads/";
  64. if( !is_dir($save_path) ) {
  65. mkdir(iconv('UTF-8', 'GBK', $save_path), 0777, TRUE);
  66. }
  67. // 生成文件名
  68. $filename = $this->msectime() . $this->salt(6) . '.amr';
  69. // 写入文件流到本地
  70. $flag = file_put_contents($save_path . '/' . $filename, $file_flow);
  71. unset($file_flow);
  72. if($flag !== FALSE) {
  73. return $save_path . '/' . $filename;
  74. }else {
  75. return FALSE;
  76. }
  77. }
  78. }
  79. }

由于微信保存录音的格式.amr,所以下载的时候只能下载amr格式的音频,强行下载成MP3格式的话,播放可能会出现一些问题,接下来就说下转码的几种方式;

转码

第三方工具 FFmpeg

FFmpeg介绍

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。———来自百度的介绍。

下载

通过这个网址下载ffmpeg.exe程序 https://ffmpeg.zeranoe.com/bu...,选择shared这个类型,如下图:

下载完成后,解压在bin目录可以看到ffmpeg.exe

转码

cmd命令行转码:直接切换到ffmpeg.exe的目录,在命令行输入

  1. ffmpeg.exe -i E:\wode\ffmpeg\amr.amr E:\wode\ffmpeg\arm.mp3

即可,就可以看到整个转码的过程。下图所示:lyOi16p2LfA9RVld.png

php转码:那么如何利用php来调用exe软件来进行转码呢?很方便的是php提供了相应的函数,execsystem,他们都可以调用cmd的命令,比如调用ffmpeg.exe来进行转码:

  1. exec("E:\\wode\\ffmpeg\\bin\\ffmpeg.exe -i E:\\wode\\ffmpeg\\amr.amr E:\\wode\\ffmpeg\\exec.mp3");

就是这么简单了。当然ffmpeg也支持其他格式的转码,音频、视频等都可以。

转码结束后用audio加载播放即可:

<a href="javascript:void(0);" class="btn btn-default" onclick="play_audio();">播放</a> <audio id="audio_player" src="http://secret8.net/u/2016-05/amr2wav.mp3"/> <script type="text/javascript"> function play_audio(){ document.getElementById('audio_player').play(); } </script>

PS:会把测试的amr文件贴在下面的资源节里,有需要的小伙伴可以下载测试,因为现在的amr格式的文件都挺难找的,别问我怎么知道的....

第三方平台 七牛云

除了通过php外部命令调用软件进行转码之外,还可以通过第三方平台实现转码操作,这里就举例七牛云,首先贴两个链接:

  1. 七牛开发者SDK列表
  2. 七牛文件上传说明

上面的的一个链接里显示的是七牛官方所有的SDK文档列表,可以根据后端语言的不同进行选择查看;第二个链接就是本小节要说的利用七牛云上传文件。下面来瞧一下代码,也同样封装成类的形式:

  1. <?php
  2. use Qiniu\Auth;
  3. use Qiniu\Storage\UploadManager;
  4. use Qiniu\Storage\BucketManager;
  5. class uploadQiniu
  6. {
  7. public $qiniu = [
  8. 'AccessKey' => '', // 配置七牛AccessKey
  9. 'SecretKey' => '', // 配置七牛SecretKey
  10. 'bucket' => 'cxiansheng', // 七牛的存储空间名,我的是cxiansheng
  11. 'voice-pipeline' => 'cxiansheng', // 设置转码队列名
  12. 'voice-domain' => 'http://***.com/' // 你的CDN加速域名 上传文件成功后通过这个域名+文件名就可以访问到相应的资源
  13. ];
  14. /**
  15. * 上传微信录音文件到七牛并转码mp3
  16. * @param string $trans_ext 本地文件路径
  17. */
  18. public function upload_qiniu($file_path)
  19. {
  20. // 获取七牛auth对象
  21. $auth = new Auth($this->qiniu['AccessKey'], $this->qiniu['SecretKey']);
  22. // 定义转码后的mp3文件名
  23. $qiniu_filename = 'qiniu' . $this->msectime() . $this->salt(6) . '.mp3';
  24. // 指定上传文件成功后的后续处理 这里为转码操作
  25. $savekey = \Qiniu\base64_urlSafeEncode($this->qiniu['bucket'] . ":" . $qiniu_filename);
  26. $fops = 'avthumb/mp3/ab/128k/ar/44100/acodec/libmp3lame|saveas/' . $savekey;
  27. /**
  28. * 设置转码格式和转码队列名
  29. */
  30. $policy = [
  31. 'persistentOps' => $fops,
  32. 'persistentPipeline' => $this->qiniu['voice-pipeline']
  33. ];
  34. /**
  35. * 设置上传到七牛的原始amr临时文件名
  36. * @var string
  37. */
  38. $qiniu_tmp_filename = 'originamrtmp.amr';
  39. // 获取上传token
  40. $uptoken = $auth->uploadToken($this->qiniu['bucket'], null, 3600, $policy);
  41. // 调用上传类
  42. $uploadMgr = new UploadManager();
  43. // 调用 UploadManager 的 putFile 方法进行文件的上传。
  44. list($ret, $err) = $uploadMgr->putFile($uptoken, $qiniu_tmp_filename, $file_path);
  45. // 资源管理类
  46. $bucketMgr = new BucketManager($auth);
  47. if($ret['key']) {
  48. // 删除原始amr临时文件
  49. $err = $bucketMgr->delete($this->qiniu['bucket'], $ret['key']);
  50. unlink($file_path); //删除服务器上的amr文件
  51. // 返回在七牛上的资源路径
  52. return $this->qiniu['voice-domain'] . $qiniu_filename;
  53. }else {
  54. return FALSE;
  55. }
  56. }
  57. }

上面的代码应该不用我做过多介绍,里面的注释每一步应该都写得很清楚。

PS:在通过第三方转码的时候,调用api成功之后,由于要待转码的文件一般都会处于转码队列中,可能还没有立即转码成功我们想要的文件。所以不能根据转码过后的文件名去下载,需要等待一会儿,或者可以先把转码后的文件名保存在数据库中,用定时任务去下载到本地。具体请看我在SF提问里的回答--下载七牛云mp3文件间歇性失败

总结

至此,在微信开发中关于录音这一块儿的功能,就已经介绍完毕。如果有写错的地方,欢迎拍砖。同样看完还不是很理解的朋友,也可以留言。

版权声明:若无特殊注明,本文皆为( yueshuo )原创,转载请保留文章出处。