跳至主要内容

語音轉文字 - Wio Terminal

在這部分課程中,你將編寫代碼,使用語音服務將捕獲的音頻中的語音轉換為文字。

將音頻發送到語音服務

可以使用 REST API 將音頻發送到語音服務。要使用語音服務,首先需要請求訪問令牌,然後使用該令牌訪問 REST API。這些訪問令牌在 10 分鐘後過期,因此你的代碼應定期請求它們以確保它們始終是最新的。

任務 - 獲取訪問令牌

  1. 如果尚未打開 smart-timer 項目,請打開它。

  2. 將以下庫依賴項添加到 platformio.ini 文件中以訪問 WiFi 並處理 JSON:

    seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
    seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
    seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
    seeed-studio/Seeed Arduino RTC @ 2.0.0
    bblanchon/ArduinoJson @ 6.17.3
  3. 將以下代碼添加到 config.h 標頭文件中:

    const char *SSID = "<SSID>";
    const char *PASSWORD = "<PASSWORD>";

    const char *SPEECH_API_KEY = "<API_KEY>";
    const char *SPEECH_LOCATION = "<LOCATION>";
    const char *LANGUAGE = "<LANGUAGE>";

    const char *TOKEN_URL = "https://%s.api.cognitive.microsoft.com/sts/v1.0/issuetoken";

    <SSID><PASSWORD> 替換為你的 WiFi 的相關值。

    <API_KEY> 替換為你的語音服務資源的 API 密鑰。將 <LOCATION> 替換為你創建語音服務資源時使用的位置。

    <LANGUAGE> 替換為你將使用的語言的區域名稱,例如英語的 en-GB 或粵語的 zn-HK。你可以在 Microsoft 文檔上的語言和語音支持文檔 中找到支持的語言及其區域名稱列表。

    TOKEN_URL 常量是令牌發行者的 URL,不包含位置。稍後將與位置結合以獲取完整的 URL。

  4. 就像連接到 Custom Vision 一樣,你需要使用 HTTPS 連接來連接到令牌發行服務。在 config.h 的末尾添加以下代碼:

    const char *TOKEN_CERTIFICATE =
    "-----BEGIN CERTIFICATE-----\r\n"
    "MIIF8zCCBNugAwIBAgIQAueRcfuAIek/4tmDg0xQwDANBgkqhkiG9w0BAQwFADBh\r\n"
    "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\n"
    "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\r\n"
    "MjAeFw0yMDA3MjkxMjMwMDBaFw0yNDA2MjcyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\r\n"
    "MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jv\r\n"
    "c29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwNjCCAiIwDQYJKoZIhvcNAQEBBQAD\r\n"
    "ggIPADCCAgoCggIBALVGARl56bx3KBUSGuPc4H5uoNFkFH4e7pvTCxRi4j/+z+Xb\r\n"
    "wjEz+5CipDOqjx9/jWjskL5dk7PaQkzItidsAAnDCW1leZBOIi68Lff1bjTeZgMY\r\n"
    "iwdRd3Y39b/lcGpiuP2d23W95YHkMMT8IlWosYIX0f4kYb62rphyfnAjYb/4Od99\r\n"
    "ThnhlAxGtfvSbXcBVIKCYfZgqRvV+5lReUnd1aNjRYVzPOoifgSx2fRyy1+pO1Uz\r\n"
    "aMMNnIOE71bVYW0A1hr19w7kOb0KkJXoALTDDj1ukUEDqQuBfBxReL5mXiu1O7WG\r\n"
    "0vltg0VZ/SZzctBsdBlx1BkmWYBW261KZgBivrql5ELTKKd8qgtHcLQA5fl6JB0Q\r\n"
    "gs5XDaWehN86Gps5JW8ArjGtjcWAIP+X8CQaWfaCnuRm6Bk/03PQWhgdi84qwA0s\r\n"
    "sRfFJwHUPTNSnE8EiGVk2frt0u8PG1pwSQsFuNJfcYIHEv1vOzP7uEOuDydsmCjh\r\n"
    "lxuoK2n5/2aVR3BMTu+p4+gl8alXoBycyLmj3J/PUgqD8SL5fTCUegGsdia/Sa60\r\n"
    "N2oV7vQ17wjMN+LXa2rjj/b4ZlZgXVojDmAjDwIRdDUujQu0RVsJqFLMzSIHpp2C\r\n"
    "Zp7mIoLrySay2YYBu7SiNwL95X6He2kS8eefBBHjzwW/9FxGqry57i71c2cDAgMB\r\n"
    "AAGjggGtMIIBqTAdBgNVHQ4EFgQU1cFnOsKjnfR3UltZEjgp5lVou6UwHwYDVR0j\r\n"
    "BBgwFoAUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1Ud\r\n"
    "JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMHYG\r\n"
    "CCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu\r\n"
    "Y29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln\r\n"
    "aUNlcnRHbG9iYWxSb290RzIuY3J0MHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9j\r\n"
    "cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOG\r\n"
    "MWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5j\r\n"
    "cmwwHQYDVR0gBBYwFDAIBgZngQwBAgEwCAYGZ4EMAQICMBAGCSsGAQQBgjcVAQQD\r\n"
    "AgEAMA0GCSqGSIb3DQEBDAUAA4IBAQB2oWc93fB8esci/8esixj++N22meiGDjgF\r\n"
    "+rA2LUK5IOQOgcUSTGKSqF9lYfAxPjrqPjDCUPHCURv+26ad5P/BYtXtbmtxJWu+\r\n"
    "cS5BhMDPPeG3oPZwXRHBJFAkY4O4AF7RIAAUW6EzDflUoDHKv83zOiPfYGcpHc9s\r\n"
    "kxAInCedk7QSgXvMARjjOqdakor21DTmNIUotxo8kHv5hwRlGhBJwps6fEVi1Bt0\r\n"
    "trpM/3wYxlr473WSPUFZPgP1j519kLpWOJ8z09wxay+Br29irPcBYv0GMXlHqThy\r\n"
    "8y4m/HyTQeI2IMvMrQnwqPpY+rLIXyviI2vLoI+4xKE4Rn38ZZ8m\r\n"
    "-----END CERTIFICATE-----\r\n";

    這與你連接到 Custom Vision 時使用的證書相同。

  5. 將 WiFi 標頭文件和 config 標頭文件的包含添加到 main.cpp 文件的頂部:

    #include <rpcWiFi.h>

    #include "config.h"
  6. setup 函數上方的 main.cpp 中添加代碼以連接到 WiFi:

    void connectWiFi()
    {
    while (WiFi.status() != WL_CONNECTED)
    {
    Serial.println("Connecting to WiFi..");
    WiFi.begin(SSID, PASSWORD);
    delay(500);
    }

    Serial.println("Connected!");
    }
  7. 在串行連接建立後,從 setup 函數調用此函數:

    connectWiFi();
  8. src 文件夾中創建一個名為 speech_to_text.h 的新標頭文件。在此標頭文件中,添加以下代碼:

    #pragma once

    #include <Arduino.h>
    #include <ArduinoJson.h>
    #include <HTTPClient.h>
    #include <WiFiClientSecure.h>

    #include "config.h"
    #include "mic.h"

    class SpeechToText
    {
    public:

    private:

    };

    SpeechToText speechToText;

    這包括一些 HTTP 連接、配置和 mic.h 標頭文件的必要標頭文件,並定義了一個名為 SpeechToText 的類,然後聲明了一個可以稍後使用的該類的實例。

  9. 將以下兩個字段添加到此類的 private 部分:

    WiFiClientSecure _token_client;
    String _access_token;

    _token_client 是一個使用 HTTPS 的 WiFi 客戶端,將用於獲取訪問令牌。然後此令牌將存儲在 _access_token 中。

  10. 將以下方法添加到 private 部分:

    String getAccessToken()
    {
    char url[128];
    sprintf(url, TOKEN_URL, SPEECH_LOCATION);

    HTTPClient httpClient;
    httpClient.begin(_token_client, url);

    httpClient.addHeader("Ocp-Apim-Subscription-Key", SPEECH_API_KEY);
    int httpResultCode = httpClient.POST("{}");

    if (httpResultCode != 200)
    {
    Serial.println("Error getting access token, trying again...");
    delay(10000);
    return getAccessToken();
    }

    Serial.println("Got access token.");
    String result = httpClient.getString();

    httpClient.end();

    return result;
    }

    此代碼使用語音資源的位置構建令牌發行者 API 的 URL。然後它創建一個 HTTPClient 來進行 Web 請求,將其設置為使用配置了令牌端點證書的 WiFi 客戶端。它將 API 密鑰設置為調用的標頭。然後它發出 POST 請求以獲取證書,如果出現任何錯誤,則重試。最後返回訪問令牌。

  11. public 部分,添加一個方法來獲取訪問令牌。這在後續課程中將需要用於將文本轉換為語音。

    String AccessToken()
    {
    return _access_token;
    }
  12. public 部分,添加一個 init 方法來設置令牌客戶端:

    void init()
    {
    _token_client.setCACert(TOKEN_CERTIFICATE);
    _access_token = getAccessToken();
    }

    這會在 WiFi 客戶端上設置證書,然後獲取訪問令牌。

  13. main.cpp 中,將此新標頭文件添加到包含指令中:

    #include "speech_to_text.h"
  14. setup 函數的末尾,在 mic.init 調用之後但在 Ready 被寫入串行監視器之前,初始化 SpeechToText 類:

    speechToText.init();

