全部产品
Search
文档中心

Alibaba Cloud Model Studio:API WebSocket sintesis suara CosyVoice

更新时间:Jan 21, 2026
Penting

Untuk menggunakan model di wilayah China (Beijing), buka halaman Kunci API untuk wilayah China (Beijing).

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

SDK DashScope saat ini hanya mendukung Java dan Python. Jika Anda ingin mengembangkan aplikasi sintesis suara CosyVoice dalam bahasa pemrograman lain, Anda dapat berkomunikasi dengan layanan melalui koneksi WebSocket.

Panduan pengguna: Untuk informasi lebih lanjut tentang model dan panduan pemilihan model, lihat Sintesis suara Real-time - CosyVoice.

WebSocket adalah protokol jaringan yang mendukung komunikasi full-duplex. Klien dan server membangun koneksi persisten melalui satu kali handshake, memungkinkan kedua pihak saling mendorong data secara aktif. Hal ini memberikan keunggulan signifikan dalam performa real-time dan efisiensi.

Untuk bahasa pemrograman umum, tersedia banyak library WebSocket siap pakai beserta contohnya, seperti:

  • Go: gorilla/websocket

  • PHP: Ratchet

  • Node.js: ws

Pahami prinsip dasar dan detail teknis WebSocket sebelum memulai pengembangan.

Prasyarat

Anda telah mengaktifkan Model Studio dan membuat Kunci API. Untuk mencegah risiko keamanan, ekspor Kunci API sebagai Variabel lingkungan alih-alih menyematkannya langsung di kode Anda.

Catatan

Untuk memberikan izin akses sementara kepada aplikasi atau pengguna pihak ketiga, atau jika Anda ingin mengontrol secara ketat operasi berisiko tinggi seperti mengakses atau menghapus data sensitif, kami menyarankan Anda menggunakan Token otentikasi sementara.

Dibandingkan dengan Kunci API jangka panjang, token otentikasi sementara lebih aman karena masa berlakunya singkat (60 detik). Token ini cocok untuk skenario panggilan sementara dan secara efektif mengurangi risiko kebocoran Kunci API.

Untuk menggunakan token sementara, gantilah Kunci API yang digunakan untuk otentikasi di kode Anda dengan token otentikasi sementara tersebut.

Model dan harga

Model

Harga

Kuota gratis (Catatan)

cosyvoice-v3-plus

$0,286706 per 10.000 karakter

Tidak ada kuota gratis

cosyvoice-v3-flash

$0,14335 per 10.000 karakter

cosyvoice-v2

$0,286706 per 10.000 karakter

Batasan teks dan format

Batas panjang teks

Teks yang dikirim dalam satu instruksi continue-task tidak boleh melebihi 20.000 karakter. Panjang total teks yang dikirim melalui beberapa instruksi continue-task tidak boleh melebihi 200.000 karakter.

Aturan penghitungan karakter

  • Satu karakter Tionghoa—termasuk Tionghoa sederhana atau tradisional, kanji Jepang, dan hanja Korea—dihitung sebagai 2 karakter. Semua karakter lain, seperti tanda baca, huruf, angka, serta kana atau hangul Jepang/Korea, dihitung sebagai 1 karakter.

  • Tag SSML tidak termasuk dalam perhitungan panjang teks.

  • Contoh:

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

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

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

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

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

Format encoding

Gunakan encoding UTF-8.

Dukungan ekspresi matematis

Fitur parsing ekspresi matematis saat ini hanya tersedia untuk model cosyvoice-v2, cosyvoice-v3-flash, dan cosyvoice-v3-plus. Fitur ini mendukung ekspresi matematis umum dari sekolah dasar dan menengah, seperti aritmetika dasar, aljabar, dan geometri.

Lihat Formula LaTeX ke Ucapan.

Dukungan SSML

Fitur Bahasa Markup Sintesis Ucapan (SSML) saat ini hanya tersedia untuk suara kloning model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta suara sistem yang ditandai sebagai didukung dalam daftar suara. Syarat-syarat berikut harus dipenuhi:

Untuk menggunakan fitur ini:

  1. Saat mengirim instruksi run-task, atur parameter enable_ssml ke true.

  2. Gunakan instruksi continue-task untuk mengirim teks yang berisi SSML.

Penting

Saat dukungan SSML diaktifkan dengan mengatur parameter enable_ssml ke true, Anda harus mengirim seluruh teks sintesis dalam satu instruksi continue-task. Anda tidak dapat mengirim teks tersebut dalam beberapa bagian.

Alur interaksi

image

Klien mengirim pesan yang disebut instruksi ke server. Server mengembalikan dua jenis pesan ke klien: event dalam format JSON dan aliran audio biner.

Alur interaksi antara klien dan server adalah sebagai berikut:

  1. Membangun koneksi: Klien membangun koneksi WebSocket dengan server.

  2. Memulai tugas:

    • Klien mengirim instruksi run-task untuk memulai tugas.

    • Server mengembalikan event task-started, yang menunjukkan bahwa tugas telah berhasil dimulai dan Anda dapat melanjutkan ke langkah berikutnya.

  3. Mengirim teks untuk sintesis:

    Klien mengirim teks yang akan disintesis ke server dalam satu atau beberapa instruksi continue-task secara berurutan. Setelah server menerima satu pernyataan lengkap, server mengembalikan event result-generated dan aliran audio. Panjang teks dibatasi. Untuk informasi lebih lanjut, lihat deskripsi field text dalam instruksi continue-task.

    Catatan

    Anda dapat mengirim instruksi continue-task beberapa kali untuk mengirim segmen teks secara berurutan. Server menerima segmen teks tersebut dan secara otomatis membaginya menjadi kalimat:

    • Server segera mensintesis kalimat lengkap dan mengirimkan audio yang sesuai ke klien.

    • Server menyimpan cache kalimat yang belum lengkap dan hanya mensintesisnya setelah kalimat tersebut lengkap. Tidak ada audio yang dikembalikan untuk kalimat yang belum lengkap.

    Saat Anda mengirim instruksi finish-task, server secara paksa mensintesis semua konten yang tersimpan di cache.

  4. Memberi tahu server untuk mengakhiri tugas:

    Setelah semua teks dikirim, klien mengirim instruksi finish-task ke server dan terus menerima aliran audio. Catatan: Jangan lewatkan langkah ini. Jika tidak, Anda mungkin tidak menerima aliran audio lengkap.

  5. Mengakhiri tugas:

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

  6. Menutup koneksi: Klien menutup koneksi WebSocket.

