· Cambiar idioma (Actualmente: Español)

GStreamer: Visualización de audio

Octubre 10, 2021 15:07 -0500
Visualización Monoscope de GStreamer

GStreamer permite representar gráficamente los datos de sonido de los archivos de audio usando diferentes complementos (plugins). En este documento dejo algunos ejemplos escritos en Python, basados en la información de los tutoriales de GStreamer, que para la fecha solo estaban disponibles en el lenguaje de programación C. También facilito la creación del entorno para ejecutar los ejemplos usando manifiestos y entornos de GNU Guix.

El archivo comprimido audio-visualization.tar.xz contiene todos los archivos que se mencionan en este documento.

Prueba de reproducción

Para empezar, simplemente reproduzcamos un sonido sin ningún tipo de visualización, solo para confirmar que el entorno tiene lo esencial para empezar a trabajar.

cd ruta/a/audio-visualization/
guix shell --pure -m manifest.scm
python3 play-audio.py
Secuencia 1. Órdenes de terminal para reproducir un archivo de audio sin usar visualización.

La primera línea de la Secuencia 1 simplemente cambia de directorio a la carpeta de archivos de ejemplo usados en este documento.

La segunda línea crea un perfil de GNU Guix con todos los paquetes de software definidos en el manifiesto (ver Manifiesto 1) y nos deja en un entorno de shell con las variables de entorno apropiadas para hacer uso de estos paquetes.

(use-modules (gnu packages))


(define DEV_PACKAGES
  (list "coreutils"))

(define PRODUCTION_PACKAGES
  (list "gobject-introspection"
        "gstreamer"
        "gst-plugins-bad"   ; Provides audiovisualizers.
        "gst-plugins-good"  ; Provides goom, goom2k1, monoscope.
        "python"
        "python-pygobject"))

(specifications->manifest
 (append DEV_PACKAGES
         PRODUCTION_PACKAGES))
      
Manifiesto 1. Manifiesto de GNU Guix que lista los paquetes de software necesarios para ejecutar los ejemplos de este documento.

Ya con el entorno listo, la última línea simplemente ejecuta el Programa play-audio, que reproduce el archivo song18.mp3. El programa finaliza al terminarse el flujo de audio.

import os

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst


# Initialize GStreamer.
Gst.init(None)

# Define audio file location.
audio_file_path = os.path.abspath("song18.mp3")

# Build a playbin pipeline.
textual_pipeline = "playbin uri=file://{}".format(audio_file_path)
pipeline = Gst.parse_launch(textual_pipeline)

# Start playing.
pipeline.set_state(Gst.State.PLAYING)

# Wait until error or EOS.
bus = pipeline.get_bus()
message = bus.timed_pop_filtered(
   Gst.CLOCK_TIME_NONE,
   Gst.MessageType.ERROR | Gst.MessageType.EOS
)

# Free resources.
pipeline.set_state(Gst.State.NULL)
      
Programa play-audio. Reproduce el archivo de audio song18.mp3.

Este programa, y los demás programas en este documento, usan un conducto playbin. Este tipo de conducto viene con todos los elementos necesarios para reproducir datos multimedia sin mayor esfuerzo. Así se puede concentrar uno en la visualización y dejar a un lado los detalles de la reproducción del sonido mismo.

Visualización predeterminada

Activar la visualización de audio en un conducto playbin es sencillo; basta con agregarle la bandera de visualización antes de reproducir el archivo. El Programa play-vis activa una visualización predeterminada de esta manera.

import os

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst


# Initialize GStreamer.
Gst.init(None)

# Define audio file location.
audio_file_path = os.path.abspath("song18.mp3")

# Build a playbin pipeline.
textual_pipeline = "playbin uri=file://{}".format(audio_file_path)
pipeline = Gst.parse_launch(textual_pipeline)

# Set the visualization flag.
flags = pipeline.get_property("flags")
vis_flag = (1 << 3)
flags |= vis_flag
pipeline.set_property("flags", flags)

# Start playing.
pipeline.set_state(Gst.State.PLAYING)

# Wait until error or EOS.
bus = pipeline.get_bus()
message = bus.timed_pop_filtered(
   Gst.CLOCK_TIME_NONE,
   Gst.MessageType.ERROR | Gst.MessageType.EOS
)

# Free resources.
pipeline.set_state(Gst.State.NULL)
      
Programa play-vis. Reproduce el archivo de audio song18.mp3 con una visualización predeterminada por GStreamer.

Ahora ejecutemos el programa en el mismo entorno de la Prueba de reproducción:

python3 play-vis.py

