mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 04:47:35 +00:00
feature(tx): слайс в FM RAW можно назначать источником передачи
TX-бейдж на флаге слайса был заблокирован и для DMR, и для FM RAW, хотя тракт FM RAW передавать умеет: SetTxSlice отбрасывает только DMR, а SetTXMode уводит FM RAW в WFM с плоской девиацией. RX-only остаётся один DMR (нет AMBE-кодера). - VfoOverlay.TxSelectionAllowed: запрет сузился до DMR. - RadioController.SetSliceMode: если режим меняют у слайса, который сейчас источник TX, передающий тракт перестраивается под новый режим. Без этого выбор FM RAW на уже выбранном TX-слайсе оставлял в эфире прежнюю модуляцию до следующего клика по бейджу. На переходах в/из FM RAW заодно пересматривается источник модуляции (внешний PCM) со сбросом кольца входа — как в главном SetMode. Заодно снята временная диагностика [MIC] (MicDbg и её вызовы) из mic-пула: зависание при выборе на слайсе входа из настроек больше не воспроизводится. Проверено на железе: TX со слайса в FM RAW работает. Подробное тестирование — следующим шагом. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+17
-37
@@ -2019,6 +2019,21 @@ begin
|
|||||||
if (Mode = MODE_DMR) and (FTxSliceId = Id) then SetTxSlice(0);
|
if (Mode = MODE_DMR) and (FTxSliceId = Id) then SetTxSlice(0);
|
||||||
if (OldMode = MODE_DMR) and (Mode <> MODE_DMR) then
|
if (OldMode = MODE_DMR) and (Mode <> MODE_DMR) then
|
||||||
FreeAndNil(FSlices[idx].DMR);
|
FreeAndNil(FSlices[idx].DMR);
|
||||||
|
// Этот слайс — источник передачи: передающий тракт идёт за его режимом
|
||||||
|
// (иначе после смены режима в эфир ушёл бы прежний вид модуляции). Вход FM
|
||||||
|
// RAW — внешний PCM, поэтому заодно пересматриваем источник модуляции.
|
||||||
|
if (FTxSliceId = Id) and (Mode <> OldMode) and
|
||||||
|
FWDSPReady and Assigned(FDSPEngine) then
|
||||||
|
begin
|
||||||
|
FDSPEngine.SetTXMode(ActiveTXMode);
|
||||||
|
if (Mode = MODE_FMRAW) or (OldMode = MODE_FMRAW) then
|
||||||
|
begin
|
||||||
|
if Mode = MODE_FMRAW then FWebMicActive := False;
|
||||||
|
FDSPEngine.SetTXMicSource(DefaultMicSource);
|
||||||
|
if Assigned(ActiveMicInput) and ActiveMicInput.IsOpen then
|
||||||
|
ActiveMicInput.Flush;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetSliceFilter(Id, Low, High: Integer);
|
procedure TRadioController.SetSliceFilter(Id, Low, High: Integer);
|
||||||
@@ -2197,14 +2212,6 @@ end;
|
|||||||
|
|
||||||
// --- Пул входных потоков -----------------------------------------------------
|
// --- Пул входных потоков -----------------------------------------------------
|
||||||
|
|
||||||
procedure MicDbg(const S: string);
|
|
||||||
// ВРЕМЕННО: диагностика mic-пула в stderr (поиск причины зависания/отказа
|
|
||||||
// Pa_OpenStream при выборе входа на слайсе). Снять после разбора.
|
|
||||||
begin
|
|
||||||
WriteLn(StdErr, '[MIC] ', S);
|
|
||||||
Flush(StdErr);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TRadioController.AcquireMicInput(DevIndex: Integer): TAudioInput;
|
function TRadioController.AcquireMicInput(DevIndex: Integer): TAudioInput;
|
||||||
// Ссылка на открытый поток устройства DevIndex (создаёт при первом обращении).
|
// Ссылка на открытый поток устройства DevIndex (создаёт при первом обращении).
|
||||||
// DevIndex<0 = «своего входа нет» → nil (читатель упадёт на общий FAudioIn).
|
// DevIndex<0 = «своего входа нет» → nil (читатель упадёт на общий FAudioIn).
|
||||||
@@ -2213,25 +2220,14 @@ var
|
|||||||
AI: TAudioInput;
|
AI: TAudioInput;
|
||||||
begin
|
begin
|
||||||
Result := nil;
|
Result := nil;
|
||||||
if Assigned(FAudioIn) then
|
|
||||||
MicDbg(Format('Acquire(dev=%d): main FAudioIn dev=%d open=%s name="%s"',
|
|
||||||
[DevIndex, FAudioIn.DeviceIndex, BoolToStr(FAudioIn.IsOpen, True),
|
|
||||||
FAudioInDevName]))
|
|
||||||
else
|
|
||||||
MicDbg(Format('Acquire(dev=%d): FAudioIn=nil', [DevIndex]));
|
|
||||||
if DevIndex < 0 then Exit;
|
if DevIndex < 0 then Exit;
|
||||||
// Общий вход уже открыт на этом же устройстве — переиспользуем его: второй
|
// Общий вход уже открыт на этом же устройстве — переиспользуем его: второй
|
||||||
// Open того же PA-устройства на эксклюзивном ALSA-hw просто не прошёл бы.
|
// Open того же PA-устройства на эксклюзивном ALSA-hw просто не прошёл бы.
|
||||||
if Assigned(FAudioIn) and (FAudioIn.DeviceIndex = DevIndex) then
|
if Assigned(FAudioIn) and (FAudioIn.DeviceIndex = DevIndex) then
|
||||||
begin
|
|
||||||
MicDbg(' -> общий FAudioIn (без открытия)');
|
|
||||||
Exit(FAudioIn);
|
Exit(FAudioIn);
|
||||||
end;
|
|
||||||
for i := 0 to High(FMicPool) do
|
for i := 0 to High(FMicPool) do
|
||||||
if FMicPool[i].DevIdx = DevIndex then
|
if FMicPool[i].DevIdx = DevIndex then
|
||||||
begin
|
begin
|
||||||
MicDbg(Format(' -> пул[%d] refs=%d open=%s',
|
|
||||||
[i, FMicPool[i].Refs, BoolToStr(FMicPool[i].AI.IsOpen, True)]));
|
|
||||||
// Запись с Refs=0 — поток закрыт, но объект жив (см. ReleaseMicInput).
|
// Запись с Refs=0 — поток закрыт, но объект жив (см. ReleaseMicInput).
|
||||||
if (FMicPool[i].Refs <= 0) and FLocalAudio and
|
if (FMicPool[i].Refs <= 0) and FLocalAudio and
|
||||||
(not FMicPool[i].AI.IsOpen) then
|
(not FMicPool[i].AI.IsOpen) then
|
||||||
@@ -2242,15 +2238,7 @@ begin
|
|||||||
AI := TAudioInput.Create(48000);
|
AI := TAudioInput.Create(48000);
|
||||||
AI.DeviceIndex := DevIndex;
|
AI.DeviceIndex := DevIndex;
|
||||||
if FLocalAudio then
|
if FLocalAudio then
|
||||||
begin
|
try AI.Open; except end;
|
||||||
MicDbg(Format(' новый поток: Open(dev=%d) ...', [DevIndex]));
|
|
||||||
try
|
|
||||||
MicDbg(Format(' Open(dev=%d) = %s err="%s"',
|
|
||||||
[DevIndex, BoolToStr(AI.Open, True), AI.LastError]));
|
|
||||||
except
|
|
||||||
on E: Exception do MicDbg(' Open исключение: ' + E.Message);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
n := Length(FMicPool);
|
n := Length(FMicPool);
|
||||||
SetLength(FMicPool, n + 1);
|
SetLength(FMicPool, n + 1);
|
||||||
FMicPool[n].DevIdx := DevIndex;
|
FMicPool[n].DevIdx := DevIndex;
|
||||||
@@ -2304,8 +2292,6 @@ procedure TRadioController.SetSliceInDeviceIdx(Idx, DevIndex: Integer;
|
|||||||
const DevName: string);
|
const DevName: string);
|
||||||
begin
|
begin
|
||||||
if (Idx < 0) or (Idx >= MAX_SLICES) or (not FSlices[Idx].Active) then Exit;
|
if (Idx < 0) or (Idx >= MAX_SLICES) or (not FSlices[Idx].Active) then Exit;
|
||||||
MicDbg(Format('SetSliceInDevice: slot=%d id=%d dev=%d name="%s"',
|
|
||||||
[Idx, FSlices[Idx].Id, DevIndex, DevName]));
|
|
||||||
if DevName = '' then DevIndex := -1;
|
if DevName = '' then DevIndex := -1;
|
||||||
ReleaseMicInput(FSlices[Idx].AudioIn);
|
ReleaseMicInput(FSlices[Idx].AudioIn);
|
||||||
FSlices[Idx].InDeviceIndex := DevIndex;
|
FSlices[Idx].InDeviceIndex := DevIndex;
|
||||||
@@ -2321,18 +2307,12 @@ procedure TRadioController.SetMainMicDevice(DevIndex: Integer;
|
|||||||
const DevName: string);
|
const DevName: string);
|
||||||
var i: Integer;
|
var i: Integer;
|
||||||
begin
|
begin
|
||||||
MicDbg(Format('SetMainMicDevice: dev=%d name="%s"', [DevIndex, DevName]));
|
|
||||||
if Assigned(FAudioIn) then
|
if Assigned(FAudioIn) then
|
||||||
begin
|
begin
|
||||||
if FAudioIn.IsOpen then FAudioIn.Close;
|
if FAudioIn.IsOpen then FAudioIn.Close;
|
||||||
FAudioIn.DeviceIndex := DevIndex;
|
FAudioIn.DeviceIndex := DevIndex;
|
||||||
if (DevIndex >= 0) and FLocalAudio then
|
if (DevIndex >= 0) and FLocalAudio then
|
||||||
try
|
try FAudioIn.Open; except end;
|
||||||
MicDbg(Format(' main Open(dev=%d) = %s err="%s"',
|
|
||||||
[DevIndex, BoolToStr(FAudioIn.Open, True), FAudioIn.LastError]));
|
|
||||||
except
|
|
||||||
on E: Exception do MicDbg(' main Open исключение: ' + E.Message);
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
FAudioInDevName := DevName;
|
FAudioInDevName := DevName;
|
||||||
// Слайсы могли ссылаться на общий вход (или держать свой поток на устройстве,
|
// Слайсы могли ссылаться на общий вход (или держать свой поток на устройстве,
|
||||||
|
|||||||
+4
-2
@@ -463,8 +463,10 @@ end;
|
|||||||
|
|
||||||
function TVfoOverlay.TxSelectionAllowed: Boolean;
|
function TVfoOverlay.TxSelectionAllowed: Boolean;
|
||||||
begin
|
begin
|
||||||
// Main A remains the normal TX source. Additional DMR slices are RX-only.
|
// Main A remains the normal TX source. DMR slices stay RX-only (no AMBE
|
||||||
Result := FIsMain or not (FMode in [OVL_MODE_DMR, OVL_MODE_FMRAW]);
|
// encoder). FM RAW transmits external PCM from its own input, so it is a
|
||||||
|
// valid TX source.
|
||||||
|
Result := FIsMain or (FMode <> OVL_MODE_DMR);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ ---- Lifecycle ---- }
|
{ ---- Lifecycle ---- }
|
||||||
|
|||||||
Reference in New Issue
Block a user