Skip to content

Commit 1caffa3

Browse files
authored
Timestamps guide (#1033)
* Timestamps guide * WIP * Add PTS and DTS sections * Add visualisation * Update circleci config * Fix typos * Add guidelines section * Apply reviewers suggestions * Apply reviewers suggestions
1 parent 79c85d9 commit 1caffa3

3 files changed

Lines changed: 144 additions & 0 deletions

File tree

.circleci/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ jobs:
7070

7171
steps:
7272
- checkout
73+
- run: git submodule sync
74+
- run: git submodule update --init
7375
- restore_deps_cache
7476
- run: mix deps.get
7577
- save_deps_cache
125 KB
Loading
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Timestamps
2+
3+
In a nutshell, timestamps determine when a given event occurred (or should occur)
4+
in time. For example, when you take a photo with your phone, the exact time and
5+
date the photo is taken is recorded - it's a timestamp. When dealing with media
6+
we also need a way to tell when different things need to happen. In Membrane we
7+
use the two most common types of timestamps:
8+
9+
* PTS (Presentation Time Stamp) - determines when the media should be
10+
displayed.
11+
* DTS (Decoding Time Stamp) - informs the decoder when the media should
12+
be decoded.
13+
14+
## Time in Membrane
15+
16+
We know that timestamps represent the time of occurrence of an event, but these
17+
concepts are pretty abstract. We need to somehow represent them in the context
18+
of our framework. To represent time - durations, latencies, timestamps - we
19+
use terms of type `t:Membrane.Time.t/0`:
20+
21+
* To create a term representing some amount of time, we use
22+
`Membrane.Time.<unit>/0` and `Membrane.Time.<unit>s/1` functions. For example,
23+
to create a term representing three seconds, we call [`Membrane.Time.seconds(3)`](`Membrane.Time.seconds/1`).
24+
* To read the amount of time represented, we can use `Membrane.Time.as_<unit>/2`
25+
functions. For example, to get an amount of milliseconds represented by a time,
26+
we call [`Membrane.Time.as_milliseconds(some_time)`](`Membrane.Time.as_milliseconds/1`).
27+
This function also allows for rounding the result - you can use `:round` mode
28+
to round the result to the nearest integer or `:exact` mode (the default)
29+
to get the result as a [rational number](https://hexdocs.pm/ratio/Ratio.html#t:t/0).
30+
31+
## Carriers of timestamps
32+
33+
We now have a way to represent timestamps, but for them to be useful,
34+
they have to refer to something, an event of some sort. Media stream
35+
chunks in Membrane are packaged in [Buffers](`t:Membrane.Buffer.t/0`)
36+
when sent between elements. A buffer is a struct with 4 fields:
37+
38+
* `:payload` - data contained in the buffer
39+
* `:pts` and `:dts` - timestamps assigned to the buffer
40+
* `:metadata` - metadata describing the contents of the buffer
41+
42+
A buffer often corresponds to some unit which the stream is composed of, for
43+
example video frames in raw video streams or RTP packets in RTP streams.
44+
These units finally can have timestamps assigned to them - and in
45+
most cases they do. For example, a PTS assigned to a buffer containing a
46+
raw video frame determines when the frame should be displayed.
47+
48+
## Presentation Time Stamps (PTS)
49+
50+
As previously mentioned, PTSs are used to tell when a piece of media should be
51+
presented to the user. It can mean either displaying a video frame, or playing a
52+
chunk of audio.
53+
54+
### Realtimer
55+
56+
[Realtimer](https://hexdocs.pm/membrane_realtimer_plugin/Membrane.Realtimer.html)
57+
is an element from
58+
[membrane_realtimer_plugin](https://hex.pm/packages/membrane_realtimer_plugin).
59+
It takes in a stream and limits its flow according to its DTSs or PTSs.
60+
For example, if it receives three buffers with timestamps of 0ms, 200ms
61+
and 400ms, then it will send the first buffer, the second buffer after
62+
200ms pass, and the third one after another 200ms pass.
63+
64+
This element is useful if we have non-realtime input, and realtime output, for
65+
example we want to stream the contents of a MP4 file with WebRTC. If we didn't
66+
use Realtimer, then we would read the contents of the file as fast as possible and
67+
send them over as fast as possible, which is not something we want. We want the receiver
68+
to get the stream in realtime, so that they can display it as it comes.
69+
70+
## Decode Time Stamps (DTS)
71+
72+
The purpose of DTSs is to tell a decoder when a frame should be
73+
decoded. In a lot of codecs the media can be decoded as it comes, but in some
74+
cases, like in [H264](../membrane_tutorials/h264/1_Introduction.md), it's
75+
not that simple. In a nutshell, in H264 some frames are encoded based on
76+
information from other frames. There are three main types of frames:
77+
78+
* I-frame (Intra-coded picture) - A frame of this type is encoded without the
79+
information from any other frames. Sometimes referred to as a _keyframe_.
80+
* P-frame (Predicted picture) - A frame of this type is encoded with the usage
81+
of information from previous frames. If we have a static scene, then it takes
82+
much less space to encode a frame by using the fact that it's almost the
83+
same as the previous one and encoding only the things that have changed.
84+
* B-frame (Bidirectional predicted picture) - A frame of this type is similar
85+
to a P-frame, as it uses information from other frames for its encoding.
86+
However, it not only depends on previous pictures, but also on future ones.
87+
That's where the DTSs come in, because to decode a B-frame we also need to
88+
decode all frames it's based on, including the future ones.
89+
90+
![image](./assets/frame_types.png)
91+
92+
For example, let's assume that we have a slice of a stream from the diagram,
93+
consisting of four frames. Frames 1 and 4 are I-frames, frame 2 is a P-frame
94+
depending on frame 1, and frame 3 is a B-frame depending on frames 2 and 4.
95+
If a decoder receives these frames with the following timestamps:
96+
97+
* [1] pts: 0ms, dts: 0ms
98+
* [2] pts: 200ms, dts: 200ms
99+
* [4] pts: 600ms, dts: 400ms
100+
* [3] pts: 400ms, dts: 600ms
101+
102+
It will first decode the frames in order (1, 2, 4, 3), according to their DTSs.
103+
If it hadn't decoded frames 2 and 4 first, it couldn't have decoded frame 3.
104+
It's also important to note that DTSs should always be monotonic, while PTSs
105+
for streams with B-frames can be non-monotonic.
106+
107+
## Tips and guidelines
108+
109+
Dealing with timestamps can be complicated and very different depending on the
110+
use case, so here is some advice on dealing with them:
111+
112+
* Filters should always forward timestamps.
113+
* If a filter doesn't use timestamps, it should still forward them.
114+
* If an element relies on timestamps and they are not set, it should raise a
115+
meaningful error.
116+
* Sources should attach timestamps to buffers whenever they're known.
117+
* Whenever possible, elements should rely on timestamps instead of
118+
framerate or audio duration calculated from the stream.
119+
* If an element queues buffers in its state (or uses a library that does so), it
120+
should make sure that the timestamps for the output buffers are the same as for
121+
the corresponding input buffers.
122+
* You should ensure that calculations on timestamps don't introduce an
123+
accumulating error. Prefer using [rationals](https://hexdocs.pm/ratio/Ratio.html#t:t/0)
124+
over floats.
125+
* Elements should generate deterministic output timestamps for better testability.
126+
* If an element transforms N input buffers into M output buffers, each of the
127+
output buffers should have either:
128+
* the timestamp of the first of the input buffers (even if only a part of it
129+
was used to construct the output buffers).
130+
* more precise timestamps, if it's possible to calculate them.
131+
* Timestamps are harder than they seem and are the source of many bugs, including:
132+
* Audio/video desynchronization
133+
* Stream hanging (due to waiting indefinitely to process/play a buffer because
134+
of a wrong timestamp)
135+
* Stream stalls (e.g. due to processing a real-time stream slightly faster
136+
than real-time)
137+
* Memory leaks (e.g. due to processing a real-time stream slightly slower
138+
than real-time and indefinite buffering)
139+
* Video flickering (due to incorrect handling of B-frames)
140+
* Audio cracking
141+
142+
Therefore, operations on timestamps should be given a lot of care and be well-tested.

0 commit comments

Comments
 (0)