All Products
Search
Document Center

Alibaba Cloud Model Studio:API WebSocket Sintesis Suara CosyVoice

Last Updated:Mar 08, 2026

Topik ini menjelaskan cara mengakses layanan sintesis suara CosyVoice menggunakan koneksi WebSocket.

SDK DashScope hanya mendukung Java dan Python. Untuk membangun aplikasi sintesis suara CosyVoice dalam bahasa pemrograman lain, gunakan koneksi WebSocket untuk berkomunikasi dengan layanan tersebut.

Panduan pengguna: Untuk ikhtisar model dan saran pemilihan, lihat Sintesis suara real-time — CosyVoice.

WebSocket adalah protokol jaringan yang mendukung komunikasi full-duplex. Klien dan server membentuk koneksi persisten melalui satu proses jabat tangan, memungkinkan kedua pihak saling mendorong data secara aktif. Hal ini memberikan keunggulan signifikan dalam hal kinerja dan efisiensi real-time.

Untuk bahasa pemrograman umum, tersedia banyak pustaka dan contoh WebSocket siap pakai, seperti:

  • Go: gorilla/websocket

  • PHP: Ratchet

  • Node.js: ws

Pahami prinsip dasar dan detail teknis WebSocket sebelum memulai pengembangan.

Penting

Model CosyVoice hanya mendukung koneksi WebSocket—bukan API REST HTTP. Jika Anda memanggil layanan menggunakan permintaan HTTP (misalnya POST), layanan akan mengembalikan kesalahan InvalidParameter atau URL.

Persyaratan Awal

Anda telah memperoleh Kunci API.

Model dan harga

Lihat Sintesis suara real-time — CosyVoice.

Batasan panjang teks dan aturan format sintesis suara

Batasan panjang teks

Setiap instruksi continue-task harus berisi tidak lebih dari 20.000 karakter. Secara keseluruhan, jumlah total karakter yang dikirimkan melalui semua panggilan ke instruksi continue-task tidak boleh melebihi 200.000 karakter.

Aturan penghitungan karakter

  • Karakter Tionghoa (termasuk Tionghoa sederhana dan tradisional, Kanji Jepang, serta Hanja Korea) dihitung sebagai dua karakter. Semua karakter lainnya, seperti tanda baca, huruf, angka, serta Kana Jepang/Hangul Korea, dihitung sebagai satu karakter.

  • Tag SSML tidak dimasukkan dalam perhitungan panjang teks.

  • Contoh:

    • "你好" → 2 (karakter Tionghoa) + 2 (karakter Tionghoa) = 4 karakter

    • "中A文123" → 2 (karakter Tionghoa) + 1 (A) + 2 (karakter Tionghoa) + 1 (1) + 1 (2) + 1 (3) = 8 karakter

    • "中文。" → 2 (karakter Tionghoa) + 2 (karakter Tionghoa) + 1 (.) = 5 karakter

    • "中 文。" → 2 (karakter Tionghoa) + 1 (spasi) + 2 (karakter Tionghoa) + 1 (.) = 6 karakter

    • "<speak>你好</speak>" → 2 (karakter Tionghoa) + 2 (karakter Tionghoa) = 4 karakter

Format pengkodean

Gunakan pengkodean UTF-8.

Dukungan ekspresi matematika

Penguraian ekspresi matematika hanya tersedia untuk cosyvoice-v3.5-flash, cosyvoice-v3.5-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2. Fitur ini mendukung ekspresi matematika umum tingkat sekolah dasar dan menengah, termasuk operasi dasar, aljabar, dan geometri.

Catatan

Fitur ini hanya mendukung bahasa Tionghoa.

Lihat Konversi rumus LaTeX menjadi ucapan (hanya bahasa Tionghoa).

Dukungan bahasa markup SSML

Untuk menggunakan SSML, penuhi semua kondisi berikut:

  1. Dukungan model: Hanya cosyvoice-v3.5-flash, cosyvoice-v3.5-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2 yang mendukung SSML.

  2. Dukungan suara: Anda harus menggunakan suara yang mendukung SSML. Suara-suara yang didukung meliputi berikut ini:

    • Semua suara hasil kloning (suara kustom yang dibuat melalui API kloning suara).

    • Suara sistem yang ditandai sebagai mendukung SSML dalam daftar suara.

    Catatan

    Jika Anda menggunakan suara sistem yang tidak mendukung SSML (misalnya beberapa suara dasar), mengaktifkan parameter enable_ssml akan mengembalikan kesalahan “SSML text is not supported at the moment!”.

  3. Pengaturan parameter: Dalam instruksi run-task, atur parameter enable_ssml ke true.

Setelah memenuhi semua kondisi, kirimkan teks berformat SSML menggunakan instruksi continue-task. Untuk contoh lengkap, lihat Panduan Cepat Mulai.

Alur interaksi

image

Pesan yang dikirimkan klien ke server disebut instruksi. Server mengembalikan dua jenis pesan ke klien: event berformat JSON dan aliran audio biner.

Secara kronologis, alur interaksi antara klien dan server adalah sebagai berikut:

  1. Membentuk koneksi: Klien membentuk koneksi WebSocket dengan server.

  2. Memulai tugas: Klien mengirimkan instruksi run-task untuk memulai tugas.

  3. Menunggu konfirmasi: Klien menerima event task-started dari server, yang mengonfirmasi bahwa tugas berhasil dimulai. Klien kemudian dapat melanjutkan ke langkah-langkah berikutnya.

  4. Mengirimkan teks untuk disintesis:

    Klien mengirimkan satu atau lebih instruksi continue-task yang berisi teks untuk disintesis, secara berurutan. Setelah menerima kalimat lengkap, server mengembalikan event result-generated dan aliran audio. (Batasan panjang teks berlaku. Lihat deskripsi bidang text dalam instruksi continue-task.)

    Catatan

    Anda dapat mengirimkan beberapa instruksi continue-task, mengirimkan fragmen teks secara berurutan. Server secara otomatis membaginya menjadi kalimat:

    • Server menyintesis kalimat lengkap secara langsung, sehingga klien menerima audio segera.

    • Server menyimpan sementara kalimat yang belum lengkap dan baru menyintesisnya setelah menjadi lengkap. Tidak ada audio yang dikembalikan untuk kalimat yang belum lengkap.

    Ketika Anda mengirimkan instruksi finish-task, server memaksa penyintesisan seluruh konten yang tersimpan sementara.

  5. Menerima audio: Terima aliran audio melalui saluran binary.

  6. Menginformasikan server untuk mengakhiri tugas:

    Setelah mengirimkan seluruh teks, klien mengirimkan instruksi finish-task untuk menginformasikan server agar mengakhiri tugas. Lanjutkan menerima audio dari server. Jangan lewati langkah ini. Jika tidak, Anda mungkin kehilangan audio atau akhir dari aliran audio.

  7. Tugas berakhir:

    Klien menerima event task-finished dari server, yang menandai akhir tugas.

  8. Menutup koneksi: Klien menutup koneksi WebSocket.

Untuk meningkatkan penggunaan sumber daya, gunakan kembali satu koneksi WebSocket untuk beberapa tugas, bukan membuka koneksi baru untuk setiap tugas. Untuk informasi selengkapnya, lihat Overhead koneksi dan penggunaan kembali.

Penting

task_id harus tetap konsisten sepanjang proses: Untuk satu tugas sintesis, instruksi run-task, semua instruksi continue-task, dan finish-task harus menggunakan task_id yang sama.

Akibat kesalahan: Menggunakan task_id yang berbeda menyebabkan:

  • Server tidak dapat mengaitkan permintaan, sehingga pengiriman aliran audio menjadi tidak teratur.

  • Konten teks dialokasikan ke tugas yang salah, mengakibatkan ketidaksesuaian ucapan.

  • Status tugas menjadi abnormal, kemungkinan kehilangan event task-finished.

  • Penagihan gagal, dan statistik penggunaan menjadi tidak akurat.

Pendekatan yang benar:

  • Buat task_id unik (misalnya menggunakan UUID) saat mengirimkan run-task.

  • Simpan task_id tersebut dalam sebuah variabel.

  • Gunakan task_id yang sama untuk semua instruksi continue-task dan finish-task berikutnya.

  • Setelah tugas berakhir (setelah menerima task-finished), buat task_id baru untuk tugas baru apa pun.

Catatan implementasi klien

Saat mengimplementasikan klien WebSocket—terutama pada platform Flutter, web, atau seluler—tentukan tanggung jawab antara server dan klien secara jelas untuk memastikan tugas sintesis suara yang lengkap dan stabil.

Tanggung jawab server dan klien

Tanggung jawab server

Server menjamin pengembalian aliran audio lengkap secara berurutan. Anda tidak perlu khawatir tentang urutan atau kelengkapan data audio—server menghasilkan dan mendorong semua potongan audio sesuai urutan teks.

Tanggung jawab klien

Klien harus menangani tugas-tugas kritis berikut:

  1. Baca dan gabungkan semua potongan audio

    Server mengirimkan audio sebagai beberapa frame biner. Klien harus menerima semua potongan dan menggabungkannya untuk membentuk file audio akhir. Contoh kode:

    # Contoh Python: Gabungkan potongan audio
    with open("output.mp3", "ab") as f:  # Mode tambah
        f.write(audio_chunk)  # audio_chunk adalah setiap potongan audio biner yang diterima
    // Contoh JavaScript: Gabungkan potongan audio
    const audioChunks = [];
    ws.onmessage = (event) => {
      if (event.data instanceof Blob) {
        audioChunks.push(event.data);  // Kumpulkan semua potongan audio
      }
    };
    // Gabungkan audio setelah tugas selesai
    const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' });
  2. Pertahankan siklus hidup WebSocket secara penuh

    Selama seluruh tugas sintesis suara—mulai dari mengirimkan instruksi run-task hingga menerima event task-finishedjangan putuskan koneksi WebSocket terlalu dini. Kesalahan umum meliputi berikut ini:

    • Menutup koneksi sebelum semua potongan audio tiba, mengakibatkan audio tidak lengkap.

    • Lupa mengirimkan instruksi finish-task, sehingga teks yang tersimpan sementara tidak diproses.

    • Gagal menangani keepalive WebSocket secara tepat saat halaman beralih atau aplikasi berpindah ke latar belakang.

    Penting

    Aplikasi seluler (Flutter, iOS, Android) harus mengelola koneksi jaringan secara cermat saat masuk ke latar belakang. Pertahankan WebSocket dalam tugas latar belakang atau layanan latar depan, atau periksa status tugas dan hubungkan kembali saat kembali ke latar depan.

  3. Integritas teks dalam alur kerja ASR→LLM→TTS

    Dalam alur kerja ASR→LLM→TTS, pastikan teks yang diteruskan ke TTS lengkap—tidak terpotong di tengah aliran. Misalnya:

    • Tunggu LLM menghasilkan kalimat atau paragraf lengkap sebelum mengirimkan instruksi continue-task, bukan mengalirkannya karakter per karakter.

    • Jika sintesis aliran diperlukan (menghasilkan sambil memutar), kirimkan teks dalam potongan kalimat alami—misalnya setelah titik atau tanda tanya.

    • Setelah output LLM selesai, selalu kirimkan instruksi finish-task untuk menghindari kehilangan konten akhir.

Tips khusus platform

  • Flutter: Saat menggunakan paket web_socket_channel, tutup koneksi secara benar dalam metode dispose untuk mencegah kebocoran memori. Selain itu, tangani peristiwa siklus hidup aplikasi (seperti AppLifecycleState.paused) untuk transisi ke latar belakang.

  • Web (browser): Beberapa browser membatasi koneksi WebSocket. Gunakan kembali satu koneksi untuk beberapa tugas. Gunakan peristiwa beforeunload untuk menutup koneksi sebelum halaman keluar, menghindari koneksi yang usang.

  • Seluler (iOS/Android native): Sistem operasi dapat menjeda atau menghentikan koneksi jaringan saat aplikasi berpindah ke latar belakang. Gunakan tugas latar belakang atau layanan latar depan untuk mempertahankan WebSocket aktif, atau inisialisasi ulang tugas saat kembali ke latar depan.

