Ein Videomixer in Perl

Max Maischein

Frankfurt.pm

Ein Videomixer in Perl

Generative Videoinstallationen faszinieren mich:

  • Videoschleifen

  • Immer wiederholend

  • Aber nicht immer gleich

Videos zusammensetzen mit Perl

  • Offline kann ich Videos zusammensetzen mit ffmpeg

  • Das macht aber keinen Spass

  • Die Videos können nicht verfremdet werden

  • Ich kann keine Live-Bilder von der Webcam einbinden

OpenGL

  • OpenGL war ursprünglich ein fester endlicher Automat

  • zur Darstellung von 3D Grafik

  • 3D Grafik ist etwas mehr als 2D Grafik (wie Videos)

OpenGL (2)

  • Damit kann ich mehrere Videos leicht überlagern

     1:                          +-Bildschirm-+
     2:  +--------+                 ^      ^
     3:  |  Logo  |----------+      |      |
     4:  +--------+          |
     5:                      +-> +----Logo----+
     6:  +--------+
     7:  | Webcam |------------> +---Webcam---+
     8:  +--------+
     9:                      +-> +---Video1---+
    10:  +--------+          |
    11:  | Video1 |----------+
    12:  +--------+
  • Und das mit Hardwareunterstützung, statt die Berechnung in Perl zu machen

OpenGL

  • Die Render-Pipeline von OpenGL war ursprünglich fest vorgegeben

     1:                          +-Bildschirm-+   3. Rendering
     2:  +--------+                 ^      ^
     3:  | Textur2|----------+      |      |
     4:  +--------+          |
     5:                      +-> +---Textur1--+   2. Texturing
     6:  +--------+
     7:  | Textur2|------------> +---Textur2--+   2. Texturing
     8:  +--------+
     9:                      +-> +---Textur3--+   2. Texturing
    10:  +--------+          |
    11:  | Textur3|----------+   +---Objekt---+   1. Raster
    12:  +--------+

OpenGL Pipeline

  • Es gab nur wenige, vorgegebene Funktionen zum Kombinieren von Pixeln bei der Texturierung:

  • Addition, Multiplikation, Subtraktion, OR, XOR, AND

  • Maskierung (->Logo)

OpenGL Shader

  • Seit OpenGL 2.0 (und jetzt OpenGL 3.0) kann man die komplette Pipeline selber programmieren ("Shader")

  • Jeder Bildpunkt im endgültigen Bild ist im Wesentlichen unabhängig von allen anderen Bildpunkten

  • Parallele Verarbeitung von vielen unabhängigen Bildpunkten von Perl aus

  • Interessante Verfremdungs- und Effektfilter

OpenGL Shader

  • Schwarzweissfilter

  • Kantenerkennung

  • Chromakey / Bluescreen / Greenscreen

  • Statischen Hintergrund vom Bild abziehen

  • Crossfades zwischen Filmen

  • Zwei Filme anhand einer Maske/eines dritten Films ineinander übergehen lassen

Das Programm

  • CPU
     1:             AVI               RGB          
     2:  +--------+      +----------+      +------+
     3:  | Video  |      |          |      |      |
     4:  | quelle | ---> |  ffmpeg  | ---> | Perl | -->
     5:  +--------+      +----------+      +------+    
  • OpenGL/GPU
     1:       +--------+     +--------+      +--------+
     2:       |        |     | OpenGL |      | Bild-  |
     3:   --> | Textur | --> | Shader | ---> | schirm |
     4:       +--------+     +--------+      +--------+
  • Perl
     1:                        ^^^
     2:  Parameter steuerbar über Perl

Perl Filter

  • (Ring)Puffer

  • Speichert die letzten 20 Einzelbilder zwischen

  • Aus ffmpeg (schnell)

  • Von der Grafikkarte (langsam)

Anwendungen für den Puffer

Delay (1 s)

 1:  +-------+     +--------------------+     +--------+
 2:  | Video | --> | Puffer (20 Bilder) | --> | OpenGL |
 3:  +-------+     +--------------------+     +--------+

Filter "Nervös"

"Nervös"

 1:  +-------+     +--------------------+
 2:  | Video | --> | Puffer (20 Bilder) | 
 3:  +-------+     +--------------------+
 4:                  ^                      +--------+
 5:                  +----rand(20)--------> | OpenGL |
 6:                                         +--------+

Filter "Feedback"

