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