Я уже некоторое время работаю над этой самой проблемой (в C), и только когда я думаю, что у меня это есть, интернет начинает работать или где-то иным образом меняется и бум! Снова прерывистый звук. Что ж. Я почти уверен, что я его облизал.
Используя приведенный ниже алгоритм, я получаю очень, очень хорошее качество звучания. Я сравнил его с другими программными телефонами, которые я запускаю в тех же условиях сети, и он работает заметно лучше.
Первое, что я делаю, это пытаюсь определить, находится ли АТС или другой SIP-прокси, на который мы регистрируемся, в локальной сети с UA (программным телефоном) или нет.
Если это так, я определяю свой джиттербуфер как 100 мс, если нет, я использую 200 мс. Таким образом, я могу ограничить время ожидания, если смогу; даже 200 мсек не вызывают каких-либо заметных проблем в разговоре или разговора.
Итак. Затем я использую системный счетчик любого типа, который у вас есть, например. Windows = GetTickCount64 (), чтобы заполнить переменную временем с точностью до миллисекунды, в которое мой первый пакет поступил для воспроизведения. Давайте назовем эту переменную "x".
Затем, когда ((GetTickCount64 () - x)> jitterbuffer) имеет значение true, я начинаю воспроизведение на этом буфере.
Прямая реализация буфера дрожания фиксированной длины. Вот хитрый бит.
Пока я декодирую кадр RTP (например, из muLaw в PCM) для буферизации его для воспроизведения, я рассчитываю СРЕДНЮЮ АБСОЛЮТНУЮ амплитуду аудиофрейма и сохраняю его вместе с кадром для воспроизведения.
Я делаю это, имея такую структуру:
typedef struct tagCCONNECTIONS {
char binuse;
struct sockaddr_in client;
SOCKET socket;
unsigned short media_index;
UINT32 media_ts;
long ssrc;
unsigned long long lasttimestamp;
int frames_buffered;
int buffer_building;
int starttime;
int ssctr;
struct {
short pcm[160];
} jb[AUDIO_BUFFER]; /* Buffered Audio frame array */
char jbstatus[AUDIO_BUFFER]; /* An array containing the status of the data in the CCONNETIONS::jb array */
char jbsilence[AUDIO_BUFFER];
int jbr,jbw; /* jbr = read position in CCONNECTIONS::jb array, jbw = write position */
short pcms[160];
char status;
/* These members are only used to buffer playback */
PCMS *outraw;
char *signal;
WAVEHDR *preparedheaders;
/**************************************************/
DIALOGITEM *primary;
int readptr;
int writeptr;
} CCONNECTIONS;
Хорошо, обратите внимание на элемент структуры tagCCONNECTIONS :: jbsilence [AUDIO_BUFFER]. Таким образом, для каждого декодированного аудиокадра в tagCCONNECTIONS :: jb [x] .pcm [] имеются соответствующие данные о том, слышим ли этот кадр или нет.
Это означает, что для каждого аудиофрейма, который должен воспроизводиться, у нас есть информация о том, слышен ли этот кадр.
Также ...
#define READY 1
#define EMPTY 0
Поле tagCCONNECTIONS :: jbstatus [AUDIO_BUFFER] позволяет нам узнать, является ли конкретный звуковой кадр, о котором мы думаем, для воспроизведения, ГОТОВЫМ или ПУСТОЙ. В теоретическом случае переполнения буфера он МОЖЕТ быть пустым, и в этом случае мы обычно ждем, пока он будет ГОТОВ, а затем начнем воспроизведение ...
Теперь в моей программе, которая воспроизводит аудио ... У меня есть две основные функции. Один называется pushframe (), а другой - popframe ().
Мой поток, который открывает сетевое соединение и получает вызовы RTP pushframe (), который преобразует muLaw в PCM, рассчитывает амплитуду AVERAGE ABSOLUTE кадра и помечает его как бесшумный, если он неслышно тихий, и помечает :: jbstatus [x] как ГОТОВ
Затем в моей теме, которая воспроизводит звук, мы сначала проверяем, истекло ли время джиттербуфера, опять же, на
if ( ( GetTickCount64() - x ) > jitterbuffer ) {...}
Затем мы проверяем, готов ли следующий кадр для воспроизведения (значит, он действительно был заполнен).
Затем мы проверяем, готов ли фрейм ПОСЛЕ ТОГО, КАК РАМКА, И ЕСЛИ ЭТО СЛУЖБА ИЛИ ТИХАЯ!
*** ВАЖНО
В принципе, мы знаем, что буфер джиттера 200 мс может содержать десять аудиофреймов 20 мс.
Если в любой момент после начальной задержки буфера джиттера в 200 мс (сохранения аудио) количество аудиокадров, которые мы поставили в очередь, падает ниже 10 (или jitterbuffer / 20), мы переходим в режим, который я называю «buffer_building». Если следующий звуковой кадр, который мы планируем воспроизвести, будет беззвучным, мы сообщаем программе, что буфер дрожания еще не заполнен, до его заполнения еще 20 миллисекунд, но мы продолжаем и воспроизводим кадр сейчас, потому что это следующий кадр, который мы видим, что он "тихий" ... еще раз. Мы просто не воспроизводим кадр без звука и используем период молчания, чтобы дождаться входящего кадра, чтобы заполнить наш буфер.
tagCCONNECTIONS::lasttimestamp = GetTickCount64() - (jitterbuffer-20);
Это будет иметь период полного молчания в течение того, что было бы "принято" молчание, но позволяет буферу пополняться. Затем, когда у меня снова есть полные 10 кадров, я выхожу из режима «buffer_building» и просто проигрываю аудио.
Я вхожу в режим «buffer_building», даже когда у нас короткий кадр из полного буфера, потому что долго разговаривающий человек может говорить и не может быть много молчания.Это может быстро истощить буфер даже в режиме «buffer_building».
Теперь ... "Что такое тишина?"Я слышал, вы спрашиваете.В своем бездельничании я жестко закодировал тишину, как любой кадр с амплитудой СРЕДНЕГО АБСОЛЮТНОГО 16-битного ИКМ менее 200. Я представляю это следующим образом:
int total_pcm_val=0;
/* int jbn= whatever frame we're on */
for (i=0;i<160;i++) {
total_pcm_val+=ABS(cc->jb[jbn].pcm[i]);
}
total_pcm_val/=160;
if (total_pcm_val < 200) {
cc->jbsilence[jbn] = 1;
}
Теперь я на самом деле планируюсохраняя общую среднюю амплитуду на этом соединении и играя с возможно, если амплитуда текущего аудиокадра, которую мы только что получили, составляет 5% или меньше от общей средней амплитуды, то мы считаем кадр бесшумным, или, возможно, 2% ...Не знаю, но так, если есть много ветра или фонового шума, определение «тишина» может адаптироваться.Я должен поиграть с этим, но я считаю, что это ключ к пополнению вашего буфера джиттера.
Делайте это, когда нет важной информации для прослушивания, и сохраняйте фактическую информацию (их голос) кристально чистой.
Надеюсь, это поможет.Я немного разбреден, когда дело доходит до объяснения вещей, но я очень, очень доволен тем, как звучит мое приложение VoIP.