6/20/2016

GSTREAMER - PYTHON 예제분석 및 다양한 예제

1. Gstreamer Programing  

Gstreamer을  gst-launch로 pipeline으로 연결하여 실행도 가능하지만, Gstreamer을 C나 python을
이용하여 간단히 Programming이 가능하다. 이때 UBUNTU에서 GTK이 이용을 한다.

Gstreamer 관련설명 및 관련 C기반 예제 
  http://blog.daum.net/basetechnology/6998023

일반 Gstreamer 의 플러그들 
  https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-libs/html/

지금 현재 GSTREAM로 PHYTHON을 지원하는 것은 UBUNTU에서 지원을 해준다.
  https://wiki.ubuntu.com/Novacut/GStreamer1.0#Using_GStreamer_1.0_from_Python


1.1  Gstreamer Python 준비

아래의 Python을 하기 위해서는 반드시 Python version 3이며, 아래와 같이 설치가 필요하다.


$ sudo apt-get install python-gi python3-gi \
    gstreamer1.0-tools \
    gir1.2-gstreamer-1.0 \
    gir1.2-gst-plugins-base-1.0 \
    gstreamer1.0-plugins-good \
    gstreamer1.0-plugins-ugly \
    gstreamer1.0-plugins-bad \
    gstreamer1.0-libav


처음 프로그래밍하기 전에 주의해야 할 사항은 다음과 같다.
1. python version 확인
2. 띄어쓰기 문제
3. #!/usr/bin/env python 문제


만약 Ubuntu를 사용한다면 Ubuntu version에따라 Gstreamer version이 지원이 다를수 있으니, 아래 사이트를 참고하자.
gstremer0.10 용 video player 와 gstreamer 1.0 용 video player는 다르다.
(video player 각 예제가 ubuntu에서 동작되는 것을 기본적으로 확인했음)

  https://wiki.ubuntu.com/Novacut/GStreamer1.0#Adding_PPA_for_Ubuntu_Precise


1.2 Python 관련기본자료 


  • Python 관련 기본설명 
  https://ahyuo79.blogspot.com/2016/06/python-and-django.html

  • GSTREAMER-PYTHON TUTORIAL 
  http://brettviren.github.io/pygst-tutorial-org/pygst-tutorial.pdf

  • Gst-1.0  (Function 과 Hierarchy 로 구조파악)
  https://lazka.github.io/pgi-docs/#Gst-1.0

  • Python GOBJECT
  https://pygobject.readthedocs.io/en/latest/

  • Python GTK
  https://python-gtk-3-tutorial.readthedocs.io/en/latest/


솔직히 Python Gstreamer은 gst-lanuch을 다룬다면, 프로그래밍을 하기가 쉬운거 같다.
아래와 같이 시작 매뉴얼을 익히고, 기본 GTK 이나, QT에 연결하여 하면 되는 것 같다.
다만 문제가 최종동작이 되는지는 보장이 되는지는 모르겠다.


gst-lanuch와 동일하게 Gst.ElementFactory.make() 에 PlugIns의 Element의 정보를 넣으면 된다.

Gstreamer 관련 사항은 Tutorial 에 나와 있지만, 관련사항을 다음과 같이 정리한다.

  1. Pipeline을 우선 만들고, 
  2. add로 추가한다
  3. link로 연결한다.
  4. connect 는 일종의 call back function이다.

manual의 4.3 connection에서 event와 bus 부분을 보면 쉽게 개념을 잘 이해 할 수 있다.
일종의 Timing 맞추기 위해서 사용한다고 보면된다.

아래의 상태는 Gstreamer의 상태를 말하며, pipeline의 상태를 말한다.

Gst.State.PAUSE
Gst.State.PLAYING


GTK관련 Example Manual 보면 Gst.parse_launch 있는데, 사용법이 독특하다.
사용하기도 편한데 아직 다뤄보지 못했다.


1.3  Python Gstremer ex-1

아래의 예제는 위의 pygst-tutorial-org/pygst-tutorial.pdf에서 가져온것이며,실제로 실행본 것이 아니다.
하지만 대충 보면 쉽게 이해가기 때문에 일단 정리한다. 

#!/usr/bin/env python
import os
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk

