GStreamer mit perl

ToPeG

Grünschnabel
Seit zweit Tagen versuche ich tiefer in die Programmierung von GStreamer ein zu steigen. Einfaches mit "Playbin" oder kurze Pipelines funktionieren. Doch nun habe ich versucht dieses in perl um zu setzten:
Code:
gst-launch filesrc location=test.avi ! avidemux name=demux  demux.audio_00 ! queue ! fakesink   demux.video_00 ! queue ! decodebin ! ffmpegcolorspace ! videoscale ! xvimagesin
über gst-launch funktioniert das und das video wird angezeigt.
Die Ausgabe:
Code:
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
Got EOS from element "pipeline0".
Execution ended after 19959987232 ns.
Setting pipeline to PAUSED ...
Setting pipeline to READY ...
Setting pipeline to NULL ...
Freeing pipeline ...

Doch das selbe in perl funktioniert nicht. Das heißt ich bekomme kein Bild angezeigt und das Script beendet sich nicht wenn das Video abgelaufen sein sollte.

Der Code:
Code:
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Glib qw(TRUE FALSE);
use GStreamer;

my $file='test.avi';

my $loop = Glib::MainLoop -> new(undef, FALSE);
GStreamer->init();
my $pipeline = GStreamer::Pipeline -> new("mypipeline");
$pipeline->get_bus()->add_watch( \&my_bus_callback, [$loop, $pipeline]);

my (
    $src,$demux,
    $vqueue,$vdecode,$vfilter1,$vfilter2,$vsink,
    $aqueue,$asink,
  ) = GStreamer::ElementFactory -> make(
    filesrc          => "data_src",
    avidemux         => "data_dmux",
    queue            => "video_queue",
    decodebin        => "video_decoder",
    ffmpegcolorspace => "video_collorspace",
    videoscale       => "video_scale",
    xvimagesink      => "video_sink",
    queue            => "audio_queue",
    fakesink         => "audio_sink",
  );

$pipeline->add($src,$demux);
$src->link($demux);

$pipeline->add($vqueue,$vdecode,$vfilter1,$vfilter2,$vsink);
$vqueue->link($vdecode,$vfilter1,$vfilter2,$vsink);

$pipeline->add($aqueue,$asink);
$aqueue->link($asink);

$demux->signal_connect(pad_added => \&new_pad, [$vqueue,$aqueue]);


$src->set(location => $file);
$pipeline->set_state("playing");
$loop->run();
$pipeline->set_state("null");

########################################################################

sub new_pad
{
  my $element=shift;
  my $pad=shift;
  my ($video_link_to, $audio_link_to)=@{shift()};

  if($pad->get_name() eq 'video_00')
  {
    $pad->link($video_link_to->get_pad('sink'));
    print "link ".$element->get_name()." video-pad to ".$video_link_to->get_name()."\n";
  }
  elsif($pad->get_name() eq 'audio_00')
  {
    $pad->link($audio_link_to->get_pad('sink'));
    print "link ".$element->get_name()." audio-pad to ".$audio_link_to->get_name()."\n";
  }
}

sub my_bus_callback
{
  my $bus=shift;
  my $message=shift;
  my ($loop, $pipe)=@{shift()};

  if ($message -> type eq "warning")
  {
    print STDERR $message->error()."\n";
    print STDERR $message->debug()."\n";
  }

  elsif ($message->type eq "error")
  {
    print STDERR $message->error()."\n";
    print STDERR $message->debug()."\n";
    $loop -> quit();
  }

  elsif ($message->type eq "eos")
  { $loop -> quit(); }

  elsif($message->type eq "state-changed")
  {
    my $src=$message->src()->get_name() || '[UNKNOWN]';
    print qq(Object "$src" ).$message->new_state()."\n";
  }

  # remove message from the queue
  return TRUE;
}

Die Ausgabe:
Code:
Object "audio_sink" ready
Object "video_sink" ready
Object "audio_queue" ready
Object "video_scale" ready
Object "video_collorspace" ready
Object "fakesink" ready
Object "typefind" ready
Object "video_decoder" ready
Object "video_queue" ready
Object "data_dmux" ready
Object "data_src" ready
Object "mypipeline" ready
Object "audio_queue" paused
Object "video_scale" paused
Object "video_collorspace" paused
Object "typefind" paused
Object "video_queue" paused
Object "data_dmux" paused
Object "data_src" paused
link data_dmux video-pad to video_queue
link data_dmux audio-pad to audio_queue
Object "audio_sink" paused
Object "ffdec_h2630" ready
Object "ffdec_h2630" paused
Object "fakesink" paused
Object "video_decoder" paused
Object "fakesink" ready
Object "fakesink" null