URL

URL WebSocket tetap:

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

Header

Tambahkan informasi berikut ke header permintaan:

{
    "Authorization": "bearer <your_dashscope_api_key>", // Wajib. Ganti <your_dashscope_api_key> dengan Kunci API Anda sendiri.
    "user-agent": "your_platform_info", // Opsional.
    "X-DashScope-WorkSpace": workspace, // Opsional. ID ruang kerja Alibaba Cloud Model Studio.
    "X-DashScope-DataInspection": "enable"
}

Instruksi (klien → server)

Instruksi adalah pesan yang dikirim klien ke server. Instruksi ini dalam format JSON dan dikirim sebagai Text Frame untuk memulai dan mengakhiri tugas, serta mengidentifikasi batas tugas.

Anda harus mengikuti urutan ini saat mengirim instruksi. Jika tidak, tugas mungkin gagal:

  1. Kirim instruksi jalankan tugas

    • Memulai tugas sintesis suara.

    • task_id yang dikembalikan diperlukan untuk instruksi continue-task dan finish-task berikutnya. Anda harus menggunakan task_id yang sama untuk semua instruksi dalam satu tugas.

  2. Kirim instruksi continue-task

    • Mengirim teks untuk sintesis.

    • Anda hanya dapat mengirim instruksi ini setelah menerima event task-started dari server.

  3. Kirim instruksi finish-task

    • Mengakhiri tugas sintesis suara.

    • Anda harus mengirim instruksi ini setelah mengirim semua instruksi continue-task.

1. Instruksi run-task: Memulai tugas

Memulai tugas sintesis suara. Atur parameter permintaan, seperti suara dan laju sampel, dalam instruksi ini.

Penting
  • Kapan mengirim: Setelah koneksi WebSocket dibangun.

  • Jangan kirim teks untuk sintesis: Untuk menyederhanakan troubleshooting, jangan sertakan teks dalam instruksi ini.

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
            "pitch": 1				// Pitch
        },
        "input": {// Field input tidak boleh dihilangkan. Jika tidak, terjadi error.
        }
    }
}

Header parameter:

Parameter

Tipe

Wajib

Deskripsi

header.action

string

Ya

Jenis instruksi.

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

header.task_id

string

Ya

ID tugas saat ini.

UUID (Universally Unique Identifier) 32-bit yang terdiri dari 32 huruf dan angka yang dihasilkan secara acak. Bisa mencakup tanda hubung (seperti "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx") atau tidak (seperti "2bf83b9abaeb4fda8d9axxxxxxxxxxxx"). Sebagian besar bahasa pemrograman memiliki API bawaan untuk menghasilkan UUID. Contohnya, dalam Python:

import uuid

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

Saat Anda kemudian mengirim instruksi continue-task dan instruksi finish-task, Anda harus menggunakan task_id yang sama dengan yang digunakan untuk instruksi run-task.

header.streaming

string

Ya

String tetap: "duplex"

Parameter payload:

Parameter

Type

Wajib

Deskripsi

payload.task_group

string

Ya

String tetap: "audio".

payload.task

string

Ya

String tetap: "tts".

payload.function

string

Ya

String tetap: "SpeechSynthesizer".

payload.model

string

Ya

model sintesis suara.

Model yang berbeda memerlukan suara yang sesuai:

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

  • cosyvoice-v2: Gunakan suara seperti longxiaochun_v2.

  • Untuk daftar lengkap, lihat Daftar suara.

payload.input

object

Ya

  • Jika Anda tidak mengirim teks untuk sintesis saat ini, format input adalah:

    "input": {}
  • Jika Anda mengirim teks untuk sintesis saat ini, format input adalah:

    "input": {
      "text": "Seperti apa cuaca hari ini?" // Teks untuk disintesis
    }

payload.parameters

text_type

string

Ya

String tetap: "PlainText".

voice

string

Ya

Suara yang digunakan untuk sintesis suara.

Didukung suara sistem dan suara kloning:

  • Suara sistem: Lihat Daftar suara.

  • Suara kloning: Sesuaikan suara menggunakan fitur kloning suara. Saat menggunakan suara kloning, pastikan akun yang sama digunakan untuk kloning suara dan sintesis suara. Untuk langkah-langkah detail, lihat API kloning suara CosyVoice.

    Saat menggunakan suara kloning, nilai parameter model dalam permintaan harus persis sama dengan versi model yang digunakan untuk membuat suara tersebut (parameter target_model).

format

string

Tidak

Format encoding audio.

Format yang didukung adalah pcm, wav, mp3 (default), dan opus.

Saat format audio adalah opus, Anda dapat menyesuaikan bitrate menggunakan parameter bit_rate.

sample_rate

integer

Tidak

Laju pengambilan sampel audio dalam Hz.

Nilai default: 22050.

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

Catatan

Laju sampel default adalah laju optimal untuk suara saat ini. Secara default, output menggunakan laju sampel ini. Downsampling dan upsampling juga didukung.

volume

integer

Tidak

Volume.

Nilai default: 50.

Rentang nilai: [0, 100]. Nilai 50 adalah volume standar. Volume memiliki hubungan linear dengan nilai ini. 0 berarti senyap dan 100 adalah volume maksimum.

rate

float

Tidak

Laju ucapan.

Nilai default: 1,0.

Rentang nilai: [0,5, 2,0]. Nilai 1,0 adalah laju standar. Nilai kurang dari 1,0 memperlambat ucapan, dan nilai lebih dari 1,0 mempercepatnya.

pitch

float

Tidak

Pitch. Nilai ini adalah pengali untuk penyesuaian pitch. Hubungan antara nilai ini dan pitch yang dirasakan tidak sepenuhnya linear atau logaritmik. Uji nilai-nilai berbeda untuk menemukan yang terbaik.

Nilai default: 1,0.

Rentang nilai: [0,5, 2,0]. Nilai 1,0 adalah pitch alami suara. Nilai lebih dari 1,0 meningkatkan pitch, dan nilai kurang dari 1,0 menurunkannya.

enable_ssml

boolean

Tidak

Menentukan apakah akan mengaktifkan fitur SSML.

Jika parameter ini diatur ke true, Anda hanya dapat mengirim teks satu kali (Anda hanya dapat mengirim instruksi continue-task satu kali).

bit_rate

int

