Création d`un codeur vidéo dédié au streaming

Transcription

Création d`un codeur vidéo dédié au streaming
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
Création d’un codeur
vidéo dédié au
streaming
Olivier Joly
Ahmed Agarbi
Vihn-Li Boun
Création d’un codeur vidéo dédié au streaming
1
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
INTRODUCTION
Le « streaming » a pour objectif de garantir une lecture de flux en temps réel.
On distingue trois parties essentielles dans une chaîne de transmission utilisant le streaming :
•
La partie dédiée au codage permettant la compression et le changement du format de
l’entrée en un format plus adaptée à la diffusion.
•
La partie axée sur la diffusion s’appuyant sur un serveur qui reçoit un flux de la part de
l’encodeur, le découpe en paquets et le diffuse vers le client l’ayant demandé.
•
La partie « lecture » représentée par le client final (ici nous utilisons le Windows Media
Player) qui utilise son lecteur pour décoder le flux en provenance du serveur.
A travers ce projet, nous avons implanté un serveur vidéo capable de réaliser la diffusion sur Internet
d’une séquence vidéo prise au laboratoire avec une web-cam.
Nous avons découvert le streaming à travers la réalisation de la partie encodage puisqu’elle est la
partie la plus déterminante dans tout le processus. On a utilisé pour cela à la fois la librairie Windows
Media SDK 7.1 et les langages C/C++. La web-cam communique avec l’encodeur à travers une plate
1
forme dédiée au traitement d’image : Attribut
1
Attribut est un logiciel développé au sein de l’Equipe Signal et Image par Marc Donias.
Création d’un codeur vidéo dédié au streaming
2
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
ATTRIBUT
1- Traitement de la luminance
On désire ajouter une option « Luminance » au menu Traitement dans le but d’obtenir la luminance
du flux vidéo. Pour calculer sa valeur, on prend la moyenne des trois composantes (rouge, verte et
bleu).
Pour chaque option ajoutée au logiciel Attribut, il faut à chaque fois définir trois fonctions du type
fonction_init(), fonction_on(), fonction_off(). Ces fonctions sont appelées lorsque l’utilisateur clique sur
le menu correspondant dans l’interface d’Attribut. fonction_init() permet de configurer le traitement,
fonction_on() exécute le traitement, fonction_off() termine le traitement.
Dans un premier temps il faut ajouter les menus dans l’interface. Les fichiers attLibraryMenu.c et
attLibraryRun.c sont à modifiés de la façon suivante :
1) attLibraryMenu.c
Le sous menu Luminance est rajouté dans le menu Capture.
ATT_DESC_MENU menu4 =
{
BEGIN_MENU
"Luminance",
IDM_LUMINANCE,
END_MENU
};
attLibraryAddMenu("MENU_CAPTURE,Luminance", menu4);
2) attLibraryRun.c
En cliquant dans le sous-menu Luminance, les fonctions Luminance_Init(), puis les fonctions de
traitement sont exécutées. La fonction attDataGetId() nous permet de récupérer l’identificateur
associé au flux. La fonction attDataFluxStatr() permet d’appliquer sur chaque modification du flux
la fonction Luminance_On(), et une fois le flux terminé, elle appelle la fonction Luminance_Off().
void attLibraryRun_IDM_LUMINANCE(long id_task, long choice)
{
long id;
id = attDataFluxGetId(id_task);
attDataFluxSetFormatIn(id, TYPE_RGBA);
attDataFluxSetFormatOut(id, TYPE_RGBA);
Création d’un codeur vidéo dédié au streaming
3
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
Luminance_Init(id);
attDataFluxStart(id, Luminance_On, Luminance_Off);
}
void attLibraryRun(long id_task, long choice, long mode)
{
switch (choice)
{
[...]
case IDM_LUMINANCE:
attLibraryRun_IDM_LUMINANCE(id_task, choice); break;
[...]
}
}
Dans un second temps, il faut écrire les fonctions _Init(), _On(), _Off() dans le fichier
code_capture.cpp
La structure CAPTURE_USER_DATA est initialisée en lui allouant la taille nécessaire et en initialisant
son compteur d’images à 0. Ensuite le flux est positionné via son id sur la structure nouvellement
créée.
void Luminance_Init(long id)
{
CAPTURE_USER_DATA * capture_user_data;
char name[200] = "composante_rouge";
attMsg("Début du traitement: extraction de la
l'image");
attMsg("***********************************");
composante
rouge
de
capture_user_data=(CAPTURE_USER_DATA*)calloc(1, sizeof(CAPTURE_USER_DATA));
capture_user_data->Compteur=0;
attDataFluxSetUserData(id, capture_user_data);
}
Les flux de sortie et d’entrée sont récupérés, ainsi que les paramètres concernant l’image (largeur,
hauteur). Le flux capturé par Attribut est assigné à une structure de type USER_DATA. La structure
CAPTURE_USER_DATA lié au flux par son id est mise à jour, en incrémentant le compteur image.
Ensuite sur chaque pixel la composante rouge, verte et bleue sont récupérées dans data_in et la
structure data_out est mise à jour. La luminance est obtenue en effectuant la moyenne de chaque
composante d’entrée, cette moyenne est ensuite écrite dans les composantes de sortie. La
ème
composante (alpha) n’est pas utilisée, mais il en est quand même tenu compte en écrivant
4
data_in++ et data_out++ .
void Luminance_On(long id)
{
long nb = 256, add, w, h;
CAPTURE_USER_DATA * capture_user_data;
char name[200] = "composante_rouge";
BYTE * data_in, * data_out, r, g, b;
void * user_data;
data_in = (BYTE *) attDataFluxGetDataIn(id);
data_out = (BYTE *) attDataFluxGetDataOut(id);
if ( data_in == NULL || data_out == NULL ) return;
w = attDataFluxGetWidth(id);
h = attDataFluxGetHeight(id);
Création d’un codeur vidéo dédié au streaming
4
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
user_data = attDataFluxGetUserData(id);
if ( user_data == NULL ) return;
capture_user_data = (CAPTURE_USER_DATA *)user_data;
capture_user_data->Compteur++;
for (add=0; add<w*h; add++)
{
r = *(data_in++);
g = *(data_in++);
b = *(data_in++);
data_in++;
*(data_out++) = (r+g+b)/3;
*(data_out++) = (r+g+b)/3;
*(data_out++) = (r+g+b)/3;
data_out++;
}
}
La fonction Luminance_Off() libère ensuite la structure USER_DATA.
void Luminance_Off(long id)
{
void * user_data = (BYTE *) attDataFluxGetUserData(id);
CAPTURE_USER_DATA * capture_user_data = (CAPTURE_USER_DATA *)user_data;
attMsg("***********************");
attMsg("Fin du traitement");
attMsgFormat("%d images traitées",capture_user_data->Compteur);
if ( user_data != NULL ) free(user_data);
}
Miroir Vertical
Il faut maintenant créé une fonction qui permette de retourner verticalement l’image. En effet la
bibliothèque Windows Media gère des images uniquement au format dib (ou bmp), c’est à dire
constituée de lignes verticalement inversées.
Les fonctions Miror_Init(), Miror_On() et Miror_Off() sont présentées ci-dessous
void Miror_Init(long id)
{
CAPTURE_USER_DATA * capture_user_data;
char name[200] = "composante_rouge";
attMsg("Début du traitement: extraction de la composante rouge de
l'image");
attMsg("***********************************");
capture_user_data=(CAPTURE_USER_DATA*)calloc(1, sizeof(CAPTURE_USER_DATA));
capture_user_data->Compteur=0;
attDataFluxSetUserData(id, capture_user_data);
}
void Miror_On(long id)
{
long nb = 256, add, add2, w, h;
CAPTURE_USER_DATA * capture_user_data;
char name[200] = "composante_rouge";
BYTE * data_in, * data_out, r, g, b;
void * user_data;
data_in = (BYTE *) attDataFluxGetDataIn(id);
Création d’un codeur vidéo dédié au streaming
5
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
data_out = (BYTE *) attDataFluxGetDataOut(id);
if ( data_in == NULL || data_out == NULL ) return;
w = attDataFluxGetWidth(id);
h = attDataFluxGetHeight(id);
user_data = attDataFluxGetUserData(id);
if ( user_data == NULL ) return;
capture_user_data = (CAPTURE_USER_DATA *)user_data;
capture_user_data->Compteur++;
for (add2=0; add2<h; add2++)
{
for (add=0; add<w; add++)
{
r = data_in[(h-add2-1)*w*4+add*4];
g = data_in[(h-add2-1)*w*4+add*4+1];
b = data_in[(h-add2-1)*w*4+add*4+2];
*(data_out++) = r;
*(data_out++) = g;
*(data_out++) = b;
data_out++;
}
}
}
void Miror_Off(long id)
{
void * user_data = (BYTE *) attDataFluxGetUserData(id);
CAPTURE_USER_DATA*capture_user_data = (CAPTURE_USER_DATA *)user_data;
attMsg("***********************");
attMsg("Fin du traitement");
attMsgFormat("%d images traitées",capture_user_data->Compteur);
if ( user_data != NULL ) free(user_data);
}
Création d’un codeur vidéo dédié au streaming
6
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
ENCODEUR
Structure de l’encodeur
Pour l’implantation de l’encodeur, nous utilisons la classe IWMWriter qui permet de produire un fichier
au format Windows media. Cependant, il faut d’abord chercher les données à partir de la source
(buffer fournit par Attribut contenant une image). Puis il faut passer ces données échantillon par
échantillon (image par image) à l’objet de production qui utilise un certain nombre de codecs pour
compresser les données.
Cette opération est pilotée par un profil qui contient les informations concernant le nombre de streams
qui seront produits dans le fichier de sortie, le type de données contenues dans chaque stream et
d’autres propriétés (débit binaire, la taille de l’image de sortie, etc..)
Programmation de l’encodeur
Avant de pouvoir implanter l’encodeur, il faut d’abord ajouter une option « Streaming- Sortie fichier »
dans le menu Traitement (de même que pour les options de Luminance et de Miroir). Ensuite, et
puisque le processus se décompose en trois étapes (initialisation, encodage et terminaison), il faut
écrire une fonction pour chaque partie.
La première fonction à écrire doit initialiser l’interface COM, configurer l’objet encodeur, initialiser
l’interface de la structure qui regroupe les variables relatives au processus, et enfin initialiser le
processus d ‘encodage.
La deuxième doit réaliser le passage des données provenant du buffer fourni par Attribut vers la
fonction de l’objet ‘Writer’.
La troisième doit terminer le processus d’écriture, libérer toutes les zones mémoire qui aient été
allouées et libérer l’interface COM.
Initialisation
Streaming_Init()
streaming_user_data=(STREAMING_USER_DATA*)
calloc(1,sizeof(STREAMING_USER_DATA));
attDataFluxSetUserData(id, streaming_user_data);
// identificateur du flux pour le streaming
streaming_user_data->Compteur=0;
// initialisation du compteur de nombre d'images
w = attDataFluxGetWidth(id); // largeur de l'image
h = attDataFluxGetHeight(id); // la hauteur de l'image
CoInitialize(NULL); //initialisation de l'interface COM
WMCreateWriter(NULL,&(streaming_user_data->Ecriveur));
// Création d’un WMWriter sur l'objet streaming
Création d’un codeur vidéo dédié au streaming
7
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
(streaming_user_data->Ecriveur)->SetProfileByID(WMProfile_V70_256Video);
// Choix du profile pour l'émission
MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,fichierLocal,strlen(fichierLocal)
+1,fichierLoc,sizeof(fichierLoc));
//on enregistre le nom du fichier au bon format pour le Writer
(streaming_user_data->Ecriveur)->SetOutputFilename(fichierLoc);
// Spécification de la sortie
(streaming_user_data->Ecriveur)->GetInputCount(&cInputs);
// nombre de flux du fichier source
streaming_user_data->Vitesse=1000000;
for(i=0;i<cInputs;i++) // Pour chaque flux
{
(streaming_user_data->Ecriveur)->GetInputProps(i,&Input);
// propriétés du flux
Input->GetMediaType(NULL,&bType);
// Taille du type de media
Type=(WM_MEDIA_TYPE*) new BYTE[bType];
Input->GetMediaType(Type,&bType);// Type de media
if(Type->majortype==WMMEDIATYPE_Video) // Configuration pour le streaming
{
streaming_user_data->newInput=i;
// Numéro du flux correspondant à la vidéo
// Configure la nature du streaming
Type->subtype=WMMEDIASUBTYPE_RGB24;
Type->bFixedSizeSamples=TRUE;
Type->bTemporalCompression=FALSE;
Type->lSampleSize=w*h*3;
Type->formattype=WMFORMAT_VideoInfo;
Type->pUnk=NULL;
// Info propre au Info Video Header
WMlocal=(WMVIDEOINFOHEADER*)(Type->pbFormat);
WMlocal->rcSource.left=0;
WMlocal->rcSource.top=0;
WMlocal->rcSource.right=w;
WMlocal->rcSource.bottom=h;
WMlocal->rcTarget.left=0;
WMlocal->rcTarget.top=0;
WMlocal->rcTarget.right=w;
WMlocal->rcTarget.bottom=h;
WMlocal->AvgTimePerFrame=streaming_user_data->Vitesse;
WMlocal->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
WMlocal->bmiHeader.biWidth=w;
WMlocal->bmiHeader.biHeight=h;
WMlocal->bmiHeader.biPlanes=1;
WMlocal->bmiHeader.biBitCount=24;
WMlocal->bmiHeader.biCompression=BI_RGB;
WMlocal->bmiHeader.biSizeImage=3*w*h;
WMlocal->bmiHeader.biClrUsed=0;
Création d’un codeur vidéo dédié au streaming
8
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
// Enregistrement ce Type dans les propriétés
Input->SetMediaType(Type);
}
(streaming_user_data->Ecriveur)->SetInputProps(i,Input);
// Les propriétés sont remises dans le flux
}
(streaming_user_data->Ecriveur)->BeginWriting();
// écriture du writer
}
Encodage
Streaming_On()
data_in = (BYTE *) attDataFluxGetDataIn(id);
// Initialisation le flux d'entrée
data_out = (BYTE *) attDataFluxGetDataOut(id);
`
// Positionnement le flux de sortie
if ( data_in == NULL || data_out == NULL ) return;
w = attDataFluxGetWidth(id);
h = attDataFluxGetHeight(id);
streaming_user_data=(STREAMING_USER_DATA*)attDataFluxGetUserData(id);
// Objet streaming associé à l'id
streaming_user_data->Compteur++; // Incrémente le nombre d'image
SampleSize=w*3*h;
//Initialisation de la taille du sample
SampleTime=streaming_user_data->Vitesse*streaming_user_data->Compteur;
(streaming_user_data->Ecriveur)->AllocateSample(SampleSize,&Sample);
Sample->GetBuffer(&data_out);
//on récupère l'adresse du buffer dans data_out
//on récupère les données de l'image.
// le format est différent, il faut faire un miroir
vertical sur l'image récupérée
// et aussi inverser la position du RGB en BGR.
for (add2=0; add2<h; add2++)
{
for (add=0; add<w; add++)
{
r = data_in[(h-add2-1)*w*4+add*4];
g = data_in[(h-add2-1)*w*4+add*4+1];
b = data_in[(h-add2-1)*w*4+add*4+2];
*(data_out++) = b;
*(data_out++) = g;
*(data_out++) = r;
}
}
//on écrit cette image dans le flux associé à la vidéo
de l'init)
Création d’un codeur vidéo dédié au streaming
(newInput
9
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
streaming_user_data->Ecriveur->
WriteSample(streaming_user_data->
newInput,SampleTime,0,Sample);
Sample->Release();// libère le buffer d'échantillonnage
streaming_user_data->Ecriveur->Flush();
// libère le buffer du writer
attMsgFormat("%d images traitées",streaming_user_data->Compteur);
}
Terminaison
Streaming_Off()
{
STREAMING_USER_DATA * streaming_user_data;
// récupère la structure streaming selon l'id
streaming_user_data=(STREAMING_USER_DATA*)attDataFluxGetUserData(id);
streaming_user_data->Ecriveur->Flush();
// libère la mémoire
(streaming_user_data->Ecriveur)->EndWriting();
// ferme l'objet. le fichier est créé pour le streaming
CoUninitialize(); //on libère l'interface COM
}
Diffusion « live » sur un port réseau
Pour redirigé le flux vers un port réseau, on doit tout d’abord instancier une interface de type
IWMWriterAdvanced. Le code suivant s’effectue dans la partie d’initialisation :
(streaming_user_data->Ecriveur)->
QueryInterface(IID_IWMWriterAdvanced,&EAdTemp);
Ensuite il faut créer une structure du port réseau et la relier à l’objet Writer.
WMCreateWriterNetworkSink(&(streaming_user_data->Sink));
streaming_user_data->EcriveurAd=(IWMWriterAdvanced*)(EAdTemp);
(streaming_user_data->EcriveurAd)->AddSink((streaming_user_data->Sink));
Il faut configurer le numéro du port et l’ouvrir.
port=8080;
(streaming_user_data->Sink)->Open(&port);
Pour la partie Streaming_Off(), il ne faut pas oublier de fermer ce port.
(streaming_user_data->Sink)->Close();
On obtient la structure STREAMING_USER_DATA suivante :
typedef struct _STREAMING_USER_DATA
{
IWMWriter* Ecriveur;
IWMWriterAdvanced* EcriveurAd; //pour la diffsuion sur le reseau
DWORD newInput; //enregistre la position du flux vidéo
Création d’un codeur vidéo dédié au streaming
10
ENSEIRB – Ingénierie des Systèmes Numériques de Communications
DWORD Compteur;
// Nombre d'images
int nbFlush;
long Vitesse;
IWMWriterNetworkSink* Sink; //structure reseau pour la diffusion
} STREAMING_USER_DATA;
Création d’un codeur vidéo dédié au streaming
11

Documents pareils