### GTK Main Class로 구성 및 이기반으로 실행 
class GTK_Main(object):
     
   def __init__(self):
          window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
          window.set_title("Mpeg2-Player")
          window.set_default_size(500, 400)
          window.connect("destroy", Gtk.main_quit, "WM destroy")
          vbox = Gtk.VBox()
          window.add(vbox)
          hbox = Gtk.HBox()
          vbox.pack_start(hbox, False, False, 0)
          self.entry = Gtk.Entry()
          hbox.add(self.entry)
          self.button = Gtk.Button("Start")
          hbox.pack_start(self.button, False, False, 0)
          self.button.connect("clicked", self.start_stop)
          self.movie_window = Gtk.DrawingArea()
          vbox.add(self.movie_window)
          window.show_all()

          self.player = Gst.Pipeline.new("player")
          source = Gst.ElementFactory.make("filesrc", "file-source")
          demuxer = Gst.ElementFactory.make("mpegpsdemux", "demuxer")
          demuxer.connect("pad-added", self.demuxer_callback)      # callback function 
          self.video_decoder = Gst.ElementFactory.make("mpeg2dec", "video-decoder")
          self.audio_decoder = Gst.ElementFactory.make("mad", "audio-decoder")
          audioconv = Gst.ElementFactory.make("audioconvert", "converter")
          audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output")
          videosink = Gst.ElementFactory.make("autovideosink", "video-output")
          self.queuea = Gst.ElementFactory.make("queue", "queuea")
          self.queuev = Gst.ElementFactory.make("queue", "queuev")
          colorspace = Gst.ElementFactory.make("videoconvert", "colorspace")

####pipleline 추가 

          self.player.add(source)
          self.player.add(demuxer)
          self.player.add(self.video_decoder)
          self.player.add(self.audio_decoder)
          self.player.add(audioconv)
          self.player.add(audiosink)
          self.player.add(videosink)
          self.player.add(self.queuea)
          self.player.add(self.queuev)
          self.player.add(colorspace)


          source.link(demuxer)


          self.queuev.link(self.video_decoder)
          self.video_decoder.link(colorspace)
          colorspace.link(videosink)

          self.queuea.link(self.audio_decoder)
          self.audio_decoder.link(audioconv)
          audioconv.link(audiosink)

          bus = self.player.get_bus()
          bus.add_signal_watch()
          bus.enable_sync_message_emission()
          bus.connect("message", self.on_message)
          bus.connect("sync-message::element", self.on_sync_message)


   def start_stop(self, w):

      if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                 filepath = os.path.realpath(filepath)
                 self.button.set_label("Stop")
                 self.player.get_by_name("file-source").set_property("location", filepath)
                 self.player.set_state(Gst.State.PLAYING)
            else:
                 self.player.set_state(Gst.State.NULL)
                 self.button.set_label("Start")


   def on_message(self, bus, message):
      t = message.type
      if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
      elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

   def on_sync_message(self, bus, message):
      if message.get_structure().get_name() == 'prepare-window-handle':
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            xid = self.movie_window.get_property('window').get_xid()
            imagesink.set_window_handle(xid)

   def demuxer_callback(self, demuxer, pad):
      if pad.get_property("template").name_template == "video_%02d":
            qv_pad = self.queuev.get_pad("sink")
            pad.link(qv_pad)
      elif pad.get_property("template").name_template == "audio_%02d":
           qa_pad = self.queuea.get_pad("sink")
           pad.link(qa_pad)
####

#### Main Call 
Gst.init(None)
GTK_Main()
GObject.threads_init()
Gtk.main()


1.4  Python simple example 수정방법

아래의 example은 video-player-1.0을 기반으로 돌아가는 video-player를 rtsp용으로 본인이 변경한 것이다.
이전에 다른회사에서 요청이 와서 관련부분들을 고쳤는데, 간단한 것만 정리한다. 

Jeston TK1/TX1 기반으로 작성한 것이므로, 일반 Ubuntu에서는 동작이 되지 않는다.
아래와 같이 Jeston TK1/TX1에서 gstreamer로 확인한 후, phython으로 아래와 같이 변경하였다.

$ gst-launch-1.0 -v rtspsrc location=rtsp://192.168.1.168:8557/PSIA/Streaming/channels/2?videoCodecType=H.264 caps="video/x-h264,mapping=/video " \
! rtph264depay ! h264parse  ! omxh264dec ! nveglglessink -e