任務 - 從閃存中讀取音頻

  1. 在本課程的早期部分,音頻被記錄到閃存中。這些音頻需要發送到語音服務 REST API,因此需要從閃存中讀取。它不能加載到內存緩衝區中,因為它會太大。HTTPClient 類可以使用 Arduino Stream 流式傳輸數據 - 一個可以加載小塊數據的類,將這些小塊數據一次一個地作為請求的一部分發送。每次你調用 read 時,它會返回下一塊數據。可以創建一個 Arduino 流來從閃存中讀取數據。在 src 文件夾中創建一個名為 flash_stream.h 的新文件,並添加以下代碼:

    #pragma once

    #include <Arduino.h>
    #include <HTTPClient.h>
    #include <sfud.h>

    #include "config.h"

    class FlashStream : public Stream
    {
    public:
    virtual size_t write(uint8_t val)
    {
    }

    virtual int available()
    {
    }

    virtual int read()
    {
    }

    virtual int peek()
    {
    }
    private:

    };

    這聲明了 FlashStream 類,派生自 Arduino Stream 類。這是一個抽象類 - 派生類必須實現一些方法,然後才能實例化該類,這些方法在此類中定義。

    ✅ 在 Arduino Stream 文檔 中閱讀更多關於 Arduino Streams 的信息

  2. 將以下字段添加到 private 部分:

    size_t _pos;
    size_t _flash_address;
    const sfud_flash *_flash;

    byte _buffer[HTTP_TCP_BUFFER_SIZE];

    這定義了一個臨時緩衝區來存儲從閃存中讀取的數據,以及用於存儲當前讀取位置、當前讀取閃存地址和閃存設備的字段。

  3. private 部分,添加以下方法:

    void populateBuffer()
    {
    sfud_read(_flash, _flash_address, HTTP_TCP_BUFFER_SIZE, _buffer);
    _flash_address += HTTP_TCP_BUFFER_SIZE;
    _pos = 0;
    }

    此代碼從當前地址讀取閃存並將數據存儲在緩衝區中。然後增加地址,因此下一次調用讀取下一塊內存。緩衝區的大小基於 HTTPClient 將一次發送到 REST API 的最大塊。

    💁 擦除閃存必須使用粒度大小,而讀取則不需要。

  4. 在此類的 public 部分,添加一個構造函數:

    FlashStream()
    {
    _pos = 0;
    _flash_address = 0;
    _flash = sfud_get_device_table() + 0;

    populateBuffer();
    }

    此構造函數設置所有字段以從閃存塊的起始位置開始讀取,並將第一塊數據加載到緩衝區中。

  5. 實現 write 方法。此流僅讀取數據,因此可以不執行任何操作並返回 0:

    virtual size_t write(uint8_t val)
    {
    return 0;
    }
  6. 實現 peek 方法。這會返回當前位置的數據而不移動流。只要沒有從流中讀取數據,多次調用 peek 將始終返回相同的數據。

    virtual int peek()
    {
    return _buffer[_pos];
    }
  7. 實現 available 函數。這會返回可以從流中讀取的字節數,或者如果流已完成則返回 -1。對於此類,最大可用量不會超過 HTTPClient 的塊大小。當此流在 HTTP 客戶端中使用時,它會調用此函數以查看有多少數據可用,然後請求這麼多數據發送到 REST API。我們不希望每個塊超過 HTTP 客戶端的塊大小,因此如果可用數量超過,則返回塊大小。如果少於,則返回可用數量。一旦所有數據都已流式傳輸,則返回 -1。

    virtual int available()
    {
    int remaining = BUFFER_SIZE - ((_flash_address - HTTP_TCP_BUFFER_SIZE) + _pos);
    int bytes_available = min(HTTP_TCP_BUFFER_SIZE, remaining);

    if (bytes_available == 0)
    {
    bytes_available = -1;
    }

    return bytes_available;
    }
  8. 實現 read 方法以返回緩衝區中的下一個字節,增加位置。如果位置超過緩衝區的大小,則用閃存中的下一塊數據填充緩衝區並重置位置。

    virtual int read()
    {
    int retVal = _buffer[_pos++];

    if (_pos == HTTP_TCP_BUFFER_SIZE)
    {
    populateBuffer();
    }

    return retVal;
    }
  9. speech_to_text.h 標頭文件中,添加此新標頭文件的包含指令:

    #include "flash_stream.h"

