Skip to main content
Speech-to-text

Pengenalan ucapan real-time - Qwen

Layanan pengenalan ucapan real-time menerima aliran audio dan mentranskripsinya menjadi teks berbobot tanda baca secara real-time. Gunakan layanan ini untuk subtitel langsung, rapat daring, obrolan suara, asisten cerdas, dan skenario serupa.

Ikhtisar

Layanan ini mengalirkan audio dan mengembalikan teks hasil transkripsi dengan latensi rendah.
  • Mengenali bahasa Mandarin dengan akurasi tinggi, serta dialek Kanton, Sichuan, dan lainnya.
  • Menangani lingkungan akustik kompleks, dengan deteksi bahasa otomatis dan penyaringan cerdas terhadap audio non-ucapan.
  • Mengenali berbagai keadaan emosional, termasuk kaget, tenang, senang, sedih, jijik, marah, dan takut.
  • Mendukung hotword kustom untuk meningkatkan akurasi pengenalan istilah tertentu.
  • Mendukung peningkatan konteks untuk meningkatkan akurasi pengenalan dengan meneruskan riwayat percakapan atau istilah domain.
  • Menghasilkan timestamp untuk menghasilkan hasil pengenalan terstruktur.
  • Menerima laju sampel fleksibel dan berbagai format audio agar sesuai dengan berbagai lingkungan perekaman.
Untuk skenario batch seperti transkripsi rapat, analisis panggilan, dan pembuatan subtitel, gunakan Pengenalan ucapan non-real-time. Untuk panduan memilih model, lihat Speech-to-text.

Prasyarat

Mulai cepat

Contoh berikut menunjukkan cara memanggil layanan pengenalan ucapan real-time melalui SDK DashScope.
  • Qwen-Audio-3.0-ASR-Flash-Streaming/ Fun-ASR -Realtime
  • Paraformer
Selain WebSocket, model ini juga mendukung protokol AOQ. Untuk integrasi sisi klien yang memprioritaskan latensi stabil, ketahanan pada jaringan lemah, serta penekanan noise full-duplex dan pembatalan gema bawaan, AOQ direkomendasikan. Untuk perbandingan protokol, lihat Ikhtisar API Realtime.
  • Kenali ucapan dari mikrofon
  • Kenali file audio lokal
Kenali ucapan dari mikrofon dan tampilkan teks secara real-time, sehingga kata-kata muncul saat pembicara berbicara.
  • Java
  • Python
import com.alibaba.dashscope.audio.asr.recognition.Recognition;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionParam;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionResult;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.utils.Constants;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.TargetDataLine;

import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // Berikut adalah konfigurasi untuk wilayah Singapura. Saat memanggil, ganti "{WorkspaceId}" dengan ID ruang kerja Anda yang sebenarnya. Konfigurasi berbeda tiap wilayah.
        Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new RealtimeRecognitionTask());
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.MINUTES);
        System.exit(0);
    }
}

class RealtimeRecognitionTask implements Runnable {
    @Override
    public void run() {
        RecognitionParam param = RecognitionParam.builder()
                .model("qwen-audio-3.0-asr-flash-streaming")
                // Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
                // Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: .apiKey("sk-xxx")
                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                .format("pcm")
                .sampleRate(16000)
                .build();
        Recognition recognizer = new Recognition();

        ResultCallback<RecognitionResult> callback = new ResultCallback<RecognitionResult>() {
            @Override
            public void onEvent(RecognitionResult result) {
                if (result.isSentenceEnd()) {
                    System.out.println("Final Result: " + result.getSentence().getText());
                } else {
                    System.out.println("Intermediate Result: " + result.getSentence().getText());
                }
            }

            @Override
            public void onComplete() {
                System.out.println("Recognition complete");
            }

            @Override
            public void onError(Exception e) {
                System.out.println("RecognitionCallback error: " + e.getMessage());
            }
        };
        try {
            recognizer.call(param, callback);
            // Buat format audio
            AudioFormat audioFormat = new AudioFormat(16000, 16, 1, true, false);
            // Sesuaikan perangkat perekaman default berdasarkan format
            TargetDataLine targetDataLine =
                    AudioSystem.getTargetDataLine(audioFormat);
            targetDataLine.open(audioFormat);
            // Mulai merekam
            targetDataLine.start();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            long start = System.currentTimeMillis();
            // Rekam selama 50 detik dan lakukan transkripsi real-time
            while (System.currentTimeMillis() - start < 50000) {
                int read = targetDataLine.read(buffer.array(), 0, buffer.capacity());
                if (read > 0) {
                    buffer.limit(read);
                    // Kirim data audio yang direkam ke layanan pengenalan streaming
                    recognizer.sendAudioFrame(buffer);
                    buffer = ByteBuffer.allocate(1024);
                    // Laju perekaman dibatasi; tidur sejenak untuk mencegah penggunaan CPU berlebihan
                    Thread.sleep(20);
                }
            }
            recognizer.stop();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Tutup koneksi WebSocket setelah tugas selesai
            recognizer.getDuplexApi().close(1000, "bye");
        }

        System.out.println(
                "[Metric] requestId: "
                        + recognizer.getLastRequestId()
                        + ", first package delay ms: "
                        + recognizer.getFirstPackageDelay()
                        + ", last package delay ms: "
                        + recognizer.getLastPackageDelay());
    }
}

