Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีพัฒนา Speech Recognizer ใน Android โดยไม่ใช้ Google API

ภาพรวม

ตัวอย่างนี้สาธิตวิธีการสร้าง Speech Recognition (การรู้จำเสียงพูด) ใน Android โดยใช้ SpeechRecognizer API ของระบบ ซึ่งทำงานบนอุปกรณ์ (On-device) ไม่ต้องพึ่งพา Google Cloud API

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

สร้างโปรเจกต์ Android ใหม่ใน Android Studio

ขั้นตอนที่ 2: ออกแบบ Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:src="@drawable/ic_mic" />

    <ToggleButton
        android:id="@+id/toggleButton1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="26dp"
        android:textOn="กำลังฟัง..."
        android:textOff="เริ่มบันทึก"/>

    <ProgressBar
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/toggleButton1"
        android:layout_marginTop="28dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/progressBar1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="47dp"
        android:textSize="18sp"/>

</RelativeLayout>

ขั้นตอนที่ 3: เขียนโค้ด MainActivity (src/MainActivity.java)

package app.com.sample;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity implements RecognitionListener {
    private static final int REQUEST_RECORD_PERMISSION = 100;
    private static final String LOG_TAG = "VoiceRecognitionActivity";

    private TextView returnedText;
    private ToggleButton toggleButton;
    private ProgressBar progressBar;
    private SpeechRecognizer speechRecognizer;
    private Intent recognizerIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        returnedText = findViewById(R.id.textView1);
        progressBar = findViewById(R.id.progressBar1);
        toggleButton = findViewById(R.id.toggleButton1);

        progressBar.setVisibility(View.INVISIBLE);

        // ตรวจสอบว่ารองรับ Speech Recognition หรือไม่
        boolean available = SpeechRecognizer.isRecognitionAvailable(this);
        Log.i(LOG_TAG, "isRecognitionAvailable: " + available);
        
        if (!available) {
            Toast.makeText(this, "อุปกรณ์นี้ไม่รองรับ Speech Recognition", Toast.LENGTH_LONG).show();
            toggleButton.setEnabled(false);
            return;
        }

        speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
        speechRecognizer.setRecognitionListener(this);

        recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, "th-TH");
        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        recognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
        recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);

        toggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    startListening();
                } else {
                    stopListening();
                }
            }
        });
    }

    private void startListening() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
                == PackageManager.PERMISSION_GRANTED) {
            progressBar.setVisibility(View.VISIBLE);
            progressBar.setIndeterminate(true);
            speechRecognizer.startListening(recognizerIntent);
        } else {
            ActivityCompat.requestPermissions(this, 
                new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_RECORD_PERMISSION);
            toggleButton.setChecked(false);
        }
    }

    private void stopListening() {
        progressBar.setIndeterminate(false);
        progressBar.setVisibility(View.INVISIBLE);
        speechRecognizer.stopListening();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_RECORD_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startListening();
            } else {
                Toast.makeText(this, "ต้องอนุญาตสิทธิ์ไมโครโฟน", Toast.LENGTH_SHORT).show();
                toggleButton.setChecked(false);
            }
        }
    }

    // ===== RecognitionListener Methods =====

    @Override
    public void onReadyForSpeech(Bundle params) {
        Log.i(LOG_TAG, "onReadyForSpeech");
    }

    @Override
    public void onBeginningOfSpeech() {
        Log.i(LOG_TAG, "onBeginningOfSpeech");
        progressBar.setIndeterminate(false);
        progressBar.setMax(10);
    }

    @Override
    public void onRmsChanged(float rmsdB) {
        Log.i(LOG_TAG, "onRmsChanged: " + rmsdB);
        progressBar.setProgress((int) Math.abs(rmsdB));
    }

    @Override
    public void onBufferReceived(byte[] buffer) {
        Log.i(LOG_TAG, "onBufferReceived");
    }

    @Override
    public void onEndOfSpeech() {
        Log.i(LOG_TAG, "onEndOfSpeech");
        progressBar.setIndeterminate(true);
        toggleButton.setChecked(false);
    }

    @Override
    public void onError(int errorCode) {
        String errorMessage = getErrorText(errorCode);
        Log.d(LOG_TAG, "Error: " + errorMessage);
        returnedText.setText("ข้อผิดพลาด: " + errorMessage);
        toggleButton.setChecked(false);
        progressBar.setVisibility(View.INVISIBLE);
    }

    @Override
    public void onResults(Bundle results) {
        Log.i(LOG_TAG, "onResults");
        ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
        if (matches != null && !matches.isEmpty()) {
            StringBuilder text = new StringBuilder();
            for (String result : matches) {
                text.append(result).append("\n");
            }
            returnedText.setText(text.toString());
        }
    }

    @Override
    public void onPartialResults(Bundle partialResults) {
        Log.i(LOG_TAG, "onPartialResults");
        ArrayList<String> matches = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
        if (matches != null && !matches.isEmpty()) {
            returnedText.setText(matches.get(0) + "...");
        }
    }

    @Override
    public void onEvent(int eventType, Bundle params) {
        Log.i(LOG_TAG, "onEvent: " + eventType);
    }

    private static String getErrorText(int errorCode) {
        switch (errorCode) {
            case SpeechRecognizer.ERROR_AUDIO: return "ข้อผิดพลาดการบันทึกเสียง";
            case SpeechRecognizer.ERROR_CLIENT: return "ข้อผิดพลาดฝั่ง Client";
            case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS: return "สิทธิ์ไม่เพียงพอ";
            case SpeechRecognizer.ERROR_NETWORK: return "ข้อผิดพลาดเครือข่าย";
            case SpeechRecognizer.ERROR_NETWORK_TIMEOUT: return "หมดเวลาเครือข่าย";
            case SpeechRecognizer.ERROR_NO_MATCH: return "ไม่พบผลลัพธ์ที่ตรงกัน";
            case SpeechRecognizer.ERROR_RECOGNIZER_BUSY: return "Recognizer ไม่ว่าง";
            case SpeechRecognizer.ERROR_SERVER: return "ข้อผิดพลาด Server";
            case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: return "ไม่มีเสียงพูด";
            default: return "ไม่เข้าใจ กรุณาลองใหม่";
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (speechRecognizer != null) {
            speechRecognizer.destroy();
            Log.i(LOG_TAG, "destroy");
        }
    }
}