main은 p = Player()  p.run() 이며 아래소스와 상위 command 를 보면 대충  쉽게 이해가며 각 필요한 부분들은 자신에 맞게 수정하도록하자 
나의 경우는 각 Jetson에 맞게 수정하였으며, 각 Jetson의 Manual을 참조해야함 

#!/usr/bin/python3
# http://bazaar.launchpad.net/~jderose/+junk/gst-examples/view/head:/video-player-1.0

from os import path

import gi
gi.require_version('Gst', '1.0')
from gi.repository import GObject, Gst, Gtk

# Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively:
from gi.repository import GdkX11, GstVideo

GObject.threads_init()
Gst.init(None)

WORKAROUND_SLEEP_SEC = 0.5
#WORKAROUND_SLEEP_SEC = 0.0

class Player(object):
    def __init__(self):
        self.window = Gtk.Window()
        self.window.connect('destroy', self.quit)
        self.window.set_default_size(800, 450)

        self.drawingarea = Gtk.DrawingArea()
        self.window.add(self.drawingarea)

        # Create GStreamer pipeline
        self.pipeline = Gst.Pipeline()

        # Create bus to get events from GStreamer pipeline
        self.bus = self.pipeline.get_bus()
        self.bus.add_signal_watch()
        self.bus.connect('message::eos', self.on_eos)
        self.bus.connect('message::error', self.on_error)

        # This is needed to make the video output in our DrawingArea:
        self.bus.enable_sync_message_emission()
        self.bus.connect('sync-message::element', self.on_sync_message)

# 상위와 같이 RTSP기반으로 Gst make 및 property 설정 
        videosource = Gst.ElementFactory.make('rtspsrc', 'videosource')
        videosource.set_property('location', 'rtsp://192.168.1.168:8557/PSIA/Streaming/channels/2?videoCodecType=H.264')
        videosource.set_property('latency', 500)
       #videoqueue = Gst.ElementFactory.make('queue', 'videoqueue')
        videodepay = Gst.ElementFactory.make('rtph264depay', 'videodepay')
        videoparser = Gst.ElementFactory.make('h264parse', 'videoparser')
        videodecoder = Gst.ElementFactory.make('omxh264dec', 'videodecoder')

        #filesink = Gst.ElementFactory.make('filesink', 'sink')
        #filesink.set_property('location', r'/home/ubuntu/Downloads/N/test11.mp4')
        #filesink.set_property('sync', 'false')

        videosink = Gst.ElementFactory.make('nveglglessink', 'videosink')
        videosink.set_property('create-window', False)


        #self.player.add(videosource, videoqueue, videodepay, videoparser,videodecoder,videosink)
        #Gst.element_link_many(videosource, videoqueue, videodepay, videoparser,videodecoder,videosink)

### Callback Functions 등록 변경 및 테스트할 경우   
        videosource.connect("pad-added", self.videosource_pad_added)
       # videodepay.connect("pad-added", self.test2_pad_added)
       # videoparser.connect("pad-added", self.test3_pad_added)
       # videodecoder.connect("pad-added", self.videodecoder_pad_added)