URL

URL WebSocket bersifat tetap sebagai berikut:

Internasional

Dalam mode penyebaran internasional, titik akses dan penyimpanan data keduanya berlokasi di wilayah Singapura. Sumber daya komputasi inferensi model dijadwalkan secara dinamis secara global, kecuali untuk Tiongkok daratan.

URL WebSocket: wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference

Tiongkok daratan

Dalam mode penyebaran Tiongkok daratan, titik akses dan penyimpanan data keduanya berlokasi di wilayah Beijing. Sumber daya komputasi inferensi model dibatasi hanya untuk Tiongkok daratan.

URL WebSocket: wss://dashscope.aliyuncs.com/api-ws/v1/inference

Penting

Kesalahan umum dalam konfigurasi URL:

  • Kesalahan: Menggunakan URL yang dimulai dengan http:// atau https:// → Benar: Anda harus menggunakan protokol wss://.

  • Kesalahan: Menempatkan parameter Authorization dalam string kueri URL (misalnya ?Authorization=bearer <your_api_key>) → Benar: Atur parameter Authorization dalam header jabat tangan HTTP. Untuk informasi selengkapnya, lihat Header.

  • Kesalahan: Menambahkan nama model atau parameter jalur lain ke akhir URL → Benar: URL bersifat tetap. Anda dapat menentukan model menggunakan parameter payload.model dalam instruksi run-task.

Header

Tambahkan header berikut ke permintaan Anda:

Parameter

Jenis

Wajib

Deskripsi

Authorization

string

Ya

Token otentikasi dalam format Bearer <your_api_key>. Ganti <your_api_key> dengan Kunci API aktual Anda.

user-agent

string

Tidak

Identifikasi klien untuk membantu server melacak sumber.

X-DashScope-WorkSpace

string

Tidak

ID ruang kerja Alibaba Cloud Model Studio Anda.

X-DashScope-DataInspection

string

Tidak

Apakah akan mengaktifkan pemeriksaan kepatuhan data. Nilai bawaan adalah tidak diatur atau enable. Jangan aktifkan kecuali diperlukan.

Penting

Waktu otentikasi dan kesalahan umum

Validasi otentikasi terjadi selama jabat tangan WebSocket, bukan saat Anda mengirimkan instruksi run-task. Jika header Authorization tidak ada atau Kunci API tidak valid, server menolak jabat tangan dan mengembalikan kesalahan HTTP 401 atau 403. Pustaka klien biasanya melaporkan ini sebagai pengecualian WebSocketBadStatus.

Menangani kegagalan otentikasi

Jika koneksi WebSocket gagal, Anda dapat mengikuti langkah-langkah berikut:

  1. Periksa format Kunci API: Pastikan header Authorization menggunakan format bearer <your_api_key>, dengan spasi antara `bearer` dan Kunci API.

  2. Verifikasi kevalidan Kunci API: Di konsol Model Studio, pastikan Kunci API belum dihapus atau dinonaktifkan dan memiliki izin untuk memanggil model CosyVoice.

  3. Periksa pengaturan header: Pastikan header Authorization diatur dengan benar selama jabat tangan WebSocket. Metode pengaturan header bervariasi tergantung bahasa pemrograman:

    • Python (pustaka websockets): extra_headers={"Authorization": f"bearer {api_key}"}

    • JavaScript: API WebSocket bawaan browser tidak mendukung header kustom. Anda dapat menggunakan proksi sisi server atau pustaka lain, seperti ws.

    • Go (gorilla/websocket): header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))

  4. Uji konektivitas jaringan: Anda dapat menggunakan curl atau Postman untuk menguji apakah Kunci API berfungsi dengan API DashScope lain yang didukung HTTP.

Menggunakan WebSocket di lingkungan browser

Di lingkungan browser, seperti Vue3 atau React, API WebSocket bawaan browser memiliki pembatasan keamanan yang mencegah penggunaan header kustom. API bawaan new WebSocket(url) tidak mengizinkan header permintaan kustom (seperti Authorization) selama jabat tangan. Ini merupakan pembatasan keamanan browser. Akibatnya, Anda tidak dapat melakukan otentikasi langsung dengan Kunci API dalam kode frontend Anda.

Solusi: Gunakan proksi sisi server

  1. Bentuk koneksi WebSocket dari layanan backend Anda (seperti Node.js, Java, atau Python) ke layanan CosyVoice. Layanan backend Anda dapat mengatur header Authorization dengan benar.

  2. Hubungkan frontend Anda ke layanan backend Anda menggunakan WebSocket. Layanan backend kemudian bertindak sebagai proksi untuk meneruskan pesan ke CosyVoice.

  3. Manfaat: Pendekatan ini menjaga keamanan Kunci API di sisi backend. Anda juga dapat menambahkan logika bisnis, seperti otentikasi, pencatatan, dan pembatasan laju, di sisi backend.

Penting

Jangan menyematkan Kunci API Anda secara langsung dalam kode frontend atau mengirimkannya langsung dari browser. Paparan Kunci API dapat menyebabkan pelanggaran akun, tagihan tinggi, atau pelanggaran data.

Contoh kode:

Untuk bahasa pemrograman lain, Anda dapat menyesuaikan logika dalam contoh ini atau menggunakan alat AI untuk mengonversinya.

Instruksi (klien → server)

Instruksi adalah pesan berformat JSON yang dikirimkan klien ke server. Instruksi menggunakan format Frame Teks untuk mengontrol awal, akhir, dan batas tugas.

Kirimkan instruksi secara ketat berurutan. Jika tidak, tugas dapat gagal:

  1. Kirimkan instruksi run-task

  2. Kirimkan instruksi continue-task

    • Mengirimkan teks untuk disintesis.

    • Kirimkan hanya setelah menerima event task-started dari server.

  3. Kirimkan instruksi finish-task

1. instruksi run-task: Memulai tugas

Instruksi ini memulai tugas sintesis suara. Anda dapat mengonfigurasi suara, laju sampel, dan parameter permintaan lainnya di sini.

Penting
  • Kapan mengirimkan: Setelah koneksi WebSocket terbentuk.

  • Jangan mengirimkan teks untuk disintesis: Mengirimkan teks dalam instruksi run-task mempersulit pemecahan masalah. Hindari hal ini. Sebagai gantinya, kirimkan teks menggunakan instruksi continue-task.

  • Bidang input harus ada: Payload harus mencakup bidang input (sebagai {}). Menghilangkannya akan mengembalikan pesan kesalahan 'task can not be null'.

Contoh:

{
    "header": {
        "action": "run-task",
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx", // UUID acak
        "streaming": "duplex"
    },
    "payload": {
        "task_group": "audio",
        "task": "tts",
        "function": "SpeechSynthesizer",
        "model": "cosyvoice-v3-flash",
        "parameters": {
            "text_type": "PlainText",
            "voice": "longanyang",            // Suara
            "format": "mp3",		        // Format audio
            "sample_rate": 22050,	        // Laju sampel
            "volume": 50,			// Volume
            "rate": 1,				// Laju ucapan
            "pitch": 1				// Nada
        },
        "input": {// input harus ada, atau akan mengembalikan kesalahan
        }
    }
}

header Referensi parameter:

Parameter

Type

Required

Description

header.action

string

Yes

Jenis instruksi.

Untuk instruksi ini, nilainya selalu "run-task".

header.task_id

string

Yes

ID task untuk permintaan ini.

Sebuah pengidentifikasi unik universal (UUID) sepanjang 32 karakter, terdiri dari 32 huruf dan angka yang dihasilkan secara acak. UUID tersebut dapat menyertakan tanda hubung (misalnya, "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx") atau tanpa tanda hubung (misalnya, "2bf83b9abaeb4fda8d9axxxxxxxxxxxx"). Sebagian besar bahasa pemrograman menyediakan API bawaan untuk menghasilkan UUID—contohnya dalam Python:

import uuid

def generateTaskId(self):
    # Hasilkan UUID acak
    return uuid.uuid4().hex

Untuk instruksi continue-task dan finish-task berikutnya, gunakan task_id yang sama seperti pada instruksi run-task.

header.streaming

string

Yes

String tetap: "duplex"

muatan referensi parameter:

Parameter

Type

Required

Description

payload.task_group

string

Yes

String tetap: "audio".

payload.task

string

Yes

String tetap: "tts".

payload.function

string

Yes

String tetap: "SpeechSynthesizer".

payload.model

string

Yes

model sintesis suara.

Versi model yang berbeda memerlukan versi voice yang sesuai:

  • cosyvoice-v3.5-flash/cosyvoice-v3.5-plus: Tidak tersedia voice sistem. Hanya voice kustom dari voice design atau voice cloning yang didukung.

  • cosyvoice-v3-flash/cosyvoice-v3-plus: Gunakan voice seperti longanyang.

  • cosyvoice-v2: Gunakan voice seperti longxiaochun_v2.

  • Untuk daftar lengkap voice, lihat Voice list.

payload.input

object

Yes

Dalam instruksi run-task, bidang input harus ada (tidak boleh dihilangkan), tetapi jangan mengirimkan teks untuk disintesis di sini (gunakan objek kosong {}). Kirimkan teks menggunakan instruksi continue-task selanjutnya agar lebih mudah troubleshooting dan mendukung streaming synthesis.

Format input adalah:

"input": {}
Penting

Kesalahan umum: Menghilangkan bidang input atau menambahkan bidang yang tidak diharapkan (seperti mode atau content) menyebabkan server menolak permintaan dengan pesan “InvalidParameter: task can not be null” atau menutup koneksi (kode WebSocket 1007).

payload.parameters

text_type

string

Yes

String tetap: "PlainText".

voice

string

Yes

Voice yang digunakan untuk sintesis suara.

Voice sistem dan voice hasil kloning didukung:

  • Voice sistem: Untuk informasi lebih lanjut, lihat Voice list.

  • Voice hasil kloning: Disesuaikan menggunakan fitur Voice cloning. Saat menggunakan voice hasil kloning, pastikan akun yang sama digunakan untuk voice cloning dan sintesis suara.

    Saat menggunakan voice hasil kloning, nilai parameter model dalam permintaan ini harus identik dengan versi model yang digunakan saat membuat voice tersebut (parameter target_model).

  • Voice hasil desain: Disesuaikan menggunakan fitur Voice design. Saat menggunakan voice hasil desain, pastikan akun yang sama digunakan untuk voice design dan sintesis suara.

    Saat menggunakan voice hasil desain, nilai parameter model dalam permintaan ini harus identik dengan versi model yang digunakan saat membuat voice tersebut (parameter target_model).

format

string

No

Format pengkodean audio.

Mendukung pcm, wav, mp3 (default), dan opus.

Saat format adalah opus, sesuaikan bitrate menggunakan parameter bit_rate.

sample_rate

integer

No

Laju sampel audio (dalam Hz).

Default: 22050.

Nilai yang valid: 8000, 16000, 22050, 24000, 44100, 48000.

Catatan

Laju sampel default merepresentasikan laju optimal untuk voice yang dipilih. Output menggunakan laju ini secara default, tetapi downsampling dan upsampling didukung.

volume

integer

No

Volume.

Nilai default: 50.

Nilai yang valid: [0, 100]. Nilai 50 merepresentasikan volume default. Volume berskala secara linear terhadap nilai ini. Nilai 0 berarti senyap, dan 100 adalah volume maksimum.

rate

float

No

Laju bicara.

Nilai default: 1.0.

Nilai yang valid: [0.5, 2.0]. Nilai 1.0 adalah laju bicara standar. Nilai kurang dari 1.0 memperlambat bicara, dan nilai lebih dari 1.0 mempercepatnya.

