Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/selkies/webrtc/jitterbuffer.py: 15%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

92 statements  

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