Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -104,7 +117,7 @@ public Recognition() {
.task(Task.ASR.getValue())
.function(Function.RECOGNITION.getValue())
.build();
duplexApi = new SynchronizeFullDuplexApi<>(serviceOption);
duplexApi = new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(null), serviceOption);
}

public Recognition(ConnectionOptions connectionOptions) {
Expand All @@ -117,7 +130,8 @@ public Recognition(ConnectionOptions connectionOptions) {
.task(Task.ASR.getValue())
.function(Function.RECOGNITION.getValue())
.build();
duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption);
duplexApi =
new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(connectionOptions), serviceOption);
}

public Recognition(ConnectionOptions connectionOptions, String baseUrl) {
Expand All @@ -131,7 +145,8 @@ public Recognition(ConnectionOptions connectionOptions, String baseUrl) {
.baseWebSocketUrl(baseUrl)
.function(Function.RECOGNITION.getValue())
.build();
duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption);
duplexApi =
new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(connectionOptions), serviceOption);
}

public Flowable<RecognitionResult> streamCall(
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <pre>{@code
* "session": {
* "audio": {
* "input": { "format": { "type": "pcm", "sample_rate": 16000 } },
* "output": { "format": { "type": "pcm", "sample_rate": 24000 } }
* }
* }
* }</pre>
*/
@Data
public class OmniRealtimeAudioFormatConfig {
private static final Set<Integer> 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, defaults to 16000. */
private int sampleRate = 16000;

/**
* 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<String, Object> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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<String, Object> 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<String, Object> inputTranscriptionConfig = new HashMap<>();
inputTranscriptionConfig.put(
Expand Down Expand Up @@ -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<String, Object> buildAudioDirectionNode(OmniRealtimeAudioFormatConfig config) {
Map<String, Object> 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<String, Object> direction = new HashMap<>();
direction.put(OmniRealtimeConstants.AUDIO_FORMAT, format);
return direction;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ public SpeechSynthesizer(
.baseWebSocketUrl(baseUrl)
.passTaskStarted(true)
.build();
duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption);
duplexApi =
new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(connectionOptions), serviceOption);
this.callback = callback;
this.asyncCall = this.callback != null;
}
Expand All @@ -120,7 +121,8 @@ public SpeechSynthesizer(String baseUrl, ConnectionOptions connectionOptions) {
.baseWebSocketUrl(baseUrl)
.passTaskStarted(true)
.build();
duplexApi = new SynchronizeFullDuplexApi<>(connectionOptions, serviceOption);
duplexApi =
new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(connectionOptions), serviceOption);
this.callback = null;
}

Expand All @@ -137,7 +139,7 @@ public SpeechSynthesizer() {
.function(Function.SPEECH_SYNTHESIZER.getValue())
.passTaskStarted(true)
.build();
duplexApi = new SynchronizeFullDuplexApi<>(serviceOption);
duplexApi = new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(null), serviceOption);
this.callback = null;
}

Expand Down Expand Up @@ -186,7 +188,7 @@ public SpeechSynthesizer(
.baseWebSocketUrl(baseUrl)
.passTaskStarted(true)
.build();
duplexApi = new SynchronizeFullDuplexApi<>(serviceOption);
duplexApi = new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(null), serviceOption);
this.callback = callback;
this.asyncCall = this.callback != null;
}
Expand Down Expand Up @@ -215,7 +217,7 @@ public SpeechSynthesizer(
.function(Function.SPEECH_SYNTHESIZER.getValue())
.passTaskStarted(true)
.build();
duplexApi = new SynchronizeFullDuplexApi<>(serviceOption);
duplexApi = new SynchronizeFullDuplexApi<>(defaultAudioOptionsIfNull(null), serviceOption);
this.callback = callback;
this.asyncCall = this.callback != null;
}
Expand All @@ -224,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
*
Expand Down
Loading
Loading