Skip to content

Commit 160794e

Browse files
authored
Fix chunk offsets (#110)
1 parent 0c81ee5 commit 160794e

File tree

11 files changed

+215
-59
lines changed

11 files changed

+215
-59
lines changed

lib/membrane_mp4/container/header.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule Membrane.MP4.Container.Header do
44
55
The `content_size` field is equal to the box size minus the size of the header (8 bytes).
66
"""
7+
use Bunch.Access
78

89
@enforce_keys [:name, :content_size, :header_size]
910

lib/membrane_mp4/container/parse_helper.ex

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,24 @@ defmodule Membrane.MP4.Container.ParseHelper do
1818

1919
def parse_boxes(data, schema, context, acc) do
2020
withl header_content:
21-
{:ok, %{name: name, content_size: content_size}, rest} <- Header.parse(data),
21+
{:ok, %{name: name, content_size: content_size, header_size: header_size}, rest} <-
22+
Header.parse(data),
2223
header_content: <<content::binary-size(content_size), data::binary>> <- rest,
2324
do: box_schema = schema[name],
2425
known?: true <- box_schema && not box_schema.black_box?,
2526
try:
2627
{:ok, {fields, rest}, context} <- parse_fields(content, box_schema.fields, context),
2728
try:
2829
{:ok, children, <<>>, context} <- parse_boxes(rest, box_schema.children, context, []) do
29-
box = %{fields: fields, children: children}
30+
box = %{fields: fields, children: children, size: content_size, header_size: header_size}
3031
parse_boxes(data, schema, context, [{name, box} | acc])
3132
else
3233
header_content: _error ->
3334
# more data needed
3435
{:ok, Enum.reverse(acc), data, context}
3536

3637
known?: _ ->
37-
box = %{content: content}
38+
box = %{content: content, size: content_size, header_size: header_size}
3839
parse_boxes(data, schema, context, [{name, box} | acc])
3940

4041
try: {:error, context} ->

lib/membrane_mp4/container/schema.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ defmodule Membrane.MP4.Container.Schema do
289289
{:list,
290290
[
291291
sample_count: :uint32,
292-
sample_offset: :uint32
292+
sample_composition_offset: :uint32
293293
]}
294294
]
295295
],
@@ -448,7 +448,8 @@ defmodule Membrane.MP4.Container.Schema do
448448
sample_duration: :uint32,
449449
sample_size: :uint32,
450450
sample_flags: :bin32,
451-
sample_offset: {:uint32, when: {0x800, :fo_flags, 0x800}}
451+
sample_composition_offset:
452+
{:uint32, when: {0x800, :fo_flags, 0x800}}
452453
]}
453454
]
454455
]

lib/membrane_mp4/demuxer/isom.ex

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,10 @@ defmodule Membrane.MP4.Demuxer.ISOM do
142142
} = state
143143
) do
144144
{samples, rest, samples_info} =
145-
SamplesInfo.get_samples(state.samples_info, state.partial <> buffer.payload)
145+
SamplesInfo.get_samples(
146+
state.samples_info,
147+
state.partial <> buffer.payload
148+
)
146149

147150
buffers = get_buffer_actions(samples)
148151

@@ -157,7 +160,10 @@ defmodule Membrane.MP4.Demuxer.ISOM do
157160
) do
158161
# Until all pads are connected we are storing all the samples
159162
{samples, rest, samples_info} =
160-
SamplesInfo.get_samples(state.samples_info, state.partial <> buffer.payload)
163+
SamplesInfo.get_samples(
164+
state.samples_info,
165+
state.partial <> buffer.payload
166+
)
161167

162168
state = store_samples(state, samples)
163169

@@ -176,40 +182,43 @@ defmodule Membrane.MP4.Demuxer.ISOM do
176182

177183
maybe_header = parse_header(rest)
178184

179-
state =
180-
if maybe_header,
181-
do: %{
182-
state
183-
| mdat_size: maybe_header.content_size,
184-
mdat_header_size: maybe_header.header_size
185-
},
186-
else: state
187-
188185
update_fsm_state_ctx =
189186
if :mdat in Keyword.keys(state.boxes) or
190187
(maybe_header != nil and maybe_header.name == :mdat) do
191188
:started_parsing_mdat
192189
end
193190

194-
state = update_fsm_state(state, update_fsm_state_ctx) |> set_partial(rest)
191+
state =
192+
set_mdat_metadata(state, update_fsm_state_ctx, maybe_header)
193+
|> update_fsm_state(update_fsm_state_ctx)
194+
|> set_partial(rest)
195195

196196
cond do
197197
state.fsm_state == :mdat_reading ->
198198
handle_can_read_mdat_box(ctx, state)
199199

200200
state.optimize_for_non_fast_start? ->
201-
state =
202-
if state.fsm_state == :skip_mdat,
203-
do: %{state | mdat_beginning: state.boxes_size},
204-
else: state
205-
206201
handle_non_fast_start_optimization(state)
207202

208203
true ->
209204
{[], state}
210205
end
211206
end
212207

208+
defp set_mdat_metadata(state, context, maybe_header) do
209+
if context == :started_parsing_mdat do
210+
%{
211+
state
212+
| mdat_beginning: state.mdat_beginning || get_mdat_header_beginning(state.boxes),
213+
mdat_header_size:
214+
state.mdat_header_size || maybe_header[:header_size] || state.boxes[:mdat].header_size,
215+
mdat_size: state.mdat_size || maybe_header[:content_size] || state.boxes[:mdat].size
216+
}
217+
else
218+
state
219+
end
220+
end
221+
213222
defp set_partial(state, rest) do
214223
partial = if state.fsm_state in [:skip_mdat, :go_back_to_mdat], do: <<>>, else: rest
215224
%{state | partial: partial}
@@ -285,7 +294,12 @@ defmodule Membrane.MP4.Demuxer.ISOM do
285294
end
286295

287296
defp handle_non_fast_start_optimization(%{fsm_state: :go_back_to_mdat} = state) do
288-
seek(state, state.mdat_beginning, state.mdat_size + state.mdat_header_size, false)
297+
seek(
298+
state,
299+
state.mdat_beginning,
300+
state.mdat_size + state.mdat_header_size,
301+
false
302+
)
289303
end
290304

291305
defp handle_non_fast_start_optimization(state) do
@@ -315,7 +329,14 @@ defmodule Membrane.MP4.Demuxer.ISOM do
315329
end
316330

317331
state =
318-
%{state | samples_info: SamplesInfo.get_samples_info(state.boxes[:moov])}
332+
%{
333+
state
334+
| samples_info:
335+
SamplesInfo.get_samples_info(
336+
state.boxes[:moov],
337+
state.mdat_beginning + state.mdat_header_size
338+
)
339+
}
319340
|> update_fsm_state()
320341

321342
# Parse the data we received so far (partial or the whole mdat box in a single buffer) and
@@ -330,7 +351,9 @@ defmodule Membrane.MP4.Demuxer.ISOM do
330351
content
331352
end
332353

333-
{samples, rest, samples_info} = SamplesInfo.get_samples(state.samples_info, data)
354+
{samples, rest, samples_info} =
355+
SamplesInfo.get_samples(state.samples_info, data)
356+
334357
state = %{state | samples_info: samples_info, partial: rest}
335358

336359
all_pads_connected? = all_pads_connected?(ctx, state)
@@ -472,4 +495,16 @@ defmodule Membrane.MP4.Demuxer.ISOM do
472495
{:end_of_stream, pad_ref}
473496
end)
474497
end
498+
499+
defp get_mdat_header_beginning([]) do
500+
0
501+
end
502+
503+
defp get_mdat_header_beginning([{:mdat, _box} | _rest]) do
504+
0
505+
end
506+
507+
defp get_mdat_header_beginning([{_other_name, box} | rest]) do
508+
box.header_size + box.size + get_mdat_header_beginning(rest)
509+
end
475510
end

lib/membrane_mp4/demuxer/isom/samples_info.ex

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do
1515
:tracks_number,
1616
:timescales,
1717
:last_dts,
18-
:sample_tables
18+
:sample_tables,
19+
:mdat_iterator
1920
]
2021

2122
defstruct @enforce_keys
@@ -42,7 +43,8 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do
4243
(track_id :: pos_integer()) => last_dts :: Ratio.t() | nil
4344
},
4445
tracks_number: pos_integer(),
45-
sample_tables: %{(track_id :: pos_integer()) => SampleTable.t()}
46+
sample_tables: %{(track_id :: pos_integer()) => SampleTable.t()},
47+
mdat_iterator: non_neg_integer()
4648
}
4749

4850
@doc """
@@ -51,9 +53,10 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do
5153
a whole sample, and has yet to be parsed.
5254
"""
5355
@spec get_samples(t, data :: binary()) ::
54-
{[{Buffer.t(), track_id :: pos_integer()}], rest :: binary, t}
56+
{[{Buffer.t(), track_id :: pos_integer()}], rest :: binary(), t()}
5557
def get_samples(samples_info, data) do
56-
{samples_info, rest, buffers} = do_get_samples(samples_info, data, [])
58+
{samples_info, rest, buffers} =
59+
do_get_samples(samples_info, data, [])
5760

5861
{buffers, rest, samples_info}
5962
end
@@ -63,24 +66,32 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do
6366
end
6467

6568
defp do_get_samples(samples_info, data, buffers) do
66-
[%{size: size, track_id: track_id} = sample | samples] = samples_info.samples
69+
[%{size: size, track_id: track_id, sample_offset: sample_offset} = sample | samples] =
70+
samples_info.samples
6771

68-
if size <= byte_size(data) do
69-
<<payload::binary-size(size), rest::binary>> = data
72+
to_skip = sample_offset - samples_info.mdat_iterator
7073

71-
{dts, pts, samples_info} = get_dts_and_pts(samples_info, sample)
74+
case data do
75+
<<_to_skip::binary-size(to_skip), payload::binary-size(size), rest::binary>> ->
76+
{dts, pts, samples_info} = get_dts_and_pts(samples_info, sample)
7277

73-
buffer =
74-
{%Buffer{
75-
payload: payload,
76-
dts: dts,
77-
pts: pts
78-
}, track_id}
78+
buffer =
79+
{%Buffer{
80+
payload: payload,
81+
dts: dts,
82+
pts: pts
83+
}, track_id}
7984

80-
samples_info = %{samples_info | samples: samples}
81-
do_get_samples(samples_info, rest, [buffer | buffers])
82-
else
83-
{samples_info, data, Enum.reverse(buffers)}
85+
samples_info = %{samples_info | samples: samples}
86+
87+
do_get_samples(
88+
%{samples_info | mdat_iterator: samples_info.mdat_iterator + to_skip + size},
89+
rest,
90+
[buffer | buffers]
91+
)
92+
93+
_other ->
94+
{samples_info, data, Enum.reverse(buffers)}
8495
end
8596
end
8697

@@ -118,8 +129,8 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do
118129
present in the `mdat` box.
119130
The list of samples in the returned struct is used to extract data from the `mdat` box and get output buffers.
120131
"""
121-
@spec get_samples_info(%{children: boxes :: Container.t()}) :: t
122-
def get_samples_info(%{children: boxes}) do
132+
@spec get_samples_info(%{children: boxes :: Container.t()}, non_neg_integer()) :: t
133+
def get_samples_info(%{children: boxes}, mdat_beginning) do
123134
tracks =
124135
boxes
125136
|> Enum.filter(fn {type, _content} -> type == :trak end)
@@ -159,15 +170,18 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do
159170
:decoding_deltas,
160171
:sample_sizes,
161172
:samples_per_chunk,
162-
:composition_offsets
173+
:composition_offsets,
174+
:chunk_offset
163175
])}
164176
end)
165177