任務 - 將語音轉換為文字

  1. 可以通過將音頻發送到語音服務的 REST API 來將語音轉換為文字。此 REST API 與令牌發行者使用不同的證書,因此在 config.h 標頭文件中添加以下代碼以定義此證書:

    const char *SPEECH_CERTIFICATE =
    "-----BEGIN CERTIFICATE-----\r\n"
    "MIIF8zCCBNugAwIBAgIQCq+mxcpjxFFB6jvh98dTFzANBgkqhkiG9w0BAQwFADBh\r\n"
    "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\n"
    "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\r\n"
    "MjAeFw0yMDA3MjkxMjMwMDBaFw0yNDA2MjcyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\r\n"
    "MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jv\r\n"
    "c29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwMTCCAiIwDQYJKoZIhvcNAQEBBQAD\r\n"
    "ggIPADCCAgoCggIBAMedcDrkXufP7pxVm1FHLDNA9IjwHaMoaY8arqqZ4Gff4xyr\r\n"
    "RygnavXL7g12MPAx8Q6Dd9hfBzrfWxkF0Br2wIvlvkzW01naNVSkHp+OS3hL3W6n\r\n"
    "l/jYvZnVeJXjtsKYcXIf/6WtspcF5awlQ9LZJcjwaH7KoZuK+THpXCMtzD8XNVdm\r\n"
    "GW/JI0C/7U/E7evXn9XDio8SYkGSM63aLO5BtLCv092+1d4GGBSQYolRq+7Pd1kR\r\n"
    "EkWBPm0ywZ2Vb8GIS5DLrjelEkBnKCyy3B0yQud9dpVsiUeE7F5sY8Me96WVxQcb\r\n"
    "OyYdEY/j/9UpDlOG+vA+YgOvBhkKEjiqygVpP8EZoMMijephzg43b5Qi9r5UrvYo\r\n"
    "o19oR/8pf4HJNDPF0/FJwFVMW8PmCBLGstin3NE1+NeWTkGt0TzpHjgKyfaDP2tO\r\n"
    "4bCk1G7pP2kDFT7SYfc8xbgCkFQ2UCEXsaH/f5YmpLn4YPiNFCeeIida7xnfTvc4\r\n"
    "7IxyVccHHq1FzGygOqemrxEETKh8hvDR6eBdrBwmCHVgZrnAqnn93JtGyPLi6+cj\r\n"
    "WGVGtMZHwzVvX1HvSFG771sskcEjJxiQNQDQRWHEh3NxvNb7kFlAXnVdRkkvhjpR\r\n"
    "GchFhTAzqmwltdWhWDEyCMKC2x/mSZvZtlZGY+g37Y72qHzidwtyW7rBetZJAgMB\r\n"
    "AAGjggGtMIIBqTAdBgNVHQ4EFgQUDyBd16FXlduSzyvQx8J3BM5ygHYwHwYDVR0j\r\n"
    "BBgwFoAUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1Ud\r\n"
    "JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMHYG\r\n"
    "CCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu\r\n"
    "Y29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln\r\n"
    "aUNlcnRHbG9iYWxSb290RzIuY3J0MHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9j\r\n"
    "cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOG\r\n"
    "MWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5j\r\n"
    "cmwwHQYDVR0gBBYwFDAIBgZngQwBAgEwCAYGZ4EMAQICMBAGCSsGAQQBgjcVAQQD\r\n"
    "AgEAMA0GCSqGSIb3DQEBDAUAA4IBAQAlFvNh7QgXVLAZSsNR2XRmIn9iS8OHFCBA\r\n"
    "WxKJoi8YYQafpMTkMqeuzoL3HWb1pYEipsDkhiMnrpfeYZEA7Lz7yqEEtfgHcEBs\r\n"
    "K9KcStQGGZRfmWU07hPXHnFz+5gTXqzCE2PBMlRgVUYJiA25mJPXfB00gDvGhtYa\r\n"
    "+mENwM9Bq1B9YYLyLjRtUz8cyGsdyTIG/bBM/Q9jcV8JGqMU/UjAdh1pFyTnnHEl\r\n"
    "Y59Npi7F87ZqYYJEHJM2LGD+le8VsHjgeWX2CJQko7klXvcizuZvUEDTjHaQcs2J\r\n"
    "+kPgfyMIOY1DMJ21NxOJ2xPRC/wAh/hzSBRVtoAnyuxtkZ4VjIOh\r\n"
    "-----END CERTIFICATE-----\r\n";
  2. 為不包含位置的語音 URL 添加一個常量到此文件。稍後將與位置和語言結合以獲取完整的 URL。

    const char *SPEECH_URL = "https://%s.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=%s";
  3. speech_to_text.h 標頭文件中,在 SpeechToText 類的 private 部分,定義一個使用語音證書的 WiFi 客戶端字段:

    WiFiClientSecure _speech_client;
  4. init 方法中,在此 WiFi 客戶端上設置證書:

    _speech_client.setCACert(SPEECH_CERTIFICATE);
  5. SpeechToText 類的 public 部分,添加以下代碼以定義一個將語音轉換為文字的方法:

    String convertSpeechToText()
    {

    }
  6. 將以下代碼添加到此方法中,以使用配置了語音證書的 WiFi 客戶端創建一個 HTTP 客戶端,並使用設置了位置和語言的語音 URL:

    char url[128];
    sprintf(url, SPEECH_URL, SPEECH_LOCATION, LANGUAGE);

    HTTPClient httpClient;
    httpClient.begin(_speech_client, url);
  7. 需要在連接上設置一些標頭:

    httpClient.addHeader("Authorization", String("Bearer ") + _access_token);
    httpClient.addHeader("Content-Type", String("audio/wav; codecs=audio/pcm; samplerate=") + String(RATE));
    httpClient.addHeader("Accept", "application/json;text/xml");

    這會設置授權標頭,使用訪問令牌,音頻格式使用採樣率,並設置客戶端期望結果為 JSON。

  8. 在此之後,添加以下代碼以發出 REST API 調用:

    Serial.println("Sending speech...");

    FlashStream stream;
    int httpResponseCode = httpClient.sendRequest("POST", &stream, BUFFER_SIZE);

    Serial.println("Speech sent!");

    這會創建一個 FlashStream 並使用它將數據流式傳輸到 REST API。

  9. 在此之下,添加以下代碼:

    String text = "";

    if (httpResponseCode == 200)
    {
    String result = httpClient.getString();
    Serial.println(result);

    DynamicJsonDocument doc(1024);
    deserializeJson(doc, result.c_str());

    JsonObject obj = doc.as<JsonObject>();
    text = obj["DisplayText"].as<String>();
    }
    else if (httpResponseCode == 401)
    {
    Serial.println("Access token expired, trying again with a new token");
    _access_token = getAccessToken();
    return convertSpeechToText();
    }
    else
    {
    Serial.print("Failed to convert text to speech - error ");
    Serial.println(httpResponseCode);
    }

    此代碼檢查響應代碼。

    如果是 200,表示成功,則檢索結果,從 JSON 解碼,並將 DisplayText 屬性設置到 text 變量中。這是返回語音文本的屬性。

    如果響應代碼是 401,則訪問令牌已過期(這些令牌僅持續 10 分鐘)。請求一個新的訪問令牌,然後再次調用。

    否則,將錯誤發送到串行監視器,並保持 text 為空。

  10. 在此方法的末尾添加以下代碼以關閉 HTTP 客戶端並返回文本:

    httpClient.end();

    return text;
  11. main.cpp 中,在 processAudio 函數中調用此新的 convertSpeechToText 方法,然後將語音記錄到串行監視器:

    String text = speechToText.convertSpeechToText();
    Serial.println(text);
  12. 構建此代碼,將其上傳到你的 Wio Terminal,並通過串行監視器進行測試。一旦你在串行監視器中看到 Ready,按下 C 按鈕(左側最接近電源開關的按鈕),然後說話。將捕獲 4 秒的音頻,然後轉換為文字。

    --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
    --- More details at http://bit.ly/pio-monitor-filters
    --- Miniterm on /dev/cu.usbmodem1101 9600,8,N,1 ---
    --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
    Connecting to WiFi..
    Connected!
    Got access token.
    Ready.
    Starting recording...
    Finished recording
    Sending speech...
    Speech sent!
    {"RecognitionStatus":"Success","DisplayText":"Set a 2 minute and 27 second timer.","Offset":4700000,"Duration":35300000}
    Set a 2 minute and 27 second timer.

💁 你可以在 code-speech-to-text/wio-terminal 文件夾中找到此代碼。

😀 你的語音轉文字程序成功了!