Publi

Generando fotogramas de vídeo en C con frei0r (y MLT) [Parte I – Introducción]

frei0r es una API Sencilla para crear efectos de vídeo capaz de generar vídeo, filtrarlo y generar transiciones entre dos o tres fuentes de vídeo. Lo realmente interesante de esta API es que la utilizan otras APIS más grandes para generar parte de sus efectos (como FFMPEG (hay que compilarlo con soporte frei0r), MLT, GStreamer, etc) y es usado además por aplicaciones de vídeo com Lives, Open Movie Editor y por supuesto, está presente en aplicaciones que utilizan MLT como KDEnlive, OpenShot o ShotCut.

Bueno, dicho esto nos podemos hacer una idea de lo que vamos a construir en este post y posts venideros, plugins para frei0r que vamos a poder utilizar en otras aplicaciones para generar y filtrar vídeo, crear nuestras propias transiciones, etc.

Podemos complicarlo tanto como nos de nuestra imaginación y podremos crear algoritmos muy complicados, incluso utilizando otras APIs como openCV, filtrar aprovechando aceleración OpenGL, multithread, varias pasadas, etc e incluso estos filtros y efectos se pueden configurar pasando parámetros a nuestro plugin.

Para empezar tenemos que instalar las cabeceras, vamos el archivo frei0r.h ; para ello, en Ubuntu y derivados podemos hacerlo con:

$ sudo apt-get install frei0r-plugins-dev

Ahora necesitamos es un esqueleto para nuestro plugin con las funciones básicas que debe tener (todas vienen descritas en frei0r.h):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdlib.h>
#include <stdio.h>
#include <frei0r.h>

int f0r_init()
{
  return 1;
}

void f0r_deinit()
{
}

void f0r_get_plugin_info(f0r_plugin_info_t* info)
{

}

f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
{
  return NULL;
}

void f0r_destruct(f0r_instance_t instance)
{
}

void f0r_get_param_info(f0r_param_info_t* info, int param_index)
{
}

void f0r_get_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{
}

void f0r_set_param_value(f0r_instance_t instance, f0r_param_t parm, int param_index)
{
}

void f0r_update(f0r_instance_t instance,
        double time, const uint32_t* inframe, uint32_t* outframe)
{
}

En este caso, hay algo que podemos personalizar, es nuestra instancia (f0r_instance_t), en realidad es un puntero a void, por lo que toda la información que requiera nuestro plugin para funcionar debemos meterla ahí. Esto pueden ser argumentos (dados por el usuario), o el tamaño del vídeo con el que estamos trabajando.
Además, debemos completar este código un poco más, introduciendo información sobre el plugin. Para todo esto, vamos a ampliar nuestra plantilla básica un poco:

config.h

1
2
3
4
5
6
7
8
9
10
11
/* Nombre del plugin */
#define FREI0R_PLUGIN_NAME "Plugin"
/* Nombre del autor del plugin */
#define FREI0R_PLUGIN_AUTHOR "Yo"
/* Versión del plugin */
#define FREI0R_PLUGIN_VERSION_MA 0
#define FREI0R_PLUGIN_VERSION_MI 1
/* Descripción del plugin */
#define FREI0R_PLUGIN_DESCRIPTION "Description"

#endif /* _CONFIG_H */

