From 2df268dcc5f2c7dc8f44be9fe542b77f2a7053fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=81=A5=E4=BB=99?= Date: Wed, 15 Jul 2026 16:16:51 +0800 Subject: [PATCH 1/4] feat(model/omni):support session.audio.input/output --- .../audio/omni/OmniRealtimeAudioCodec.java | 30 +++++ .../omni/OmniRealtimeAudioFormatConfig.java | 107 ++++++++++++++++++ .../audio/omni/OmniRealtimeConfig.java | 69 ++++++++++- .../audio/omni/OmniRealtimeConstants.java | 8 ++ 4 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioCodec.java create mode 100644 src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioFormatConfig.java diff --git a/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioCodec.java b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioCodec.java new file mode 100644 index 00000000..f484504c --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioCodec.java @@ -0,0 +1,30 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.audio.omni; + +import com.google.gson.annotations.SerializedName; + +/** + * Audio codec/format type used in the new-style upstream/downstream audio configuration (see {@link + * OmniRealtimeAudioFormatConfig}), e.g. + */ +public enum OmniRealtimeAudioCodec { + @SerializedName("pcm") + PCM, + @SerializedName("wav") + WAV; + + public static OmniRealtimeAudioCodec fromValue(String type) { + if (type == null) { + return PCM; + } + switch (type.toLowerCase()) { + case "pcm": + return PCM; + case "wav": + return WAV; + default: + throw new IllegalArgumentException( + "Unsupported audio format: " + type + ", supported values are: pcm, wav"); + } + } +} diff --git a/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioFormatConfig.java b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioFormatConfig.java new file mode 100644 index 00000000..81fa89ef --- /dev/null +++ b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioFormatConfig.java @@ -0,0 +1,107 @@ +// Copyright (c) Alibaba, Inc. and its affiliates. +package com.alibaba.dashscope.audio.omni; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import lombok.Data; + +/** + * New-style upstream/downstream audio format configuration, used to build the nested {@code + * audio.input.format} / {@code audio.output.format} structure in the session.update request, e.g. + * + *
{@code
+ * "session": {
+ *   "audio": {
+ *     "input": { "format": { "type": "pcm", "sample_rate": 16000 } },
+ *     "output": { "format": { "type": "pcm", "sample_rate": 24000 } }
+ *   }
+ * }
+ * }
+ */ +@Data +public class OmniRealtimeAudioFormatConfig { + private static final Set SUPPORTED_SAMPLE_RATES = + new HashSet<>(Arrays.asList(8000, 16000, 24000, 48000)); + + /** audio format type, supports "pcm" and "wav", defaults to "pcm". */ + private OmniRealtimeAudioCodec type = OmniRealtimeAudioCodec.PCM; + + /** sample rate in Hz, supports 8000/16000/24000/48000. */ + private int sampleRate; + + /** + * Extra format parameters for future extension (e.g. speech rate). These entries are merged into + * the {@code format} node alongside {@code type}/{@code sample_rate} when serialized, so new + * server-side parameters can be passed through without changing this SDK. Reserved keys ({@code + * type}/{@code sample_rate}) set here are ignored to avoid overriding the typed fields. + */ + private Map parameters; + + public OmniRealtimeAudioFormatConfig() {} + + /** + * Creates a new audio format config. + * + * @param type audio format type + * @param sampleRate sample rate in Hz, must be one of 8000/16000/24000/48000 + */ + public OmniRealtimeAudioFormatConfig(OmniRealtimeAudioCodec type, int sampleRate) { + setType(type); + setSampleRate(sampleRate); + } + + /** + * Creates a new audio format config. + * + * @param type "pcm" or "wav" + * @param sampleRate sample rate in Hz, must be one of 8000/16000/24000/48000 + */ + public OmniRealtimeAudioFormatConfig(String type, int sampleRate) { + this(OmniRealtimeAudioCodec.fromValue(type), sampleRate); + } + + public void setType(OmniRealtimeAudioCodec type) { + this.type = (type == null) ? OmniRealtimeAudioCodec.PCM : type; + } + + public void setSampleRate(int sampleRate) { + if (!SUPPORTED_SAMPLE_RATES.contains(sampleRate)) { + throw new IllegalArgumentException( + "Unsupported sample rate: " + + sampleRate + + ", supported values are: 8000, 16000, 24000, 48000"); + } + this.sampleRate = sampleRate; + } + + /** + * Creates a new audio format config, convenience factory method equivalent to {@code new + * OmniRealtimeAudioFormatConfig(type, sampleRate)}. + * + * @param type "pcm" or "wav" + * @param sampleRate sample rate in Hz, must be one of 8000/16000/24000/48000 + * @return the created config + */ + public static OmniRealtimeAudioFormatConfig of(String type, int sampleRate) { + return new OmniRealtimeAudioFormatConfig(type, sampleRate); + } + + /** + * Adds a single extra format parameter for future extension (e.g. {@code addParameter("rate", + * 1.2)} for speech rate). Merged into the {@code format} node when serialized. + * + * @param key parameter name + * @param value parameter value + * @return this config for chaining + */ + public OmniRealtimeAudioFormatConfig addParameter(String key, Object value) { + if (this.parameters == null) { + this.parameters = new HashMap<>(); + } + this.parameters.put(key, value); + return this; + } +} diff --git a/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeConfig.java b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeConfig.java index 330f130c..4f6ae343 100644 --- a/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeConfig.java +++ b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeConfig.java @@ -22,12 +22,34 @@ public class OmniRealtimeConfig { /** voice to be used in session ,not need in qwen-asr-realtime */ @Builder.Default String voice = null; - /** input audio format */ + /** + * input audio format (legacy). Only supports pcm16/pcm24. Ignored when {@link #inputAudio} is + * set. + */ @Builder.Default OmniRealtimeAudioFormat inputAudioFormat = OmniRealtimeAudioFormat.PCM_16000HZ_MONO_16BIT; - /** output audio format */ + /** + * output audio format (legacy). Only supports pcm16/pcm24. Ignored when {@link #outputAudio} is + * set. + */ @Builder.Default OmniRealtimeAudioFormat outputAudioFormat = OmniRealtimeAudioFormat.PCM_24000HZ_MONO_16BIT; + /** + * New-style input(upstream) audio format, supports "pcm"/"wav" format and 8k/16k/24k/48k sample + * rate, e.g. {@code new OmniRealtimeAudioFormatConfig("pcm", 16000)}. When set, this takes + * precedence over the legacy {@link #inputAudioFormat} and will be serialized as the nested + * {@code audio.input.format} structure instead of the legacy flat {@code input_audio_format} + * field. Setting only one of {@link #inputAudio}/{@link #outputAudio} is fine, the other side + * falls back to its legacy field value. + */ + @Builder.Default OmniRealtimeAudioFormatConfig inputAudio = null; + /** + * New-style output(downstream) audio format, see {@link #inputAudio}. When set, this takes + * precedence over the legacy {@link #outputAudioFormat} and will be serialized as the nested + * {@code audio.output.format} structure instead of the legacy flat {@code output_audio_format} + * field. + */ + @Builder.Default OmniRealtimeAudioFormatConfig outputAudio = null; /** enable transcription for input audio */ @Builder.Default boolean enableInputAudioTranscription = true; /** model used for input audio transcription */ @@ -61,8 +83,29 @@ public JsonObject getConfig() { if (voice != null) { config.put(OmniRealtimeConstants.VOICE, voice); } - config.put(OmniRealtimeConstants.INPUT_AUDIO_FORMAT, inputAudioFormat); - config.put(OmniRealtimeConstants.OUTPUT_AUDIO_FORMAT, outputAudioFormat); + if (inputAudio != null || outputAudio != null) { + // New-style nested audio format, takes precedence over the legacy flat fields. The side + // that is not explicitly set falls back to the legacy inputAudioFormat/outputAudioFormat + // value so that the resulting "audio" node is always complete and consistent. + OmniRealtimeAudioFormatConfig effectiveInputAudio = + inputAudio != null + ? inputAudio + : new OmniRealtimeAudioFormatConfig( + OmniRealtimeAudioCodec.PCM, inputAudioFormat.getSampleRate()); + OmniRealtimeAudioFormatConfig effectiveOutputAudio = + outputAudio != null + ? outputAudio + : new OmniRealtimeAudioFormatConfig( + OmniRealtimeAudioCodec.PCM, outputAudioFormat.getSampleRate()); + Map audio = new HashMap<>(); + audio.put(OmniRealtimeConstants.AUDIO_INPUT, buildAudioDirectionNode(effectiveInputAudio)); + audio.put(OmniRealtimeConstants.AUDIO_OUTPUT, buildAudioDirectionNode(effectiveOutputAudio)); + config.put(OmniRealtimeConstants.AUDIO, audio); + } else { + // Legacy flat fields, kept unchanged for full backward compatibility. + config.put(OmniRealtimeConstants.INPUT_AUDIO_FORMAT, inputAudioFormat); + config.put(OmniRealtimeConstants.OUTPUT_AUDIO_FORMAT, outputAudioFormat); + } if (enableInputAudioTranscription) { Map inputTranscriptionConfig = new HashMap<>(); inputTranscriptionConfig.put( @@ -140,4 +183,22 @@ public JsonObject getConfig() { JsonObject jsonObject = gson.toJsonTree(config).getAsJsonObject(); return jsonObject; } + + /** + * Builds the {@code { "format": { "type": ..., "sample_rate": ... } } } node used under {@code + * audio.input} / {@code audio.output}. + */ + private Map buildAudioDirectionNode(OmniRealtimeAudioFormatConfig config) { + Map format = new HashMap<>(); + // Merge extra parameters first so that the typed fields below always take precedence and + // can't be accidentally overridden by reserved keys (type/sample_rate). + if (config.getParameters() != null) { + format.putAll(config.getParameters()); + } + format.put(OmniRealtimeConstants.AUDIO_FORMAT_TYPE, config.getType()); + format.put(OmniRealtimeConstants.SAMPLE_RATE, config.getSampleRate()); + Map direction = new HashMap<>(); + direction.put(OmniRealtimeConstants.AUDIO_FORMAT, format); + return direction; + } } diff --git a/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeConstants.java b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeConstants.java index f942f2b3..fcd07f1e 100644 --- a/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeConstants.java +++ b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeConstants.java @@ -8,6 +8,14 @@ public class OmniRealtimeConstants { public static final String VOICE = "voice"; public static final String INPUT_AUDIO_FORMAT = "input_audio_format"; public static final String OUTPUT_AUDIO_FORMAT = "output_audio_format"; + // New-style nested upstream/downstream audio format constants, see session.update: + // { "audio": { "input": { "format": { "type": "pcm", "sample_rate": 16000 } }, + // "output": { "format": { "type": "pcm", "sample_rate": 24000 } } } } + public static final String AUDIO = "audio"; + public static final String AUDIO_INPUT = "input"; + public static final String AUDIO_OUTPUT = "output"; + public static final String AUDIO_FORMAT = "format"; + public static final String AUDIO_FORMAT_TYPE = "type"; public static final String INPUT_AUDIO_TRANSCRIPTION = "input_audio_transcription"; public static final String INPUT_AUDIO_TRANSCRIPTION_MODEL = "model"; public static final String INPUT_AUDIO_TRANSCRIPTION_CORPUS = "corpus"; From 50ad0520998c4375e71cfe80f681c4f0a4a1cf85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=81=A5=E4=BB=99?= Date: Tue, 28 Jul 2026 15:53:04 +0800 Subject: [PATCH 2/4] fix(model/tts): stoplatch safe problem when network loss --- .../audio/ttsv2/SpeechSynthesizerV2.java | 83 ++++++++++--------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizerV2.java b/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizerV2.java index 1837a9a2..c96e1c76 100644 --- a/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizerV2.java +++ b/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizerV2.java @@ -34,7 +34,7 @@ public final class SpeechSynthesizerV2 implements AudioWebsocketCallback { private SpeechSynthesisState state = SpeechSynthesisState.IDLE; private ResultCallback callback; - private AtomicReference stopLatch = new AtomicReference<>(null); + private final AtomicReference stopLatch = new AtomicReference<>(null); private SpeechSynthesisParam parameters; @@ -48,7 +48,7 @@ public final class SpeechSynthesizerV2 implements AudioWebsocketCallback { private double recvAudioLength = 0; @Getter @Setter private long startedTimeout = 5000; @Getter @Setter private long firstAudioTimeout = -1; - private AtomicReference startLatch = new AtomicReference<>(null); + private final AtomicReference startLatch = new AtomicReference<>(null); private AudioWebsocketRequest websocketRequest; private String websocketUrl = Constants.baseWebsocketApiUrl; private JsonObject bailianHeader = new JsonObject(); @@ -102,8 +102,8 @@ public void updateParamAndCallback( this.canceled.set(false); // reset inner params - this.stopLatch = new AtomicReference<>(null); - this.startLatch = new AtomicReference<>(null); + this.stopLatch.set(null); + this.startLatch.set(null); this.firstAudioTimeout = -1; this.isFirst = true; this.audioStream = new ByteArrayOutputStream(); @@ -168,6 +168,20 @@ public void close() { log.warn("Failed to close websocket connection: " + e.getMessage()); } } + releaseLatches(); + } + + /** Release startLatch and stopLatch to unblock any waiting threads. */ + private void releaseLatches() { + CountDownLatch startLatch = this.startLatch.get(); + if (startLatch != null && startLatch.getCount() > 0) { + startLatch.countDown(); + } + + CountDownLatch stopLatch = this.stopLatch.get(); + if (stopLatch != null && stopLatch.getCount() > 0) { + stopLatch.countDown(); + } } private synchronized void sendTaskMessage(String action, JsonObject input) { @@ -308,16 +322,7 @@ public void onError(WebSocket webSocket, Throwable t) { // callback error first callback.onError(new ApiException(t)); } - - CountDownLatch startLatch = this.startLatch.get(); - if (startLatch != null && startLatch.getCount() > 0) { - startLatch.countDown(); - } - - CountDownLatch stopLatch = this.stopLatch.get(); - if (stopLatch != null && stopLatch.getCount() > 0) { - stopLatch.countDown(); - } + releaseLatches(); if (audioStream != null) { audioStream.reset(); @@ -327,21 +332,24 @@ public void onError(WebSocket webSocket, Throwable t) { @Override public void onClose(int code, String reason) { log.warn("WebSocket connection closed: " + reason + " (" + code + ")"); + releaseLatches(); } private void handleTaskStarted(JsonObject message) { log.info("Task started"); state = SpeechSynthesisState.TTS_STARTED; firstPackageTimeStamp = -1; - if (startLatch.get() != null) { - startLatch.get().countDown(); + CountDownLatch startLatch = this.startLatch.get(); + if (startLatch != null) { + startLatch.countDown(); } } private void handleTaskFinished(JsonObject message) { log.info("Task finished"); - if (stopLatch.get() != null) { - stopLatch.get().countDown(); + CountDownLatch stopLatch = this.stopLatch.get(); + if (stopLatch != null) { + stopLatch.countDown(); } if (callback != null) { callback.onComplete(); @@ -370,8 +378,9 @@ private void handleTaskFailed(JsonObject message) { .build(); callback.onError(new ApiException(status)); } - if (stopLatch.get() != null) { - stopLatch.get().countDown(); + CountDownLatch stopLatch = this.stopLatch.get(); + if (stopLatch != null) { + stopLatch.countDown(); } } @@ -421,9 +430,10 @@ private void startStream(boolean enableSsml) throws NoApiKeyException, Interrupt } checkConnectStatus(); // check websocket connection, if socket is closed. - startLatch = new AtomicReference<>(new CountDownLatch(1)); + CountDownLatch startLatch = new CountDownLatch(1); + this.startLatch.set(startLatch); startSynthesizer(enableSsml); - boolean startResult = startLatch.get().await(startedTimeout, TimeUnit.MILLISECONDS); + boolean startResult = startLatch.await(startedTimeout, TimeUnit.MILLISECONDS); if (!startResult) { throw new RuntimeException( "TimeoutError: waiting for task started more than " + startedTimeout + " ms."); @@ -458,25 +468,24 @@ public void streamingComplete(long completeTimeoutMillis) { "State invalid: expect stream input tts state is started but " + state.getValue())); } } - stopLatch = new AtomicReference<>(new CountDownLatch(1)); + CountDownLatch stopLatch = new CountDownLatch(1); + this.stopLatch.set(stopLatch); stopSynthesizer(); - if (stopLatch.get() != null) { - try { - if (completeTimeoutMillis > 0) { - log.debug("start waiting for stopLatch"); - if (!stopLatch.get().await(completeTimeoutMillis, TimeUnit.MILLISECONDS)) { - throw new RuntimeException("TimeoutError: waiting for streaming complete"); - } - } else { - log.debug("start waiting for stopLatch"); - stopLatch.get().await(); + try { + if (completeTimeoutMillis > 0) { + log.debug("start waiting for stopLatch"); + if (!stopLatch.await(completeTimeoutMillis, TimeUnit.MILLISECONDS)) { + throw new RuntimeException("TimeoutError: waiting for streaming complete"); } - log.debug("stopLatch is done"); - } catch (InterruptedException ignored) { - log.error("Interrupted while waiting for streaming complete"); - Thread.currentThread().interrupt(); + } else { + log.debug("start waiting for stopLatch"); + stopLatch.await(); } + log.debug("stopLatch is done"); + } catch (InterruptedException ignored) { + log.error("Interrupted while waiting for streaming complete"); + Thread.currentThread().interrupt(); } } From 98269751bedfafe5dee93d84571d5458cefbed96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=81=A5=E4=BB=99?= Date: Tue, 28 Jul 2026 16:19:11 +0800 Subject: [PATCH 3/4] feat(model/audio): upgrade audio models use dedicated thread pool --- .../audio/asr/recognition/Recognition.java | 18 ++++++++++--- .../audio/ttsv2/SpeechSynthesizer.java | 26 +++++++++++++++---- .../dashscope/protocol/ConnectionOptions.java | 12 +++++++++ .../protocol/okhttp/OkHttpClientFactory.java | 17 +++++++----- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/alibaba/dashscope/audio/asr/recognition/Recognition.java b/src/main/java/com/alibaba/dashscope/audio/asr/recognition/Recognition.java index 2776a64b..45db08a4 100644 --- a/src/main/java/com/alibaba/dashscope/audio/asr/recognition/Recognition.java +++ b/src/main/java/com/alibaba/dashscope/audio/asr/recognition/Recognition.java @@ -104,7 +104,9 @@ public Recognition() { .task(Task.ASR.getValue()) .function(Function.RECOGNITION.getValue()) .build(); - duplexApi = new SynchronizeFullDuplexApi<>(serviceOption); + ConnectionOptions connectionOptions = ConnectionOptions.builder().build(); + connectionOptions.setUseDefaultClient(false); + duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); } public Recognition(ConnectionOptions connectionOptions) { @@ -117,7 +119,12 @@ public Recognition(ConnectionOptions connectionOptions) { .task(Task.ASR.getValue()) .function(Function.RECOGNITION.getValue()) .build(); - duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); + ConnectionOptions thisOptions = connectionOptions; + if (connectionOptions == null) { + thisOptions = ConnectionOptions.builder().build(); + thisOptions.setUseDefaultClient(false); + } + duplexApi = new SynchronizeFullDuplexApi<>(thisOptions, serviceOption); } public Recognition(ConnectionOptions connectionOptions, String baseUrl) { @@ -131,7 +138,12 @@ public Recognition(ConnectionOptions connectionOptions, String baseUrl) { .baseWebSocketUrl(baseUrl) .function(Function.RECOGNITION.getValue()) .build(); - duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); + ConnectionOptions thisOptions = connectionOptions; + if (connectionOptions == null) { + thisOptions = ConnectionOptions.builder().build(); + thisOptions.setUseDefaultClient(false); + } + duplexApi = new SynchronizeFullDuplexApi<>(thisOptions, serviceOption); } public Flowable streamCall( diff --git a/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizer.java b/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizer.java index bcd53688..a4d53f09 100644 --- a/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizer.java +++ b/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizer.java @@ -96,7 +96,12 @@ public SpeechSynthesizer( .baseWebSocketUrl(baseUrl) .passTaskStarted(true) .build(); - duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); + ConnectionOptions thisOptions = connectionOptions; + if (connectionOptions == null) { + thisOptions = ConnectionOptions.builder().build(); + thisOptions.setUseDefaultClient(false); + } + duplexApi = new SynchronizeFullDuplexApi<>(thisOptions, serviceOption); this.callback = callback; this.asyncCall = this.callback != null; } @@ -120,7 +125,12 @@ public SpeechSynthesizer(String baseUrl, ConnectionOptions connectionOptions) { .baseWebSocketUrl(baseUrl) .passTaskStarted(true) .build(); - duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); + ConnectionOptions thisOptions = connectionOptions; + if (connectionOptions == null) { + thisOptions = ConnectionOptions.builder().build(); + thisOptions.setUseDefaultClient(false); + } + duplexApi = new SynchronizeFullDuplexApi<>(thisOptions, serviceOption); this.callback = null; } @@ -137,7 +147,9 @@ public SpeechSynthesizer() { .function(Function.SPEECH_SYNTHESIZER.getValue()) .passTaskStarted(true) .build(); - duplexApi = new SynchronizeFullDuplexApi<>(serviceOption); + ConnectionOptions connectionOptions = ConnectionOptions.builder().build(); + connectionOptions.setUseDefaultClient(false); + duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); this.callback = null; } @@ -186,7 +198,9 @@ public SpeechSynthesizer( .baseWebSocketUrl(baseUrl) .passTaskStarted(true) .build(); - duplexApi = new SynchronizeFullDuplexApi<>(serviceOption); + ConnectionOptions connectionOptions = ConnectionOptions.builder().build(); + connectionOptions.setUseDefaultClient(false); + duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); this.callback = callback; this.asyncCall = this.callback != null; } @@ -215,7 +229,9 @@ public SpeechSynthesizer( .function(Function.SPEECH_SYNTHESIZER.getValue()) .passTaskStarted(true) .build(); - duplexApi = new SynchronizeFullDuplexApi<>(serviceOption); + ConnectionOptions connectionOptions = ConnectionOptions.builder().build(); + connectionOptions.setUseDefaultClient(false); + duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); this.callback = callback; this.asyncCall = this.callback != null; } diff --git a/src/main/java/com/alibaba/dashscope/protocol/ConnectionOptions.java b/src/main/java/com/alibaba/dashscope/protocol/ConnectionOptions.java index d11e0710..a2059c6d 100644 --- a/src/main/java/com/alibaba/dashscope/protocol/ConnectionOptions.java +++ b/src/main/java/com/alibaba/dashscope/protocol/ConnectionOptions.java @@ -93,4 +93,16 @@ public boolean isUseDefaultClient() { public void setUseDefaultClient(boolean useDefaultClient) { this.useDefaultClient = useDefaultClient; } + + public boolean hasCustomConnectTimeout() { + return connectTimeout != null; + } + + public boolean hasCustomWriteTimeout() { + return writeTimeout != null; + } + + public boolean hasCustomReadTimeout() { + return readTimeout != null; + } } diff --git a/src/main/java/com/alibaba/dashscope/protocol/okhttp/OkHttpClientFactory.java b/src/main/java/com/alibaba/dashscope/protocol/okhttp/OkHttpClientFactory.java index 8d6d1651..749f5430 100644 --- a/src/main/java/com/alibaba/dashscope/protocol/okhttp/OkHttpClientFactory.java +++ b/src/main/java/com/alibaba/dashscope/protocol/okhttp/OkHttpClientFactory.java @@ -75,12 +75,17 @@ public static OkHttpClient getOkHttpClient() { } public static OkHttpClient getNewOkHttpClient(ConnectionOptions connectionOptions) { - Builder builder = - Holder.INSTANCE - .newBuilder() - .connectTimeout(connectionOptions.getConnectTimeout()) - .readTimeout(connectionOptions.getReadTimeout()) - .writeTimeout(connectionOptions.getWriteTimeout()); + Builder builder = Holder.INSTANCE.newBuilder(); + + if (connectionOptions.hasCustomConnectTimeout()) { + builder.connectTimeout(connectionOptions.getConnectTimeout()); + } + if (connectionOptions.hasCustomReadTimeout()) { + builder.readTimeout(connectionOptions.getReadTimeout()); + } + if (connectionOptions.hasCustomWriteTimeout()) { + builder.writeTimeout(connectionOptions.getWriteTimeout()); + } // Configure proxy if available if (connectionOptions.getProxy() != null) { From 233a27fc66e1f3d5627eac2895c49e7dd80b380f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=81=A5=E4=BB=99?= Date: Wed, 29 Jul 2026 17:09:14 +0800 Subject: [PATCH 4/4] fix: duplicate code optimization and minor bug fixes --- .../audio/asr/recognition/Recognition.java | 33 ++++++++------- .../omni/OmniRealtimeAudioFormatConfig.java | 4 +- .../audio/ttsv2/SpeechSynthesizer.java | 41 +++++++++---------- .../audio/ttsv2/SpeechSynthesizerV2.java | 7 ++-- 4 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/alibaba/dashscope/audio/asr/recognition/Recognition.java b/src/main/java/com/alibaba/dashscope/audio/asr/recognition/Recognition.java index 45db08a4..2c17ca1e 100644 --- a/src/main/java/com/alibaba/dashscope/audio/asr/recognition/Recognition.java +++ b/src/main/java/com/alibaba/dashscope/audio/asr/recognition/Recognition.java @@ -94,6 +94,19 @@ public static RecognitionParamWithStream FromRecognitionParam( } } + /** + * Returns the given options, or a default instance routed to the dedicated audio client + * (useDefaultClient=false) when null. + */ + private static ConnectionOptions defaultAudioOptionsIfNull(ConnectionOptions connectionOptions) { + if (connectionOptions != null) { + return connectionOptions; + } + ConnectionOptions defaultOptions = ConnectionOptions.builder().build(); + defaultOptions.setUseDefaultClient(false); + return defaultOptions; + } + public Recognition() { serviceOption = ApiServiceOption.builder() @@ -104,9 +117,7 @@ public Recognition() { .task(Task.ASR.getValue()) .function(Function.RECOGNITION.getValue()) .build(); - ConnectionOptions connectionOptions = ConnectionOptions.builder().build(); - connectionOptions.setUseDefaultClient(false); - duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); + duplexApi = new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(null), serviceOption); } public Recognition(ConnectionOptions connectionOptions) { @@ -119,12 +130,8 @@ public Recognition(ConnectionOptions connectionOptions) { .task(Task.ASR.getValue()) .function(Function.RECOGNITION.getValue()) .build(); - ConnectionOptions thisOptions = connectionOptions; - if (connectionOptions == null) { - thisOptions = ConnectionOptions.builder().build(); - thisOptions.setUseDefaultClient(false); - } - duplexApi = new SynchronizeFullDuplexApi<>(thisOptions, serviceOption); + duplexApi = + new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(connectionOptions), serviceOption); } public Recognition(ConnectionOptions connectionOptions, String baseUrl) { @@ -138,12 +145,8 @@ public Recognition(ConnectionOptions connectionOptions, String baseUrl) { .baseWebSocketUrl(baseUrl) .function(Function.RECOGNITION.getValue()) .build(); - ConnectionOptions thisOptions = connectionOptions; - if (connectionOptions == null) { - thisOptions = ConnectionOptions.builder().build(); - thisOptions.setUseDefaultClient(false); - } - duplexApi = new SynchronizeFullDuplexApi<>(thisOptions, serviceOption); + duplexApi = + new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(connectionOptions), serviceOption); } public Flowable streamCall( diff --git a/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioFormatConfig.java b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioFormatConfig.java index 81fa89ef..87cee0d1 100644 --- a/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioFormatConfig.java +++ b/src/main/java/com/alibaba/dashscope/audio/omni/OmniRealtimeAudioFormatConfig.java @@ -29,8 +29,8 @@ public class OmniRealtimeAudioFormatConfig { /** audio format type, supports "pcm" and "wav", defaults to "pcm". */ private OmniRealtimeAudioCodec type = OmniRealtimeAudioCodec.PCM; - /** sample rate in Hz, supports 8000/16000/24000/48000. */ - private int sampleRate; + /** sample rate in Hz, supports 8000/16000/24000/48000, defaults to 16000. */ + private int sampleRate = 16000; /** * Extra format parameters for future extension (e.g. speech rate). These entries are merged into diff --git a/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizer.java b/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizer.java index a4d53f09..e4786787 100644 --- a/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizer.java +++ b/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizer.java @@ -96,12 +96,8 @@ public SpeechSynthesizer( .baseWebSocketUrl(baseUrl) .passTaskStarted(true) .build(); - ConnectionOptions thisOptions = connectionOptions; - if (connectionOptions == null) { - thisOptions = ConnectionOptions.builder().build(); - thisOptions.setUseDefaultClient(false); - } - duplexApi = new SynchronizeFullDuplexApi<>(thisOptions, serviceOption); + duplexApi = + new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(connectionOptions), serviceOption); this.callback = callback; this.asyncCall = this.callback != null; } @@ -125,12 +121,8 @@ public SpeechSynthesizer(String baseUrl, ConnectionOptions connectionOptions) { .baseWebSocketUrl(baseUrl) .passTaskStarted(true) .build(); - ConnectionOptions thisOptions = connectionOptions; - if (connectionOptions == null) { - thisOptions = ConnectionOptions.builder().build(); - thisOptions.setUseDefaultClient(false); - } - duplexApi = new SynchronizeFullDuplexApi<>(thisOptions, serviceOption); + duplexApi = + new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(connectionOptions), serviceOption); this.callback = null; } @@ -147,9 +139,7 @@ public SpeechSynthesizer() { .function(Function.SPEECH_SYNTHESIZER.getValue()) .passTaskStarted(true) .build(); - ConnectionOptions connectionOptions = ConnectionOptions.builder().build(); - connectionOptions.setUseDefaultClient(false); - duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); + duplexApi = new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(null), serviceOption); this.callback = null; } @@ -198,9 +188,7 @@ public SpeechSynthesizer( .baseWebSocketUrl(baseUrl) .passTaskStarted(true) .build(); - ConnectionOptions connectionOptions = ConnectionOptions.builder().build(); - connectionOptions.setUseDefaultClient(false); - duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); + duplexApi = new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(null), serviceOption); this.callback = callback; this.asyncCall = this.callback != null; } @@ -229,9 +217,7 @@ public SpeechSynthesizer( .function(Function.SPEECH_SYNTHESIZER.getValue()) .passTaskStarted(true) .build(); - ConnectionOptions connectionOptions = ConnectionOptions.builder().build(); - connectionOptions.setUseDefaultClient(false); - duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption); + duplexApi = new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(null), serviceOption); this.callback = callback; this.asyncCall = this.callback != null; } @@ -240,6 +226,19 @@ public String getLastRequestId() { return preRequestId; } + /** + * Returns the given options, or a default instance routed to the dedicated audio client + * (useDefaultClient=false) when null. + */ + private static ConnectionOptions defaultAudioOptionsIfNull(ConnectionOptions connectionOptions) { + if (connectionOptions != null) { + return connectionOptions; + } + ConnectionOptions defaultOptions = ConnectionOptions.builder().build(); + defaultOptions.setUseDefaultClient(false); + return defaultOptions; + } + /** * Stream input and output speech synthesis using Flowable features * diff --git a/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizerV2.java b/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizerV2.java index c96e1c76..7fd0ee2c 100644 --- a/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizerV2.java +++ b/src/main/java/com/alibaba/dashscope/audio/ttsv2/SpeechSynthesizerV2.java @@ -378,10 +378,9 @@ private void handleTaskFailed(JsonObject message) { .build(); callback.onError(new ApiException(status)); } - CountDownLatch stopLatch = this.stopLatch.get(); - if (stopLatch != null) { - stopLatch.countDown(); - } + // Release both latches: the task may fail before task-started, in which case the + // thread blocked in startStream() should fail fast instead of waiting for timeout. + releaseLatches(); } private void handleResultGenerated(JsonObject message) {