pitch

float

No

Pitch. Nilai ini berfungsi sebagai pengali untuk penyesuaian pitch, tetapi hubungannya dengan perubahan pitch yang dirasakan tidak sepenuhnya linear atau logaritmik. Kami menyarankan melakukan pengujian untuk menemukan nilai yang sesuai.

Nilai default: 1.0.

Nilai yang valid: [0.5, 2.0]. Nilai 1.0 adalah pitch alami dari voice. Nilai lebih dari 1.0 meningkatkan pitch, dan nilai kurang dari 1.0 menurunkannya.

enable_ssml

boolean

No

Aktifkan SSML.

Jika diatur ke true, Anda hanya dapat mengirimkan teks satu kali (hanya satu instruksi continue-task yang diperbolehkan).

bit_rate

int

No

Bitrate audio dalam kbps. Jika format audio adalah Opus, sesuaikan bitrate menggunakan parameter bit_rate.

Nilai default: 32.

Nilai yang valid: [6, 510].

word_timestamp_enabled

boolean

No

Menentukan apakah akan mengaktifkan timestamp tingkat kata.

Nilai default: false.

  • true

  • false

Fitur ini hanya tersedia untuk voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai sebagai didukung dalam voice list.

Saat word_timestamp_enabled diaktifkan, informasi timestamp muncul dalam event result-generated. Contoh:

{
  "header": {
    "task_id": "3f39be22-efbd-4844-91d5-xxxxxxxxxxxx",
    "event": "result-generated",
    "attributes": {}
  },
  "payload": {
    "output": {
      "sentence": {
        "index": 0,
        "words": [
          {
            "text": "bed",
            "begin_index": 0,
            "end_index": 1,
            "begin_time": 280,
            "end_time": 640
          }
        ]
      },
      "type": "sentence-begin",
      "original_text": "Before my bed, moonlight shines bright,"
    }
  }
}

seed

int

No

Seed acak yang digunakan selama proses generasi. Seed yang berbeda menghasilkan output sintesis yang berbeda. Jika model, teks, voice, dan parameter lainnya identik, penggunaan seed yang sama akan mereproduksi output yang sama.

Nilai default: 0.

Nilai yang valid: [0, 65535].

language_hints

array[string]

No

Menentukan target language untuk sintesis suara guna meningkatkan kualitas sintesis.

Gunakan parameter ini ketika pelafalan angka, singkatan, atau simbol, atau kualitas sintesis untuk bahasa yang kurang umum tidak sesuai harapan. Contohnya:

  • Angka tidak dibaca seperti yang diharapkan. Misalnya, "hello, this is 110" dibaca sebagai "hello, this is one one zero" alih-alih "hello, this is yāo yāo líng".

  • Simbol '@' salah dilafalkan sebagai 'ai te' alih-alih 'at'.

  • Kualitas sintesis untuk bahasa yang kurang umum buruk dan terdengar tidak alami.

Nilai yang valid:

  • zh: Chinese

  • en: English

  • fr: French

  • de: German

  • ja: Japanese

  • ko: Korean

  • ru: Russian

  • pt: Portuguese

  • th: Thai

  • id: Indonesian

  • vi: Vietnamese

Catatan: Parameter ini berupa array, tetapi versi saat ini hanya memproses elemen pertama. Oleh karena itu, kami menyarankan hanya mengirimkan satu nilai.

Penting

Parameter ini menentukan target language untuk sintesis suara. Pengaturan ini independen dari bahasa audio sampel yang digunakan untuk voice cloning. Untuk mengatur source language pada tugas cloning, lihat CosyVoice Voice Cloning/Design API.

instruction

string

No

Mengatur instruksi untuk mengontrol efek sintesis seperti dialek, emosi, atau gaya bicara. Fitur ini hanya tersedia untuk voice hasil kloning dari model cosyvoice-v3.5-flash, cosyvoice-v3.5-plus, dan cosyvoice-v3-flash, serta voice sistem yang ditandai mendukung Instruct dalam voice list.

Batas panjang: 100 karakter.

Satu karakter Cina (termasuk Cina sederhana dan tradisional, Kanji Jepang, dan Hanja Korea) dihitung sebagai dua karakter. Semua karakter lain, seperti tanda baca, huruf, angka, dan Kana/Hangul Jepang/Korea, dihitung sebagai satu karakter.

Persyaratan penggunaan (berbeda-beda tergantung model):

  • cosyvoice-v3.5-flash dan cosyvoice-v3.5-plus: Masukkan instruksi apa pun untuk mengontrol efek sintesis, seperti emosi dan laju bicara.

    Penting

    cosyvoice-v3.5-flash dan cosyvoice-v3.5-plus tidak memiliki voice sistem. Hanya voice kustom dari voice design atau voice cloning yang didukung.

    Contoh instruksi:

    Speak in a very excited and high-pitched tone, expressing the ecstasy and excitement of a great success.
    Please maintain a medium-slow speech rate, with an elegant and intellectual tone, giving a sense of calm and reassurance.
    The tone should be full of sorrow and nostalgia, with a slight nasal quality, as if narrating a heartbreaking story.
    Please try to speak in a breathy voice, with a very low volume, creating a sense of intimate and mysterious whispering.
    The tone should be very impatient and annoyed, with a faster speech rate and minimal pauses between sentences.
    Please imitate a kind and gentle elder, with a steady speech rate and a voice full of care and affection.
    The tone should be sarcastic and disdainful, with emphasis on keywords and a slightly rising intonation at the end of sentences.
    Please speak in an extremely fearful and trembling voice.
    The tone should be like a professional news anchor: calm, objective, and articulate, with a neutral emotion.
    The tone should be lively and playful, with a clear smile, making the voice sound energetic and sunny.
  • cosyvoice-v3-flash: Persyaratan berikut harus dipenuhi:

    • Voice hasil kloning: Gunakan bahasa alami apa pun untuk mengontrol efek sintesis suara.

      Contoh instruksi:

      Please speak in Cantonese. (Supported dialects: Cantonese, Northeastern, Gansu, Guizhou, Henan, Hubei, Jiangxi, Minnan, Ningxia, Shanxi, Shaanxi, Shandong, Shanghainese, Sichuan, Tianjin, Yunnan.)
      Please say a sentence as loudly as possible.
      Please say a sentence as slowly as possible.
      Please say a sentence as quickly as possible.
      Please say a sentence very softly.
      Can you speak a little slower?
      Can you speak very quickly?
      Can you speak very slowly?
      Can you speak a little faster?
      Please say a sentence very angrily.
      Please say a sentence very happily.
      Please say a sentence very fearfully.
      Please say a sentence very sadly.
      Please say a sentence very surprisedly.
      Please try to sound as firm as possible.
      Please try to sound as angry as possible.
      Please try an approachable tone.
      Please speak in a cold tone.
      Please speak in a majestic tone.
      I want to experience a natural tone.
      I want to see how you express a threat.
      I want to see how you express wisdom.
      I want to see how you express seduction.
      I want to hear you speak in a lively way.
      I want to hear you speak with passion.
      I want to hear you speak in a steady manner.
      I want to hear you speak with confidence.
      Can you talk to me with excitement?
      Can you show an arrogant emotion?
      Can you show an elegant emotion?
      Can you answer the question happily?
      Can you give a gentle emotional demonstration?
      Can you talk to me in a calm tone?
      Can you answer me in a deep way?
      Can you talk to me with a gruff attitude?
      Tell me the answer in a sinister voice.
      Tell me the answer in a resilient voice.
      Narrate in a natural and friendly chat style.
      Speak in the tone of a radio drama podcaster.
    • Voice sistem: Instruksi harus menggunakan format dan konten tetap. Untuk informasi lebih lanjut, lihat voice list.

enable_aigc_tag

boolean

No

Menentukan apakah akan menambahkan identifier AIGC tak terlihat ke audio yang dihasilkan. Jika diatur ke true, identifier tak terlihat akan disisipkan ke dalam audio dalam format yang didukung (WAV, MP3, dan Opus).

Nilai default: false.

Hanya cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2 yang mendukung fitur ini.

aigc_propagator

string

No

Mengatur bidang ContentPropagator dalam identifier AIGC tak terlihat untuk mengidentifikasi propagator konten. Parameter ini hanya berlaku jika enable_aigc_tag bernilai true.

Nilai default: Alibaba Cloud UID.

Hanya cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2 yang mendukung fitur ini.

aigc_propagate_id

string

No

Mengatur bidang PropagateID dalam identifier AIGC tak terlihat untuk mengidentifikasi secara unik perilaku propagasi tertentu. Parameter ini hanya berlaku jika enable_aigc_tag bernilai true.

Nilai default: ID permintaan dari permintaan sintesis suara saat ini.

Hanya cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2 yang mendukung fitur ini.

hot_fix

object

No

Konfigurasi untuk hotpatching teks. Memungkinkan Anda menyesuaikan pelafalan kata tertentu atau mengganti teks sebelum sintesis. Fitur ini hanya tersedia untuk voice hasil kloning dari cosyvoice-v3-flash.

Parameter:

  • pronunciation: Menyesuaikan pelafalan. Berikan pinyin untuk kata-kata guna memperbaiki pelafalan default yang salah.

  • replace: Penggantian teks. Ganti kata yang ditentukan dengan teks target sebelum sintesis. Teks yang diganti menjadi konten aktual yang disintesis.

Contoh:

"hot_fix": {
  "pronunciation": [
    {"weather": "tian1 qi4"}
  ],
  "replace": [
    {"today": "jin1 tian1"}
  ]
}

enable_markdown_filter

boolean

No

Menentukan apakah akan mengaktifkan penyaringan Markdown. Jika diaktifkan, sistem secara otomatis menghapus simbol Markdown dari teks input sebelum mensintesis suara, sehingga simbol tersebut tidak terbaca. Fitur ini hanya tersedia untuk voice hasil kloning dari cosyvoice-v3-flash.

Nilai default: false.

Nilai yang valid:

  • true

  • false

2. Instruksi Melanjutkan Tugas

Instruksi ini mengirimkan teks untuk disintesis.

Anda dapat mengirimkan seluruh teks dalam satu instruksi continue-task, atau membaginya ke dalam beberapa instruksi continue-task, secara berurutan.

Penting

Kapan mengirimkan: Setelah menerima event task-started.

Catatan

Jangan menunggu lebih dari 23 detik antara pengiriman fragmen teks, jika tidak, akan muncul kesalahan 'request timeout after 23 seconds'.

Jika tidak ada teks lagi yang tersisa, kirimkan instruksi finish-task untuk mengakhiri tugas.

Server memberlakukan batas waktu 23 detik. Klien tidak dapat mengubah batas waktu ini.

Contoh:

{
    "header": {
        "aksi": "continue-task",
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx", // UUID acak
        "streaming": "duplex"
    },
    "muatan": {
        "input": {
            "teks": "Di depan tempat tidurku, cahaya bulan bersinar terang; kusangka itu embun beku di atas tanah."
        }
    }
}

Header referensi parameter:

Parameter

Type

Required

Description

header.action

string

Yes

Jenis instruksi.

Untuk instruksi ini, nilainya selalu "continue-task".

header.task_id

string

Yes

ID task untuk permintaan ini.

Harus sesuai dengan task_id yang digunakan dalam instruksi run-task.

header.streaming

string

Yes

String tetap: "duplex"

muatan referensi parameter:

Parameter

Type

Required

Description

input.text

string

Yes

Teks yang akan disintesis.

3. instruksi penyelesaian tugas: Mengakhiri tugas

Instruksi ini mengakhiri task sintesis suara.

