Live video on the AT Protocol
at eli/bump-xcode 210 lines 4.6 kB view raw
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}