### Pipeline self.pipeline.add(videosource) #self.pipeline.add(videoqueue) self.pipeline.add(videodepay) self.pipeline.add(videoparser) self.pipeline.add(videodecoder) self.pipeline.add(videosink) #self.pipelie.add(filesink) ### TEST Callback 과 필요 Callback Functions 추가하며 필요시 디버깅 및 상위에서 등록 def test1_pad_added(self, dbin, pad): print('jhlee-debugmsg connected test1_pad_added!!!!!!!!!!!!!!!!!!!!!!!!!!!1!!!') def test2_pad_added(self, dbin, pad): print('jhlee-debugmsg connected test2_pad_added!!!!!!!!!!!!!!!!!!!!!!!!!!!1!!!') def test3_pad_added(self, dbin, pad): print('jhlee-debugmsg connected test3_pad_added!!!!!!!!!!!!!!!!!!!!!!!!!!!1!!!') def test4_pad_added(self, dbin, pad): print('jhlee-debugmsg connected test4_pad_added!!!!!!!!!!!!!!!!!!!!!!!!!!!1!!!') def videosource_pad_added(self, dbin, pad): print('jhlee-debugmsg connected videosource_pad_added start') #####videosource.link(videodepay) 각 부분 디버깅 pipeline = dbin.get_parent() videodepay = pipeline.get_by_name('videodepay') dbin.link(videodepay) #####videodepay.link(videoparser) videoparser = pipeline.get_by_name('videoparser') videodepay.link(videoparser) ######videoparser.link(videodecoder) videodecoder = pipeline.get_by_name('videodecoder') videoparser.link(videodecoder) videosink = pipeline.get_by_name('videosink') videodecoder.link(videosink) #### PIPELINE STATE 변경 pipeline.set_state(Gst.State.PLAYING) if (WORKAROUND_SLEEP_SEC > 0): sleep(WORKAROUND_SLEEP_SEC) print('jhlee-debugmsg connected videosource_pad_added end') def videodepay_pad_added(self, dbin, pad): print('jhlee-debugmsg connected videodepay_pad_added start') def videodecoder_pad_added(self, dbin, pad): print('jhlee-debugmsg connected videodecoder_pad_added start') pipeline = dbin.get_parent() videosink = pipeline.get_by_name('videosink') # Link decodebin to video sink dbin.link(videosink) pipeline.set_state(Gst.State.PLAYING) // PIPELINE STATE 변경 if (WORKAROUND_SLEEP_SEC > 0): sleep(WORKAROUND_SLEEP_SEC) print('jhlee-debugmsg connected videodecoder_pad_added end') def run(self): self.window.show_all() # You need to get the XID after window.show_all(). You shouldn't get it # in the on_sync_message() handler because threading issues will cause # segfaults there. self.xid = self.drawingarea.get_property('window').get_xid() self.pipeline.set_state(Gst.State.PLAYING) Gtk.main() def quit(self, window): self.pipeline.set_state(Gst.State.NULL) Gtk.main_quit() def on_sync_message(self, bus, msg): if msg.get_structure().get_name() == 'prepare-window-handle': print('prepare-window-handle') msg.src.set_window_handle(self.xid) def on_eos(self, bus, msg): print('on_eos(): seeking to start of video') self.pipeline.seek_simple( Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 0 ) def on_error(self, bus, msg): print('on_error():', msg.parse_error()) p = Player() p.run()


1.5 Python 의 API 관련 설명 

솔직히 상위 Python은 Python3를 이해하고, Gstreamer를 이해하며 눈짐작으로 대충은 이해하지만 정확한 이해는 하지 못한다.
아래의 각각의 GTK or Gstreamer의 API를 정확히 어떻게 동작하는 지를 그 함수들의 API 설명해주는 곳이 
아래 SITE이며 , 이곳에서 각각의 함수의 동작을 알아보자.

  https://lazka.github.io/pgi-docs/


1.6  Python 관련 Open Source 

python의 opensource를 보다보니, repo source도 좋은 예제인 것 같다.

  • QT Example
  https://wiki.python.org/moin/PyQt/Using%20GStreamer%20with%20PyQt
  http://www.jonobacon.org/2006/08/28/getting-started-with-gstreamer-with-python/

  • Gstreamer 일반다른예제들 (아래 예제포함)
  http://bazaar.launchpad.net/~jderose/+junk/gst-examples/files/79

  • Video Player (상위예제)
기본적으로 ubuntu에서 동작이 되는것을 확인했으며, 본인의 것에 따라 수정을 해줘야한다.
  http://bazaar.launchpad.net/~jderose/+junk/gst-examples/view/head:/video-player-1.0

  • Webcam 예제 
  http://bazaar.launchpad.net/~jderose/+junk/gst-examples/view/head:/webcam-0.10
  https://storage.googleapis.com/git-repo-downloads/repo

  • NVIDIA의 JETSON 의 RTSP 작업 오픈소스에 반영
이전에 다른회사에서 개발의뢰가 들어와서 이기반으로 소스를 수정하여 다른 회사의 RTSP 문제를 해결 해주었다. (적은돈이 아니라 기쁘다) 
mulitmedia.py 기반으로 multimedia-rtsp.py 새로 만들어 아래에 내가 반영했으며, Open Source라서 회사에는 크게 문제는 상관이 없다.