Notesclub

Mixing audio files

audio_mixer.livemd

Mixing audio files

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

Mix.install([
  {:membrane_core, "~> 0.11.2"},
  {:membrane_audio_mix_plugin, "~> 0.12.0"},
  {:membrane_file_plugin, "~> 0.13.0"},
  {:membrane_mp3_mad_plugin, "~> 0.14.0"},
  {:membrane_ffmpeg_swresample_plugin, "~> 0.16.1"},
  {:membrane_aac_fdk_plugin, "~> 0.14.0"},
  {:membrane_kino_plugin, github: "membraneframework-labs/membrane_kino_plugin"},
  {:membrane_tee_plugin, "~> 0.10.1"}
])

Installation

To run this demo one needs to install native dependencies:

  1. MP3 MAD
  2. AAC FDK
  3. SWResample FFmpeg

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_music_short.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: 44_100},
    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.

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

Playing audio

alias Membrane.RemoteControlled, as: RC

pipeline = RC.Pipeline.start!()
RC.Pipeline.exec_actions(pipeline, spec: structure, playback: :playing)

kino