科大訊飛語音聽寫—JavaScript

查看科大訊飛語音聽寫demo即可看見最新的js demo,適用於前端。直接下載即可食用。
貼上主要代碼:
index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link href="index.css" type="text/css" rel="stylesheet"></link>
</head>

<body>
    <div>
        <div class="service-item service-item-taste">
            <h2 class="service-item-title">語音識別</h2>
            <div class="service-item-content service-item-taste-content">
                <div class="taste-content">
                    <button class="taste-button ready-button" id="taste_button">開始識別</button>
                </div>
                <div class="start-taste">
                    <div class="start-taste-left">
                        <div class="time-box">
                            <span class="start-taste-line">
              <hr class="hr hr1">
              <hr class="hr hr2">
              <hr class="hr hr3">
              <hr class="hr hr4">
              <hr class="hr hr5">
              <hr class="hr hr6">
              <hr class="hr hr7">
              <hr class="hr hr8">
              <hr class="hr hr9">
              <hr class="hr hr10">
            </span>
                            <span class="total-time"><span class="used-time">00: 00</span> / 01: 00</span>
                            </span>
                        </div>
                        <div class="start-taste-button">
                            <button class="taste-button start-button">結束識別</button>
                        </div>
                    </div>
                    <div class="output-box" id="result_output"></div>
                </div>
            </div>
        </div>
    </div>
    <script src="hmac-sha256.js"></script>
    <script src="enc-base64-min.js"></script>
    <script src="jquery.js"></script>
    <script src="index.js"></script>
</body>

</html>

index.js


/**
 * Created by lycheng on 2019/8/1.
 * 
 * 語音聽寫流式 WebAPI 接口調用示例 接口文檔(必看):https://doc.xfyun.cn/rest_api/語音聽寫(流式版).html
 * webapi 聽寫服務參考帖子(必看):http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38947&extra=
 * 語音聽寫流式WebAPI 服務,熱詞使用方式:登陸開放平臺https://www.xfyun.cn/後,找到控制檯--我的應用---語音聽寫---個性化熱詞,上傳熱詞
 * 注意:熱詞只能在識別的時候會增加熱詞的識別權重,需要注意的是增加相應詞條的識別率,但並不是絕對的,具體效果以您測試爲準。
 * 錯誤碼鏈接:
 * https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E9%94%99%E8%AF%AF%E7%A0%81
 * https://www.xfyun.cn/document/error-code (code返回錯誤碼時必看)
 * 語音聽寫流式WebAPI 服務,方言或小語種試用方法:登陸開放平臺https://www.xfyun.cn/後,在控制檯--語音聽寫(流式)--方言/語種處添加
 * 添加後會顯示該方言/語種的參數值
 * 
 */

// 音頻轉碼worker
let recorderWorker = new Worker('./transformpcm.worker.js')
    // 記錄處理的緩存音頻
let buffer = []
let AudioContext = window.AudioContext || window.webkitAudioContext
let notSupportTip = '請試用chrome瀏覽器且域名爲localhost或127.0.0.1測試'
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia

recorderWorker.onmessage = function(e) {
    buffer.push(...e.data.buffer)
}

class IatRecorder {
    constructor(config) {
        this.config = config
        this.state = 'ing'
        this.language = config.language || 'zh_cn'
        this.accent = config.accent || 'mandarin'

        //以下信息在控制檯-我的應用-語音聽寫(流式版)頁面獲取
        this.appId = '5d429130'
        this.apiKey = 'e8e58c25fe7fcb3fd19a9595761654c8'
        this.apiSecret = 'a23d4ed0f9e42147479fe1a5060060d9'
    }