Debería verse un resultado similar al de la Figura 1:

Figura 1
Figura 1. Pantallazo de la visualización de los datos de sonido del archivo de audio song18.mp3 generada por el Programa play-vis.

Cuando no se especifica qué complemento de visualización usar, se selecciona uno predeterminadamente, que parece ser el complemento goom, que viene en el paquete gst-plugins-good (ver Manifiesto 1).

Algo que se nota en este tipo de visualización es que sus gráficos no son idénticos en cada ejecución del programa. La sección Otras visualizaciones incluye algunas que sí parecen ser reproducibles.

Sobre las banderas de playbin

La activación de la bandera de visualización en el Programa play-vis ocurre en estas líneas:

# Set the visualization flag.
flags = pipeline.get_property("flags")
vis_flag = (1 << 3)
flags |= vis_flag
pipeline.set_property("flags", flags)

Pero, ¿por qué (1 << 3)? Las banderas que reconoce el conducto playbin están definidas en la documentación de uno de sus elementos: playsink, como parte de la constante GstPlayFlags, que dice:

video (0x00000001) – Render the video stream
audio (0x00000002) – Render the audio stream
text (0x00000004) – Render subtitles
vis (0x00000008) – Render visualisation when no video is present
soft-volume (0x00000010) – Use software volume
native-audio (0x00000020) – Only use native audio formats
native-video (0x00000040) – Only use native video formats
download (0x00000080) – Attempt progressive download buffering
buffering (0x00000100) – Buffer demuxed/parsed data
deinterlace (0x00000200) – Deinterlace video if necessary
soft-colorbalance (0x00000400) – Use software color balance
force-filters (0x00000800) – Force audio/video filter(s) to be applied
force-sw-decoders (0x00001000) – Force only software-based decoders (no effect for playbin3)
    

El conducto activa varias de estas predeterminadamente, como se ve al inspeccionar el valor devuelto por pipeline.get_property("flags"):

<flags Render the video stream | Render the audio stream | Render subtitles | Use software volume | Deinterlace video if necessary | Use software color balance of type __main__.GstPlayFlags>

Otras visualizaciones

Para usar un tipo de visualización diferente se modifica la propiedad vis-plugin del conducto. Así:

pipeline.set_property("vis-plugin", plugin)

Donde plugin debe ser un objeto Gst.Element que represente alguno de los complementos de visualización del registro de complementos de GStreamer.

Los siguientes programas muestran cómo inspeccionar el registro de complementos y cómo seleccionar uno de ellos para generar otro tipo de visualización.

Primero miremos cómo ver el registro de todos los complementos, sin importar si son de visualización de audio o no:

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst


# Initialize GStreamer.
Gst.init(None)

# Show available plugins.
registry = Gst.Registry.get()
plugins = registry.get_feature_list(Gst.ElementFactory)

print("{} Plugins found.".format(len(plugins)))
for plugin in plugins:
    name = plugin.get_plugin_name()
    long_name = plugin.get_metadata("long-name")
    print("NAME:  {}: {}".format(name, long_name))
    print("KLASS: {}".format(plugin.get_metadata("klass")))
    print("-" * 10)
      
Programa show-plugins. Lista todos los complementos del registro de GStreamer.

Ejecutemos el Programa show-plugins:

python3 show-plugins.py

Se listan 601 complementos de la siguiente manera:

601 Plugins found.
NAME:  staticelements: Generic bin
KLASS: Generic/Bin
----------
NAME:  staticelements: Pipeline object
KLASS: Generic/Bin
----------
NAME:  sndfile: Sndfile decoder
KLASS: Decoder/Audio
----------
NAME:  vorbis: Vorbis audio encoder
KLASS: Codec/Encoder/Audio

...

NAME:  xvimagesink: Video sink
KLASS: Sink/Video
----------
NAME:  audiovisualizers: Stereo visualizer
KLASS: Visualization
...
    

De todos estos complementos solo nos interesan los de la klase Visualization, que se pueden filtrar como se muestra en este otro programa:

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst


# FUNCTIONS
# =========

def is_visualizer(feature):
    """Return True if FEATURE's klass is Visualization."""
    return (
        isinstance(feature, Gst.ElementFactory) and
        "Visualization" in feature.get_metadata("klass")
    )


# MAIN
# ====

# Initialize GStreamer.
Gst.init(None)

# Show available visualization plugins.
registry = Gst.Registry.get()
vis_plugins = registry.feature_filter(is_visualizer, False)

