208 lines
6.0 KiB
Go
208 lines
6.0 KiB
Go
//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
|
|
}
|
|
}
|
|
}
|