Skip to main content

Reconocimiento de voz en tiempo real - Qwen

El servicio de reconocimiento de voz en tiempo real recibe un flujo de audio y lo transcribe a texto puntuado en tiempo real. Úselo para subtitulado en vivo, reuniones en línea, chat de voz, asistentes inteligentes y escenarios similares.

Visión general

El servicio transmite audio y devuelve texto transcrito con baja latencia.
  • Reconoce chino mandarín con alta precisión, además de cantonés, sichuanés y otros dialectos.
  • Maneja entornos acústicos complejos, con detección automática de idioma y filtrado inteligente de audio no verbal.
  • Reconoce una variedad de estados emocionales, incluyendo sorpresa, calma, felicidad, tristeza, disgusto, ira y miedo.
  • Admite palabras clave personalizadas para mejorar la precisión del reconocimiento de términos específicos.
  • Admite la mejora de contexto para aumentar la precisión del reconocimiento al incluir historial de conversaciones o términos del dominio.
  • Genera marcas de tiempo para producir resultados de reconocimiento estructurados.
  • Acepta tasas de muestreo flexibles y múltiples formatos de audio para adaptarse a diferentes entornos de grabación.

Requisitos previos

Inicio rápido

Los siguientes ejemplos muestran cómo llamar al servicio de reconocimiento de voz en tiempo real a través del SDK de DashScope.
  • Qwen-Audio-ASR-Message
  • Qwen-Audio-ASR-Streaming / Fun-ASR-Realtime
  • Paraformer
Para el acceso mediante SDK, consulte Acceso mediante SDK a Qwen-Audio-ASR-Message.
import os

import dashscope

# Si no se ha configurado la variable de entorno, sustituya la siguiente línea por su API Key de Model Studio: api_key="sk-xxx"
# La siguiente configuración corresponde a la región de Singapur. Sustituya {WorkspaceId} por el ID real de su espacio de trabajo. El valor de region varía según la región.
client = dashscope.Client(
    api_key=os.environ["DASHSCOPE_API_KEY"],
    workspace="{WorkspaceId}",
    region="ap-southeast-1",
)

# audio.wav: un archivo WAV mono de 16 kHz con codificación PCM de 16 bits.
# recognize_file envía automáticamente el audio en fragmentos y conduce la tarea hasta su finalización
for event in client.audio.asr.recognize_file(
        "audio.wav",
        model="qwen-audio-3.1-asr-flash-message",
        format="wav",
        sample_rate=16000):
    payload = event.get('payload') or {}
    sentence = (payload.get('output') or {}).get('sentence') or {}
    if sentence.get('text') and not sentence.get('heartbeat'):
        print(sentence)

Funciones avanzadas

Resultados intermedios y pulido de texto

Para qwen-audio-3.1-asr-flash-message, intermediate_result_enabled controla los resultados intermedios y disfluency_removal_enabled controla el pulido de texto. Ambos tienen como valor predeterminado false. Cuando los resultados intermedios están habilitados, actualice la oración mostrada mediante sentence_id. El texto intermedio puede revisarse, por lo que no debe concatenar cada resultado. Cuando sentence_end=true, reemplace la oración con el resultado final. Con el pulido habilitado, los resultados finales pueden eliminar muletillas, repeticiones y autocorrecciones, además de refinar la redacción. Eventos del cliente · Eventos del servidor

Configuración de segmentación VAD

