aboutsummaryrefslogtreecommitdiff
path: root/voctogui/lib/videodisplay.py
blob: bec67891f65b01567694256cb2c2dc28d4fbaa05 (plain)
  1. import logging
  2. from gi.repository import Gst
  3. from lib.config import Config
  4. class VideoDisplay(object):
  5. """ Displays a Voctomix-Video-Stream into a GtkWidget """
  6. def __init__(self, drawing_area, port, play_audio=False, level_callback=None):
  7. self.log = logging.getLogger('VideoDisplay[%s]' % drawing_area.get_name())
  8. self.drawing_area = drawing_area
  9. self.level_callback = level_callback
  10. caps = Config.get('mix', 'videocaps')
  11. use_previews = Config.getboolean('previews', 'enabled') and Config.getboolean('previews', 'use')
  12. # Preview-Ports are Raw-Ports + 1000
  13. if use_previews:
  14. self.log.info('using jpeg-previews instead of raw-video for gui')
  15. port += 1000
  16. else:
  17. self.log.info('using raw-video instead of jpeg-previews for gui')
  18. # Setup Server-Connection, Demuxing and Decoding
  19. pipeline = """
  20. tcpclientsrc host={host} port={port} !
  21. queue !
  22. matroskademux name=demux
  23. """
  24. if use_previews:
  25. pipeline += """
  26. demux. !
  27. image/jpeg !
  28. jpegdec !
  29. {previewcaps} !
  30. videoscale method=nearest-neighbour !
  31. videorate !
  32. {vcaps} !
  33. queue !
  34. """
  35. else:
  36. pipeline += """
  37. demux. !
  38. {vcaps} !
  39. queue !
  40. """
  41. # Video Display
  42. if Config.getboolean('x11', 'xv'):
  43. pipeline += """
  44. xvimagesink name=v
  45. """
  46. else:
  47. pipeline += """
  48. videoconvert !
  49. videoscale !
  50. ximagesink name=v
  51. """
  52. # If an Audio-Path is required, add an Audio-Path through a level-Element
  53. if self.level_callback or play_audio:
  54. pipeline += """
  55. demux. !
  56. {acaps} !
  57. queue !
  58. level name=lvl interval=50000000 !
  59. """
  60. # If Playback is requested, push fo alsa
  61. if play_audio:
  62. # ts-offset=1000000000 (1s) - should keep audio & video in sync but delay by 1s
  63. pipeline += """
  64. alsasink sync=False
  65. """
  66. # Otherwise just trash the Audio
  67. else:
  68. pipeline += """
  69. fakesink
  70. """
  71. pipeline = pipeline.format(
  72. acaps=Config.get('mix', 'audiocaps'),
  73. vcaps=Config.get('mix', 'videocaps'),
  74. previewcaps=Config.get('previews', 'videocaps'),
  75. host=Config.get('server', 'host'),
  76. port=port,
  77. )
  78. self.log.debug('Creating Display-Pipeline:\n%s', pipeline)
  79. self.pipeline = Gst.parse_launch(pipeline)
  80. self.drawing_area.realize()
  81. self.xid = self.drawing_area.get_property('window').get_xid()
  82. self.log.debug('Realized Drawing-Area with xid %u', self.xid)
  83. bus = self.pipeline.get_bus()
  84. bus.add_signal_watch()
  85. bus.enable_sync_message_emission()
  86. bus.connect('message::error', self.on_error)
  87. bus.connect("sync-message::element", self.on_syncmsg)
  88. if self.level_callback:
  89. bus.connect("message::element", self.on_level)
  90. self.log.debug('Launching Display-Pipeline')
  91. self.pipeline.set_state(Gst.State.PLAYING)
  92. def on_syncmsg(self, bus, msg):
  93. if msg.get_structure().get_name() == "prepare-window-handle":
  94. self.log.info('Setting xvimagesink window-handle to %s', self.xid)
  95. msg.src.set_window_handle(self.xid)
  96. def on_error(self, bus, message):
  97. self.log.debug('Received Error-Signal on Display-Pipeline')
  98. (error, debug) = message.parse_error()
  99. self.log.debug('Error-Details: #%u: %s', error.code, debug)
  100. def on_level(self, bus, msg):
  101. if msg.src.name != 'lvl':
  102. return
  103. if msg.type != Gst.MessageType.ELEMENT:
  104. return
  105. peaks = msg.get_structure().get_value('peak')
  106. rms = msg.get_structure().get_value('rms')
  107. self.level_callback(peaks, rms)