Files
voice_recognition/whisper/lib/whisper_web/channels/audio_channel.ex

100 lines
3.0 KiB
Elixir

defmodule WhisperWeb.AudioChannel do
@moduledoc """
Phoenix Channel que gestiona la recepción de audio en tiempo real desde el cliente.
El audio se envía en chunks binarios y se acumula en un buffer temporal (`AudioBuffer`).
Al recibir el evento `"stop_audio"`, todos los chunks se combinan y se guardan como un único archivo WAV final.
"""
use Phoenix.Channel
require Logger
alias Phoenix.PubSub
def join("audio:lobby", _payload, socket) do
ref = socket_id(socket)
Logger.info("Cliente conectado al canal audio:lobby")
{:ok, socket}
end
@doc """
Maneja la entrada de un chunk de audio desde el cliente.
El mensaje binario contiene un encabezado JSON (con la tasa de muestreo) seguido del audio binario.
Se almacena el chunk en `AudioBuffer` y también se guarda como archivo WAV parcial.
"""
def handle_in("audio_chunk", {:binary, raw_binary}, socket) do
<<header_len::16, rest::binary>> = raw_binary
<<header::binary-size(header_len), audio::binary>> = rest
%{"sample_rate" => rate} = Jason.decode!(header)
ref = socket_id(socket)
Logger.info("Chunk recibido: #{byte_size(audio)} bytes, sample_rate: #{rate}")
AudioBuffer.append(ref, {rate, audio})
chunks = AudioBuffer.get_and_clear(ref)
start_total = System.monotonic_time(:millisecond)
if chunks != [] do
Task.start(fn ->
[{rate, _} | _] = chunks
full_audio = Enum.map(chunks, fn {_, bin} -> bin end) |> IO.iodata_to_binary()
{wav_time, {:ok, path}} =
:timer.tc(fn ->
AudioSaver.save_chunk_as_wav(ref, full_audio, rate, "part")
end)
model_start = System.monotonic_time(:millisecond)
Logger.info("WAV guardado en #{div(wav_time, 1000)} ms")
transcription =
if path do
case Nx.Serving.batched_run(Whisper.LargeModel.Serving, {:file, path}) do
%{chunks: chunks} ->
chunks
|> Enum.map(& &1.text)
|> Enum.join(" ")
_ ->
"Transcripción no disponible"
end
else
"Archivo no disponible"
end
model_end = System.monotonic_time(:millisecond)
Logger.info("El modelo procesó en #{model_end - model_start} ms")
Logger.info("✅ Transcripción:\n#{transcription}")
message = %{"chunks" => [%{"text" => transcription}]}
PubSub.broadcast(Whisper.PubSub, "transcription", {:transcription, %{
"received_at" => model_start,
"text" => transcription
}})
File.rm!(path)
end_total = System.monotonic_time(:millisecond)
Logger.info("⏱ Total procesamiento stop_audio: #{end_total - start_total} ms")
end)
end
{:noreply, socket}
end
def handle_in("stop_audio", _payload, socket) do
Logger.info("🛑 Grabación detenida por cliente")
ref = socket_id(socket)
{:noreply, socket}
end
defp socket_id(socket), do: socket.transport_pid |> :erlang.pid_to_list() |> List.to_string()
end