Fitur lanjutan

Konfigurasi segmentasi VAD

Voice Activity Detection (VAD) menentukan kapan segmen ucapan berkelanjutan berakhir, yang memicu event hasil pengenalan akhir. Ketiga keluarga model mengaktifkan VAD sisi server secara default, tetapi nama parameter dan granularitas penyetelannya berbeda:
  • Qwen-Audio-3.0-ASR-Flash-Streaming / Fun-ASR-Realtime / Paraformer: Dikonfigurasi melalui max_sentence_silence (ambang batas diam VAD untuk segmentasi, dalam milidetik). Ketika keheningan setelah segmen ucapan melebihi ambang batas ini, sistem menganggap kalimat tersebut selesai.
  • Qwen3-ASR-Flash-Realtime: Dikonfigurasi melalui session.turn_detection, yang mencakup silence_duration_ms (durasi ambang batas keheningan yang mengakhiri giliran ketika dilewati; default server 800, dengan 400 direkomendasikan untuk skenario percakapan dan obrolan yang memerlukan segmentasi cepat) dan threshold (sensitivitas deteksi VAD; default server 0.2). Qwen3-ASR-Flash-Realtime juga mendukung Mode Manual, yang menonaktifkan VAD dan menggunakan commit sisi klien untuk segmentasi. Untuk detailnya, lihat Mode interaksi Qwen3-ASR-Flash-Realtime.
Nama parameter bervariasi berdasarkan protokol: konsep yang sama disebut max_sentence_silence di Qwen-Audio-3.0-ASR-Flash-Streaming / Fun-ASR-Realtime / Paraformer, dan silence_duration_ms di Qwen3-ASR-Flash-Realtime. Untuk definisi field lengkap, lihat Referensi API.

Tingkatkan akurasi dengan hotword

Gunakan hotword untuk meningkatkan akurasi pengenalan istilah tertentu, seperti nama merek, nama pribadi, dan terminologi khusus. Untuk konfigurasi dan penggunaan hotword terperinci, lihat Tingkatkan akurasi pengenalan.

Tingkatkan akurasi dengan peningkatan konteks

Peningkatan konteks meneruskan riwayat percakapan atau terminologi domain ke model ASR untuk secara signifikan meningkatkan akurasi transkripsi istilah khusus. Untuk penggunaan terperinci dan contoh hasil, lihat Peningkatan konteks.

Dapatkan timestamp

Keluarga model Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime, dan Paraformer menghasilkan timestamp baik di tingkat kalimat maupun kata secara default, yang mendukung penyelarasan subtitel, penyorotan kata kunci, pembacaan karaoke, dan skenario serupa. Qwen3-ASR-Flash-Realtime (qwen3-asr-flash-realtime) saat ini tidak mengembalikan timestamp. Jika Anda memerlukan timestamp, gunakan Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime, atau Paraformer. Untuk transkripsi file, model transkripsi file rekaman Qwen ASR qwen3-asr-flash-filetrans mendukung timestamp tingkat kata. Untuk detailnya, lihat Pengenalan ucapan non-real-time. Timestamp dikembalikan dalam milidetik pada dua tingkat:
  • Tingkat kalimat: payload.output.sentence.begin_time dan payload.output.sentence.end_time menandai awal dan akhir kalimat lengkap dalam audio. Dalam hasil antara, end_time mungkin null dan diisi dengan nilai akhir ketika kalimat berakhir (sentence_end = true).
  • Tingkat kata: Array payload.output.sentence.words, di mana setiap elemen berisi begin_time, end_time, text (teks kata atau karakter), dan punctuation (tanda baca yang mengikuti kata, atau string kosong jika tidak ada).
Kutipan berikut menunjukkan struktur respons:
{
  "payload": {
    "output": {
      "sentence": {
        "begin_time": 170,
        "end_time": 920,
        "text": "OK, I got it",
        "sentence_end": true,
        "words": [
          { "begin_time": 170, "end_time": 295, "text": "OK", "punctuation": "," },
          { "begin_time": 295, "end_time": 503, "text": "I", "punctuation": "" },
          { "begin_time": 503, "end_time": 711, "text": "got", "punctuation": "" },
          { "begin_time": 711, "end_time": 920, "text": "it", "punctuation": "" }
        ]
      }
    }
  }
}
Nama field di atas mengikuti jalur JSON WebSocket. SDK berbeda mengekspos field ini dengan konvensi penamaan mereka sendiri (kunci kamus, properti objek, metode getter, dan sebagainya). Untuk pemetaan field lengkap, lihat referensi API untuk setiap SDK. Untuk definisi field lengkap, lihat Referensi API.

Pengenalan emosi