Tidak

Bitrate audio dalam kbps. Jika format audio adalah Opus, Anda dapat menyesuaikan bitrate menggunakan parameter bit_rate.

Nilai default: 32.

Rentang nilai: [6, 510].

word_timestamp_enabled

boolean

Tidak

Menentukan apakah akan mengaktifkan timestamp tingkat karakter.

Nilai default: false.

  • true

  • false

Fitur ini hanya tersedia untuk suara kloning model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta suara sistem yang ditandai sebagai didukung dalam daftar suara.

seed

int

Tidak

Bilangan acak seed yang digunakan selama generasi, yang memvariasikan efek sintesis. Jika versi model, teks, suara, dan parameter lainnya sama, menggunakan seed yang sama akan mereproduksi hasil sintesis yang sama.

Nilai default: 0.

Rentang nilai: [0, 65535].

language_hints

array[string]

Tidak

Menentukan bahasa target untuk sintesis suara guna meningkatkan efek sintesis.

Gunakan parameter ini saat pelafalan angka, singkatan, atau simbol, atau saat efek sintesis untuk bahasa non-Tionghoa tidak sesuai harapan.

Nilai yang valid:

  • zh: Tionghoa

  • en: Inggris

  • fr: Prancis

  • de: Jerman

  • ja: Jepang

  • ko: Korea

  • ru: Rusia

Catatan: Meskipun parameter ini berupa array, versi saat ini hanya memproses elemen pertama. Oleh karena itu, Anda harus hanya mengirim satu nilai.

Penting

Parameter ini menentukan bahasa target untuk sintesis suara. Pengaturan ini independen dari bahasa audio sampel yang digunakan untuk kloning suara. Untuk mengatur bahasa sumber untuk tugas kloning suara, lihat API kloning suara CosyVoice.

instruction

string

Tidak

Atur instruksi: Fitur ini hanya tersedia untuk suara kloning model cosyvoice-v3-flash dan cosyvoice-v3-plus, serta suara sistem yang ditandai sebagai didukung dalam Daftar Suara.

Tidak ada nilai default. Parameter ini tidak berpengaruh jika tidak diatur.

Sintesis suara memiliki efek berikut:

  1. Menentukan dialek (hanya untuk suara kloning)

    • Format: "请用<方言>表达。" (Catatan: Jangan menghilangkan titik (。) di akhir. Ganti <方言> dengan 方言 tertentu, seperti 广东话.)

    • Contoh: "请用广东话表达。"

    • Dialek yang didukung: 广东话 (Kanton), 东北话 (Dongbei), 甘肃话 (Gansu), 贵州话 (Guizhou), 河南话 (Henan), 湖北话 (Hubei), 江西话 (Jiangxi), 闽南话 (Minnan), 宁夏话 (Ningxia), 山西话 (Shanxi), 陕西话 (Shaanxi), 山东话 (Shandong), 上海话 (Shanghainese), 四川话 (Sichuan), 天津话 (Tianjin), dan 云南话 (Yunnan).

  2. Menentukan emosi, skenario, peran, atau identitas. Hanya beberapa suara sistem yang mendukung fitur ini, dan bervariasi tergantung suaranya. Lihat Daftar suara.

enable_aigc_tag

boolean

Tidak

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

Nilai default: false.

Fitur ini hanya tersedia untuk model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2.

aigc_propagator

string

Tidak

Mengatur field ContentPropagator dalam identifier AIGC tak terlihat untuk menentukan propagator konten. Pengaturan ini hanya berlaku saat enable_aigc_tag diatur ke true.

Nilai default: UID Alibaba Cloud.

Fitur ini hanya tersedia untuk model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2.

aigc_propagate_id

string

Tidak

Mengatur field PropagateID dalam identifier AIGC tak terlihat untuk mengidentifikasi secara unik perilaku propagasi tertentu. Field ini hanya berlaku saat enable_aigc_tag diatur ke true.

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

Fitur ini hanya tersedia untuk model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2.

2. Instruksi continue-task

Instruksi ini mengirim teks untuk sintesis.

Kirim seluruh teks dalam satu instruksi continue-task, atau bagi teks tersebut dan kirim secara berurutan dalam beberapa instruksi continue-task.

Penting

Kapan mengirim: Kirim instruksi ini setelah Anda menerima event task-started.

Catatan

Interval antara pengiriman segmen teks tidak boleh melebihi 23 detik. Jika tidak, pengecualian "request timeout after 23 seconds" akan dipicu.

Jika Anda tidak memiliki teks lagi untuk dikirim, kirim instruksi finish-task untuk mengakhiri tugas.

Server menerapkan mekanisme timeout 23 detik, yang tidak dapat dimodifikasi oleh klien.

Contoh:

{
    "header": {
        "action": "continue-task",
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx", // UUID acak
        "streaming": "duplex"
    },
    "payload": {
        "input": {
            "text": "Moonlight before my bed, could it be frost on the ground?"
        }
    }
}

Parameter header:

Parameter

Type

Wajib

Deskripsi

header.action

string

Ya

Jenis instruksi.

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

header.task_id

string

Ya

ID tugas saat ini.

Nilainya harus konsisten dengan task_id yang Anda gunakan untuk mengirim instruksi run-task.

header.streaming

string

Ya

String tetap: "duplex"

Parameter payload:

Parameter

Tipe

Wajib

Deskripsi

input.text

string

Ya

Teks untuk disintesis.

3. Instruksi finish-task: Mengakhiri Tugas

Mengakhiri tugas sintesis suara.

Anda harus mengirim instruksi ini. Jika tidak, ucapan yang disintesis mungkin tidak lengkap.

Setelah Anda mengirim instruksi ini, server mengonversi sisa teks menjadi ucapan. Saat sintesis selesai, server mengembalikan event task-finished ke klien.

Penting

Kapan mengirim: Kirim instruksi ini setelah Anda mengirim semua instruksi continue-task.

Contoh:

{
    "header": {
        "action": "finish-task",
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "streaming": "duplex"
    },
    "payload": {
        "input": {}//Field input tidak boleh dihilangkan. Jika tidak, terjadi error.
    }
}

Parameter header:

Parameter

Tipe

Wajib

Deskripsi

header.action

string

Ya

Jenis instruksi.

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

header.task_id

string

Ya

ID tugas saat ini.

Nilai ini harus sama dengan task_id yang digunakan saat mengirim instruksi run-task.

header.streaming

