aboutsummaryrefslogtreecommitdiff
path: root/clients/source/ingest.py
blob: 2b4c425a509b88331c3b5a920acaac2bbbdb879b (plain)
  1. #!/usr/bin/env python3
  2. # ingest.py
  3. # source client for Voctomix
  4. import sys
  5. import gi
  6. import signal
  7. import os
  8. import socket
  9. import argparse
  10. gi.require_version('Gst', '1.0')
  11. from gi.repository import Gst, GstNet, GObject
  12. # init GObject & Co. before importing local classes
  13. GObject.threads_init()
  14. Gst.init([])
  15. # this is kinda icky.
  16. sys.path.insert(0, '../..' )
  17. import voctogui.lib.connection as Connection
  18. # import lib.clock as ClockManager
  19. def mk_video_src(args, videocaps):
  20. # make video soure part of pipeline
  21. video_device = "device={}".format(args.video_dev) \
  22. if args.video_dev else ""
  23. monitor = """tee name=t ! queue !
  24. videoconvert ! fpsdisplaysink sync=false
  25. t. ! queue !""" \
  26. if args.monitor else ""
  27. if args.video_source == 'dv':
  28. video_src = """
  29. dv1394src name=videosrc {video_device}!
  30. dvdemux name=demux !
  31. queue !
  32. dvdec !
  33. {monitor}
  34. deinterlace mode=1 !
  35. videoconvert !
  36. videorate !
  37. videoscale !
  38. """
  39. elif args.video_source == 'hdv':
  40. video_src = """
  41. hdv1394src do-timestamp=true name=videosrc {video_device} !
  42. tsdemux name=demux!
  43. queue !
  44. decodebin !
  45. {monitor}
  46. deinterlace mode=1 !
  47. videorate !
  48. videoscale !
  49. videoconvert !
  50. """
  51. elif args.video_source == 'hdmi2usb':
  52. video_src = """
  53. v4l2src device=%s name=videosrc !
  54. queue !
  55. image/jpeg,width=1280,height=720 !
  56. jpegdec !
  57. {monitor}
  58. videoconvert !
  59. videorate !
  60. """
  61. elif args.video_source == 'ximage':
  62. video_src = """
  63. ximagesrc name=videosrc
  64. use-damage=false !
  65. {monitor}
  66. videoconvert !
  67. videorate !
  68. videoscale !
  69. """
  70. # startx=0 starty=0 endx=1919 endy=1079 !
  71. elif args.video_source == 'blackmagichdmi':
  72. video_src = """
  73. decklinkvideosrc mode=17 connection=2 !
  74. {monitor}
  75. videoconvert !
  76. videorate !
  77. videoscale !
  78. """
  79. elif args.video_source == 'test':
  80. video_src = """
  81. videotestsrc name=videosrc
  82. pattern=ball
  83. foreground-color=0x00ff0000 background-color=0x00440000 !
  84. {monitor}
  85. """
  86. video_src = video_src.format(
  87. video_device=video_device,
  88. monitor=monitor)
  89. video_src += videocaps + "!\n"
  90. return video_src
  91. def mk_audio_src(args, audiocaps):
  92. audio_device = "device={}".format(args.audio_dev) \
  93. if args.audio_dev else ""
  94. if args.audio_source in [ 'dv', 'hdv' ]:
  95. # this only works if video is from DV also.
  96. # or some gst source that gets demux ed
  97. audio_src = """
  98. demux. !
  99. audioconvert !
  100. """
  101. elif args.audio_source == 'pulse':
  102. audio_src = """
  103. pulsesrc {audio_device} name=audiosrc !
  104. """.format(audio_device=audio_device)
  105. elif args.audio_source == 'blackmagichdmi':
  106. audio_src = """
  107. decklinkaudiosrc !
  108. """
  109. elif args.audio_source == 'test':
  110. audio_src = """
  111. audiotestsrc name=audiosrc freq=330 !
  112. """
  113. audio_src += audiocaps + "!\n"
  114. return audio_src
  115. def mk_mux(args):
  116. mux = """
  117. mux.
  118. matroskamux name=mux !
  119. """
  120. return mux
  121. def mk_client(args):
  122. core_ip = socket.gethostbyname(args.host)
  123. client = """
  124. tcpclientsink host={host} port={port}
  125. """.format(host=core_ip, port=args.port)
  126. return client
  127. def mk_pipeline(args, server_caps):
  128. video_src = mk_video_src(args, server_caps['videocaps'])
  129. audio_src = mk_audio_src(args, server_caps['audiocaps'])
  130. mux = mk_mux(args)
  131. client = mk_client(args)
  132. pipeline = video_src + "mux.\n" + audio_src + mux + client
  133. # remove blank lines to make it more human readable
  134. pipeline = pipeline.replace("\n\n","\n")
  135. return pipeline
  136. def get_server_caps():
  137. # fetch config from server
  138. server_config = Connection.fetchServerConfig()
  139. server_caps = {'videocaps': server_config['mix']['videocaps'],
  140. 'audiocaps': server_config['mix']['audiocaps']}
  141. return server_caps
  142. # obtain network-clock
  143. ClockManager.obtainClock(Connection.ip)
  144. def run_pipeline(pipeline, args):
  145. core_ip = socket.gethostbyname(args.host)
  146. clock = GstNet.NetClientClock.new('voctocore', core_ip, 9998, 0)
  147. print('obtained NetClientClock from host', clock)
  148. print('waiting for NetClientClock to sync…')
  149. clock.wait_for_sync(Gst.CLOCK_TIME_NONE)
  150. print('starting pipeline')
  151. senderPipeline = Gst.parse_launch(pipeline)
  152. senderPipeline.use_clock(clock)
  153. src = senderPipeline.get_by_name('src')
  154. def on_eos(self, bus, message):
  155. print('Received EOS-Signal')
  156. sys.exit(1)
  157. def on_error(self, bus, message):
  158. print('Received Error-Signal')
  159. (error, debug) = message.parse_error()
  160. print('Error-Details: #%u: %s' % (error.code, debug))
  161. sys.exit(1)
  162. # Binding End-of-Stream-Signal on Source-Pipeline
  163. senderPipeline.bus.add_signal_watch()
  164. senderPipeline.bus.connect("message::eos", on_eos)
  165. senderPipeline.bus.connect("message::error", on_error)
  166. print("playing")
  167. senderPipeline.set_state(Gst.State.PLAYING)
  168. mainloop = GObject.MainLoop()
  169. try:
  170. mainloop.run()
  171. except KeyboardInterrupt:
  172. print('Terminated via Ctrl-C')
  173. def get_args():
  174. parser = argparse.ArgumentParser(description='Vocto-ingest client')
  175. parser.add_argument('-v', '--verbose', action='count', default=0,
  176. help="Also print INFO and DEBUG messages.")
  177. parser.add_argument( '--video-source', action='store',
  178. choices=[
  179. 'dv', 'hdv', 'hdmi2usb', 'blackmagichdmi',
  180. 'ximage',
  181. 'test', ],
  182. default='test',
  183. help="Where to get video from")
  184. parser.add_argument( '--video-dev', action='store',
  185. help="video device")
  186. parser.add_argument( '--audio-source', action='store',
  187. choices=['dv', 'alsa', 'pulse', 'blackmagichdmi', 'test'],
  188. default='test',
  189. help="Where to get audio from")
  190. parser.add_argument( '--audio-dev', action='store',
  191. default='hw:CARD=CODEC',
  192. help="for alsa/pulse, audio device")
  193. # maybe hw:1,0
  194. parser.add_argument( '--audio-delay', action='store',
  195. default='10',
  196. help="ms to delay audio")
  197. parser.add_argument('-m', '--monitor', action='store_true',
  198. help="fps display sink")
  199. parser.add_argument( '--host', action='store',
  200. default='localhost',
  201. help="hostname of vocto core")
  202. parser.add_argument( '--port', action='store',
  203. default='10000',
  204. help="port of vocto core")
  205. args = parser.parse_args()
  206. return args
  207. def main():
  208. args = get_args()
  209. core_ip = socket.gethostbyname(args.host)
  210. # establish a synchronus connection to server
  211. Connection.establish(core_ip)
  212. server_caps = get_server_caps()
  213. pipeline = mk_pipeline(args, server_caps)
  214. print(pipeline)
  215. run_pipeline(pipeline, args)
  216. if __name__ == '__main__':
  217. main()