Qwen3-ASR-Flash-Realtime dan beberapa model Paraformer dapat menyertakan keadaan emosional pembicara dalam hasil transkripsi, tetapi keduanya berbeda dalam granularitas output dan cara fitur diaktifkan. Qwen3-ASR-Flash-Realtime (qwen3-asr-flash-realtime): Selalu aktif, tidak perlu konfigurasi. Emosi dikembalikan melalui field tingkat atas emotion baik dalam event conversation.item.input_audio_transcription.text maupun conversation.item.input_audio_transcription.completed. Nilainya adalah salah satu dari tujuh emosi detail halus: surprised, neutral, happy, sad, disgusted, angry, dan fearful.
{
  "type": "conversation.item.input_audio_transcription.text",
  "emotion": "neutral",
  "text": "The weather is nice today",
  "stash": ""
}
Paraformer (paraformer-realtime-8k-v2): Ini adalah satu-satunya model Paraformer yang mendukung pengenalan emosi. Hasilnya dikembalikan melalui payload.output.sentence.emo_tag dan payload.output.sentence.emo_confidence. Nilainya adalah salah satu dari tiga polaritas: positive (seperti senang atau puas), negative (seperti marah atau murung), dan neutral (tidak ada emosi jelas). Keyakinan berkisar dari 0,0 hingga 1,0. Pengenalan emosi dikembalikan hanya jika semua kondisi berikut terpenuhi:
  • Modelnya adalah paraformer-realtime-8k-v2.
  • Segmentasi semantik dimatikan: semantic_punctuation_enabled = false (false adalah default, jadi tidak perlu pengaturan khusus).
  • Hasil dikembalikan hanya dalam event akhir kalimat, di mana sentence_end = true.
Untuk berhenti mengembalikan field emosi, atur semantic_punctuation_enabled ke true. Ini mengaktifkan segmentasi semantik dan tidak lagi mengembalikan field emo_tag dan emo_confidence. Nama field di atas mengikuti jalur JSON WebSocket. SDK berbeda mengekspos field ini dengan konvensi penamaan mereka sendiri (kunci kamus, properti objek, metode getter, dan sebagainya). Untuk pemetaan field lengkap, lihat referensi API untuk setiap SDK. Untuk definisi field lengkap, batasan nilai, dan contoh, lihat Referensi API.

Penyaringan kata sensitif

Penyaringan kata sensitif mengganti atau menghapus kata sensitif dalam hasil pengenalan. Gunakan untuk inspeksi kualitas call-center, kepatuhan konten, tinjauan subtitel, dan skenario serupa. Model yang didukung: Hanya Qwen-Audio-3.0-ASR-Flash-Streaming dan Fun-ASR-Realtime. Batas: Anda dapat mengatur hingga 32 kata sensitif. Perilaku default: Ketika parameter special_word_filter tidak diteruskan, tidak ada kata sensitif yang disaring. Cara mengonfigurasi: special_word_filter adalah objek JSON dengan tiga subfield:
  • filter_with_signed.word_list: Array string yang mencantumkan kata sensitif untuk diganti dengan string karakter * sepanjang yang sama. Misalnya, dengan ["test"], "Help me test it" menjadi "Help me **** it".
  • filter_with_empty.word_list: Array string yang mencantumkan kata sensitif untuk dihapus sepenuhnya dari hasil. Misalnya, dengan ["start"], "Is the game about to start" menjadi "Is the game about to".
  • system_reserved_filter: Boolean yang default-nya false. Ini menentukan apakah penyaringan kata sensitif diaktifkan.
Contoh konfigurasi:
{
  "special_word_filter": {
    "filter_with_signed": {
      "word_list": ["test"]
    },
    "filter_with_empty": {
      "word_list": ["start", "occur"]
    },
    "system_reserved_filter": true
  }
}
SDK berbeda mengekspos parameter ini dengan konvensi penamaan mereka sendiri (kunci kamus, properti objek, metode, dan sebagainya). Untuk pemetaan field lengkap, lihat referensi API.

Panggil protokol WebSocket mentah

Contoh berikut menunjukkan cara menghubungkan langsung ke server melalui protokol WebSocket mentah, untuk skenario yang tidak menggunakan SDK DashScope. Setiap contoh adalah implementasi minimal yang dapat dijalankan. Untuk protokol WebSocket, lihat referensi API masing-masing model.
  • Qwen-Audio-3.0-ASR-Flash-Streaming/ Fun-ASR-Realtime
  • Qwen3-ASR-Flash-Realtime
  • Paraformer
  • Python
  • Java
  • Node.js
  • C#
  • PHP
  • Go
Sebelum menjalankan contoh, instal dependensi dengan perintah berikut:
pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
Jangan beri nama file contoh websocket.py. Nama ini bertentangan dengan pustaka websocket dan menyebabkan error berikut: AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?.
# pip install websocket-client
import os
import json
import time
import uuid
import threading
import websocket

# Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API Anda: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Alibaba Cloud Model Studio Anda: api_key = "sk-xxx"
api_key = os.environ.get('DASHSCOPE_API_KEY')
# Berikut ini adalah konfigurasi untuk wilayah Singapura. Saat memanggil, ganti "{WorkspaceId}" dengan ID ruang kerja aktual Anda. Konfigurasi berbeda berdasarkan wilayah.
url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'  # Alamat server WebSocket
audio_file = '{YOUR_AUDIO_FILE}'  # Ganti dengan path ke file audio Anda