    start() {
        this.stop()
        if (navigator.getUserMedia && AudioContext) {
            this.state = 'ing'
            if (!this.recorder) {
                var context = new AudioContext()
                this.context = context
                this.recorder = context.createScriptProcessor(0, 1, 1)

                var getMediaSuccess = (stream) => {
                    var mediaStream = this.context.createMediaStreamSource(stream)
                    this.mediaStream = mediaStream
                    this.recorder.onaudioprocess = (e) => {
                        this.sendData(e.inputBuffer.getChannelData(0))
                    }
                    this.connectWebsocket()
                }
                var getMediaFail = (e) => {
                    this.recorder = null
                    this.mediaStream = null
                    this.context = null
                    console.log('請求麥克風失敗')
                }
                if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
                    navigator.mediaDevices.getUserMedia({
                        audio: true,
                        video: false
                    }).then((stream) => {
                        getMediaSuccess(stream)
                    }).catch((e) => {
                        getMediaFail(e)
                    })
                } else {
                    navigator.getUserMedia({
                        audio: true,
                        video: false
                    }, (stream) => {
                        getMediaSuccess(stream)
                    }, function(e) {
                        getMediaFail(e)
                    })
                }
            } else {
                this.connectWebsocket()
            }
        } else {
            var isChrome = navigator.userAgent.toLowerCase().match(/chrome/)
            alert(notSupportTip)
        }
    }

    stop() {
        this.state = 'end'
        try {
            this.mediaStream.disconnect(this.recorder)
            this.recorder.disconnect()
        } catch (e) {}
    }

    sendData(buffer) {
        recorderWorker.postMessage({
            command: 'transform',
            buffer: buffer
        })
    }
    connectWebsocket() {
        var url = 'wss://iat-api.xfyun.cn/v2/iat'
        var host = 'iat-api.xfyun.cn'
        var apiKey = this.apiKey
        var apiSecret = this.apiSecret
        var date = new Date().toGMTString()
        var algorithm = 'hmac-sha256'
        var headers = 'host date request-line'
        var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
        var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
        var signature = CryptoJS.enc.Base64.stringify(signatureSha)
        var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
        var authorization = btoa(authorizationOrigin)
        url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
        if ('WebSocket' in window) {
            this.ws = new WebSocket(url)
        } else if ('MozWebSocket' in window) {
            this.ws = new MozWebSocket(url)
        } else {
            alert(notSupportTip)
            return null
        }
        this.ws.onopen = (e) => {
            this.mediaStream.connect(this.recorder)
            this.recorder.connect(this.context.destination)
            setTimeout(() => {
                this.wsOpened(e)
            }, 500)
            this.config.onStart && this.config.onStart(e)
        }
        this.ws.onmessage = (e) => {
            this.config.onMessage && this.config.onMessage(e)
            this.wsOnMessage(e)
        }
        this.ws.onerror = (e) => {
            this.stop()
            this.config.onError && this.config.onError(e)
        }
        this.ws.onclose = (e) => {
            this.stop()
            this.config.onClose && this.config.onClose(e)
        }
    }

    wsOpened() {
        if (this.ws.readyState !== 1) {
            return
        }
        var audioData = buffer.splice(0, 1280)
        console.log('audioData', audioData.length)
        var params = {
            'common': {
                'app_id': this.appId
            },
            'business': {
                'language': this.language, //小語種可在控制檯--語音聽寫(流式)--方言/語種處添加試用
                'domain': 'iat',
                'accent': this.accent, //中文方言可在控制檯--語音聽寫(流式)--方言/語種處添加試用
                'vad_eos': 5000,
                'dwa': 'wpgs' //爲使該功能生效,需到控制檯開通動態修正功能(該功能免費)
            },
            'data': {
                'status': 0,
                'format': 'audio/L16;rate=16000',
                'encoding': 'raw',
                'audio': this.ArrayBufferToBase64(audioData)
            }
        }
        this.ws.send(JSON.stringify(params))
        this.handlerInterval = setInterval(() => {
            // websocket未連接
            if (this.ws.readyState !== 1) {
                clearInterval(this.handlerInterval)
                return
            }
            if (buffer.length === 0) {
                if (this.state === 'end') {
                    this.ws.send(JSON.stringify({
                        'data': {
                            'status': 2,
                            'format': 'audio/L16;rate=16000',
                            'encoding': 'raw',
                            'audio': ''
                        }
                    }))
                    clearInterval(this.handlerInterval)
                }
                return false
            }
            audioData = buffer.splice(0, 1280)
                // 中間幀
            this.ws.send(JSON.stringify({
                'data': {
                    'status': 1,
                    'format': 'audio/L16;rate=16000',
                    'encoding': 'raw',
                    'audio': this.ArrayBufferToBase64(audioData)
                }
            }))
        }, 40)
    }

    wsOnMessage(e) {
        let jsonData = JSON.parse(e.data)
            // 識別結束
        if (jsonData.code === 0 && jsonData.data.status === 2) {
            this.ws.close()
        }
        if (jsonData.code !== 0) {
            this.ws.close()
            console.log(`${jsonData.code}:${jsonData.message}`)
        }
    }

    ArrayBufferToBase64(buffer) {
        var binary = ''
        var bytes = new Uint8Array(buffer)
        var len = bytes.byteLength
        for (var i = 0; i < len; i++) {
            binary += String.fromCharCode(bytes[i])
        }
        return window.btoa(binary)
    }
}