La detección de actividad de voz (VAD) determina cuándo termina un segmento continuo de habla, lo que desencadena el evento de resultado de reconocimiento final. Los siguientes modelos habilitan VAD del lado del servidor de forma predeterminada, pero sus nombres de parámetros y granularidad de ajuste difieren:
  • qwen-audio-3.1-asr-flash-message: Use vad_model para seleccionar near_meeting_16k (campo cercano) o far_field_meeting_16k (campo lejano, predeterminado). Para las definiciones de parámetros, consulte Eventos del cliente.
  • qwen-audio-3.1-asr-flash-streaming: Seleccione el modelo VAD con vad_model: near_meeting_16k para audio de campo cercano o far_field_meeting_16k para audio de campo lejano (predeterminado). Para las definiciones de parámetros, consulte Eventos del cliente.
  • qwen-audio-3.0-asr-flash-streaming / Fun-ASR-Realtime / Paraformer: Se configura mediante max_sentence_silence (el umbral de silencio VAD para la segmentación, en milisegundos). Cuando el silencio después de un segmento de habla supera este umbral, el sistema considera que la oración está completa.
  • Qwen3-ASR-Flash-Realtime: Se configura mediante session.turn_detection, que incluye silence_duration_ms (el umbral de duración de silencio que finaliza un turno al superarse; valor predeterminado del servidor 800, con 400 recomendado para escenarios de conversación y chat que requieren una segmentación rápida) y threshold (sensibilidad de detección VAD; valor predeterminado del servidor 0.2). Qwen3-ASR-Flash-Realtime también admite el modo Manual, que desactiva el VAD y utiliza la confirmación del lado del cliente para la segmentación. Para más detalles, consulte los modos de interacción de Qwen3-ASR-Flash-Realtime.
Los nombres de los parámetros varían según el protocolo: el mismo concepto se denomina max_sentence_silence en qwen-audio-3.0-asr-flash-streaming / Fun-ASR-Realtime / Paraformer, y silence_duration_ms en Qwen3-ASR-Flash-Realtime. Para consultar las definiciones completas de los campos, véase la Referencia de API.

Mejorar la precisión con palabras clave

Utilice palabras clave para mejorar la precisión de reconocimiento de términos específicos, como nombres de marcas, nombres de personas y terminología técnica. Para obtener detalles sobre la configuración y el uso de palabras clave, consulte Mejorar la precisión del reconocimiento.

Mejorar la precisión con la mejora de contexto

La mejora de contexto transmite el historial de conversaciones o terminología del dominio al modelo ASR para mejorar significativamente la precisión de transcripción de términos propios. Para obtener detalles sobre el uso y ejemplos de resultados, consulte Mejora de contexto.

Obtener marcas de tiempo

qwen-audio-3.1-asr-flash-message también devuelve marcas de tiempo a nivel de oración y palabra en los resultados finales. Para las definiciones de campos, consulte Eventos del servidor. Las familias de modelos Qwen-Audio-ASR-Streaming, Fun-ASR-Realtime y Paraformer generan marcas de tiempo tanto a nivel de oración como a nivel de palabra de forma predeterminada, lo cual permite la alineación de subtítulos, el resaltado de palabras clave, el karaoke tipo lectura sincronizada y escenarios similares. Qwen3-ASR-Flash-Realtime (qwen3-asr-flash-realtime) actualmente no devuelve marcas de tiempo. Si necesita marcas de tiempo, utilice Qwen-Audio-ASR-Streaming, Fun-ASR-Realtime o Paraformer. Para la transcripción de archivos, el modelo de transcripción de archivos de grabación ASR de Qwen qwen3-asr-flash-filetrans admite marcas de tiempo a nivel de palabra. Para más detalles, consulte Reconocimiento de voz no en tiempo real. Las marcas de tiempo se devuelven en milisegundos en dos niveles:
  • Nivel de oración: payload.output.sentence.begin_time y payload.output.sentence.end_time marcan el inicio y el final de una oración completa en el audio. En un resultado intermedio, end_time puede ser null y se completa con el valor final cuando termina la oración (sentence_end = true).
  • Nivel de palabra: La matriz payload.output.sentence.words, donde cada elemento contiene begin_time, end_time, text (el texto de la palabra o carácter) y punctuation (la puntuación que sigue a la palabra, o una cadena vacía si es none).
El siguiente extracto muestra la estructura de la respuesta:
{
  "payload": {
    "output": {
      "sentence": {
        "begin_time": 170,
        "end_time": 920,
        "text": "OK, I got it",
        "sentence_end": true,
        "words": [
          { "begin_time": 170, "end_time": 295, "text": "OK", "punctuation": "," },
          { "begin_time": 295, "end_time": 503, "text": "I", "punctuation": "" },
          { "begin_time": 503, "end_time": 711, "text": "got", "punctuation": "" },
          { "begin_time": 711, "end_time": 920, "text": "it", "punctuation": "" }
        ]
      }
    }
  }
}
Los nombres de campo anteriores siguen las rutas JSON de WebSocket. Los diferentes SDK exponen estos campos con sus propias convenciones de nomenclatura (claves de diccionario, propiedades de objeto, métodos getter, etc.). Para obtener la asignación completa de campos, consulte la referencia de la API de cada SDK. Para consultar las definiciones completas de los campos, consulte la referencia de la API.