# Hasilkan ID acak 32 karakter
TASK_ID = uuid.uuid4().hex[:32]

task_started = False  # Bendera yang menunjukkan apakah tugas telah dimulai

# Kirim instruksi run-task
def send_run_task(ws):
    run_task_message = {
        'header': {
            'action': 'run-task',
            'task_id': TASK_ID,
            'streaming': 'duplex'
        },
        'payload': {
            'task_group': 'audio',
            'task': 'asr',
            'function': 'recognition',
            'model': 'qwen-audio-3.0-asr-flash-streaming',
            'parameters': {
                'sample_rate': 16000,
                'format': 'wav'
            },
            'input': {}
        }
    }
    ws.send(json.dumps(run_task_message))

# Kirim instruksi finish-task
def send_finish_task(ws):
    finish_task_message = {
        'header': {
            'action': 'finish-task',
            'task_id': TASK_ID,
            'streaming': 'duplex'
        },
        'payload': {
            'input': {}
        }
    }
    ws.send(json.dumps(finish_task_message))

# Kirim aliran audio (kirim satu chunk biner setiap 100ms)
def send_audio_stream(ws):
    chunk_size = 3200  # 100ms @ 16kHz 16bit mono
    try:
        with open(audio_file, 'rb') as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                ws.send(chunk, opcode=websocket.ABNF.OPCODE_BINARY)
                time.sleep(0.1)
        print('Audio stream ended')
        send_finish_task(ws)
    except Exception as e:
        print('Error reading audio file:', e)
        ws.close()

# Kirim instruksi run-task saat koneksi terbuka
def on_open(ws):
    print('Connected to server')
    send_run_task(ws)

# Tangani pesan yang diterima
def on_message(ws, data):
    global task_started
    message = json.loads(data)
    event = message['header']['event']
    if event == 'task-started':
        print('Task started')
        task_started = True
        threading.Thread(target=send_audio_stream, args=(ws,), daemon=True).start()
    elif event == 'result-generated':
        print('Recognition result:', message['payload']['output']['sentence']['text'])
        if message['payload'].get('usage'):
            print('Task billing duration (seconds):', message['payload']['usage']['duration'])
    elif event == 'task-finished':
        print('Task finished')
        ws.close()
    elif event == 'task-failed':
        print('Task failed:', message['header'].get('error_message'))
        ws.close()
    else:
        print('Unknown event:', event)

# Tutup koneksi jika event task-started tidak diterima
def on_close(ws, close_status_code, close_msg):
    if not task_started:
        print('Task not started, closing connection')

# Penanganan error
def on_error(ws, error):
    print('WebSocket error:', error)

if __name__ == '__main__':
    ws = websocket.WebSocketApp(
        url,
        header={'Authorization': f'bearer {api_key}'},
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )
    ws.run_forever()

Terapkan di produksi

Gunakan kembali koneksi (WebSocket)

Koneksi WebSocket untuk Qwen-Audio-3.0-ASR-Flash-Streaming/Fun-ASR-Realtime dan Paraformer mendukung penggunaan kembali: setelah satu tugas pengenalan selesai, Anda dapat memulai tugas berikutnya tanpa membuat koneksi baru. Alur penggunaan kembali: Klien mengirim finish-task. Setelah server mengembalikan task-finished, klien dapat mengirim run-task lagi untuk memulai tugas baru.
  1. Tunggu server mengembalikan event task-finished sebelum memulai tugas baru.
  2. Tugas berbeda melalui koneksi yang digunakan kembali harus menggunakan nilai task_id yang berbeda.
  3. Saat tugas gagal, server mengembalikan event error dan menutup koneksi. Koneksi tersebut tidak dapat digunakan kembali.
  4. Jika tidak ada tugas baru yang dimulai dalam waktu 60 detik setelah tugas berakhir, koneksi akan ditutup secara otomatis.
Qwen3-ASR-Flash-Realtime menggunakan model sesi dan tidak mendukung penggunaan kembali koneksi. Tutup koneksi setelah setiap sesi berakhir. Untuk event masing-masing model, lihat referensi API yang sesuai.

Praktik terbaik konkurensi tinggi

SDK DashScope mencakup mekanisme pooling bawaan yang menggunakan kembali koneksi WebSocket dan objek pengenalan, yang menghindari overhead pembuatan dan penghancuran yang sering.
Saat ini, hanya SDK Java Paraformer yang mendukung fitur ini.

Prasyarat

SDK Java menggabungkan pool koneksi bawaan dengan pool objek kustom untuk mencapai kinerja optimal:
  • Pool koneksi: Pool koneksi OkHttp3 yang terintegrasi dalam SDK mengelola dan menggunakan kembali koneksi WebSocket dasar, yang mengurangi overhead handshake jaringan. Fitur ini diaktifkan secara default.
  • Pool objek: Dibangun di atas commons-pool2, pool objek mempertahankan serangkaian objek Recognition yang koneksi-nya sudah dibuat. Meminjam objek dari pool menghilangkan latensi pengaturan koneksi dan secara signifikan mengurangi latensi paket pertama.

