2012-02-29 5 views
4

Je suis à la recherche d'une implémentation de Jitter Buffer adaptative en Java pour mon application VoIP. J'ai écrit un tampon de gigue fixe pour mon application, mais soit je rencontre des problèmes de buffer undererrun ou de buffer buffer en raison de la mauvaise qualité du réseau.Mise en œuvre du tampon de gigue en Java

Existe-t-il des implémentations basées sur Java de tampon de gigue adaptable pouvant être utilisées directement avec mon application ou utilisées comme référence.

Toute aide serait grandement appréciée.

Merci

+0

Quel est exactement votre problème? Estimez la taille du tampon nécessaire? (La théorie des files d'attente peut aider.) Ou l'allocation de mémoire dynamique? Ou autre chose? – Matthias

+0

Je veux pouvoir faire varier la taille de la mémoire tampon en fonction de la gigue actuelle. –

+0

Et où vous coincez-vous? – Matthias

Répondre

8

Je travaille sur ce problème très (en C cependant) pendant un certain temps, et juste quand je pense que je l'ai, l'Internet est très fréquenté ou non des changements quelque part et boom! Encore de l'audio saccadé. Bien. Je suis sûr que je l'ai léché maintenant. En utilisant l'algorithme ci-dessous, j'ai vraiment, vraiment une bonne qualité sonore. Je l'ai comparé à d'autres softphones que j'ai tournés dans les mêmes conditions de réseau et il fonctionne nettement mieux. La première chose que je fais est d'essayer de déterminer si le PBX ou un autre proxy SIP auquel nous nous enregistrons est sur un réseau local avec l'UA (softphone) ou non.

Si c'est le cas, je définis mon jitterbuffer comme 100ms, sinon, j'utilise 200ms. De cette façon, je limite ma latence si je peux; même 200 ms ne produit aucun problème de conversation ou de surdicécité notable.

Donc. Ensuite, j'utilise un compteur système de tout type que vous avez disponible, par ex. Windows = GetTickCount64(), pour remplir une variable avec l'heure de précision milliseconde que mon premier paquet est entré pour la lecture. Appelons cette variable "x".

Puis, lorsque ((GetTickCount64() - x)> jitterbuffer) est vrai, je commence la lecture sur ce tampon.

Mise en œuvre directe du tampon de gigue de longueur fixe. Voici le peu difficile. Pendant que je décode la trame RTP (comme de muLaw vers PCM) pour la mettre en mémoire tampon, je calcule l'amplitude ABSOLUE MOYENNE de la trame audio, et la sauvegarde avec la trame pour la lecture.

Je le fais en ayant une structure comme ceci:

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; 

Ok, notez les tagCCONNECTIONS :: jbsilence membre struct [AUDIO_BUFFER]. De cette façon, pour chaque trame audio décodée dans tagCCONNECTIONS :: jb [x] .pcm [], les données correspondantes indiquent si cette trame est audible ou non.

Cela signifie que pour chaque image audio sur le point d'être lue, nous avons l'information indiquant si cette image est audible.

aussi ...

#define READY 1 
#define EMPTY 0 

Le tagCCONNECTIONS :: jbstatus [AUDIO_BUFFER] champ nous allons nous savoir si le cadre audio particulier, nous pensons à jouer est prêt ou videz. Dans le cas théorique de buffer underflow, il pourrait être vide, dans ce cas, nous attendrions normalement qu'il soit prêt, puis commencer à jouer ...

Maintenant dans ma routine qui joue l'audio ... J'ai deux fonctions principales. Un, appelé pushframe(), et un appelé popframe().

Mon thread qui ouvre la connexion réseau et reçoit les appels RTP pushframe(), qui convertit le muLaw en PCM, calcule l'amplitude ABSOLUE MOYENNE de la trame et la marque comme silencieuse si elle est silencieuse et marque: jbstatus [x] Ready

Puis dans mon thread qui joue l'audio, nous vérifions d'abord si le temps de jitterbuffer a expiré, encore une fois, par

if ((GetTickCount64() - x) > jitterbuffer) {...} 

Ensuite, nous vérifions si la trame suivante à jouer est PRET (ce qui signifie qu'il a bien été rempli).

Puis nous vérifions si le cadre APRÈS CETTE IMAGE est PRÊT aussi, et SI ELLE EST AUDIBLE OU SILENCIEUSE!

*** IMPORTANT

Fondamentalement, nous savons qu'un tampon de gigue 200ms peut contenir dix trames audio 20ms. Si à tout moment après le délai initial du buffer de gigue 200ms (enregistrement audio) le nombre de trames audio en file d'attente chute en dessous de 10 (ou jitterbuffer/20), nous passons en mode "buffer_building". Dans le cas où la prochaine trame audio que nous avons prévu de jouer est silencieuse, nous disons au programme que le tampon de gigue n'est pas encore plein, il est encore à 20 millisecondes d'être plein, mais nous allons jouer le cadre que nous sommes maintenant parce que c'est le cadre NEXT nous voyons que c'est "silencieux" ... encore une fois. Nous ne jouons simplement pas le cadre silencieux et utilisons la période de silence pour attendre sur une trame entrante pour remplir notre tampon.

tagCCONNECTIONS::lasttimestamp = GetTickCount64() - (jitterbuffer-20); 

Cela aura une période de silence complet pendant ce qui aurait été « pris » le silence, mais permet le tampon de se reconstituer. Puis, quand j'ai à nouveau plein les 10 images complètes, je sors du mode "buffer_building", et je joue simplement l'audio.

Je passe en mode «buffer_building» même lorsque nous sommes à court d'une image d'un tampon complet, car une personne qui parle longuement peut parler et il ne peut y avoir beaucoup de silence. Cela pourrait épuiser rapidement un tampon même en mode "buffer_building".

Maintenant ... "Qu'est-ce que le silence?" Je vous entends demander. Dans mon déconner, je suis codé en dur silence comme tout cadre avec une amplitude MOYENNE ABSOLUE PCM 16 bits inférieur à 200. Je figure cela comme suit:

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; 
} 

Maintenant, je prévois réellement sur le maintien d'un amplitude moyenne globale sur cette connexion et en jouant avec peut-être si l'amplitude du cadre audio actuel que nous venons de recevoir est 5% ou moins de l'amplitude moyenne globale, alors nous considérons le cadre silencieux, ou peut-être 2% ... Je ne sais pas , mais de cette façon, s'il y a beaucoup de vent ou de bruit de fond, la définition de «silence» peut s'adapter. Je dois jouer avec cela, mais je crois que c'est la clé pour réapprovisionner votre tampon de jitter. Faites-le quand il n'y a pas d'informations importantes à écouter, et gardez les informations réelles (leur voix) parfaitement claires.

J'espère que cela aide. Je suis un peu distraite quand il s'agit d'expliquer les choses, mais je suis très, très heureux de la façon dont mon application VoIP sonne.

+0

Connaissez-vous tous les schémas raisonnables pour gérer l'épuisement des sources sonores continues (comme la diffusion de musique?) –

+0

Véritable article utile! Voulez-vous dire que le programme passerait du mode PLAY au mode «buffer building» après avoir joué une seule image (première image)? Parce que dans ce cas, le programme continuerait à passer de la lecture au mode de construction du tampon à chaque fois qu'il joue la première image. Pouvez-vous clarifier s'il vous plait ? –