Reconocimiento de emociones

Qwen3-ASR-Flash-Realtime y algunos modelos Paraformer pueden incluir el estado emocional del hablante en el resultado de la transcripción, pero ambos difieren en la granularidad de salida y en la forma en que se habilita la función. Qwen3-ASR-Flash-Realtime (qwen3-asr-flash-realtime): Siempre activo, sin necesidad de configuración. La emoción se devuelve a través de un campo emotion de nivel superior tanto en los eventos conversation.item.input_audio_transcription.text como conversation.item.input_audio_transcription.completed. El valor corresponde a una de siete emociones detalladas: surprised, neutral, happy, sad, disgusted, angry y fearful.
{
  "type": "conversation.item.input_audio_transcription.text",
  "emotion": "neutral",
  "text": "The weather is nice today",
  "stash": ""
}
Paraformer (paraformer-realtime-8k-v2): Este es el único modelo Paraformer que admite el reconocimiento de emociones. El resultado se devuelve a través de payload.output.sentence.emo_tag y payload.output.sentence.emo_confidence. El valor corresponde a una de tres polaridades: positive (como feliz o satisfecho), negative (como enojado o decaído) y neutral (sin emoción clara). La confianza oscila entre 0.0 y 1.0. El reconocimiento de emociones se devuelve solo cuando se cumplen todas las siguientes condiciones:
  • El modelo es paraformer-realtime-8k-v2.
  • La segmentación semántica está desactivada: semantic_punctuation_enabled = false (false es el valor predeterminado, por lo que no se requiere ninguna configuración especial).
  • El resultado se devuelve solo en el evento de fin de oración, donde sentence_end = true.
Para dejar de devolver los campos de emoción, establezca semantic_punctuation_enabled en true. Esto habilita la segmentación semántica y deja de devolver los campos emo_tag y emo_confidence. Los nombres de campo anteriores siguen las rutas JSON de WebSocket. Los diferentes SDK exponen estos campos con sus propias convenciones de nomenclatura (claves de diccionario, propiedades de objeto, métodos getter, etc.). Para obtener la asignación completa de campos, consulte la referencia de la API de cada SDK. Para consultar las definiciones completas de los campos, las restricciones de valores y los ejemplos, consulte la referencia de la API.

Filtrado de palabras sensibles

El filtrado de palabras sensibles reemplaza o elimina palabras sensibles en el resultado de reconocimiento. Utilícelo para inspección de calidad en centros de llamadas, cumplimiento de contenido, revisión de subtítulos y escenarios similares. Modelos admitidos: Qwen-Audio-ASR-Streaming y Fun-ASR-Realtime únicamente. Límite: Puede configurar hasta 32 palabras sensibles. Comportamiento predeterminado: Cuando no se pasa el parámetro special_word_filter, no se filtra ninguna palabra sensible. Cómo configurar: special_word_filter es un objeto JSON con tres subcampos:
  • filter_with_signed.word_list: Una matriz de cadenas que enumera las palabras sensibles que se reemplazarán por una cadena de caracteres * de igual longitud. Por ejemplo, con ["test"], "Help me test it" se convierte en "Help me **** it".
  • filter_with_empty.word_list: Una matriz de cadenas que enumera las palabras sensibles que se eliminarán por completo del resultado. Por ejemplo, con ["start"], "Is the game about to start" se convierte en "Is the game about to".
  • system_reserved_filter: Un valor booleano cuyo valor predeterminado es false. Determina si el filtrado de palabras sensibles está habilitado.
Ejemplo de configuración:
{
  "special_word_filter": {
    "filter_with_signed": {
      "word_list": ["test"]
    },
    "filter_with_empty": {
      "word_list": ["start", "occur"]
    },
    "system_reserved_filter": true
  }
}
Los diferentes SDK exponen estos parámetros con sus propias convenciones de nomenclatura (claves de diccionario, propiedades de objeto, métodos, etc.). Para obtener la asignación completa de campos, consulte la referencia de la API.

