ถอดเสียงสดภาษาไทย
ถอดเสียงภาษาไทยระหว่างที่ยังพูดอยู่ เปิด WebSocket เดียวส่งเสียงขึ้นไป ระบบหาขอบเขตรอบพูดให้ แล้วส่งบทถอดที่จบแล้วกลับมาทีละรอบ ใช้ได้กับวอยซ์เอเจนต์ คำบรรยายสด และการมอนิเตอร์สายโทรศัพท์
บทถอดเสียงเขียนว่าอย่างไร
ทวนการจอง
spokenขอจองโต๊ะวันเสาร์ที่ สิบห้า สำหรับ สี่ ท่านค่ะ
writtenขอจองโต๊ะวันเสาร์ที่ 15 สำหรับ 4 ท่านค่ะ
หนึ่งรอบพูดจากลูกค้า แบบเขียนใส่วันที่และจำนวนคนเป็นตัวเลข ซึ่งเป็นรูปแบบที่ระบบจองต้องกรอก
เลขพัสดุที่สะกดทีละตัว
spokenพัสดุเลขที่ ที เอช หนึ่ง สอง สาม สี่ ห้า หก ส่งถึงไหนแล้วคะ
writtenพัสดุเลขที่ TH123456 ส่งถึงไหนแล้วคะ
เลขอ้างอิงที่พูดสะกดทีละตัวกลางประโยคไทย บทถอดรอบสุดท้ายมาเป็นข้อความเดียวที่เอาไปค้นต่อได้
ทวนยอดเงิน
spokenยอดที่ต้องชำระคือ หนึ่งพันสองร้อยห้าสิบ บาท ใช่ไหมคะ
writtenยอดที่ต้องชำระคือ 1,250 บาท ใช่ไหมคะ
ลูกค้าทวนตัวเลขกลับมา ตัวเลขลงช่องที่ระบบชำระเงินอ่าน ตั้งแต่รอบพูดที่ลูกค้าพูดมัน
บทถอดทุกคู่บนหน้านี้เป็นตัวอย่างที่เขียนขึ้นเพื่อแสดงว่าตัวเลือกรูปแบบเปลี่ยนอะไร และตรวจกับผลลัพธ์จริงก่อนเผยแพร่ หน้า Speech-to-Text มีบทถอดจริงของโมเดลจากไฟล์เสียงจริง พร้อมวันที่บันทึก
วิธีที่ระบบสดใช้ซ็อกเก็ตนี้
ไฟล์บันทึกรอได้ บทสนทนาที่กำลังเกิดขึ้นรอไม่ได้ เมื่อขั้นตอนถัดไปต้องใช้บทถอดเป็นตัวตั้ง ไม่ว่าจะเป็นวอยซ์เอเจนต์ที่ต้องตัดสินใจว่าจะพูดอะไร หัวหน้าทีมที่เฝ้าดูสายที่กำลังไปผิดทาง หรือคำบรรยายใต้เวทีที่ต้องขึ้นทัน ระยะห่างระหว่างคำพูดกับข้อความคือตัวสินค้าเอง การถอดเสียงสดตัดไฟล์ออกจากตรงกลาง เสียงเดินทางขึ้น WebSocket ทันทีที่ไมโครโฟนหรือสายโทรศัพท์ผลิตออกมา และบทถอดเดินทางกลับมาทีละรอบพูดขณะที่คนยังพูดต่อ สิ่งที่ทำให้ใช้งานได้จริงคือรอบพูด ระบบหาจุดที่ผู้พูดหยุดแล้วส่งประโยคที่จบแล้วออกมา โค้ดปลายทางจึงอ่านประโยคไทยทั้งประโยคโดยเชื่อจุดจบได้ ภาษาไทยทำให้การตัดสินนี้ยากกว่าที่คิด เพราะเขียนติดกันโดยไม่มีช่องว่างระหว่างคำ และสัญญาณว่าความคิดจบแล้วมักเป็นคำลงท้ายมากกว่าเครื่องหมายวรรคตอน
เปิดหนึ่งซ็อกเก็ตต่อหนึ่งบทสนทนา
ส่งเฟรม start ระบุโมเดล การเข้ารหัสเสียง และแซมเปิลเรต แล้วส่งเฟรมเสียงขึ้นไปเรื่อย ๆ ตามที่บันทึกได้ API key อยู่ที่เซิร์ฟเวอร์ของคุณ ส่วนเบราว์เซอร์ส่งเสียงจากไมโครโฟนผ่านรีเลย์ของคุณ
แสดงข้อความระหว่างทาง ทำงานกับฉบับจบ
ถ้ามีหน้าจอ ให้วาดบทถอดระหว่างทางไปก่อน แล้วสั่งขั้นตอนถัดไปด้วยบทถอดฉบับจบของแต่ละรอบพูด ซึ่งมีหมายเลขรอบ ข้อความ และตำแหน่งในเสียงมาให้พร้อม
ปิดเซสชันแล้วอ่านยอดรวม
ส่งเฟรม end เมื่อสายวางแล้ว อีเวนต์ done รายงานเสียงที่รับเข้ามา จำนวนรอบพูด และเครดิตที่การเชื่อมต่อนี้ต้องจ่าย ทั้งการเชื่อมต่อลงในบันทึกคำขอเป็นบรรทัดเดียว
หนึ่งบทสนทนาบนสาย
client → {"type":"start","model":"paxa-stt-lite-realtime-v1-preview"}
server ← {"type":"started","connection":"7d9c2b7e"}
client → เฟรมเสียง ตลอดเวลาที่สายยังเปิดอยู่
server ← {"type":"speech_started","turn":1}
server ← {"type":"speech_ended","turn":1}
server ← {"type":"transcript","turn":1,"is_final":true}
client → {"type":"end"}
server ← {"type":"done","turns":12}การเชื่อมต่อเดียวรับบทสนทนาทั้งบท อีเวนต์ของทุกรอบพูดกลับมาทางซ็อกเก็ตเดียวกัน สลับกับเสียงที่กำลังส่งขึ้นไป
สิ่งที่ฝั่งเซิร์ฟเวอร์ตัดสินให้
16 หรือ 48 kHz
เสียงที่รับเข้า
2 อีเวนต์
ขอบเขตรอบพูด
1 ฉบับจบ
ระหว่างทางก่อน แล้วค่อยฉบับจบ
4 ชั่วโมง
เพดานต่อหนึ่งเซสชัน
การหาขอบเขตรอบพูดทำงานอยู่ฝั่งเซิร์ฟเวอร์ ไคลเอนต์มีหน้าที่ส่งเสียงและอ่านอีเวนต์ สัญญาเดียวกันนี้ใช้ได้ทั้งกับรีเลย์ในเบราว์เซอร์ สะพาน SIP และไฟล์ที่เล่นตามจังหวะจริง
หนึ่งรอบพูด จากระหว่างทางถึงฉบับจบ
transcript turn 2 interim สวัสดีค่ะ transcript turn 2 interim สวัสดีค่ะ อยากสอบถาม transcript turn 2 interim สวัสดีค่ะ อยากสอบถามเรื่องบิล speech_ended turn 2 charged seq 2 transcript turn 2 final สวัสดีค่ะ อยากสอบถามเรื่องบิลเดือนนี้ค่ะ
ข้อความระหว่างทางคือร่างของรอบพูดที่ยังไม่จบ และถูกแทนที่ด้วยชุดถัดไป จุดจบของรอบพูดเป็นจุดตัดช่วงคิดเงิน และบทถอดฉบับจบตามหลังการคิดเงินนั้น ส่วนรอบพูดที่โมเดลถอดไม่สำเร็จจะส่งอีเวนต์ error มาแทนฉบับจบ และเสียงของรอบนั้นถูกตัดออกจากยอดเรียกเก็บ
ค่าฟัง
| ปริมาณงาน | เสียงที่รับเข้ามา | ค่าใช้จ่าย |
|---|---|---|
| หนึ่งสาย 5 นาที | 5 นาที | $0.06 |
| 100 สาย | 8.3 ชม. | $6.25 |
| รับสาย 1,000 สาย | ประมาณ $62.50 | |
12.5 เครดิตต่อนาทีของเสียงที่รับเข้ามา รวมช่วงเงียบ คำนวณที่ 5 นาทีต่อหนึ่งสายที่รับ รอบพูดที่ถอดไม่สำเร็จไม่มีค่าใช้จ่าย และทั้งเซสชันแสดงเป็นหนึ่งบรรทัดในบันทึกคำขอ
สำหรับนักพัฒนา
ตัวอย่าง TypeScript และ Python เปิดซ็อกเก็ต ส่งเสียงขึ้นไปตามจังหวะจริง แล้วอ่านแต่ละรอบพูดจากบทถอดฉบับจบ ส่วนแท็บที่สามคือสายอีเวนต์ที่หนึ่งรอบพูดสร้างขึ้น
เอกสารอ้างอิง API ฉบับเต็มimport { open } from "node:fs/promises"; // Runs on Bun as-is; on Node, `import WebSocket from "ws"` accepts // the same constructor options. Browsers cannot send this header, and // API keys never ship to browsers anyway: relay the audio through your // server. const socket = new WebSocket("wss://api.paxalabs.com/v1/stt/live", { headers: { Authorization: `Bearer ${process.env.PAXA_API_KEY}` }, }); socket.onopen = async () => { socket.send(JSON.stringify({ type: "start", model: "paxa-stt-lite-realtime-v1-preview", audio: { encoding: "pcm_s16le", sample_rate: 16000 }, language: "th", })); // 16 kHz signed 16-bit mono is 32,000 bytes a second. A microphone // delivers frames at this pace on its own; a file has to be paced. const file = await open("caller.pcm"); const frame = new Uint8Array(640); // 20 ms for (;;) { const { bytesRead } = await file.read(frame, 0, frame.length); if (bytesRead === 0) break; socket.send(frame.subarray(0, bytesRead)); await new Promise((resolve) => setTimeout(resolve, 20)); } socket.send(JSON.stringify({ type: "end" })); }; socket.onmessage = (event) => { const message = JSON.parse(String(event.data)); switch (message.type) { case "speech_started": // The user is talking: stop the agent's audio, cancel its reply. break; case "transcript": if (message.is_final) console.log(`turn ${message.turn}: ${message.text}`); break; case "done": socket.close(); } };
import json import os import time from websockets.sync.client import connect # pip install websockets with connect( "wss://api.paxalabs.com/v1/stt/live", additional_headers={"Authorization": f"Bearer {os.environ['PAXA_API_KEY']}"}, ) as socket: socket.send(json.dumps({ "type": "start", "model": "paxa-stt-lite-realtime-v1-preview", "audio": {"encoding": "pcm_s16le", "sample_rate": 16000}, "language": "th", })) # 16 kHz signed 16-bit mono is 32,000 bytes a second; a file has to # be paced the way a microphone paces itself. with open("caller.pcm", "rb") as audio: while frame := audio.read(640): # 20 ms socket.send(frame) time.sleep(0.02) socket.send(json.dumps({"type": "end"})) for message in socket: event = json.loads(message) if event["type"] == "transcript" and event["is_final"]: print(event["turn"], event["text"]) elif event["type"] == "done": break
{"type":"started","connection":"7d9c2b7e-4a41-4c8e-9a35-1f26d8c04b7a","model":"paxa-stt-lite-realtime-v1-preview"} {"type":"speech_started","turn":1,"time":0.3} {"type":"speech_ended","turn":1,"time":12.252} {"type":"charged","seq":1,"seconds":12.252,"credits":2.56,"total_credits":2.56} {"type":"transcript","turn":1,"is_final":true,"text":"สวัสดีค่ะคุณสมชายยอดชำระของท่านจำนวน พันสองร้อยห้าสิบ บาทครบกำหนดวันที่ สิบห้า สิงหาคมนี้ชำระผ่านแอปได้ตลอด ยี่สิบสี่ ชั่วโมงค่ะ","start":0.3,"end":12.252} {"type":"done","total_seconds":12.252,"turns":1,"total_credits":2.56}
โมเดลฟังอะไรบนสายสด
Paxa STT Lite Realtime ถอดภาษาไทยเป็นหลัก และถอดภาษาอื่นตามที่พูด รวมถึงประโยคที่สลับภาษากลางทาง ระบบอ่านขอบเขตรอบพูดจากตัวเสียงเอง โดยไม่ต้องรับสัญญาณจากไคลเอนต์ และยังไม่ประกาศตัวเลขความแม่นยำจนกว่าจะเผยแพร่วิธีวัดควบคู่กันไป
คำถามที่พบบ่อย
เซสชันสดคิดเงินอย่างไร
คิดจากเสียงที่ซ็อกเก็ตรับเข้ามา รวมช่วงเงียบ ที่ 12.5 เครดิตต่อนาที การเชื่อมต่อถูกตัดเป็นช่วงตามรอบพูดและคิดเงินไปตลอดทาง ส่วนในบันทึกคำขอ ทั้งเซสชันแสดงเป็นหนึ่งบรรทัดพร้อมยอดรวม
ถ้ารอบพูดหนึ่งล้มเหลวจะเกิดอะไรขึ้น
เสียงของรอบพูดนั้นถูกตัดออกจากยอดเรียกเก็บ และมีอีเวนต์ error ระบุหมายเลขรอบมาให้ การเชื่อมต่อยังเปิดอยู่ รอบถัดไปทำงานตามปกติ และยอดสะสมที่รายงานคิดให้แล้ว
เบราว์เซอร์เปิดซ็อกเก็ตเองได้ไหม
ห้ามใส่ API key ไว้ในเบราว์เซอร์ เสียงจึงต้องผ่านเซิร์ฟเวอร์ของคุณ หน้าเว็บส่งเฟรมเสียงไปยังรีเลย์ที่คุณเป็นเจ้าของ แล้วรีเลย์เป็นฝ่ายถือคีย์และถือซ็อกเก็ต
ถ้ามีไฟล์บันทึกอยู่แล้วต้องใช้เอนด์พอยต์ไหน
ใช้แบบบัฟเฟอร์ POST /v1/stt รับไฟล์ยาวได้ถึง 60 นาที ตอบครั้งเดียวพร้อมบทถอดทั้งหมด และคิดค่าบริการต่อนาทีถูกกว่า เพราะไม่ต้องเดินตามจังหวะของผู้พูด
เริ่มต้นด้วยเครดิตฟรี
เข้าสู่ระบบด้วย Google, GitHub หรือ Hugging Face แล้วใช้เครดิตฟรี 100 เครดิตกับข้อความของคุณเอง
บทความที่เกี่ยวข้อง
- แบบพูดหรือแบบเขียน: บทถอดเสียงภาษาไทยเขียนตัวเลขอย่างไร
- วัดความแม่นยำของการถอดเสียงภาษาไทย: ทำไม WER จึงหลอกตา
- ช่วงผู้พูดในบทถอดเสียงสายโทรศัพท์ภาษาไทย
- ทำคำบรรยายภาษาไทยจากเวลาระดับคำ
- เลือกเอนจินถอดเสียงภาษาไทยอย่างไร
- ถอดเสียงภาษาไทยที่สลับไปเป็นอังกฤษ
- Paxa STT Lite Realtime ถอดเสียงภาษาไทยทีละรอบพูด ตั้งแต่ยังพูดไม่จบ
- ถอดเสียงสดภาษาไทย กับคำถามว่ารอบพูดจบตรงไหน
- วิธีถอดเทป แปลงไฟล์เสียงเป็นข้อความ สำหรับประชุม สัมภาษณ์ และงานบรรยาย
- ถอดเสียงภาษาไทยที่ดีที่สุด สำหรับงานประชุม ซับไตเติล และ Voice Agent