GStreamer plug-in audio fai da te!

Pubblicato: aprile 18, 2014 in Programmazione, Programmazione & Software
Tag:,

gstreamerlGStreamer è un framework estremamente potente e versatile pensato per la creazione di applicazioni di streaming Audio/Video.
Una delle caratteristiche più importanti ( e forse anche la chiave del suo successo )  è la sua modularità ed il fatto che in GStreamer è possibile creare in autonomia nuovi plug-in che possono essere integrati e combinati con i moduli già esistenti; in pratica, se nella sua vasta libreria, non viene trovato quello di cui si ha bisogno, è possibile realizzarlo ed integrarlo nelle proprie pipeline. Chiunque può estendere ed aumentare le potenzialità di questo framework.
In questo breve articolo vedremo come realizzare un semplice plug-in che controlla il volume di un flusso audio e successivamente ne realizzeremo uno in grado di modificare il pitch di un brano audio utilizzando una DTFT.

Cosa bisogna sapere per leggere questa guida?
Questa guida presuppone che è una certa familiarità con il funzionamento di base di GStreamer, una sua breve introduzione era stata fatta in questo mio precedente articolo.
E’ consigliato leggere il manuale di sviluppo di GStreamer  o almeno leggere la documentazione disponibile sul sito ufficiale.
E inoltre richiesta una discreta conoscenza di base del linguaggio C e sapere qualcosa dei GObject.

Il primo obiettivo che ci prefissiamo è quello di realizzare un semplice controllo di volume; anche se nessuno acquisterà questo nostro primo plug-in, esso ci servirà per mettere in piedi tutto il sistema di build, per capire come è possibile compilare e caricare un plug-in all’interno delle  nostra pipeline.

Si tratta in sostanza di realizzare quello che in GStreamer viene chiamato un “Filtro” ovvero di un blocco software che che dispone di un singolo pad di ingresso e un singolo pad uscita .
I dati entrano nel pad di ingresso, vengono modificati dal filtro e fluiscono dal pad di uscita.

filter-element

Costruiamo il Boilerplate

Attualmente ci sono due modi per sviluppare un nuovo plug-in per GStreamer :

  • Scrivere l’intero plugin a mano partendo da zero,
  • Copiare un modello già esistente e scrivere il codice del plugin necessario alla nostra nuova funzionalità.

Il secondo metodo è di gran lunga il più semplice, per cui cercheremo di seguire questa strada.

La prima cosa da fare è quindi  procurarsi tutto l’ambiente necessario alla compilazione e/o alla realizzazione del plug in.
L’intero repository è posto come di consueto su di un server git.
Per cui dopo essersi posizionato in una directory del vostro disco nella quale volete riporre tutte le risorse necessarie ed essere sicuri di essere connessi a internet  digitate dalla consolle i seguenti comandi:

shell $ git clone git ://anongit.freedesktop.org/gstreamer/gst- template.git

Vedrete una serie di messaggi comparire e al termine otterrete la copia locale del repository sul vostro disco rigido.

Imm_1
Ho dato ovviamente per scontato che il vostro sistema operativo di sviluppo sua in Linux, nel mio caso una distribuzione Ubuntu 12.04 LTS

Entriamo nella cartella appena scaricata ( gst-template ) con il classico CD ed analizziamo i file scaricati.

Imm_2

Primo problema:
Purtroppo ( o per fortuna dipende dai punti di vista ) GStreamer nel corso degli ultimi mesi ha fatto un grande balzo in avanti ed è passato alla versione 1.0 ed oltre. Attualmente sul repositor git troviamo questa ultima versione.
Questo tutorial era stato fatto per la versione precedente 0.10.0,  per poterlo seguire è quindi necessario prendere la versione precedente del repository e non l’ultima disponibile. State tranquilli, tutto quello che farete con questa versione continua ad essere utilizzabile anche con le nuove versioni di Gstreamer compresa la 1.0.