string

Ya

String tetap: "duplex"

Parameter payload:

Parameter

Tipe

Wajib

Deskripsi

payload.input

object

Ya

Format tetap: {}.

Event (server → klien)

Event adalah pesan yang dikirim dari server ke klien. Pesan-pesan ini dalam format JSON dan menunjukkan tahapan pemrosesan yang berbeda.

Catatan

Aliran audio biner yang dikirim dari server ke klien tidak termasuk dalam event apa pun dan harus diterima secara terpisah.

1. Event task-started: Tugas dimulai

Event task-started menunjukkan bahwa tugas telah berhasil dimulai. Anda harus menerima event ini sebelum mengirim instruksi continue-task atau instruksi finish-task ke server. Jika tidak, tugas akan gagal.

payload dari event task-started kosong.

Contoh:

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

Parameter header:

Parameter

Tipe

Deskripsi

header.event

string

Jenis event.

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

header.task_id

string

task_id yang dihasilkan oleh klien.

2. event berbasis hasil

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

Untuk membantu pengguna mengaitkan data audio dengan konten teks yang sesuai, server mengembalikan metadata kalimat dalam event result-generated saat mengembalikan data audio. Server secara otomatis membagi teks input pengguna. Proses sintesis untuk setiap kalimat mencakup tiga sub-event berikut:

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

  • sentence-synthesis: Menandai blok data audio. Segera setelah event ini, frame data audio dikirim melalui saluran biner WebSocket.

    • Beberapa event sentence-synthesis dihasilkan selama sintesis satu kalimat, masing-masing sesuai dengan blok data audio.

    • Klien perlu menerima blok data audio ini secara berurutan dan menuliskannya ke file yang sama dalam mode tambahan (append).

    • Ada korespondensi satu-satu antara event sentence-synthesis dan frame data audio berikutnya, memastikan tidak terjadi ketidaksesuaian.

  • sentence-end: Menandai akhir kalimat dan mengembalikan konten teks kalimat serta jumlah kumulatif karakter yang dikenai biaya.

Jenis sub-event ditunjukkan oleh field payload.output.type.

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": "Moonlight before my bed,"
        }
    }
}

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": "Moonlight before my bed,"
        },
        "usage": {
            "characters": 11
        }
    }
}

Parameter header:

Parameter

Type

Deskripsi

header.event

string

Jenis event.

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

header.task_id

string

task_id yang dihasilkan oleh klien.

header.attributes

object

Properti tambahan, biasanya berupa objek null.

Parameter payload:

Parameter

Type

Deskripsi

payload.output.type

string

Jenis sub-event.

Nilai yang valid:

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

  • sentence-synthesis: Menandai blok data audio. Segera setelah event ini, frame data audio dikirim melalui saluran biner WebSocket.

    • Beberapa event sentence-synthesis dihasilkan selama sintesis satu kalimat, masing-masing sesuai dengan blok data audio.

    • Klien perlu menerima blok data audio ini secara berurutan dan menuliskannya ke file yang sama dalam mode tambahan (append).

    • Ada korespondensi satu-satu antara event sentence-synthesis dan frame data audio berikutnya, memastikan tidak terjadi ketidaksesuaian.

  • sentence-end: Menandai akhir kalimat dan mengembalikan konten teks kalimat serta jumlah kumulatif karakter yang dikenai biaya.

Alur event lengkap

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

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

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

  3. sentence-end: Menandai akhir kalimat dan berisi konten teks kalimat serta jumlah kumulatif karakter yang dikenai biaya.

payload.output.sentence.index

integer

Nomor kalimat, dimulai dari 0.

payload.output.sentence.words

array

Array informasi tingkat kata, biasanya berupa array kosong.

payload.output.original_text

string

Konten kalimat setelah membagi teks input pengguna. Kalimat terakhir mungkin tidak memiliki field ini.

payload.usage.characters

integer

Jumlah kumulatif karakter yang dikenai biaya untuk permintaan saat ini. Dalam satu tugas, field usage dapat muncul dalam event result-generated atau event task-finished. Field usage yang dikembalikan merupakan nilai kumulatif. Nilai dari event terakhir adalah nilai akhir.

3. Event task-finished: Tugas selesai

Event task-finished dari server menunjukkan bahwa tugas telah selesai.

Setelah tugas selesai, Anda dapat menutup koneksi WebSocket untuk mengakhiri program atau menggunakan kembali koneksi tersebut dan mengirim ulang instruksi run-task untuk memulai tugas berikutnya. Untuk informasi lebih lanjut, lihat Overhead pembentukan koneksi dan penggunaan kembali koneksi.

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
        }
    }
}

Parameter header:

Parameter

Tipe

Deskripsi

header.event

string

Jenis event.

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

header.task_id

string

task_id yang dihasilkan oleh klien.

header.attributes.request_uuid

string

ID Permintaan. Anda dapat memberikan ID ini kepada pengembang CosyVoice untuk membantu menemukan masalah.

Parameter payload:

Parameter

Tipe

Deskripsi

payload.usage.characters

integer

Jumlah karakter yang dikenai biaya dalam permintaan saat ini hingga saat ini. Dalam satu tugas, field usage mungkin muncul dalam event result-generated atau event task-finished. Field usage yang dikembalikan merupakan hasil kumulatif, dan nilai akhir adalah nilai yang harus digunakan.

payload.output.sentence.index

integer

Nomor kalimat, dimulai dari 0.

Field ini dan field berikutnya memerlukan pengaktifan timestamp tingkat kata dengan `word_timestamp_enabled`.

payload.output.sentence.words[k]

text

string

Teks karakter.

begin_index

integer

Indeks awal karakter dalam kalimat, dimulai dari 0.

end_index

integer

Indeks akhir karakter dalam kalimat, dimulai dari 1.

begin_time

integer

Timestamp awal audio yang sesuai dengan karakter, dalam milidetik.

end_time

integer

Timestamp akhir audio yang sesuai dengan karakter, dalam milidetik.

Setelah Anda mengaktifkan timestamp tingkat kata menggunakan `word_timestamp_enabled`, respons akan mengembalikan informasi timestamp. Berikut adalah contohnya:

Klik untuk melihat respons setelah mengaktifkan timestamp tingkat kata