Selalu kirim instruksi ini. Jika tidak, Anda mungkin mengalami:

  • Audio tidak lengkap: Server tidak akan memaksa mensintesis kalimat yang tersimpan di cache secara tidak lengkap, sehingga bagian akhir audio akan hilang.

  • Timeout koneksi: Jika Anda menunggu lebih dari 23 detik setelah instruksi continue-task terakhir sebelum mengirim finish-task, koneksi akan timeout dan ditutup.

  • Masalah penagihan: Task yang tidak diakhiri secara normal dapat mengembalikan informasi penggunaan yang tidak akurat.

Penting

Kapan harus dikirim: Kirim segera setelah semua instruksi continue-task telah dikirim. Jangan menunggu hingga audio selesai atau menunda pengiriman—hal ini dapat memicu timeout.

Contoh:

{
    "header": {
        "action": "finish-task",
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "streaming": "duplex"
    },
    "payload": {
        "input": {}//input harus ada, atau akan mengembalikan error
    }
}

header referensi parameter:

Parameter

Tipe

Wajib

Deskripsi

header.action

string

Ya

Jenis instruksi.

Untuk instruksi ini, nilainya selalu "finish-task".

header.task_id

string

Ya

ID task untuk permintaan ini.

Harus sesuai dengan task_id yang digunakan dalam instruksi run-task.

header.streaming

string

Ya

String tetap: "duplex"

payload referensi parameter:

Parameter

Tipe

Wajib

Deskripsi

payload.input

object

Ya

Format tetap: {}.

Events (server → client)

Events adalah pesan berformat JSON yang dikirim oleh server ke client. Setiap event merepresentasikan tahap pemrosesan.

Catatan

Server mengirim audio data biner secara terpisah—tidak termasuk dalam event apa pun.

1. task-started event: Tugas dimulai

Saat Anda menerima event task-started, tugas telah berhasil dimulai. Anda hanya boleh mengirim instruksi continue-task atau finish-task setelah menerima event ini. Jika tidak, tugas akan gagal.

Event task-started tidak memiliki konten dalam payload-nya.

Contoh:

{
    "header": {
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "event": "task-started",
        "attributes": {}
    },
    "payload": {}
}

header referensi parameter:

Parameter

Tipe

Deskripsi

header.event

string

Jenis event.

Untuk event ini, nilainya selalu "task-started".

header.task_id

string

ID task yang dihasilkan oleh client.

2. result-generated event

Selama Anda mengirim instruksi continue-task dan finish-task, server terus-menerus mengembalikan event result-generated.

Untuk menghubungkan audio data dengan teks yang sesuai, server menyertakan metadata kalimat dalam event result-generated bersamaan dengan audio. Server secara otomatis membagi teks input menjadi kalimat-kalimat. Sintesis setiap kalimat terdiri dari tiga sub-event:

  • sentence-begin: Menandai awal kalimat dan mengembalikan teks yang akan disintesis.

  • sentence-synthesis: Menandai chunk audio data. Setiap event segera diikuti oleh frame audio data melalui saluran biner WebSocket.

    • Satu kalimat menghasilkan beberapa event sentence-synthesis—satu untuk setiap chunk audio.

    • Client harus menerima chunk audio ini secara berurutan dan menambahkannya ke file yang sama.

    • Setiap event sentence-synthesis memiliki korespondensi satu-ke-satu dengan frame audio berikutnya—tidak terjadi ketidaksesuaian.

  • sentence-end: Menandai akhir kalimat dan mengembalikan teks kalimat serta jumlah karakter yang ditagih secara kumulatif.

Gunakan bidang payload.output.type untuk membedakan jenis sub-event.

Contoh:

sentence-begin

{
    "header": {
        "task_id": "3f2d5c86-0550-45c0-801f-xxxxxxxxxx",
        "event": "result-generated",
        "attributes": {}
    },
    "payload": {
        "output": {
            "sentence": {
                "index": 0,
                "words": []
            },
            "type": "sentence-begin",
            "original_text": "Before my bed, moonlight shines bright,"
        }
    }
}

sentence-synthesis

{
    "header": {
        "task_id": "3f2d5c86-0550-45c0-801f-xxxxxxxxxx",
        "event": "result-generated",
        "attributes": {}
    },
    "payload": {
        "output": {
            "sentence": {
                "index": 0,
                "words": []
            },
            "type": "sentence-synthesis"
        }
    }
}

sentence-end

{
    "header": {
        "task_id": "3f2d5c86-0550-45c0-801f-xxxxxxxxxx",
        "event": "result-generated",
        "attributes": {}
    },
    "payload": {
        "output": {
            "sentence": {
                "index": 0,
                "words": []
            },
            "type": "sentence-end",
            "original_text": "Before my bed, moonlight shines bright,"
        },
        "usage": {
            "characters": 11
        }
    }
}

header referensi parameter:

Parameter

Tipe

Deskripsi

header.event

string

Jenis event.

Untuk event ini, nilainya selalu "result-generated".

header.task_id

string

ID task yang dihasilkan oleh client.

header.attributes

object

Atribut tambahan—biasanya berupa objek kosong.

payload referensi parameter:

Parameter

Tipe

Deskripsi

payload.output.type

string

Jenis sub-event.

Nilai:

  • sentence-begin: Menandai awal kalimat dan mengembalikan teks yang akan disintesis.

  • sentence-synthesis: Menandai chunk audio data. Setiap event segera diikuti oleh frame audio data melalui saluran biner WebSocket.

    • Satu kalimat menghasilkan beberapa event sentence-synthesis—satu untuk setiap chunk audio.

    • Client harus menerima chunk audio ini secara berurutan dan menambahkannya ke file yang sama.

    • Setiap event sentence-synthesis memiliki korespondensi satu-ke-satu dengan frame audio berikutnya—tidak terjadi ketidaksesuaian.

  • sentence-end: Menandai akhir kalimat dan mengembalikan teks kalimat serta jumlah karakter yang ditagih secara kumulatif.

Alur event lengkap

Untuk setiap kalimat yang akan disintesis, server mengembalikan event dalam urutan berikut:

  1. sentence-begin: Menandai awal kalimat dan menyertakan teks kalimat (original_text).

  2. sentence-synthesis (berkali-kali): Setiap event segera diikuti oleh frame audio data biner.

  3. sentence-end: Menandai akhir kalimat dan menyertakan teks kalimat serta jumlah karakter yang ditagih secara kumulatif.

payload.output.sentence.index

integer

Nomor kalimat, dimulai dari 0.

payload.output.sentence.words

array

Array informasi karakter.

payload.output.sentence.words.text

string

Teks kata.

payload.output.sentence.words.begin_index

integer

Indeks posisi awal kata dalam kalimat, dihitung dari 0.

payload.output.sentence.words.end_index

integer

Indeks posisi akhir kata dalam kalimat, dihitung dari 1.

payload.output.sentence.words.begin_time

integer

Timestamp awal audio kata, dalam milidetik.

payload.output.sentence.words.end_time

integer

Timestamp akhir audio kata, dalam milidetik.

payload.output.original_text

string

Konten kalimat setelah teks input pengguna dibagi. Kalimat terakhir mungkin tidak menyertakan bidang ini.

payload.usage.characters

integer

Total karakter yang ditagih dalam permintaan ini hingga saat ini. Dalam satu task, bidang usage dapat muncul di event result-generated atau di event task-finished. Bidang usage berisi total kumulatif. Gunakan kemunculan terakhirnya.

3. task-finished event: Tugas selesai

Saat Anda menerima event task-finished, tugas telah berakhir.

Setelah tugas berakhir, Anda dapat menutup koneksi WebSocket dan keluar, atau menggunakan kembali koneksi tersebut untuk mengirim instruksi run-task baru (lihat Connection overhead and reuse).

Contoh:

{
    "header": {
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "event": "task-finished",
        "attributes": {
            "request_uuid": "0a9dba9e-d3a6-45a4-be6d-xxxxxxxxxxxx"
        }
    },
    "payload": {
        "output": {
            "sentence": {
                "words": []
            }
        },
        "usage": {
            "characters": 13
        }
    }
}

header referensi parameter:

Parameter

Tipe

Deskripsi

header.event

string

Jenis event.

Untuk event ini, nilainya selalu "task-finished".

header.task_id

string

ID task yang dihasilkan oleh client.

header.attributes.request_uuid

string

ID permintaan. Berikan ini kepada pengembang CosyVoice untuk diagnosis masalah.

payload referensi parameter:

Parameter

Tipe

Deskripsi

payload.usage.characters

integer

Total karakter yang ditagih dalam permintaan ini hingga saat ini. Dalam satu task, bidang usage dapat muncul di event result-generated atau di event task-finished. Bidang usage berisi total kumulatif. Gunakan kemunculan terakhirnya.

4. task-failed event: Tugas gagal

Jika Anda menerima event task-failed, tugas telah gagal. Tutup koneksi WebSocket dan tangani error tersebut. Analisis pesan error—jika kegagalan disebabkan oleh masalah kode, perbaiki kode tersebut.

Contoh:

{
    "header": {
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "event": "task-failed",
        "error_code": "InvalidParameter",
        "error_message": "[tts:]Engine return error code: 418",
        "attributes": {}
    },
    "payload": {}
}

header referensi parameter:

Parameter

Tipe

Deskripsi

header.event

string

Jenis event.

Untuk event ini, statusnya selalu task-failed.

header.task_id

string

ID task yang dihasilkan oleh client.

header.error_code

string

Deskripsi jenis error.

header.error_message

string

Alasan error secara detail.

Metode penginterupsi Tugas

Selama sintesis streaming, Anda dapat menginterupsi tugas saat ini lebih awal—misalnya, jika pengguna membatalkan pemutaran atau menginterupsi percakapan langsung—dengan menggunakan salah satu metode berikut:

Interrupt Mode

Server behavior

Use case

Directly close the connection

  • Server segera menghentikan sintesis.

  • Audio apa pun yang telah dihasilkan tetapi belum dikirim akan dibuang.

  • Klien tidak menerima event task-finished.

  • Koneksi tidak dapat digunakan kembali setelah ditutup.

Immediate interruption: Pengguna membatalkan pemutaran, mengganti konten, atau keluar dari aplikasi.

Send a finish-task command

  • Server memaksa sintesis seluruh teks yang ada di cache.

  • Server mengembalikan semua chunk audio yang tersisa.

  • Ini mengembalikan event task-selesai.

  • Koneksi tetap dapat digunakan kembali, sehingga Anda dapat memulai tugas baru.

Elegant end: Anda berhenti mengirim teks baru tetapi tetap menerima audio untuk konten yang telah di-cache.

Overhead koneksi dan penggunaan ulang

Layanan WebSocket mendukung penggunaan ulang koneksi untuk meningkatkan efisiensi resource dan menghindari overhead saat menyiapkan koneksi.

Setelah server menerima run-task instruction dari client, server memulai task baru. Setelah client mengirim finish-task instruction, server mengembalikan task-finished event ketika task selesai. Setelah task berakhir, koneksi WebSocket dapat digunakan ulang dengan mengirim run-task instruction lain untuk memulai task berikutnya.

Penting
  1. Anda hanya dapat mengirim run-task instruction baru setelah server mengembalikan task-finished event.

  2. Task yang berbeda pada koneksi yang digunakan ulang harus menggunakan task_id yang berbeda.

  3. Jika sebuah task gagal selama eksekusi, server mengembalikan task-failed event dan menutup koneksi. Koneksi ini tidak dapat digunakan ulang.

  4. Jika tidak ada task baru yang dimulai dalam waktu 60 detik setelah task berakhir, koneksi akan timeout dan ditutup secara otomatis.

Metrik kinerja dan batas konkurensi

Batas konkurensi

Untuk informasi lebih lanjut, lihat Rate limiting.

Untuk menambah kuota konkurensi Anda, misalnya guna mendukung lebih banyak koneksi bersamaan, Anda dapat menghubungi layanan pelanggan. Penyesuaian kuota memerlukan tinjauan dan biasanya selesai dalam 1 hingga 3 hari kerja.

