NVENC support
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
//go:build windows
|
||||
|
||||
package capture
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"time"
|
||||
)
|
||||
|
||||
type H264SmokeResolution struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
type H264SmokeOptions struct {
|
||||
Frames int `json:"frames"`
|
||||
Resolutions []H264SmokeResolution `json:"resolutions"`
|
||||
FPS []int `json:"fps"`
|
||||
Providers []string `json:"providers"`
|
||||
}
|
||||
|
||||
type H264SmokeResult struct {
|
||||
Provider string `json:"provider"`
|
||||
Input string `json:"input"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
RequestedFPS int `json:"requested_fps"`
|
||||
ConfiguredFPS int `json:"configured_fps"`
|
||||
ConfigureOK bool `json:"configure_ok"`
|
||||
EncodeOK bool `json:"encode_ok"`
|
||||
ConfigureMS float64 `json:"configure_ms"`
|
||||
FramesTried int `json:"frames_tried"`
|
||||
FramesWithData int `json:"frames_with_data"`
|
||||
FirstOutputAt int `json:"first_output_at"`
|
||||
FirstOutputMS float64 `json:"first_output_ms"`
|
||||
AvgEncodeMS float64 `json:"avg_encode_ms"`
|
||||
MaxEncodeMS float64 `json:"max_encode_ms"`
|
||||
AvgBytes float64 `json:"avg_bytes"`
|
||||
TotalBytes int `json:"total_bytes"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func DefaultH264SmokeOptions() H264SmokeOptions {
|
||||
return H264SmokeOptions{
|
||||
Frames: 30,
|
||||
Resolutions: []H264SmokeResolution{
|
||||
{Width: 1280, Height: 720},
|
||||
{Width: 1920, Height: 1080},
|
||||
{Width: 2560, Height: 1440},
|
||||
{Width: 3840, Height: 2160},
|
||||
},
|
||||
FPS: []int{30, 60, 120},
|
||||
Providers: []string{"hardware", "software"},
|
||||
}
|
||||
}
|
||||
|
||||
func RunH264Smoke(opts H264SmokeOptions) []H264SmokeResult {
|
||||
opts = normalizeH264SmokeOptions(opts)
|
||||
out := make([]H264SmokeResult, 0, len(opts.Resolutions)*len(opts.FPS)*len(opts.Providers)*4)
|
||||
for _, res := range opts.Resolutions {
|
||||
if res.Width <= 0 || res.Height <= 0 || res.Width%2 != 0 || res.Height%2 != 0 {
|
||||
out = append(out, H264SmokeResult{Width: res.Width, Height: res.Height, Error: "resolution must be positive and even"})
|
||||
continue
|
||||
}
|
||||
for _, fps := range opts.FPS {
|
||||
for _, provider := range opts.Providers {
|
||||
for _, candidate := range h264SmokeCandidates(provider, fps) {
|
||||
out = append(out, runH264SmokeCase(provider, res.Width, res.Height, fps, candidate, opts.Frames))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeH264SmokeOptions(opts H264SmokeOptions) H264SmokeOptions {
|
||||
def := DefaultH264SmokeOptions()
|
||||
if opts.Frames <= 0 {
|
||||
opts.Frames = def.Frames
|
||||
}
|
||||
if len(opts.Resolutions) == 0 {
|
||||
opts.Resolutions = def.Resolutions
|
||||
}
|
||||
if len(opts.FPS) == 0 {
|
||||
opts.FPS = def.FPS
|
||||
}
|
||||
if len(opts.Providers) == 0 {
|
||||
opts.Providers = def.Providers
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func h264SmokeCandidates(provider string, requestedFPS int) []mfH264Candidate {
|
||||
if provider == "hardware" {
|
||||
return hardwareH264Candidates(requestedFPS)
|
||||
}
|
||||
return []mfH264Candidate{
|
||||
{fps: requestedFPS, inputSubtype: MFVideoFormat_NV12, inputFormat: "NV12"},
|
||||
{fps: requestedFPS, inputSubtype: MFVideoFormat_I420, inputFormat: "I420"},
|
||||
}
|
||||
}
|
||||
|
||||
func runH264SmokeCase(provider string, width, height, requestedFPS int, candidate mfH264Candidate, frames int) H264SmokeResult {
|
||||
result := H264SmokeResult{
|
||||
Provider: provider,
|
||||
Input: candidate.inputFormat,
|
||||
Width: width,
|
||||
Height: height,
|
||||
RequestedFPS: requestedFPS,
|
||||
ConfiguredFPS: candidate.fps,
|
||||
FirstOutputAt: -1,
|
||||
}
|
||||
if err := ensureMFStartup(); err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
|
||||
transform, hardware, providerName, err := createH264SmokeTransform(provider)
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
result.Provider = providerName
|
||||
enc := newMFH264EncoderFromTransform(transform, width, height, requestedFPS, candidate.fps, hardware, providerName, candidate.inputSubtype, candidate.inputFormat)
|
||||
|
||||
t0 := time.Now()
|
||||
if err := enc.configure(); err != nil {
|
||||
result.ConfigureMS = float64(time.Since(t0).Microseconds()) / 1000
|
||||
result.Error = err.Error()
|
||||
enc.Close()
|
||||
return result
|
||||
}
|
||||
result.ConfigureOK = true
|
||||
result.ConfigureMS = float64(time.Since(t0).Microseconds()) / 1000
|
||||
defer enc.Close()
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
totalEncode := time.Duration(0)
|
||||
maxEncode := time.Duration(0)
|
||||
firstStart := time.Now()
|
||||
for frame := 0; frame < frames; frame++ {
|
||||
fillH264SmokeFrame(img, frame)
|
||||
encStart := time.Now()
|
||||
data, err := enc.Encode(img)
|
||||
encDur := time.Since(encStart)
|
||||
result.FramesTried++
|
||||
totalEncode += encDur
|
||||
if encDur > maxEncode {
|
||||
maxEncode = encDur
|
||||
}
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
if len(data) > 0 {
|
||||
result.FramesWithData++
|
||||
result.TotalBytes += len(data)
|
||||
if result.FirstOutputAt < 0 {
|
||||
result.FirstOutputAt = frame + 1
|
||||
result.FirstOutputMS = float64(time.Since(firstStart).Microseconds()) / 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
if result.FramesTried > 0 {
|
||||
result.AvgEncodeMS = float64(totalEncode.Microseconds()) / 1000 / float64(result.FramesTried)
|
||||
result.MaxEncodeMS = float64(maxEncode.Microseconds()) / 1000
|
||||
}
|
||||
if result.FramesWithData > 0 {
|
||||
result.EncodeOK = true
|
||||
result.AvgBytes = float64(result.TotalBytes) / float64(result.FramesWithData)
|
||||
}
|
||||
if !result.EncodeOK {
|
||||
result.Error = "configured but produced no output"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func createH264SmokeTransform(provider string) (*mfTransform, bool, string, error) {
|
||||
switch provider {
|
||||
case "hardware":
|
||||
transform, err := activateHardwareH264MFT()
|
||||
if err != nil {
|
||||
return nil, false, "hardware", err
|
||||
}
|
||||
return transform, true, "hardware", nil
|
||||
case "software":
|
||||
return createSoftwareH264Transform()
|
||||
default:
|
||||
return nil, false, provider, fmt.Errorf("unknown provider %q", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func fillH264SmokeFrame(img *image.RGBA, frame int) {
|
||||
width := img.Bounds().Dx()
|
||||
height := img.Bounds().Dy()
|
||||
for y := 0; y < height; y++ {
|
||||
row := img.Pix[y*img.Stride:]
|
||||
for x := 0; x < width; x++ {
|
||||
i := x * 4
|
||||
row[i+0] = byte((x + frame*7) & 0xff)
|
||||
row[i+1] = byte((y + frame*5) & 0xff)
|
||||
row[i+2] = byte((x/2 + y/3 + frame*11) & 0xff)
|
||||
row[i+3] = 255
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user