Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Mixing audio files

livebooks/audio_mixer/audio_mixer.livemd

Mixing audio files

File.cd(__DIR__)
Logger.configure(level: :error)

Mix.install([
  {:membrane_core, "~> 1.0"},
  {:membrane_audio_mix_plugin, "~> 0.16.0"},
  {:membrane_file_plugin, "~> 0.16.0"},
  {:membrane_mp3_mad_plugin, "~> 0.18.2"},
  {:membrane_ffmpeg_swresample_plugin, "~> 0.19.1"},
  {:membrane_aac_fdk_plugin, "~> 0.18.5"},
  {:membrane_kino_plugin, github: "membraneframework-labs/membrane_kino_plugin", tag: "v0.3.1"},
  {:membrane_tee_plugin, "~> 0.12.0"}
])

Description

This is an example of mixing multiple short “beep” sound into background music, one by one, every second.

Pipeline definition

Define all constants.

n_beeps = 30
beep_filepath = "./assets/beep.aac"
background_filepath = "./assets/sample.mp3"
:ok

The file’s “beep” sound input is decoded from AAC and split into separate inputs using the Tee element. These inputs are then filled into the mixer with corresponding time offsets.

import Membrane.ChildrenSpec

alias Membrane.{File, AAC, Tee, Time}

beep_audio_input =
  child({:file_source, :beep}, %File.Source{location: beep_filepath})
  |> child({:decoder_aac, :beep}, AAC.FDK.Decoder)
  |> child(:beeps, Tee.PushOutput)

beeps_split =
  for i <- 1..n_beeps do
    get_child(:beeps)
    |> via_in(:input, options: [offset: Time.seconds(i)])
    |> get_child(:mixer)
  end

:ok

The background music is loaded from a file and then decoded from the MP3 format to the appropriate Raw Audio format.

All mixer inputs must be of the same format.

import Membrane.ChildrenSpec

alias Membrane.{File, RawAudio, MP3}
alias Membrane.FFmpeg.SWResample.Converter

background_audio_input =
  child(:file_source, %File.Source{location: background_filepath})
  |> child(:decoder_mp3, MP3.MAD.Decoder)
  |> child(:converter, %Converter{
    input_stream_format: %RawAudio{channels: 2, sample_format: :s24le, sample_rate: 48_000},
    output_stream_format: %RawAudio{channels: 1, sample_format: :s16le, sample_rate: 44_100}
  })
  |> get_child(:mixer)

:ok

Mixer is created and directly connected to audio input of the player.

import Membrane.ChildrenSpec

alias Membrane.{AudioMixer, AAC, Kino}

kino = Membrane.Kino.Player.new(audio: true)

mixer_output =
  child(:mixer, Membrane.AudioMixer)
  |> child(:encoder_aac, AAC.FDK.Encoder)
  |> via_in(:audio)
  |> child(:player, %Kino.Player.Sink{kino: kino})

:ok

Whole pipeline structure.

spec = beeps_split ++ [beep_audio_input, background_audio_input, mixer_output]
:ok

Playing audio

alias Membrane.RCPipeline, as: RC

pipeline = RC.start!()
RC.exec_actions(pipeline, spec: spec)

kino