Catatan

Praktik terbaik: Untuk meningkatkan penggunaan resource, Anda dapat menggunakan kembali satu koneksi WebSocket untuk multiple task alih-alih membuka koneksi baru untuk setiap task. Untuk informasi lebih lanjut, lihat Connection overhead and reuse.

Kinerja koneksi dan latensi

Waktu koneksi tipikal:

  • Klien di Tiongkok daratan: Pembentukan koneksi WebSocket (dari newWebSocket hingga onOpen) biasanya memakan waktu 200 hingga 1.000 ms.

  • Koneksi lintas batas (seperti Hong Kong atau wilayah internasional): Latensi koneksi dapat mencapai 1 hingga 3 detik. Dalam kasus jarang terjadi, latensi tersebut dapat mencapai 10 hingga 30 detik.

Pemecahan masalah waktu koneksi yang lama:

Jika pembentukan koneksi WebSocket memerlukan waktu lebih dari 30 detik, penyebabnya mungkin salah satu dari isu berikut:

  1. Isu jaringan: Latensi jaringan tinggi antara client dan server, seperti latensi akibat koneksi lintas batas atau kualitas ISP yang buruk.

  2. Resolusi DNS lambat: Waktu resolusi DNS yang lama untuk dashscope.aliyuncs.com. Anda dapat mencoba menggunakan DNS publik, seperti 8.8.8.8, atau mengonfigurasi file hosts lokal Anda.

  3. Proses jabat tangan TLS lambat: Versi TLS yang usang pada client atau validasi sertifikat yang lambat. Kami menyarankan Anda menggunakan TLS 1.2 atau versi yang lebih baru.

  4. Proxy atau firewall: Jaringan perusahaan mungkin memblokir koneksi WebSocket atau mengharuskan penggunaan proxy.

Alat pemecahan masalah:

  • Anda dapat menggunakan Wireshark atau tcpdump untuk menganalisis waktu fase jabat tangan TCP, jabat tangan TLS, dan Upgrade WebSocket.

  • Anda dapat menguji latensi koneksi HTTP dengan curl: curl -w "@curl-format.txt" -o /dev/null -s https://dashscope.aliyuncs.com

Catatan

API WebSocket CosyVoice dideploy di wilayah Beijing, Tiongkok daratan. Jika client Anda berada di wilayah lain, seperti Hong Kong atau wilayah luar negeri, Anda dapat menggunakan server relay terdekat atau CDN untuk mempercepat koneksi.

Kinerja generasi audio

Kecepatan sintesis:

  • Faktor Real-time (RTF): Model CosyVoice biasanya mensintesis audio dengan kecepatan 0,1 hingga 0,5 kali kecepatan Real-time. Artinya, menghasilkan audio selama 1 detik memerlukan waktu 0,1 hingga 0,5 detik. Kecepatan aktual bergantung pada versi model, panjang teks, dan beban server.

  • Latensi paket pertama: Latensi dari pengiriman instruksi continue-task hingga penerimaan chunk audio pertama biasanya 200 hingga 800 ms.

Kode contoh

Kode contoh ini hanya menunjukkan konektivitas layanan dasar. Anda harus mengimplementasikan logika siap produksi untuk kasus penggunaan spesifik Anda.

Saat menulis kode klien WebSocket, gunakan pemrograman asinkron untuk mengirim dan menerima pesan secara bersamaan. Ikuti langkah-langkah berikut:

  1. Bentuk koneksi WebSocket

    Panggil fungsi koneksi pustaka WebSocket Anda—implementasi bervariasi tergantung bahasa atau pustaka—dan berikan Header dan URL.

  2. Dengarkan pesan server

    Gunakan fungsi callback pustaka WebSocket Anda—berdasarkan pola observer—untuk mendengarkan pesan server. Implementasi bervariasi tergantung bahasa.

    Pesan server terbagi menjadi dua kategori: aliran audio biner dan event.

    Dengarkan event

    Proses aliran audio biner: Server mengirimkan audio melalui saluran binary dalam frame. Data audio lengkap dibagi ke dalam beberapa paket.

    • Dalam sintesis ucapan aliran, untuk format terkompresi seperti MP3 dan Opus, data audio tersegmentasi harus diputar menggunakan pemutar aliran. Jangan putar frame per frame, karena hal ini menyebabkan kegagalan decoding.

      Pemutar aliran meliputi FFmpeg, PyAudio (Python), AudioFormat (Java), dan MediaSource (JavaScript).
    • Saat menggabungkan data audio menjadi file audio lengkap, tulis ke file yang sama dalam mode tambah.

    • Untuk audio WAV dan MP3 dari sintesis ucapan aliran, hanya frame pertama yang berisi informasi header. Frame berikutnya hanya berisi data audio.

  3. Kirimkan pesan ke server (perhatikan waktu dengan cermat)

    Dari thread yang terpisah dari thread yang mendengarkan pesan server—misalnya, thread utama—kirimkan instruksi ke server. Implementasi bervariasi tergantung bahasa.

    Kirimkan instruksi secara ketat berurutan. Jika tidak, tugas dapat gagal:

    1. Kirimkan instruksi run-task

    2. Kirimkan instruksi continue-task

      • Mengirimkan teks untuk disintesis.

      • Kirimkan hanya setelah menerima event task-started dari server.

    3. Kirimkan instruksi finish-task

  4. Tutup koneksi WebSocket

    Tutup koneksi WebSocket ketika program berakhir secara normal, mengalami pengecualian, atau menerima event task-finished atau event task-failed. Biasanya panggil fungsi close pustaka.

Lihat contoh lengkap

Go

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/gorilla/websocket"
)

const (
	// Gunakan URL ini untuk wilayah Singapura. Untuk wilayah Beijing, ganti dengan: wss://dashscope.aliyuncs.com/api-ws/v1/inference/
	wsURL      = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/"
	outputFile = "output.mp3"
)

func main() {
	// Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
	// Jika tidak ada variabel lingkungan yang diatur, ganti baris berikut dengan: apiKey := "sk-xxx"
	apiKey := os.Getenv("DASHSCOPE_API_KEY")

	// Hapus file output
	os.Remove(outputFile)
	os.Create(outputFile)

	// Hubungkan WebSocket
	header := make(http.Header)
	header.Add("X-DashScope-DataInspection", "enable")
	header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))

	conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header)
	if err != nil {
		if resp != nil {
			fmt.Printf("Koneksi gagal kode status HTTP: %d\n", resp.StatusCode)
		}
		fmt.Println("Koneksi gagal:", err)
		return
	}
	defer conn.Close()

	// Hasilkan ID tugas
	taskID := uuid.New().String()
	fmt.Printf("ID tugas yang dihasilkan: %s\n", taskID)

	// Kirimkan instruksi run-task
	runTaskCmd := map[string]interface{}{
		"header": map[string]interface{}{
			"action":    "run-task",
			"task_id":   taskID,
			"streaming": "duplex",
		},
		"payload": map[string]interface{}{
			"task_group": "audio",
			"task":       "tts",
			"function":   "SpeechSynthesizer",
			"model":      "cosyvoice-v3-flash",
			"parameters": map[string]interface{}{
				"text_type":   "PlainText",
				"voice":       "longanyang",
				"format":      "mp3",
				"sample_rate": 22050,
				"volume":      50,
				"rate":        1,
				"pitch":       1,
				// Jika enable_ssml true, hanya satu instruksi continue-task yang diizinkan. Jika tidak, akan mengembalikan “Text request limit violated, expected 1.”
				"enable_ssml": false,
			},
			"input": map[string]interface{}{},
		},
	}

	runTaskJSON, _ := json.Marshal(runTaskCmd)
	fmt.Printf("Instruksi run-task terkirim: %s\n", string(runTaskJSON))

	err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
	if err != nil {
		fmt.Println("Gagal mengirim run-task:", err)
		return
	}

	textSent := false

	// Proses pesan
	for {
		messageType, message, err := conn.ReadMessage()
		if err != nil {
			fmt.Println("Gagal membaca pesan:", err)
			break
		}

		// Proses pesan biner
		if messageType == websocket.BinaryMessage {
			fmt.Printf("Menerima pesan biner, panjang: %d\n", len(message))
			file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
			file.Write(message)
			file.Close()
			continue
		}

		// Proses pesan teks
		messageStr := string(message)
		fmt.Printf("Menerima pesan teks: %s\n", strings.ReplaceAll(messageStr, "\n", ""))

		// Parsing JSON sederhana untuk mendapatkan jenis event
		var msgMap map[string]interface{}
		if json.Unmarshal(message, &msgMap) == nil {
			if header, ok := msgMap["header"].(map[string]interface{}); ok {
				if event, ok := header["event"].(string); ok {
					fmt.Printf("Jenis event: %s\n", event)

					switch event {
					case "task-started":
						fmt.Println("=== Menerima event task-started ===")

						if !textSent {
							// Kirimkan instruksi continue-task

							texts := []string{"Before my bed, moonlight shines bright, I suspect it's frost upon the ground.", "I raise my eyes to gaze at the bright moon, then bow my head, thinking of home."}

							for _, text := range texts {
								continueTaskCmd := map[string]interface{}{
									"header": map[string]interface{}{
										"action":    "continue-task",
										"task_id":   taskID,
										"streaming": "duplex",
									},
									"payload": map[string]interface{}{
										"input": map[string]interface{}{
											"text": text,
										},
									},
								}

								continueTaskJSON, _ := json.Marshal(continueTaskCmd)
								fmt.Printf("Instruksi continue-task terkirim: %s\n", string(continueTaskJSON))

								err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON)
								if err != nil {
									fmt.Println("Gagal mengirim continue-task:", err)
									return
								}
							}

							textSent = true

							// Tunda sebelum mengirimkan finish-task
							time.Sleep(500 * time.Millisecond)

							// Kirimkan instruksi finish-task
							finishTaskCmd := map[string]interface{}{
								"header": map[string]interface{}{
									"action":    "finish-task",
									"task_id":   taskID,
									"streaming": "duplex",
								},
								"payload": map[string]interface{}{
									"input": map[string]interface{}{},
								},
							}

							finishTaskJSON, _ := json.Marshal(finishTaskCmd)
							fmt.Printf("Instruksi finish-task terkirim: %s\n", string(finishTaskJSON))

							err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON)
							if err != nil {
								fmt.Println("Gagal mengirim finish-task:", err)
								return
							}
						}

					case "task-finished":
						fmt.Println("=== Tugas selesai ===")
						return

					case "task-failed":
						fmt.Println("=== Tugas gagal ===")
						if header["error_message"] != nil {
							fmt.Printf("Pesan kesalahan: %s\n", header["error_message"])
						}
						return

					case "result-generated":
						fmt.Println("Menerima event result-generated")
					}
				}
			}
		}
	}
}

C#

using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

class Program {
    // Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    // Jika tidak ada variabel lingkungan yang diatur, ganti baris berikut dengan: private static readonly string ApiKey = "sk-xxx"
    private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("Variabel lingkungan DASHSCOPE_API_KEY tidak diatur.");

    // Gunakan URL ini untuk wilayah Singapura. Untuk wilayah Beijing, ganti dengan: wss://dashscope.aliyuncs.com/api-ws/v1/inference/
    private const string WebSocketUrl = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/";
    // Jalur file output
    private const string OutputFilePath = "output.mp3";

    // Klien WebSocket
    private static ClientWebSocket _webSocket = new ClientWebSocket();
    // Sumber token pembatalan
    private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    // ID tugas
    private static string? _taskId;
    // Bendera tugas dimulai
    private static TaskCompletionSource<bool> _taskStartedTcs = new TaskCompletionSource<bool>();