Per modificare la versione utilizziamo il tool: gitg
Se nel vostro sistema non è installato vi basterà digitare apt-get install gitg
Una volta installato digitate gitg sulla riga comandi ed il programma si avvia.

Selezionate ora nel meni un alto il Ramo:origin/0.10  e successivamente con un click del mouse destro sull’albero di file creiamo un
Nuovo ramo locale 0.10.
Il gioco è fatto, ora sul nostro HD avremo tutti i file pronti per essere lavorati.

Imm_5

 Cosa abbiamo scaricato?

Se diamo un’occhiata al contenuto della directory  appena scaricata otteniamo il seguente albero:

Imm_7

La cartella gst -plugin contiene “il modello” con il quale andremo a scrivere il nostro plug-in GStreamer .
I file di nostro primo interesse sono contenuti nelal cartella SRC

Imm_8

Il codice è volutamente molto semplice e consente di capire rapidamente le basi e le impostazioni necessarie alla compilazione e all’utilizzo del plug-in.

La prima cosa da fare  è quello di specificare alcuni dettagli fondamentali sul plug-in che andremo a creare; in particolare dobbiamo definire :
il suo nome, chi lo ha scritto, i numero di versione e poche altre cose.
Questi dettagli sono noti collettivamente come il “boilerplate” .
Il metodo standard per definire il boilerplate è semplicemente quello di scrivere del codice in alcune strutture già precostituite e compilare il tutto ma, per semplificare la vita ai meno esperti nella cartella gst -plugins/tools vi è un’utility chiamata  make_element  che crea il codice standard per la nostra applicazione in modo semi automatico.

dalla consolle digitiamo:

shell $ gst-template/gst-plugin/src cd
shell $ .. / tools / make_element MyFilter