Llamar al protocolo WebSocket nativo

Los siguientes ejemplos muestran cómo conectarse directamente al servidor mediante el protocolo WebSocket nativo, para escenarios que no utilizan el SDK de DashScope. Cada ejemplo es una implementación mínima y ejecutable. Para el protocolo WebSocket, consulte la Referencia de API de cada modelo.
  • Qwen-Audio-ASR-Message
  • Qwen-Audio-ASR-Streaming / Fun-ASR-Realtime
  • Qwen3-ASR-Flash-Realtime
  • Paraformer
Use qwen-audio-3.1-asr-flash-message. Para ejemplos, consulte API de WebSocket.Los resultados finales se devuelven de forma predeterminada. Establezca intermediate_result_enabled=true para mostrar el texto mientras el usuario habla, y disfluency_removal_enabled=true para eliminar muletillas, repeticiones y autocorrecciones, así como para pulir el texto. Ambos tienen como valor predeterminado false.Eventos del cliente · Eventos del servidor

Aplicar en producción

Reutilización de conexiones (WebSocket)

Las conexiones WebSocket para Qwen-Audio-ASR-Streaming/Fun-ASR-Realtime y Paraformer admiten reutilización: una vez que finaliza una tarea de reconocimiento, puede iniciar la siguiente tarea sin necesidad de restablecer la conexión. Flujo de reutilización: El cliente envía finish-task. Después de que el servidor devuelve task-finished, el cliente puede enviar run-task nuevamente para iniciar una nueva tarea.
  1. Espere a que el servidor devuelva el evento task-finished antes de iniciar una nueva tarea.
  2. Las diferentes tareas sobre una conexión reutilizada deben usar valores task_id distintos.
  3. Cuando una tarea falla, el servidor devuelve un evento de error y cierra la conexión. Dicha conexión no se puede reutilizar.
  4. Si no se inicia ninguna nueva tarea dentro de los 60 segundos posteriores al final de una tarea, la conexión se cierra automáticamente.
Qwen3-ASR-Flash-Realtime utiliza un modelo de sesión y no admite la reutilización de conexiones. Cierre la conexión después de que finalice cada sesión. Para consultar los eventos de cada modelo, vea la referencia de la API correspondiente.

Mejores prácticas de alta concurrencia

El SDK de DashScope incluye un mecanismo de pooling integrado que reutiliza conexiones WebSocket y objetos de reconocimiento, lo que evita la sobrecarga de creación y destrucción frecuentes.
Actualmente, solo el SDK de Java admite esta función.

Requisitos previos

El SDK de Java combina un pool de conexiones integrado con un pool de objetos personalizado para lograr un rendimiento óptimo:
  • Pool de conexiones: El pool de conexiones OkHttp3 integrado en el SDK gestiona y reutiliza las conexiones WebSocket subyacentes, lo que reduce la sobrecarga del protocolo de enlace de red. Esta función está habilitada de forma predeterminada.
  • Pool de objetos: Construido sobre commons-pool2, el pool de objetos mantiene un conjunto de objetos Recognition cuyas conexiones ya están establecidas. Tomar prestado un objeto del pool elimina la latencia de configuración de la conexión y reduce significativamente la latencia del primer paquete.

