//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 } } }