一.目的与实现过程

1.目的:将.wav/.mp3音频文件翻译成文字
2.方式:基于科大讯飞语音转写 WebApi的安卓实现
3.机制:采用自定义计时器轮询。
4.坑点1:科大讯飞当前暂无安卓文档/代码开放,需要自己写网络请求。
坑点2:免费的5小时转写套餐只能用于一个AppId

二.上效果图


音频文件随便选了个.wav文件

三.实现流程

1)上科大讯飞官网智能语音,语音转写查看文档和资费预览。这里用于测试,可以免费领取5小时套餐
2)注册账户,实名认证,领取到免费领取5小时套餐
3)创建应用,得到AppId

4)在语音转写功能下,记下APPID和SecretKey,后面需要用到
5)语音转写API

语音转写WebAPI文档地址
6)官网提供三种语言的webApi接口。由于对安卓不支持(网络协议的问题),所以需要我们进行手撸代码。

下载Demo Java语言。得到其中lib下的依赖包,lfasr-sdk-clientxxxx.jar

四.基于安卓的实现

0.实现原理:
创建一个简单的activity,根据APPID和secriteKey加密得到signa
语音转写经过以下流程:语音文件预处理,语音文件分片,文件合并,轮询结果,查询最后的翻译结果。
1.build.gardle:引入语音转写依赖包和OKHTTP(必须要的网络请求工具)

     implementation 'com.squareup.okhttp3:okhttp:3.10.0'//网络请求工具implementation fileTree(dir: 'libs', include: ['*.jar'])implementation 'androidx.legacy:legacy-support-v4:1.0.0'implementation 'androidx.appcompat:appcompat:1.1.0'implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

2.代码展示 Audio2TextActivity

 private static String myTag = "讯飞调试";public static final String LFASR_HOST = "https://raasr.xfyun.cn/api";/*** TODO 设置appid和secret_key*/public static final String APPID = "xxxxxxx";//public static final String SECRET_KEY = "8c4xxxxxxxxxxxxx";//public static final String PREPARE = "/prepare";public static final String UPLOAD = "/upload";public static final String MERGE = "/merge";public static final String GET_RESULT = "/getResult";public static final String GET_PROGRESS = "/getProgress";public static final int SLICE_SICE = 10485760;// 10M 文件分片大小,可根据实际情况调整private Timer myTimer = null;private TextView tv_myTestInfo;private StringBuffer stringBuffer = null;//用来显示测试信息和结果

OnCreate方法入口:

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_audio2_text);tv_myTestInfo = findViewById(R.id.myTestInfo);if(stringBuffer == null){stringBuffer = new StringBuffer(256);}else{int  sb_length = stringBuffer.length();stringBuffer.delete(0,sb_length);    }String myPath = Environment.getExternalStorageDirectory()+"/patrol/1576159897660tt.wav";myPrepare(myPath);}

3.对文件进行预处理。这里直接选取既定路径的文件,这里有需要可以自己自定义