frei0r_utils.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* @(#)frei0r_utils.h
 */


#ifndef _FREI0R_UTILS_H
#define _FREI0R_UTILS_H 1

#include <frei0r.h>

typedef struct
{
  int width;
  int height;
} videosize_t;

void f0ut_set_plugin_info(f0r_plugin_info_t* info, char* name, char* author, char* description,
              int plugin_type, int color_model, int major_version,
              int minor_version, int num_params);

void f0ut_set_videosize(videosize_t* vsz, int width, int height);

#endif /* _FREI0R_UTILS_H */

frei0r_utils.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
*************************************************************
* @file frei0r_utils.c
* @brief Tools to make frei0r plugins easier
*
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @version
* @date 09 feb 2013
*
*
*************************************************************/


#include "frei0r_utils.h"

void f0ut_set_plugin_info(f0r_plugin_info_t* info, char* name, char* author, char* description,
              int plugin_type, int color_model, int major_version,
              int minor_version, int num_params)
{
  info->name=name;
  info->author=author;
  info->plugin_type=plugin_type;
  info->color_model=color_model;
  info->frei0r_version=FREI0R_MAJOR_VERSION;
  info->major_version=major_version;
  info->minor_version=minor_version;
  info->num_params=num_params;
  info->explanation=description;
}

void f0ut_set_videosize(videosize_t* vsz, int width, int height)
{
  vsz->width=width;
  vsz->height=height;
}

el nombre de tu plugin.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdlib.h>
#include <stdio.h>
#include "config.h"
#include <frei0r.h>
#include "frei0r_utils.h"

typedef struct
{
  videosize_t size;
} inst_t;

int f0r_init()
{
  return 1;
}

void f0r_deinit()
{
}

void f0r_get_plugin_info(f0r_plugin_info_t* info)
{
  f0ut_set_plugin_info(info, FREI0R_PLUGIN_NAME,
               FREI0R_PLUGIN_AUTHOR,
               FREI0R_PLUGIN_DESCRIPTION,
               F0R_PLUGIN_TYPE_SOURCE,
               F0R_COLOR_MODEL_RGBA8888,
               FREI0R_PLUGIN_VERSION_MA,
               FREI0R_PLUGIN_VERSION_MI,
               0);
}

f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
{
  inst_t *inst;
  inst=malloc(sizeof(inst_t));

  /* Guardamos las dimesiones del vídeo */
  f0ut_set_videosize(&inst->size, width, height);

  return (f0r_instance_t)inst;
}

void f0r_destruct(f0r_instance_t instance)
{
}

void f0r_get_param_info(f0r_param_info_t* info, int param_index)
{
}

void f0r_get_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{
}

void f0r_set_param_value(f0r_instance_t instance, f0r_param_t parm, int param_index)
{
}

void f0r_update(f0r_instance_t instance,
        double time, const uint32_t* inframe, uint32_t* outframe)
{
}

Es importante crear un registro de nuestra instancia, aunque sólo sea para almacenar las dimensiones del vídeo, que sólo nos las van a dar una vez en f0r_construct(), para eso creamos inst_t. También hemos almacenado la información base de nuestro plugin en config.h.

Si miramos un poco la documentación veremos los posibles tipos de plugin (SOURCE, FILTER, MIXER2, MIXER3), aunque por ahora sólo veremos SOURCE. También debemos mirar el espacio de color, actualmente tenemos BGRA8888, RGBA8888 y PACKED32. Los dos primeros son iguales, pero con las componentes (rojo, verde y azul) invertidas de orden. Para cada componente se utilizan 8bits (1 byte). La A corresponde al canal alfa, es decir, el nivel de transparencia del pixel. El último, PACKED32 también utiliza 4bytes (32bits), pero no nos dice cómo vienen dados los colores, ni el orden de las componentes, ni el espacio de color utilizado, por lo que puede que no nos lo den en RGB siquiera, esto sólo está indicado para efectos donde el color no importe (a lo mejor estamos cambiando los pixels de orden).

Toca compilar el plugin, para ello tenemos que hacerlo de tal manera que nuestro código se pueda linkar dinámicamente. Además, el archivo resultante (.so) tenemos que colocarlo en una ruta determinada para poder utilizarlo:

  • /usr/lib/frei0r-1/mi_plugin.so
  • /usr/local/lib/frei0r-1/mi_plugin.so
  • $HOME/.frei0r-1/mi_plugin.so

También podemos utilizar un pequeño script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

FILE=$1

if [ -f $FILE.c ]
then
    if [ ! -d $HOME/.frei0r-1/lib/ ]
    then
        mkdir -p $HOME/.frei0r-1/lib/
    fi
    gcc -c -fPIC frei0r_utils.c -o frei0r_utils.o && gcc -c -fPIC $FILE.c -o $FILE.o && gcc -shared -o $FILE.so frei0r_utils.o $FILE.o && cp $FILE.so $HOME/.frei0r-1/lib/
else
    echo "Especificar nombre de archivo";
fi

Y, tras darle privilegios de escritura debemos llamarlo:

$ ./build mi_plugin

Hecho todo esto, podemos utilizar el programa MLT para ver si frei0r ha cogido bien nuestro plugin (lo he marcado en negrita):

$ melt -query producers

producers:
– frei0r.poesia_producer
– frei0r.ising0r
– frei0r.lissajous0r
– frei0r.nois0r
– frei0r.onecol0r
– frei0r.partik0l
– frei0r.plasma
– frei0r.test_pat_B
– frei0r.test_pat_C
– frei0r.test_pat_G
– frei0r.test_pat_I
– frei0r.test_pat_L
– frei0r.test_pat_R
– abnormal
– color
– colour
– consumer
– hold
– melt
– melt_file
– noise
– ppm
– vorbis
– kino
– avformat
– avformat-novalidate
– xml
– xml-string
– slowmotion
– decklink
– pango
– pixbuf
– framebuffer
– pgm
– libdv
– qimage
– kdenlivetitle

Ahora, vamos a pedir información sobre el plugin de la siguiente forma:

$ melt -query producer=frei0r.poesia_producer

schema_version: 0.100000
title: Generador del poesía binaria
version: 0.100000
identifier: frei0r.poesia_producer
description: Es sólo un plugin de ejemplo que no hace nada
creator: Gaspar Fernández
type: producer
parameters:
tags:
– Video

Aunque todavía no hace nada, vamos a generar algo de ruido, como una primera rutina de aproximación a estos efectos. Para ello utilizamos la función update(), que aunque tiene un parámetro llamado inframe, un generador no tendrá ningún vídeo de entrada, por lo que sólo cogemos el outframe.

Modificaremos la función update() de la siguiente manera:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void f0r_update(f0r_instance_t instance,
        double time, const uint32_t* inframe, uint32_t* outframe)
{
  /* Lo hacemos para escribir las componentes por separado */
  unsigned char* dst = (unsigned char*)outframe;
  inst_t* in=(inst_t*)instance;
  unsigned int len = in->size.width * in->size.height;
  while (len--)
    {
      *dst++=(unsigned char)rand()%255;
      *dst++=(unsigned char)rand()%255;
      *dst++=(unsigned char)rand()%255;
      *dst++=255;
    }
}

Aunque podríamos definir como plantilla casi todo el método, ya que las primeras líneas, nos permiten escribir los componentes por separado (de 8bit en 8bit) de los bloques de 32bit que debemos exportar en outgrame. Por otro lado, nos llega la información de la instancia (que es donde guardamos el tamaño), tenemos que cambiarla de tipo; y más tarde, tendremos que calcular el tamaño total del frame a generar en pixels (ancho * alto), y por cada pixel, sacaremos 4 bytes de datos con cada una de las componentes y el alpha, al que le damos opacidad total.

También podría interesarte...

Leave a Reply