Langkah implementasi

  1. Tambahkan dependensi Tambahkan dashscope-sdk-java dan commons-pool2 ke file konfigurasi dependensi Anda, berdasarkan alat build proyek Anda. Contoh berikut menunjukkan konfigurasi untuk Maven dan Gradle:
    • Maven
    • Gradle
    1. Buka file pom.xml proyek Maven Anda.
    2. Tambahkan dependensi berikut di dalam tag <dependencies>.
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>dashscope-sdk-java</artifactId>
        <!-- Ganti 'the-latest-version' dengan versi 2.16.9 atau lebih baru. Anda dapat mencari nomor versi di: https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
        <version>the-latest-version</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <!-- Ganti 'the-latest-version' dengan versi terbaru. Anda dapat mencari nomor versi di: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <version>the-latest-version</version>
    </dependency>
    
    1. Simpan file pom.xml.
    2. Jalankan perintah Maven (seperti mvn clean install atau mvn compile) untuk memperbarui dependensi proyek.
  2. Konfigurasikan pool koneksi Konfigurasikan parameter utama pool koneksi melalui variabel lingkungan:

    Variabel lingkungan

    Deskripsi

    DASHSCOPE_CONNECTION_POOL_SIZE

    Ukuran pool koneksi.

    Nilai yang direkomendasikan: minimal dua kali konkurensi puncak.

    Nilai default: 32.

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS

    Jumlah maksimum permintaan asinkron.

    Nilai yang direkomendasikan: sama dengan DASHSCOPE_CONNECTION_POOL_SIZE.

    Nilai default: 32.

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST

    Jumlah maksimum permintaan asinkron per host.

    Nilai yang direkomendasikan: sama dengan DASHSCOPE_CONNECTION_POOL_SIZE.

    Nilai default: 32.

  3. Konfigurasikan pool objek Konfigurasikan ukuran pool objek melalui variabel lingkungan:

    Variabel lingkungan

    Deskripsi

    RECOGNITION_OBJECTPOOL_SIZE

    Ukuran pool objek.

    Nilai yang direkomendasikan: 1,5 hingga 2 kali konkurensi puncak.

    Nilai default: 500.

    • Ukuran pool objek (RECOGNITION_OBJECTPOOL_SIZE) harus kurang dari atau sama dengan ukuran pool koneksi (DASHSCOPE_CONNECTION_POOL_SIZE). Jika tidak, saat pool objek meminta objek dan pool koneksi penuh, thread pemanggil akan diblokir.
    • Ukuran pool objek tidak boleh melebihi batas permintaan per detik (QPS) akun Anda.
    Buat pool objek dengan kode berikut:
class RecognitionObjectPool {
    // ... Untuk contoh lengkap, lihat kode lengkap.
    public static GenericObjectPool<Recognition> getInstance() {
        lock.lock();
        if (recognitionGenericObjectPool == null) {
            int objectPoolSize = getObjectivePoolSize();
            RecognitionObjectFactory recognitionObjectFactory =
                    new RecognitionObjectFactory();
            GenericObjectPoolConfig<Recognition> config =
                    new GenericObjectPoolConfig<>();
            config.setMaxTotal(objectPoolSize);
            config.setMaxIdle(objectPoolSize);
            config.setMinIdle(objectPoolSize);
            recognitionGenericObjectPool =
                    new GenericObjectPool<>(recognitionObjectFactory, config);
        }
        lock.unlock();
        return recognitionGenericObjectPool;
    }
}
  1. Pinjam objek Recognition dari pool objek Saat jumlah objek yang belum dikembalikan melebihi batas pool objek, sistem membuat objek Recognition tambahan. Objek baru ini harus membuat koneksi WebSocket baru dan tidak dapat digunakan kembali.
recognizer = RecognitionObjectPool.getInstance().borrowObject();
  1. Lakukan pengenalan ucapan Panggil metode call atau streamCall objek Recognition untuk melakukan pengenalan ucapan.
  2. Kembalikan objek Recognition Setelah tugas pengenalan ucapan selesai, kembalikan objek Recognition agar dapat digunakan kembali. Jangan mengembalikan objek dengan tugas yang belum selesai atau gagal.
RecognitionObjectPool.getInstance().returnObject(recognizer);

Kode lengkap

package org.alibaba.bailian.example.examples;

import com.alibaba.dashscope.audio.asr.recognition.Recognition;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionParam;
import com.alibaba.dashscope.audio.asr.recognition.RecognitionResult;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.ApiKey;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import com.alibaba.dashscope.utils.Constants;

public class Main {
    public static void checkoutEnv(String envName, int defaultSize) {
        if (System.getenv(envName) != null) {
            System.out.println("[ENV CHECK]: " + envName + " "
                    + System.getenv(envName));
        } else {
            System.out.println("[ENV CHECK]: " + envName
                    + " Using Default which is " + defaultSize);
        }
    }