/*** 预处理* okHttp* @param path     需要转写的音频* @return* @throws SignatureException*/private void myPrepare(final String path){final String myUrl = LFASR_HOST + PREPARE;new Thread() {@Overridepublic void run() {String ts = String.valueOf(System.currentTimeMillis() / 1000L);String signa = "";try {signa = EncryptUtil.HmacSHA1Encrypt(EncryptUtil.MD5(APPID + ts), SECRET_KEY);} catch (SignatureException e) {e.printStackTrace();}File file = new File(path);long fileLenth = file.length();String file_len = fileLenth + "";String file_name = file.getName()+"";String slice_num = (fileLenth/SLICE_SICE) + (fileLenth % SLICE_SICE == 0 ? 0 : 1) + "";// 2.创建文件上传请求对象RequestBody fileBody = RequestBody.create(MediaType.parse("audio/x-wav"), file);// 3.new okHttpOkHttpClient okHttpClient = new OkHttpClient();// 4.创建多媒体 请求对象RequestBody multipartBody = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("app_id", APPID)// 文件上传的参数.addFormDataPart("ts",ts ).addFormDataPart("signa",signa ).addFormDataPart("file", file_name,fileBody)//文件主体.addFormDataPart("file_len", file_len ).addFormDataPart("file_name", file_name ).addFormDataPart("slice_num", slice_num ).build();//5. 创建request对象 //.header("gis4_imei",myImei)final Request request = new Request.Builder().url(myUrl).post(multipartBody).build();Call call =   okHttpClient.newCall(request);try{Response response = call.execute();String result = response.body().string();Log.w(myTag,"result:"+result);ApiResultDto resultDto = JSON.parseObject(result, ApiResultDto.class);if (resultDto.getOk() != 0) {throw new RuntimeException("预处理失败!" + response);}else{String taskId = resultDto.getData();Log.w(myTag,"预处理成功");stringBuffer.append("预处理成功");Message message = new Message();message.what = 0;message.obj = taskId;mHandler.sendMessage(message);}}catch (IOException e){Log.w(myTag,"**********上传录音失败了"+e.toString());}}}.start();}

4.这里是异步的,所以采取Handlert异步消息机制。科大讯飞的官方解释是:上传的音频翻译结果在24H内完成,经测试,正常情况下不堵塞,大概3-5s内可以返回。
Handler方法:

Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message message) {super.handleMessage(message);//完成主界面更新,拿到数据switch (message.what) {case 0://预处理完成,执行分片和分片上传final String task_id = (String) message.obj;Log.w(myTag,"收到来自预处理完成后Handler的消息.What:"+message.what+"task_id:"+task_id);tv_myTestInfo.setText(stringBuffer);doMythod(task_id);break;case 1://分片完成,进行合并String task_id1 = (String) message.obj;Log.w(myTag,"收到来自分片处理完成后Handler的消息.What:"+message.what+"task_id:"+task_id1);tv_myTestInfo.setText(stringBuffer);myMerge(task_id1);  // 合并文件break;case 2://合并完成,轮询进度final String task_id2 = (String) message.obj;Log.w(myTag,"收到来自合并处理完成后Handler的消息.What:"+message.what+"task_id:"+task_id2);tv_myTestInfo.setText(stringBuffer);if(myTimer == null){myTimer = new Timer();myTimer.schedule(new TimerTask() {@Overridepublic void run() {myProgress(task_id2);}},10*1000,20*1000);}break;case 3://服务器端处理完成状态为9,最后进行查询结果final String task_id3 = (String) message.obj;Log.w(myTag,"收到来自查询进度后Handler的消息.What:"+message.what+"task_id:"+task_id3);tv_myTestInfo.setText(stringBuffer);getMyResult(task_id3);break;case 4://服务器端最后的查询结果final String text = (String) message.obj;Log.w(myTag,"收到来自最后的查询结果的消息.What:"+message.what+" text:"+text);stringBuffer.append("\n查询结果的消息:"+text);tv_myTestInfo.setText(stringBuffer);break;default:break;}}};
  1. 在预处理完成后,可以得到任务ID:task_id,然后在消息接收后,执行doSlice(task_id);方法
    doSlice方法:
 //提取主干方法 预处理后分支 合并 得到进度 上传 回调结果等private void doSlice(String taskId){File audio = new File(Environment.getExternalStorageDirectory()+"/patrol/1576159897660tt.wav");try (FileInputStream fis = new FileInputStream(audio)) {// 已得到预处理后的任务id为参数taskId// 分片上传文件int len = 0;byte[] slice = new byte[SLICE_SICE];SliceIdGenerator generator = new SliceIdGenerator();while ((len =fis.read(slice)) > 0) {// 上传分片if (fis.available() == 0) {slice = Arrays.copyOfRange(slice, 0, len);}myUploadSlice(taskId, generator.getNextSliceId(), slice);}} catch (SignatureException e) {e.printStackTrace();} catch (FileNotFoundException e1) {e1.printStackTrace();} catch (IOException e1) {e1.printStackTrace();}}

myUploadSlice(taskId, generator.getNextSliceId(), slice)方法:

/*** okHttp分片上传** @param taskId        任务id* @param slice         分片的byte数组* @throws SignatureException*/public void myUploadSlice(final String taskId, final String sliceId, final byte[] slice) throws SignatureException {Log.w(myTag,"*****uploadSlice()+sliceId"+sliceId);Map<String, String> uploadParam = getBaseAuthParam(taskId);uploadParam.put("slice_id", sliceId);new Thread() {@Overridepublic void run() {String ts = String.valueOf(System.currentTimeMillis() / 1000L);String signa = "";try {signa = EncryptUtil.HmacSHA1Encrypt(EncryptUtil.MD5(APPID + ts), SECRET_KEY);} catch (SignatureException e) {e.printStackTrace();}OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象。RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), slice);RequestBody multipartBody = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("app_id", APPID)// 文件上传的参数.addFormDataPart("ts",ts ).addFormDataPart("signa",signa )//.addFormDataPart("file", file_name,fileBody)//文件主体.addFormDataPart("task_id", taskId)//文件主体.addFormDataPart("slice_id", sliceId ).addFormDataPart("content", "fileName",requestBody).build();Request request = new Request.Builder().post(multipartBody)//.addHeader("Content-Type", "multipart/form-data;").url(LFASR_HOST + UPLOAD).build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()){//回调的方法执行在子线程。String result = response.body().string();Log.w(myTag,"分片请求发送后的状态回调"+result);ApiResultDto apiResultDto = JSONObject.parseObject(result,ApiResultDto.class);if(apiResultDto.getOk()==0){Log.w(myTag,"分片上传成功, sliceId: " + sliceId + ", sliceLen: " + slice.length);stringBuffer.append("\n分片上传成功, sliceId: " + sliceId + ", sliceLen: " + slice.length);Message message = new Message();message.what = 1;message.obj = taskId;mHandler.sendMessage(message);}else{Log.w(myTag,"分片上传失败! + response + taskId:"+taskId+"/状态码:"+apiResultDto.getErr_no()+"/原因:"+apiResultDto.getFailed());}}}@Overridepublic void onFailure(Call call, IOException e) {Log.w(myTag,"分片请求发送后的状态回调失败"+e.toString());}});}}.start();}

6.同理, 分片完成,进行合并 myMerge(task_id1)

 /*** 文件合并** @param taskId        任务id* @throws SignatureException*/public void myMerge(final String taskId) {final String mergeUrl = LFASR_HOST + MERGE;/*if (response == null) {throw new RuntimeException("文件合并接口请求失败!");}if (JSON.parseObject(response).getInteger("ok") == 0) {System.out.println("文件合并成功, taskId: " + taskId);return;}throw new RuntimeException("文件合并失败!" + response);*/new Thread() {@Overridepublic void run() {String ts = String.valueOf(System.currentTimeMillis() / 1000L);String signa = "";try {signa = EncryptUtil.HmacSHA1Encrypt(EncryptUtil.MD5(APPID + ts), SECRET_KEY);} catch (SignatureException e) {e.printStackTrace();}OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象。FormBody formBody = new FormBody.Builder().add("app_id", APPID).add("ts", ts).add("signa", signa).add("task_id", taskId).build();Request request = new Request.Builder().post(formBody).url(mergeUrl).build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()){//回调的方法执行在子线程。String result = response.body().string();Log.w(myTag,"文件合并接口请求成功"+result);ApiResultDto apiResultDto = JSONObject.parseObject(result,ApiResultDto.class);if(apiResultDto.getOk()==0){Log.w(myTag,"文件合并成功");stringBuffer.append("\n文件合并成功");Message message = new Message();message.what = 2;message.obj = taskId;mHandler.sendMessage(message);}else{Log.w(myTag,"文件合并失败!taskId:"+taskId+"/状态码:"+apiResultDto.getErr_no()+"/原因:"+apiResultDto.getFailed());return;}}}@Overridepublic void onFailure(Call call, IOException e) {Log.w(myTag,"文件合并接口请求失败"+e.toString());}});}}.start();}

7.合并后,设置轮询定时器。不断查询服务器是否完成,查询到返回结果,状态为9,即可对该任务查询最后的翻译结果。

if(myTimer == null){myTimer = new Timer();myTimer.schedule(new TimerTask() {@Overridepublic void run() {myProgress(task_id2);}},10*1000,20*1000);}
     /*** 获取任务进度* @param taskId        任务id* @throws SignatureException*/public void myProgress(final String taskId)  {final String progressUrl = LFASR_HOST + GET_PROGRESS;new Thread(){@Overridepublic void run() {String ts = String.valueOf(System.currentTimeMillis() / 1000L);String signa = "";try {signa = EncryptUtil.HmacSHA1Encrypt(EncryptUtil.MD5(APPID + ts), SECRET_KEY);} catch (SignatureException e) {e.printStackTrace();}OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象。FormBody formBody = new FormBody.Builder().add("app_id", APPID).add("ts", ts).add("signa", signa).add("task_id", taskId).build();Request request = new Request.Builder().post(formBody).url(progressUrl).build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()){//回调的方法执行在子线程。String result = response.body().string();Log.w(myTag,"获取任务进度接口请求成功"+result);ApiResultDto apiResultDto = JSONObject.parseObject(result,ApiResultDto.class);if(apiResultDto.getOk()==0){Log.w(myTag,"任务进度接口回调成功!taskId:"+taskId);JSONObject jsonObject = JSONObject.parseObject(apiResultDto.getData());//"data":"{\"desc\":\"任务创建成功\",\"status\":0}"Log.w(myTag,"任务进度回调得到数据getData:"+JSONObject.toJSONString(jsonObject));int status = jsonObject.getInteger("status");Log.w(myTag,"任务进度回调得到数据status:"+status);if(status == 9){stringBuffer.append("\n任务进度回调得到数据成功");Message message = new Message();message.what = 3;message.obj = taskId;mHandler.sendMessage(message);}}else{Log.w(myTag,"任务进度接口回调失败!taskId:"+taskId+"/状态码:"+apiResultDto.getErr_no()+"/原因:"+apiResultDto.getFailed());return;}myTimer.cancel();myTimer = null;//无论音频是否被完成,都取消轮询定时器。比如APPID时长不足,会导致老是报提示。}}@Overridepublic void onFailure(Call call, IOException e) {Log.w(myTag,"获取任务进度接口请求失败!"+e.toString());}});}}.start();

8.获取转写结果:

 /*** 获取转写结果** @param taskId* @return* @throws SignatureException*/public void getMyResult(final String taskId){final String myUrl = LFASR_HOST + GET_RESULT;//获取转写结果new Thread() {@Overridepublic void run() {String ts = String.valueOf(System.currentTimeMillis() / 1000L);String signa = "";try {signa = EncryptUtil.HmacSHA1Encrypt(EncryptUtil.MD5(APPID + ts), SECRET_KEY);Log.w(myTag, "signa: "+signa);} catch (SignatureException e) {e.printStackTrace();}OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象。FormBody formBody = new FormBody.Builder().add("app_id", APPID).add("ts", ts).add("signa", signa).add("task_id", taskId).build();Request request = new Request.Builder().post(formBody).url(myUrl).build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()){//回调的方法执行在子线程。String result = response.body().string();Log.w(myTag,"获取转写结果接口请求成功:"+result);ApiResultDto apiResultDto = JSONObject.parseObject(result,ApiResultDto.class);if(apiResultDto.getOk()==0){Log.w(myTag,"获取转写结果得到数据!taskId:"+taskId);/* JSONObject jsonObject = JSONObject.parseObject(apiResultDto.getData());//得到字符串的数组JSONArray jsonArray = (JSONArray)jsonObject.get("bills");List<JSONObject> jsonArray = JSONArray.parseArray("apiResultDto.getData()") ;*/JSONArray jsonArray = JSONArray.parseArray(apiResultDto.getData());//"data":"[{\"bg\":\"0\",\"ed\":\"4950\",\"onebest\":\"科大讯飞是中国最大的智能语音技术提供商。\",\"speaker\":\"0\"}]"Log.w(myTag,"获取转写结果得到数据Json getData:"+JSONArray.toJSONString(jsonArray));StringBuffer stringBuffer = new StringBuffer(256);for (int i =0;i<jsonArray.size();i++){JSONObject jsonObject = (JSONObject) jsonArray.get(i);stringBuffer.append(jsonObject.get("onebest"));}String resultString = stringBuffer.toString();stringBuffer.append("\n最后拼接出的结果为:"+resultString);Log.w(myTag,"最后拼接出的结果为:"+resultString);//String onebest = jsonObject.getString("onebest");//Log.w(myTag,"获取转写结果得到文本Text:"+onebest);//主线程得到文本信息和更新UIMessage message = new Message();message.what = 4;message.obj = resultString;mHandler.sendMessage(message);}else{Log.w(myTag,"任务进度接口回调失败!taskId:"+taskId+"/状态码:"+apiResultDto.getErr_no()+"/原因:"+apiResultDto.getFailed());return;}}}@Overridepublic void onFailure(Call call, IOException e) {Log.w(myTag,"获取转写结果接口请求失败"+e.toString());}});}}.start();}

9.其他
别忘了给权限:

 <uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.READ_CONTACTS" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_SETTINGS" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><!-- 允许程序读取或写入系统设置 --><uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<activity android:name=".Audio2TextActivity"android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"android:screenOrientation="portrait"android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".Audio2TextActivity"><TextViewandroid:id="@+id/myTestInfo"android:layout_width="match_parent"android:layout_height="wrap_content"android:minHeight="375dp"android:text=""android:textColor="@color/content_color"android:textSize="18sp"android:isScrollContainer="true"/>
</LinearLayout>

10.最后得到的翻译结果成功:
结果为:行,明天在是吧,我看待机时间嗯。

android实现科大讯飞对接就这么完成了。
就这么简单。

语音转写基于科大讯飞WebApi接口的安卓实现--上传录音音频翻译成文字相关推荐

  1. 防微杜渐,未雨绸缪,百度网盘(百度云盘)接口API自动化备份上传以及开源发布,基于Golang1.18

    奉行长期主义的开发者都有一个共识:对于服务器来说,数据备份非常重要,因为服务器上的数据通常是无价的,如果丢失了这些数据,可能会导致严重的后果,伴随云时代的发展,备份技术也让千行百业看到了其" ...

  2. 分享三个例程:使用ESP32做一个实时语音对讲机,MQTT协议实现公网对讲,ESP32-CAM 上传图像数据到网络。并附上例程链接。

    分享三个例程:使用ESP32做一个实时语音对讲机,MQTT协议实现公网对讲,ESP32-CAM 上传图像数据到网络.并附上例程链接.可以比对着看来学习NOW,MQTT使用方法.想办法把图像和语音代码融 ...

  3. pupload 文件分块 php,基于Plupload实现Base64分割的文件上传方案

    标题:基于Plupload实现Base64分割的文件上传方案 关键词:文件上传.Base64.Plupload.Blob.分割上传 领域:Web前端 作者:孙振强 日期:2018-04-13 目录 背 ...

  4. 微信sdk上传录音php,HTML5实现微信jssdk录音播放语音的实例

    HTML5微信jssdk录音播放语音的方法 需要注意的2个问题 1 就是一定要判断1秒内 录音都不算 ps:太短不能录音 2 录音超过1分钟 会发现正在录音突然消失 所以要写wx.onVoiceRec ...

  5. python调用接口上传文件_python接口自动化7-post文件上传

    前言 文件上传在我们软件是不可少的,最多的使用是体现在我们后台,当然我们前台也会有.但是了解过怎样上传文件吗?这篇我们以禅道文档-创建文档,上传文件为例. post请求中的:Content-Type: ...

  6. axios文件上传 formdata_基于业务场景下的图片/文件上传方案总结

    图片/文件上传组是企业项目开发中必不可少的环节之一, 但凡涉及到用户模块的都会有图片/文件上传需求, 在很多第三方组件库(ant desigin, element ui)中它也是基础组件之一. 接下来 ...

  7. python post 上传文件_python接口自动化7-post文件上传

    前言 文件上传在我们软件是不可少的,最多的使用是体现在我们后台,当然我们前台也会有.但是了解过怎样上传文件吗?这篇我们以禅道文档-创建文档,上传文件为例. post请求中的:Content-Type: ...

  8. 大文件分片上传前端框架_基于Node.js的大文件分片上传

    基于Node.js的大文件分片上传 我们在做文件上传的时候,如果文件过大,可能会导致请求超时的情况.所以,在遇到需要对大文件进行上传的时候,就需要对文件进行分片上传的操作.同时如果文件过大,在网络不佳 ...

  9. 使用jQuery开发一个基于HTML5的漂亮图片拖拽上传web应用

    昨天我们介绍了一款HTML5文件上传的jQuery插件:jQuery HTML5 uploader,今天我们将开发一个简单的叫upload center的图片上传程序,允许用户使用拖拽方式来上传电脑上 ...

最新文章

  1. GitHub 上值得前端学习的数据结构与算法项目
  2. 179一个错误的认识
  3. 如何查询oracle的共享内存,[20190104]ipcs查看共享内存段.txt
  4. 6年Python开发,教你一天入门 Python
  5. RocketMq 事务消息使用
  6. DBGridEh全部属性设置详解
  7. ig 焊接机器人_发那科机器人焊接应用的IO配置(总线型)
  8. 《逻辑与计算机设计基础(原书第5版)》——导读
  9. 基于c#的winform中图片放大后不清晰问题
  10. 转 OpenGL核心技术之帧缓冲
  11. 计算机网络速度测试指令,怎么ping网速 ping命令简单测试网速方法【详解】
  12. U8glib如何显示中文
  13. Gym 100015 F Fighting for Triangles 博弈,状压dp
  14. excel清单数据导入到开票软件中进行开票
  15. 内蒙古大学计算机考研892,893计算机考研真题分享
  16. 一个人在家怎么赚钱?普通人如何通过网络实现在家就能赚钱
  17. 微博签到数据——北京、上海、昆明、深圳(2018-2022已更新完毕)
  18. svelte-scrollbar: 基于svelte.js自定义滚动条组件|svelte3虚拟滚动条
  19. android 权限开启回调,Android M请求onSurfaceTextureAvailable回调权限不在活动
  20. Labview连接bartender自动打印条码

热门文章

  1. python画八卦_python编程也能八卦?
  2. 《2022-移动端游戏版号申请详解》
  3. 2022 IDEA大会引领科技创新趋势 沈向洋团队重磅发布低空经济白皮书
  4. 微信小程序与java语言mysql数据库
  5. word或wps中如何把visio或公式等转换为图片
  6. android 蓝牙电话号码,Android拨打电话和蓝牙状态监听
  7. 1分钟快速实现高效的扫描二维码,急速识别手机相册二维码
  8. 进击的DApp:区块链上将长出怎么样的新事物?
  9. 什么是“好”系统呢?
  10. python redis缓存_第二百九十五节,python操作redis缓存-字符串类型