use crate::{
event::SessionEvent,
media::track::file::read_wav_file,
transcription::{
TranscriptionClient, TranscriptionOption, aliyun::AliyunAsrClientBuilder,
tencent_cloud::TencentCloudAsrClientBuilder,
},
};
use dotenvy::dotenv;
use once_cell::sync::OnceCell;
use rustls::crypto::aws_lc_rs::default_provider;
use std::{collections::HashMap, env};
use tokio::time::{Duration, timeout};
static CRYPTO_PROVIDER: OnceCell<()> = OnceCell::new();
fn init_crypto() {
CRYPTO_PROVIDER.get_or_init(|| {
rustls::crypto::CryptoProvider::install_default(default_provider()).ok();
});
}
fn get_tencent_credentials() -> Option<(String, String, String)> {
dotenv().ok();
let secret_id = env::var("TENCENT_SECRET_ID").ok()?;
let secret_key = env::var("TENCENT_SECRET_KEY").ok()?;
let app_id = env::var("TENCENT_APPID").ok()?;
Some((secret_id, secret_key, app_id))
}
fn get_aliyun_credentials() -> Option<String> {
dotenv().ok();
env::var("DASHSCOPE_API_KEY").ok()
}
#[tokio::test]
async fn test_tencent_cloud_asr() {
init_crypto();
let (secret_id, secret_key, app_id) = match get_tencent_credentials() {
Some(creds) => creds,
None => {
println!("Skipping test_tencent_cloud_asr: No credentials found in .env file");
return;
}
};
println!(
"Using credentials: secret_id={}, app_id={}",
secret_id, app_id
);
let config = TranscriptionOption {
secret_id: Some(secret_id),
secret_key: Some(secret_key),
app_id: Some(app_id),
model_type: Some("16k_zh".to_string()),
..Default::default()
};
let (event_sender, mut event_receiver) = tokio::sync::broadcast::channel(16);
let client_builder = TencentCloudAsrClientBuilder::new(config, event_sender);
let client = match client_builder.build().await {
Ok(c) => c,
Err(e) => {
println!("Failed to connect to ASR service: {:?}", e);
return;
}
};
let audio_path = "fixtures/hello_book_course_zh_16k.wav";
let (samples, sample_rate) = read_wav_file(audio_path).expect("Failed to read WAV file");
println!(
"Read {} samples {}HZ from WAV file ({} seconds of audio)",
samples.len(),
sample_rate,
samples.len() as f32 / sample_rate as f32
);
let chunk_size = 3200; let chunks: Vec<_> = samples.chunks(chunk_size).collect();
println!("Starting to send {} chunks of audio data", chunks.len());
for chunk in chunks.iter() {
client
.send_audio(chunk, None)
.expect("Failed to send audio chunk");
tokio::time::sleep(Duration::from_millis(20)).await;
}
let timeout_duration = Duration::from_secs(5);
let result_fut = async {
let mut fulltext = String::new();
while let Ok(event) = event_receiver.recv().await {
match event {
SessionEvent::AsrFinal { text, .. } => {
fulltext += &text;
}
SessionEvent::Error { error, .. } => {
println!("Error: {:?}", error);
break;
}
_ => {}
}
if fulltext.contains("你好") {
break;
}
}
fulltext
};
let final_text = match timeout(timeout_duration, result_fut).await {
Ok(fulltext) => fulltext,
Err(_) => {
println!("Timeout waiting for transcription result");
String::new()
}
};
println!("Final transcription result: {}", final_text);
assert!(
final_text.contains("你好"),
"Expected transcription to contain booking or course"
);
}
#[tokio::test]
async fn test_aliyun_asr() {
init_crypto();
let api_key = match get_aliyun_credentials() {
Some(key) => key,
None => {
println!("Skipping test_aliyun_asr: No DASHSCOPE_API_KEY found in .env file");
return;
}
};
println!("Using Aliyun API key: {}", &api_key[..8]); let audio_path = "fixtures/hello_book_course_zh_16k.wav";
let (samples, sample_rate) = read_wav_file(audio_path).expect("Failed to read WAV file");
println!(
"Read {} samples {}HZ from WAV file ({} seconds of audio)",
samples.len(),
sample_rate,
samples.len() as f32 / sample_rate as f32
);
let config = TranscriptionOption {
secret_key: Some(api_key),
samplerate: Some(sample_rate),
model_type: Some("paraformer-realtime-v2".to_string()),
language: Some("zh".to_string()),
extra: Some(HashMap::from([
(
"vocabulary_id".to_string(),
"vocab-testpfx-707ac9a9dc0f44cf98796191f0563b4b".to_string(),
),
("disfluency_removal_enabled".to_string(), "true".to_string()),
(
"semantic_punctuation_enabled".to_string(),
"true".to_string(),
),
("max_sentence_silence".to_string(), "800".to_string()),
(
"multi_threshold_mode_enabled".to_string(),
"true".to_string(),
),
(
"punctuation_prediction_enabled".to_string(),
"true".to_string(),
),
("heartbeat".to_string(), "true".to_string()),
(
"inverse_text_normalization_enabled".to_string(),
"true".to_string(),
),
])),
..Default::default()
};
let (event_sender, mut event_receiver) = tokio::sync::broadcast::channel(16);
let client_builder = AliyunAsrClientBuilder::new(config, event_sender);
let client = match client_builder.build().await {
Ok(c) => c,
Err(e) => {
println!("Failed to connect to Aliyun ASR service: {:?}", e);
return;
}
};
let chunk_size = 3200; let chunks: Vec<_> = samples.chunks(chunk_size).collect();
println!("Starting to send {} chunks of audio data", chunks.len());
for chunk in chunks.iter() {
client
.send_audio(chunk, None)
.expect("Failed to send audio chunk");
tokio::time::sleep(Duration::from_millis(20)).await;
}
let timeout_duration = Duration::from_secs(10);
let result_fut = async {
let mut fulltext = String::new();
while let Ok(event) = event_receiver.recv().await {
match event {
SessionEvent::AsrDelta { text, .. } => {
fulltext += &text;
}
SessionEvent::AsrFinal { text, .. } => {
fulltext += &text;
}
_ => {}
}
if fulltext.contains("你好") || fulltext.len() > 20 {
break;
}
}
fulltext
};
let final_text = match timeout(timeout_duration, result_fut).await {
Ok(fulltext) => fulltext,
Err(_) => {
println!("Timeout waiting for transcription result");
String::new()
}
};
println!("Final transcription result: {}", final_text);
assert!(
!final_text.is_empty(),
"Expected some transcription result from Aliyun ASR"
);
}