Skip to content

Commit 47f76bd

Browse files
authored
Merge pull request #57 from membraneframework/forward_timestamps
Forward timestamps
2 parents dd8e90d + 1751941 commit 47f76bd

File tree

6 files changed

+110
-10
lines changed

6 files changed

+110
-10
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ It is a part of [Membrane Multimedia Framework](https://membrane.stream).
1313
Add the following line to your `deps` in `mix.exs`. Run `mix deps.get`.
1414

1515
```elixir
16-
{:membrane_ffmpeg_swresample_plugin, "~> 0.19.2"}
16+
{:membrane_ffmpeg_swresample_plugin, "~> 0.20.0"}
1717
```
1818

1919
The precompiled builds of the [ffmpeg](https://www.ffmpeg.org) will be pulled and linked automatically. However, should there be any problems, consider installing it manually.

lib/membrane_ffmpeg_swresample_plugin/converter.ex

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ defmodule Membrane.FFmpeg.SWResample.Converter do
6464
|> Map.merge(%{
6565
native: nil,
6666
queue: <<>>,
67-
input_stream_format_provided?: options.input_stream_format != nil
67+
input_stream_format_provided?: options.input_stream_format != nil,
68+
pts_queue: []
6869
})
6970

7071
{[], state}
@@ -122,16 +123,29 @@ defmodule Membrane.FFmpeg.SWResample.Converter do
122123
end
123124

124125
@impl true
125-
def handle_buffer(:input, %Buffer{payload: payload}, _ctx, state) do
126+
def handle_buffer(:input, %Buffer{payload: payload, pts: input_pts}, _ctx, state) do
127+
input_frame_size = RawAudio.frame_size(state.input_stream_format)
128+
output_frame_size = RawAudio.frame_size(state.output_stream_format)
129+
130+
expected_output_frames_count =
131+
(byte_size(payload) * output_frame_size / (input_frame_size * input_frame_size)) |> round()
132+
133+
state =
134+
Map.update!(state, :pts_queue, fn pts_queue ->
135+
pts_queue ++ [{input_pts, expected_output_frames_count}]
136+
end)
137+
126138
conversion_result =
127-
convert!(state.native, RawAudio.frame_size(state.input_stream_format), payload, state.queue)
139+
convert!(state.native, input_frame_size, payload, state.queue)
128140

129141
case conversion_result do
130142
{<<>>, queue} ->
131143
{[], %{state | queue: queue}}
132144

133145
{converted, queue} ->
134-
{[buffer: {:output, %Buffer{payload: converted}}], %{state | queue: queue}}
146+
{state, out_pts} = update_pts_queue(state, byte_size(converted) / output_frame_size)
147+
148+
{[buffer: {:output, %Buffer{payload: converted, pts: out_pts}}], %{state | queue: queue}}
135149
end
136150
end
137151

@@ -154,8 +168,15 @@ defmodule Membrane.FFmpeg.SWResample.Converter do
154168
{[end_of_stream: :output], %{state | queue: <<>>}}
155169

156170
converted ->
157-
{[buffer: {:output, %Buffer{payload: converted}}, end_of_stream: :output],
158-
%{state | queue: <<>>}}
171+
converted_frames_count =
172+
byte_size(converted) / RawAudio.frame_size(state.output_stream_format)
173+
174+
{state, out_pts} = update_pts_queue(state, converted_frames_count)
175+
176+
{[
177+
buffer: {:output, %Buffer{payload: converted, pts: out_pts}},
178+
end_of_stream: :output
179+
], %{state | queue: <<>>}}
159180
end
160181
end
161182

@@ -212,4 +233,15 @@ defmodule Membrane.FFmpeg.SWResample.Converter do
212233
{:error, reason} -> raise "Error while flushing converter: #{inspect(reason)}"
213234
end
214235
end
236+
237+
defp update_pts_queue(state, converted_frames_count) do
238+
[{out_pts, expected_frames} | rest] = state.pts_queue
239+
240+
if converted_frames_count < expected_frames do
241+
{%{state | pts_queue: [{out_pts, expected_frames - converted_frames_count}] ++ rest},
242+
out_pts}
243+
else
244+
{%{state | pts_queue: rest}, out_pts}
245+
end
246+
end
215247
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Membrane.FFmpeg.SWResample.Mixfile do
22
use Mix.Project
33

44
@github_url "https://github.com/membraneframework/membrane_ffmpeg_swresample_plugin"
5-
@version "0.19.2"
5+
@version "0.20.0"
66

77
def project do
88
[

test/fixtures/input_s16le_stereo_16khz.raw

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

test/membrane_ffmpeg_swresample_plugin/converter_test.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do
2727
output_stream_format: @u8_format,
2828
frames_per_buffer: 2048,
2929
native: nil,
30-
queue: <<>>
30+
queue: <<>>,
31+
pts_queue: []
3132
}
3233
}
3334
end
@@ -156,7 +157,7 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do
156157

157158
assert {[], new_state} = @module.handle_buffer(:input, buffer, nil, state)
158159

159-
assert new_state == %{state | queue: payload}
160+
assert %{new_state | pts_queue: nil} == %{state | queue: payload, pts_queue: nil}
160161
refute_called(@native, :convert)
161162
end
162163

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
defmodule Membrane.FFmpeg.SWResample.PtsForwardTest do
2+
use ExUnit.Case
3+
4+
import Membrane.ChildrenSpec
5+
import Membrane.Testing.Assertions
6+
7+
alias Membrane.FFmpeg.SWResample.Converter
8+
alias Membrane.{RawAudio, Testing}
9+
10+
@pts_multiplier 31_250_000
11+
12+
test "pts forward test" do
13+
input_stream_format = %RawAudio{sample_format: :s16le, sample_rate: 16_000, channels: 2}
14+
output_stream_format = %RawAudio{sample_format: :s32le, sample_rate: 32_000, channels: 2}
15+
16+
# 32 frames * 2048 bytes
17+
fixture_path = "test/fixtures/input_s16le_stereo_16khz.raw"
18+
19+
spec = [
20+
child(:source, %Membrane.Testing.Source{output: buffers_from_file(fixture_path)})
21+
|> child(:resampler, %Converter{
22+
input_stream_format: input_stream_format,
23+
output_stream_format: output_stream_format
24+
})
25+
|> child(:sink, Membrane.Testing.Sink)
26+
]
27+
28+
pipeline = Testing.Pipeline.start_link_supervised!(spec: spec)
29+
assert_sink_buffer(pipeline, :sink, _buffer)
30+
31+
Enum.each(0..30, fn index ->
32+
assert_sink_buffer(pipeline, :sink, %Membrane.Buffer{pts: out_pts})
33+
assert out_pts == index * @pts_multiplier
34+
end)
35+
36+
assert_sink_buffer(pipeline, :sink, %Membrane.Buffer{pts: last_out_pts})
37+
assert last_out_pts == 30 * @pts_multiplier
38+
39+
assert_end_of_stream(pipeline, :sink)
40+
Testing.Pipeline.terminate(pipeline)
41+
end
42+
43+
defp buffers_from_file(path) do
44+
binary = File.read!(path)
45+
46+
split_binary(binary)
47+
|> Enum.with_index()
48+
|> Enum.map(fn {payload, index} ->
49+
%Membrane.Buffer{
50+
payload: payload,
51+
pts: index * @pts_multiplier
52+
}
53+
end)
54+
end
55+
56+
@spec split_binary(binary(), list(binary())) :: list(binary())
57+
def split_binary(binary, acc \\ [])
58+
59+
def split_binary(<<binary::binary-size(2048), rest::binary>>, acc) do
60+
split_binary(rest, [binary] ++ acc)
61+
end
62+
63+
def split_binary(rest, acc) when byte_size(rest) <= 2048 do
64+
Enum.reverse(acc) ++ [rest]
65+
end
66+
end

0 commit comments

Comments
 (0)