mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-31 10:28:43 -04:00
audio-cpp was the only C++ backend without the model-identity guard, and no later task in the plan added it. pkg/grpc/server.go enforces checkModelIdentity on exactly these two RPCs, for the reason #10952 records: in distributed mode a worker can recycle a stopped backend's gRPC port for another model's backend, and the controller's liveness-only probe cannot tell a stale cached route from a live one. Without this guard a stale route gets a different model's VAD or diarization answer back with a 200. The loaded identity lives on LoadedModel rather than in a separate global, which is where this differs from llama-cpp. A handler holding the model through snapshot() then necessarily judges against the identity that model was loaded with, and a concurrent reload cannot swap one without the other. The refusal is NOT_FOUND carrying the verbatim grpcerrors.ModelMismatchSentinel substring. session_for and run_offline now take a const LaneEntry & proof-of-holding parameter. The rule that both must run under the inference lane was prose, which is exactly how the plan came to specify the inverted order; it is now a compile error. Restoring the inverted order fails to build rather than racing on an unsynchronised session map with a mutating prepare(). Diarize's speaker-hint comment claimed the dropped hints were "not a silent failure". From the caller's side that is what they are, and backend.proto documents num_speakers as forcing, so the comment now says plainly that the forwarding is dead for sortformer and that the family which lands must either honour num_speakers or refuse it. read_audio_file inspects the error_code from exists(), so an unsearchable parent directory no longer reports as a missing file. The VAD handler records the stimulus that actually works, since silero correctly ignores synthetic tones and the next task would otherwise rediscover that. Assisted-by: Claude:claude-opus-5 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
83 lines
3.1 KiB
C++
83 lines
3.1 KiB
C++
#include "audio_io.h"
|
|
|
|
#include "loaded_model.h"
|
|
|
|
#include "engine/framework/audio/wav_reader.h"
|
|
#include "engine/framework/audio/wav_writer.h"
|
|
|
|
#include <filesystem>
|
|
#include <utility>
|
|
|
|
namespace audiocpp_backend {
|
|
|
|
engine::runtime::AudioBuffer read_audio_file(const std::string &path) {
|
|
if (path.empty()) {
|
|
throw ConfigError("audio-cpp: no input audio path was supplied");
|
|
}
|
|
std::error_code ec;
|
|
const bool present = std::filesystem::exists(std::filesystem::path(path), ec);
|
|
if (ec) {
|
|
// exists() returning false with ec set does NOT mean the file is
|
|
// absent, it means the question could not be answered: most often a
|
|
// parent directory is not searchable. Reporting that as "does not
|
|
// exist" sends the operator after the file when the fault is the
|
|
// permissions on the directory above it.
|
|
throw ConfigError("audio-cpp: cannot stat input audio " + path + ": " +
|
|
ec.message());
|
|
}
|
|
if (!present) {
|
|
throw ConfigError("audio-cpp: input audio does not exist: " + path);
|
|
}
|
|
engine::runtime::AudioBuffer buffer;
|
|
try {
|
|
const engine::audio::WavData wav =
|
|
engine::audio::read_wav_f32(std::filesystem::path(path));
|
|
buffer.sample_rate = wav.sample_rate;
|
|
// AudioBuffer's own default is 1, and a reader that reports 0 channels
|
|
// still gave us an interleaving of one.
|
|
buffer.channels = wav.channels > 0 ? wav.channels : 1;
|
|
buffer.samples = wav.samples;
|
|
} catch (const std::exception &err) {
|
|
throw ConfigError("audio-cpp: cannot read " + path +
|
|
" as WAV: " + err.what());
|
|
}
|
|
if (buffer.sample_rate <= 0) {
|
|
throw ConfigError("audio-cpp: " + path +
|
|
" declares a non-positive sample rate; every "
|
|
"timestamp derived from it would be zero");
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
void write_audio_file(const std::string &path,
|
|
const engine::runtime::AudioBuffer &audio) {
|
|
if (path.empty()) {
|
|
throw ConfigError("audio-cpp: no output path was supplied");
|
|
}
|
|
const std::filesystem::path destination(path);
|
|
if (destination.has_parent_path()) {
|
|
// Best effort: a failure here shows up as a write failure below, with a
|
|
// message naming the file the caller actually asked for.
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(destination.parent_path(), ec);
|
|
}
|
|
try {
|
|
engine::audio::write_pcm16_wav(destination, audio.sample_rate,
|
|
audio.channels > 0 ? audio.channels : 1,
|
|
audio.samples);
|
|
} catch (const std::exception &err) {
|
|
throw ConfigError("audio-cpp: cannot write " + path + ": " + err.what());
|
|
}
|
|
}
|
|
|
|
engine::runtime::AudioBuffer buffer_from_mono(std::vector<float> samples,
|
|
int sample_rate) {
|
|
engine::runtime::AudioBuffer buffer;
|
|
buffer.sample_rate = sample_rate;
|
|
buffer.channels = 1;
|
|
buffer.samples = std::move(samples);
|
|
return buffer;
|
|
}
|
|
|
|
} // namespace audiocpp_backend
|