summaryrefslogtreecommitdiff
path: root/voctocore/videomix.py
blob: 6e81626d6fe883589197628713cb88fdd1bc5849 (plain)
  1. import sys, inspect, math
  2. from pprint import pprint
  3. from gi.repository import GLib, Gst
  4. class Videomix:
  5. decoder = []
  6. mixerpads = []
  7. monitorSize = (1024, 576)
  8. def __init__(self):
  9. self.pipeline = Gst.Pipeline()
  10. # create audio and video mixer
  11. mixerbin = self.createMixer()
  12. # collection of video-sources to connect to the quadmix
  13. quadmixSources = []
  14. # create camera sources
  15. for camberabin in self.createDummyCamSources():
  16. # link camerasource to audiomixer
  17. camberabin.get_by_name('audio_src').link(self.pipeline.get_by_name('liveaudio'))
  18. # inject a ×2 distributor and link one end to the live-mixer
  19. distributor = self.createDistributor(camberabin.get_by_name('video_src'), camberabin.get_name())
  20. distributor.get_by_name('a').link(self.pipeline.get_by_name('livevideo'))
  21. # collect the other end to add it later to the quadmix
  22. quadmixSources.append(distributor.get_by_name('b'))
  23. # would generate pause & slides with another generator which only
  24. # yields if the respective fil are there and which only have a video-pad
  25. self.addVideosToQuadmix(quadmixSources, self.pipeline.get_by_name('quadmix'))
  26. # demonstrate some control
  27. liveaudio = self.pipeline.get_by_name('liveaudio')
  28. liveaudio.set_property('active-pad', liveaudio.get_static_pad('sink_0'))
  29. livevideo = self.pipeline.get_by_name('livevideo')
  30. pad = livevideo.get_static_pad('sink_1')
  31. pad.set_property('alpha', 0.5)
  32. Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'test')
  33. self.pipeline.set_state(Gst.State.PLAYING)
  34. def createMixer(self):
  35. mixerbin = Gst.parse_bin_from_description("""
  36. videomixer name=livevideo ! autovideosink
  37. input-selector name=liveaudio ! autoaudiosink
  38. videotestsrc pattern="solid-color" foreground-color=0x808080 ! capsfilter name=filter ! videomixer name=quadmix ! autovideosink
  39. """, False)
  40. quadmixcaps = Gst.Caps.new_empty_simple('video/x-raw')
  41. quadmixcaps.set_value('width', round(self.monitorSize[0]))
  42. quadmixcaps.set_value('height', round(self.monitorSize[1]))
  43. mixerbin.get_by_name('filter').set_property('caps', quadmixcaps)
  44. mixerbin.set_name('mixerbin')
  45. self.pipeline.add(mixerbin)
  46. return mixerbin
  47. def addVideosToQuadmix(self, videosources, quadmix):
  48. count = len(videosources)
  49. place = [0, 0]
  50. grid = [0, 0]
  51. grid[0] = math.ceil(math.sqrt(count))
  52. grid[1] = math.ceil(count / grid[0])
  53. cellSize = (self.monitorSize[0] / grid[0], self.monitorSize[1] / grid[1])
  54. print("showing {} videosources in a {}×{} grid in a {}×{} px window, which gives cells of {}×{} px per videosource".format(
  55. count, grid[0], grid[1], self.monitorSize[0], self.monitorSize[1], cellSize[0], cellSize[1]))
  56. for idx, videosource in enumerate(videosources):
  57. previewbin = Gst.parse_bin_from_description("""
  58. videoscale name=in ! capsfilter name=caps !
  59. videobox name=crop top=0 left=0 bottom=0 right=0 ! videobox fill=red top=-0 left=-0 bottom=-0 right=-0 name=add !
  60. textoverlay color=0xFFFFFFFF halignment=left valignment=top xpad=10 ypad=5 font-desc="sans 35" name=text ! identity name=out
  61. """, False)
  62. self.pipeline.add(previewbin)
  63. previewbin.set_name('previewbin-{}'.format(0))
  64. previewbin.get_by_name('text').set_property('text', str(idx))
  65. if idx == 2:
  66. crop = previewbin.get_by_name('crop')
  67. add = previewbin.get_by_name('add')
  68. for side in ('top', 'left', 'right', 'bottom'):
  69. crop.set_property(side, 5)
  70. add.set_property(side, -5)
  71. caps = videosource.get_static_pad('src').query_caps(None)
  72. capsstruct = caps.get_structure(0)
  73. srcSize = (
  74. capsstruct.get_int('width')[1],
  75. capsstruct.get_int('height')[1],
  76. )
  77. f = max(srcSize[0] / cellSize[0], srcSize[1] / cellSize[1])
  78. scaleSize = (
  79. srcSize[0] / f,
  80. srcSize[1] / f,
  81. )
  82. coord = (
  83. place[0] * cellSize[0] + (cellSize[0] - scaleSize[0]) / 2,
  84. place[1] * cellSize[1] + (cellSize[1] - scaleSize[1]) / 2,
  85. )
  86. print("placing videosrc {} of size {}×{} scaled by {} to {}×{} in a cell {}×{} px cell ({}/{}) at position ({}/{})".format(
  87. idx, srcSize[0], srcSize[1], f, scaleSize[0], scaleSize[1], cellSize[0], cellSize[1], place[0], place[1], coord[0], coord[1]))
  88. videosource.link(previewbin.get_by_name('in'))
  89. scalecaps = Gst.Caps.new_empty_simple('video/x-raw')
  90. scalecaps.set_value('width', round(scaleSize[0]))
  91. scalecaps.set_value('height', round(scaleSize[1]))
  92. previewbin.get_by_name('caps').set_property('caps', scalecaps)
  93. # define size somewhere, scale and place here
  94. sinkpad = quadmix.get_request_pad('sink_%u')
  95. sinkpad.set_property('xpos', round(coord[0]))
  96. sinkpad.set_property('ypos', round(coord[1]))
  97. previewbin.get_by_name('out').link(quadmix)
  98. place[0] += 1
  99. if place[0] >= grid[0]:
  100. place[1] += 1
  101. place[0] = 0
  102. def createDistributor(self, videosource, name):
  103. distributor = Gst.parse_bin_from_description("""
  104. tee name=t
  105. t. ! queue name=a
  106. t. ! queue name=b
  107. """, False)
  108. distributor.set_name('distributor({0})'.format(name))
  109. self.pipeline.add(distributor)
  110. videosource.link(distributor.get_by_name('t'))
  111. return distributor
  112. def createDummyCamSources(self):
  113. uris = ('file:///home/peter/122.mp4', 'file:///home/peter/10025.mp4',)
  114. for idx, uri in enumerate(uris):
  115. # create a bin for camera input
  116. camberabin = Gst.parse_bin_from_description("""
  117. uridecodebin name=input
  118. input. ! videoconvert ! videoscale ! videorate ! video/x-raw,width=1024,height=576,framerate=25/1 ! identity name=video_src
  119. input. ! audioconvert name=audio_src
  120. """, False)
  121. camberabin.set_name('dummy-camberabin({0})'.format(uri))
  122. # configure camera input
  123. camberabin.get_by_name('input').set_property('uri', uri)
  124. # pass bin upstream
  125. self.pipeline.add(camberabin)
  126. yield camberabin
  127. def createCamSources(self):
  128. for cam in range(2):
  129. # create a bin for camera input
  130. camberabin = Gst.parse_bin_from_description("""
  131. decklinksrc name=input input=sdi input-mode=1080p25
  132. input. ! videoconvert ! videoscale ! videorate ! video/x-raw,width=1920,height=1080,framerate=25/1 ! identity name=video_src
  133. input. ! audioconvert name=audio_src
  134. """, False)
  135. camberabin.set_name('camberabin({0})'.format(cam))
  136. # configure camera input
  137. camberabin.get_by_name('input').set_property('subdevice', cam)
  138. # pass bin upstream
  139. self.pipeline.add(camberabin)
  140. yield camberabin