summaryrefslogtreecommitdiff
path: root/clients/source/ingest.py
blob: 1e8fd40c775211f4794553e9021f3297a5e8977d (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. {monitor}
  65. videoconvert !
  66. videorate !
  67. videoscale !
  68. """
  69. elif args.video_source == 'blackmagichdmi':
  70. video_src = """
  71. decklinkvideosrc mode=17 connection=2 !
  72. {monitor}
  73. videoconvert !
  74. videorate !
  75. videoscale !
  76. """
  77. elif args.video_source == 'test':
  78. video_src = """
  79. videotestsrc name=videosrc
  80. pattern=ball
  81. foreground-color=0x00ff0000 background-color=0x00440000 !
  82. {monitor}
  83. """
  84. video_src = video_src.format(
  85. video_device=video_device,
  86. monitor=monitor)
  87. video_src += videocaps + "!\n"
  88. return video_src
  89. def mk_audio_src(args, audiocaps):
  90. audio_device = "device={}".format(args.audio_dev) \
  91. if args.audio_dev else ""
  92. if args.audio_source in [ 'dv', 'hdv' ]:
  93. # this only works if video is from DV also.
  94. # or some gst source that gets demux ed
  95. audio_src = """
  96. demux. !
  97. audioconvert !
  98. """
  99. elif args.audio_source == 'pulse':
  100. audio_src = """
  101. pulsesrc {audio_device} name=audiosrc !
  102. """.format(audio_device=audio_device)
  103. elif args.audio_source == 'blackmagichdmi':
  104. audio_src = """
  105. decklinkaudiosrc !
  106. """
  107. elif args.audio_source == 'test':
  108. audio_src = """
  109. audiotestsrc name=audiosrc freq=330 !
  110. """
  111. audio_src += audiocaps + "!\n"
  112. return audio_src
  113. def mk_mux(args):
  114. mux = """
  115. mux.
  116. matroskamux name=mux !
  117. """
  118. return mux
  119. def mk_client(args):
  120. core_ip = socket.gethostbyname(args.host)
  121. client = """
  122. tcpclientsink host={host} port={port}
  123. """.format(host=core_ip, port=args.port)
  124. return client
  125. def mk_pipeline(args, server_caps):
  126. video_src = mk_video_src(args, server_caps['videocaps'])
  127. audio_src = mk_audio_src(args, server_caps['audiocaps'])
  128. mux = mk_mux(args)
  129. client = mk_client(args)
  130. pipeline = video_src + "mux.\n" + audio_src + mux + client
  131. # remove blank lines to make it more human readable
  132. pipeline = pipeline.replace("\n\n","\n")
  133. return pipeline
  134. def get_server_caps():
  135. # fetch config from server
  136. server_config = Connection.fetchServerConfig()
  137. server_caps = {'videocaps': server_config['mix']['videocaps'],
  138. 'audiocaps': server_config['mix']['audiocaps']}
  139. return server_caps
  140. # obtain network-clock
  141. ClockManager.obtainClock(Connection.ip)
  142. def run_pipeline(pipeline, args):
  143. core_ip = socket.gethostbyname(args.host)
  144. clock = GstNet.NetClientClock.new('voctocore', core_ip, 9998, 0)
  145. print('obtained NetClientClock from host', clock)
  146. print('waiting for NetClientClock to sync…')
  147. clock.wait_for_sync(Gst.CLOCK_TIME_NONE)
  148. print('starting pipeline')
  149. senderPipeline = Gst.parse_launch(pipeline)
  150. senderPipeline.use_clock(clock)
  151. src = senderPipeline.get_by_name('src')
  152. def on_eos(self, bus, message):
  153. print('Received EOS-Signal')
  154. sys.exit(1)
  155. def on_error(self, bus, message):
  156. print('Received Error-Signal')
  157. (error, debug) = message.parse_error()
  158. print('Error-Details: #%u: %s' % (error.code, debug))
  159. sys.exit(1)
  160. # Binding End-of-Stream-Signal on Source-Pipeline
  161. senderPipeline.bus.add_signal_watch()
  162. senderPipeline.bus.connect("message::eos", on_eos)
  163. senderPipeline.bus.connect("message::error", on_error)
  164. print("playing")
  165. senderPipeline.set_state(Gst.State.PLAYING)
  166. mainloop = GObject.MainLoop()
  167. try:
  168. mainloop.run()
  169. except KeyboardInterrupt:
  170. print('Terminated via Ctrl-C')
  171. def get_args():
  172. parser = argparse.ArgumentParser(description='Vocto-ingest client')
  173. parser.add_argument('-v', '--verbose', action='count', default=0,
  174. help="Also print INFO and DEBUG messages.")
  175. parser.add_argument( '--video-source', action='store',
  176. choices=[
  177. 'dv', 'hdv', 'hdmi2usb', 'blackmagichdmi',
  178. 'ximage',
  179. 'test', ],
  180. default='test',
  181. help="Where to get video from")
  182. parser.add_argument( '--video-dev', action='store',
  183. help="video device")
  184. parser.add_argument( '--audio-source', action='store',
  185. choices=['dv', 'alsa', 'pulse', 'blackmagichdmi', 'test'],
  186. default='test',
  187. help="Where to get audio from")
  188. parser.add_argument( '--audio-dev', action='store',
  189. default='hw:CARD=CODEC',
  190. help="for alsa/pulse, audio device")
  191. # maybe hw:1,0
  192. parser.add_argument( '--audio-delay', action='store',
  193. default='10',
  194. help="ms to delay audio")
  195. parser.add_argument('-m', '--monitor', action='store_true',
  196. help="fps display sink")
  197. parser.add_argument( '--host', action='store',
  198. default='localhost',
  199. help="hostname of vocto core")
  200. parser.add_argument( '--port', action='store',
  201. default='10000',
  202. help="port of vocto core")
  203. args = parser.parse_args()
  204. return args
  205. def main():
  206. args = get_args()
  207. core_ip = socket.gethostbyname(args.host)
  208. # establish a synchronus connection to server
  209. Connection.establish(core_ip)
  210. server_caps = get_server_caps()
  211. pipeline = mk_pipeline(args, server_caps)
  212. print(pipeline)
  213. run_pipeline(pipeline, args)
  214. if __name__ == '__main__':
  215. main()