1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at https://mozilla.org/MPL/2.0/.
4#
5# This file incorporates work covered by the following copyright and
6# permission notice:
7#
8# Copyright (c) Jeremy Lainé.
9# All rights reserved.
10#
11# Redistribution and use in source and binary forms, with or without
12# modification, are permitted provided that the following conditions are met:
13#
14# * Redistributions of source code must retain the above copyright notice,
15# this list of conditions and the following disclaimer.
16# * Redistributions in binary form must reproduce the above copyright notice,
17# this list of conditions and the following disclaimer in the documentation
18# and/or other materials provided with the distribution.
19# * Neither the name of aiortc nor the names of its contributors may
20# be used to endorse or promote products derived from this software without
21# specific prior written permission.
22#
23# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34from typing import Optional
35
36from .rtp import RtpPacket
37from .utils import uint16_add
38
39MAX_MISORDER = 100
40
41
42class JitterFrame:
43 def __init__(self, data: bytes, timestamp: int) -> None:
44 self.data = data
45 self.timestamp = timestamp
46
47
48class JitterBuffer:
49 def __init__(
50 self, capacity: int, prefetch: int = 0, is_video: bool = False
51 ) -> None:
52 assert capacity & (capacity - 1) == 0, "capacity must be a power of 2"
53 self._capacity = capacity
54 self._origin: Optional[int] = None
55 self._packets: list[Optional[RtpPacket]] = [None for i in range(capacity)]
56 self._prefetch = prefetch
57 self._is_video = is_video
58
59 @property
60 def capacity(self) -> int:
61 return self._capacity
62
63 def add(self, packet: RtpPacket) -> tuple[bool, Optional[JitterFrame]]:
64 pli_flag = False
65 if self._origin is None:
66 self._origin = packet.sequence_number
67 delta = 0
68 misorder = 0
69 else:
70 delta = uint16_add(packet.sequence_number, -self._origin)
71 misorder = uint16_add(self._origin, -packet.sequence_number)
72
73 if misorder < delta:
74 if misorder >= MAX_MISORDER:
75 self.remove(self.capacity)
76 self._origin = packet.sequence_number
77 delta = misorder = 0
78 if self._is_video:
79 pli_flag = True
80 else:
81 return pli_flag, None
82
83 if delta >= self.capacity:
84 # remove just enough frames to fit the received packets
85 excess = delta - self.capacity + 1
86 if self.smart_remove(excess):
87 self._origin = packet.sequence_number
88 if self._is_video:
89 pli_flag = True
90
91 pos = packet.sequence_number % self._capacity
92 self._packets[pos] = packet
93
94 return pli_flag, self._remove_frame(packet.sequence_number)
95
96 def _remove_frame(self, sequence_number: int) -> Optional[JitterFrame]:
97 frame = None
98 frames = 0
99 packets: list[RtpPacket] = []
100 remove = 0
101 timestamp = None
102
103 for count in range(self.capacity):
104 pos = (self._origin + count) % self._capacity
105 packet = self._packets[pos]
106 if packet is None:
107 break
108 if timestamp is None:
109 timestamp = packet.timestamp
110 elif packet.timestamp != timestamp:
111 # we now have a complete frame, only store the first one
112 if frame is None:
113 # A codec whose packets cannot be joined carries its own assembler.
114 assemble = getattr(packets[0], "_assemble", None)
115 parts = [x._data for x in packets] # type: ignore
116 frame = JitterFrame(
117 data=assemble(parts) if assemble else b"".join(parts),
118 timestamp=timestamp,
119 )
120 remove = count
121
122 # check we have prefetched enough
123 frames += 1
124 if frames >= self._prefetch:
125 self.remove(remove)
126 return frame
127
128 # start a new frame
129 packets = []
130 timestamp = packet.timestamp
131
132 packets.append(packet)
133
134 return None
135
136 def remove(self, count: int) -> None:
137 assert count <= self._capacity
138 for i in range(count):
139 pos = self._origin % self._capacity
140 self._packets[pos] = None
141 self._origin = uint16_add(self._origin, 1)
142
143 def smart_remove(self, count: int) -> bool:
144 """
145 Makes sure that all packages belonging to the same frame are removed
146 to prevent sending corrupted frames to the decoder.
147 """
148 timestamp = None
149 for i in range(self._capacity):
150 pos = self._origin % self._capacity
151 packet = self._packets[pos]
152 if packet is not None:
153 if i >= count and timestamp != packet.timestamp:
154 break
155 timestamp = packet.timestamp
156 self._packets[pos] = None
157 self._origin = uint16_add(self._origin, 1)
158 if i == self._capacity - 1:
159 return True
160 return False