Delphi: смешивание двух аудиопотоков

#delphi #audio #mixing

#delphi #Аудио #микширование

Вопрос:

Я пытаюсь добавить поддержку конференц-чата в уже запущенное приложение для чата с одним микрофоном (в котором одновременно может говорить только один человек). Потоковая передача для обоих клиентов, и все сделано, и голос записывается и воспроизводится хорошо на обоих компьютерах, которые используют микрофон, но когда третий человек получает пакеты, звук получается действительно странным, я поискал вокруг и обнаружил, что мне нужно микшировать два потока, а затем воспроизводить их как один. Я попробовал несколько алгоритмов, которые нашел в Интернете, но я не получаю нужного мне результата.

Я использую speex в качестве кодировщика / декодера после декодирования входящего потока на стороне клиента я попытался смешать два байтовых массива / потока с помощью следующих алгоритмов.

 Var Buffer1, Buffer2, MixedBuf: TIdBytes;
Begin
  For I := 0 To Length(Buffer1) - 1 Do Begin
    If Length(Buffer2) >= I Then
      MixedBuf[I] := (Buffer1[I]   Buffer2[I]) / 2
    Else
      MixedBuf[I] := Buffer1[I];
  End;
End;
  

Полученный буфер состоит либо из 492, либо из 462 байт, поэтому я проверяю, меньше ли Buffer2, чем Buffer1, затем смешиваю первые 462 байта и оставляю остальные байты неизмененными и просто добавляю их в MixedBuff.

При использовании этого алгоритма возникает много шума и искажений, и слышна только часть голоса.

Другой алгоритм, который я нашел здесь, в stackoverflow, представленный Mark Heath , заключается в том, чтобы сначала преобразовать байты в значения с плавающей запятой.

 Var Buffer1, Buffer2, MixedBuf: TIdBytes;
    samplef1, samplef2, Mixed: Extended;
Begin
  For I := 0 To Length(Buffer1) - 1 Do Begin
    If Length(Buffer2) >= I Then Begin
        samplef1 := Buffer1[I] / 65535;
        samplef2 := Buffer2[I] / 65535;
        Mixed := samplef1   samplef2;
        if (Mixed > 1.0) Then Mixed := 1.0;
        if (Mixed < -1.0) Then Mixed := -1.0;

        MixedBuf[I] := Round(Mixed * 65535);
    End Else
      MixedBuf[I] := Buffer1[I];
  End;
End;
  

Значение никогда не опускается ниже 0, но все же я оставил проверку на то, опускается ли значение ниже -1.0, как это было в алгоритме. Этот метод работает намного лучше, но все равно присутствуют шумы и искажения, и голос из второго потока всегда очень слабый, в то время как голос из первого потока громкий, как и должно быть, даже если первый собеседник не разговаривает, второй голос слабый.

P.S: Да, и некоторые подробности об аудиопотоке:

Сведения о записи tWAVEFORMATEX для воспроизведения аудиозаписи следующие:

 FWaveFormat.wFormatTag := WAVE_FORMAT_PCM;
FWaveFormat.nChannels := 1;
FWaveFormat.nSamplesPerSec := WAVESAMPLERATE; // i.e WAVESAMPLERATE = 16000
FWaveFormat.nAvgBytesPerSec := WAVESAMPLERATE*2;
FWaveFormat.nBlockAlign := 2;
FWaveFormat.wBitsPerSample := 16;
FWaveFormat.cbSize := SizeOf(tWAVEFORMATEX);
  

Я надеюсь, что предоставляю всю необходимую информацию.

Комментарии:

1. Являются ли Buffer1 и Buffer2 байтовыми массивами? Если это так, вам нужно подписать их, чтобы придать им 16-разрядный подписанный тип данных перед добавлением.

2. Имеют ли оба потока одинаковую частоту дискретизации и разрядность? Имеют ли они одинаковое «смещение по постоянному току» (например, минимальный уровень шума)? Я бы, вероятно, попытался сначала нормализовать их, прежде чем микшировать.

3. Да, это массивы байтов… как их подписать? есть какой-нибудь код? И да, оба потока имеют одинаковую частоту дискретизации и биты на выборку, информация FWaveFormat применяется к обоим потокам.

Ответ №1:

 FWaveFormat.wBitsPerSample := 16;
  

Вы должны учитывать тот факт, что ваши сэмплы имеют ширину 16 бит. Ваш код работает с 8 битами одновременно. Вы могли бы написать это примерно так:

 function MixAudioStreams(const strm1, strm2: TBytes): TBytes;
// assumes 16 bit samples, single channel, common sample rate
var
  i: Integer;
  n1, n2, nRes: Integer;
  p1, p2, pRes: PSmallInt;
  samp1, samp2: Integer;
begin
  Assert(Length(strm1) mod 2 = 0);
  Assert(Length(strm2) mod 2 = 0);
  n1 := Length(strm1) div 2;
  n2 := Length(strm2) div 2;
  nRes := Max(n1, n2);
  SetLength(Result, nRes*2);
  p1 := PSmallInt(strm1);
  p2 := PSmallInt(strm2);
  pRes := PSmallInt(Result);
  for i := 0 to nRes-1 do begin
    if i < n1 then begin
      samp1 := p1^;
      inc(p1);
    end else begin
      samp1 := 0;
    end;
    if i < n2 then begin
      samp2 := p2^;
      inc(p2);
    end else begin
      samp2 := 0;
    end;
    pRes^ := EnsureRange(
      (samp1 samp2) div 2,
      low(pRes^),
      high(pRes^)
    );
    inc(pRes);
  end;
end;
  

Некоторые люди рекомендуют масштабирование на sqrt(2) , чтобы сохранить объединенную мощность двух сигналов. Это выглядело бы примерно так:

 pRes^ := EnsureRange(
  Round((samp1 samp2) / Sqrt(2.0)),
  low(pRes^),
  high(pRes^)
);
  

Комментарии:

1. Хорошо, я исправил нарушение доступа. Это был глупый пропущенный раздел 2.

2. Что касается щелчка, я не уверен. Я подозреваю, что вам нужно выполнить более сложную обработку сигналов.

3. Оба потока воспроизводятся намного лучше, и звук теперь одинаковой громкости, но есть непрерывный звук щелчка, и через некоторое время он начинает генерировать ошибку нарушения доступа в строке samp1 := p1^; , но если я изменю for i := 0 to nRes-1 do begin на for i := 0 to (nRes-1) div 2 do begin , поскольку он считывает два байта при каждом запуске цикла, то проблема с щелчком и нарушением доступа исчезнет, но тогда оба голоса становятся слишком слабыми, и в основном второй голос почти не слышен, если первый человек говорит даже очень медленно

4. Черт возьми, я так и понял, страница не обновлялась, я попытался отредактировать первый комментарий, но он мне не позволил, поэтому я удалил его, а затем увидел ваш комментарий: D … теперь проблема с щелчками также исправлена, но проблема со слишком слабым голосом снова появилась

5. Возможно, вам нужно каким-то образом нормализовать потоки. Не зная более подробной информации о содержимом потоков, мне трудно сказать больше. Все, что я действительно пытался здесь сделать, это показать, как объединить два потока и правильно обработать данные. Очевидно, что у вашего кода была большая проблема с байтовой ориентацией. Но я думаю, вам нужно выполнить более подробную обработку сигнала.