    public static void main(String[] args)
            throws NoApiKeyException, InterruptedException {
        // Berikut adalah konfigurasi untuk wilayah China (Beijing). Saat memanggil, ganti "{WorkspaceId}" dengan ID ruang kerja Anda yang sebenarnya. Konfigurasi berbeda tiap wilayah.
        Constants.baseHttpApiUrl = "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1";
        checkoutEnv("DASHSCOPE_CONNECTION_POOL_SIZE", 32);
        checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS", 32);
        checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST", 32);
        checkoutEnv(RecognitionObjectPool.RECOGNITION_OBJECTPOOL_SIZE_ENV,
                RecognitionObjectPool.DEFAULT_OBJECT_POOL_SIZE);

        int threadNums = 3;
        String currentDir = System.getProperty("user.dir");
        Path[] filePaths = {
                Paths.get(currentDir, "{YOUR_AUDIO_FILE}"),
                Paths.get(currentDir, "{YOUR_AUDIO_FILE}"),
                Paths.get(currentDir, "{YOUR_AUDIO_FILE}"),
        };
        ExecutorService executorService = Executors.newFixedThreadPool(threadNums);
        for (int i = 0; i < threadNums; i++) {
            executorService.submit(new RealtimeRecognizeTask(filePaths));
        }
        executorService.shutdown();
        executorService.awaitTermination(10, TimeUnit.MINUTES);
        System.exit(0);
    }
}

class RecognitionObjectFactory extends BasePooledObjectFactory<Recognition> {
    public RecognitionObjectFactory() {
        super();
    }

    @Override
    public Recognition create() throws Exception {
        return new Recognition();
    }

    @Override
    public PooledObject<Recognition> wrap(Recognition obj) {
        return new DefaultPooledObject<>(obj);
    }
}

class RecognitionObjectPool {
    public static GenericObjectPool<Recognition> recognitionGenericObjectPool;
    public static String RECOGNITION_OBJECTPOOL_SIZE_ENV =
            "RECOGNITION_OBJECTPOOL_SIZE";
    public static int DEFAULT_OBJECT_POOL_SIZE = 500;
    private static Lock lock = new java.util.concurrent.locks.ReentrantLock();

    public static int getObjectivePoolSize() {
        try {
            Integer n = Integer.parseInt(
                    System.getenv(RECOGNITION_OBJECTPOOL_SIZE_ENV));
            return n;
        } catch (NumberFormatException e) {
            return DEFAULT_OBJECT_POOL_SIZE;
        }
    }

    public static GenericObjectPool<Recognition> getInstance() {
        lock.lock();
        if (recognitionGenericObjectPool == null) {
            int objectPoolSize = getObjectivePoolSize();
            System.out.println("RECOGNITION_OBJECTPOOL_SIZE: "
                    + objectPoolSize);
            RecognitionObjectFactory recognitionObjectFactory =
                    new RecognitionObjectFactory();
            GenericObjectPoolConfig<Recognition> config =
                    new GenericObjectPoolConfig<>();
            config.setMaxTotal(objectPoolSize);
            config.setMaxIdle(objectPoolSize);
            config.setMinIdle(objectPoolSize);
            recognitionGenericObjectPool =
                    new GenericObjectPool<>(recognitionObjectFactory, config);
        }
        lock.unlock();
        return recognitionGenericObjectPool;
    }
}

class RealtimeRecognizeTask implements Runnable {
    private static final Object lock = new Object();
    private Path[] filePaths;

    public RealtimeRecognizeTask(Path[] filePaths) {
        this.filePaths = filePaths;
    }

    private static String getDashScopeApiKey() throws NoApiKeyException {
        String dashScopeApiKey = null;
        try {
            ApiKey apiKey = new ApiKey();
            dashScopeApiKey = ApiKey.getApiKey(null);
        } catch (NoApiKeyException e) {
            System.out.println("No API key found in environment.");
        }
        if (dashScopeApiKey == null) {
            dashScopeApiKey = "your-dashscope-apikey";
        }
        return dashScopeApiKey;
    }