print("{} Visualization plugins found.".format(len(vis_plugins)))
for plugin in vis_plugins:
    name = plugin.get_plugin_name()
    long_name = plugin.get_metadata("long-name")
    print("NAME:  {}: {}".format(name, long_name))
    print("KLASS: {}".format(plugin.get_metadata("klass")))
    print("-" * 10)
      
Programa show-vis-plugins. Lista solo los complementos de visualización disponibles en el registro de GStreamer.

Ejecutemos el Programa show-vis-plugins:

python3 show-vis-plugins.py

Ahora se listan solo 7 complementos:

7 Visualization plugins found.
NAME:  goom: GOOM: what a GOOM!
KLASS: Visualization
----------
NAME:  goom2k1: GOOM: what a GOOM! 2k1 edition
KLASS: Visualization
----------
NAME:  monoscope: Monoscope
KLASS: Visualization
----------
NAME:  audiovisualizers: Stereo visualizer
KLASS: Visualization
----------
NAME:  audiovisualizers: Frequency spectrum scope
KLASS: Visualization
----------
NAME:  audiovisualizers: Synaescope
KLASS: Visualization
----------
NAME:  audiovisualizers: Waveform oscilloscope
KLASS: Visualization
----------
    

La Figura 2 muestra el tipo de visualización que genera cada uno de estos complementos.

Figura 2
Figura 2. Fotogramas de ejemplo de cada una de las siete visualizaciones disponibles en el registro de complementos del entorno de software usado en este documento en particular.

En la Figura 2 las áreas de visualización aparecen del mismo tamaño, pero, en realidad, el área varía según el tipo de visualización. No encontré forma de cambiar el área, que es algo que necesito para la aplicación que pienso darle a estas visualizaciones, pero seguramente es posible hacerlo.

Ya con la lista de visualizadores, podemos seleccionar el que queramos. La forma de hacerlo no es muy conveniente porque parece que los complementos no tienen identificadores únicos. El siguiente programa selecciona el complemento de visualización por nombre corto, por ejemplo audiovisualizers, o por nombre largo, por ejemplo, Waveform oscilloscope:

import os
import sys

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst


# CONSTANTS
# =========

DEFAULT_VIS_PLUGIN = "Waveform oscilloscope"


# FUNCTIONS
# =========

def get_vis_plugin(name):
    """Return the visualization plugin called NAME or None."""
    registry = Gst.Registry.get()
    vis_plugins = registry.feature_filter(is_visualizer, False)
    vis_plugin = None

    for plugin in vis_plugins:
        short_name = plugin.get_plugin_name()
        long_name = plugin.get_metadata("long-name")
        if short_name == name or long_name == name:
            vis_plugin = plugin.create()

    return vis_plugin


def is_visualizer(feature):
    """Return True if FEATURE's klass is Visualization."""
    return (
        isinstance(feature, Gst.ElementFactory) and
        "Visualization" in feature.get_metadata("klass")
    )


# MAIN
# ====

# Initialize GStreamer.
Gst.init(None)

# Define audio file location.
audio_file_path = os.path.abspath("song18.mp3")

# Build a playbin pipeline.
textual_pipeline = "playbin uri=file://{}".format(audio_file_path)
pipeline = Gst.parse_launch(textual_pipeline)

# Set the visualization flag.
flags = pipeline.get_property("flags")
vis_flag = (1 << 3)
flags |= vis_flag
pipeline.set_property("flags", flags)

# Set visualization plugin.
vis_plugin = get_vis_plugin(DEFAULT_VIS_PLUGIN)

if len(sys.argv) == 2:
    # A plugin name was passed as argument on CLI, use it.
    vis_plugin = get_vis_plugin(sys.argv[1])

pipeline.set_property("vis-plugin", vis_plugin)

# Start playing.
pipeline.set_state(Gst.State.PLAYING)

# Wait until error or EOS.
bus = pipeline.get_bus()
message = bus.timed_pop_filtered(
   Gst.CLOCK_TIME_NONE,
   Gst.MessageType.ERROR | Gst.MessageType.EOS
)

# Free resources.
pipeline.set_state(Gst.State.NULL)
      
Programa play-another-vis. Reproduce el archivo de audio song18.mp3 con la visualización del complemento Waveform oscilloscope de audiovisualizers.

Ejecutemos el Programa play-another-vis:

python3 play-another-vis.py

Debería verse un resultado como el de la Figura 2, fotograma G. Para ver otra visualización, se puede pasar como argumento el nombre corto o largo del complemento deseado. Por ejemplo:

python3 play-another-vis.py "Frequency spectrum scope"

Pendiente

Integrar la visualización en una aplicación con interfaz gráfica escrita en GTK.

Temas relacionados: