web-dev-qa-db-fra.com

Échantillons de médias maintenus dans le graphique pendant une longue période (effet cumulatif)

Il y a plusieurs mois, j'ai écrit cette question , concernant la famine de tampon sur un graphique DirectShow.

Le problème de famine a été résolu en implémentant un allocateur personnalisé dont la taille augmente en cas de famine. Cependant, cela n'a fait qu'atténuer le vrai problème; avec suffisamment de temps, le nombre d'échantillons contenus dans le graphique devient excessif et le pool en constante expansion crée une situation de mémoire insuffisante.

Voici quelques faits que j'ai réussi à rassembler:

  1. Le graphique transcode essentiellement un flux MPEG2-TS en un fichier MP4, ainsi que l'extraction de données audio et vidéo pour un traitement DSP en temps réel.

  2. Le flux est un flux de multidiffusion UDP. Le flux contient 14 programmes SD différents.

  3. Je lis le flux UDP en utilisant un filtre personnalisé dérivé de l'exemple DsNetwork. À la suite de l'exemple susmentionné, un échantillon de support (sans horodatage) est créé autour du bloc de données reçu UDP (un bloc de 8 Ko) et transmis au filtre démultiplexeur MPEG2 de Microsoft, qui est configuré pour filtrer le programme d'intérêt. (Dois-je horodater les échantillons?)

  4. Le filtre qui nécessite un allocateur extensible est le démultiplexeur MPEG2, en particulier il est requis pour les échantillons délivrés par la broche vidéo de sortie. La broche audio de sortie fonctionne correctement avec un allocateur par défaut, aucun échantillon n'est conservé par le décodeur audio ou le démultiplexeur.

  5. Les échantillons vidéo sont en cours de décodage par le décodeur vidéo LAV. Le remplacement du filtre LAV par le filtre ffdshow n'a aucun effet positif - l'accumulation est toujours présente. Je n'ai trouvé aucun paramètre dans LAV ou ffdshow (y compris les paramètres de file d'attente d'exemple) qui atténue le problème d'accumulation.

  6. Le problème est entièrement lié à la qualité du flux reçu. Plus il y a de discontinuités détectées sur le flux (comme indiqué par les échantillons de sortie du démultiplexeur MPEG), plus les échantillons ont tendance à s'accumuler. Incidemment, l'exécution en parallèle d'un lecteur VLC consommant le même flux enregistre les mêmes discontinuités, donc elles ne semblent pas être induites par un code réseau buggé de ma part.

  7. Les échantillons persistants ne sont pas perdus, ils sont finalement traités par le graphique. J'ai écrit une logique de surveillance pour détecter la possibilité d'échantillons perdus et chaque échantillon est finalement correctement libéré et renvoyé dans le pool.

  8. Le décalage n'est pas lié à la famine du processeur. Si j'arrête de fournir des échantillons au démultiplexeur, le démultiplexeur cesse de fournir des échantillons aux broches de sortie. J'AI BESOIN de pousser de nouveaux échantillons dans le démultiplexeur pour que les échantillons persistants soient correctement libérés et retournés au pool.

  9. J'ai essayé de supprimer l'horloge du graphique de capture, ainsi que des graphiques muxer (pontés par un filtre pont GDCL). Cela ne résout pas le problème et peut en fait bloquer le flux de données.

Je n'ai aucune idée si les échantillons sont détenus par le démultiplexeur ou par le décodeur vidéo. La vérité est que je ne sais absolument pas comment je peux déboguer et j'espère corriger cette situation, et tous les pointeurs ou suggestions sont plus que bienvenus.

Addendum:

J'ai quelques informations supplémentaires:

  1. La vidéo transcodée est en retard par rapport à l'audio.
  2. Le temps de latence est proportionnel à la quantité d'échantillons persistants.

Je pense donc qu'à un moment donné du traitement du graphique, les horodatages des échantillons audio et vidéo décodés sont désynchronisés, et probablement le point de terminaison muxer du graphique bloque le thread de décodage vidéo, en attendant que l'audio correspondant arrive.

Des conseils sur la façon de détecter le filtre incriminé, ou peut-être comment "rebaser" la synchronisation?

Addendum2:

Comme vous pouvez le voir dans les commentaires sur la réponse de Roman, j'avais en fait trouvé un bug qui induisait de fausses discontinuités sur le stream. En corrigeant ça bug, j'ai réduit le nombre d'incidences du problème, mais je n'ai pas corrigé la cause première!

Il s'avère que t la racine du problème a été causée par le filtre de l'encodeur Monogram AAC (au moins la version que j'ai réussi à obtenir, car il semble que n'est plus supporté).

