formats_test.go (3916B)
1 package test 2 3 import ( 4 "os" 5 "os/exec" 6 "path/filepath" 7 "testing" 8 9 "git.sr.ht/~jackmordaunt/nativeaudio" 10 ) 11 12 // formatProbe is one row of the support matrix: a way to produce a file 13 // with ffmpeg, and whether the package promises to decode it. 14 type formatProbe struct { 15 // name labels the row in the matrix. 16 name string 17 // file is the name to write, whose extension picks the container. 18 file string 19 // args are the ffmpeg codec arguments. 20 args []string 21 // guaranteed marks formats the readme claims support for. Those are 22 // assertions; everything else is reported and left to the matrix. 23 guaranteed bool 24 } 25 26 var formatProbes = []formatProbe{ 27 {"AAC-LC in MP4", "out.m4a", []string{"-c:a", "aac", "-b:a", "128k"}, true}, 28 {"AAC-LC in ADTS", "out.aac", []string{"-c:a", "aac", "-b:a", "128k", "-f", "adts"}, true}, 29 {"MP3", "out.mp3", []string{"-c:a", "libmp3lame"}, true}, 30 {"WAV 16-bit", "out.wav", []string{"-c:a", "pcm_s16le"}, true}, 31 {"WAV 24-bit", "out24.wav", []string{"-c:a", "pcm_s24le"}, true}, 32 {"FLAC", "out.flac", []string{"-c:a", "flac"}, false}, 33 {"ALAC in MP4", "alac.m4a", []string{"-c:a", "alac"}, false}, 34 {"Opus in Ogg", "out.opus", []string{"-c:a", "libopus"}, false}, 35 {"Vorbis in Ogg", "out.ogg", []string{"-c:a", "libvorbis"}, false}, 36 {"WMA v2", "out.wma", []string{"-c:a", "wmav2"}, false}, 37 } 38 39 // generate writes one second of tone in the probe's format and returns 40 // its path, or skips when ffmpeg cannot produce it. 41 func (p formatProbe) generate(t *testing.T, dir string) string { 42 t.Helper() 43 path := filepath.Join(dir, p.file) 44 args := []string{ 45 "-hide_banner", "-loglevel", "error", "-y", 46 "-f", "lavfi", "-i", "sine=frequency=440:sample_rate=44100", 47 "-t", "1", "-ac", "2", 48 } 49 args = append(args, p.args...) 50 args = append(args, path) 51 if out, err := exec.Command("ffmpeg", args...).CombinedOutput(); err != nil { 52 t.Skipf("ffmpeg cannot produce %s: %v: %s", p.name, err, out) 53 } 54 return path 55 } 56 57 // TestFormatSupport decodes one file per format and reports what the 58 // platform's decoder made of it. 59 // 60 // The published support matrix comes from running this on each platform, 61 // which is the only way to keep it honest: the operating system decoders 62 // handle far more than AAC, and which extras they handle differs. Rows 63 // marked guaranteed are assertions; the rest are reported so the matrix 64 // can be filled in from a CI run rather than from vendor documentation. 65 func TestFormatSupport(t *testing.T) { 66 requireTools(t, "ffmpeg") 67 dir := t.TempDir() 68 69 for _, probe := range formatProbes { 70 t.Run(probe.name, func(t *testing.T) { 71 path := probe.generate(t, dir) 72 73 in, err := os.ReadFile(path) 74 if err != nil { 75 t.Fatalf("reading generated file: %v", err) 76 } 77 78 d := newDecoder(t) 79 pcm, format, err := d.Decode(in) 80 if err != nil { 81 if probe.guaranteed { 82 t.Errorf("MATRIX %-16s UNSUPPORTED (but claimed): %v", probe.name, err) 83 } else { 84 t.Logf("MATRIX %-16s unsupported: %v", probe.name, err) 85 } 86 return 87 } 88 89 t.Logf("MATRIX %-16s native supported: %d bytes, %+v", probe.name, len(pcm), format) 90 91 // The ffmpeg fallback is the backend on platforms with no 92 // native decoder, so probe it wherever ffmpeg exists rather 93 // than only where it is in use. 94 if fpcm, fformat, ferr := nativeaudio.FFmpegDecode(in); ferr != nil { 95 t.Logf("MATRIX %-16s ffmpeg unsupported: %v", probe.name, ferr) 96 } else { 97 t.Logf("MATRIX %-16s ffmpeg supported: %d bytes, %+v", probe.name, len(fpcm), fformat) 98 } 99 100 // Whatever the input, the output contract is the same. 101 if format.BytesPerSample != 2 { 102 t.Errorf("%s decoded to %d bytes per sample, want 2", probe.name, format.BytesPerSample) 103 } 104 if format.SampleRate < 1 || format.Channels < 1 { 105 t.Errorf("%s reported an implausible format: %+v", probe.name, format) 106 } 107 if len(pcm) == 0 { 108 t.Errorf("%s decoded to no audio", probe.name) 109 } 110 }) 111 } 112 }