Skip to content

Commit 86aad4a

Browse files
committed
Add IVFReader.close/1 and IVFWriter.close/1
1 parent d0b2778 commit 86aad4a

File tree

4 files changed

+61
-9
lines changed

4 files changed

+61
-9
lines changed

lib/ex_webrtc/media/ivf_reader.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ defmodule ExWebRTC.Media.IVFReader do
8484

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

87+
@doc """
88+
Opens an IVF file and reads its header.
89+
"""
8790
@spec open(Path.t()) :: {:ok, IVFHeader.t(), t()} | {:error, term()}
8891
def open(path) do
8992
with {:ok, file} <- File.open(path),
@@ -111,6 +114,9 @@ defmodule ExWebRTC.Media.IVFReader do
111114
end
112115
end
113116

117+
@doc """
118+
Reads the next video frame from an IVF file.
119+
"""
114120
@spec next_frame(t()) :: {:ok, IVFFrame.t()} | {:error, term()} | :eof
115121
def next_frame(reader) do
116122
with <<len_frame::little-integer-size(32), timestamp::little-integer-size(64)>> <-
@@ -124,4 +130,14 @@ defmodule ExWebRTC.Media.IVFReader do
124130
_other -> {:error, :invalid_file}
125131
end
126132
end
133+
134+
@doc """
135+
Closes an IVF reader.
136+
137+
When a process owning the IVF reader exits, IVF reader is closed automatically.
138+
"""
139+
@spec close(t()) :: :ok | {:error, File.posix() | term()}
140+
def close(reader) do
141+
File.close(reader)
142+
end
127143
end

lib/ex_webrtc/media/ivf_writer.ex

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ defmodule ExWebRTC.Media.IVFWriter do
1313
@enforce_keys [:file]
1414
defstruct @enforce_keys ++ [update_header_after: 0, frames_cnt: 0]
1515

16-
defguard update_header?(writer)
17-
when writer.frames_cnt >= writer.update_header_after and
18-
rem(writer.frames_cnt, writer.update_header_after) == 0
16+
defguardp update_header?(writer)
17+
when writer.frames_cnt >= writer.update_header_after and
18+
rem(writer.frames_cnt, writer.update_header_after) == 0
1919

2020
@doc """
2121
Creates a new IVF writer.
2222
2323
Initially, IVF header is written with `num_frames` and
2424
is updated every `num_frames` by `num_frames`.
25+
To have a precise number of frames in the header,
26+
either write exactly `num_frames` or call `close/1`
27+
at the end of writing.
2528
"""
2629
@spec open(Path.t(),
2730
fourcc: non_neg_integer(),
@@ -53,20 +56,35 @@ defmodule ExWebRTC.Media.IVFWriter do
5356
end
5457

5558
@doc """
56-
Writes IVF frame into a file.
59+
Writes an IVF frame into a file.
5760
"""
5861
@spec write_frame(t(), IVFFrame.t()) :: {:ok, t()} | {:error, term()}
5962
def write_frame(writer, frame) when update_header?(writer) and frame.data != <<>> do
60-
case update_header(writer) do
63+
case update_header(writer, writer.frames_cnt + writer.update_header_after) do
6164
:ok -> do_write_frame(writer, frame)
6265
{:error, _reason} = error -> error
6366
end
6467
end
6568

6669
def write_frame(writer, frame) when frame.data != <<>>, do: do_write_frame(writer, frame)
6770

68-
defp update_header(writer) do
69-
num_frames = <<writer.frames_cnt + writer.update_header_after::little-32>>
71+
@doc """
72+
Updates a number of frames in the header and closes the writer.
73+
74+
If a process owning an IVF writer exits, a file open by the IVF writer
75+
will be closed automatically but header will not be updated.
76+
See also `open/2` for more information on automatic header updates.
77+
"""
78+
@spec close(t()) :: :ok | {:error, File.posix() | term()}
79+
def close(writer) do
80+
case update_header(writer, writer.frames_cnt) do
81+
:ok -> File.close(writer.file)
82+
{:error, _reason} = error -> error
83+
end
84+
end
85+
86+
defp update_header(writer, num_frames) do
87+
num_frames = <<num_frames::little-32>>
7088
ret = :file.pwrite(writer.file, 24, num_frames)
7189
{:ok, _position} = :file.position(writer.file, :eof)
7290
ret

test/ex_webrtc/media/ivf_reader_test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule ExWebRTC.Media.IVFReaderTest do
2626
end
2727

2828
assert :eof == IVFReader.next_frame(reader)
29+
assert :ok == IVFReader.close(reader)
2930
end
3031

3132
test "empty file" do

test/ex_webrtc/media/ivf_writer_test.exs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule ExWebRTC.Media.IVFWritertTest do
77
test "IVF writer", %{tmp_dir: tmp_dir} do
88
path = Path.join([tmp_dir, "output.ivf"])
99
<<fourcc::little-32>> = "VP80"
10-
num_frames = 2
10+
num_frames = 5
1111

1212
{:ok, writer} =
1313
IVFWriter.open(path,
@@ -42,7 +42,7 @@ defmodule ExWebRTC.Media.IVFWritertTest do
4242
# `num_frames + 1` frame
4343
expected_num_frames = 2 * num_frames
4444
frame = %IVFFrame{timestamp: num_frames, data: <<0, 1, 2, 3, 4>>}
45-
assert {:ok, _writer} = IVFWriter.write_frame(writer, frame)
45+
assert {:ok, writer} = IVFWriter.write_frame(writer, frame)
4646

4747
assert {:ok,
4848
%IVFHeader{
@@ -65,5 +65,22 @@ defmodule ExWebRTC.Media.IVFWritertTest do
6565
assert frame.timestamp == i
6666
assert frame.data == <<0, 1, 2, 3, 4>>
6767
end
68+
69+
# assert that after calling close/1, number of frames
70+
# in the header is equal to the
71+
# exact number of frames that were written
72+
assert :ok = IVFWriter.close(writer)
73+
exact_num_frames = num_frames + 1
74+
75+
assert {:ok,
76+
%IVFHeader{
77+
fourcc: ^fourcc,
78+
height: 640,
79+
width: 480,
80+
num_frames: ^exact_num_frames,
81+
timebase_denum: 30,
82+
timebase_num: 1,
83+
unused: 0
84+
}, _reader} = IVFReader.open(path)
6885
end
6986
end

0 commit comments

Comments
 (0)