hwaccel_test.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package transcoder
  2. import (
  3. "regexp"
  4. "strings"
  5. "testing"
  6. )
  7. // TestNV12ScaleFilter verifies the shared scale/format filter chain used by
  8. // the hardware encoders that accept plain system-memory frames.
  9. func TestNV12ScaleFilter(t *testing.T) {
  10. cases := []struct {
  11. name string
  12. height string
  13. want string
  14. }{
  15. {"no resize", "", "format=nv12"},
  16. {"resize to 360", "360", "scale=-1:360,format=nv12"},
  17. {"resize to 720", "720", "scale=-1:720,format=nv12"},
  18. }
  19. for _, tc := range cases {
  20. t.Run(tc.name, func(t *testing.T) {
  21. got := nv12ScaleFilter(tc.height)
  22. if got != tc.want {
  23. t.Errorf("nv12ScaleFilter(%q) = %q, want %q", tc.height, got, tc.want)
  24. }
  25. })
  26. }
  27. }
  28. // TestCandidateHWProfiles verifies that every profile the current platform
  29. // offers is well-formed: it has a name, a codec, and a scale filter that
  30. // does not panic and always returns a non-empty filter chain.
  31. func TestCandidateHWProfiles(t *testing.T) {
  32. for _, profile := range candidateHWProfiles() {
  33. if profile.Name == "" {
  34. t.Errorf("profile has empty Name: %+v", profile)
  35. }
  36. if profile.Codec == "" {
  37. t.Errorf("profile %q has empty Codec", profile.Name)
  38. }
  39. if profile.ScaleFilter == nil {
  40. t.Fatalf("profile %q has nil ScaleFilter", profile.Name)
  41. }
  42. for _, height := range []string{"", "360", "720", "1080"} {
  43. if vf := profile.ScaleFilter(height); vf == "" {
  44. t.Errorf("profile %q ScaleFilter(%q) returned empty string", profile.Name, height)
  45. }
  46. }
  47. if profile.ProbeSize != "" && !regexp.MustCompile(`^\d+x\d+$`).MatchString(profile.ProbeSize) {
  48. t.Errorf("profile %q has malformed ProbeSize %q, want WxH", profile.Name, profile.ProbeSize)
  49. }
  50. }
  51. }
  52. // TestProbeArgs verifies the probe command line: profile args are placed in the
  53. // order ffmpeg expects, and ProbeSize overrides the default test frame size
  54. // (VideoToolbox needs a larger frame than the 320x240 default to open at all).
  55. func TestProbeArgs(t *testing.T) {
  56. cases := []struct {
  57. name string
  58. profile *hwEncoderProfile
  59. wantSize string
  60. wantOrdered []string // args that must appear in this relative order
  61. wantAbsent []string
  62. }{
  63. {
  64. name: "default probe size and no pre-input",
  65. profile: &hwEncoderProfile{
  66. Name: "system memory encoder",
  67. Codec: "h264_fake",
  68. ScaleFilter: nv12ScaleFilter,
  69. EncodeArgs: []string{"-preset", "fast"},
  70. },
  71. wantSize: defaultProbeSize,
  72. wantOrdered: []string{"-i", "-vf", "format=nv12", "-vcodec", "h264_fake", "-preset", "fast", "-f", "null", "-"},
  73. },
  74. {
  75. name: "explicit probe size overrides the default",
  76. profile: &hwEncoderProfile{
  77. Name: "videotoolbox-like",
  78. Codec: "h264_videotoolbox",
  79. ScaleFilter: nv12ScaleFilter,
  80. EncodeArgs: []string{"-realtime", "1"},
  81. ProbeSize: "640x480",
  82. },
  83. wantSize: "640x480",
  84. wantOrdered: []string{"-i", "-vcodec", "h264_videotoolbox", "-realtime", "1"},
  85. wantAbsent: []string{"color=c=black:s=" + defaultProbeSize + ":d=0.1"},
  86. },
  87. {
  88. name: "pre-input args come before the input",
  89. profile: &hwEncoderProfile{
  90. Name: "vaapi-like",
  91. Codec: "h264_vaapi",
  92. PreInput: []string{"-vaapi_device", "/dev/dri/renderD128"},
  93. ScaleFilter: func(string) string { return "format=nv12,hwupload" },
  94. },
  95. wantSize: defaultProbeSize,
  96. wantOrdered: []string{"-vaapi_device", "/dev/dri/renderD128", "-f", "lavfi", "-i"},
  97. },
  98. {
  99. name: "empty scale filter emits no -vf",
  100. profile: &hwEncoderProfile{
  101. Name: "no filter",
  102. Codec: "h264_fake",
  103. ScaleFilter: func(string) string { return "" },
  104. },
  105. wantSize: defaultProbeSize,
  106. wantAbsent: []string{"-vf"},
  107. },
  108. }
  109. for _, tc := range cases {
  110. t.Run(tc.name, func(t *testing.T) {
  111. args := probeArgs(tc.profile)
  112. joined := strings.Join(args, " ")
  113. wantInput := "color=c=black:s=" + tc.wantSize + ":d=0.1"
  114. if !strings.Contains(joined, wantInput) {
  115. t.Errorf("probeArgs() = %v, want it to contain input %q", args, wantInput)
  116. }
  117. at := 0
  118. for _, want := range tc.wantOrdered {
  119. found := -1
  120. for i := at; i < len(args); i++ {
  121. if args[i] == want {
  122. found = i
  123. break
  124. }
  125. }
  126. if found == -1 {
  127. t.Fatalf("probeArgs() = %v, missing %q at or after index %d", args, want, at)
  128. }
  129. at = found + 1
  130. }
  131. for _, absent := range tc.wantAbsent {
  132. for _, arg := range args {
  133. if arg == absent {
  134. t.Errorf("probeArgs() = %v, did not expect %q", args, absent)
  135. }
  136. }
  137. }
  138. })
  139. }
  140. }
  141. // TestGetHWEncoderProfile_Cached verifies that repeated calls return the same
  142. // cached result (the underlying probe only runs once per process).
  143. func TestGetHWEncoderProfile_Cached(t *testing.T) {
  144. first := getHWEncoderProfile()
  145. second := getHWEncoderProfile()
  146. if first != second {
  147. t.Errorf("getHWEncoderProfile() returned different results across calls: %v vs %v", first, second)
  148. }
  149. }
  150. // TestNVENCProfilesForceIDR guards the HLS segment length: without -forced-idr
  151. // NVENC ignores -force_key_frames for segmenting purposes and cuts ~10s
  152. // segments at its default GOP. Platforms with no NVENC candidate pass trivially.
  153. func TestNVENCProfilesForceIDR(t *testing.T) {
  154. for _, profile := range candidateHWProfiles() {
  155. if profile.Codec != "h264_nvenc" {
  156. continue
  157. }
  158. i := indexOf(profile.EncodeArgs, "-forced-idr")
  159. if i == -1 || i+1 >= len(profile.EncodeArgs) || profile.EncodeArgs[i+1] != "1" {
  160. t.Errorf("%s EncodeArgs = %v, want -forced-idr 1", profile.Name, profile.EncodeArgs)
  161. }
  162. }
  163. }