Ich hoffe jemand kann mir weiter helfen.
Danke
 
Nach einigen versuchen kann ich sagen, dass sich "Glib::MainLoop" und GStreamer nicht vertragen. Aus irgend einem Grund scheint GStreamer oder MainLoop nicht Threadsave oder inkompatibel zu sein. Nicht nur das sich einiges nicht starten lässt, auch kommt es immer immer wieder zu Kopierfehlern zwischen den Threads und damit sporadisch Fehlermeldung . Nach viel probieren bin ich nun zu so einer Lösung gekommen:

Das Script kopiert ein Frame aus einem Video und speichert es als jpg.
Code:
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Glib qw(TRUE FALSE);
use GStreamer -init;

# eingabe datei
my $file='test.avi';
# ausgabe datei
my $ofile='test.jpg';
# position im video
my $pos=3_000_000_000; # ns
# höhe /breite
my $scale_w=720;
my $scale_h=510;

my @scales;
push(@scales,"width=$scale_w") if($scale_w);
push(@scales,"height=$scale_h") if($scale_h);

#pipeline erzeugen
my $pipeline = GStreamer::parse_launch(sprintf(
        'filesrc location=%s ! avidemux name=data_demux '.
        'data_demux.audio_00 ! queue ! fakesink '.
        'data_demux.video_00 ! decodebin name=video_decode ! ffmpegcolorspace '.
        '! videoscale ! video/x-raw-yuv,%s ! jpegenc ! fakesink name=video_sink signal-handoffs=TRUE ',
      $file,join(',',@scales)));

# der pipeline einen Namen geben
$pipeline->set_name('my_pipeline');

# Puffer für die Ausgabe
my $buffer=undef;

# Listener für Frame einrichten
my $video_sink=$pipeline->get_by_name('video_sink');
$video_sink->signal_connect(handoff => sub{
    return TRUE if($buffer);

    my $query=GStreamer::Query::Position->new('time');
    $video_sink->query($query);
    my (undef, $pos_now)=$query->position;

    return TRUE if($pos_now<$pos);

    $buffer=$_[1];

    return TRUE;
  });

# video abspielen
$pipeline->set_state("playing");
my $seek=1;
my $bus=$pipeline->get_bus();
while (1)
{
  # alle nahrichten vom bus holen
  my $message = $bus -> poll("any", 0);

  # Schleife beenden wenn wir ein Bild haben
  last if($buffer);

  # wenn keine Nachrichten warten
  unless (defined $message)
  {
    select(undef,undef,undef,0.25);
    next;
  }


  # beenden wenn das Video am Ende ist,
  # oder ein Fehler auftrat
  last if (
            $message->type & "eos"     ||
            $message->type & "error"   ||
            $message->type & "warning"
          );


  # wenn sich det Zusand eines pileelemts geändert hat
  if($message->type & "state-changed")
  {
    # Name des Elementes ermitteln
    my $name=$message->src()->get_name() || 'UNDEF';
    #print "$name => ".$message->new_state()."\n";

    # es wurde noch nicht zu der stelle gespringen,
    # der Name stimmt und die pipeline ist "playing"
    if($seek && $name eq 'my_pipeline' && $message->new_state() eq 'playing')
    {
      # zur gewünschten Stelle springen
      $pipeline->seek(1,'time','flush','set',$pos,'none',0) || last;
      $seek--;
    }
  }
}
# pipeline stoppen
$pipeline->set_state("null");

# wenn ein bild da ist
if($buffer)
{
  # bild in Datei schreiben.
  open(my $fh, '>', $ofile) or die("Error open $ofile! ($!)");
  binmode($fh);
  print $fh $buffer->data();
  close($fh);
}
 
Ich konnte mich nicht damit zufrieden geben.
Nach einigem Herumprobieren habe ich auch eine Lösung ohne "parse_launch" gefunden. Das Problem scheint zu sein, dass das Plugin "parsebin" es nicht mag als reiner Videodecoder eingesetzt zu werden. Ich vermute mal das "parse_launch" erkennt wenn ein Plugin mehr als einen "pad" anbietet und das anders behandelt. Die von "Hand" erzeugte Pipeline beachtete das gar nicht. Wenn Ich "parsebin" auch als Demuxer einsetze funktioniert alles wie es soll.

Ein Hinweis in der Richtung wäre sicherlich sinnvoll.

Code:
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Glib qw(TRUE FALSE);
use GStreamer -init;

