Chargement...
 
[Voir/Cacher menus de gauche]
[Voir/Cacher menus de droite]

7 - Développer/traduire/documenter/annoncer une application

> Forums de discussion > 7 - Développer/traduire/documenter/annoncer une application > Traitement du signal jack multichannel, délai constant...
Dernier post

Traitement du signal jack multichannel, délai constant...

LeChacal619 utilisateur non connecté
Bonjour à tous !

Je suis ici car j'utilise Jack pour effectuer du filtrage actif a partir de mon pc, et je manque cruellement d'informations quant à la synchronisation temporelle des flux audio de jack...

Je vais donc poser quelques questions primaires dont vous aurez, étant développeurs, certainement la réponse :

1- comment est déterminée la latence du serveur jack ? Je m'explique : jack a une latence fixe indiquée que Qjackctl qui est apparemment du nombres de frames / period * nombres de périodes/fréquence de sampling.

frames = samples
frames/period*period = buffer ?

Je ne comprend pas très bien comment est assuré cette latence. Je m'explique :

Si j'ai le schéma suivant dans les connections jack:

appli_1-out ==> appli_2-in
appli_2-out ==> system:output

Est-ce que le temps de latence est le temps de latence annoncé sur l'interface Qjackctl ou 2x le temps de latence ? (puisque le signal fais 2 fois le chemin avant de sortir par la carte son ?)

2- J'utilise les plugins LADSPA pour appliquer des filtres passe-bas/passe-haut sur plusieurs canaux de mon système. Ces plugins sont hostées par des threads jack-rack. J'ai des problèmes quant à la synchronisation des delais des canaux. Je m'explique encore, par exemple j'ai ceci :

canal 1 ==> jack-rack-lowpass ==> system:out1
canal 2 ==> jack-rack-highpass ==> system:out2

Dans le thread jack-rack-lowpass j'ai divers plugins : un filtre IIR lowpass, un simple_amplifier, et admettons plusieurs simples bandes paramétriques (nombre indéterminé).

Lorsque j'effectue des mesues de mon système, le délai en samples des réponses d'impulsions des 2 voies ne sont pas constants. Je suis donc obligé d'appliquer un delai sur 1 canal pour scinder les 2 impulsions, repérer leur décalage, et calculer le décalage réel sans le délai que j'ajoute. Ainsi, en supprimant le délai, j'ai normalement les 2 impulsions des voies "grave" et "aigu" qui se superposent en phase. Le problème est qu'a chaque fois que je relance les applications (qjackctl,jackd, les threads jack-rack) les impulsions des 2 voies se désynchronisent (autrement dit le delai entre le flux du canal1 et 2 jusqu'à la sortie n'est pas fixe lorsqu'on relance les processus (!).

De même, si j'ajoute un plugin dans un des 2 threads jack-rack, il se peut que le délai du flux en question varie encore (délai augmenté).

Je suis donc devant un problème de latence que j'aimerai résoudre en appliquant tout les effets par une seule application, en appliquant les effets dans une seule fonction, et uniquement une fois tout les effets appliquer, effectuer une copie memoire vers les ports de sorties pour que les canaux soient tous synchronisés. J'ai déjà réussi a utiliser l'API jack, avec une appliation qui gère 8 canaux (je dois réaliser un 4voies stéréo) et implémenter des délais réglables sur chaque voie avec un buffer (circulaire ?) (un buffer de 20000 samples inialisé a 0 qui enregistre les samples du port in avec un index de (i+delay)%20000, et je renvoie les samples d'index i%tailledubuffer du buffer). Ainsi les samples qui rentrent et qui sont stockés a l'index n+delay sont lus avec "delay" samples de retard, et a chaque tour de buffer on revient au début.

Je n'ai cependant aucune idée pour implémenter les effets LADSPA ! Faut-il que je code mon propre "host" de plugin intégré a mon appli jack ? Comment faire ?

