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