Live video on the AT Protocol
1package renditions
2
3import (
4 "encoding/json"
5 "testing"
6
7 "github.com/stretchr/testify/require"
8 "stream.place/streamplace/pkg/streamplace"
9)
10
11func seg(width int, height int, fpsNum int, fpsDen int) *streamplace.Segment {
12 return &streamplace.Segment{
13 Video: []*streamplace.Segment_Video{
14 {
15 Width: int64(width),
16 Height: int64(height),
17 Framerate: &streamplace.Segment_Framerate{
18 Num: int64(fpsNum),
19 Den: int64(fpsDen),
20 },
21 },
22 },
23 }
24}
25
26var cases = []struct {
27 name string
28 spseg *streamplace.Segment
29 lp string
30}{
31 {
32 name: "4K 60fps",
33 spseg: seg(3840, 2160, 60, 1),
34 lp: `
35 [
36 {
37 "name": "1080p",
38 "height": 1080,
39 "bitrate": 6000000,
40 "profile": "h264constrainedhigh"
41 },
42 {
43 "name": "720p",
44 "height": 720,
45 "bitrate": 3000000,
46 "profile": "h264constrainedhigh"
47 },
48 {
49 "name": "360p",
50 "height": 360,
51 "bitrate": 1000000,
52 "profile": "h264constrainedhigh",
53 "fps": 60,
54 "fpsDen": 2
55 },
56 {
57 "name": "240p",
58 "height": 240,
59 "bitrate": 500000,
60 "profile": "h264constrainedhigh",
61 "fps": 60,
62 "fpsDen": 2
63 },
64 {
65 "name": "160p",
66 "height": 160,
67 "bitrate": 250000,
68 "profile": "h264baseline",
69 "fps": 60,
70 "fpsDen": 2
71 }
72 ]
73 `,
74 },
75 {
76 name: "2K with fractional framerate",
77 spseg: seg(2160, 1440, 60000, 1001),
78 lp: `
79 [
80 {
81 "name": "1080p",
82 "height": 1080,
83 "bitrate": 6000000,
84 "profile": "h264constrainedhigh"
85 },
86 {
87 "name": "720p",
88 "height": 720,
89 "bitrate": 3000000,
90 "profile": "h264constrainedhigh"
91 },
92 {
93 "name": "360p",
94 "height": 360,
95 "bitrate": 1000000,
96 "profile": "h264constrainedhigh",
97 "fps": 60000,
98 "fpsDen": 2002
99 },
100 {
101 "name": "240p",
102 "height": 240,
103 "bitrate": 500000,
104 "profile": "h264constrainedhigh",
105 "fps": 60000,
106 "fpsDen": 2002
107 },
108 {
109 "name": "160p",
110 "height": 160,
111 "bitrate": 250000,
112 "profile": "h264baseline",
113 "fps": 60000,
114 "fpsDen": 2002
115 }
116 ]
117 `,
118 },
119 {
120 name: "720p 50fps",
121 spseg: seg(1280, 720, 50, 1),
122 lp: `
123 [
124 {
125 "name": "360p",
126 "height": 360,
127 "bitrate": 1000000,
128 "profile": "h264constrainedhigh",
129 "fps": 50,
130 "fpsDen": 2
131 },
132 {
133 "name": "240p",
134 "height": 240,
135 "bitrate": 500000,
136 "profile": "h264constrainedhigh",
137 "fps": 50,
138 "fpsDen": 2
139 },
140 {
141 "name": "160p",
142 "height": 160,
143 "bitrate": 250000,
144 "profile": "h264baseline",
145 "fps": 50,
146 "fpsDen": 2
147 }
148 ]
149 `,
150 },
151 {
152 name: "720p 30fps",
153 spseg: seg(1280, 720, 30, 1),
154 lp: `
155 [
156 {
157 "name": "360p",
158 "height": 360,
159 "bitrate": 1000000,
160 "profile": "h264constrainedhigh"
161 },
162 {
163 "name": "240p",
164 "height": 240,
165 "bitrate": 500000,
166 "profile": "h264constrainedhigh"
167 },
168 {
169 "name": "160p",
170 "height": 160,
171 "bitrate": 250000,
172 "profile": "h264baseline"
173 }
174 ]
175 `,
176 },
177 {
178 name: "720p 25fps",
179 spseg: seg(1280, 720, 25, 1),
180 lp: `
181 [
182 {
183 "name": "360p",
184 "height": 360,
185 "bitrate": 1000000,
186 "profile": "h264constrainedhigh"
187 },
188 {
189 "name": "240p",
190 "height": 240,
191 "bitrate": 500000,
192 "profile": "h264constrainedhigh"
193 },
194 {
195 "name": "160p",
196 "height": 160,
197 "bitrate": 250000,
198 "profile": "h264baseline"
199 }
200 ]
201 `,
202 },
203 {
204 name: "Vertical video 60fps",
205 spseg: seg(480, 640, 60, 1),
206 lp: `
207 [
208 {
209 "name": "360p",
210 "width": 360,
211 "bitrate": 1000000,
212 "profile": "h264constrainedhigh",
213 "fps": 60,
214 "fpsDen": 2
215 },
216 {
217 "name": "240p",
218 "width": 240,
219 "bitrate": 500000,
220 "profile": "h264constrainedhigh",
221 "fps": 60,
222 "fpsDen": 2
223 },
224 {
225 "name": "160p",
226 "width": 160,
227 "bitrate": 250000,
228 "profile": "h264baseline",
229 "fps": 60,
230 "fpsDen": 2
231 }
232 ]
233 `,
234 },
235}
236
237func TestRenditions(t *testing.T) {
238 for _, c := range cases {
239 t.Run(c.name, func(t *testing.T) {
240 rends, err := GenerateRenditions(c.spseg)
241 require.NoError(t, err)
242 lp := rends.ToLivepeerProfiles()
243 bs, err := json.Marshal(lp)
244 require.NoError(t, err)
245 require.JSONEq(t, c.lp, string(bs))
246 })
247 }
248}
249
250var singleCases = []struct {
251 name string
252 spseg *streamplace.Segment
253 lp string
254 dimensions []int
255}{
256 {
257 name: "Nearly-Square Landscape 4K 60fps",
258 spseg: seg(3840, 3830, 60, 1),
259 dimensions: []int{1083, 1080},
260 lp: `
261 {
262 "name": "1080p",
263 "height": 1080,
264 "bitrate": 6000000,
265 "profile": "h264constrainedhigh"
266 }
267 `,
268 },
269 {
270 name: "Nearly-Square Portrait 4K 60fps",
271 spseg: seg(3830, 3840, 60, 1),
272 dimensions: []int{1080, 1083},
273 lp: `
274 {
275 "name": "1080p",
276 "width": 1080,
277 "bitrate": 6000000,
278 "profile": "h264constrainedhigh"
279 }
280 `,
281 },
282 {
283 name: "Stupidly-Wide Landscape 4K 60fps",
284 spseg: seg(5000, 2160, 60, 1),
285 dimensions: []int{1920, 829},
286 lp: `
287 {
288 "name": "1080p",
289 "width": 1920,
290 "bitrate": 6000000,
291 "profile": "h264constrainedhigh"
292 }
293 `,
294 },
295 {
296 name: "Stupidly-Tall Portrait 4K 60fps",
297 spseg: seg(2160, 5000, 60, 1),
298 dimensions: []int{829, 1920},
299 lp: `
300 {
301 "name": "1080p",
302 "height": 1920,
303 "bitrate": 6000000,
304 "profile": "h264constrainedhigh"
305 }
306 `,
307 },
308}
309
310func TestSingleRendition(t *testing.T) {
311 for _, c := range singleCases {
312 t.Run(c.name, func(t *testing.T) {
313 rends, err := GenerateRenditions(c.spseg)
314 require.NoError(t, err)
315 first := rends[0]
316 require.Equal(t, c.dimensions, []int{int(first.Width), int(first.Height)})
317 lp := rends.ToLivepeerProfiles()
318 bs, err := json.Marshal(lp[0])
319 require.NoError(t, err)
320 require.JSONEq(t, c.lp, string(bs))
321 })
322 }
323}