+
🚀 Ultravox PCM - Otimizado
+
+
+
+
+ Desconectado
+
+
Latência: --ms
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
🎵 Text-to-Speech Direto
+
Digite ou edite o texto abaixo e escolha uma voz para converter em áudio
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⏳ Processando...
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/services/webrtc_gateway/ultravox-chat-ios.html b/services/webrtc_gateway/ultravox-chat-ios.html
new file mode 100644
index 0000000000000000000000000000000000000000..b95806d2fc02c6ea57ccf3364bc22d1e11b338bc
--- /dev/null
+++ b/services/webrtc_gateway/ultravox-chat-ios.html
@@ -0,0 +1,1843 @@
+
+
+
+
+
+
+
+
+
+
+
+ Desconectado
+ Latência: --ms
+
+
+
+
+
+
+
+ Voz TTS
+
+
+ arrow_drop_down
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Digite ou edite o texto abaixo e escolha uma voz para converter em áudio
+
+
+
+
+
+
+
+
+
+ Voz TTS
+
+
+ arrow_drop_down
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/services/webrtc_gateway/ultravox-chat-opus.html b/services/webrtc_gateway/ultravox-chat-opus.html
new file mode 100644
index 0000000000000000000000000000000000000000..20a53f44244604c831fdf60df8052acc05b7e3f2
--- /dev/null
+++ b/services/webrtc_gateway/ultravox-chat-opus.html
@@ -0,0 +1,581 @@
+
+
+
+
+
🚀 Ultravox PCM - Otimizado
+
+
+
+
+ Desconectado
+
+
Latência: --ms
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
🎵 Text-to-Speech Direto
+
Digite ou edite o texto abaixo e escolha uma voz para converter em áudio
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⏳ Processando...
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/services/webrtc_gateway/ultravox-chat-server.js b/services/webrtc_gateway/ultravox-chat-server.js
index 65b1f9f4f14bfd4eb7e96c1be8825c0a9e2156b0..86a5919cb6c5083e4c51605bc9c7be9545075231 100644
--- a/services/webrtc_gateway/ultravox-chat-server.js
+++ b/services/webrtc_gateway/ultravox-chat-server.js
@@ -317,6 +317,22 @@ function handleMessage(clientId, data) {
handleAudioData(clientId, data.audio);
break;
+ case 'audio':
+ // Processar áudio enviado em formato JSON (como no teste)
+ if (data.data && data.format) {
+ const audioBuffer = Buffer.from(data.data, 'base64');
+ console.log(`🎤 Received audio JSON: ${audioBuffer.length} bytes, format: ${data.format}`);
+
+ if (data.format === 'float32') {
+ // Áudio já está em Float32, processar diretamente sem conversão
+ handleFloat32Audio(clientId, audioBuffer);
+ } else {
+ // Processar como PCM int16
+ handlePCMData(clientId, audioBuffer);
+ }
+ }
+ break;
+
case 'broadcast':
handleBroadcast(clientId, data.message);
break;
@@ -561,27 +577,146 @@ const pcmBuffers = new Map();
function handleBinaryMessage(clientId, buffer) {
// Verificar se é header ou dados
if (buffer.length === 8) {
- // Header PCM
+ // Header PCM ou Opus
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
const magic = view.getUint32(0);
const size = view.getUint32(4);
if (magic === 0x50434D16) { // "PCM16"
console.log(`🎤 PCM header: ${size} bytes esperados`);
- pcmBuffers.set(clientId, { expectedSize: size, data: Buffer.alloc(0) });
+ pcmBuffers.set(clientId, { expectedSize: size, data: Buffer.alloc(0), type: 'pcm' });
+ } else if (magic === 0x4F505553) { // "OPUS"
+ console.log(`🎵 Opus header: ${size} bytes esperados`);
+ pcmBuffers.set(clientId, { expectedSize: size, data: Buffer.alloc(0), type: 'opus' });
}
} else {
- // Processar PCM diretamente (com ou sem header prévio)
- console.log(`🎵 Processando PCM direto: ${buffer.length} bytes`);
- handlePCMData(clientId, buffer);
+ // Verificar se temos um buffer esperando dados
+ const bufferInfo = pcmBuffers.get(clientId);
- // Limpar buffer info se existir
- if (pcmBuffers.has(clientId)) {
- pcmBuffers.delete(clientId);
+ if (bufferInfo) {
+ // Adicionar dados ao buffer
+ bufferInfo.data = Buffer.concat([bufferInfo.data, buffer]);
+ console.log(`📦 Buffer acumulado: ${bufferInfo.data.length}/${bufferInfo.expectedSize} bytes`);
+
+ // Se recebemos todos os dados esperados
+ if (bufferInfo.data.length >= bufferInfo.expectedSize) {
+ if (bufferInfo.type === 'opus') {
+ console.log(`🎵 Processando Opus: ${bufferInfo.data.length} bytes`);
+ handleOpusData(clientId, bufferInfo.data);
+ } else {
+ console.log(`🎤 Processando PCM: ${bufferInfo.data.length} bytes`);
+ handlePCMData(clientId, bufferInfo.data);
+ }
+ pcmBuffers.delete(clientId);
+ }
+ } else {
+ // Processar PCM diretamente (sem header)
+ console.log(`🎵 Processando PCM direto: ${buffer.length} bytes`);
+ handlePCMData(clientId, buffer);
}
}
}
+// Processar dados Opus
+async function handleOpusData(clientId, opusBuffer) {
+ try {
+ // Descomprimir Opus para PCM
+ const pcmBuffer = decompressOpusToPCM(opusBuffer);
+ console.log(`🎵 Opus descomprimido: ${opusBuffer.length} bytes -> ${pcmBuffer.length} bytes PCM`);
+
+ // Processar como PCM
+ await handlePCMData(clientId, pcmBuffer);
+ } catch (error) {
+ console.error(`❌ Erro ao processar Opus: ${error.message}`);
+ }
+}
+
+// Processar áudio que já está em Float32
+async function handleFloat32Audio(clientId, float32Buffer) {
+ const client = clients.get(clientId);
+ const session = sessions.get(clientId);
+
+ if (!client || !session) return;
+
+ if (client.isProcessing) {
+ console.log('⚠️ Já processando áudio, ignorando...');
+ return;
+ }
+
+ client.isProcessing = true;
+ const startTime = Date.now();
+
+ try {
+ console.log(`\n🎤 FLOAT32 AUDIO RECEBIDO [${clientId}]`);
+ console.log(` Tamanho: ${float32Buffer.length} bytes`);
+ console.log(` Formato: Float32 normalizado`);
+
+ // Áudio já está em Float32, apenas passar adiante
+ console.log(` 📊 Áudio Float32 pronto: ${float32Buffer.length} bytes`);
+
+ // Processar com Ultravox
+ const response = await processWithUltravox(clientId, float32Buffer, session);
+ console.log(` 📝 Resposta: "${response}"`);
+
+ // Armazenar na memória de conversação
+ const conversationId = client.conversationId;
+ if (conversationId) {
+ conversationMemory.addMessage(conversationId, {
+ role: 'user',
+ content: '[Áudio processado]',
+ audioSize: float32Buffer.length,
+ timestamp: startTime
+ });
+
+ conversationMemory.addMessage(conversationId, {
+ role: 'assistant',
+ content: response,
+ latency: Date.now() - startTime
+ });
+ }
+
+ // Enviar transcrição primeiro
+ client.ws.send(JSON.stringify({
+ type: 'transcription',
+ text: response,
+ timestamp: Date.now()
+ }));
+
+ // Sintetizar áudio com TTS
+ const ttsResult = await synthesizeWithTTS(clientId, response, session);
+ const responseAudio = ttsResult.audioData;
+ console.log(` 🔊 Áudio sintetizado: ${responseAudio.length} bytes @ ${ttsResult.sampleRate}Hz`);
+
+ // Enviar áudio como JSON
+ client.ws.send(JSON.stringify({
+ type: 'audio',
+ data: responseAudio.toString('base64'),
+ format: 'pcm',
+ sampleRate: ttsResult.sampleRate || 16000,
+ isFinal: true
+ }));
+
+ const totalLatency = Date.now() - startTime;
+ console.log(`⏱️ Latência total: ${totalLatency}ms`);
+
+ // Enviar métricas
+ client.ws.send(JSON.stringify({
+ type: 'metrics',
+ latency: totalLatency,
+ response: response
+ }));
+
+ } catch (error) {
+ console.error('❌ Erro ao processar áudio Float32:', error);
+ client.ws.send(JSON.stringify({
+ type: 'error',
+ message: error.message
+ }));
+ } finally {
+ client.isProcessing = false;
+ }
+}
+
// Processar dados PCM direto (sem conversão!)
async function handlePCMData(clientId, pcmBuffer) {
const client = clients.get(clientId);
@@ -655,13 +790,26 @@ async function handlePCMData(clientId, pcmBuffer) {
});
}
+ // Enviar transcrição primeiro
+ client.ws.send(JSON.stringify({
+ type: 'transcription',
+ text: response,
+ timestamp: Date.now()
+ }));
+
// Sintetizar áudio com TTS
const ttsResult = await synthesizeWithTTS(clientId, response, session);
const responseAudio = ttsResult.audioData;
console.log(` 🔊 Áudio sintetizado: ${responseAudio.length} bytes @ ${ttsResult.sampleRate}Hz`);
- // Enviar PCM direto (sem conversão para WebM!)
- client.ws.send(responseAudio);
+ // Enviar áudio como JSON
+ client.ws.send(JSON.stringify({
+ type: 'audio',
+ data: responseAudio.toString('base64'),
+ format: 'pcm',
+ sampleRate: ttsResult.sampleRate || 16000,
+ isFinal: true
+ }));
const totalLatency = Date.now() - startTime;
console.log(`⏱️ Latência total: ${totalLatency}ms`);
diff --git a/services/webrtc_gateway/ultravox-chat-tailwind.html b/services/webrtc_gateway/ultravox-chat-tailwind.html
new file mode 100644
index 0000000000000000000000000000000000000000..399283dc5922d91cb852b03d578a8d02dea858ba
--- /dev/null
+++ b/services/webrtc_gateway/ultravox-chat-tailwind.html
@@ -0,0 +1,393 @@
+
+
+
+
+
🚀 Ultravox PCM - Otimizado
+
+
+
+
+ Desconectado
+
+
Latência: --ms
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
🎵 Text-to-Speech Direto
+
Digite ou edite o texto abaixo e escolha uma voz para converter em áudio
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⏳ Processando...
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/services/webrtc_gateway/webrtc.pid b/services/webrtc_gateway/webrtc.pid
new file mode 100644
index 0000000000000000000000000000000000000000..50f2d183613dbcf7bfeb6659e1f165b0fcc29d30
--- /dev/null
+++ b/services/webrtc_gateway/webrtc.pid
@@ -0,0 +1 @@
+5415
diff --git a/test-24khz-support.html b/test-24khz-support.html
new file mode 100644
index 0000000000000000000000000000000000000000..78562427b3e7dd0f1b42b09245099e4b3c3a7de8
--- /dev/null
+++ b/test-24khz-support.html
@@ -0,0 +1,243 @@
+
+
+
+