class IatTaste {
    constructor() {
        var iatRecorder = new IatRecorder({
            onClose: () => {
                this.stop()
                this.reset()
            },
            onError: (data) => {
                this.stop()
                this.reset()
                alert('WebSocket連接失敗')
            },
            onMessage: (e) => {
                let str = ''
                let jsonData = JSON.parse(e.data)
                if (jsonData.data && jsonData.data.result) {
                    this.setResult(jsonData.data.result)
                }
            },
            onStart: () => {
                $('hr').addClass('hr')
                var dialect = $('.dialect-select').find('option:selected').text()
                $('.taste-content').css('display', 'none')
                $('.start-taste').addClass('flex-display-1')
                $('.dialect-select').css('display', 'none')
                $('.start-button').text('結束識別')
                $('.time-box').addClass('flex-display-1')
                $('.dialect').text(dialect).css('display', 'inline-block')
                this.counterDown($('.used-time'))
            }
        })
        this.iatRecorder = iatRecorder
        this.counterDownDOM = $('.used-time')
        this.counterDownTime = 0

        this.text = {
            start: '開始識別',
            stop: '結束識別'
        }
        this.resultText = ''
    }

    start() {
        this.iatRecorder.start()
    }

    stop() {
        $('hr').removeClass('hr')
        this.iatRecorder.stop()
    }

    reset() {
        this.counterDownTime = 0
        clearTimeout(this.counterDownTimeout)
        buffer = []
        $('.time-box').removeClass('flex-display-1').css('display', 'none')
        $('.start-button').text(this.text.start)
        $('.dialect').css('display', 'none')
        $('.dialect-select').css('display', 'inline-block')
    }

    init() {
        let self = this
        $('#taste_button').click(function() {
            if (navigator.getUserMedia && AudioContext && recorderWorker) {
                self.start()
            } else {
                alert(notSupportTip)
            }
        })
        $('.start-button').click(function() {
            if ($(this).text() === self.text.start) {
                $('#result_output').text('')
                self.resultText = ''
                self.start()
            } else {
                self.reset()
                self.stop()
            }
        })
    }
    setResult(data) {
        var str = ''
        var resultStr = ''
        let ws = data.ws
        for (let i = 0; i < ws.length; i++) {
            str = str + ws[i].cw[0].w
        }
        // 開啓wpgs會有此字段(前提:在控制檯開通動態修正功能)
        // 取值爲 "apd"時表示該片結果是追加到前面的最終結果;取值爲"rpl" 時表示替換前面的部分結果,替換範圍爲rg字段
        if (data.pgs === 'apd') {
            this.resultText = $('#result_output').text()
        }
        resultStr = this.resultText + str
        $('#result_output').text(resultStr)
    }

    counterDown() {
        if (this.counterDownTime === 60) {
            this.counterDownDOM.text('01: 00')
            this.stop()
        } else if (this.counterDownTime > 60) {
            this.reset()
            return false
        } else if (this.counterDownTime >= 0 && this.counterDownTime < 10) {
            this.counterDownDOM.text('00: 0' + this.counterDownTime)
        } else {
            this.counterDownDOM.text('00: ' + this.counterDownTime)
        }
        this.counterDownTime++
            this.counterDownTimeout = setTimeout(() => {
                this.counterDown()
            }, 1000)
    }
}
var iatTaste = new IatTaste()
iatTaste.init()

後期準備修改一下,適用於自己的風格。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章