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