    static async Task Main(string[] args) {
        try {
            // Hapus file output
            ClearOutputFile(OutputFilePath);

            // Hubungkan ke layanan WebSocket
            await ConnectToWebSocketAsync(WebSocketUrl);

            // Mulai menerima pesan
            Task receiveTask = ReceiveMessagesAsync();

            // Kirimkan instruksi run-task
            _taskId = GenerateTaskId();
            await SendRunTaskCommandAsync(_taskId);

            // Tunggu event task-started
            await _taskStartedTcs.Task;

            // Kirimkan instruksi continue-task
            string[] texts = {
                "Before my bed, moonlight shines bright",
                "I suspect it's frost upon the ground",
                "I raise my eyes to gaze at the bright moon",
                "then bow my head, thinking of home"
            };
            foreach (string text in texts) {
                await SendContinueTaskCommandAsync(text);
            }

            // Kirimkan instruksi finish-task
            await SendFinishTaskCommandAsync(_taskId);

            // Tunggu tugas penerimaan selesai
            await receiveTask;

            Console.WriteLine("Tugas selesai. Koneksi ditutup.");
        } catch (OperationCanceledException) {
            Console.WriteLine("Tugas dibatalkan.");
        } catch (Exception ex) {
            Console.WriteLine($"Kesalahan: {ex.Message}");
        } finally {
            _cancellationTokenSource.Cancel();
            _webSocket.Dispose();
        }
    }

    private static void ClearOutputFile(string filePath) {
        if (File.Exists(filePath)) {
            File.WriteAllText(filePath, string.Empty);
            Console.WriteLine("File output dihapus.");
        } else {
            Console.WriteLine("File output tidak ada. Tidak perlu tindakan.");
        }
    }

    private static async Task ConnectToWebSocketAsync(string url) {
        var uri = new Uri(url);
        if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) {
            return;
        }

        // Atur header permintaan WebSocket
        _webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
        _webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable");

        try {
            await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
            Console.WriteLine("Berhasil terhubung ke layanan WebSocket.");
        } catch (OperationCanceledException) {
            Console.WriteLine("Koneksi WebSocket dibatalkan.");
        } catch (Exception ex) {
            Console.WriteLine($"Koneksi WebSocket gagal: {ex.Message}");
            throw;
        }
    }

    private static async Task SendRunTaskCommandAsync(string taskId) {
        var command = CreateCommand("run-task", taskId, "duplex", new {
            task_group = "audio",
            task = "tts",
            function = "SpeechSynthesizer",
            model = "cosyvoice-v3-flash",
            parameters = new
            {
                text_type = "PlainText",
                voice = "longanyang",
                format = "mp3",
                sample_rate = 22050,
                volume = 50,
                rate = 1,
                pitch = 1,
                // Jika enable_ssml true, hanya satu instruksi continue-task yang diizinkan. Jika tidak, akan mengembalikan “Text request limit violated, expected 1.”
                enable_ssml = false
            },
            input = new { }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("Instruksi run-task terkirim.");
    }

    private static async Task SendContinueTaskCommandAsync(string text) {
        if (_taskId == null) {
            throw new InvalidOperationException("ID tugas belum diinisialisasi.");
        }

        var command = CreateCommand("continue-task", _taskId, "duplex", new {
            input = new {
                text
            }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("Instruksi continue-task terkirim.");
    }

    private static async Task SendFinishTaskCommandAsync(string taskId) {
        var command = CreateCommand("finish-task", taskId, "duplex", new {
            input = new { }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("Instruksi finish-task terkirim.");
    }

    private static async Task SendJsonMessageAsync(string message) {
        var buffer = Encoding.UTF8.GetBytes(message);
        try {
            await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
        } catch (OperationCanceledException) {
            Console.WriteLine("Pengiriman pesan dibatalkan.");
        }
    }

    private static async Task ReceiveMessagesAsync() {
        while (_webSocket.State == WebSocketState.Open) {
            var response = await ReceiveMessageAsync();
            if (response != null) {
                var eventStr = response.RootElement.GetProperty("header").GetProperty("event").GetString();
                switch (eventStr) {
                    case "task-started":
                        Console.WriteLine("Tugas dimulai.");
                        _taskStartedTcs.TrySetResult(true);
                        break;
                    case "task-finished":
                        Console.WriteLine("Tugas selesai.");
                        _cancellationTokenSource.Cancel();
                        break;
                    case "task-failed":
                        Console.WriteLine("Tugas gagal: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString());
                        _cancellationTokenSource.Cancel();
                        break;
                    default:
                        // Tangani result-generated di sini
                        break;
                }
            }
        }
    }

    private static async Task<JsonDocument?> ReceiveMessageAsync() {
        var buffer = new byte[1024 * 4];
        var segment = new ArraySegment<byte>(buffer);

        try {
            WebSocketReceiveResult result = await _webSocket.ReceiveAsync(segment, _cancellationTokenSource.Token);

            if (result.MessageType == WebSocketMessageType.Close) {
                await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token);
                return null;
            }

            if (result.MessageType == WebSocketMessageType.Binary) {
                // Proses data biner
                Console.WriteLine("Menerima data biner...");

                // Simpan data biner ke file
                using (var fileStream = new FileStream(OutputFilePath, FileMode.Append)) {
                    fileStream.Write(buffer, 0, result.Count);
                }

                return null;
            }

            string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
            return JsonDocument.Parse(message);
        } catch (OperationCanceledException) {
            Console.WriteLine("Penerimaan pesan dibatalkan.");
            return null;
        }
    }

    private static string GenerateTaskId() {
        return Guid.NewGuid().ToString("N").Substring(0, 32);
    }

    private static string CreateCommand(string action, string taskId, string streaming, object payload) {
        var command = new {
            header = new {
                action,
                task_id = taskId,
                streaming
            },
            payload
        };

        return JsonSerializer.Serialize(command);
    }
}

PHP

Struktur direktori kode contoh:

my-php-project/

├── composer.json

├── vendor/

└── index.php

Isi composer.json (sesuaikan versi sesuai kebutuhan):

{
    "require": {
        "react/event-loop": "^1.3",
        "react/socket": "^1.11",
        "react/stream": "^1.2",
        "react/http": "^1.1",
        "ratchet/pawl": "^0.4"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

Isi index.php:

<?php

require __DIR__ . '/vendor/autoload.php';

use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;

// Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// Jika tidak ada variabel lingkungan yang diatur, ganti baris berikut dengan: $api_key = "sk-xxx"
$api_key = getenv("DASHSCOPE_API_KEY");
// Gunakan URL ini untuk wilayah Singapura. Untuk wilayah Beijing, ganti dengan: wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/
$websocket_url = 'wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/'; // Alamat server WebSocket
$output_file = 'output.mp3'; // Jalur file output

$loop = Loop::get();

if (file_exists($output_file)) {
    // Hapus isi file
    file_put_contents($output_file, '');
}

// Buat konektor kustom
$socketConnector = new SocketConnector($loop, [
    'tcp' => [
        'bindto' => '0.0.0.0:0',
    ],
    'tls' => [
        'verify_peer' => false,
        'verify_peer_name' => false,
    ],
]);

$connector = new Connector($loop, $socketConnector);

$headers = [
    'Authorization' => 'bearer ' . $api_key,
    'X-DashScope-DataInspection' => 'enable'
];

$connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $output_file) {
    echo "Terhubung ke server WebSocket\n";

    // Hasilkan ID tugas
    $taskId = generateTaskId();

    // Kirimkan instruksi run-task
    sendRunTaskMessage($conn, $taskId);

    // Definisikan fungsi untuk mengirimkan instruksi continue-task
    $sendContinueTask = function() use ($conn, $loop, $taskId) {
        // Teks untuk dikirim
        $texts = ["Before my bed, moonlight shines bright", "I suspect it's frost upon the ground", "I raise my eyes to gaze at the bright moon", "then bow my head, thinking of home"];
        $continueTaskCount = 0;
        foreach ($texts as $text) {
            $continueTaskMessage = json_encode([
                "header" => [
                    "action" => "continue-task",
                    "task_id" => $taskId,
                    "streaming" => "duplex"
                ],
                "payload" => [
                    "input" => [
                        "text" => $text
                    ]
                ]
            ]);
            echo "Bersiap mengirimkan instruksi continue-task: " . $continueTaskMessage . "\n";
            $conn->send($continueTaskMessage);
            $continueTaskCount++;
        }
        echo "Jumlah instruksi continue-task yang dikirim: " . $continueTaskCount . "\n";

        // Kirimkan instruksi finish-task
        sendFinishTaskMessage($conn, $taskId);
    };

    // Bendera untuk event task-started
    $taskStarted = false;

    // Dengarkan pesan
    $conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) {
        if ($msg->isBinary()) {
            // Tulis data biner ke file lokal
            file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
        } else {
            // Proses pesan non-biner
            $response = json_decode($msg, true);

            if (isset($response['header']['event'])) {
                handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted);
            } else {
                echo "Format pesan tidak dikenal\n";
            }
        }
    });

    // Dengarkan penutupan koneksi
    $conn->on('close', function($code = null, $reason = null) {
        echo "Koneksi ditutup\n";
        if ($code !== null) {
            echo "Kode penutupan: " . $code . "\n";
        }
        if ($reason !== null) {
            echo "Alasan penutupan: " . $reason . "\n";
        }
    });
}, function ($e) {
    echo "Tidak dapat terhubung: {$e->getMessage()}\n";
});

$loop->run();

/**
 * Hasilkan ID tugas
 * @return string
 */
function generateTaskId(): string {
    return bin2hex(random_bytes(16));
}

/**
 * Kirimkan instruksi run-task
 * @param $conn
 * @param $taskId
 */
function sendRunTaskMessage($conn, $taskId) {
    $runTaskMessage = json_encode([
        "header" => [
            "action" => "run-task",
            "task_id" => $taskId,
            "streaming" => "duplex"
        ],
        "payload" => [
            "task_group" => "audio",
            "task" => "tts",
            "function" => "SpeechSynthesizer",
            "model" => "cosyvoice-v3-flash",
            "parameters" => [
                "text_type" => "PlainText",
                "voice" => "longanyang",
                "format" => "mp3",
                "sample_rate" => 22050,
                "volume" => 50,
                "rate" => 1,
                "pitch" => 1,
                // Jika enable_ssml true, hanya satu instruksi continue-task yang diizinkan. Jika tidak, akan mengembalikan “Text request limit violated, expected 1.”
                "enable_ssml" => false
            ],
            "input" => (object) []
        ]
    ]);
    echo "Bersiap mengirimkan instruksi run-task: " . $runTaskMessage . "\n";
    $conn->send($runTaskMessage);
    echo "Instruksi run-task terkirim\n";
}

/**
 * Baca file audio
 * @param string $filePath
 * @return bool|string
 */
function readAudioFile(string $filePath) {
    $voiceData = file_get_contents($filePath);
    if ($voiceData === false) {
        echo "Tidak dapat membaca file audio\n";
    }
    return $voiceData;
}

/**
 * Pisahkan data audio
 * @param string $data
 * @param int $chunkSize
 * @return array
 */
function splitAudioData(string $data, int $chunkSize): array {
    return str_split($data, $chunkSize);
}

/**
 * Kirimkan instruksi finish-task
 * @param $conn
 * @param $taskId
 */
function sendFinishTaskMessage($conn, $taskId) {
    $finishTaskMessage = json_encode([
        "header" => [
            "action" => "finish-task",
            "task_id" => $taskId,
            "streaming" => "duplex"
        ],
        "payload" => [
            "input" => (object) []
        ]
    ]);
    echo "Bersiap mengirimkan instruksi finish-task: " . $finishTaskMessage . "\n";
    $conn->send($finishTaskMessage);
    echo "Instruksi finish-task terkirim\n";
}

/**
 * Tangani event
 * @param $conn
 * @param $response
 * @param $sendContinueTask
 * @param $loop
 * @param $taskId
 * @param $taskStarted
 */
function handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, &$taskStarted) {
    switch ($response['header']['event']) {
        case 'task-started':
            echo "Tugas dimulai. Mengirimkan instruksi continue-task...\n";
            $taskStarted = true;
            // Kirimkan instruksi continue-task
            $sendContinueTask();
            break;
        case 'result-generated':
            // Menerima event result-generated
            break;
        case 'task-finished':
            echo "Tugas selesai\n";
            $conn->close();
            break;
        case 'task-failed':
            echo "Tugas gagal\n";
            echo "Kode kesalahan: " . $response['header']['error_code'] . "\n";
            echo "Pesan kesalahan: " . $response['header']['error_message'] . "\n";
            $conn->close();
            break;
        case 'error':
            echo "Kesalahan: " . $response['payload']['message'] . "\n";
            break;
        default:
            echo "Event tidak dikenal: " . $response['header']['event'] . "\n";
            break;
    }

    // Tutup koneksi jika tugas selesai
    if ($response['header']['event'] == 'task-finished') {
        // Tunggu 1 detik untuk memastikan semua data ditransmisikan
        $loop->addTimer(1, function() use ($conn) {
            $conn->close();
            echo "Klien menutup koneksi\n";
        });
    }

    // Tutup koneksi jika tidak ada event task-started yang diterima
    if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
        $conn->close();
    }
}

