Live video on the AT Protocol
1package renditions
2
3import (
4 "fmt"
5 "math"
6
7 "stream.place/streamplace/pkg/streamplace"
8)
9
10type FPS struct {
11 Passthrough bool
12 Num uint
13 Den uint
14}
15
16type Rendition struct {
17 Width int64
18 Height int64
19 Bitrate int
20 Framerate FPS
21 Profile string
22 Name string
23 Parent *Rendition
24}
25
26type JSONProfile struct {
27 Name string `json:"name,omitempty"`
28 Width int `json:"width,omitempty"`
29 Height int `json:"height,omitempty"`
30 Bitrate int `json:"bitrate,omitempty"`
31 FPS uint `json:"fps,omitempty"`
32 FPSDen uint `json:"fpsDen,omitempty"`
33 Profile string `json:"profile,omitempty"`
34 GOP string `json:"gop,omitempty"`
35 Encoder string `json:"encoder,omitempty"`
36 Quality uint `json:"quality,omitempty"`
37}
38
39func (r Rendition) ToLivepeerProfile() JSONProfile {
40 p := JSONProfile{
41 Name: r.Name,
42 Bitrate: r.Bitrate,
43 FPS: r.Framerate.Num,
44 FPSDen: r.Framerate.Den,
45 Profile: r.Profile,
46 }
47 if r.Parent == nil {
48 p.Width = int(r.Width)
49 p.Height = int(r.Height)
50 } else {
51 // We want to set the dimension that is the same as the parent
52 if r.Width < r.Height {
53 if r.Parent.Width == r.Height {
54 p.Height = int(r.Parent.Width)
55 } else {
56 p.Width = int(r.Parent.Height)
57 }
58 } else {
59 if r.Parent.Height == r.Height {
60 p.Height = int(r.Parent.Height)
61 } else {
62 p.Width = int(r.Parent.Width)
63 }
64 }
65 }
66 return p
67}
68
69type Renditions []Rendition
70
71func (rs Renditions) ToLivepeerProfiles() []JSONProfile {
72 profiles := make([]JSONProfile, len(rs))
73 for i, r := range rs {
74 profiles[i] = r.ToLivepeerProfile()
75 }
76 return profiles
77}
78
79var DesiredRenditions = []Rendition{
80 {
81 Name: "1080p",
82 Width: 1920,
83 Height: 1080,
84 Bitrate: 6_000_000,
85 Framerate: FPS{
86 Num: 60,
87 Den: 1,
88 },
89 Profile: "h264constrainedhigh",
90 },
91 {
92 Name: "720p",
93 Width: 1280,
94 Height: 720,
95 Bitrate: 3_000_000,
96 Framerate: FPS{
97 Num: 60,
98 Den: 1,
99 },
100 Profile: "h264constrainedhigh",
101 },
102 {
103 Name: "360p",
104 Width: 640,
105 Height: 360,
106 Bitrate: 1_000_000,
107 Framerate: FPS{
108 Num: 30,
109 Den: 1,
110 },
111 Profile: "h264constrainedhigh",
112 },
113 {
114 Name: "240p",
115 Width: 426,
116 Height: 240,
117 Bitrate: 500_000,
118 Framerate: FPS{
119 Num: 30,
120 Den: 1,
121 },
122 Profile: "h264constrainedhigh",
123 },
124 {
125 Name: "160p",
126 Width: 284,
127 Height: 160,
128 Bitrate: 250_000,
129 Framerate: FPS{
130 Num: 30,
131 Den: 1,
132 },
133 Profile: "h264baseline",
134 },
135}
136
137// GenerateRenditions generates renditions for a given spseg
138func GenerateRenditions(spseg *streamplace.Segment) (Renditions, error) {
139 vid := spseg.Video[0]
140 if vid == nil {
141 return nil, fmt.Errorf("no video stream found")
142 }
143 rs := []Rendition{}
144 for _, r := range DesiredRenditions {
145 vidWidth := int64(vid.Width)
146 vidHeight := int64(vid.Height)
147 vertical := vid.Height > vid.Width
148 // do all the math as if it's horizontal then flip at the end
149 if vertical {
150 vidWidth, vidHeight = vidHeight, vidWidth
151 }
152 if vidWidth <= r.Width && vidHeight <= r.Height {
153 continue
154 }
155 rAspectRatio := float64(r.Width) / float64(r.Height)
156 vidAspectRatio := float64(vidWidth) / float64(vidHeight)
157 if vidAspectRatio > rAspectRatio {
158 // vid is wider than r
159 // scale down to r.Width
160 scale := float64(r.Width) / float64(vidWidth)
161 vidWidth = r.Width
162 vidHeight = int64(math.Round(float64(vidHeight) * scale))
163 } else {
164 // vid is taller than r
165 // scale down to r.Height
166 scale := float64(r.Height) / float64(vidHeight)
167 vidHeight = r.Height
168 vidWidth = int64(math.Round(float64(vidWidth) * scale))
169 }
170 outR := Rendition{
171 Name: r.Name,
172 Parent: &r,
173 Profile: r.Profile,
174 }
175 if vertical {
176 outR.Width = vidHeight
177 outR.Height = vidWidth
178 } else {
179 outR.Width = vidWidth
180 outR.Height = vidHeight
181 }
182
183 // if vertical {
184 // ratio := float64(r.Height) / float64(vid.Height)
185 // outR.Height = int64(float64(vid.Width) * (16.0 / 9.0) * ratio)
186 // outR.Width = r.Height
187 // } else {
188 // ratio := float64(r.Width) / float64(vid.Width)
189 // outR.Width = r.Width
190 // outR.Height = int64(float64(vid.Width) * (9.0 / 16.0) * ratio)
191 // }
192 if vid.Framerate.Den > 0 {
193 vidFPS := float64(vid.Framerate.Num) / float64(vid.Framerate.Den)
194 rFPS := float64(r.Framerate.Num) / float64(r.Framerate.Den)
195 delta := rFPS / vidFPS
196
197 if rFPS < vidFPS {
198 if delta < 0.75 {
199 outR.Framerate.Num = uint(vid.Framerate.Num)
200 outR.Framerate.Den = uint(vid.Framerate.Den * 2)
201 }
202 }
203 }
204
205 outR.Bitrate = r.Bitrate
206 outR.Profile = r.Profile
207 rs = append(rs, outR)
208 }
209 return rs, nil
210}