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