ขั้นตอนที่ 4: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

คำอธิบายสำคัญ

  • SpeechRecognizer.isRecognitionAvailable() - ตรวจสอบว่าอุปกรณ์รองรับหรือไม่
  • LANGUAGE_MODEL_FREE_FORM - เหมาะสำหรับการพูดทั่วไป (Dictation)
  • LANGUAGE_MODEL_WEB_SEARCH - เหมาะสำหรับคำค้นหาสั้นๆ
  • EXTRA_PARTIAL_RESULTS - แสดงผลลัพธ์ระหว่างพูด (Real-time)
  • onRmsChanged - ระดับเสียง (RMS) สำหรับ Progress Bar

ข้อจำกัด

  • ต้องการอินเทอร์เน็ตสำหรับบางอุปกรณ์ (ส่งไป Server ของ Google)
  • ไม่ใช่ Offline 100% บนอุปกรณ์เก่า
  • Android 10+ รองรับ On-device recognition ดีกว่า

การทดสอบ

รันแอปบนอุปกรณ์จริง กด Toggle Button ให้สิทธิ์ไมโครโฟน พูดภาษาไทย/อังกฤษ จะแสดงผลลัพธ์

วิธีพัฒนา Speech Recognizer ใน Android โดยไม่ใช้ Google API วิธีพัฒนา Speech Recognizer ใน Android โดยไม่ใช้ Google API