Flutter學(xué)習(xí)第十三天毯盈,2021最新版超詳細(xì)Flutter2.0實(shí)現(xiàn)百度語音轉(zhuǎn)文字功能,Android和Flutter混合開發(fā)病袄?

雖然是實(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末热鞍,一起剝皮案震驚了整個(gè)濱河市葫慎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌薇宠,老刑警劉巖偷办,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異澄港,居然都是意外死亡椒涯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門回梧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來废岂,“玉大人,你說我怎么就攤上這事狱意『” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵详囤,是天一觀的道長财骨。 經(jīng)常有香客問我,道長纬纪,這世上最難降的妖魔是什么蚓再? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮包各,結(jié)果婚禮上摘仅,老公的妹妹穿的比我還像新娘。我一直安慰自己问畅,他們只是感情好娃属,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著护姆,像睡著了一般矾端。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卵皂,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天秩铆,我揣著相機(jī)與錄音,去河邊找鬼。 笑死殴玛,一個(gè)胖子當(dāng)著我的面吹牛捅膘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滚粟,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼寻仗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了凡壤?” 一聲冷哼從身側(cè)響起署尤,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亚侠,沒想到半個(gè)月后曹体,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盖奈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年混坞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钢坦。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡究孕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爹凹,到底是詐尸還是另有隱情厨诸,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布禾酱,位于F島的核電站微酬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏颤陶。R本人自食惡果不足惜颗管,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滓走。 院中可真熱鬧垦江,春花似錦、人聲如沸搅方。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姨涡。三九已至衩藤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涛漂,已是汗流浹背赏表。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓢剿。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓岁诉,卻偏偏與公主長得像蝌箍,于是被迫代替她去往敵國和親折联。 傳聞我的和親對(duì)象是個(gè)殘疾皇子裸卫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容