Skip to content

Commit e1c0646

Browse files
committed
fix(realtime): resolve platform opus codec backend by alias (#9813)
The realtime WebRTC transport hardcoded loading the literal "opus" backend. On darwin/arm64 the only installable opus codec is "metal-opus" (it shares the gallery alias "opus"). Backend names resolve through the model loader's external-backends map, and the "opus" alias key is only registered when a user-path variant's alias was collected; a system-path metal-opus registers only under its concrete name. As a result, with metal-opus installed the realtime path still failed with "opus backend not available". Resolve the opus codec from the set of currently loadable backends instead of the hardcoded literal: an exact "opus" match wins (covers the plain backend and the alias key), otherwise fall back to a platform-appropriate "*opus*" codec, preferring the metal build on darwin/arm64. Behavior is unchanged when a plain "opus" backend is present, and the same error is surfaced when no opus codec is installed at all. The selection logic is extracted into resolveOpusBackend and unit-tested. Assisted-by: claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 51f4f67 commit e1c0646

2 files changed

Lines changed: 95 additions & 5 deletions

File tree

core/http/endpoints/openai/realtime_webrtc.go

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package openai
22

33
import (
44
"net/http"
5+
"runtime"
6+
"strings"
57
"time"
68

79
"github.com/labstack/echo/v4"
@@ -11,6 +13,52 @@ import (
1113
"github.com/pion/webrtc/v4"
1214
)
1315

16+
// opusBackendName is the canonical gallery name/alias of the opus audio codec
17+
// backend that the realtime WebRTC transport needs.
18+
const opusBackendName = "opus"
19+
20+
// resolveOpusBackend picks which installed opus-codec backend the realtime
21+
// WebRTC transport should load. The transport historically hardcoded the
22+
// literal "opus" backend name, but on darwin/arm64 the only installable opus
23+
// codec is "metal-opus" (it shares the gallery alias "opus"). When that
24+
// platform-specific variant is registered under its concrete directory name
25+
// rather than the "opus" alias key, loading the literal "opus" fails with
26+
// "opus backend not available" (issue #9813). Given the set of currently
27+
// loadable backend names, this returns the best opus codec to load for the
28+
// running platform, falling back to the literal name so the caller surfaces
29+
// the same error as before when no opus codec is installed at all.
30+
func resolveOpusBackend(installed []string, goos, goarch string) string {
31+
// An exact match wins: this covers the plain "opus" backend as well as the
32+
// "opus" alias key registered by gallery alias resolution for a
33+
// user-installed platform variant.
34+
for _, b := range installed {
35+
if b == opusBackendName {
36+
return opusBackendName
37+
}
38+
}
39+
40+
// No "opus" key is registered (e.g. a system-path metal-opus whose alias
41+
// was never collected). Fall back to a platform-appropriate "*opus*" codec
42+
// backend; on darwin/arm64 prefer the metal build.
43+
var fallback string
44+
for _, b := range installed {
45+
if !strings.Contains(strings.ToLower(b), opusBackendName) {
46+
continue
47+
}
48+
if goos == "darwin" && goarch == "arm64" && strings.Contains(strings.ToLower(b), "metal") {
49+
return b
50+
}
51+
if fallback == "" {
52+
fallback = b
53+
}
54+
}
55+
if fallback != "" {
56+
return fallback
57+
}
58+
59+
return opusBackendName
60+
}
61+
1462
// RealtimeCallRequest is the JSON body for POST /v1/realtime/calls.
1563
type RealtimeCallRequest struct {
1664
SDP string `json:"sdp"`
@@ -94,15 +142,25 @@ func RealtimeCalls(application *application.Application) echo.HandlerFunc {
94142
}
95143
}()
96144

97-
// Load the Opus backend
98-
opusBackend, err := application.ModelLoader().Load(
99-
model.WithBackendString("opus"),
145+
// Load the Opus backend. The opus codec ships under different backend
146+
// names per platform (e.g. "metal-opus" on darwin/arm64), so resolve the
147+
// platform-appropriate variant from the installed backends instead of
148+
// hardcoding the literal "opus" name (issue #9813).
149+
ml := application.ModelLoader()
150+
installed := make([]string, 0)
151+
for name := range ml.GetAllExternalBackends(nil) {
152+
installed = append(installed, name)
153+
}
154+
opusName := resolveOpusBackend(installed, runtime.GOOS, runtime.GOARCH)
155+
156+
opusBackend, err := ml.Load(
157+
model.WithBackendString(opusName),
100158
model.WithModelID("__opus_codec__"),
101-
model.WithModel("opus"),
159+
model.WithModel(opusName),
102160
)
103161
if err != nil {
104162
pc.Close()
105-
xlog.Error("failed to load opus backend", "error", err)
163+
xlog.Error("failed to load opus backend", "error", err, "backend", opusName)
106164
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "opus backend not available"})
107165
}
108166

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package openai
2+
3+
import (
4+
. "github.com/onsi/ginkgo/v2"
5+
. "github.com/onsi/gomega"
6+
)
7+
8+
var _ = Describe("resolveOpusBackend", func() {
9+
It("prefers the exact opus backend when it is installed", func() {
10+
Expect(resolveOpusBackend([]string{"opus", "metal-opus"}, "linux", "amd64")).To(Equal("opus"))
11+
})
12+
13+
It("resolves to the opus alias key on linux", func() {
14+
Expect(resolveOpusBackend([]string{"opus"}, "linux", "amd64")).To(Equal("opus"))
15+
})
16+
17+
It("selects metal-opus on darwin/arm64 when no plain opus is installed", func() {
18+
Expect(resolveOpusBackend([]string{"metal-opus"}, "darwin", "arm64")).To(Equal("metal-opus"))
19+
})
20+
21+
It("selects metal-opus on darwin/arm64 even when other backends are present", func() {
22+
Expect(resolveOpusBackend([]string{"silero-vad", "metal-opus", "whisper"}, "darwin", "arm64")).To(Equal("metal-opus"))
23+
})
24+
25+
It("falls back to any opus codec backend when there is no exact match (non-darwin)", func() {
26+
Expect(resolveOpusBackend([]string{"metal-opus"}, "linux", "amd64")).To(Equal("metal-opus"))
27+
})
28+
29+
It("returns the literal opus name when no opus codec is installed", func() {
30+
Expect(resolveOpusBackend([]string{"silero-vad", "whisper"}, "darwin", "arm64")).To(Equal("opus"))
31+
})
32+
})

0 commit comments

Comments
 (0)