Le site de LADSPA donne des infos pour développer ces propres plugins, mais concernant le "hosting" de plugins, je n'ai trouvé aucune information (je ne sais pas du tout ou chercher).

3- Les filtres FIR ajoutent du délai inévitable au signal (phase linéaire mais group delay de (N-1)/2 taps, plus le filtre FIR est précis plus le retard est important). Enfin, je vois mal comment implémenter la convolution des samples avec les "blocs" de sample que jack envoie par période : sur le domaine temporel je ne vois pas comment faire, sur le domaine fréquentiel il faudrait idéalement que le filtre soit appliqué sur des parties de signal périodique (sinon obligé d'appliquer des fenêtres qui réduisent les infos, et plus les blocs de samples sont petits plus les pertes seraient importantes). Donc beaucoup de problèmes d'implémentation se posent pour moi, la solution serait peut-être un buffer circulaire de taille bcp plus grande, et scinder les parties du buffer en signaux périodiques (sample début ~= sample fin).

Comme le signal croise souvent 0, il est facile de "couper" le signal apériodique en petites parties "périodiques" pour appliquer le filtre sur le domaine fréquentiel et reconstituer le signal original ensuite.

Mon système ayant besoin d'avoir la latence la plus faible possible (j'utilise le système pour traiter le son qui sort d'une console donc le retard doit être non perceptible ;))

4- Je me suis alors penché vers les filtres IIR (avantage pour la coupure dans les basses fréquences mais plus de linéarité de phase (!).

Apparement les calculs sont bien inférieurs aux filtres FIR mais je ne comprend pas comment les "designer", "implémenter", je ne comprend pas non plus la notion de "poles" etc.

J'ai cependant trouvé un plugin (le plugin "lowpass_iir_1891.so") qui permet de choisir la fréquence de coupure et le nombre de stage pour régler l'ordre du filtre (plus l'ordre est élevée plus la phase est perturbée). Le code source n'est pas accessible, et je ne sais pas comment est appliquer le filtre pour contrôler que ces "séparations" par "blocs" ne perturbe pas l'application correcte du filtre.

Ce plugin serait parfait si j'arrivais a l'intégrer sur mon appli jack, d'ou une réponse a ma question 2 m'aiderait énormément !


J'ai aussi tenté d'utiliser ecarack pour charger les plugins sur ecasound, mais j'obtiens une erreur de fichier introuvable lorsque j'essaie de charger un .rack qui fonctionne sur jack-rack.... J'ai également tenté ingen (j'ai eu un pb de compilation donc j'ai supprimé un dossier des plugins lv2 et les liens de compilation, et après j'ai pu l'installer), mais celui-ci plante également et la doc est quasi-inexistante (peut-être a cause de la manip pour bypasser l'erreur de compilation ?)


Voilà si quelqu'un (Chritophe ? wink) peut me fournir quelques aides sur ces concepts je serai ravi !

pianolivier utilisateur non connecté France
pour le 1/
ce qu'on m'a fait comprendre sur #jack (freenode, carrefour conseillé) c'est que justement jack ne fonctionne pas ainsi : in > app1 > app2 > out, en fait il s'agit plutot de multiples flux qui se baladent dans tous les sens à tout moment, jack n'attend pas vraiment la fin d'une boucle pour continuer son chemin
l'idée qui consiste a penser que jack a une latence (ou un tampon) lui-même est fausse, en fait jack défini seulement le tampon chaque application sous son contrôle
Du coup mes connaissances s'arretent la, mais je suppose qu'on va se retrouver avec le shéma suivant : un tampon s'applique à chaque fois que le flux audio "traverse" une "processing unit" du graph de jack, pour un peu que celle-ci soit codée pour fonstionner en RT correctement

pour les autres questions, je passe la main... wink

oliv'

LeChacal619 utilisateur non connecté
pianolivier écrit :
pour le 1/
ce qu'on m'a fait comprendre sur #jack (freenode, carrefour conseillé) c'est que justement jack ne fonctionne pas ainsi : in app1 app2 out, en fait il s'agit plutot de multiples flux qui se baladent dans tous les sens à tout moment, jack n'attend pas vraiment la fin d'une boucle pour continuer son chemin
l'idée qui consiste a penser que jack a une latence (ou un tampon) lui-même est fausse, en fait jack défini seulement le tampon chaque application sous son contrôle
Du coup mes connaissances s'arretent la, mais je suppose qu'on va se retrouver avec le shéma suivant : un tampon s'applique à chaque fois que le flux audio "traverse" une "processing unit" du graph de jack, pour un peu que celle-ci soit codée pour fonstionner en RT correctement

pour les autres questions, je passe la main... wink

oliv'


Ok merci je crois avoir compris. Donc en fait jack applique un buffer sur chaque application qui possède la fonction "process()". Autrement dit toutes les applications jack.... Puisqu'il faut au moins cette fonction pour faire passer le flux d'entrée vers le flux de sortie (avec un copie du buffer d'entrée vers la sortie).

Jack n'attend peut-être pas la fin de chaque process(), mais appliquer un tampon revient au même : on lui confère juste un temps de process() égal a la taille du tampon/fréquence de sampling ! Exemple si le tampon fais 44100 sur une fréquence de sampling.

Dans l'appli 1 le premier process est appellé par jack quand il y a du travail a faire, donc lorsque le tampon est plein : au bout de 44100 samples lus. Autrement dit quand l'application va commencer son travail, le flux aura déjà un retard de 44100 samples. Si on traite le son instantanément, et qu'on le fait sortir admettons vers une appli2, qui repasse encore par un process(), on applique encore ce retard de 44100 samples puisque le process() de l'appli2 ne sera appellé que lorsque le tampon de sortie de l'appli1 sera rempli, autrement dit quand le process() de l'appli1 sera terminé. On a alors un temps de latence = 44100samples du tampon de l'appli1 + temps de processing de l'appli1 + 44100 samples du tampon de l'appli2 + temps de process de l'appli2 + 44100 samples de tampon vers la sortie hardware ALSA ?

Alors si je dois contrôler la latence de la chaîne audio il faut que je puisse contrôler le temps de process de chaque appli ? Autrement dit leur créer moi-même un contrôle temporel pour que la durée de la fonction process() soit toujours constante ?

Je pensais que jack() contrôler l'exécution des process() de la manière suivante :

cycle 1 : (0 sample)
in ==> tampon
cycle 2 : (1 tampon = 44100 samples)
tampon rempli ==> applijack,appel process()
cycle 3 : (2 tampons = 88200 samples)
contrôle process() terminé (=a renvoyé une valeur) ==>
si oui ==> out
si non ==> xrun()
Nouvel appel de la fonction process() de l'appli jack...

Autrement dit je pensais que jack permettait a la fonction process() un temps de fonctionnement

LeChacal619 utilisateur non connecté
Ah c'est bon pour la latence je crois avoir compris en fouillant un peu plus j'ai trouvé ca : http://trac.jackaudio.org/wiki/WalkThrough/Dev/LatencyBufferProcess

''"Processing audio data with a line in, two plugins and a line out happens this way:

1. A/D converter of your sound card has processed a new data packet on line in. The size of packet is determined by Jack configuration. You set it through command line or qjackctl.
2. Plugin one's process method is executed. After executing it will produce its output on its output buffer.
3. _as soon as_ plugin1 finished plugin2's process method is executed. After executing it will produce output on its output buffer.
4. The output buffer is passed to the line out D/A converter of your sound card. The D/A converter will consume samples in its own frequency one by one. "''

Comme indiqué plus en dessous, cela nous donne donc ca :

"The latency of this system is built from the following factors:

* The packet length is a latency. If all plugins could pass the audio data in 0 time then the output could be played as soon as the input buffer is filled so the minimum theoretical latency would be the buffer's length (measured in time). This latency is always T.
* Plugin processing time is not zero. So playback of the buffer can not be started as soon as the input buffer is ready. This gives a second latency factor. This factor is a free decision. This is the output latency. If you select it to be too small the chance of underrun will be high.
* Some effects (such as fft based effects) analyse sound in blocks. They can not produce output (as a result of a sample) until the whole buffer is filled. So the time until the buffer is filled will cause a latency. "


Je confirme expérimentalement le fait que traverser plusieurs process() n'implique pas une augmentation du temps de latence : sur mon serveur jack j'ai configuré une latence de 0,372sec, et j'ai vérifié a bistodénas la latence entre la chaîne suivante :

lecteur audio => sortie carte son

et la chaîne suivant :

lecteur audio => ecasound1
ecasound1 => ecasound2
ecasound2 => ecasound3
ecasound3 => ecasound4
ecasound4 => sorte carte son

Si a chaque passage de process() la latence augmentait d'une taille de buffer, j'aurai dans le deuxieme cas une latence de 4x0,372sec = 1,5sec.

Ce n'est pas le cas puisqu'en switchant rapidement entre les 2 cas, la variation de latence n'est pas perceptible et elle semble identique. Donc effectivement les process() s'exécutent a la suite les uns les autres sans ajouter de latence autre que le temps d'exécution des fonctions.

Je vais contrôler a présent, puisque chaque process() doit être exécuté avec le prochain appel du serveur jack, si avec des fonctions qui prennent par exemple 0,3sec a s'exécuter, si le fait de les mettres a la chaîne entraînera ou non un xrun() (puisque le temps global d'exécution des clients process() sera de 4x0,3sec = 1,2sec.

C'est un peu une analyse grossière du principe de fonctionnement du transport du flux audio mais a défaut d'avoir plus de renseignements c'est la seule méthode qui me permettra de bien comprendre comment tout cela fonctionne ! ;)

PS: j'ai aussi tenté de régler output latency sur une valeur extrême genre 200000 samples pour avoir un retard de plus de 2 secondes a 88000samples/sec, mais ca ne change rien la latence est inchangée (0,372sec). Je me demande donc bien a quoi ca peut servir....

LeChacal619 utilisateur non connecté
Petit avancement a nouveau dans l'exécution des process : j'ai pu vérifier que les clients ne sont pas appellés dans l'ordre de leur "connexions" et donc qu'ils ne sont pas appellés dans le même ordre que le flux audio...

Autrement dit :

Sur la chaîne suivant :

entrée audio ==> client1 ==> client2 ==> sortie audio

Jack peut très bien appellé le client2 avant le client1 !

Pour cela j'ai effectué des printf() 1 fois sur 100 appels de la fontion process() du client (pour ne pas avoir d'xruns) qui affichent la différence entre le temps au moment de l'appel des fonctions process() de jack, et le moment ou le printf est affiché dans le process() du client (donc grosso modo le temps ou le process() client est appelé).

J'ai ajouté juste après ce printf un sleep de 10ms, compilé le code sur un client nommé "client1" et un client nommé "client2", que je lance tout les 2 a la fois.

Sur le résultat affiché :

client1 : entre 1 et 3 frames de latence entre l'appel de jack et l'exécution du printf
client2 : entre 921 et 923 frames de latence (922/88200 de fréquence de sampling ~= 10ms, ce qui correspond bien au sleep de l'autre client executé avant, ici le client 1 est exécuté avant le client2).

Problème : avec la ligne suivant : ./client1 & ./client2, les clients sont exécutés parfois le client1 avant le client2, parfois le client2 avant le client1. J'en déduis donc que l'ordre d'appel des clients ne dépend pas de leur "nom", mais de leur connexion a jack. Si le processus "client1" se connecte et s'active avant le "client2", alors il sera exécuté avant le "client2" a chaque callback de process() par le serveur.

En revanche, si le client2 se connecte avant au serveur jack, alors celui-ci sera exécuté avant. J'ai pu vérifier mon hypothèse/analyse en faisant la chose suivant :

./client1 &

et une fois le client1 initialisé : ./client2

Le client2 est bien exécuté toujours après le client1.

La chose inverse est également vérifié :

./client2 &

et peu de temps après ./client1

indique que le client2 s'exécute toujours avant le client1.


Il n'y a donc aucune gestion de l'ordre d'appel des clients par le serveur jack, les clients sont exécutés dans l'ordre du plus anciens activés au plus récent (sûrement un vecteur ou une liste avec un push() ou remove() qui ajoute les nouveaux clients en fin de liste, et les exécute donc après les clients plus anciens).

C'est pas terrible tout ca !!!

TChris utilisateur non connecté
Petites précisions :

Comme tu as pu le lire, le temps de latence est principalement du à 3 phénomènes :

1. La taille du buffer
2. Le temps impliqué par les algorithmes utilisés dans les filtres, effets, applis, ...
3. Le retard introduit par certains algorithmes qui demandent plusieurs échantillons avant de fournir une sortie

Explications du point 1 : avec un buffer de 1 échantillon, le temps de latence théorique serait réduit au minimum sauf que ceci impliquerait des entrées/sorties vers la carte son trop nombreux => ceci se traduit par des interruptions, des transferts de la mémoire vers la carte son. Comme pour les disques durs : on écrit pas un octet par un octet mais par bloc (écriture bufferisée). Avec un buffer de taille plus importante, on limite ces interruptions mais du coup, on a un temps de latence minimum théorique plus important. La taille du buffer acceptable dépend essentiellement de la carte son utilisée.

Explication du point 2 : ici, il est question uniquement de la puissance de calcul du pc. Sur une grosse machine, on pourra enchaîner les effets, filtres, ... sans trop de problème. Sur une machine moins importante, la surcharge du cpu entraînera un temps de latence important.

Explication du point 3 : ici, rien à voir avec la carte son ou la puissance de calcul. Il s'agit simplement de la structure de l'algo utilisé. Par exemple, certaines filtres effectuent des FFTs qui ont besoins d'un certain nombre d'échantillons avant de produire un résultat. Peu importe alors la puissance de calcul de la machine, il faudra toujours ce nombre d'échantillon et comme on va pas aller plus vite que la musique ...

Au sujet de l'ordre d'exécution des clients, il faut bien comprendre que ce n'est pas Jack qui décide de lancer tel ou tel client. Les clients sont des applications (des processus) indépdendantes de Jack. L'ordre d'exécution des processus est déterminé par le Le noyau Linux linux. Voilà pourquoi il est conseillé d'utiliser un noyau temps réel pour les applications audio car celui-ci à un processus de "scheduling" différent du noyau standard. Que fait Jack dans ces cas là ? Jack indique simplement aux clients que des données sont présentes sur telles ou telles entrées (ports). Quand le noyau décidera de donner du temps processeur à telle ou telle application, celle-ci entrera alors dans la fonction de traitetement (process()) puisqu'il y a des données à traiter. Il faut noter que l'exécution de la fonction process() peut être interrompue à tout moment par le noyau qui donnera alors un peu de temps à un autre processus. C'est pour cela que le noyau est dit multi-tâches.

Il est donc tout à fait normal que tu obtiennes les résultats que tu indiques.

Christophe

pianolivier utilisateur non connecté France
coucou ici, j'en rajoute une petite couche perso :

les reglages de latence que tu utilise sont deja tres gros, sur un système bien configuré un buffer "normal" est 3x512 échantillons, c'est peut être pour ca que tes essais a tres-tres-gros buffer ne fonctionnent pas, je ne suis pas sur qu'il soit prévu d'utiliser plusieurs secondes de latence sous jack

Citation :
Voilà pourquoi il est conseillé d'utiliser un noyau temps réel pour les applications audio car celui-ci à un processus de "scheduling" différent du noyau standard


en fait non, le procédé de sheduling utilisé pour chaque application peut etre différent, et est géré généralement par PAM (voir PAM), que ce soit sur un noyau rt ou non : les deux ont la capacité de lancer les applis "normales" avec le sheduling standard et de lancer les applis qui le demandent en SHED_FIFO (processus de scheduling temps-réel)
le Le noyau Temps-Réel actuel apporte d'autres améliorations qui n'ont pas grand chose a voir

oliv', qui suis attentivement ce fil de discution ;-)

TChris utilisateur non connecté
Salut Oliv,

Effectivement, le Le noyau Linux Linux standard propose trois modes de scheduling : SCHED_NORMAL, SCHED_FIFO et SCHED_RR. Les deux derniers sont des modes "temps réel". Ils permettent d'exécuter une tâche tant qu'une tâche de plus haute priorité ne demande pas son exécution. Ceci-dit, les noyaux -rt utilisent un autre mode de scheduling temps réel qui n'est pas dispo dans le noyau standard.

On peut trouver un article complet sur http://www.linuxjournal.com/magazine/real-time-linux-kernel-scheduler?page=0 ,0

Christophe

pianolivier utilisateur non connecté France
Citation :
Effectivement, le noyau Linux standard propose trois modes de scheduling : SCHED_NORMAL, SCHED_FIFO et SCHED_RR. Les deux derniers sont des modes "temps réel". Ils permettent d'exécuter une tâche tant qu'une tâche de plus haute priorité ne demande pas son exécution. Ceci-dit, les noyaux -rt utilisent un autre mode de scheduling temps réel qui n'est pas dispo dans le noyau standard.

ah ok je crois que j'ai loupé une subtilité
d'apres ce que je comprend de ce lien et de mes experiences, c'est que les modes de sheduling (SCHED_NORMAL, SCHED_FIFO...) restent les mêmes quelque soit le noyau mais le processus de sheduling derrière est différent ?

merci pour le lien dans tous les cas Christophe, je l'ajoute de ce pas à Le noyau Temps-Réel wink

Afficher les articles :
Aller au forum :

Documentation [Afficher / Cacher]

Faire un don
[Afficher / Cacher]

Connexion
[Afficher / Cacher]



Mégaphone [Afficher / Cacher]

calixtus06, 14:33, jeu. 28 mars 2024: Bonjour et bienvenue à b.vl :-)
calixtus06, 09:30, mer. 27 mars 2024: Bonjour et bienvenue à Noar :-)
olinuxx, 18:50, lun. 25 mars 2024: Bonjour et bienvenue à Ted Demore cool
olinuxx, 17:52, dim. 24 mars 2024: Bonjour et bienvenue à Noitavon cool
calixtus06, 11:07, jeu. 21 mars 2024: Bonjour et bienvenue à obds, ceric :-)
obds, 16:12, mar. 19 mars 2024: Cet édito est juste parfait. Trop beau !
olinuxx, 11:48, ven. 15 mars 2024: Bonjour et bienvenue à Jerry cool
calixtus06, 18:03, mer. 13 mars 2024: Bonjour et bienvenue à tanguero :-)
olinuxx, 11:01, dim. 10 mars 2024: Bonjour et bienvenue à lolo cool
bda, 16:59, sam. 09 mars 2024: Chapeau pour l'édito. Vous êtes au top les gars :-)
allany, 07:20, jeu. 07 mars 2024: Ça ne fait jamais de mal, c'est l'éditorial ! [Lien]
olinuxx, 19:52, mer. 06 mars 2024: Bonjour et bienvenue à TrkNrk cool