雖然是實(shí)現(xiàn)Flutter的百度語音轉(zhuǎn)文字功能搂赋,主要還是通過Android端來實(shí)現(xiàn)百度語音轉(zhuǎn)文字功能,然后通過Flutter和Android端來傳數(shù)據(jù)(如下圖)陪拘,來實(shí)現(xiàn)Flutter需求的功能厂镇。其中ios端也是可以實(shí)現(xiàn)百度語音轉(zhuǎn)文字的功能,但是因?yàn)槲业碾娔X是window系統(tǒng)左刽,所以不能用flutter來開發(fā)ios捺信,所以我下面的教程主要是針對(duì)Android端的實(shí)現(xiàn)。
原則肯定是先看一下效果呀欠痴,實(shí)例為證:
百度語音轉(zhuǎn)漢字的時(shí)候一般都會(huì)在后面加一個(gè)句號(hào)迄靠。
1.新建一個(gè)flutter項(xiàng)目,來實(shí)現(xiàn)View端喇辽。
下圖紅色圓圈處不要打勾
2.新建android端的module
1.打開flutter里面的Android文件
==找到flutter項(xiàng)目里面的Android文件掌挚,然后找到MainActivty,通過android studio,這里爆紅不需要管他菩咨,開始build的時(shí)候可能會(huì)有點(diǎn)慢==吠式。
打開成功
1.新建一個(gè)module
記得選擇Android Library
。名字自己命名吧抽米,我取名為asr_plugin特占,不細(xì)說了。
新建成功
3.配置百度語音轉(zhuǎn)文字sdk
1.下載SDK
百度SDK下載地址:https://ai.baidu.com/sdk
2.在asr_plugin里面配置SDK
1.找到core文件夾
2.把下圖文件放到asr_plugin文件的lib文件中
不要忘記加載這個(gè)jar包
3.把jniLibs粘貼到你的src文件中
如圖所示:
把無用的包刪掉云茸,只留下如圖中的so包
是目。4.配置你的AndroidMainfest權(quán)限
代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.asr_plugin">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<!-- <service
android:name="com.baidu.speech.VoiceRecognitionService"
android:exported="false" />-->
<meta-data
android:name="com.baidu.speech.APP_ID"
android:value="15519387" />
<meta-data
android:name="com.baidu.speech.API_KEY"
android:value="Ywyh8ErHPQXBSapdQZqVBtdl" />
<meta-data
android:name="com.baidu.speech.SECRET_KEY"
android:value="DFPpDWnOGrNGzxfX07D5GFLryh3d3Nne" />
</application>
</manifest>
有的同學(xué)可能會(huì)想把那個(gè)APP_ID,API_KEY,SECRET_KEY改為自己的應(yīng)用,但是我在改為自己的應(yīng)用的時(shí)候标捺,出現(xiàn)了錯(cuò)誤碼懊纳,具體是什么錯(cuò)誤揉抵,我也忘了,反正是關(guān)于我創(chuàng)建應(yīng)用沒有語音轉(zhuǎn)文字這功能嗤疯,個(gè)人提議冤今,如果你不想麻煩的話,可以直接這些數(shù)據(jù)身弊,如果你想配置自己的辟汰,也可以去嘗試一下,可以根據(jù)錯(cuò)誤碼來查看錯(cuò)誤文件阱佛,查看錯(cuò)誤碼的網(wǎng)址:https://ai.baidu.com/ai-doc/SPEECH/qk38lxh1q帖汞。
5.在flutter的Android文件的build.gradle文件里面加載asr_plugin庫
此功能主要把a(bǔ)sr_plugin加載到flutter里面,方便等下進(jìn)行數(shù)據(jù)通信凑术。
還需要一步翩蘸,記得在
4.建立flutter和Android端的橋梁
1.配置flutter端的MethodChannel
建立一個(gè)asr_manager文件,通過MethodChannel 來進(jìn)行通信淮逊〈呤祝總共有三種通信方式,我們這里是屬于方法通信泄鹏。
總共有三個(gè)方法:開始錄音郎任、停止錄音、取消錄音备籽。
代碼如下:
import 'package:flutter/services.dart';
class AsrManager {
static const MethodChannel _channel = const MethodChannel('asr_plugin');
///開始錄音
static Future<String> start({Map params}) async {
return await _channel.invokeMethod('start', params ?? {});
}
///停止錄音
static Future<String> stop() async {
return await _channel.invokeMethod('stop');
}
///取消錄音
static Future<String> cancel() async {
return await _channel.invokeMethod('cancel');
}
}
2.配置Android端的MethodChannel
這些文件除了AsrPlugin舶治,其他你可以在你下載的那個(gè)百度SDK里面找到,但是有一些需要修改的地方车猬。
這些文件你可以自己去下面我的百度網(wǎng)盤處下載
霉猛,應(yīng)該都是適配的:鏈接:https://pan.baidu.com/s/1ODBeSEBfpSu3-GFOZgCvgw
提取碼:gnkq
關(guān)于AsrPlugin文件,需要在Android端配置Flutter的環(huán)境:
在build.gradle文件中加入如圖中代碼:
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
flutter {
source '../..'
}
配置成功之后珠闰,我們就可以實(shí)現(xiàn)Android端可以使用flutter的文件了惜浅。
如果你沒用配置好這個(gè)環(huán)境的話,你就不能實(shí)現(xiàn)Android端與flutter端的通信伏嗜,用下面代碼來實(shí)現(xiàn)與flutter的start,stop,cancel操作:
代碼如下:
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class AsrPlugin implements MethodChannel.MethodCallHandler {
private final static String TAG = "AsrPlugin";
private final Activity activity;
private ResultStateful resultStateful;
private AsrManager asrManager;
public static void registerWith(Activity activity, BinaryMessenger messenger) {
MethodChannel channel = new MethodChannel(messenger, "asr_plugin");
AsrPlugin instance = new AsrPlugin(activity);
channel.setMethodCallHandler(instance);
}
public AsrPlugin(Activity activity) {
this.activity = activity;
}
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
initPermission();
switch (methodCall.method) {
case "start":
resultStateful = ResultStateful.of(result);
start(methodCall, resultStateful);
break;
case "stop":
stop(methodCall,result);
break;
case "cancel":
cancel(methodCall,result);
break;
default:
result.notImplemented();
}
}
private void start(MethodCall call, ResultStateful result) {
if (activity == null) {
Log.e(TAG, "Ignored start, current activity is null.");
result.error("Ignored start, current activity is null.", null, null);
return;
}
if (getAsrManager() != null) {
getAsrManager().start(call.arguments instanceof Map ? (Map) call.arguments : null);
} else {
Log.e(TAG, "Ignored start, current getAsrManager is null.");
result.error("Ignored start, current getAsrManager is null.", null, null);
}
}
private void stop(MethodCall call, MethodChannel.Result result) {
if (asrManager != null) {
asrManager.stop();
}
}
private void cancel(MethodCall call, MethodChannel.Result result) {
if (asrManager != null) {
asrManager.cancel();
}
}
@Nullable
private AsrManager getAsrManager() {
if (asrManager == null) {
if (activity != null && !activity.isFinishing()) {
asrManager = new AsrManager(activity, onAsrListener);
}
}
return asrManager;
}
/**
* android 6.0 以上需要?jiǎng)討B(tài)申請權(quán)限
*/
private void initPermission() {
String permissions[] = {Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.INTERNET,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
ArrayList<String> toApplyList = new ArrayList<String>();
for (String perm :permissions){
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(activity, perm)) {
toApplyList.add(perm);
//進(jìn)入到這里代表沒有權(quán)限.
}
}
String tmpList[] = new String[toApplyList.size()];
if (!toApplyList.isEmpty()){
ActivityCompat.requestPermissions(activity, toApplyList.toArray(tmpList), 123);
}
}
private OnAsrListener onAsrListener = new OnAsrListener() {
@Override
public void onAsrReady() {
}
@Override
public void onAsrBegin() {
}
@Override
public void onAsrEnd() {
}
@Override
public void onAsrPartialResult(String[] results, RecogResult recogResult) {
}
@Override
public void onAsrOnlineNluResult(String nluResult) {
}
@Override
public void onAsrFinalResult(String[] results, RecogResult recogResult) {
if (resultStateful != null) {
resultStateful.success(results[0]);
}
}
@Override
public void onAsrFinish(RecogResult recogResult) {
}
@Override
public void onAsrFinishError(int errorCode, int subErrorCode, String descMessage, RecogResult recogResult) {
if (resultStateful != null) {
resultStateful.error(descMessage, null, null);
}
}
@Override
public void onAsrLongFinish() {
}
@Override
public void onAsrVolume(int volumePercent, int volume) {
}
@Override
public void onAsrAudio(byte[] data, int offset, int length) {
}
@Override
public void onAsrExit() {
}
@Override
public void onOfflineLoaded() {
}
@Override
public void onOfflineUnLoaded() {
}
};
}
3.在flutter的Android文件中配置打包環(huán)境
主要為了避免環(huán)境沖突而導(dǎo)致功能不成功坛悉,配置如圖所示:
代碼如下:
ndk {
abiFilters "armeabi-v7a","arm64-v8a","x86_64","x86" /*只打包flutter所支持的架構(gòu),flutter沒有armeabi架構(gòu)的so承绸,加x86的原因是為了能夠兼容模擬器 abiFilters "armeabi-v7a" release 時(shí)打"armeabi-v7包 */
}
packagingOptions {
/* 確保app與asr_plugin都依賴的libflutter.so libapp.so merge時(shí)不沖突@https://github.com/card-io/card.io-Android-SDK/issues/186#issuecomment-427552552 */
pickFirst 'lib/x86_64/libflutter.so'
pickFirst 'lib/x86_64/libapp.so'
pickFirst 'lib/x86/libflutter.so'
pickFirst 'lib/arm64-v8a/libflutter.so'
pickFirst 'lib/arm64-v8a/libapp.so'
pickFirst 'lib/armeabi-v7a/libapp.so'
}
5.注冊plugin裸影,實(shí)現(xiàn)通信功能
主要是在flutter的android文件夾中調(diào)用我們上面建立的android的module里面通信文件AsrPlugin,然后用來通信數(shù)據(jù)通信八酒,達(dá)到我們所需要的效果。
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.example.asr_plugin.AsrPlugin;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
//flutter sdk >= v1.17.0 時(shí)使用下面方法注冊自定義plugin
AsrPlugin.registerWith(this, flutterEngine.getDartExecutor().getBinaryMessenger());
}
@Override
protected void onCreate(Bundle savedInstanceState ) {
super.onCreate(savedInstanceState);
}
}
6.最后刃唐,編寫一個(gè)dart界面來展示效果
代碼如圖所示:
import 'package:flutter/material.dart';
import 'asr_manager.dart';
void main(){
runApp(MaterialApp(
home: SpeakPage(),
));
}
///語音識(shí)別
class SpeakPage extends StatefulWidget {
@override
_SpeakPageState createState() => _SpeakPageState();
}
class _SpeakPageState extends State<SpeakPage>
with SingleTickerProviderStateMixin {
String speakTips = '長按說話';
String speakResult = '';
String result='故宮門票\n北京一日游\n迪士尼樂園';
Animation<double> animation;
AnimationController controller;
@override
void initState() {
controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(30),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[_topItem(), _bottomItem()],
),
),
),
);
}
_speakStart() {
controller.forward();
setState(() {
speakTips = '- 識(shí)別中 -';
});
AsrManager.start().then((text) {
if (text != null && text.length > 0) {
setState(() {
speakResult = text;
result=speakResult;
});
/*Navigator.pop(context);
Navigator.push(context,MaterialPageRoute(builder: (context)=> SearchPage(
keyword: speakResult,
)));*/
print("錯(cuò)誤" + text);
}
}).catchError((e) {
print("----------123" + e.toString());
});
}
_speakStop() {
setState(() {
speakTips = '長按說話';
});
controller.reset();
controller.stop();
AsrManager.stop();
}
_topItem() {
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, 30, 0, 30),
child: Text('你可以這樣說',
style: TextStyle(fontSize: 16, color: Colors.black54))),
Text(result,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Colors.grey,
)),
Padding(
padding: EdgeInsets.all(20),
child: Text(
speakResult,
style: TextStyle(color: Colors.blue),
),
)
],
);
}
_bottomItem() {
return FractionallySizedBox(
widthFactor: 1,
child: Stack(
children: <Widget>[
GestureDetector(
onTapDown: (e) {
_speakStart();
},
onTapUp: (e) {
_speakStop();
},
onTapCancel: () {
_speakStop();
},
child: Center(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(10),
child: Text(
speakTips,
style: TextStyle(color: Colors.blue, fontSize: 12),
),
),
Stack(
children: <Widget>[
Container(
//占坑羞迷,避免動(dòng)畫執(zhí)行過程中導(dǎo)致父布局大小變得
height: MIC_SIZE,
width: MIC_SIZE,
),
Center(
child: AnimatedMic(
animation: animation,
),
)
],
)
],
),
),
),
Positioned(
right: 0,
bottom: 20,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.close,
size: 30,
color: Colors.grey,
),
),
)
],
),
);
}
}
const double MIC_SIZE = 80;
class AnimatedMic extends AnimatedWidget {
static final _opacityTween = Tween<double>(begin: 1, end: 0.5);
static final _sizeTween = Tween<double>(begin: MIC_SIZE, end: MIC_SIZE - 20);
AnimatedMic({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(MIC_SIZE / 2),
),
child: Icon(
Icons.mic,
color: Colors.white,
size: 30,
),
),
);
}
}
步驟可能畢竟多界轩,我可能有也很多沒用考慮的地方哦,如果遇到問題可以提出來衔瓮,這個(gè)功能的實(shí)現(xiàn)主要來自我學(xué)習(xí)flutter視頻的那個(gè)老師浊猾,老師博客地址:https://blog.csdn.net/fengyuzhengfan。