Skip to content

Commit

Permalink
Add IVF reader
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 committed Nov 29, 2023
1 parent 007aef2 commit 1f6e03b
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
138 changes: 138 additions & 0 deletions lib/ex_webrtc/media/ivf_reader.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
defmodule ExWebRTC.Media.IVFHeader do
@moduledoc """
Defines IVF Frame Header type.
"""

@typedoc """
IVF Frame Header.
Description of these fields is taken from:
https://chromium.googlesource.com/chromium/src/media/+/master/filters/ivf_parser.h
* `signature` - always "DKIF"
* `version` - should be 0
* `header_size` - size of header in bytes
* `fourcc` - codec FourCC (e.g, 'VP80').
For more information, see https://fourcc.org/codecs.php
* `width` - width in pixels
* `height` - height in pixels
* `timebase_denum` - timebase denumerator
* `timebase_num` - timebase numerator. For example, if
`timebase_denum` is 30 and `timebase_num` is 2, the unit
of `ExWebRTC.Media.IVFFrame`'s timestamp is 2/30 seconds.
* `num_frames` - number of frames in a file
* `unused` - unused
"""
@type t() :: %__MODULE__{
signature: binary(),
version: integer(),
header_size: integer(),
fourcc: integer(),
width: integer(),
height: integer(),
timebase_denum: integer(),
timebase_num: integer(),
num_frames: integer(),
unused: integer()
}

@enforcekeyes [
:signature,
:version,
:header_size,
:fourcc,
:width,
:height,
:timebase_denum,
:timebase_num,
:num_frames,
:unused
]
defstruct @enforcekeyes
end

defmodule ExWebRTC.Media.IVFFrame do
@moduledoc """
Defines IVF Frame type.
"""

@typedoc """
IVF Frame.
`timestamp` is in `timebase_num`/`timebase_denum` seconds.
For more information see `ExWebRTC.Media.IVFHeader`.
"""
@type t() :: %__MODULE__{
timestamp: integer(),
data: binary()
}

@enforcekeys [:timestamp, :data]
defstruct @enforcekeys
end

defmodule ExWebRTC.Media.IVFReader do
@moduledoc """
Defines IVF reader.
"""

alias ExWebRTC.Media.{IVFHeader, IVFFrame}

@opaque t() :: File.io_device()

@spec open(Path.t()) :: {:ok, t()} | {:error, File.posix()}
def open(path), do: File.open(path)

@spec read_header(t()) :: {:ok, IVFHeader.t()} | {:error, :invalid_file} | :eof
def read_header(reader) do
case IO.binread(reader, 32) do
<<"DKIF", 0::little-integer-size(16), header_size::little-integer-size(16),
fourcc::little-integer-size(32), width::little-integer-size(16),
height::little-integer-size(16), timebase_denum::little-integer-size(32),
timebase_num::little-integer-size(32), num_frames::little-integer-size(32),
unused::little-integer-size(32)>> ->
{:ok,
%IVFHeader{
signature: "DKIF",
version: 0,
header_size: header_size,
fourcc: fourcc,
width: width,
height: height,
timebase_denum: timebase_denum,
timebase_num: timebase_num,
num_frames: num_frames,
unused: unused
}}

:eof ->
:eof

_other ->
{:error, :invalid_file}
end
end

@spec next_frame(t()) :: {:ok, IVFFrame.t()} | {:error, :invalid_file} | :eof
def next_frame(reader) do
case IO.binread(reader, 12) do
<<len_frame::little-integer-size(32), timestamp::little-integer-size(64)>> ->
case IO.binread(reader, len_frame) do
data when is_binary(data) ->
{:ok, %IVFFrame{timestamp: timestamp, data: data}}

:eof ->
:eof

_other ->
{:error, :invalid_file}
end

:eof ->
:eof

_other ->
{:error, :invalid_file}
end
end
end
31 changes: 31 additions & 0 deletions test/ex_webrtc/media/ivf_reader_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
defmodule ExWebRTC.Media.IVFReaderTest do
use ExUnit.Case, async: true

alias ExWebRTC.Media.{IVFFrame, IVFHeader, IVFReader}

test "ivf reader" do
# VP8 test vector - https://chromium.googlesource.com/webm/vp8-test-vectors/
{:ok, reader} = IVFReader.open("test/fixtures/vp80-00-comprehensive-001.ivf")

{:ok,
%IVFHeader{
signature: "DKIF",
version: 0,
header_size: 32,
fourcc: 808_996_950,
width: 176,
height: 144,
timebase_denum: 30_000,
timebase_num: 1000,
num_frames: 29,
unused: 0
}} = IVFReader.read_header(reader)

for i <- 0..28 do
{:ok, %IVFFrame{} = frame} = IVFReader.next_frame(reader)
assert frame.timestamp == i
assert is_binary(frame.data)
assert frame.data != <<>>
end
end
end
Binary file added test/fixtures/vp80-00-comprehensive-001.ivf
Binary file not shown.

0 comments on commit 1f6e03b

Please sign in to comment.