Реализация буфера джиттера в Java - PullRequest
5 голосов
/ 29 февраля 2012

Я ищу адаптивную реализацию Jitter Buffer на Java для моего приложения VOIP.Я написал фиксированный буфер дрожания для своего приложения, но я столкнулся с проблемами переполнения буфера или переполнения буфера из-за низкого качества сети.

Существуют ли какие-либо реализации адаптивного буфера дрожания на основе Java, доступные для непосредственного использованияс моим заявлением или использовать в качестве ссылки.

Любая помощь будет принята с благодарностью.

Спасибо

1 Ответ

8 голосов
/ 11 января 2014

Я уже некоторое время работаю над этой самой проблемой (в 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.

...