View Source Xav (xav v0.12.0)
Xav
Elixir wrapper over FFmpeg for reading audio and video files.
See an interview with FFmpeg enthusiast: https://youtu.be/9kaIXkImCAM
Installation
Make sure you have installed FFMpeg (ver. 4.x - 7.x) development packages on your system (see here for installation one-liners) and add Xav to the list of your dependencies:
def deps do
[
{:xav, "~> 0.12.0"},
# Add Nx if you want to have Xav.Frame.to_nx/1
{:nx, ">= 0.0.0"}
]
endControlling FFmpeg log output
FFmpeg's underlying libraries (libavcodec, libswscale, ...) print to stderr at the AV_LOG_INFO level by default. This is usually fine but can produce informational noise such as
[swscaler @ 0x1490a0000] No accelerated colorspace conversion found from yuv420p to rgb24.when libswscale falls back to a generic colorspace conversion path. These are not errors — decoded frames are bit-exact — but they can clutter test output and logs.
You can raise the threshold from Elixir:
Xav.set_log_level(:error)Or set it once at application start by configuring your application env:
# config/runtime.exs
config :xav, ffmpeg_log_level: :errorXav.Application reads this on boot and applies it before your supervision tree starts. Valid atoms are :quiet, :panic, :fatal, :error, :warning, :info, :verbose, :debug, and :trace. An integer FFmpeg level is also accepted.
Note that av_log_set_level/1 is process-global — changing the level affects every libav* call made from the current OS process, not just the reader that triggered the change.
Usage
Decode
decoder = Xav.Decoder.new(:vp8, out_format: :rgb24)
{:ok, %Xav.Frame{} = frame} = Xav.Decoder.decode(decoder, <<"somebinary">>)Decode with audio resampling
decoder = Xav.Decoder.new(:opus, out_format: :flt, out_sample_rate: 16_000)
{:ok, %Xav.Frame{} = frame} = Xav.Decoder.decode(decoder, <<"somebinary">>)Read from a file:
r = Xav.Reader.new!("./some_mp4_file.mp4")
{:ok, %Xav.Frame{} = frame} = Xav.Reader.next_frame(r)
tensor = Xav.Frame.to_nx(frame)
Kino.Image.new(tensor)Read from a camera:
r = Xav.Reader.new!("/dev/video0", device?: true, out_format: :rgb24)
{:ok, %Xav.Frame{} = frame} = Xav.Reader.next_frame(r)
tensor = Xav.Frame.to_nx(frame)
Kino.Image.new(tensor)Speech to text:
{:ok, whisper} = Bumblebee.load_model({:hf, "openai/whisper-tiny"})
{:ok, featurizer} = Bumblebee.load_featurizer({:hf, "openai/whisper-tiny"})
{:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "openai/whisper-tiny"})
{:ok, generation_config} = Bumblebee.load_generation_config({:hf, "openai/whisper-tiny"})
serving =
Bumblebee.Audio.speech_to_text_whisper(whisper, featurizer, tokenizer, generation_config,
defn_options: [compiler: EXLA]
)
# Read a couple of frames.
# See https://hexdocs.pm/bumblebee/Bumblebee.Audio.WhisperFeaturizer.html for default sampling rate.
frames =
Xav.Reader.stream!("sample.mp3", read: :audio, out_format: :flt, out_channels: 1, out_sample_rate: 16_000)
|> Stream.take(200)
|> Enum.map(fn frame -> Xav.Frame.to_nx(frame) end)
batch = Nx.Batch.concatenate(frames)
batch = Nx.Defn.jit_apply(&Function.identity/1, [batch])
Nx.Serving.run(serving, batch) Development
To make clangd aware of the header files used in your project, you can create a compile_commands.json file.
clangd uses this file to know the compiler flags, include paths, and other compilation options for each source file.
Install bear
The easiest way to generate compile_commands.json from a Makefile is to use the bear tool. bear is a tool that records the compiler calls during a build and creates the compile_commands.json file.
You can install bear with your package manager:
- macOS: brew install bear
- Ubuntu/Debian: sudo apt install bear
- Fedora: sudo dnf install bear
Generate compile_commands.json
After installing bear, you can run it alongside your make command to capture the necessary information.
bear -- mix compile
Summary
Functions
List all decoders.
List all encoders.
Get all available pixel formats.
Get all available audio sample formats.
Sets the FFmpeg log level.
Types
@type log_level() ::
:quiet
| :panic
| :fatal
| :error
| :warning
| :info
| :verbose
| :debug
| :trace
A human-readable FFmpeg log level.
The mapping to FFmpeg's AV_LOG_* constants is:
| Atom | FFmpeg constant | Value |
|---|---|---|
:quiet | AV_LOG_QUIET | -8 |
:panic | AV_LOG_PANIC | 0 |
:fatal | AV_LOG_FATAL | 8 |
:error | AV_LOG_ERROR | 16 |
:warning | AV_LOG_WARNING | 24 |
:info | AV_LOG_INFO | 32 |
:verbose | AV_LOG_VERBOSE | 40 |
:debug | AV_LOG_DEBUG | 48 |
:trace | AV_LOG_TRACE | 56 |
FFmpeg's default is :info.
Functions
@spec list_decoders() :: [decoder()]
List all decoders.
@spec list_encoders() :: [encoder()]
List all encoders.
Get all available pixel formats.
The result is a list of 3-element tuples {name, nb_components, hw_accelerated_format?}:
name- The name of the pixel format.nb_components- The number of the components in the pixel format.hw_accelerated_format?- Whether the pixel format is a hardware accelerated format.
Get all available audio sample formats.
The result is a list of 2-element tuples {name, nb_bytes}:
name- The name of the sample format.nb_bytes- The number of bytes per sample.
Sets the FFmpeg log level.
Accepts either one of the level atoms listed in log_level/0
or a raw integer level. Returns :ok.
This call wraps FFmpeg's av_log_set_level/1, which is
process-global: the level is shared across every libav* and
libswscale call in the current OS process, including readers,
decoders, encoders, and converters created from the Elixir VM.
Typical use is to silence the informational [swscaler @ ...] lines
that libswscale prints when it falls back to a non-SIMD
colorspace conversion path (which happens for example on
yuv420p -> rgb24 on Apple Silicon):
Xav.set_log_level(:error)To configure the level declaratively at application start, use the
:ffmpeg_log_level key in your application env instead:
# config/runtime.exs
config :xav, ffmpeg_log_level: :errorXav.Application reads this on boot and calls set_log_level/1
for you.
Examples
iex> Xav.set_log_level(:error)
:ok
iex> Xav.set_log_level(:quiet)
:ok