Le codeur calcule les horodatages de sortie de manière incrémentielle, en multipliant la quantité d'échantillons reçus par la fréquence d'échantillonnage de l'entrée. Le filtre suppose que le flux de données est toujours continu et n'examine même pas les échantillons entrants pour les discontinuités! . Le réparer était facile une fois que j'ai identifié le problème, mais c'était en effet le problème le plus difficile que j'ai dû déboguer de toute ma vie en tant que développeur , comme tous les problèmes pointaient vers le démultiplexeur MPEG2 (les horodatages dérivaient entre les broches audio et vidéo de sortie codées et c'était ce filtre qui manquait d'échantillons groupés en premier lieu), pourtant, cela était dû indirectement par le thread de travail de la broche de sortie vidéo bloqué à la fin du graphique, par le multiplexeur MPEG4, qui recevait des échantillons de synchronisation entre l'audio et la vidéo et étranglait l'entrée vidéo pour essayer de garder les choses synchronisées.

En effet, l'illusion que les filtres sont des "boîtes noires" doit être prise avec prudence, car les threads circulent le long du graphique, et un problème sur un filtre en aval peut se manifester comme un faux problème dans un amont filtre.

4
BlueStrat

Enfin, j'ai trouvé la source du problème.

Après avoir réécrit le code de lecture UDP pour utiliser les E/S hautes performances (RIO), je voulais obtenir une métrique sur le nombre de paquets qui étaient abandonnés. J'ai implémenté un vérificateur de continuité MPEG-TS très, très simple, et j'ai trouvé quelque chose de vraiment étrange. Je ne perdais aucun paquet, pourtant, les encodeurs signalaient toujours des discontinuités. Cela n'avait aucun sens!

Après un examen approfondi, j'ai trouvé que j'avais un problème de comptage des références dans les tampons réseau. Je retournais apparemment les paquets TS au pool tôt, alors qu'ils étaient toujours utilisés par les démultiplexeurs. (Le paquet réseau a été partagé sur de nombreux graphiques et j'ai utilisé le comptage des références pour contrôler la durée de vie partagée).

Donc, en substance, il y avait une condition de concurrence où le code réseau pouvait obtenir un "tampon libre" toujours utilisé par le démultiplexeur et les données étaient clobées. Je suppose que les démultiplexeurs ont trouvé des erreurs critiques et inexpliquées qui ont provoqué la perte de synchronisation.

Voici le code du vérificateur de continuité, au cas où il serait utile à quelqu'un ayant des problèmes avec les flux de multidiffusion UDP.

void MulticastMediaSample::Initialize(MulticastSourceFilter* pFilter, MulticastSourceFilter::UDPBuffer* pBuffer) {
   _props.pbBuffer = pBuffer->Data;
   _props.lActual = pBuffer->payloadSizeInBytes;
   _pBuffer = pBuffer;

   // Network packet should be a multiple of a TS packet length (188 bytes)
   int tsPacketCount = pBuffer->payloadSizeInBytes / 188;
   if( pBuffer->payloadSizeInBytes % 188 != 0 ) {
      printf("Invalid TCP packet, length is not multiple of 188\r\n");
      exit(-8828);
   }
   BYTE* pPacket = pBuffer->Data;
   UINT header;
   for( int i = 0; i < tsPacketCount; i++ ) {
      if( pPacket[0] != 0x47 ) {
         printf("Lost Sync!\r\n");
         exit(-12423);
      }
      UINT pId = (pPacket[1] & 0x1f) << 8 | pPacket[2];
      if( pId != 0x1fff ) {  // ignore "filler" packets
         UINT afc = (pPacket[3] & 0x30) >> 4;
         BYTE cc = pPacket[3] & 0xf;
         auto it = pFilter->_ccMap.lower_bound(pId);
         if( it != pFilter->_ccMap.end() && !(pFilter->_ccMap.key_comp()(pId, it->first)) ) {
            // PID key exists in map, check continuity
            if( afc != 2 ) {  // don't check for packets carrying no payload
               BYTE expected = (it->second + 1) & 0xf;
               if( cc != expected ) {
                  printf("Continuity check error for pId %d: expected %d, got %d\r\n", pId, expected, cc);
                  SetDiscontinuity(TRUE);
               }
            }
            // update key
            it->second = cc;
         } else {
            // key does not exist, insert first time 
            pFilter->_ccMap.insert(it, std::map<UINT16, BYTE>::value_type(pId, cc));
         }
      }
      pPacket += 188;
   }
#ifdef DEBUG
   ASSERT(pBuffer->payloadSizeInBytes <= sizeof pBuffer->DataCopy);
   memcpy(pBuffer->DataCopy, pBuffer->Data, pBuffer->payloadSizeInBytes);
#endif
   _pBuffer->AddRef();
   ASSERT(_refCnt == 1);
}
0
BlueStrat