[TOC] # Principes encodage des fichiers vidéos Une video = une succession d'images 24 FPS = 24 images rendues par seconde ![image](https://hackmd.io/_uploads/BJ6vn4U-A.png) Mais stocker toutes les images de manière complète -> trop lourd en mémoire. Pour optimiser le stockage en mémoire on utilise **des codecs** Un CODEC est un circuit **electronique** ou bien un **composant logiciel** qui permet la compression et la decompression d'un stream video ou audio. Les fichiers vidéo compréssés sont stockés sous la forme d'un **container**/d'un **wrapper**. ![image](https://hackmd.io/_uploads/rkd_hVUZR.png) Il permet de regrouper les streams d'une vidéo au sein d'un seul fichier et contient d'autre informations (données de syncronisation, de sous titres). Chacun de ces streams peut être décompressé par son CODEC associé. Un fichier vidéo est donc un regroupement de plusieurs streams au sein d'un wrapper. Pour chacun de ces streams, les données seront accessibles sous la forme de paquets, permettant de reconstituer les éléments constituant la vidéo 'une frame, une paquet audio, etc...) Beaucoup de ces codecs réalisent leur économie de mémoire par l'utilisation du principe de frames clé et frames calculées ![image](https://hackmd.io/_uploads/BkfK2VI-A.png) Une I‑frame (Intra-coded picture) est une image complete. A P‑frame (Predicted picture) contient uniquement les changements par rapport à la frame precedente (qui est forcement une I-frame). A B‑frame (Bidirectional predicted picture) contient les changements par rapport à la frame precedente et la frame suivante (qui peuvent être des frames I ou P). Il est donc souvent necessaire de decoder plusieurs paquet d'un flux pour permettre le rendu d'une frame complete. # Cas particulier, les fichiers MPEG-TS Les fichiers MPEG-TS sont des wrapper permettant le transfert d'un stream de DATA en plus des streams de video et d'audio. ![image](https://hackmd.io/_uploads/ryAKnEIWA.png) Dans notre cas, nous les utilisons pour transmetre des informations relatives à la vidéo. Ces informations sont transmises sous la forme de paquets construit selon Le protocole STANAG (pour Standardization Agreement). Il s'agit d'un ensemble de normes utilisées par les forces armées de plusieurs pays membres de l'OTAN pour faciliter l'interopérabilité et l'échange d'informations dans le domaine militaire. Il couvre divers aspects, en particulier les communications vocales et protocoles de transmission de données. Plus précisément, pour la transmission d’informations entre un système de drones sans pilote dit **UAS** (Unmanned Aircraft System) et une station de contrôle au sol dit **GCS** (Ground Control Station), il définit la structure d’un paquet de donné nommé **UAS Datalink Packet** (abrégé UDP) comme suit : ![image](https://hackmd.io/_uploads/SJd5nNL-A.png) Ces paquets suivent une structure de type **KLV** (pour Key-Length-Value). Il s’agit d’un standard d’encodage de données selon trois champs : - Le champ **Key** (ou clé), de longueur **16 octets**, identifie le **type de métadonnées** contenues dans le paquet. Il s’agit d’un nombre entier unique qui est attribué à chaque type de métadonnées. Il indique comment la valeur doit être interprétée et utilisée. Par exemple, une clé spécifique peut indiquer la latitude d'une position géographique. - Le champ **Length** (ou longueur) indique la **longueur, en octets**, du champ Value. Elle permet au décodeur de savoir combien d'octets il doit lire pour extraire correctement la valeur. La longueur de ce champ varie entre 1 et 4 octets. - Le champ **Value** (ou valeur) représente les données réelles des métadonnées. Il peut être de différents types, tels que des chaînes de caractères, des nombres entiers, des valeurs booléennes, des données binaires, etc. La valeur est interprétée en fonction du champ Key associé. Plus precisement, les paquets utilisé par MERIO utilisent l'encodage BER pour la longueur. De ce fait, le champ Value, contient lui même plusieurs champs paquets KLV. ![image](https://hackmd.io/_uploads/rkLsh48W0.png) Ainsi, un paquet sur le flux des données contiendra un ensemble de couple clé, valeur, afin de produire, pour une frame, les informations suivantes : ![image](https://hackmd.io/_uploads/B1Cj3E8WA.png) # FFMPEG [Lien de la doc](https://ffmpeg.org/doxygen/trunk/index.html) FFMPEG va permettre d'acceder aux paquets de chacun des streams d'un fichier en recevant les paquets les uns à la suite des autres. On parle de ***Demuxing*** (démultiplexage en FR) Un paquet est une **portion de données** contenant **une ou plusieurs frames** appartenant à un stream specifique. Dans le code, nous allons utiliser FFMPEG au travers de la librairie **`libavformat`**, nous fournissant une API pour les traitement de ***Muxing*** et ***Demuxing***. Le processus de demuxing se fera au travers d'un ensemble de structure C: ![image](https://hackmd.io/_uploads/rJ52hVLbA.png) L'utilisation de ces structures sera orchestrée par un appel aux fonctions : - **`avformat_open_input()`** : Ouverture du wrapper video - **`av_read_frame()`** : Récupération d'un paquet d'un des streams du wrapper - **`avformat_close_input()`** : Libération et nettoyage des ressources allouées Le `AVFormatContext` contient le contexte du wrapper et permet d'acceder à ses informations. Il rend accessible chacun des **streams** du fichier au travers de son tableau **`m_pFormatContext->streams`**. ```C // We allocate memory to the component AVFormatContext that // will hold information about the format (container). m_pFormatContext = avformat_alloc_context(); if(m_pFormatContext == NULL) { throw std::runtime_error("Couldn't allocate memory for the AVFormatContext"); } // We fill the AVFormatContext with minimal information about // the format (container) of the file response = avformat_open_input(&m_pFormatContext, m_filename.toStdString().c_str(), NULL, NULL); if(response < 0) { throw std::runtime_error("Couldn't fill the AVFormatContext: , error code: " + std::to_string(response)); } // We now read data from the media // After that, pFormatContext->streams[i] returns the i stream (an AVStream) avformat_find_stream_info(m_pFormatContext, NULL); ``` :::info **`avformat_find_stream_info`** lit quelques paquets des streams du *wrapper* afin d'obtenir plus d'informations sur ces derniers Les paquets ainsi examinés sont *bufferisés* par FFMPEG afin d'optimiser leur demuxage futur C'est utile pour les *wrappers* contenant peu d'informations dans leur *header*, comme **MPEG**. ::: Ces streams sont représentés par la structure **`AVStream`** ```C // We can now save the extracted codecs's index for each stream for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++) { if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_duration = m_pFormatContext->streams[i]->duration; m_video_stream = i; } else if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { m_audio_stream = i; } else if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_DATA) { m_data_stream = i; } } ``` :::info - **`streams[i]`** est un **`AVStream*`** - **`streams[i]->codecpar `** est de type **`AVCodecParameters`** une structure contenant les parametres du codec associé au stream - **`streams[i]->codecpar->codec_type`** est de type **`AVMediaType`**, une `enum` indiquant le type de donnée encodée dans les paquets du stream. ![image](https://hackmd.io/_uploads/B1Js4r8-0.png) ::: Nous sauvegardons l'indice de chacun des streams dans le tableau **`streams[i]`** Pour le décodage des frame vidéo, il est necessaire d'allouer un **`AVCodecContext`** qui contiendra l'ensemble des information du codec en mémoire ```C // We save the propreties of the codec used by the video stream in an AVCodecParameters object m_codec_parameters = m_pFormatContext->streams[m_video_stream_index]->codecpar; // We find the codec used by the video stream in an AVCodec object m_codec = avcodec_find_decoder(m_codec_parameters->codec_id); // In order to decode the video stream, we need a codec context (AVCodecContext) m_codec_context = avcodec_alloc_context3(m_codec); // We fill those codec contexts with the respective codec parameters (method 'avcodec_parameters_to_context') avcodec_parameters_to_context(m_codec_context, m_codec_parameters); // Then, we open each one of these codecs using its corresponding codec context (method 'avcodec_open2') avcodec_open2(m_codec_context, m_codec, NULL); ``` :::info - **`m_codec_parameters->codec_id`** est de type **`AVCodecID`**, un identifiant de la syntaxe et de la sémantique utilisé par le streams d'octets bruts. ![image](https://hackmd.io/_uploads/HJ348HUZC.png) - **`avcodec_find_decoder()`** trouve un décodeur disponible et compatible avec l'ID de codec passé en parametre, puis le retourne sous la forme d'un **`AVCodec*`**. - **`avcodec_open2()`** initialise le **`AVCodecContext`** pour une utilisation du codec passé en parametre. ::: Les frames vidéo seront recues au sein d'une structure **`AVPacket`** que nous devons allouer. Des traitements spécifiques seront à appliquer en fonction du type de paquet reçu. ```C // First, we allocate an AVFrame paquet for the future current packet m_pPacket = av_packet_alloc(); while(av_read_frame(m_pFormatContext, m_pPacket) != AVERROR_EOF) { // ----- VIDEO PART ----- if(m_pPacket->stream_index == m_video_stream) { if(m_frame_decoder->DecodeFrameThroughOpenCV(m_pPacket, decodedFrame, framerate)) { { // Décodage du paquet vidéo decoded_packet = DecodedPacket(AVMEDIA_TYPE_VIDEO, m_pPacket->flags, m_pPacket->pts); m_buffer_mutex.lock(); m_buffer->push_back(decoded_packet); m_buffer_mutex.unlock(); } } } // ----- DATA PART ----- if(m_pPacket->stream_index == m_data_stream) { if(m_metadata_decoder->decodeMDPacket(m_pPacket, decodedMetadata)) { { // Décodage du paquet de données decoded_packet = DecodedPacket(AVMEDIA_TYPE_DATA, m_pPacket->flags, m_pPacket->pts); m_buffer_mutex.lock(); m_buffer->push_back(decoded_packet); m_buffer_mutex.unlock(); } } } av_packet_unref(m_pPacket); } // We do not receive any more frame : video ended ``` :::info - **`av_read_frame()`** stocke le prochain paquet d'un des streams dans l'`AVPaquet` passé par adresse. Cette fonction se contente de diviser en paquet ce qui est contenu dans les streams du fichier et ne garantie par que le paquet retourné contient des données valides. Lorsqu'il n'est plus utilisé, le paquet doit être libéré par un appel à **`av_packet_unref()`**. ::: ## Décodage des paquets vidéo Pour le flux vidéo, un **`AVPaquet`** contient de la donnée encodée, qu'il convient de décoder grâce au codec alloué précédement. Ce décodage des paquets vidéo se fait au travers de deux fonctions : - `avcodec_send_packet` qui envoie le `AVPaquet` non décodé au codec chargé en mémoire pour décodage - `avcodec_receive_frame` : qui recoit le paqué décodé au format `AVFrame` ```C // ALLOCATION // First, we allocate memory for the decoded YUV frame m_pFrame = av_frame_alloc(); if (!m_pFrame) { throw std::runtime_error("Couldn't allocate AVFrame"); } // Sending the AVPaquet for decoding avcodec_send_packet(m_codec_context, packet); // Receiving the decoded paquet response = avcodec_receive_frame(m_codec_context, m_pFrame); // Checking if an error occured on the returned value if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) { qDebug() << AVERROR(EAGAIN); qDebug() << AVERROR_EOF; qDebug() << AVERROR(EINVAL); return false; } ``` La **`AVFrame`** obtenue doit ensuite être convertie dans des formats de couleurs adéquats avant d'être affichée ### Sans OpenCV ```c // ALLOCATION // We allocate memory for the converted RGB frame m_pFrameRGB = av_frame_alloc(); if (!m_pFrameRGB) { throw std::runtime_error("Couldn't allocate AVFrame"); } // Setting up the sws scaler sws_getContext(m_codec_context->width, m_codec_context->height, m_codec_context->pix_fmt, m_codec_context->width, m_codec_context->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, // algorithm and options to use for rescaling NULL, // src filter NULL, // dst filter NULL // params ); [...] // Defining the RGB frame size and color format m_pFrameRGB->format = AV_PIX_FMT_RGB24; m_pFrameRGB->width = m_pFrame->width; m_pFrameRGB->height = m_pFrame->height; // Number of butes needed to store a AV_PIX_FMT_RGB24 frame // of the size given by the codec context int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, m_codec_context->width, m_codec_context->height, 1); // Allocating a 0 initialized buffer uint8_t * tmp_conversion_buffer = (uint8_t*)calloc(numBytes,sizeof(uint8_t)); // Filling the RGB Frame data and linesize with the tmp_buffer content (zeros) av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, tmp_conversion_buffer, AV_PIX_FMT_RGB24, m_codec_context->width, m_codec_context->height,1 ); // Getting RGB AVFrame from YUV AVFrame sws_scale(m_sws_scaler_context, (uint8_t const * const *)m_pFrame->data, m_pFrame->linesize, 0, // the number of the first row of the slice to process m_codec_context->height, // the number of rows in the slice m_pFrameRGB->data, m_pFrameRGB->linesize); // Converting m_pFrameRGB to QImage decodedFrame = QImage(m_pFrameRGB->data[0], m_codec_context->width, m_codec_context->height, QImage::Format_RGB888).copy(); free(tmp_conversion_buffer); // Freeing the temporary buffer ``` L'image est enfin convertie en `QImage` au format `QImage::Format_RGB888` via un constructeur prennant en entrée la bitmap `data[0]`. ![image](https://hackmd.io/_uploads/S1T3WI8Z0.png) :::info - `AV_PIX_FMT_RGB24` : Format de couleur 24bits 1 pixel = 3 octets, un pour chaque composante de couleur - Pour une frame : - `data[i]` : bitmap de la ième `plane` - `linesize[i]` : pas de la ième `plane `(taille d'une ligne) - Exemples : - Frame YUV 640x480 - `data[0]` : bitmap de la composante Y - `data[1]` : bitmap de la composante U - `data[2]` : bitmap de la composante V - `linesize[0]` = 640 - `linesize[1]` = 320 (640/2) - `linesize[2]` = 320 (640/2) - Frame RGB24 640x480 - `data[0]` : une seule bitmap - `linesize[0]` == 640 * 3 (width * nb_channels) ::: L'`AVFrame` renvoyée par le décodeur est au format **`AV_PIX_FMT_YUV420P`**. :::info - `420` represente le schéma de sous-echantillonage des couches `U` et `V` de l'image - `p` represente la manière dont sont structurées les valeurs des composantes `Y`, `U` et `V`. Ici, `p` signifie **Planar**, ce qui signifie que les valeures des composantes sont groupées ensemble (ex : `Y1Y2…YnU1U2…Un/2V1V2…Vn/2`) Sources : - [Packed, Planar, and Semi-planar](https://gist.github.com/Jim-Bar/3cbba684a71d1a9d468a6711a6eddbeb#packed-planar-and-semi-planar) - [Sampling systems and ratios](https://en.wikipedia.org/wiki/Chroma_subsampling#Sampling_systems_and_ratios) ::: Il est nécessaire de la convertir au format RGB afin de pouvoir la rendre à l'écran. Cette convertion est faite au travers de la méthode **`sws_scale()`** du **`sws_scaler`** :::info **`sws_scale()`** : Convertit et met à l'échelle la ***slice*** définie dans l'image source au format de couleurs et à la taille définis dans le contexte Une ***slice*** est une **suite de ligne consécutives dans une image**. ::: ### Via OpenCV ```c // Creating the converter Frame as a cv::Mat cv::Mat image(m_pFrame->height, m_pFrame->width, CV_8UC3); int cvLinesizes[1]; // Array containing the linesizes of the planes (only one plane here) cvLinesizes[0] = image.step1(); // Setting up the sws scaler SwsContext* conversion = sws_getContext(m_pFrame->width, m_pFrame->height, m_pFrame->format, m_pFrameRGB->width, m_pFrameRGB->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, // algorithm and options to use for rescaling NULL, // src filter NULL, // dst filter NULL // params ); [...] // Getting RGB cv::Mat from YUV AVFrame sws_scale(conversion, m_pFrame->data, m_pFrame->linesize, 0, // the number of the first row of the slice to process m_pFrame->height, // the number of rows in the slice &image.data, cvLinesizes); sws_freeContext(conversion); ``` :::info - `CV_8UC3` : matrice 3 channels d'`unsigned int` sur 8 bits ::: L'image décodée est ensuite bufferisée, puis rendue. Pour la méthode d'affichage utilisant les matrices OpenCV comme frames décodées, l'image est tout d'abord redimentionnée : ```c // params // size : size to resize the decoded frame to cv::Mat* ret; if(keep_ratio) { // The image source size ratio needs to be preserved on the width int ratio_height = size.width * mat.rows/mat.cols; if(ratio_height > size.height) size.width = size.height * mat.cols / mat.rows; else size.height = ratio_height; } if( size.width >= mat.size().width || size.height >= mat.size().height ) { if(!do_not_enlarge) { resize(mat, dst, size, 0, 0, cv::INTER_CUBIC); ret = &dst; } else { ret = &mat; } } else { resize(mat, dst, size, 0, 0, cv::INTER_CUBIC); ret = &dst; } return *ret; ``` Puis convertie en `QImage` : ```c QImage img= QImage((uchar*) mat.data, mat.cols, mat.rows, mat.step, QImage::Format_RGB888).copy(); ``` Et enfin rendue à l'écran via la `QGraphicView` et une `QPixmap` : ```c // params // ResizeImg : The resized QImage m_videoView->setSceneRect(0,0, ResizeImg.size().width(), ResizeImg.size().height()); m_videoScene->removeItem(m_frameItem); delete m_frameItem; m_frameItem = nullptr; m_frameItem = m_videoScene->addPixmap(QPixmap::fromImage(ResizeImg)); ``` ## Décodage des paquets STANAG reçus Pour le stockage des paquets de métadonnée décodés, nous utiliserons la classe `MDPacket` ```C class MDPacket { private: KLVHeaderData m_header_data; std::stack<KLVValue> m_values; public: MDPacket(); ~MDPacket(); void setKLVHeaderData(KLVHeaderData header_data); const KLVHeaderData &getKLVHeaderData(); std::stack<KLVValue> getKLVValues(); // returns a copy of the KLVValues stack std::stack<KLVValue>* getP_KLVValues(); void addKLVValue(KLVValue klv_value); }; ``` Les `AVPaquet` recus via la fonction `av_read_frame()` ne sont pas décodé via un decodeur FFMPEG. Nous parsons et intepretons le contenu en nous basant sur les données brutes qu'ils contiennent. le décodage complet subit les traitement suivants : ```C bool MetadataDecoder::decodeMDPacket(AVPacket *raw_packet, MDPacket& decoded_packet) { KLVHeaderData header_data{}; result = result && m_header_data_extractor->extractHeaderData(raw_packet, header_data); decoded_packet.setKLVHeaderData(header_data); result = result && m_klv_values_extractor->extractValues(raw_packet, decoded_packet); return result; } ``` Un `AVPaquet` correspond à un paquet de donées STANAG ![image](https://hackmd.io/_uploads/H11b0D8bR.png) Premièrement, nous extrayons l'entête (`Universal Key` + `Length`) de ce paquet via la fonction **`extractHeaderData()`**. ```c bool HeaderDataExtractor::extractHeaderData(AVPacket *raw_packet, KLVHeaderData& decoded_header_data) { int ulkey_size; ULKey ulkey; int raw_length_size = 0; uint32_t raw_data_length; // the number of bytes coding the data field ulkey = extractULKey(raw_packet, ulkey_size); raw_data_length = extractBERLength(raw_packet, ulkey_size, raw_length_size); decoded_header_data.setUlKey(ulkey); decoded_header_data.setRawKeySize(ulkey_size); decoded_header_data.setRawLengthSize(raw_length_size); decoded_header_data.setRawDataLength(raw_data_length); return true; } ``` Une première fonction `ULKey extractULKey(AVPacket *raw_packet, int& ulkey_size)` renvoie le type de clé dans l'en-tête. Deux types de clées sont pour l'heure identifiable dans `MVA` : `UAS` et `VMTI` ![image](https://hackmd.io/_uploads/BktYCvUZ0.png) La longueur de la payload est ensuite parsée et interpretée grâce à la fonction `extractBERLength()`. Les paquet STANAG contenus dans les fichier `MPEG-TS` issus du VIPER utilisent la ***notation BER*** pour encoder la longueur de leur payload : ![image](https://hackmd.io/_uploads/BkINJu8Z0.png) Le champs Length est classiquement encodé sur un octet, dont le 1er joue le rôle de flag. La taille maximale encodable est donc 127 (127 octets de payload). Pour les payload plus grands, la notation BER passe le bit de flag à 1. Les bits suivant de ce 1er octet indiquent alors le nombre d'octets suivants encodant la taille du payload Ainsi la suite d'octets `10000001 11001001` indique : - 1er bit à 1 = on utilise la notation BER - les 7 bits suivants indiquent 1 = L'octet suivant encode la taille du payload - le deuxième octet encode 201 : Le payload commence à l'octet suivant et est long de 201 octets Un fois décodé, l'entête du paquet est retourné en un type `KLVHeaderData` : ```C class KLVHeaderData private: ULKey m_ul_key; int m_raw_key_size; int m_raw_length_size; uint32_t m_raw_data_length; ``` La fonction `extractValues` est ensuite appelée, elle est chargé de décoder l'ensemble des sous-paquets de données KLV contenu dans le payload ```c bool KLVValuesExtractor::extractValues(AVPacket *raw_packet, MDPacket& decoded_packet) { uint8_t* raw_data = raw_packet->data; int _read_ptr; int key, len; _read_ptr = decoded_packet.getKLVHeaderData().getFullHeaderSize(); while(_read_ptr < decoded_packet.getKLVHeaderData().getFullPacketSize()) { key = raw_data[_read_ptr++]; len = raw_data[_read_ptr++]; if(len > 0) { decoded_packet.addKLVValue(decodeValue(key, len, _read_ptr, raw_data)); } _read_ptr += len; } return true; } ``` C'est la fonction `decodeValue()` qui est chargée de décoder, independament les paquets KLV du payload : ```c KLVValue KLVValuesExtractor::decodeValue(int key, int len, int ptr_start, uint8_t* raw_data) { bool is_displayable = false; // Checkinf if the KLV can be displayed in a KLV Widget // (i.e. checking if it is in the KLVUsedTagsValues array) auto found_tag = std::find(std::begin(KLVUsedTagsValues), std::end(KLVUsedTagsValues), KLVTag(key)); if(found_tag != std::end(KLVUsedTagsValues)) { // The KLV Tag has been found in KLVUsedTagsValues : it is displayable is_displayable = true; } std::vector<uint8_t> raw_data_vector = uint8_to_std_vector(raw_data+ptr_start, len); switch (key) { case Checksum: { return KLVValue(Checksum, QString::number(GetUShortFromData(&raw_data[ptr_start])), len, raw_data_vector, is_displayable); } break; [...] ``` Un objet de type KLVValue est ensuite renvoyé : ```c class KLVValue { private: KLVTag m_tag; QString m_decoded_value; int m_raw_data_len; std::vector<uint8_t> m_raw_data; bool m_is_displayable; bool m_is_knowed; bool m_is_decoded; ``` Enfin, les paquets STANAG sont bufferisés, puis rendus à l'écran par un appel à la fonction `renderMDPacket` : ```c void TimeBasedRenderer::renderMDPacket(MDPacket metadata_packet) { std::stack<int> unknowed_klvtags{}; std::stack<KLVValue> klv_values = metadata_packet.getKLVValues(); // Getting a stack of the KLVValues contained in the MDpaquet while (!klv_values.empty()) { KLVValue current_klv_value = klv_values.top(); if(current_klv_value.isDisplayable()) { // The KLV is displayable throught QT Widgets KLVWidget* klv_widget = getKLVWidgetFromTag(current_klv_value); if (!current_klv_value.isDecoded()){ // The KLV value is not decoded (a switch is missing in KLVValuesExtractor::decodeValue) klv_widget->setValue("Not Decoded !"); } else { // The KLV value is decoded klv_widget->setValue(current_klv_value.getDecodedValue()); } } else if(current_klv_value.isKnowed()) { // The KLV is not displayable but is known if(current_klv_value.isDecoded()) { // The KLV is decoded, adding it to the line_edit, top part klv_known_string += "<p>"; klv_known_string += "<strong>♦ " + KLVTagsNames[current_klv_value.getKLVTag()-1] + "</strong>"; klv_known_string += ":&nbsp;"; klv_known_string += current_klv_value.getDecodedValue(); } else { // The KLV is not decoded, adding it to the line_edit, top part klv_known_string += "<p>"; klv_known_string += "<strong>♦ " + KLVTagsNames[current_klv_value.getKLVTag()-1] + "</strong>"; klv_known_string += ":&nbsp;"; klv_known_string += "Not Decoded !"; } klv_known_string += "</p>"; } else { // The KLV is not known (and not decoded), adding it to the line_edit, bellow part klv_unknown_string += QString::number(unknowed_klvtags.top()) + "; "; } klv_values.pop(); } klv_unknown_string += "</p>"; m_klv_text_edit->setText(""); m_klv_text_edit->insertHtml(klv_known_string + klv_unknown_string); } ``` # Guides utiles ## [An ffmpeg and SDL Tutorial - dranger.com](http://www.dranger.com/ffmpeg/ffmpeg.html) - **Déprecié** : A jour à la date de Fevrier 2015 - "Mise à jour" du programme [`ffplay.c`](https://github.com/FFmpeg/FFmpeg/blob/master/fftools/ffplay.c) de [Martin Böhme](http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html) - Codé en **C** - Utilise **SDL** pour l'affichage graphique ## [GitHub - fmpeg-video-player](https://github.com/rambodrahmani/ffmpeg-video-player/blob/master/tutorial07/tutorial07.c) - Dernier commit : **8 Novembre 2023** - Mise à jour du tutoriel de [dranger.com](http://www.dranger.com/ffmpeg/ffmpeg.html) (utilise la dernière version de l'API) - Codé en **C** - Utilise **SDL** pour l'affichage graphique # Implementation existantes ## [GitHub - YetAnotherFFmpegPlayer](https://github.com/antzol/YetAnotherFFmpegPlayer) - Dernier commit : **20 Decembre 2022** - Developpement d'un Lecteur Video basé sur **Qt** - Fonctionne avec les streams **MPEG2-TS** - Lit depuis un fichier et depuis un stream UDP ## [GitHub - fanplayer](https://github.com/rockcarry/fanplayer) - Dernier commit : **11 Octobre 2023** - Developpement d'un Lecteur Video crossplatforme pour **Android** et **Windows**. - Codé en **C uniquement** (utilise gdi & direct3d sur win32) Définition des notions de **codecs vidéo** (*Video Codecs*) et de **wrapper vidéo** (*Video Container Formats*) : [Understanding of Video Codec and Video Container Format](https://ceciliadigiarty.medium.com/understanding-of-video-codec-and-video-container-format-16c6bd353c9d) Distinction between I frames, P frames and B frames [Background of MPEG](https://cseweb.ucsd.edu/classes/su03/cse126/lecture6,7.htm#:~:text=P%20frames%20are%20predicted%20frames,next%20I%20or%20P%20frames.) [![cb1c1cf1a803fae72e42d3a8310e20a2.png](:/fe56e84a58a442d387555ebc04d18b41)](https://nonuruzun.medium.com/i-p-b-frames-b6782bcd1460#:~:text=P%E2%80%91frames%20can%20use%20data,highest%20amount%20of%20data%20compression.)