Feedback

 1:  +---------------+    +-------------+
 2:  | Puffer        | -> | OpenGL      | -+-> Ausgabe
 3:  | (1-...Bilder) |    | Verfremdung |  |
 4:  +---------------+    +-------------+  |
 5:            ^                           |
 6:            +--------<--Kopie----<------+

Der Code

  • OpenGL - OpenGL Anbindung

  • OpenGL::Shader - OpenGL Shader/Filter

  • Source::FFmpeg - der Decoder für die Videostreams

  • Filter::Buffer - Ringpuffer für Feedback/Delay

Das Hauptprogramm

1. Filme öffnen (Source::FFmpeg)

2. Mainloop

 1:    my $curr_texture;
 2:    for my $movie (@movies) {
 3:        # Bild in Grafikkarte laden
 4:        $curr_texture = $movie->tick();
 5:        # Bild in Ringpuffer laden
 6:        ...
 7:        
 8:        # Textur-Ping-Pong
 9:        for my $filter (@active_filters) {
10:            $curr_texture = $filter->render($texture);
11:        };
12:    };

Source::FFmpeg

  • Startet ffmpeg mit ein paar Kommandozeilenparametern

  • ffmpeg gibt den Film nach STDOUT aus

  • Liefert ein Filehandle zurück, aus dem die einzelnen Bilder gelesen werden

Filminformationen lesen

 1:  sub stream_info {
 2:    my ($self,$filename) = @_;
 3:    my ($child_in, $stream, $info);
 4:    my $cmd = sprintf
 5:        qq{bin\\ffmpeg-old.exe -t 0 -i "%s" -},
 6:        $filename;
 7:    my $pid = open3 $child_in, $stream, $stream, $cmd
 8:        or die "Couldn't spawn '$cmd': $!/$?";

Filminformationen lesen (2)

 1:    while (my $line = <$stream>) {
 2:        #print ">>$line";
 3:        if ($line =~ /Video: .*/) {
 4:            chomp $line;
 5:            print ">$line<\n";
 6:            my ($width,$height) = $line =~ /(\d+)x(\d+)/;
 7:            return ($width,$height);
 8:        };
 9:    };
10:  };

Film einlesen

Parameterüberprüfung

 1:  sub new {
 2:    my ($class,%args) = @_;
 3:    my $file = delete $args{filename};
 4:    die "No file: '$file'"
 5:        unless -f $file;
 6:    warn "$file seems valid.";

Film einlesen (2)

Die Informationen über den Film werden gleich beim Start eingelesen

 1:    my ($width,$height) = $class->stream_info($file);
 2:    
 3:    my $cmd = sprintf
 4:          qq{bin\\ffmpeg-old.exe -i "%s" }
 5:        . qq{-f rawvideo -pix_fmt rgb24 - |},
 6:        $file;
 7:    my $pid = open my $stream, "$cmd"
 8:        or die "Couldn't spawn '$cmd': $!/$?";
 9:    binmode $stream;

...

Einzelbild lesen

tick() wird aufgerufen, wenn ein neues Bild eingelesen werden soll

 1:  sub tick {
 2:    my ($self) = @_;
 1:    # Textur anlegen
 2:    my $texture_id = $self->texture_id;
 3:    if (! $texture_id) {
 4:        ($texture_id) = glGenTextures_p(1);
 5:        $self->texture_id($texture_id);
 6:    };

Einzelbild lesen (2)

 1:    # Daten lesen
 2:    my $frame;
 3:    read $self->stream, $frame, $self->width * $self->height * $self->depth
 4:        or die "Read failure";
 5:    
 6:    # Übergeben an OpenGL
 7:    OpenGL::Tools::set_texture_pixels(
 8:        texture => $texture_id,
 9:        pixels => $frame,
10:        ...
11:    );

Aufräumen

Wenn das Objekt weggeworfen wird, und ffmpeg noch läuft, auch mit ffmpeg aufräumen.

 1:  sub DESTROY {
 2:    if (my $pid = $_[0]->pid) {
 3:        kill 9 => $pid
 4:    };
 5:  };

Live Demo

Fällt aus, da mein Notebook nur OpenGL 1.3 kann :(

Nachteile von FFmpeg+OpenGL

  • Kein Sound

  • Keine Pause/Vorwärts/Zurückspulen

  • Kein (bequemes) Speichern in neuem Format

  • Benötigt OpenGL, also nicht Server-fähig

Vorteile von FFmpeg+OpenGL

  • Kein Sound

  • Keine Pause/Vorwärts/Zurückspulen

  • Einfache Installation

Danke

Fragen?