|
| 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 | + |
| 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