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