166178
# Create a samples' description list for each chunk and flatten it
167179
{samples, _acc} =
168180
chunk_offsets
169181
|> Enum.flat_map_reduce(tracks_data, fn %{track_id: track_id} = chunk, tracks_data ->
170-
{new_samples, track_data} = get_chunk_samples(chunk, tracks_data[track_id])
182+
{new_samples, {track_data, _sample_offset}} =
183+
get_chunk_samples(chunk, tracks_data[track_id])
184+
171185
{new_samples, %{tracks_data | track_id => track_data}}
172186
end)
173187

@@ -183,19 +197,28 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do
183197
tracks_number: map_size(tracks),
184198
timescales: timescales,
185199
last_dts: last_dts,
186-
sample_tables: sample_tables
200+
sample_tables: sample_tables,
201+
mdat_iterator: mdat_beginning
187202
}
188203
end
189204

190205
defp get_chunk_samples(chunk, track_data) do
191-
%{chunk_no: chunk_no, track_id: track_id} = chunk
206+
%{chunk_no: chunk_no, track_id: track_id, chunk_offset: chunk_offset} = chunk
192207

193208
{track_data, samples_no} = get_samples_no(chunk_no, track_data)
194209

195-
Enum.map_reduce(1..samples_no, track_data, fn _no, track_data ->
210+
Enum.map_reduce(1..samples_no, {track_data, chunk_offset}, fn _no,
211+
{track_data, sample_offset} ->
196212
{sample, track_data} = get_sample_description(track_data)
197-
sample = Map.put(sample, :track_id, track_id)
198-
{sample, track_data}
213+
214+
sample =
215+
Map.merge(sample, %{
216+
track_id: track_id,
217+
chunk_offset: chunk_offset,
218+
sample_offset: sample_offset
219+
})
220+
221+
{sample, {track_data, sample_offset + sample.size}}
199222
end)
200223
end
201224

@@ -250,14 +273,19 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do
250273

251274
{sample_composition_offset, composition_offsets} =
252275
case composition_offsets do
253-
[%{sample_count: 1, sample_offset: offset} | composition_offsets] ->
276+
[%{sample_count: 1, sample_composition_offset: offset} | composition_offsets] ->
254277
{offset, composition_offsets}
255278

256-
[%{sample_count: count, sample_offset: offset} | composition_offsets] ->
257-
{offset, [%{sample_count: count - 1, sample_offset: offset} | composition_offsets]}
279+
[%{sample_count: count, sample_composition_offset: offset} | composition_offsets] ->
280+
{offset,
281+
[%{sample_count: count - 1, sample_composition_offset: offset} | composition_offsets]}
258282
end
259283

260-
{%{size: size, sample_delta: delta, sample_composition_offset: sample_composition_offset},
284+
{%{
285+
size: size,
286+
sample_delta: delta,
287+
sample_composition_offset: sample_composition_offset
288+
},
261289
%{
262290
track_data
263291
| decoding_deltas: deltas,

lib/membrane_mp4/movie_box/sample_table_box.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ defmodule Membrane.MP4.MovieBox.SampleTableBox do
255255
# if no :ctts box is available, assume that the offset between
256256
# composition time and the decoding time is equal to 0
257257
Enum.map(boxes[:stts].fields.entry_list, fn entry ->
258-
%{sample_count: entry.sample_count, sample_offset: 0}
258+
%{sample_count: entry.sample_count, sample_composition_offset: 0}
259259
end)
260260
end
261261
end

lib/membrane_mp4/muxer/cmaf.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ defmodule Membrane.MP4.Muxer.CMAF do
568568
sample.metadata.duration
569569
|> Helper.timescalify(timescale)
570570
|> Ratio.trunc(),
571-
sample_offset: Helper.timescalify(sample.pts - sample.dts, timescale)
571+
sample_composition_offset: Helper.timescalify(sample.pts - sample.dts, timescale)
572572
}
573573
end)
574574
end

lib/membrane_mp4/track/sample_table.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ defmodule Membrane.MP4.Track.SampleTable do
2828
],
2929
composition_offsets: [
3030
%{
31-
sample_offset: Ratio.t(),
31+
sample_composition_offset: Ratio.t(),
3232
sample_count: pos_integer
3333
}
3434
],
14.7 MB
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)