{
    "header": {
        "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
        "event": "task-finished",
        "attributes": {"request_uuid": "0a9dba9e-d3a6-45a4-be6d-xxxxxxxxxxxx"}
    },
    "payload": {
        "output": {
            "sentence": {
                "index": 0,
                "words": [
                    {
                        "text": "What",
                        "begin_index": 0,
                        "end_index": 1,
                        "begin_time": 80,
                        "end_time": 200
                    },
                    {
                        "text": "is",
                        "begin_index": 1,
                        "end_index": 2,
                        "begin_time": 240,
                        "end_time": 360
                    },
                    {
                        "text": "the",
                        "begin_index": 2,
                        "end_index": 3,
                        "begin_time": 360,
                        "end_time": 480
                    },
                    {
                        "text": "weather",
                        "begin_index": 3,
                        "end_index": 4,
                        "begin_time": 480,
                        "end_time": 680
                    },
                    {
                        "text": "like",
                        "begin_index": 4,
                        "end_index": 5,
                        "begin_time": 680,
                        "end_time": 800
                    },
                    {
                        "text": "to",
                        "begin_index": 5,
                        "end_index": 6,
                        "begin_time": 800,
                        "end_time": 920
                    },
                    {
                        "text": "day",
                        "begin_index": 6,
                        "end_index": 7,
                        "begin_time": 920,
                        "end_time": 1000
                    },
                    {
                        "text": "?",
                        "begin_index": 7,
                        "end_index": 8,
                        "begin_time": 1000,
                        "end_time": 1320
                    }
                ]
            }
        },
        "usage": {"characters": 15}
    }
}

4. Event task-failed: Tugas gagal

Event task-failed menunjukkan bahwa tugas telah gagal. Dalam kasus ini, Anda harus menutup koneksi WebSocket dan menangani error tersebut. Anda dapat menganalisis pesan error untuk mengidentifikasi dan memperbaiki masalah pemrograman.

Pernyataan 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 parameter:

Parameter

Tipe

Deskripsi

header.event

string

Jenis event.

Untuk event ini, nilainya tetap "task-failed".

header.task_id

string

task_id yang dihasilkan oleh klien.

header.error_code

string

Deskripsi jenis error.

header.error_message

string

Alasan spesifik terjadinya error.

Overhead koneksi dan penggunaan kembali

Layanan WebSocket mendukung penggunaan kembali koneksi untuk meningkatkan pemanfaatan resource dan menghindari overhead pembentukan koneksi.

Saat server menerima instruksi run-task dari klien, server memulai tugas baru. Klien kemudian mengirim instruksi finish-task, dan server mengembalikan event task-finished setelah tugas selesai. Setelah tugas selesai, koneksi WebSocket dapat digunakan kembali, memungkinkan klien memulai tugas berikutnya dengan mengirim instruksi run-task lagi.

Penting
  1. Tugas yang berbeda dalam koneksi yang digunakan kembali harus menggunakan `task_id` yang berbeda.

  2. Jika tugas gagal selama eksekusi, layanan mengembalikan event task-failed dan menutup koneksi. Koneksi tidak dapat digunakan kembali.

  3. Jika tidak ada tugas baru selama 60 detik setelah tugas selesai, koneksi akan timeout dan terputus secara otomatis.

Kode contoh

Kode contoh menyediakan implementasi dasar untuk menjalankan layanan. Anda perlu mengembangkan kode tersebut untuk skenario bisnis spesifik Anda.

Saat menulis kode klien WebSocket, pemrograman asinkron biasanya digunakan untuk mengirim dan menerima pesan secara bersamaan. Anda dapat menulis program dengan mengikuti langkah-langkah berikut:

  1. Membangun koneksi WebSocket

    Panggil fungsi library WebSocket dan berikan Header dan URL untuk membangun koneksi WebSocket. Implementasi spesifik bervariasi tergantung bahasa pemrograman atau library yang digunakan.

  2. Mendengarkan pesan dari server

    Dengarkan pesan dari server menggunakan fungsi callback yang disediakan oleh library WebSocket (pola observer). Implementasi spesifik bervariasi tergantung bahasa pemrograman.

    Server mengembalikan dua jenis pesan: aliran audio biner dan event.

    Pemantauan event

    • task-started: Event task-started menunjukkan bahwa tugas telah berhasil dimulai. Anda hanya dapat mengirim instruksi continue-task atau instruksi finish-task ke server setelah event ini dipicu. Jika tidak, tugas akan gagal.

    • result-generated: Saat klien mengirim instruksi continue-task atau instruksi finish-task, server mungkin berulang kali mengembalikan event result-generated.

    • task-finished: Saat Anda menerima event task-finished, itu menunjukkan bahwa tugas telah selesai. Anda kemudian dapat menutup koneksi WebSocket dan mengakhiri program.

    • task-failed: Jika Anda menerima event task-failed, itu menunjukkan bahwa tugas telah gagal. Anda perlu menutup koneksi WebSocket dan memperbaiki kode Anda berdasarkan pesan error.

    Memproses aliran audio biner: Server mengirim aliran audio dalam frame melalui saluran binary. Data audio lengkap dikirim dalam beberapa paket data.

    • Dalam sintesis suara streaming, untuk format terkompresi seperti MP3 dan Opus, gunakan pemutar streaming untuk memutar segmen audio. Jangan memutarnya frame demi frame untuk menghindari kegagalan decoding.

      Pemutar yang mendukung pemutaran streaming termasuk ffmpeg, pyaudio (Python), AudioFormat (Java), dan MediaSource (JavaScript).
    • Saat menggabungkan data audio menjadi file audio lengkap, tambahkan data tersebut ke file yang sama.

    • Untuk format audio WAV dan MP3 dalam sintesis suara streaming, hanya frame pertama yang berisi informasi header. Frame berikutnya hanya berisi data audio.

  3. Mengirim pesan ke server (waktu sangat penting)

    Kirim instruksi ke server pada thread yang berbeda dari thread yang mendengarkan pesan dari server (seperti thread utama). Implementasi spesifik bervariasi tergantung bahasa pemrograman.

    Anda harus mengikuti urutan ini saat mengirim instruksi. Jika tidak, tugas mungkin gagal:

    1. Kirim instruksi run-task

      • Memulai tugas sintesis suara.

      • task_id yang dikembalikan diperlukan untuk instruksi continue-task dan finish-task berikutnya. Anda harus menggunakan task_id yang sama untuk semua instruksi dalam satu tugas.

    2. Kirim instruksi continue-task

      • Mengirim teks untuk sintesis.

      • Anda hanya dapat mengirim instruksi ini setelah menerima event task-started dari server.

    3. Kirim instruksi finish-task

      • Mengakhiri tugas sintesis suara.

      • Anda harus mengirim instruksi ini setelah mengirim semua instruksi continue-task.

  4. Menutup koneksi WebSocket

    Tutup koneksi WebSocket saat program berhenti secara normal, terjadi pengecualian selama runtime, atau Anda menerima event task-finished atau event task-failed. Anda biasanya dapat melakukan ini dengan memanggil fungsi close dalam library utilitas.

