Add managed codec DSP and initial control server
This commit is contained in:
@@ -115,3 +115,99 @@ On Unix new files use owner read/write permissions; Windows inherits directory A
|
||||
The credential directory must have one provisioning owner. PEM strings and crypto
|
||||
library internal copies are managed memory; owned-array clearing does not promise
|
||||
erasure of every runtime/library copy.
|
||||
|
||||
## Codec and DSP
|
||||
|
||||
`VoiceCat.Codec` and `VoiceCat.Dsp` call the desktop `voicecat_media` native library
|
||||
through source-generated `LibraryImport`. It links pinned Opus 1.5.2 and the existing
|
||||
vendored RNNoise; it has no dependency on libvoicecat or its C ABI. Fixed C signatures
|
||||
wrap Opus controls so P/Invoke never calls C varargs. SafeHandle owns every native
|
||||
encoder, decoder, DRED parser/state, and denoiser, including failed initialization.
|
||||
|
||||
`OpusOptions` is an immutable record. Supported PCM rates are 8/12/16/24/48 kHz,
|
||||
one or two interleaved channels, and integral 10/20/40/60 ms frames. These match the
|
||||
current VoiceCat protocol's integer frame duration; fractional Opus frame durations
|
||||
are not exposed. Low-delay application mode requires at most 20 ms. Channel capture
|
||||
bandwidth is controlled separately by `MaximumBandwidthHz`; the production audio
|
||||
clock will remain 48 kHz. Options are validated before native creation, and native
|
||||
control failures throw `OpusException` with the libopus error code.
|
||||
|
||||
`OpusEncoder.Encode(ReadOnlySpan<short>, Span<byte>)` accepts exactly one frame
|
||||
and returns encoded bytes. `OpusDecoder.Decode(packet, pcm, samplesPerChannel,
|
||||
recoverPreviousFrame)` returns samples **per channel**, not total interleaved samples.
|
||||
An empty packet requests PLC. Passing the next packet with `recoverPreviousFrame`
|
||||
requests in-band FEC; absence of FEC permits libopus's PLC fallback. Decode that next
|
||||
packet normally afterward. Capacity/overlap errors throw before native processing.
|
||||
|
||||
DRED is explicit. Unsupported native builds reject `DeepRedundancy = true` rather
|
||||
than silently disabling it. With pinned Opus 1.5.2, DRED encoding requires PCM at
|
||||
16/24/48 kHz; its activity analysis cannot emit DRED at 8/12 kHz. Such configurations
|
||||
are rejected. DRED packets can still be decoded at all five rates. The encoder uses
|
||||
a 30 ms minimum redundancy duration because this release needs two redundancy chunks;
|
||||
the old 20 ms setting produces no DRED packets. Actual redundancy remains adaptive
|
||||
to bitrate, loss estimate, and activity; it is not guaranteed in every packet.
|
||||
|
||||
`OpusDeepRedundancy.TryRecover(audioDecoder, nextPacket, pcm, samplesPerChannel,
|
||||
offset)` parses the next packet and reconstructs a missing frame. Default offset is
|
||||
one missing frame's samples per channel before the next packet's start, matching
|
||||
libopus's offset convention. A packet without DRED returns false; then the owner
|
||||
can try FEC/PLC. Only consume recovery output on success. Parse/native errors throw.
|
||||
|
||||
`RnnoiseProcessor.Process(Span<short>, sampleRate)` operates in place on complete
|
||||
480-sample mono chunks at 48 kHz. Other rates pass through unchanged; partial chunks
|
||||
at 48 kHz throw instead of leaving a tail silently untreated. Float scratch is
|
||||
preallocated, and rounding/clipping matches the C++ processor. Use distinct instances
|
||||
for stereo channels when the later pipeline supports stereo microphone denoising.
|
||||
Noise reduction does not gate speech.
|
||||
|
||||
`EnergyVadProcessor.Process(ReadOnlySpan<short>)` compares normalized RMS against
|
||||
`Threshold`, retains speech for `HangTime`, and starts closed. It uses monotonic
|
||||
`TimeProvider` timestamps; tests inject a clock. Threshold changes are atomic; all
|
||||
processing state otherwise has one owner. Codec/DSP processing methods allocate no
|
||||
managed memory after initialization, verified across 1,000 combined cycles. They run
|
||||
on a managed worker, never the native real-time device callback. Native device rings,
|
||||
jitter, mixer, and audio scheduling remain later work.
|
||||
|
||||
## Initial managed server
|
||||
|
||||
`VoiceServer(directory, endpoint, allowGuests, name)` owns credentials, the SQLite
|
||||
store, a TCP listener and its connection tasks. `EndPoint` reports the actual bound
|
||||
port (zero requests an ephemeral port). Dispose asynchronously to stop the listener
|
||||
and wait for all connections. The CLI currently binds loopback and accepts optional
|
||||
data-directory/port positional arguments.
|
||||
|
||||
All control traffic uses TLS 1.3 with the existing v2 protobuf. A single async loop
|
||||
owns each `TlsSession`; handlers exchange envelopes through bounded queues.
|
||||
This checkpoint caps connections at 64, queued input at 32 envelopes, queued output
|
||||
at 64 envelopes, and each control payload at 64 KiB (stricter than the shared framer's
|
||||
16 MiB limit). Queue exhaustion disconnects slow consumers. Handshake timeout is
|
||||
15 seconds; completed TLS connections have a 60-second receive-idle timeout.
|
||||
|
||||
Authentication starts users in unprotected Lobby (id 1), subject to its capacity.
|
||||
Success returns permissions, then a cloned snapshot; peers receive joined/updated/left
|
||||
events. Server-authoritative text replaces supplied sender ids/timestamps, limits
|
||||
bodies to 4096 UTF-8 bytes, and acknowledges valid or rejected routing. Channel text
|
||||
requires membership; private text echoes to sender and recipient. Protected channel
|
||||
joins and all admin/moderation handlers are pending. No UDP port or media features
|
||||
are advertised; voice subscription explicitly fails until the SFU is implemented.
|
||||
|
||||
`AccountStore(path)` retains the C++ schema version 2, accepts version 1 migration,
|
||||
and rejects unknown revisions. Opening an existing channel table does not reseed it.
|
||||
Account creation/authentication uses parameterized SQL; two password workers bound
|
||||
per-store Argon2 work. Failed authentication leaves `last_login` unchanged. Dispose
|
||||
after its operations finish. Account provisioning currently uses this API or the
|
||||
existing native administration path; there is no automatic bootstrap account.
|
||||
|
||||
`PasswordHasher` uses strict UTF-8 without normalization and libsodium-compatible
|
||||
Argon2id v19 PHC strings: 16-byte salt, 32-byte output, new-hash parameters
|
||||
64 MiB memory, two iterations, parallelism one. Verification supports up to 128 MiB,
|
||||
ten iterations, parallelism four and 1024 UTF-8 password bytes; malformed or excessive
|
||||
hashes fail closed. Standard C++ interactive-cost accounts are preserved. These
|
||||
bounds intentionally reject imported hashes above those costs. Native fixtures cover
|
||||
ASCII, Unicode and embedded NUL; the database oracle verifies cross-implementation
|
||||
authentication in both directions.
|
||||
|
||||
SQLite's MIT provider/bundle uses the pinned public-domain SourceGear SQLite build.
|
||||
The license audit checks that exact package version and repository identity because
|
||||
the native package lacks a NuGet license expression; other dependencies still require
|
||||
an approved permissive expression.
|
||||
|
||||
Reference in New Issue
Block a user