# eingabe datei
my $file='test.avi';
# ausgabe datei
my $ofile='test.jpg';
# position im video
my $pos=3_000_000_000; # ns
my $scale_w=720;
my $scale_h=210;

my @scales=('video/x-raw-yuv');
push(@scales,"width=$scale_w") if($scale_w);
push(@scales,"height=$scale_h") if($scale_h);

#pipeline erzeugen
my $pipeline = GStreamer::Pipeline->new("my_pipeline");
my( $src, $decoder,
    $vcolor, $vscale, $vcaps, $vjpeg, $vsink,
    $aqueue, $asink ) =
  GStreamer::ElementFactory -> make(
    filesrc          => "data_src",
    decodebin        => "data_decoder",

    ffmpegcolorspace => "video_colorspace",
    videoscale       => "video_scale",
    capsfilter       => "video_caps",
    jpegenc          => "video_jpeg",
    fakesink         => "video_sink",

    queue            => "audio_queue",
    fakesink         => "audio_sink",
  );

$pipeline->add($src, $decoder, $vcolor, $vscale, $vcaps, $vjpeg, $vsink, $aqueue, $asink);

$src   ->link($decoder);
$vcolor->link($vscale, $vcaps, $vjpeg, $vsink);
$aqueue->link($asink);

$vcaps->set('caps', GStreamer::Caps->from_string(join(', ',@scales)));

$pipeline->get_by_name('data_decoder')->signal_connect(pad_added => sub{
    my $element=shift;
    my $pad=shift;

    my $type='unknown';
    $type=lc($1) if($pad->get_caps()->to_string()=~m!^([^/]+)!);

    if($type eq 'video' || $type eq 'image')
    {
      $pad->link($vcolor->get_pad('sink'));
      #print "link ".$element->get_name()." video (".$pad->get_name().") to ".$vcolor->get_name()."\n";
    }
    elsif($type eq 'audio')
    {
      $pad->link($aqueue->get_pad('sink'));
      #print "link ".$element->get_name()." audio (".$pad->get_name().") to ".$aqueue->get_name()."\n";
    }
    else
    { print "not link $type from ".$pad->get_name()."\n"; }
  });
$src->set('location' => $file);

# Puffer für die Ausgabe
my $buffer=undef;

# Listener für Frame einrichten
$vsink->set('signal-handoffs' => TRUE);
$vsink->signal_connect(handoff => sub{
    return TRUE if($buffer);

    my $query=GStreamer::Query::Position->new('time');
    $vsink->query($query);
    my (undef, $pos_now)=$query->position;

    return TRUE if($pos_now<$pos);

    $buffer=$_[1];

    return TRUE;
  });

# video abspielen
$pipeline->set_state("playing");
my $seek=1;
my $bus=$pipeline->get_bus();
while (1)
{
  # alle nahrichten vom bus holen
  my $message = $bus -> poll("any", 0);


  # wenn keine Nachrichten warten
  unless (defined $message)
  {
    # Schleife beenden wenn wir ein Bild haben
    last if($buffer);

    select(undef,undef,undef,0.25);
    #print "WAIT ".time()."\n";
    next;
  }


  # beenden wenn das Video am Ende ist,
  # oder ein Fehler auftrat
  last if ( $message->type & "eos");
  if ( $message->type & "error"   || $message->type & "warning" )
  {
    print "ERROR:\n";
    print $message->error()."\n";
    print $message->debug()."\n";
    last;
  }


  # wenn sich det Zusand eines pileelemts geändert hat
  if($message->type & "state-changed")
  {
    # Name des Elementes ermitteln
    my $name=$message->src()->get_name() || 'UNDEF';
    #print "$name => ".$message->new_state()."\n";

    # es wurde noch nicht zu der stelle gespringen,
    # der Name stimmt und die pipeline ist "playing"
    if($seek && $name eq 'my_pipeline' && $message->new_state() eq 'playing')
    {
      # zur gewünschten Stelle springen
      $pipeline->seek(1,'time','flush','set',$pos,'none',0) || last;
      $seek--;
    }
  }
}
# pipeline stoppen
$pipeline->set_state("null");

# wenn ein bild da ist
if($buffer)
{
  # bild in Datei schreiben.
  #open(my $fh, '>', $ofile) or die("Error open $ofile! ($!)");
  open(my $fh, '| display -') or die("Error open $ofile! ($!)");
  binmode($fh);
  print $fh $buffer->data();
  close($fh);
}


EDIT:
Ich denke so würde das auch über "Glib::MainLoop" funktionieren.
 
Zuletzt bearbeitet:
Zurück