Klik untuk melihat contoh lengkap

Go

package main

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

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

const (
	wsURL      = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/"
	outputFile = "output.mp3"
)

func main() {
	// Jika Anda belum mengonfigurasi Kunci API sebagai variabel lingkungan, Anda dapat mengganti baris berikut dengan: apiKey := "your_api_key". Kami tidak menyarankan menyematkan Kunci API secara langsung di kode Anda dalam lingkungan produksi untuk mengurangi risiko kebocoran Kunci API.
	apiKey := os.Getenv("DASHSCOPE_API_KEY")

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

	// Terhubung ke 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)

	// Kirim 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 diatur ke true, Anda hanya dapat mengirim instruksi continue-task satu kali. Jika tidak, error "Text request limit violated, expected 1." dilaporkan.
				"enable_ssml": false,
			},
			"input": map[string]interface{}{},
		},
	}

	runTaskJSON, _ := json.Marshal(runTaskCmd)
	fmt.Printf("Mengirim instruksi run-task: %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 {
							// Kirim instruksi continue-task

							texts := []string{"Moonlight before my bed, could it be frost on the ground?", "I look up to see the moon, then look down and think 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("Mengirim instruksi continue-task: %s\n", string(continueTaskJSON))

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

							textSent = true

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

							// Kirim 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("Mengirim instruksi finish-task: %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 error: %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 {
    // Jika Anda belum mengonfigurasi Kunci API sebagai variabel lingkungan, Anda dapat mengganti baris berikut dengan: private const string ApiKey="your_api_key". Kami tidak menyarankan menyematkan Kunci API secara langsung di kode Anda dalam lingkungan produksi untuk mengurangi risiko kebocoran Kunci API.
    private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("Variabel lingkungan DASHSCOPE_API_KEY belum diatur.");

    // Alamat server WebSocket
    private const string WebSocketUrl = "wss://dashscope.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;
    // Apakah tugas telah dimulai
    private static TaskCompletionSource<bool> _taskStartedTcs = new TaskCompletionSource<bool>();

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

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

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

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

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

            // Kirim instruksi continue-task secara berkelanjutan
            string[] texts = {
                "Moonlight before my bed",
                "could it be frost on the ground?",
                "I look up to see the moon",
                "then look down and think of home"
            };
            foreach (string text in texts) {
                await SendContinueTaskCommandAsync(text);
            }

            // Kirim 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($"Terjadi error: {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 dikosongkan.");
        } else {
            Console.WriteLine("File output tidak ada, tidak perlu dikosongkan.");
        }
    }

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

        // Atur header untuk koneksi 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 diatur ke true, Anda hanya dapat mengirim instruksi continue-task satu kali. Jika tidak, error "Text request limit violated, expected 1." dilaporkan.
                enable_ssml = false
            },
            input = new { }
        });

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

    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 dikirim.");
    }

    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 dikirim.");
    }

    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:
                        // result-generated dapat ditangani 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

Kode contoh memiliki struktur direktori sebagai berikut:

my-php-project/

├── composer.json

├── vendor/

└── index.php

Berikut adalah isi composer.json. Anda dapat menentukan nomor versi dependensi berdasarkan kebutuhan Anda:

{
    "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 adalah sebagai berikut:

<?php

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

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

// Jika Anda belum mengonfigurasi Kunci API sebagai variabel lingkungan, Anda dapat mengganti baris berikut dengan: $api_key="your_api_key". Kami tidak menyarankan menyematkan Kunci API secara langsung di kode Anda dalam lingkungan produksi untuk mengurangi risiko kebocoran Kunci API.
$api_key = getenv("DASHSCOPE_API_KEY");
$websocket_url = 'wss://dashscope.aliyuncs.com/api-ws/v1/inference/'; // Alamat server WebSocket
$output_file = 'output.mp3'; // Jalur file output

$loop = Loop::get();

if (file_exists($output_file)) {
    // Kosongkan 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();

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

    // Definisikan fungsi untuk mengirim instruksi continue-task
    $sendContinueTask = function() use ($conn, $loop, $taskId) {
        // Teks yang akan dikirim
        $texts = ["Moonlight before my bed", "could it be frost on the ground?", "I look up to see the moon", "then look down and think 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 mengirim instruksi continue-task: " . $continueTaskMessage . "\n";
            $conn->send($continueTaskMessage);
            $continueTaskCount++;
        }
        echo "Jumlah instruksi continue-task yang dikirim: " . $continueTaskCount . "\n";

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

    // Bendera untuk memeriksa apakah event task-started telah diterima
    $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));
}

/**
 * Kirim 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 diatur ke true, Anda hanya dapat mengirim instruksi continue-task satu kali. Jika tidak, error "Text request limit violated, expected 1." dilaporkan.
                "enable_ssml" => false
            ],
            "input" => (object) []
        ]
    ]);
    echo "Bersiap mengirim instruksi run-task: " . $runTaskMessage . "\n";
    $conn->send($runTaskMessage);
    echo "Instruksi run-task dikirim\n";
}

/**
 * Baca file audio
 * @param string $filePath
 * @return bool|string
 */