Node.js

Instal dependensi:

npm install ws
npm install uuid

Kode contoh:

const WebSocket = require('ws');
const fs = require('fs');
const uuid = require('uuid').v4;

// Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// Jika tidak ada variabel lingkungan yang diatur, ganti baris berikut dengan: const apiKey = "sk-xxx"
const apiKey = process.env.DASHSCOPE_API_KEY;
// Gunakan URL ini untuk wilayah Singapura. Untuk wilayah Beijing, ganti dengan: wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/
const url = 'wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/';
// Jalur file output
const outputFilePath = 'output.mp3';

// Hapus file output
fs.writeFileSync(outputFilePath, '');

// Buat klien WebSocket
const ws = new WebSocket(url, {
  headers: {
    Authorization: `bearer ${apiKey}`,
    'X-DashScope-DataInspection': 'enable'
  }
});

let taskStarted = false;
let taskId = uuid();

ws.on('open', () => {
  console.log('Terhubung ke server WebSocket');

  // Kirimkan instruksi run-task
  const runTaskMessage = JSON.stringify({
    header: {
      action: 'run-task',
      task_id: taskId,
      streaming: 'duplex'
    },
    payload: {
      task_group: 'audio',
      task: 'tts',
      function: 'SpeechSynthesizer',
      model: 'cosyvoice-v3-flash',
      parameters: {
        text_type: 'PlainText',
        voice: 'longanyang', // Suara
        format: 'mp3', // Format audio
        sample_rate: 22050, // Laju sampel
        volume: 50, // Volume
        rate: 1, // Laju ucapan
        pitch: 1, // Nada
        enable_ssml: false // Aktifkan SSML. Jika true, hanya satu instruksi continue-task yang diizinkan. Jika tidak, akan mengembalikan “Text request limit violated, expected 1.”
      },
      input: {}
    }
  });
  ws.send(runTaskMessage);
  console.log('Pesan run-task terkirim');
});

const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' });
ws.on('message', (data, isBinary) => {
  if (isBinary) {
    // Tulis data biner ke file
    fileStream.write(data);
  } else {
    const message = JSON.parse(data);

    switch (message.header.event) {
      case 'task-started':
        taskStarted = true;
        console.log('Tugas dimulai');
        // Kirimkan instruksi continue-task
        sendContinueTasks(ws);
        break;
      case 'task-finished':
        console.log('Tugas selesai');
        ws.close();
        fileStream.end(() => {
          console.log('Aliran file ditutup');
        });
        break;
      case 'task-failed':
        console.error('Tugas gagal: ', message.header.error_message);
        ws.close();
        fileStream.end(() => {
          console.log('Aliran file ditutup');
        });
        break;
      default:
        // Tangani result-generated di sini
        break;
    }
  }
});

function sendContinueTasks(ws) {
  const texts = [
    'Before my bed, moonlight shines bright,',
    'I suspect it\'s frost upon the ground.',
    'I raise my eyes to gaze at the bright moon,',
    'then bow my head, thinking of home.'
  ];

  texts.forEach((text, index) => {
    setTimeout(() => {
      if (taskStarted) {
        const continueTaskMessage = JSON.stringify({
          header: {
            action: 'continue-task',
            task_id: taskId,
            streaming: 'duplex'
          },
          payload: {
            input: {
              text: text
            }
          }
        });
        ws.send(continueTaskMessage);
        console.log(`Instruksi continue-task terkirim, teks: ${text}`);
      }
    }, index * 1000); // Kirim setiap detik
  });

  // Kirimkan instruksi finish-task
  setTimeout(() => {
    if (taskStarted) {
      const finishTaskMessage = JSON.stringify({
        header: {
          action: 'finish-task',
          task_id: taskId,
          streaming: 'duplex'
        },
        payload: {
          input: {}
        }
      });
      ws.send(finishTaskMessage);
      console.log('Instruksi finish-task terkirim');
    }
  }, texts.length * 1000 + 1000); // Kirim 1 detik setelah continue-task terakhir
}

ws.on('close', () => {
  console.log('Terputus dari server WebSocket');
});

Java

Jika Anda menggunakan Java, kami merekomendasikan menggunakan SDK DashScope Java. Lihat SDK Java.

Berikut adalah contoh penggunaan WebSocket Java. Sebelum menjalankan, impor dependensi berikut:

  • Java-WebSocket

  • jackson-databind

Kami merekomendasikan mengelola dependensi dengan Maven atau Gradle. Contoh konfigurasi:

pom.xml

<dependencies>
    <!-- Klien WebSocket -->
    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>1.5.3</version>
    </dependency>

    <!-- Pemrosesan JSON -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.0</version>
    </dependency>
</dependencies>

build.gradle

// Abaikan kode lainnya
dependencies {
  // Klien WebSocket
  implementation 'org.java-websocket:Java-WebSocket:1.5.3'
  // Pemrosesan JSON
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}
// Abaikan kode lainnya

Kode Java:

import com.fasterxml.jackson.databind.ObjectMapper;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.*;

public class TTSWebSocketClient extends WebSocketClient {
    private final String taskId = UUID.randomUUID().toString();
    private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3";
    private boolean taskFinished = false;

    public TTSWebSocketClient(URI serverUri, Map<String, String> headers) {
        super(serverUri, headers);
    }

    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        System.out.println("Koneksi berhasil");

