This topic describes the service endpoint, request headers, and interaction flow for accessing the Qwen-Audio-3.1-ASR-Flash-Message real-time speech recognition service over a WebSocket connection.
Service endpoint
Use the following WebSocket URLs with the wss:// protocol:
Singapore
wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference
Replace {WorkspaceId} with your actual Workspace ID.
China (Beijing)
wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference
Replace {WorkspaceId} with your actual Workspace ID.
ImportantAlibaba Cloud Model Studio has released workspace-specific domains for the China (Beijing) and Singapore regions. The new dedicated domains deliver superior performance and higher stability for inference requests. We recommend migrating to the new domains:
- China (Beijing): from
dashscope.aliyuncs.comto{WorkspaceId}.cn-beijing.maas.aliyuncs.com - Singapore: from
dashscope-intl.aliyuncs.comto{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com
Request headers
Add the following information to the request header:
Parameter | Type | Required | Description |
|---|---|---|---|
Authorization | string | Yes | Authentication token in the format |
user-agent | string | No | Client identifier that helps the server track the request source. |
X-DashScope-WorkSpace | string | No | Model Studio workspace ID. |
X-DashScope-DataInspection | string | No | Whether to enable data compliance inspection. This parameter is omitted or set to |
ImportantAuthorization is verified during the WebSocket handshake. If the API key is invalid or missing, the handshake fails and returns an HTTP 401 or 403 error.
Interaction flow
For details about client events and server events, see Client events and Server-side events.
The client and server interact in the following order:
- Establish the connection: The client establishes a WebSocket connection to the server.
- Start the task: The client sends a run-task instruction to start the task and receives a task-started event from the server. This event confirms that the task has started and that you can proceed to the subsequent steps.
- Send the audio stream: The client starts sending binary audio (which must be mono) and simultaneously receives a stream of result-generated events from the server. Each event contains a speech recognition result.
- Notify the server to end the task: The client sends a finish-task instruction to signal the server to end the task and continues to receive result-generated events from the server.
- End the task: The client receives a task-finished event from the server, which indicates that the task has ended.
- Close the connection: The client closes the WebSocket connection.
Examples
These examples use qwen-audio-3.1-asr-flash-message. Get your API key and Base URL from the console, and set them as DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL.
Download the sample audio as asr_example.wav: a 16 kHz, mono WAV file with 16-bit PCM encoding.
Java
Before you run the example, install the Java-WebSocket dependency:
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
implementation 'org.java-websocket:Java-WebSocket:1.5.6'
implementation 'org.json:json:20240303'
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
public class FunASRRealtimeClient {
private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY");
private static final String URL = "wss://" + URI.create(System.getenv("DASHSCOPE_BASE_URL")).getHost() + "/api-ws/v1/inference";
private static final String AUDIO_FILE = "asr_example.wav"; // Replace with the path to your audio file
private static final String MODEL = "qwen-audio-3.1-asr-flash-message";
// Generate a 32-character random ID
private static final String TASK_ID = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
private static final AtomicBoolean taskStarted = new AtomicBoolean(false);
private static WebSocketClient client;
public static void main(String[] args) throws Exception {
client = new WebSocketClient(new URI(URL)) {
@Override
public void onOpen(ServerHandshake handshake) {
System.out.println("Connected to server");
sendRunTask();
}
@Override
public void onMessage(String data) {
JSONObject message = new JSONObject(data);
String event = message.getJSONObject("header").getString("event");
switch (event) {
case "task-started":
System.out.println("Task started");
taskStarted.set(true);
new Thread(FunASRRealtimeClient::sendAudioStream).start();
break;
case "result-generated":
JSONObject payload = message.getJSONObject("payload");
String text = payload.getJSONObject("output").getJSONObject("sentence").getString("text");
System.out.println("Recognition result: " + text);
if (payload.has("usage")) {
System.out.println("Audio duration (seconds): " + payload.getJSONObject("usage").get("duration"));
}
break;
case "task-finished":
System.out.println("Task finished");
close();
break;
case "task-failed":
String errMsg = message.getJSONObject("header").optString("error_message");
System.err.println("Task failed: " + errMsg);
close();
break;
default:
System.out.println("Unknown event: " + event);
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
if (!taskStarted.get()) {
System.err.println("Task not started, closing connection");
}
}
@Override
public void onError(Exception ex) {
System.err.println("WebSocket error: " + ex.getMessage());
}
};
client.addHeader("Authorization", "bearer " + API_KEY);
client.connectBlocking();
}
// Send the run-task instruction
private static void sendRunTask() {
JSONObject runTask = new JSONObject()
.put("header", new JSONObject()
.put("action", "run-task")
.put("task_id", TASK_ID)
.put("streaming", "duplex"))
.put("payload", new JSONObject()
.put("task_group", "audio")
.put("task", "asr")
.put("function", "recognition")
.put("model", MODEL)
.put("parameters", new JSONObject()
.put("sample_rate", 16000)
.put("format", "wav"))
.put("input", new JSONObject()));
client.send(runTask.toString());
}
// Send the audio stream (send one binary chunk every 100ms)
private static void sendAudioStream() {
int chunkSize = 3200; // 100ms @ 16kHz 16bit mono
try {
byte[] audio = Files.readAllBytes(Paths.get(AUDIO_FILE));
int offset = 0;
while (offset < audio.length) {
int end = Math.min(offset + chunkSize, audio.length);
byte[] chunk = new byte[end - offset];
System.arraycopy(audio, offset, chunk, 0, end - offset);
client.send(ByteBuffer.wrap(chunk));
offset = end;
Thread.sleep(100);
}
System.out.println("Audio stream ended");
sendFinishTask();
} catch (Exception e) {
System.err.println("Error reading audio file: " + e.getMessage());
client.close();
}
}
// Send the finish-task instruction
private static void sendFinishTask() {
JSONObject finishTask = new JSONObject()
.put("header", new JSONObject()
.put("action", "finish-task")
.put("task_id", TASK_ID)
.put("streaming", "duplex"))
.put("payload", new JSONObject()
.put("input", new JSONObject()));
client.send(finishTask.toString());
}
}
Python
Before you run the example, install the dependencies with the following commands:
python -m pip install websocket-client
NoteDo not name the example file websocket.py, because it conflicts with the websocket module during import.
# pip install websocket-client
import os
import json
import time
import uuid
import threading
from urllib.parse import urlsplit
import websocket
api_key = os.environ.get('DASHSCOPE_API_KEY')
url = f"wss://{urlsplit(os.environ['DASHSCOPE_BASE_URL']).netloc}/api-ws/v1/inference" # WebSocket server address
audio_file = 'asr_example.wav' # Replace with the path to your audio file
# Generate a 32-character random ID
TASK_ID = uuid.uuid4().hex[:32]
task_started = False # Flag indicating whether the task has started
# Send the run-task instruction
def send_run_task(ws):
run_task_message = {
'header': {
'action': 'run-task',
'task_id': TASK_ID,
'streaming': 'duplex'
},
'payload': {
'task_group': 'audio',
'task': 'asr',
'function': 'recognition',
'model': 'qwen-audio-3.1-asr-flash-message',
'parameters': {
'sample_rate': 16000,
'format': 'wav'
},
'input': {}
}
}
ws.send(json.dumps(run_task_message))
# Send the finish-task instruction
def send_finish_task(ws):
finish_task_message = {
'header': {
'action': 'finish-task',
'task_id': TASK_ID,
'streaming': 'duplex'
},
'payload': {
'input': {}
}
}
ws.send(json.dumps(finish_task_message))
# Send the audio stream (send one binary chunk every 100ms)
def send_audio_stream(ws):
chunk_size = 3200 # 100ms @ 16kHz 16bit mono
try:
with open(audio_file, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
ws.send(chunk, opcode=websocket.ABNF.OPCODE_BINARY)
time.sleep(0.1)
print('Audio stream ended')
send_finish_task(ws)
except Exception as e:
print('Error reading audio file:', e)
ws.close()
# Send the run-task instruction when the connection opens
def on_open(ws):
print('Connected to server')
send_run_task(ws)
# Handle received messages
def on_message(ws, data):
global task_started
message = json.loads(data)
event = message['header']['event']
if event == 'task-started':
print('Task started')
task_started = True
threading.Thread(target=send_audio_stream, args=(ws,), daemon=True).start()
elif event == 'result-generated':
print('Recognition result:', message['payload']['output']['sentence']['text'])
if message['payload'].get('usage'):
print('Audio duration (seconds):', message['payload']['usage']['duration'])
elif event == 'task-finished':
print('Task finished')
ws.close()
elif event == 'task-failed':
print('Task failed:', message['header'].get('error_message'))
ws.close()
else:
print('Unknown event:', event)
# Close the connection if the task-started event is not received
def on_close(ws, close_status_code, close_msg):
if not task_started:
print('Task not started, closing connection')
# Error handling
def on_error(ws, error):
print('WebSocket error:', error)
if __name__ == '__main__':
ws = websocket.WebSocketApp(
url,
header={'Authorization': f'bearer {api_key}'},
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
ws.run_forever()
Go
Initialize a module in a new Go example directory and install the dependencies:
go mod init asr-example
go get github.com/gorilla/websocket@v1.5.3 github.com/google/uuid@v1.6.0
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
neturl "net/url"
"os"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
const (
audioFile = "asr_example.wav" // Replace with the path to your audio file
)
var dialer = websocket.DefaultDialer
func main() {
apiKey := os.Getenv("DASHSCOPE_API_KEY")
// Connect to the WebSocket service
conn, err := connectWebSocket(apiKey)
if err != nil {
log.Fatal("Failed to connect to WebSocket: ", err)
}
defer closeConnection(conn)
// Start a goroutine to receive results
taskStarted := make(chan bool)
taskDone := make(chan bool)
startResultReceiver(conn, taskStarted, taskDone)
// Send the run-task instruction
taskID, err := sendRunTaskCmd(conn)
if err != nil {
log.Fatal("Failed to send the run-task instruction: ", err)
}
// Wait for the task-started event
waitForTaskStarted(taskStarted)
// Send the audio file stream to be recognized
if err := sendAudioData(conn); err != nil {
log.Fatal("Failed to send audio: ", err)
}
// Send the finish-task instruction
if err := sendFinishTaskCmd(conn, taskID); err != nil {
log.Fatal("Failed to send the finish-task instruction: ", err)
}
// Wait for the task to finish or fail
<-taskDone
}
// Define structs to represent the JSON data
type Header struct {
Action string `json:"action"`
TaskID string `json:"task_id"`
Streaming string `json:"streaming"`
Event string `json:"event"`
ErrorCode string `json:"error_code,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
Attributes map[string]interface{} `json:"attributes"`
}
type Output struct {
Sentence struct {
BeginTime int64 `json:"begin_time"`
EndTime *int64 `json:"end_time"`
Text string `json:"text"`
Words []struct {
BeginTime int64 `json:"begin_time"`
EndTime *int64 `json:"end_time"`
Text string `json:"text"`
Punctuation string `json:"punctuation"`
} `json:"words"`
} `json:"sentence"`
}
type Payload struct {
TaskGroup string `json:"task_group"`
Task string `json:"task"`
Function string `json:"function"`
Model string `json:"model"`
Parameters Params `json:"parameters"`
Input Input `json:"input"`
Output Output `json:"output,omitempty"`
Usage *struct {
Duration int `json:"duration"`
} `json:"usage,omitempty"`
}
type Params struct {
Format string `json:"format"`
SampleRate int `json:"sample_rate"`
DisfluencyRemovalEnabled bool `json:"disfluency_removal_enabled"`
}
type Input struct {
}
type Event struct {
Header Header `json:"header"`
Payload Payload `json:"payload"`
}
// Connect to the WebSocket service
func connectWebSocket(apiKey string) (*websocket.Conn, error) {
base, err := neturl.Parse(os.Getenv("DASHSCOPE_BASE_URL"))
if err != nil {
return nil, err
}
wsURL := "wss://" + base.Host + "/api-ws/v1/inference"
header := make(http.Header)
header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))
conn, _, err := dialer.Dial(wsURL, header)
return conn, err
}
// Start a goroutine to receive WebSocket messages asynchronously
func startResultReceiver(conn *websocket.Conn, taskStarted chan<- bool, taskDone chan<- bool) {
go func() {
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("Failed to parse server message: ", err)
return
}
var event Event
err = json.Unmarshal(message, &event)
if err != nil {
log.Println("Failed to parse event: ", err)
continue
}
if handleEvent(conn, event, taskStarted, taskDone) {
return
}
}
}()
}
// Send the run-task instruction
func sendRunTaskCmd(conn *websocket.Conn) (string, error) {
runTaskCmd, taskID, err := generateRunTaskCmd()
if err != nil {
return "", err
}
err = conn.WriteMessage(websocket.TextMessage, []byte(runTaskCmd))
return taskID, err
}
// Generate the run-task instruction
func generateRunTaskCmd() (string, string, error) {
taskID := uuid.New().String()
runTaskCmd := Event{
Header: Header{
Action: "run-task",
TaskID: taskID,
Streaming: "duplex",
},
Payload: Payload{
TaskGroup: "audio",
Task: "asr",
Function: "recognition",
Model: "qwen-audio-3.1-asr-flash-message",
Parameters: Params{
Format: "wav",
SampleRate: 16000,
},
Input: Input{},
},
}
runTaskCmdJSON, err := json.Marshal(runTaskCmd)
return string(runTaskCmdJSON), taskID, err
}
// Wait for the task-started event
func waitForTaskStarted(taskStarted chan bool) {
select {
case <-taskStarted:
fmt.Println("Task started successfully")
case <-time.After(10 * time.Second):
log.Fatal("Timed out waiting for task-started; failed to start the task")
}
}
// Send audio data
func sendAudioData(conn *websocket.Conn) error {
file, err := os.Open(audioFile)
if err != nil {
return err
}
defer file.Close()
buf := make([]byte, 3200)
for {
n, err := file.Read(buf)
if n == 0 {
break
}
if err != nil && err != io.EOF {
return err
}
err = conn.WriteMessage(websocket.BinaryMessage, buf[:n])
if err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
// Send the finish-task instruction
func sendFinishTaskCmd(conn *websocket.Conn, taskID string) error {
finishTaskCmd, err := generateFinishTaskCmd(taskID)
if err != nil {
return err
}
err = conn.WriteMessage(websocket.TextMessage, []byte(finishTaskCmd))
return err
}
// Generate the finish-task instruction
func generateFinishTaskCmd(taskID string) (string, error) {
finishTaskCmd := Event{
Header: Header{
Action: "finish-task",
TaskID: taskID,
Streaming: "duplex",
},
Payload: Payload{
Input: Input{},
},
}
finishTaskCmdJSON, err := json.Marshal(finishTaskCmd)
return string(finishTaskCmdJSON), err
}
// Handle events
func handleEvent(conn *websocket.Conn, event Event, taskStarted chan<- bool, taskDone chan<- bool) bool {
switch event.Header.Event {
case "task-started":
fmt.Println("Received the task-started event")
taskStarted <- true
case "result-generated":
if event.Payload.Output.Sentence.Text != "" {
fmt.Println("Recognition result: ", event.Payload.Output.Sentence.Text)
}
if event.Payload.Usage != nil {
fmt.Println("Audio duration (seconds): ", event.Payload.Usage.Duration)
}
case "task-finished":
fmt.Println("Task finished")
taskDone <- true
return true
case "task-failed":
handleTaskFailed(event, conn)
taskDone <- true
return true
default:
log.Printf("Unexpected event: %v", event)
}
return false
}
// Handle the task-failed event
func handleTaskFailed(event Event, conn *websocket.Conn) {
if event.Header.ErrorMessage != "" {
log.Fatalf("Task failed: %s", event.Header.ErrorMessage)
} else {
log.Fatal("The task failed for an unknown reason")
}
}
// Close the connection
func closeConnection(conn *websocket.Conn) {
if conn != nil {
conn.Close()
}
}
Node.js
Install the required dependencies:
npm install ws@8 uuid@9
The example code is as follows:
const fs = require('fs');
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid'); // Used to generate a UUID
const apiKey = process.env.DASHSCOPE_API_KEY;
const url = `wss://${new URL(process.env.DASHSCOPE_BASE_URL).host}/api-ws/v1/inference`; // WebSocket server address
const audioFile = 'asr_example.wav'; // Replace with the path to your audio file
// Generate a 32-character random ID
const TASK_ID = uuidv4().replace(/-/g, '').slice(0, 32);
// Create the WebSocket client
const ws = new WebSocket(url, {
headers: {
Authorization: `bearer ${apiKey}`
}
});
let taskStarted = false; // Flag indicating whether the task has started
// Send the run-task instruction when the connection opens
ws.on('open', () => {
console.log('Connected to server');
sendRunTask();
});
// Handle received messages
ws.on('message', (data) => {
const message = JSON.parse(data);
switch (message.header.event) {
case 'task-started':
console.log('Task started');
taskStarted = true;
sendAudioStream();
break;
case 'result-generated':
console.log('Recognition result:', message.payload.output.sentence.text);
if (message.payload.usage) {
console.log('Audio duration (seconds):', message.payload.usage.duration);
}
break;
case 'task-finished':
console.log('Task finished');
ws.close();
break;
case 'task-failed':
console.error('Task failed:', message.header.error_message);
ws.close();
break;
default:
console.log('Unknown event:', message.header.event);
}
});
// Close the connection if the task-started event is not received
ws.on('close', () => {
if (!taskStarted) {
console.error('Task not started, closing connection');
}
});
// Send the run-task instruction
function sendRunTask() {
const runTaskMessage = {
header: {
action: 'run-task',
task_id: TASK_ID,
streaming: 'duplex'
},
payload: {
task_group: 'audio',
task: 'asr',
function: 'recognition',
model: 'qwen-audio-3.1-asr-flash-message',
parameters: {
sample_rate: 16000,
format: 'wav'
},
input: {}
}
};
ws.send(JSON.stringify(runTaskMessage));
}
// Send the audio stream
function sendAudioStream() {
const audioStream = fs.createReadStream(audioFile);
let chunkCount = 0;
function sendNextChunk() {
const chunk = audioStream.read();
if (chunk) {
ws.send(chunk);
chunkCount++;
setTimeout(sendNextChunk, 100); // Send once every 100ms
}
}
audioStream.on('readable', () => {
sendNextChunk();
});
audioStream.on('end', () => {
console.log('Audio stream ended');
sendFinishTask();
});
audioStream.on('error', (err) => {
console.error('Error reading audio file:', err);
ws.close();
});
}
// Send the finish-task instruction
function sendFinishTask() {
const finishTaskMessage = {
header: {
action: 'finish-task',
task_id: TASK_ID,
streaming: 'duplex'
},
payload: {
input: {}
}
};
ws.send(JSON.stringify(finishTaskMessage));
}
// Error handling
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
Save the code as asr_example.cjs and run node asr_example.cjs.
C#
Save the code as Program.cs and run it in a .NET console project.
The example code is as follows:
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
class Program {
private static ClientWebSocket _webSocket = new ClientWebSocket();
private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private static bool _taskStartedReceived = false;
private static bool _taskFinishedReceived = false;
private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");
private static readonly string WebSocketUrl = "wss://" + new Uri(Environment.GetEnvironmentVariable("DASHSCOPE_BASE_URL")!).Authority + "/api-ws/v1/inference";
// Replace with the path to your audio file
private const string AudioFilePath = "asr_example.wav";
static async Task Main(string[] args) {
// Establish the WebSocket connection and configure headers for authentication
_webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
await _webSocket.ConnectAsync(new Uri(WebSocketUrl), _cancellationTokenSource.Token);
// Start a thread to receive WebSocket messages asynchronously
var receiveTask = ReceiveMessagesAsync();
// Send the run-task instruction
string _taskId = Guid.NewGuid().ToString("N"); // Generate a 32-character random ID
var runTaskJson = GenerateRunTaskJson(_taskId);
await SendAsync(runTaskJson);
// Wait for the task-started event
while (!_taskStartedReceived) {
await Task.Delay(100, _cancellationTokenSource.Token);
}
// Read the local file and send the audio stream to be recognized to the server
await SendAudioStreamAsync(AudioFilePath);
// Send the finish-task instruction to end the task
var finishTaskJson = GenerateFinishTaskJson(_taskId);
await SendAsync(finishTaskJson);
// Wait for the task-finished event
while (!_taskFinishedReceived && !_cancellationTokenSource.IsCancellationRequested) {
try {
await Task.Delay(100, _cancellationTokenSource.Token);
} catch (OperationCanceledException) {
// The task has been canceled, exit the loop
break;
}
}
// Close the connection
if (!_cancellationTokenSource.IsCancellationRequested) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token);
}
_cancellationTokenSource.Cancel();
try {
await receiveTask;
} catch (OperationCanceledException) {
// Ignore the operation canceled exception
}
}
private static async Task ReceiveMessagesAsync() {
try {
while (_webSocket.State == WebSocketState.Open && !_cancellationTokenSource.IsCancellationRequested) {
var message = await ReceiveMessageAsync(_cancellationTokenSource.Token);
if (message != null) {
var eventValue = message["header"]?["event"]?.GetValue<string>();
switch (eventValue) {
case "task-started":
Console.WriteLine("Task started successfully");
_taskStartedReceived = true;
break;
case "result-generated":
Console.WriteLine($"Recognition result: {message["payload"]?["output"]?["sentence"]?["text"]?.GetValue<string>()}");
if (message["payload"]?["usage"] != null && message["payload"]?["usage"]?["duration"] != null) {
Console.WriteLine($"Audio duration (seconds): {message["payload"]?["usage"]?["duration"]?.GetValue<int>()}");
}
break;
case "task-finished":
Console.WriteLine("Task finished");
_taskFinishedReceived = true;
_cancellationTokenSource.Cancel();
break;
case "task-failed":
Console.WriteLine($"Task failed: {message["header"]?["error_message"]?.GetValue<string>()}");
_cancellationTokenSource.Cancel();
break;
}
}
}
} catch (OperationCanceledException) {
// Ignore the operation canceled exception
}
}
private static async Task<JsonNode?> ReceiveMessageAsync(CancellationToken cancellationToken) {
var buffer = new byte[1024 * 4];
var segment = new ArraySegment<byte>(buffer);
var sb = new StringBuilder();
WebSocketReceiveResult result;
// Receive and concatenate fragments until EndOfMessage to avoid JSON parsing failures on long messages
do {
result = await _webSocket.ReceiveAsync(segment, cancellationToken);
if (result.MessageType == WebSocketMessageType.Close) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
return null;
}
sb.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
} while (!result.EndOfMessage);
return JsonNode.Parse(sb.ToString());
}
private static async Task SendAsync(string message) {
var buffer = Encoding.UTF8.GetBytes(message);
var segment = new ArraySegment<byte>(buffer);
await _webSocket.SendAsync(segment, WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
}
private static async Task SendAudioStreamAsync(string filePath) {
using (var audioStream = File.OpenRead(filePath)) {
var buffer = new byte[3200]; // Send 100ms of audio data each time
int bytesRead;
while ((bytesRead = await audioStream.ReadAsync(buffer, 0, buffer.Length)) > 0) {
var segment = new ArraySegment<byte>(buffer, 0, bytesRead);
await _webSocket.SendAsync(segment, WebSocketMessageType.Binary, true, _cancellationTokenSource.Token);
await Task.Delay(100); // 100ms interval
}
}
}
private static string GenerateRunTaskJson(string taskId) {
var runTask = new JsonObject {
["header"] = new JsonObject {
["action"] = "run-task",
["task_id"] = taskId,
["streaming"] = "duplex"
},
["payload"] = new JsonObject {
["task_group"] = "audio",
["task"] = "asr",
["function"] = "recognition",
["model"] = "qwen-audio-3.1-asr-flash-message",
["parameters"] = new JsonObject {
["format"] = "wav",
["sample_rate"] = 16000,
},
["input"] = new JsonObject()
}
};
return JsonSerializer.Serialize(runTask);
}
private static string GenerateFinishTaskJson(string taskId) {
var finishTask = new JsonObject {
["header"] = new JsonObject {
["action"] = "finish-task",
["task_id"] = taskId,
["streaming"] = "duplex"
},
["payload"] = new JsonObject {
["input"] = new JsonObject()
}
};
return JsonSerializer.Serialize(finishTask);
}
}
PHP
Save the dependency configuration as composer.json, run composer install, and then run the PHP example.
The example project has the following directory structure:
my-php-project/
├── composer.json
├── vendor/
└── index.php
The contents of composer.json are as follows. Adjust the dependency versions as needed:
{
"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/"
}
}
}
The contents of index.php are as follows:
<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;
use Ratchet\rfc6455\Messaging\Frame;
$api_key = getenv("DASHSCOPE_API_KEY");
$websocket_url = 'wss://' . parse_url(getenv('DASHSCOPE_BASE_URL'), PHP_URL_HOST) . '/api-ws/v1/inference';
$audio_file_path = 'asr_example.wav'; // Replace with the path to your audio file
$loop = Loop::get();
// Create a custom connector
$socketConnector = new SocketConnector($loop, [
'tcp' => [
'bindto' => '0.0.0.0:0',
],
'tls' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
]);
$connector = new Connector($loop, $socketConnector);
$headers = [
'Authorization' => 'bearer ' . $api_key
];
$connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $audio_file_path) {
echo "Connected to WebSocket server\n";
// Start a thread to receive WebSocket messages asynchronously
$taskId = generateTaskId();
$conn->on('message', function($msg) use ($conn, $loop, $audio_file_path, $taskId) {
$response = json_decode($msg, true);
if (isset($response['header']['event'])) {
handleEvent($conn, $response, $loop, $audio_file_path, $taskId);
} else {
echo "Unknown message format\n";
}
});
// Listen for the connection close
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed\n";
if ($code !== null) {
echo "Close code: " . $code . "\n";
}
if ($reason !== null) {
echo "Close reason: " . $reason . "\n";
}
});
// Generate the task ID
// Send the run-task instruction
sendRunTaskMessage($conn, $taskId);
}, function ($e) {
echo "Unable to connect: {$e->getMessage()}\n";
});
$loop->run();
/**
* Generate the task ID
* @return string
*/
function generateTaskId(): string {
return bin2hex(random_bytes(16));
}
/**
* Send the run-task instruction
* @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" => "asr",
"function" => "recognition",
"model" => "qwen-audio-3.1-asr-flash-message",
"parameters" => [
"format" => "wav",
"sample_rate" => 16000
],
"input" => new stdClass()
]
]);
echo "Preparing to send the run-task instruction: " . $runTaskMessage . "\n";
$conn->send($runTaskMessage);
echo "run-task instruction sent\n";
}
/**
* Read the audio file
* @param string $filePath
* @return bool|string
*/
function readAudioFile(string $filePath) {
$voiceData = file_get_contents($filePath);
if ($voiceData === false) {
echo "Unable to read the audio file\n";
}
return $voiceData;
}
/**
* Split the audio data
* @param string $data
* @param int $chunkSize
* @return array
*/
function splitAudioData(string $data, int $chunkSize): array {
return str_split($data, $chunkSize);
}
/**
* Send the finish-task instruction
* @param $conn
* @param $taskId
*/
function sendFinishTaskMessage($conn, $taskId) {
$finishTaskMessage = json_encode([
"header" => [
"action" => "finish-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"input" => new stdClass()
]
]);
echo "Preparing to send the finish-task instruction: " . $finishTaskMessage . "\n";
$conn->send($finishTaskMessage);
echo "finish-task instruction sent\n";
}
/**
* Handle events
* @param $conn
* @param $response
* @param $loop
* @param $audio_file_path
*/
function handleEvent($conn, $response, $loop, $audio_file_path, $taskId) {
static $chunks;
static $allChunksSent = false;
switch ($response['header']['event']) {
case 'task-started':
echo "Task started, sending audio data...\n";
// Read the audio file
$voiceData = readAudioFile($audio_file_path);
if ($voiceData === false) {
echo "Unable to read the audio file\n";
$conn->close();
return;
}
// Split the audio data
$chunks = splitAudioData($voiceData, 3200);
// Define the send function
$sendChunk = function() use ($conn, &$chunks, $loop, &$sendChunk, &$allChunksSent, $taskId) {
if (!empty($chunks)) {
$chunk = array_shift($chunks);
$binaryMsg = new Frame($chunk, true, Frame::OP_BINARY);
$conn->send($binaryMsg);
// Send the next chunk after 100ms
$loop->addTimer(0.1, $sendChunk);
} else {
echo "All data chunks sent\n";
$allChunksSent = true;
// Send the finish-task instruction
sendFinishTaskMessage($conn, $taskId);
}
};
// Start sending audio data
$sendChunk();
break;
case 'result-generated':
$result = $response['payload']['output']['sentence'];
echo "Recognition result: " . $result['text'] . "\n";
if (isset($response['payload']['usage']['duration'])) {
echo "Audio duration (seconds): " . $response['payload']['usage']['duration'] . "\n";
}
break;
case 'task-finished':
echo "Task finished\n";
$conn->close();
break;
case 'task-failed':
echo "Task failed\n";
echo "Error code: " . $response['header']['error_code'] . "\n";
echo "Error message: " . $response['header']['error_message'] . "\n";
$conn->close();
break;
case 'error':
echo "Error: " . $response['payload']['message'] . "\n";
break;
default:
echo "Unknown event: " . $response['header']['event'] . "\n";
break;
}
// If all data has been sent and the task has finished, close the connection
if ($allChunksSent && $response['header']['event'] == 'task-finished') {
// Wait 1 second to ensure all data has been transmitted
$loop->addTimer(1, function() use ($conn) {
$conn->close();
echo "Client closed the connection\n";
});
}
}