Current section

Files

Jump to
ex_webrtc lib ex_webrtc media ivf reader.ex
Raw

lib/ex_webrtc/media/ivf/reader.ex

defmodule ExWebRTC.Media.IVF.Reader do
@moduledoc """
Reads video frames from an IVF file.
Based on:
* https://formats.kaitai.io/vp8_ivf/
* https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/filters/ivf_parser.cc
"""
alias ExWebRTC.Media.IVF.{Frame, Header}
@opaque t() :: File.io_device()
@doc """
Opens an IVF file and reads its header.
"""
@spec open(Path.t()) :: {:ok, Header.t(), t()} | {:error, term()}
def open(path) do
with {:ok, file} <- File.open(path),
<<"DKIF", 0::little-16, 32::little-16, fourcc::little-32, width::little-16,
height::little-16, timebase_denum::little-32, timebase_num::little-32,
num_frames::little-32, unused::little-32>> <- IO.binread(file, 32) do
header = %Header{
signature: "DKIF",
version: 0,
header_size: 32,
fourcc: fourcc,
width: width,
height: height,
timebase_denum: timebase_denum,
timebase_num: timebase_num,
num_frames: num_frames,
unused: unused
}
{:ok, header, file}
else
{:error, _reason} = error -> error
# eof or invalid pattern matching
_other -> {:error, :invalid_file}
end
end
@doc """
Reads the next video frame from an IVF file.
"""
@spec next_frame(t()) :: {:ok, Frame.t()} | {:error, term()} | :eof
def next_frame(reader) do
with <<len_frame::little-integer-size(32), timestamp::little-integer-size(64)>> <-
IO.binread(reader, 12),
data when is_binary(data) and byte_size(data) == len_frame <-
IO.binread(reader, len_frame) do
{:ok, %Frame{timestamp: timestamp, data: data}}
else
:eof -> :eof
{:error, _reason} = error -> error
_other -> {:error, :invalid_file}
end
end
@doc """
Closes an IVF reader.
When a process owning the IVF reader exits, IVF reader is closed automatically.
"""
@spec close(t()) :: :ok | {:error, term()}
def close(reader) do
File.close(reader)
end
end