    public void runCallback() {
        for (Path filePath : filePaths) {
            RecognitionParam param = null;
            try {
                param = RecognitionParam.builder()
                        .model("paraformer-realtime-v2")
                        .format("pcm")
                        .sampleRate(16000)
                        .apiKey(getDashScopeApiKey())
                        .build();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

            Recognition recognizer = null;
            final boolean[] hasError = {false};
            try {
                recognizer = RecognitionObjectPool.getInstance().borrowObject();
                String threadName = Thread.currentThread().getName();

                ResultCallback<RecognitionResult> callback =
                        new ResultCallback<RecognitionResult>() {
                            @Override
                            public void onEvent(RecognitionResult message) {
                                synchronized (lock) {
                                    if (message.isSentenceEnd()) {
                                        System.out.println("[process " + threadName
                                                + "] Fix:" + message.getSentence().getText());
                                    } else {
                                        System.out.println("[process " + threadName
                                                + "] Result: " + message.getSentence().getText());
                                    }
                                }
                            }

                            @Override
                            public void onComplete() {
                                System.out.println("[" + threadName
                                        + "] Recognition complete");
                            }

                            @Override
                            public void onError(Exception e) {
                                System.out.println("[" + threadName
                                        + "] RecognitionCallback error: " + e.getMessage());
                                hasError[0] = true;
                            }
                        };
                System.out.println("[" + threadName
                        + "] Input file_path is: " + filePath);
                FileInputStream fis = null;
                try {
                    fis = new FileInputStream(filePath.toFile());
                } catch (Exception e) {
                    System.out.println("Error when loading file: " + filePath);
                    e.printStackTrace();
                }
                recognizer.call(param, callback);

                // ukuran chunk diatur ke 100 ms untuk laju sampel 16KHz
                byte[] buffer = new byte[3200];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    ByteBuffer byteBuffer;
                    if (bytesRead < buffer.length) {
                        byteBuffer = ByteBuffer.wrap(buffer, 0, bytesRead);
                    } else {
                        byteBuffer = ByteBuffer.wrap(buffer);
                    }
                    recognizer.sendAudioFrame(byteBuffer);
                    Thread.sleep(100);
                    buffer = new byte[3200];
                }
                System.out.println("[" + threadName + "] send audio done");
                recognizer.stop();
                System.out.println("[" + threadName + "] asr task finished");
            } catch (Exception e) {
                e.printStackTrace();
                hasError[0] = true;
            }
            if (recognizer != null) {
                try {
                    if (hasError[0] == true) {
                        recognizer.getDuplexApi().close(1000, "bye");
                        RecognitionObjectPool.getInstance()
                                .invalidateObject(recognizer);
                    } else {
                        RecognitionObjectPool.getInstance()
                                .returnObject(recognizer);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void run() {
        runCallback();
    }
}

Konfigurasi yang direkomendasikan

Konfigurasi berikut didasarkan pada hasil pengujian dari menjalankan hanya layanan pengenalan ucapan real-time Paraformer pada server Alibaba Cloud dengan spesifikasi yang ditentukan. Konkurensi mesin tunggal adalah jumlah tugas pengenalan ucapan real-time Paraformer yang berjalan secara bersamaan (yaitu, jumlah thread pekerja).

Spesifikasi mesin (Alibaba Cloud)

Konkurensi maksimum mesin tunggal

Ukuran pool objek

Ukuran pool koneksi

4 vCPU, 8 GiB

100

500

2000

8 vCPU, 16 GiB

200

500

2000

16 vCPU, 32 GiB

400

500

2000

Manajemen sumber daya dan penanganan error

  • Tugas berhasil: Panggil GenericObjectPool.returnObject() untuk mengembalikan objek Recognition ke pool untuk digunakan kembali.
    Jangan mengembalikan objek Recognition dengan tugas yang belum selesai atau gagal.
  • Tugas gagal: Saat SDK atau logika bisnis Anda melemparkan pengecualian yang mengganggu tugas, lakukan dua tindakan berikut:
    1. Tutup secara aktif koneksi WebSocket dasar.
    2. Invalidasi objek dalam pool objek untuk mencegahnya digunakan kembali.
// Tutup koneksi.
recognizer.getDuplexApi().close(1000, "bye");
// Invalidasi recognizer yang gagal dalam pool objek.
RecognitionObjectPool.getInstance().invalidateObject(recognizer);
  • Saat layanan mengembalikan error TaskFailed, tidak diperlukan penanganan tambahan.

Pemanasan dan pengukuran latensi

Saat mengevaluasi kinerja seperti latensi pemanggilan konkuren untuk SDK Java DashScope, kami menyarankan Anda menjalankan pemanasan yang cukup sebelum pengujian formal.
Mekanisme penggunaan kembali koneksi
SDK Java DashScope mengelola dan menggunakan kembali koneksi WebSocket melalui pool koneksi singleton global. Mekanisme ini bekerja sebagai berikut:
  • Pembuatan sesuai permintaan: SDK tidak membuat koneksi WebSocket sebelumnya saat startup layanan. Sebaliknya, SDK membuat koneksi sesuai permintaan pada pemanggilan pertama.
  • Penggunaan kembali berbatas waktu: Setelah permintaan selesai, koneksi tetap berada di pool hingga 60 detik untuk digunakan kembali.
    • Jika permintaan baru tiba dalam waktu 60 detik, SDK menggunakan kembali koneksi yang ada dan menghindari overhead handshake berulang.
    • Jika koneksi tetap tidak aktif selama lebih dari 60 detik, SDK menutupnya secara otomatis untuk melepaskan sumber daya.
Mengapa pemanasan penting
Dalam skenario berikut, pool koneksi mungkin tidak memiliki koneksi aktif untuk digunakan kembali, sehingga permintaan harus membuat koneksi baru:
  • Aplikasi baru saja dimulai dan belum melakukan pemanggilan apa pun.
  • Layanan telah tidak aktif selama lebih dari 60 detik, sehingga koneksi dalam pool telah ditutup karena timeout.
Dalam skenario ini, permintaan pertama atau awal memicu proses koneksi WebSocket lengkap (termasuk handshake TCP, negosiasi TLS, dan peningkatan protokol). Latensi end-to-end-nya jauh lebih tinggi dibandingkan permintaan berikutnya yang menggunakan kembali koneksi.
Pendekatan yang direkomendasikan
Sebelum menjalankan pengujian beban formal atau mengukur latensi, ikuti langkah pemanasan berikut:
  1. Simulasikan tingkat konkurensi pengujian formal dengan mengirim sejumlah pemanggilan terlebih dahulu (misalnya, selama 1 hingga 2 menit) untuk sepenuhnya mengisi pool koneksi.
  2. Setelah Anda memastikan bahwa pool koneksi telah membuat dan mempertahankan koneksi aktif yang cukup, mulailah mengumpulkan data kinerja formal.

Tingkatkan akurasi pengenalan

  • Pilih model yang sesuai dengan laju sampel: Untuk audio telepon 8 kHz, gunakan model 8 kHz secara langsung. Ini menghindari kehilangan informasi yang disebabkan oleh upsampling ke 16 kHz.
  • Tingkatkan kualitas input audio: Gunakan mikrofon berkualitas tinggi dan rekam di lingkungan dengan rasio signal-to-noise tinggi dan tanpa gema. Di lapisan aplikasi, Anda dapat mengintegrasikan algoritma seperti pengurangan noise (misalnya, RNNoise) dan pembatalan gema akustik (AEC) untuk pra-pemrosesan.

Siapkan strategi toleransi kesalahan

  • Koneksi ulang sisi klien: Klien harus mengimplementasikan koneksi ulang otomatis untuk menangani fluktuasi jaringan. Berikut adalah implementasi referensi untuk SDK Python:
    1. Tangkap pengecualian: Implementasikan metode on_error dalam kelas Callback. SDK dashscope memanggil metode ini saat mengalami error jaringan atau masalah lain.
    2. Beri sinyal status: Saat on_error dipicu, atur sinyal koneksi ulang. Di Python, Anda dapat menggunakan threading.Event, flag sinyal aman thread.
    3. Loop koneksi ulang: Bungkus logika utama dalam loop for (misalnya, coba 3 kali). Saat sinyal koneksi ulang terdeteksi, putaran pengenalan saat ini dihentikan, sumber daya dibersihkan, dan setelah beberapa detik loop dijalankan lagi untuk membuat koneksi baru.
  • Atur heartbeat untuk menjaga koneksi tetap aktif: Untuk mempertahankan koneksi jangka panjang dengan server, atur parameter heartbeat ke true. Koneksi ke server kemudian tetap terbuka meskipun audio tidak mengandung suara untuk waktu yang lama.
  • Batas laju model: Saat memanggil API model, perhatikan aturan Pembatasan laju model.

Model dan wilayah yang didukung

  • Singapura
  • China (Beijing)
Untuk memanggil model berikut, gunakan Kunci API untuk wilayah Singapura:
  • Qwen-Audio-3.0-ASR-Flash-Streaming: qwen-audio-3.0-asr-flash-streaming
  • Fun-ASR-Realtime: fun-asr-realtime (versi stabil, saat ini setara dengan fun-asr-realtime-2025-11-07), fun-asr-realtime-2025-11-07 (versi snapshot)
  • Qwen3-ASR-Flash-Realtime: qwen3-asr-flash-realtime (versi stabil, saat ini setara dengan qwen3-asr-flash-realtime-2025-10-27), qwen3-asr-flash-realtime-2026-02-10 (versi snapshot terbaru), qwen3-asr-flash-realtime-2025-10-27 (versi snapshot)

Referensi API

FAQ

Format audio apa saja yang didukung oleh pengenalan ucapan real-time?

Model Qwen-Audio-3.0-ASR-Flash-Streaming, Fun-ASR-Realtime, dan Paraformer mendukung format pcm, wav, mp3, opus, speex, aac, dan amr. Untuk model Qwen3-ASR-Flash-Realtime, kami merekomendasikan format pcm atau opus. Format lain (seperti wav, aac, dan amr) diterima oleh lapisan validasi session.update, tetapi decoding sisi server mungkin gagal. Pastikan aliran audio menggunakan format yang direkomendasikan sebelum mengirimnya.

Apa perbedaan antara SDK dan API WebSocket, dan bagaimana cara memilih?

SDK DashScope menyembunyikan detail seperti manajemen koneksi WebSocket, autentikasi, dan koneksi ulang, yang menjadikannya pilihan tepat untuk integrasi cepat. Menghubungkan langsung ke API WebSocket memberikan kontrol lebih rinci dan cocok untuk bahasa pemrograman yang tidak didukung SDK atau skenario yang memerlukan manajemen koneksi kustom. Kami menyarankan Anda menggunakan SDK terlebih dahulu.

Bagaimana cara meningkatkan akurasi pengenalan untuk nama diri?

Gunakan hotword atau peningkatan konteks. Untuk metode konfigurasi dan catatan penggunaan terperinci, lihat Tingkatkan akurasi pengenalan.

Apa yang harus saya lakukan ketika koneksi sering terputus?

Implementasikan koneksi ulang sisi klien dan aktifkan parameter heartbeat (heartbeat=true) untuk mencegah koneksi terputus saat tidak ada audio untuk waktu yang lama. Untuk strategi toleransi kesalahan terperinci, lihat Terapkan di produksi.
Rencana Token (Team Edition)
Model playground
Statistik dan Pemantauan
Asset Center
Dukungan layanan
Pengenalan ucapan real-time - Qwen - Alibaba Cloud Model Studio