Pasos de implementación

  1. Agregar dependencias Agregue dashscope-sdk-java y commons-pool2 a su archivo de configuración de dependencias, según la herramienta de compilación de su proyecto. Los siguientes ejemplos muestran la configuración para Maven y Gradle:
    • Maven
    • Gradle
    1. Abra el archivo pom.xml de su proyecto Maven.
    2. Agregue las siguientes dependencias dentro de la etiqueta <dependencies>.
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>dashscope-sdk-java</artifactId>
        <!-- Replace 'the-latest-version' with version 2.16.9 or later. You can look up version numbers at: https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
        <version>the-latest-version</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <!-- Replace 'the-latest-version' with the latest version. You can look up version numbers at: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <version>the-latest-version</version>
    </dependency>
    
    1. Guarde el archivo pom.xml.
    2. Ejecute un comando de Maven (como mvn clean install o mvn compile) para actualizar las dependencias del proyecto.
  2. Configurar el pool de conexiones Configure los parámetros clave del pool de conexiones mediante variables de entorno:

    Variable de entorno

    Descripción

    DASHSCOPE_CONNECTION_POOL_SIZEEl tamaño del pool de conexiones.Valor recomendado: al menos el doble de la concurrencia pico.Valor predeterminado: 32.
    DASHSCOPE_MAXIMUM_ASYNC_REQUESTSEl número máximo de solicitudes asíncronas.Valor recomendado: el mismo que DASHSCOPE_CONNECTION_POOL_SIZE.Valor predeterminado: 32.
    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOSTEl número máximo de solicitudes asíncronas por host.Valor recomendado: el mismo que DASHSCOPE_CONNECTION_POOL_SIZE.Valor predeterminado: 32.
  3. Configurar el pool de objetos Configure el tamaño del pool de objetos mediante una variable de entorno:

    Variable de entorno

    Descripción

    RECOGNITION_OBJECTPOOL_SIZEEl tamaño del pool de objetos.Valor recomendado: de 1.5 a 2 veces la concurrencia pico.Valor predeterminado: 500.
    • El tamaño del pool de objetos (RECOGNITION_OBJECTPOOL_SIZE) debe ser menor o igual al tamaño del pool de conexiones (DASHSCOPE_CONNECTION_POOL_SIZE). De lo contrario, cuando el pool de objetos solicite un objeto y el pool de conexiones esté lleno, el subproceso de llamada se bloqueará.
    • El tamaño del pool de objetos no debe exceder el límite de consultas por segundo (QPS) de su cuenta.
    Cree el pool de objetos con el siguiente código:
class RecognitionObjectPool {
    // ... For the full example, see the complete code.
    public static GenericObjectPool<Recognition> getInstance() {
        lock.lock();
        if (recognitionGenericObjectPool == null) {
            int objectPoolSize = getObjectivePoolSize();
            RecognitionObjectFactory recognitionObjectFactory =
                    new RecognitionObjectFactory();
            GenericObjectPoolConfig<Recognition> config =
                    new GenericObjectPoolConfig<>();
            config.setMaxTotal(objectPoolSize);
            config.setMaxIdle(objectPoolSize);
            config.setMinIdle(objectPoolSize);
            recognitionGenericObjectPool =
                    new GenericObjectPool<>(recognitionObjectFactory, config);
        }
        lock.unlock();
        return recognitionGenericObjectPool;
    }
}
  1. Tomar prestado un objeto Recognition del pool de objetos Cuando el número de objetos no devueltos supera el límite del pool de objetos, el sistema crea objetos Recognition adicionales. Estos nuevos objetos deben restablecer una conexión WebSocket y no pueden ser reutilizados.
recognizer = RecognitionObjectPool.getInstance().borrowObject();
  1. Realizar reconocimiento de voz Llame al método call o streamCall del objeto Recognition para realizar el reconocimiento de voz.
  2. Devolver el objeto Recognition Una vez finalizada la tarea de reconocimiento de voz, devuelva el objeto Recognition para que pueda ser reutilizado. No devuelva objetos con tareas sin finalizar o fallidas.
RecognitionObjectPool.getInstance().returnObject(recognizer);

Código completo

