[TOC]
# Principes encodage des fichiers vidéos
Une video = une succession d'images
24 FPS = 24 images rendues par seconde

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**.

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

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.

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 :

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.

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 :

# 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:

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.

:::
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.

- **`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]`.

:::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

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`

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 :

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 += ": ";
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 += ": ";
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.)
[](https://nonuruzun.medium.com/i-p-b-frames-b6782bcd1460#:~:text=P%E2%80%91frames%20can%20use%20data,highest%20amount%20of%20data%20compression.)