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

80 lines
2.6 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})
# {:ok, path} = AudioSaver.save_chunk_as_wav(ref, audio, rate, "part")
# AudioFilesList.add_file(path)
{:noreply, socket}
end
@doc """
Recupera todos los chunks acumulados en el buffer, los concatena y guarda un archivo WAV final (sufijo `"final"`).
"""
def handle_in("stop_audio", _payload, socket) do
Logger.info("🛑 Grabación detenida por cliente")
ref = socket_id(socket)
chunks = AudioBuffer.get_and_clear(ref)
if chunks != [] do
[{rate, _} | _] = chunks
full_audio = Enum.map(chunks, fn {_, bin} -> bin end) |> IO.iodata_to_binary()
{:ok, path} = AudioSaver.save_chunk_as_wav(ref, full_audio, rate, "final")
Task.start(fn ->
transcription = Whisper.SendToModel.large(path)
Logger.info("✅ Transcripción completa:\n#{transcription}")
message = %{"chunks" => [%{"text" => transcription}]}
Phoenix.PubSub.broadcast(Whisper.PubSub, "transcription", {:transcription, Jason.encode!(message)})
File.rm!(path)
end)
end
{:noreply, socket}
end
defp socket_id(socket), do: socket.transport_pid |> :erlang.pid_to_list() |> List.to_string()
def save_raw(ref, bin) do
File.mkdir_p!("recordings/")
filename = "#{ref}_#{Whisper.Counter.next(ref)}.raw"
path = Path.join("recordings", filename)
File.write!(path, bin)
{:ok, path}
end
end