commit 1f7e8a70d558e0b31085092b28648558188e9b21
parent 0c9c77611e8067c20f4e99b7fc374434a54904c0
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Wed, 16 Sep 2026 21:29:38 -0400
audio: use os.CreateTemp for the ffmpeg decode scratch file
The scratch file was named from a process-local counter, so two
processes decoding at once both wrote nativeaudio-1 and could corrupt
each other's input. os.CreateTemp guarantees a unique name and lets the
unused counter type go.
Diffstat:
1 file changed, 13 insertions(+), 28 deletions(-)
diff --git a/audio_ffmpeg.go b/audio_ffmpeg.go
@@ -12,10 +12,8 @@ import (
"fmt"
"os"
"os/exec"
- "path/filepath"
"strconv"
"strings"
- "sync"
)
// FFmpegPlay an audio file with ffplay.
@@ -65,26 +63,6 @@ func FFmpegLoad(path string) ([]byte, Format, error) {
return buffer.Bytes(), f, nil
}
-var count = counter{}
-
-type counter struct {
- count int
- m sync.Mutex
-}
-
-func (c *counter) Next() int {
- c.m.Lock()
- defer c.m.Unlock()
- c.count++
- return c.count
-}
-
-func (c *counter) Done() {
- c.m.Lock()
- defer c.m.Unlock()
- c.count--
-}
-
// FFmpegDecode raw PCM with ffmpeg.
//
// ffmpeg -f m4a -i pipe: -f s16le -
@@ -95,14 +73,21 @@ func (c *counter) Done() {
// NOTE(jfm): unfortunately, some formats cannot be piped, so we will
// create a temporary file instead.
func FFmpegDecode(by []byte) ([]byte, Format, error) {
- // id ensures that multiple concurrent tmp files do not collide.
- id := count.Next()
- tmp := filepath.Join(os.TempDir(), fmt.Sprintf("nativeaudio-%d", id))
- if err := os.WriteFile(tmp, by, 0644); err != nil {
+ // CreateTemp picks a unique name, so concurrent decodes in this or any
+ // other process cannot collide.
+ tmp, err := os.CreateTemp("", "nativeaudio-*")
+ if err != nil {
return nil, Format{}, fmt.Errorf("creating tmp file: %w", err)
}
- defer os.Remove(tmp)
- return FFmpegLoad(tmp)
+ defer os.Remove(tmp.Name())
+ if _, err := tmp.Write(by); err != nil {
+ tmp.Close()
+ return nil, Format{}, fmt.Errorf("writing tmp file: %w", err)
+ }
+ if err := tmp.Close(); err != nil {
+ return nil, Format{}, fmt.Errorf("closing tmp file: %w", err)
+ }
+ return FFmpegLoad(tmp.Name())
}
// probe queries the format information for a given audio file by parsing