        // Kirimkan instruksi run-task
        // Jika enable_ssml true, hanya satu instruksi continue-task yang diizinkan. Jika tidak, akan mengembalikan “Text request limit violated, expected 1.”
        String runTaskCommand = "{ \"header\": { \"action\": \"run-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"task_group\": \"audio\", \"task\": \"tts\", \"function\": \"SpeechSynthesizer\", \"model\": \"cosyvoice-v3-flash\", \"parameters\": { \"text_type\": \"PlainText\", \"voice\": \"longanyang\", \"format\": \"mp3\", \"sample_rate\": 22050, \"volume\": 50, \"rate\": 1, \"pitch\": 1, \"enable_ssml\": false }, \"input\": {} }}";
        send(runTaskCommand);
    }

    @Override
    public void onMessage(String message) {
        System.out.println("Menerima pesan server: " + message);
        try {
            // Parsing pesan JSON
            Map<String, Object> messageMap = new ObjectMapper().readValue(message, Map.class);

            if (messageMap.containsKey("header")) {
                Map<String, Object> header = (Map<String, Object>) messageMap.get("header");

                if (header.containsKey("event")) {
                    String event = (String) header.get("event");

                    if ("task-started".equals(event)) {
                        System.out.println("Menerima event task-started");

                        List<String> texts = Arrays.asList(
                                "Before my bed, moonlight shines bright, I suspect it's frost upon the ground",
                                "I raise my eyes to gaze at the bright moon, then bow my head, thinking of home"
                        );

                        for (String text : texts) {
                            // Kirimkan instruksi continue-task
                            sendContinueTask(text);
                        }

                        // Kirimkan instruksi finish-task
                        sendFinishTask();
                    } else if ("task-finished".equals(event)) {
                        System.out.println("Menerima event task-finished");
                        taskFinished = true;
                        closeConnection();
                    } else if ("task-failed".equals(event)) {
                        System.out.println("Tugas gagal: " + message);
                        closeConnection();
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("Terjadi pengecualian: " + e.getMessage());
        }
    }

    @Override
    public void onMessage(ByteBuffer message) {
        System.out.println("Menerima ukuran data audio biner: " + message.remaining());

        try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
            byte[] buffer = new byte[message.remaining()];
            message.get(buffer);
            fos.write(buffer);
            System.out.println("Data audio ditulis ke file lokal " + outputFile);
        } catch (IOException e) {
            System.err.println("Gagal menulis data audio ke file lokal: " + e.getMessage());
        }
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        System.out.println("Koneksi ditutup: " + reason + " (" + code + ")");
    }

    @Override
    public void onError(Exception ex) {
        System.err.println("Kesalahan: " + ex.getMessage());
        ex.printStackTrace();
    }

    private void sendContinueTask(String text) {
        String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}";
        send(command);
    }

    private void sendFinishTask() {
        String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}";
        send(command);
    }

    private void closeConnection() {
        if (!isClosed()) {
            close();
        }
    }

    public static void main(String[] args) {
        try {
            // Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
            // Jika tidak ada variabel lingkungan yang diatur, ganti baris berikut dengan: String apiKey = "sk-xxx"
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
            if (apiKey == null || apiKey.isEmpty()) {
                System.err.println("Atur variabel lingkungan DASHSCOPE_API_KEY");
                return;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "bearer " + apiKey);
            // Gunakan URL ini untuk wilayah Singapura. Untuk wilayah Beijing, ganti dengan: wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/
            TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/"), headers);

            client.connect();

            while (!client.isClosed() && !client.taskFinished) {
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.err.println("Gagal terhubung ke layanan WebSocket: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Python

Jika Anda menggunakan Python, kami merekomendasikan menggunakan SDK DashScope Python. Lihat SDK Python.

Berikut adalah contoh penggunaan WebSocket Python. Sebelum menjalankan, instal dependensi:

pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
Penting

Jangan beri nama skrip Python Anda “websocket.py”, karena akan menyebabkan AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?

import websocket
import json
import uuid
import os
import time


class TTSClient:
    def __init__(self, api_key, uri):
        """
    Inisialisasi instance TTSClient

    Parameter:
        api_key (str): Kunci API untuk otentikasi
        uri (str): Alamat layanan WebSocket
    """
        self.api_key = api_key  # Ganti dengan Kunci API Anda
        self.uri = uri  # Ganti dengan alamat WebSocket Anda
        self.task_id = str(uuid.uuid4())  # Hasilkan ID tugas unik
        self.output_file = f"output_{int(time.time())}.mp3"  # Jalur file audio output
        self.ws = None  # Instance WebSocketApp
        self.task_started = False  # Apakah task-started telah diterima
        self.task_finished = False  # Apakah task-finished atau task-failed telah diterima

    def on_open(self, ws):
        """
    Callback saat koneksi WebSocket terbuka
    Kirimkan instruksi run-task untuk memulai sintesis suara
    """
        print("WebSocket terhubung")

        # Bangun instruksi run-task
        run_task_cmd = {
            "header": {
                "action": "run-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "task_group": "audio",
                "task": "tts",
                "function": "SpeechSynthesizer",
                "model": "cosyvoice-v3-flash",
                "parameters": {
                    "text_type": "PlainText",
                    "voice": "longanyang",
                    "format": "mp3",
                    "sample_rate": 22050,
                    "volume": 50,
                    "rate": 1,
                    "pitch": 1,
                    # Jika enable_ssml True, hanya satu instruksi continue-task yang diizinkan. Jika tidak, akan mengembalikan “Text request limit violated, expected 1.”
                    "enable_ssml": False
                },
                "input": {}
            }
        }

        # Kirimkan instruksi run-task
        ws.send(json.dumps(run_task_cmd))
        print("Instruksi run-task terkirim")

    def on_message(self, ws, message):
        """
    Callback saat pesan diterima
    Tangani pesan teks dan biner secara terpisah
    """
        if isinstance(message, str):
            # Tangani pesan teks JSON
            try:
                msg_json = json.loads(message)
                print(f"Pesan JSON diterima: {msg_json}")

                if "header" in msg_json:
                    header = msg_json["header"]

                    if "event" in header:
                        event = header["event"]

                        if event == "task-started":
                            print("Tugas dimulai")
                            self.task_started = True

                            # Kirimkan instruksi continue-task
                            texts = [
                                "Before my bed, moonlight shines bright, I suspect it's frost upon the ground",
                                "I raise my eyes to gaze at the bright moon, then bow my head, thinking of home"
                            ]

                            for text in texts:
                                self.send_continue_task(text)

                            # Kirimkan finish-task setelah semua instruksi continue-task
                            self.send_finish_task()

                        elif event == "task-finished":
                            print("Tugas selesai")
                            self.task_finished = True
                            self.close(ws)

                        elif event == "task-failed":
                            error_msg = msg_json.get("error_message", "Kesalahan tidak dikenal")
                            print(f"Tugas gagal: {error_msg}")
                            self.task_finished = True
                            self.close(ws)

            except json.JSONDecodeError as e:
                print(f"Parsing JSON gagal: {e}")
        else:
            # Tangani pesan biner (data audio)
            print(f"Pesan biner diterima, ukuran: {len(message)} byte")
            with open(self.output_file, "ab") as f:
                f.write(message)
            print(f"Data audio ditulis ke file lokal {self.output_file}")

    def on_error(self, ws, error):
        """Callback saat terjadi kesalahan"""
        print(f"Kesalahan WebSocket: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        """Callback saat ditutup"""
        print(f"WebSocket ditutup: {close_msg} ({close_status_code})")

    def send_continue_task(self, text):
        """Kirimkan instruksi continue-task dengan teks untuk disintesis"""
        cmd = {
            "header": {
                "action": "continue-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "input": {
                    "text": text
                }
            }
        }

        self.ws.send(json.dumps(cmd))
        print(f"Instruksi continue-task terkirim, teks: {text}")

    def send_finish_task(self):
        """Kirimkan instruksi finish-task untuk mengakhiri sintesis suara"""
        cmd = {
            "header": {
                "action": "finish-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "input": {}
            }
        }

        self.ws.send(json.dumps(cmd))
        print("Instruksi finish-task terkirim")

    def close(self, ws):
        """Tutup koneksi secara manual"""
        if ws and ws.sock and ws.sock.connected:
            ws.close()
            print("Koneksi ditutup secara manual")

    def run(self):
        """Mulai klien WebSocket"""
        # Atur header permintaan (otentikasi)
        header = {
            "Authorization": f"bearer {self.api_key}",
            "X-DashScope-DataInspection": "enable"
        }

        # Buat instance WebSocketApp
        self.ws = websocket.WebSocketApp(
            self.uri,
            header=header,
            on_open=self.on_open,
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close
        )

        print("Mendengarkan pesan WebSocket...")
        self.ws.run_forever()  # Mulai koneksi jangka panjang


# Contoh penggunaan
if __name__ == "__main__":
    # Dapatkan Kunci API: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    # Jika tidak ada variabel lingkungan yang diatur, ganti baris berikut dengan: API_KEY = "sk-xxx"
    API_KEY = os.environ.get("DASHSCOPE_API_KEY")
    # Gunakan URL ini untuk wilayah Singapura. Untuk wilayah Beijing, ganti dengan: wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/
    SERVER_URI = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/"  # Ganti dengan alamat WebSocket Anda

    client = TTSClient(API_KEY, SERVER_URI)
    client.run()

Kode kesalahan

Jika terjadi kesalahan, lihat Pesan kesalahan untuk pemecahan masalah.

FAQ

Fitur, penagihan, dan pembatasan laju

T: Apa yang bisa saya lakukan jika pengucapan tidak akurat?

Gunakan SSML untuk menyesuaikan efek sintesis suara.

T: Mengapa menggunakan WebSocket alih-alih HTTP/HTTPS? Mengapa tidak menyediakan API RESTful?

Layanan Ucapan menggunakan WebSocket alih-alih HTTP/HTTPS atau API RESTful karena memerlukan komunikasi full-duplex. WebSocket memungkinkan server dan klien mendorong data secara proaktif, seperti pembaruan progres real-time untuk sintesis atau pengenalan. API RESTful melalui HTTP hanya mendukung siklus permintaan-respons yang diprakarsai klien dan tidak dapat memenuhi persyaratan interaksi real-time.

T: Sintesis suara ditagih per karakter. Bagaimana cara memeriksa atau mengambil jumlah karakter untuk setiap sintesis?

Anda dapat memperoleh jumlah karakter dari parameter payload.usage.characters dalam event result-generated server. Gunakan nilai dari event result-generated terakhir yang diterima.

Pemecahan masalah

Penting

Jika kode Anda melemparkan kesalahan, periksa apakah instruksi yang dikirimkan ke server benar. Cetak instruksi tersebut dan verifikasi format serta bidang yang diperlukan. Jika instruksi benar, lihat kode kesalahan untuk diagnosis lebih lanjut.

T: Bagaimana cara mendapatkan Request ID?

Anda dapat memperolehnya dengan dua cara:

T: Mengapa SSML gagal?

Pecahkan masalah ini langkah demi langkah:

  1. Pastikan Anda mengikuti dengan benar batasan dan kendala.

  2. Pastikan Anda memanggil SSML dengan benar. Untuk informasi selengkapnya, lihat Dukungan bahasa markup SSML.

  3. Pastikan teks Anda adalah teks biasa dan memenuhi persyaratan format. Untuk informasi selengkapnya, lihat Ikhtisar bahasa markup SSML.

T: Mengapa audio tidak dapat diputar?

Periksa skenario berikut satu per satu:

  1. Audio disimpan sebagai file lengkap (seperti xx.mp3).

    1. Konsistensi format audio: Pastikan format audio yang diatur dalam parameter permintaan sesuai dengan ekstensi file. Misalnya, mengatur format WAV tetapi menyimpan dengan ekstensi .mp3 menyebabkan pemutaran gagal.

    2. Kompatibilitas pemutar: Verifikasi bahwa pemutar Anda mendukung format dan laju sampel file audio. Beberapa pemutar mungkin tidak mendukung laju sampel tinggi atau pengkodean audio tertentu.

  2. Audio diputar dalam aliran.

    1. Simpan aliran audio sebagai file lengkap dan coba putar dengan pemutar. Jika file tidak dapat diputar, lihat metode pemecahan masalah untuk skenario 1.

    2. Jika file diputar secara normal, masalahnya mungkin pada implementasi pemutaran aliran Anda. Verifikasi bahwa pemutar Anda mendukung pemutaran aliran.

      Alat dan pustaka umum yang mendukung pemutaran aliran meliputi FFmpeg, PyAudio (Python), AudioFormat (Java), dan MediaSource (JavaScript).

T: Mengapa pemutaran audio tersendat?

Periksa skenario berikut satu per satu:

  1. Periksa kecepatan pengiriman teks: Pastikan interval antara segmen teks masuk akal. Hindari situasi di mana segmen berikutnya tidak dikirim tepat waktu setelah segmen audio sebelumnya selesai diputar.

  2. Periksa kinerja fungsi callback:

    • Periksa apakah ada terlalu banyak logika bisnis dalam fungsi callback, yang dapat menyebabkan pemblokiran.

    • Fungsi callback berjalan di thread WebSocket. Jika diblokir, WebSocket mungkin gagal menerima paket jaringan tepat waktu, menyebabkan pemutaran audio tersendat.

    • Kami merekomendasikan menulis data audio ke buffer terpisah dan memprosesnya di thread lain untuk menghindari pemblokiran thread WebSocket.

  3. Periksa stabilitas jaringan: Pastikan koneksi jaringan Anda stabil untuk menghindari gangguan atau penundaan transmisi audio akibat fluktuasi jaringan.

T: Mengapa sintesis suara memakan waktu lama?

Ikuti langkah-langkah berikut untuk memecahkan masalah:

  1. Periksa interval input

    Periksa interval input. Jika Anda menggunakan sintesis ucapan aliran, verifikasi apakah interval antara pengiriman segmen teks terlalu lama (misalnya, penundaan beberapa detik). Interval yang lama meningkatkan total waktu sintesis.

  2. Analisis metrik kinerja.

    • Latensi paket pertama: Biasanya sekitar 500 ms.

    • RTF (RTF = Total waktu sintesis / Durasi audio): Biasanya kurang dari 1,0.

T: Bagaimana cara menangani pengucapan yang salah dalam ucapan hasil sintesis?

Gunakan tag <phoneme> SSML untuk menentukan pengucapan yang benar.

T: Mengapa tidak ada audio yang dikembalikan? Mengapa bagian akhir teks hilang dari audio? (Audio hilang)

Konfirmasi bahwa Anda tidak lupa mengirimkan instruksi finish-task. Selama sintesis, server menunggu hingga menyimpan cukup teks sebelum memulai. Jika Anda lupa mengirimkan finish-task, teks akhir dalam cache mungkin tidak pernah dikonversi menjadi audio.

T: Mengapa urutan aliran audio acak, menyebabkan pemutaran kacau?

Pecahkan masalah ini di dua area:

T: Bagaimana cara menangani kesalahan koneksi WebSocket?

  • Bagaimana cara menangani penutupan koneksi WebSocket (kode 1007)?

    Koneksi WebSocket ditutup segera setelah mengirimkan instruksi run-task, dengan kode penutupan 1007.

    • Akar penyebab: Server mendeteksi kesalahan protokol atau format data dan memutus koneksi. Alasan umum meliputi berikut ini:

      • Bidang tidak valid dalam payload run-task, seperti menambahkan bidang selain "input": {}.

      • Kesalahan format JSON, seperti koma yang hilang atau tanda kurung yang tidak cocok.

      • Bidang wajib hilang, seperti task_id atau action.

    • Solusi:

      1. Validasi format JSON: Periksa sintaks badan permintaan.

      2. Verifikasi bidang wajib: Konfirmasi bahwa header.action, header.task_id, header.streaming, payload.task_group, payload.task, payload.function, payload.model, dan payload.input semuanya diatur.

      3. Hapus bidang tidak valid: Dalam payload.input run-task, izinkan hanya objek kosong {} atau bidang teks. Jangan tambahkan bidang lain.

  • Bagaimana cara menangani WebSocketBadStatus, 401 Unauthorized, atau 403 Forbidden?

    Koneksi WebSocket gagal dengan WebSocketBadStatus, 401 Unauthorized, atau 403 Forbidden.

    • Akar penyebab: Kegagalan otentikasi. Server memvalidasi header Authorization selama jabat tangan WebSocket. Kunci API yang tidak valid atau hilang memicu penolakan.

    • Solusi: Untuk informasi selengkapnya, lihat Pemecahan masalah kegagalan otentikasi.

Izin dan otentikasi

T: Bagaimana cara membatasi Kunci API saya hanya untuk layanan sintesis suara CosyVoice saja (isolasi izin)?

Buat ruang kerja dan berikan otorisasi hanya ke model tertentu untuk membatasi cakupan Kunci API. Untuk informasi selengkapnya, lihat Kelola ruang kerja.

Pertanyaan lainnya

Lihat QA di GitHub.