I seguenti comandi creano il plugin MyFilter  basato sul modello standard base e mette i file di output (gstmyfilter.c e gstmyfilter.h .nella directory gst-template/gst-plugin/src.

Imm_9

Analizziamo il codice del nostro plug-in.

Con un editor di testo ( ad esempio anche gedit )  apriamo il file : GstMyFilter.h e GstMyFilter.c.

Imm_10

Notiamo subito che la struttura _GstMyFilter definisce le caratteristiche principale del nostro plug-in, in particolare che dispone di un pad source, uno di sink, ed un solo parametro che qui viene chiamato silent di tipo boolean.
Le altre righe sono di definizione e/o di impostazioni.

Nel file gstmyfilter.c notiamo le due funzioni di inizializzazione ( una della classe base, l’altra del nostro plug-in ) gst_my_filter_base_init e gst_my_filter_class_init.

In particolare in quest’ultima, si installa la “proprieta” Silent e la si inizializza con le seguenti righe:

g_object_class_install_property (gobject_class, PROP_SILENT,
g_param_spec_boolean ("silent", "Silent", "Produce verbose output ?",FALSE, G_PARAM_READWRITE));

Ovviamente questo è il punto in cui dobbiamo  intervenire per aggiungere ed installare nuovi paramentri ( come il volume o il controllo di pitch che vedremo in seguito).

Scorrendo il file troviamo la funzione gst_my_filter_chain (GstPad * pad, GstBuffer * buf); questa è la funzione che  viene chiamata ogni volta che c’è un buffer di dati che dovrà essere processato e in cui dovremo mettere il nostro codice per intervenire sui dati audio del plug-in.

Imm_13

In questo caso vediamo che se la variabile silent è falsa, ad ogni buffer viene inviata una scritta nello standard output.

Un plug-In per il controllo del volume.

Dopo aver dato un’occhiata veloce al codice generato per noi dal make_element,  procediamo con io nostro obbiettivo che consisteva nel realizzare in primis un plug-in per il controllo del volume.
A tale scobo dovremo modificare il nostro plug-in inserendo un nuovo parametro di tipo float (il cui valore andrà da 0 a 1, il volume) e dovremo modificare la funzione che processa i dati in modo da moltiplicare ogni campione per il valore del volume.

Il file gstmyfilter.h diventerà:
//--------------------------------------------------------------------
#ifndef __GST_MYFILTER_H__
#define __GST_MYFILTER_H__
//--------------------------------------------------------------------
#include <gst/gst.h>
//--------------------------------------------------------------------
G_BEGIN_DECLS
/* #defines don't like whitespacey bits */
#define GST_TYPE_MYFILTER \
(gst_my_filter_get_type())
#define GST_MYFILTER(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MYFILTER,GstMyFilter))
#define GST_MYFILTER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MYFILTER,GstMyFilterClass))
#define GST_IS_MYFILTER(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MYFILTER))
#define GST_IS_MYFILTER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MYFILTER))
//--------------------------------------------------------------------
typedef struct _GstMyFilter GstMyFilter;
typedef struct _GstMyFilterClass GstMyFilterClass;
//--------------------------------------------------------------------
struct _GstMyFilter
{
GstElement element;
GstPad *sinkpad, *srcpad;
gboolean silent;
gfloat volume;
};
struct _GstMyFilterClass
{
GstElementClass parent_class;
};
GType gst_my_filter_get_type (void);
G_END_DECLS
#endif /* __GST_MYFILTER_H__ */

Mentre il file gstmyfilter.c diventerà:

//--------------------------------------------------------------------
#ifdef HAVE_CONFIG_H
# include
#endif
//--------------------------------------------------------------------
#include <gst/gst.h>
//--------------------------------------------------------------------
#include "gstmyfilter.h"
//--------------------------------------------------------------------
GST_DEBUG_CATEGORY_STATIC (gst_my_filter_debug);
#define GST_CAT_DEFAULT gst_my_filter_debug
//--------------------------------------------------------------------
/* Filter signals and args */
enum
{
/* FILL ME */
LAST_SIGNAL
};
//--------------------------------------------------------------------
enum
{
PROP_0,
PROP_SILENT,
PROP_VOLUME
};
//--------------------------------------------------------------------
// the capabilities of the inputs and outputs.
// describe the real formats here.
// Descrizione dei pad che l'elemento potrebbe generare
//--------------------------------------------------------------------
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int,channels=2,rate=44100,signed=(boolean)true,width=16,depth=16,endianness=BYTE_ORDER") // Tipi supportati
);
//--------------------------------------------------------------------
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int,channels=2,rate=44100,signed=(boolean)true,width=16,depth=16,endianness=BYTE_ORDER") // Tipi supportati
);
//--------------------------------------------------------------------
GST_BOILERPLATE (GstMyFilter, gst_my_filter, GstElement, GST_TYPE_ELEMENT);
static void gst_my_filter_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_my_filter_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
static gboolean gst_my_filter_set_caps (GstPad * pad, GstCaps * caps);
static GstFlowReturn gst_my_filter_chain (GstPad * pad, GstBuffer * buf);
//--------------------------------------------------------------------
// GObject vmethod implementations
//--------------------------------------------------------------------
static void gst_my_filter_base_init (gpointer gclass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
//
// Imposta i dettagli della classe
//
gst_element_class_set_details_simple(
element_class,
"MyFilter",
"Prova plug in GST",
"Generic Test Filtro volume",
"giorgio <<giorgio@giorgio.it>>"
);
//
// Aggiunge i pad In/Out
//
gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&src_factory));
gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&sink_factory));
}
//--------------------------------------------------------------------
// Initialize the myfilter's class
//--------------------------------------------------------------------
static void gst_my_filter_class_init (GstMyFilterClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gobject_class->set_property = gst_my_filter_set_property;
gobject_class->get_property = gst_my_filter_get_property;
g_object_class_install_property (gobject_class, PROP_SILENT,
g_param_spec_boolean ("silent", "Silent", "Produce una scritta", FALSE, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_VOLUME,
g_param_spec_float ("volume", "Volume", "Regola il volume", 0.0, 1.0, 1.0, G_PARAM_READWRITE));
}
//--------------------------------------------------------------------
// initialize the new element
// instantiate pads and add them to element
// set pad calback functions
// initialize instance structure
//--------------------------------------------------------------------
static void gst_my_filter_init (GstMyFilter * filter, GstMyFilterClass * gclass)
{
filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
gst_pad_set_setcaps_function (filter->sinkpad, GST_DEBUG_FUNCPTR(gst_my_filter_set_caps));
gst_pad_set_getcaps_function (filter->sinkpad, GST_DEBUG_FUNCPTR(gst_pad_proxy_getcaps));
gst_pad_set_chain_function (filter->sinkpad, GST_DEBUG_FUNCPTR(gst_my_filter_chain));
filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
gst_pad_set_getcaps_function (filter->srcpad, GST_DEBUG_FUNCPTR(gst_pad_proxy_getcaps));
gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
filter->silent = FALSE;
filter->volume = 1.0;
}
//--------------------------------------------------------------------
static void gst_my_filter_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstMyFilter *filter = GST_MYFILTER (object);
switch (prop_id) {
case PROP_SILENT:
filter->silent = g_value_get_boolean (value);
break;
case PROP_VOLUME:
filter->volume = g_value_get_float (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
//--------------------------------------------------------------------
static void gst_my_filter_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec)
{
GstMyFilter *filter = GST_MYFILTER (object);
switch (prop_id) {
case PROP_SILENT:
g_value_set_boolean (value, filter->silent);
break;
case PROP_VOLUME:
g_value_set_float (value, filter->volume);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
//--------------------------------------------------------------------
// GstElement vmethod implementations
// this function handles the link with other elements
//--------------------------------------------------------------------
static gboolean gst_my_filter_set_caps (GstPad * pad, GstCaps * caps)
{
GstMyFilter *filter;
GstPad *otherpad;
filter = GST_MYFILTER (gst_pad_get_parent (pad));
otherpad = (pad == filter->srcpad) ? filter->sinkpad : filter->srcpad;
gst_object_unref (filter);
return gst_pad_set_caps (otherpad, caps);
}
//------------------------------------------------------------------------------------------------------
// chain function
// this function does the actual processing
//--------------------------------------------------------------------
static GstFlowReturn gst_my_filter_chain (GstPad * pad, GstBuffer * buf)
{
GstMyFilter *filter;
g_print ("size: %d\n",GST_BUFFER_SIZE(buf));
signed short* p = (signed short*)GST_BUFFER_DATA(buf);
unsigned long nSample = (GST_BUFFER_SIZE(buf)>>1);
filter = GST_MYFILTER (GST_OBJECT_PARENT (pad));
if (filter->silent == FALSE){
g_print ("Mi sono attaccato nella catena per cui ci sono....\n");
}
int i=0;
for(i=0; i<nSample; i++){ p[i] = (signed short) (p[i]*filter->volume);
}
/* just push out the incoming buffer without touching it */
return gst_pad_push (filter->srcpad, buf);
}
//--------------------------------------------------------------------
// entry point to initialize the plug-in
// initialize the plug-in itself
// register the element factories and other features
//--------------------------------------------------------------------
static gboolean myfilter_init (GstPlugin * myfilter)
{
// debug category for fltering log messages
//
// exchange the string 'Template myfilter' with your description
//
GST_DEBUG_CATEGORY_INIT (gst_my_filter_debug, "myfilter", 0, "Test di prova per un plug-in GST");
return gst_element_register (myfilter, "myfilter", GST_RANK_NONE, GST_TYPE_MYFILTER);
}
//--------------------------------------------------------------------
/* PACKAGE: this is usually set by autotools depending on some _INIT macro
* in configure.ac and then written into and defined in config.h, but we can
* just set it ourselves here in case someone doesn't use autotools to
* compile this code. GST_PLUGIN_DEFINE needs PACKAGE to be defined.
*/
#ifndef PACKAGE
#define PACKAGE "myfirstmyfilter"
#endif
//--------------------------------------------------------------------
/* gstreamer looks for this structure to register myfilters
*
* exchange the string 'Template myfilter' with your myfilter description
*/
GST_PLUGIN_DEFINE (
GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"myfilter",
"Template myfilter",
myfilter_init,
VERSION,
"LGPL",
"GM",
"www.GM.it"
)

Terminato l’Edit dei file, per effettuare la compilazione è necessario ora effettuare le necessarie modifiche al Makefile.am.
Apriamo questo file con un editor ed aggiungiamo i nome dei file appena modificati come in figura:

Imm_14

Dalla consolle dei comandi dobbiamo avviare lo script autogen.sh

Imm_15
al termine del quale dobbiamo digitare i classici make && sudo make install

Per saperne di più su Automake cliacca qui

Imm_16

Se non avete commesso errori la compilazione va a buon fine e la libreria ( ovvero il plug-in) veine installata ( copiata ) nella directory
/usr/local/lib/gstreamer-0.10

 

Imm_17

 Testiamo il nostro nuovo plug-in

Per prima cosa controlliamo se il nostro plug-in è visibile nel sistema.
gst-inspect analizza e mostra a video tutti gli elementi del framework, lanciamolo dalla riga di comando.

Probabilmente il plug-in appena creato non compare nella lista generata da gst-inspect e questo potrebbe dipendere dal settaggio della variabile d’ambiente: GST_PLUGIN_PATH.

Se digitando dalla consolle:

$ echo $GST_PLUGIN_PATH
ottenere una riga vuota, questa variabile d’ambiente non è stata;  in questo caso digitate:

$ export GST_PLUGIN_PATH=/usr/local/lib/gstreamer-0.10
$ gst-inspect -p
$ gst-inspect myfilter

Questo è quello che dovreste ottenere:

——————

giorgio@giorgio-MS-7529:~/gst-template/gst-plugin$ gst-inspect myfilter
Factory Details:
Long name: MyFilter
Class: Prova plug in GST
Description: Generic Test Filtro volume
Author(s): giorgio <<giorgio@giorgio.it>>
Rank: none (0)
Plugin Details:
Name: myfilter
Description: Template myfilter
Filename: /usr/local/lib/gstreamer-0.10/libmyfilter.so
Version: 0.10.0
License: LGPL
Source module: my-plugin-package
Binary package: GM
Origin URL: http://www.GM.it
GObject
+----GstObject
+----GstElement
+----GstMyFilter
Pad Templates:
SRC template: 'src'
Availability: Always
Capabilities:
audio/x-raw-int
channels: 2
rate: 44100
signed: true
width: 16
depth: 16
endianness: 1234
SINK template: 'sink'
Availability: Always
Capabilities:
audio/x-raw-int
channels: 2
rate: 44100
signed: true
width: 16
depth: 16
endianness: 1234
Element Flags:
no flags set
Element Implementation:
Has change_state() function: gst_element_change_state_func
Has custom save_thyself() function: gst_element_save_thyself
Has custom restore_thyself() function: gst_element_restore_thyself
Element has no clocking capabilities.
Element has no indexing capabilities.
Element has no URI handling capabilities.
Pads:
SRC: 'src'
Implementation:
Has custom eventfunc(): gst_pad_event_default
Has custom queryfunc(): gst_pad_query_default
Has custom iterintlinkfunc(): gst_pad_iterate_internal_links_default
Has getcapsfunc(): gst_pad_proxy_getcaps
Has acceptcapsfunc(): gst_pad_acceptcaps_default
Pad Template: 'src'
SINK: 'sink'
Implementation:
Has chainfunc(): gst_my_filter_chain
Has custom eventfunc(): gst_pad_event_default
Has custom queryfunc(): gst_pad_query_default
Has custom iterintlinkfunc(): gst_pad_iterate_internal_links_default
Has getcapsfunc(): gst_pad_proxy_getcaps
Has setcapsfunc(): gst_my_filter_set_caps
Has acceptcapsfunc(): gst_pad_acceptcaps_default
Pad Template: 'sink'
Element Properties:
name : The name of the object
flags: leggibile, scrivibile
String. Default: "myfilter0"
silent : Produce una scritta
flags: leggibile, scrivibile
Boolean. Default: false
volume : Regola il volume
flags: leggibile, scrivibile
Float. Range: 0 - 1 Default: 1

Il plug-in e stato riconosciuto e correttamente interpretato da GSreamer.

Per vedere come funziona creiamo una pipeline da riga di comando con gst-launch e lanciamolo:

$ gst-launch audiotestsrc ! myfilter volume=0.9 !  audioconvert  !  alsasink

Modificando il valore volume ( minimo 0.0, max 1.0) sentirete il volume della sinusoide di test cambiare.
Se volte applicare il vostro nuovo filtro su di un brano Mp3:

$ gst-launch filesrc location=musica.mp3 ! mad ! audioconvert ! myfilter volume=0.5 ! alsasink

N.B. il file mp3 deve esser nella direcotry corrente.

Potete scaricare i file  sorgenti per questo primo plug-in cliccando qui

 

Un plug-in che traspone l’audio di una canzone.

Bene, visto che il nostro primo plug-in è andato procediamo verso il nostro vero obiettivo, ovvero realizzare un modulo che ci permetta di fare un pitch sifth in tempo reale di un brano audio.
Con il make_element creiamo lo scheletro di un nuovo plug-in chiamato myPitch

Imm_18

Nella cartella src compariranno due nuovi file gstmypitch.h e gstmypitch.c

Scaricate il file smbPitchShift.c ( file in cui si effettua la trasposizione ) e copiatelo nella cartella src.

Nel file gstmypitch.h aggiungiamo un nuovo parametro ( pitch ) nella struttura _GstmyPitch

Imm_19

Nel file gstmypitch.c

  • includiamo il file “smbPitchShift.c”
  • aggiungiamo la proprietà PROP_PITCH
  • specifichiamo il formato dei dati dei PADS, (“audio/x-raw-int,channels=2,rate=44100,signed=        (boolean)true,width=16,depth=16,endianness=BYTE_ORDER”)
  • aggiungiamo il parametro pitch nella funzione gst_my_pitch_class_init specificando il range ed il valore min e max. g_object_class_install_property (gobject_class, PROP_PITCH,
    g_param_spec_float  (“pitch”, “Pitch”, “Regola il pitch”, 0.5, 2.0, 2.0, G_PARAM_READWRITE));
  • Nella funzione gst_my_pitch_init, resettiamo il parametro pitch a 0. filter->pitch = 1.0;
  • Modifichiamo la routin gst_my_pitch_chain in modo da processare i dati audio.

Qui di seguito potete vedere il listato completo.
—–

//---------------------------------------------------------------------------
#ifdef HAVE_CONFIG_H
# include
#endif
//---------------------------------------------------------------------------
#include <gst/gst.h>
//---------------------------------------------------------------------------
#include "gstmypitch.h"
#include "smbPitchShift.c"
//---------------------------------------------------------------------------
GST_DEBUG_CATEGORY_STATIC (gst_my_pitch_debug);
#define GST_CAT_DEFAULT gst_my_pitch_debug
//---------------------------------------------------------------------------
/* Filter signals and args */
enum
{
/* FILL ME */
LAST_SIGNAL
};
//---------------------------------------------------------------------------
enum
{
PROP_0,
PROP_SILENT,
PROP_PITCH
};
//---------------------------------------------------------------------------
/* the capabilities of the inputs and outputs.
gst-launch -v filesrc location=/home/giorgio/Scrivania/a.MP3 ! mad ! audioconvert ! audio/x-raw-float,channels=2,rate=44100 ! myfilter silent=FALSE pitch=1.0 ! audioconvert ! audio/x-raw-int,channels=2,rate=44100 ! alsasink
gst-launch -v filesrc location=/home/giorgio/Scrivania/a.MP3 ! mad ! audioconvert ! audio/x-raw-int,channels=2,rate=44100,signed=true,width=16,depth=16,endianness=BYTE_ORDER ! myfilter silent=TRUE pitch=1.0 ! alsasink
*/
//---------------------------------------------------------------------------
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int,channels=2,rate=44100,signed= (boolean)true,width=16,depth=16,endianness=BYTE_ORDER") // Tipi supportati
);
//---------------------------------------------------------------------------
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int,channels=2,rate=44100,signed=(boolean)true,width=16,depth=16,endianness=BYTE_ORDER") // Tipi supportati
);
//---------------------------------------------------------------------------
GST_BOILERPLATE (GstmyPitch, gst_my_pitch, GstElement,GST_TYPE_ELEMENT);
//---------------------------------------------------------------------------
static void gst_my_pitch_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_my_pitch_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static gboolean gst_my_pitch_set_caps (GstPad * pad, GstCaps * caps);
static GstFlowReturn gst_my_pitch_chain (GstPad * pad, GstBuffer * buf);
//---------------------------------------------------------------------------
/* GObject vmethod implementations */
//---------------------------------------------------------------------------
static void
gst_my_pitch_base_init (gpointer gclass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
//
// Imposta i dettagli della classe
//
gst_element_class_set_details_simple(element_class,
"myPitch",
"Pitchshift audio",
"Generic Template Element",
"giorgio <<giorgio@hostname.org>>");
//
// Aggiunge i pad In/Out
//
gst_element_class_add_pad_template (element_class,gst_static_pad_template_get (&src_factory));
gst_element_class_add_pad_template (element_class,gst_static_pad_template_get (&sink_factory));
}
//---------------------------------------------------------------------------
/* initialize the mypitch's class */
//---------------------------------------------------------------------------
static void
gst_my_pitch_class_init (GstmyPitchClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gobject_class->set_property = gst_my_pitch_set_property;
gobject_class->get_property = gst_my_pitch_get_property;
g_object_class_install_property (gobject_class, PROP_SILENT,
g_param_spec_boolean ("silent", "Silent", "Produce verbose output ?",FALSE, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_PITCH,
g_param_spec_float ("pitch", "Pitch", "Regola il pitch", 0.5, 2.0, 2.0, G_PARAM_READWRITE));
}
//---------------------------------------------------------------------------
/* initialize the new element
* instantiate pads and add them to element
* set pad calback functions
* initialize instance structure
*/
//---------------------------------------------------------------------------
static void gst_my_pitch_init (GstmyPitch * filter, GstmyPitchClass * gclass)
{
filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
gst_pad_set_setcaps_function (filter->sinkpad,
GST_DEBUG_FUNCPTR(gst_my_pitch_set_caps));
gst_pad_set_getcaps_function (filter->sinkpad,
GST_DEBUG_FUNCPTR(gst_pad_proxy_getcaps));
gst_pad_set_chain_function (filter->sinkpad,
GST_DEBUG_FUNCPTR(gst_my_pitch_chain));
filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
gst_pad_set_getcaps_function (filter->srcpad,
GST_DEBUG_FUNCPTR(gst_pad_proxy_getcaps));
gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
filter->silent = FALSE;
filter->pitch = 1.0;
}
//---------------------------------------------------------------------------
static void gst_my_pitch_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstmyPitch *filter = GST_MYPITCH (object);
switch (prop_id) {
case PROP_SILENT:
filter->silent = g_value_get_boolean (value);
break;
case PROP_PITCH:
filter->pitch = g_value_get_float (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
//---------------------------------------------------------------------------
static void gst_my_pitch_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstmyPitch *filter = GST_MYPITCH (object);
switch (prop_id) {
case PROP_SILENT:
g_value_set_boolean (value, filter->silent);
break;
case PROP_PITCH:
g_value_set_float (value, filter->pitch);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
//---------------------------------------------------------------------------
/* GstElement vmethod implementations */
//---------------------------------------------------------------------------
/* this function handles the link with other elements */
static gboolean
gst_my_pitch_set_caps (GstPad * pad, GstCaps * caps)
{
GstmyPitch *filter;
GstPad *otherpad;
filter = GST_MYPITCH (gst_pad_get_parent (pad));
otherpad = (pad == filter->srcpad) ? filter->sinkpad : filter->srcpad;
gst_object_unref (filter);
return gst_pad_set_caps (otherpad, caps);
}
//---------------------------------------------------------------------------
float fBuff_L[4*1024];
float fBuff_R[4*1024];
//---------------------------------------------------------------------------
static GstFlowReturn gst_my_pitch_chain (GstPad * pad, GstBuffer * buf)
{
GstMyFilter *filter;
signed short* p = (signed short*)GST_BUFFER_DATA(buf);
unsigned long nSample = (GST_BUFFER_SIZE(buf)>>1);
filter = GST_MYFILTER (GST_OBJECT_PARENT (pad));
if (filter->silent == FALSE){
g_print ("%d\n",nSample);
g_print ("%f\n",p[0]);
}
int i=0;
int idx = 0;
for(i=0; i<nSample; i+=2){ fBuff_L[idx] = p[i] /(32768.0*1.5); fBuff_R[idx] = p[i+1]/(32768.0*1.5); idx++; } smbPitchShift(filter->volume, idx, 1024, 4, 44100, fBuff_L, fBuff_L);
smbPitchShift(filter->volume, idx, 1024, 4, 44100, fBuff_R, fBuff_R);
int nidx=0;
for(i=0; i<idx; i++){ p[nidx++] = (signed short) (fBuff_L[i]*32768.0); p[nidx++] = (signed short) (fBuff_R[i]*32768.0); } /* just push out the incoming buffer without touching it */ return gst_pad_push (filter->srcpad, buf);
}
//---------------------------------------------------------------------------
/* entry point to initialize the plug-in
* initialize the plug-in itself
* register the element factories and other features
*/
static gboolean mypitch_init (GstPlugin * mypitch)
{
/* debug category for fltering log messages
*
* exchange the string 'Template mypitch' with your description
*/
GST_DEBUG_CATEGORY_INIT (gst_my_pitch_debug, "mypitch",
0, "Template mypitch");
return gst_element_register (mypitch, "mypitch", GST_RANK_NONE,
GST_TYPE_MYPITCH);
}
//---------------------------------------------------------------------------
/* PACKAGE: this is usually set by autotools depending on some _INIT macro
* in configure.ac and then written into and defined in config.h, but we can
* just set it ourselves here in case someone doesn't use autotools to
* compile this code. GST_PLUGIN_DEFINE needs PACKAGE to be defined.
*/
#ifndef PACKAGE
#define PACKAGE "myfirstmypitch"
#endif
/* gstreamer looks for this structure to register mypitchs
*
* exchange the string 'Template mypitch' with your mypitch description
*/
GST_PLUGIN_DEFINE (
GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"mypitch",
"Template mypitch",
mypitch_init,
VERSION,
"LGPL",
"GStreamer",
"http://gstreamer.net/"
)

Aggiorniamo il il Makefile.am con i nomi dei nuovi file e lanciamo lo script autogen.sh, make && sudo make install ed il plugin per lo shift è servito.

Ovviamente un plug-in piu serio richiederebbe un sistema di buffering ( preroll ) ottimizzazioni piè spinte, magari un algoritmo di trasposizione diverso ma, questi sono problemi più avanzati che per un primo approccio possono essere trascurati…..

Qui scaricate il pacchetto completo con i files sorgente.

Giorgio.

p.s.
Scusate eventuali errori ed omissioni, purtroppo il tempo è sempre troppo poco!

 

 

 

 

 

 

 

 

 

 

commenti
  1. […] Un esempio di elaborazione in frequenza è stato fatto nel mio precedente articolo relativo al plug-in per gstremaer. […]

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...