Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Transcode Quality #371

Merged
merged 12 commits into from
Aug 30, 2023
18 changes: 18 additions & 0 deletions ffmpeg/ffmpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,24 @@ func createCOutputParams(input *TranscodeOptionsIn, ps []TranscodeOptions) ([]C.
if len(p.VideoEncoder.Name) <= 0 && len(p.VideoEncoder.Opts) <= 0 {
p.VideoEncoder.Opts = map[string]string{
"forced-idr": "1",
"preset": "slow",
"tier": "high",
}
if p.Profile.CRF != 0 {
if p.Profile.CRF <= 63 {
p.VideoEncoder.Opts["crf"] = strconv.Itoa(int(p.Profile.CRF))
} else {
glog.Warning("Cannot use CRF param, value out of range (0-63)")
}

// There's no direct numerical correspondence between CQ and CRF.
// From some experiments, it seems that setting CQ = CRF + 7 gives similar visual effects.
cq := p.Profile.CRF + 7
Copy link

@emranemran emranemran Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you end up with this formula? Lower CQ values typically indicate better quality at expense of compressions. Also encoders typically will pick either CQ and CRF as the main control so I'm not sure if CQ here will actually make a difference. Do you see a difference if we leave out CQ changes here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRF is used by CPU-based transcoding, CQ is used by GPU-based transcoding.
So, only one of these values will be used at the time.

Technically there is no direct numerical correspondence between CQ and CRF, so the other option would be to set both CRF and CQ in our API. But I think it's not a pragmatic approach right now. Most of our transcoders are GPU anyway.

How did I come with cq := p.Profile.CRF + 7, just experimenting with videos. But obviously not for all videos it will give the same results, since technically CRF and CQ values do not correspond to each other.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cq := p.Profile.CRF + 7 doesn't quite make sense to me esp since they don't correspond to one another. Also if CRF is CPU only, then why do we need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if cq <= 51 {
p.VideoEncoder.Opts["cq"] = strconv.Itoa(int(cq))
} else {
glog.Warning("Cannot use CQ param, value out of range (0-51)")
}
}
switch p.Profile.Profile {
case ProfileH264Baseline, ProfileH264ConstrainedHigh:
Expand Down
29 changes: 18 additions & 11 deletions ffmpeg/videoprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,18 @@ var FfmpegNameToVideoCodec = map[string]VideoCodec{
"vp9": VP9,
}

//Standard Profiles:
//1080p60fps: 9000kbps
//1080p30fps: 6000kbps
//720p60fps: 6000kbps
//720p30fps: 4000kbps
//480p30fps: 2000kbps
//360p30fps: 1000kbps
//240p30fps: 700kbps
//144p30fps: 400kbps
// Standard Profiles:
// 1080p60fps: 9000kbps
// 1080p30fps: 6000kbps
// 720p60fps: 6000kbps
// 720p30fps: 4000kbps
// 480p30fps: 2000kbps
// 360p30fps: 1000kbps
// 240p30fps: 700kbps
// 144p30fps: 400kbps
type VideoProfile struct {
Name string
Name string
// Bitrate is used to set min, avg, and max bitrate
Bitrate string
Framerate uint
FramerateDen uint
Expand All @@ -97,9 +98,13 @@ type VideoProfile struct {
Encoder VideoCodec
ColorDepth ColorDepthBits
ChromaFormat ChromaSubsampling
// CRF is used to set CRF and CQ
// If set, then constant rate factor is used instead of constant bitrate
// If both CRF and Bitrate are set, then Bitrate is used only as max bitrate
CRF uint
}

//Some sample video profiles
// Some sample video profiles
var (
P720p60fps16x9 = VideoProfile{Name: "P720p60fps16x9", Bitrate: "6000k", Framerate: 60, AspectRatio: "16:9", Resolution: "1280x720"}
P720p30fps16x9 = VideoProfile{Name: "P720p30fps16x9", Bitrate: "4000k", Framerate: 30, AspectRatio: "16:9", Resolution: "1280x720"}
Expand Down Expand Up @@ -230,6 +235,7 @@ type JsonProfile struct {
Encoder string `json:"encoder"`
ColorDepth ColorDepthBits `json:"colorDepth"`
ChromaFormat ChromaSubsampling `json:"chromaFormat"`
CRF uint `json:"crf"`
}

func ParseProfilesFromJsonProfileArray(profiles []JsonProfile) ([]VideoProfile, error) {
Expand Down Expand Up @@ -275,6 +281,7 @@ func ParseProfilesFromJsonProfileArray(profiles []JsonProfile) ([]VideoProfile,
ColorDepth: profile.ColorDepth,
// profile.ChromaFormat of 0 is default ChromaSubsampling420
ChromaFormat: profile.ChromaFormat,
CRF: profile.CRF,
}
parsedProfiles = append(parsedProfiles, prof)
}
Expand Down
Loading