add browser mic TX: Opus-encoded mic audio from web client to WDSP TX chain

- Browser: AudioWorklet accumulates 960-sample frames, AudioEncoder encodes
  to Opus, sends binary WS frame 'M' + raw Opus packet
- Server: opcode $02 handler decodes Opus via libopus, calls OnWebMic callback
- MainForm: WebOnMic converts PSingle→Double, pushes to PushTXMicSamplesD;
  ApplyMOX switches mic source to txmsWeb when web client active, restores on TX-off
- WDSPEngine: added txmsWeb to TTXMicSource enum

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-09 21:49:51 +03:00
co-authored by Claude Sonnet 4.6
parent 3d25f837af
commit 2ab90e8855
4 changed files with 117 additions and 2 deletions
+43
View File
@@ -103,6 +103,13 @@ type
TOpus_encoder_ctl_set = function(st: POpusEncoder;
request: Integer; value: Integer): Integer; cdecl;
POpusDecoder = Pointer;
TOpus_decoder_create = function(Fs, channels: Integer;
error: PInteger): POpusDecoder; cdecl;
TOpus_decode_float = function(st: POpusDecoder; data: PByte; len: Integer;
pcm: PSingle; frame_size, decode_fec: Integer): Integer; cdecl;
TOpus_decoder_destroy = procedure(st: POpusDecoder); cdecl;
// ── Callbacks в MainForm ──────────────────────────────────────────────────
TWebCmdFreq = procedure(Hz: Double) of object;
TWebCmdMode = procedure(Mode: Integer) of object;
@@ -129,6 +136,7 @@ type
TWebCmdFreqA = procedure(Hz: Double) of object;
TWebCmdAttn = procedure(Idx: Integer) of object;
TWebCmdTun = procedure(On_: Boolean) of object;
TWebMicCB = procedure(Samples: PSingle; Count: Integer) of object;
// ── Главный класс сервера ─────────────────────────────────────────────────
TWebServer = class
@@ -144,6 +152,12 @@ type
FOpusBufPos: Integer;
FOpusOut: array[0..3999] of Byte;
FOpusReady: Boolean;
// Opus decoder — для RX TX-mic аудио от браузера
FOpusDec: POpusDecoder;
FOpusDecCreate: TOpus_decoder_create;
FOpusDecDecode: TOpus_decode_float;
FOpusDecDestroy: TOpus_decoder_destroy;
FOnWebMic: TWebMicCB;
// ── TCP ──
FListenSock: TSocket;
@@ -292,6 +306,7 @@ type
property OnFreqA: TWebCmdFreqA read FOnFreqA write FOnFreqA;
property OnAttn: TWebCmdAttn read FOnAttn write FOnAttn;
property OnTun: TWebCmdTun read FOnTun write FOnTun;
property OnWebMic: TWebMicCB read FOnWebMic write FOnWebMic;
end;
implementation
@@ -423,6 +438,9 @@ begin
FOpusDestroy := TOpus_encoder_destroy(GetProcAddress(FOpusLib, 'opus_encoder_destroy'));
FOpusEncode := TOpus_encode_float( GetProcAddress(FOpusLib, 'opus_encode_float'));
FOpusCtl := TOpus_encoder_ctl_set(GetProcAddress(FOpusLib, 'opus_encoder_ctl'));
FOpusDecCreate := TOpus_decoder_create( GetProcAddress(FOpusLib, 'opus_decoder_create'));
FOpusDecDecode := TOpus_decode_float( GetProcAddress(FOpusLib, 'opus_decode_float'));
FOpusDecDestroy := TOpus_decoder_destroy(GetProcAddress(FOpusLib, 'opus_decoder_destroy'));
if not Assigned(FOpusCreate) or not Assigned(FOpusEncode) then
begin
@@ -439,6 +457,14 @@ begin
if Assigned(FOpusCtl) then
FOpusCtl(FOpusEnc, 4002, OPUS_BITRATE);
// Декодер для TX mic (браузер → Opus → WDSP)
if Assigned(FOpusDecCreate) then
begin
Err := 0;
FOpusDec := FOpusDecCreate(OPUS_SAMPLE_RATE, OPUS_CHANNELS, @Err);
if Err <> 0 then FOpusDec := nil;
end;
FOpusBufPos := 0;
FOpusReady := True;
Result := True;
@@ -449,6 +475,9 @@ begin
if FOpusReady and Assigned(FOpusDestroy) and (FOpusEnc <> nil) then
FOpusDestroy(FOpusEnc);
FOpusEnc := nil;
if Assigned(FOpusDec) and Assigned(FOpusDecDestroy) then
FOpusDecDestroy(FOpusDec);
FOpusDec := nil;
FOpusReady := False;
if FOpusLib <> 0 then
begin
@@ -664,6 +693,8 @@ var
Consumed: Integer;
Raw: array[0..8191] of Byte;
RawLen: Integer;
PcmBuf: array[0..5759] of Single; // 120ms max @ 48kHz (для RX TX-mic)
Decoded: Integer;
begin
WsHandled := False;
RawLen := 0;
@@ -813,6 +844,18 @@ begin
if PayLen > 0 then Move(Payload[0], Header[1], PayLen);
ProcessCommand(Client, Header);
end;
$02: // Binary — TX mic Opus frame: byte 'M' + raw Opus packet
begin
if (PayLen > 1) and (Payload[0] = Ord('M')) and
Assigned(FOpusDec) and Assigned(FOpusDecDecode) and
Assigned(FOnWebMic) then
begin
Decoded := FOpusDecDecode(FOpusDec, @Payload[1], PayLen - 1,
@PcmBuf[0], Length(PcmBuf), 0);
if Decoded > 0 then
FOnWebMic(@PcmBuf[0], Decoded);
end;
end;
$08: // Close
begin
Client.State := wsClosed;