package org.alibaba.bailian.example.examples;

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

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

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

    public static void main(String[] args)
            throws NoApiKeyException, InterruptedException {
        // The following is the configuration for the China (Beijing) region. When calling, replace "{WorkspaceId}" with your actual workspace ID. Configurations differ by region.
        Constants.baseHttpApiUrl = "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1";
        checkoutEnv("DASHSCOPE_CONNECTION_POOL_SIZE", 32);
        checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS", 32);
        checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST", 32);
        checkoutEnv(RecognitionObjectPool.RECOGNITION_OBJECTPOOL_SIZE_ENV,
                RecognitionObjectPool.DEFAULT_OBJECT_POOL_SIZE);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Configuración recomendada

Las siguientes configuraciones se basan en resultados de pruebas obtenidos al ejecutar únicamente el servicio de reconocimiento de voz en tiempo real de Paraformer en servidores de Alibaba Cloud con las especificaciones indicadas. La concurrencia en una sola máquina es el número de tareas de reconocimiento de voz en tiempo real de Paraformer que se ejecutan simultáneamente (es decir, el número de hilos de trabajo).

Especificación de la máquina (Alibaba Cloud)

Concurrencia máxima por máquina

Tamaño del pool de objetos

Tamaño del pool de conexiones

4 vCPUs, 8 GiB1005002000
8 vCPUs, 16 GiB2005002000
16 vCPUs, 32 GiB4005002000

Gestión de recursos y manejo de errores

  • La tarea se ejecuta correctamente: Llame a GenericObjectPool.returnObject() para devolver el objeto Recognition al pool y poder reutilizarlo.
    No devuelva objetos Recognition con tareas sin finalizar o fallidas.
  • Fallo de la tarea: Cuando una excepción lanzada por el SDK o por su lógica de negocio interrumpe una tarea, realice las siguientes dos acciones:
    1. Cerrar activamente la conexión WebSocket subyacente.
    2. Invalidar el objeto en el pool de objetos para evitar que se reutilice.
// Close the connection.
recognizer.getDuplexApi().close(1000, "bye");
// Invalidate the failed recognizer in the object pool.
RecognitionObjectPool.getInstance().invalidateObject(recognizer);
  • Cuando el servicio devuelve un error TaskFailed, no se requiere manejo adicional.

Calentamiento y medición de latencia

Al evaluar el rendimiento, como la latencia de llamadas concurrentes para el SDK de Java de DashScope, le recomendamos que ejecute un calentamiento suficiente antes de la prueba formal.
Mecanismo de reutilización de conexiones
El SDK de Java de DashScope gestiona y reutiliza las conexiones WebSocket mediante un pool de conexiones global singleton. Este mecanismo funciona de la siguiente manera:
  • Creación bajo demanda: El SDK no crea previamente conexiones WebSocket al iniciar el servicio. En su lugar, establece conexiones bajo demanda en la primera llamada.
  • Reutilización con límite de tiempo: Una vez completada una solicitud, la conexión permanece en el pool hasta 60 segundos para su reutilización.
    • Si llega una nueva solicitud dentro de los 60 segundos siguientes, el SDK reutiliza la conexión existente y evita la sobrecarga de un nuevo protocolo de enlace.
    • Si una conexión permanece inactiva durante más de 60 segundos, el SDK la cierra automáticamente para liberar recursos.
Por qué es importante el calentamiento
En los siguientes escenarios, es posible que el pool de conexiones no tenga una conexión activa disponible para reutilizar, por lo que la solicitud deberá crear una nueva conexión:
  • La aplicación acaba de iniciarse y aún no ha realizado ninguna llamada.
  • El servicio ha estado inactivo durante más de 60 segundos, por lo que las conexiones agrupadas se han cerrado debido a tiempo de espera agotado.
En estos escenarios, las primeras solicitudes o las iniciales desencadenan el proceso completo de conexión WebSocket (incluido el protocolo de enlace TCP, la negociación TLS y la actualización de protocolo). Su latencia de extremo a extremo es significativamente mayor que la de las solicitudes posteriores que reutilizan conexiones.
Enfoque recomendado
Antes de ejecutar una prueba de carga formal o medir la latencia, siga estos pasos de calentamiento:
  1. Simule el nivel de concurrencia de la prueba formal enviando varias llamadas con antelación (por ejemplo, durante 1 a 2 minutos) para llenar completamente el pool de conexiones.
  2. Después de confirmar que el pool de conexiones ha establecido y mantenido suficientes conexiones activas, comience a recopilar los datos de rendimiento formales.

Mejorar la precisión del reconocimiento

  • Elija un modelo que coincida con la frecuencia de muestreo: Para audio telefónico de 8 kHz, utilice directamente un modelo de 8 kHz. Esto evita la pérdida de información causada por el sobremuestreo a 16 kHz.
  • Mejore la calidad del audio de entrada: Utilice un micrófono de alta calidad y grabe en un entorno con una alta relación señal-ruido y sin eco. En la capa de aplicación, puede integrar algoritmos como reducción de ruido (por ejemplo, RNNoise) y cancelación de eco acústico (AEC) para el preprocesamiento.

Configurar una estrategia de tolerancia a fallos

  • Reconexión del lado del cliente: El cliente debe implementar la reconexión automática para manejar la inestabilidad de la red. A continuación se muestra una implementación de referencia para el SDK de Python:
    1. Capturar excepciones: Implemente el método on_error en la clase Callback. El SDK de dashscope llama a este método cuando encuentra un error de red u otro problema.
    2. Señalizar el estado: Cuando se activa on_error, establezca una señal de reconexión. En Python, puede utilizar threading.Event, un indicador de señal seguro para subprocesos.
    3. Bucle de reconexión: Envuelva la lógica principal en un bucle for (por ejemplo, reintente 3 veces). Cuando se detecta la señal de reconexión, se interrumpe la ronda de reconocimiento actual, se limpian los recursos y, después de unos segundos, el bucle se ejecuta nuevamente para crear una conexión completamente nueva.
  • Configure un latido para mantener activa la conexión: Para mantener una conexión persistente con el servidor, establezca el parámetro heartbeat en true. De este modo, la conexión con el servidor permanecerá abierta incluso cuando el audio no contenga sonido durante un largo periodo de tiempo.
  • Límites de tasa del modelo: Al llamar a la API del modelo, tenga en cuenta las reglas de limitación de tasa del modelo.

Modelos y regiones compatibles

  • Singapore
  • China (Beijing)
Para llamar a los siguientes modelos, utilice una API Key para la región de Singapore:
  • Qwen-Audio-ASR-Message: qwen-audio-3.1-asr-flash-message
  • Qwen-Audio-ASR-Streaming: qwen-audio-3.1-asr-flash-streaming, qwen-audio-3.0-asr-flash-streaming
  • Fun-ASR-Realtime: fun-asr-realtime (versión estable)
  • Qwen-ASR-Realtime: qwen3-asr-flash-realtime (versión estable)

Referencia de API

Para la conexión mediante AOQ, seleccione la documentación de su modelo: Qwen-Audio-ASR-Message, Qwen-Audio-ASR-Streaming o Fun-ASR-Realtime. Para conocer el alcance exacto de compatibilidad, consulte Compatibilidad de modelos y protocolos.

FAQ

¿Qué formatos de audio admite el reconocimiento de voz en tiempo real?

qwen-audio-3.1-asr-flash-message Admite pcm, wav, mp3, opus, speex, aac y amr. Establezca sample_rate en 16000. Para requisitos de codificación y contenedor, consulte Eventos del cliente. Los modelos Qwen-Audio-ASR-Streaming, Fun-ASR-Realtime y Paraformer admiten los formatos pcm, wav, mp3, opus, speex, aac y amr. Para el modelo Qwen3-ASR-Flash-Realtime, se recomienda el formato pcm u opus. Otros formatos (como wav, aac y amr) son aceptados por la capa de validación session.update, pero la decodificación del lado del servidor podría fallar. Confirme que el flujo de audio utilice un formato recomendado antes de enviarlo.

¿Cuál es la diferencia entre el SDK y la API de WebSocket, y cómo debo elegir?

El SDK de DashScope encapsula detalles como la gestión de conexiones WebSocket, la autenticación y la reconexión, lo que lo hace adecuado para una integración rápida. Conectarse directamente a la API de WebSocket proporciona un control más granular y es apropiado para lenguajes de programación no cubiertos por el SDK o para escenarios que requieren una gestión personalizada de la conexión. Se recomienda utilizar primero el SDK.

¿Cómo puedo mejorar la precisión de reconocimiento de nombres propios?

Utilice palabras clave o mejora de contexto. Para obtener métodos de configuración detallados y notas de uso, consulte Mejorar la precisión del reconocimiento.

¿Qué debo hacer cuando la conexión se cae frecuentemente?

Implemente la reconexión del lado del cliente y habilite el parámetro de latido (heartbeat=true) para evitar que la conexión se interrumpa cuando no hay audio durante un período prolongado. Para obtener estrategias detalladas de tolerancia a fallos, consulte Aplicación en producción.