function readAudioFile(string $filePath) {
    $voiceData = file_get_contents($filePath);
    if ($voiceData === false) {
        echo "Gagal 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);
}

/**
 * Kirim 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 mengirim instruksi finish-task: " . $finishTaskMessage . "\n";
    $conn->send($finishTaskMessage);
    echo "Instruksi finish-task dikirim\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, mengirim instruksi continue-task...\n";
            $taskStarted = true;
            // Kirim 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 error: " . $response['header']['error_code'] . "\n";
            echo "Pesan error: " . $response['header']['error_message'] . "\n";
            $conn->close();
            break;
        case 'error':
            echo "Error: " . $response['payload']['message'] . "\n";
            break;
        default:
            echo "Event tidak dikenal: " . $response['header']['event'] . "\n";
            break;
    }

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

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

Node.js

Instal dependensi yang diperlukan:

npm install ws
npm install uuid

Kode contoh adalah sebagai berikut:

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

// Jika Anda belum mengonfigurasi Kunci API sebagai variabel lingkungan, Anda dapat mengganti baris berikut dengan: apiKey = 'your_api_key'. Kami tidak menyarankan menyematkan Kunci API secara langsung di kode Anda dalam lingkungan produksi untuk mengurangi risiko kebocoran Kunci API.
const apiKey = process.env.DASHSCOPE_API_KEY;
// Alamat server WebSocket
const url = 'wss://dashscope.aliyuncs.com/api-ws/v1/inference/';
// Jalur file output
const outputFilePath = 'output.mp3';

// Kosongkan 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');

  // Kirim 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
        pitch: 1, // Pitch
        enable_ssml: false // Menentukan apakah akan mengaktifkan fitur SSML. Jika enable_ssml diatur ke true, Anda hanya dapat mengirim instruksi continue-task satu kali. Jika tidak, error "Text request limit violated, expected 1." dilaporkan.
      },
      input: {}
    }
  });
  ws.send(runTaskMessage);
  console.log('Pesan run-task dikirim');
});

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');
        // Kirim 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:
        // Anda dapat menangani result-generated di sini
        break;
    }
  }
});

function sendContinueTasks(ws) {
  const texts = [
    'Moonlight before my bed,',
    'could it be frost on the ground?',
    'I look up to see the moon,',
    'then look down and think 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(`Kirim continue-task, teks: ${text}`);
      }
    }, index * 1000); // Kirim setiap 1 detik
  });

  // Kirim 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('Kirim finish-task');
    }
  }, texts.length * 1000 + 1000); // Kirim 1 detik setelah semua instruksi continue-task dikirim
}

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

Java

Jika Anda mengembangkan dalam Java, kami menyarankan Anda menggunakan SDK DashScope untuk Java. Untuk informasi lebih lanjut, lihat SDK Java.

Berikut adalah contoh panggilan WebSocket Java. Sebelum menjalankan contoh, pastikan untuk mengimpor dependensi berikut:

  • Java-WebSocket

  • jackson-databind

Kami menyarankan Anda menggunakan Maven atau Gradle untuk mengelola dependensi. Konfigurasinya adalah sebagai berikut:

pom.xml

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

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

build.gradle

// Kode lain dihilangkan
dependencies {
  // WebSocket Client
  implementation 'org.java-websocket:Java-WebSocket:1.5.3'
  // JSON Processing
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}
// Kode lain dihilangkan

Kode Java adalah sebagai berikut:

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");

        // Kirim instruksi run-task
        // Jika enable_ssml diatur ke true, Anda hanya dapat mengirim instruksi continue-task satu kali. Jika tidak, error "Text request limit violated, expected 1." dilaporkan.
        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 dari 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 dari server");

                        List<String> texts = Arrays.asList(
                                "Moonlight before my bed, could it be frost on the ground?",
                                "I look up to see the moon, then look down and think of home"
                        );

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

                        // Kirim instruksi finish-task
                        sendFinishTask();
                    } else if ("task-finished".equals(event)) {
                        System.out.println("Menerima event task-finished dari server");
                        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("Ukuran data audio biner yang diterima: " + 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 telah 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("Terjadi error: " + 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 {
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
            if (apiKey == null || apiKey.isEmpty()) {
                System.err.println("Harap atur variabel lingkungan DASHSCOPE_API_KEY");
                return;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "bearer " + apiKey);
            TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://dashscope.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 mengembangkan dalam Python, kami menyarankan Anda menggunakan SDK DashScope untuk Python. Untuk informasi lebih lanjut, lihat SDK Python.

Berikut adalah contoh panggilan WebSocket Python. Sebelum menjalankan contoh, pastikan untuk mengimpor dependensi sebagai berikut:

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

Jangan beri nama file Python yang menjalankan kode contoh ini "websocket.py". Jika tidak, error berikut terjadi: 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):
        """
    Menginisialisasi 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 output audio
        self.ws = None  # Instance WebSocketApp
        self.task_started = False  # Apakah task-started telah diterima
        self.task_finished = False  # Apakah task-finished / task-failed telah diterima

    def on_open(self, ws):
        """
    Fungsi callback saat koneksi WebSocket dibangun.
    Mengirim instruksi run-task untuk memulai tugas 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 diatur ke True, Anda hanya dapat mengirim instruksi continue-task satu kali. Jika tidak, error "Text request limit violated, expected 1." dilaporkan.
                    "enable_ssml": False
                },
                "input": {}
            }
        }

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

    def on_message(self, ws, message):
        """
    Fungsi callback saat pesan diterima.
    Menangani pesan teks dan biner secara berbeda.
    """
        if isinstance(message, str):
            # Proses pesan teks JSON
            try:
                msg_json = json.loads(message)
                print(f"Menerima pesan JSON: {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

                            # Kirim instruksi continue-task
                            texts = [
                                "Moonlight before my bed, could it be frost on the ground?",
                                "I look up to see the moon, then look down and think of home"
                            ]

                            for text in texts:
                                self.send_continue_task(text)

                            # Kirim finish-task setelah semua instruksi continue-task dikirim
                            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", "Error 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:
            # Proses pesan biner (data audio)
            print(f"Menerima pesan biner, ukuran: {len(message)} byte")
            with open(self.output_file, "ab") as f:
                f.write(message)
            print(f"Data audio telah ditulis ke file lokal {self.output_file}")

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

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

    def send_continue_task(self, text):
        """Mengirim instruksi continue-task dengan konten teks yang akan 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"Kirim instruksi continue-task, konten teks: {text}")

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

        self.ws.send(json.dumps(cmd))
        print("Kirim instruksi finish-task")

    def close(self, ws):
        """Menutup koneksi secara aktif"""
        if ws and ws.sock and ws.sock.connected:
            ws.close()
            print("Menutup koneksi secara aktif")

    def run(self):
        """Memulai 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 mendengarkan pada koneksi persisten


# Contoh penggunaan
if __name__ == "__main__":
    API_KEY = os.environ.get("DASHSCOPE_API_KEY")  # Jika Anda belum mengonfigurasi Kunci API sebagai variabel lingkungan, atur API_KEY ke Kunci API Anda.
    SERVER_URI = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/"  # Ganti dengan alamat WebSocket Anda

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

Kode error

Untuk informasi troubleshooting, lihat Pesan error.

FAQ

Fitur/Penagihan/Pembatasan laju

T: Apa yang bisa saya lakukan untuk memperbaiki pelafalan yang tidak akurat?

Anda dapat menggunakan SSML untuk menyesuaikan output sintesis suara.

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

Layanan Suara menggunakan WebSocket alih-alih HTTP, HTTPS, atau RESTful karena memerlukan komunikasi full-duplex. WebSocket memungkinkan server dan klien saling mengirim data secara aktif dalam dua arah, yang diperlukan untuk fitur seperti mendorong progres sintesis atau pengenalan ucapan secara real-time. Sebaliknya, API RESTful berbasis HTTP hanya mendukung model permintaan-respons satu arah yang diprakarsai klien dan tidak dapat memenuhi persyaratan interaksi real-time.

T: Sintesis suara ditagih berdasarkan jumlah karakter teks. Bagaimana cara melihat atau mendapatkan panjang teks untuk setiap sintesis?

Anda dapat mengambil jumlah karakter dari parameter payload.usage.characters dalam event result-generated yang dikembalikan oleh server. Pastikan Anda menggunakan jumlah dari event result-generated terakhir yang Anda terima.

Troubleshooting

Penting

Jika kode Anda melaporkan error, periksa apakah instruksi yang dikirim ke sisi server benar. Anda dapat mencetak konten instruksi untuk memeriksa kesalahan format atau parameter wajib yang hilang. Jika instruksi benar, gunakan kode error untuk troubleshooting.

T: Bagaimana cara mendapatkan Request ID?

Anda dapat mengambilnya dengan salah satu dari dua cara berikut:

T: Mengapa fitur SSML gagal?

Lakukan troubleshooting dengan mengikuti langkah-langkah berikut:

  1. Pastikan penggunaan berada dalam batasan dan kendala yang ditentukan.

  2. Pastikan panggilan dilakukan dengan benar. Untuk informasi lebih lanjut, lihat Dukungan Bahasa Markup SSML.

  3. Pastikan teks yang akan disintesis dalam format teks biasa dan memenuhi persyaratan format. Untuk informasi lebih lanjut, lihat Pengenalan Bahasa Markup SSML.

T: Mengapa audio tidak bisa diputar?

Lakukan troubleshooting berdasarkan skenario berikut:

  1. Audio disimpan sebagai file lengkap, seperti file .mp3.

    1. Konsistensi format audio: Pastikan format audio yang ditentukan dalam parameter permintaan sesuai dengan ekstensi file. Misalnya, pemutaran mungkin gagal jika format audio diatur ke WAV dalam parameter permintaan tetapi file memiliki ekstensi .mp3.

    2. Kompatibilitas pemutar: Pastikan pemutar Anda mendukung format dan laju sampel file audio. Misalnya, beberapa pemutar mungkin tidak mendukung laju sampel tinggi atau encoding audio tertentu.

  2. Audio diputar dalam mode streaming.

    1. Simpan aliran audio sebagai file lengkap dan coba putar. Jika file gagal diputar, lihat langkah troubleshooting untuk skenario pertama.

    2. Jika file diputar dengan benar, masalahnya mungkin pada implementasi pemutaran streaming. Pastikan pemutar Anda mendukung pemutaran streaming.

      Alat dan library umum yang mendukung pemutaran streaming termasuk ffmpeg, pyaudio (Python), AudioFormat (Java), dan MediaSource (JavaScript).

T: Mengapa pemutaran audio tersendat?

Lakukan troubleshooting berdasarkan skenario berikut:

  1. Periksa kecepatan pengiriman teks: Pastikan interval pengiriman teks masuk akal. Hindari penundaan dalam mengirim segmen teks berikutnya setelah audio untuk segmen sebelumnya selesai diputar.

  2. Periksa performa fungsi callback:

    • Periksa apakah fungsi callback berisi logika bisnis berlebihan yang dapat menyebabkannya terblokir.

    • Fungsi callback berjalan di thread WebSocket. Jika thread ini terblokir, dapat mengganggu kemampuan WebSocket untuk menerima paket jaringan, mengakibatkan audio tersendat.

    • Untuk menghindari pemblokiran thread WebSocket, tulis data audio ke buffer audio terpisah dan gunakan thread lain untuk membaca dan memprosesnya.

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

T: Mengapa sintesis suara lambat (waktu sintesis lama)?

Lakukan langkah troubleshooting berikut:

  1. Periksa interval input

    Jika Anda menggunakan sintesis suara streaming, periksa apakah interval pengiriman teks terlalu lama. Misalnya, penundaan beberapa detik sebelum mengirim segmen berikutnya akan meningkatkan total waktu sintesis.

  2. Analisis metrik performa

    • Penundaan paket pertama: Biasanya sekitar 500 ms.

    • Faktor Real-Time (RTF): Dihitung sebagai Total Waktu Sintesis / Durasi Audio. RTF biasanya kurang dari 1,0.

T: Bagaimana cara menangani pelafalan yang salah dalam ucapan yang disintesis?

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

T: Mengapa tidak ada audio yang dikembalikan? Mengapa akhir teks tidak dikonversi menjadi ucapan? (Ucapan yang disintesis hilang)

Pastikan Anda telah mengirim instruksi finish-task. Selama sintesis suara, sisi server menyimpan cache sejumlah teks sebelum memulai sintesis. Jika Anda tidak mengirim instruksi finish-task, teks yang tersisa di cache mungkin tidak disintesis.

T: Mengapa urutan aliran audio yang dikembalikan salah, menyebabkan pemutaran kacau?

Lakukan troubleshooting dengan memeriksa poin-poin berikut:

  • Pastikan instruksi run-task, instruksi continue-task, dan instruksi finish-task untuk tugas sintesis yang sama menggunakan task_id yang sama.

  • Periksa apakah operasi asinkron menyebabkan data audio ditulis tidak berurutan.

Izin dan otentikasi

T: Saya ingin Kunci API saya hanya digunakan untuk layanan sintesis suara CosyVoice, bukan untuk model Model Studio lainnya (isolasi izin). Apa yang harus saya lakukan?

Anda dapat membuat ruang kerja dan hanya mengotorisasi model tertentu untuk membatasi cakupan Kunci API. Lihat Kelola ruang kerja.

Pertanyaan lainnya

Lihat Q&A di GitHub.