summaryrefslogtreecommitdiff
path: root/bin/capture+encode+serve
blob: 1d9f46986ac74721b79cbf7cf1058e9eacd571c2 (plain)
  1. #!/usr/bin/perl
  2. # Send live video/audio media as RTP streams, published via RTSP
  3. # Depends: libglib-object-introspection-perl, gir1.2-gst-rtsp-server-1.0
  4. # Recommends: gstreamer1.0-plugins-good
  5. use v5.12;
  6. use warnings;
  7. use Glib qw( TRUE FALSE );
  8. use Glib::Object::Introspection;
  9. use IPC::System::Simple qw(capturex);
  10. BEGIN {
  11. Glib::Object::Introspection->setup(
  12. basename => 'Gst',
  13. version => '1.0',
  14. package => 'Gst',
  15. );
  16. Glib::Object::Introspection->setup(
  17. basename => 'GstRtspServer',
  18. version => '1.0',
  19. package => 'Gst',
  20. );
  21. }
  22. my $ADDRESS = shift || $ENV{'ADDRESS'} || '127.0.0.1';
  23. my $PORT = shift || $ENV{'PORT'} || '8554';
  24. my $VDEVICES = shift || $ENV{'VDEVICES'} || '';
  25. my $ADEVICES = shift || $ENV{'ADEVICES'} || '';
  26. my $VFORMAT = shift || $ENV{'VFORMAT'} || 'VP8'; # H264 VP8 RAW - default: VP8
  27. my $AFORMAT = shift || $ENV{'AFORMAT'} || 'OPUS'; # AMR OPUS RAW - default: OPUS
  28. my @VDEVICES = $VDEVICES ? split ' ', $VDEVICES : sort split ' ', capturex('find', qw(/dev -maxdepth 1 -type c -name video*));
  29. # FIXME: Detect/blacklist and skip faulty devices
  30. #my @ADEVICES = grep { /^hw:/ } capturex( 'arecord', qw(-L) );
  31. my @ADEVICES = split ' ', $ADEVICES;
  32. chomp @ADEVICES;
  33. #use Data::Dump; die dd @ADEVICES;
  34. my $HEIGHT = 240;
  35. my $FRAMERATE = 25;
  36. my $AUDIORATE = 48000;
  37. my $VBITRATE = 256000;
  38. my $VCAPS = "video/x-raw,height=$HEIGHT";
  39. my $ACAPS = "audio/x-raw,rate=$AUDIORATE,channels=2,depth=16";
  40. # * http://stackoverflow.com/a/42237307
  41. my $ABUFFERS = 20000;
  42. # * force threads using queues - see http://stackoverflow.com/a/30738533
  43. # * generous queue sizes inspired by https://wiki.xiph.org/GST_cookbook
  44. my $QUEUE = "queue max-size-bytes=100000000 max-size-time=0";
  45. my %VFORMAT = (
  46. H264 => {
  47. # * let x264 use low-latency sliced-threads (i.e. don't disable treads)
  48. VENC => "x264enc speed-preset=ultrafast tune=zerolatency bitrate=800 byte-stream=true key-int-max=15 intra-refresh=true option-string=\"slice-max-size=8192:vbv-maxrate=80:vbv-bufsize=10\" ! video/x-h264,profile=baseline ! $QUEUE ! rtph264pay",
  49. },
  50. VP8 => {
  51. VENC => "vp8enc threads=4 cpu-used=15 deadline=1000000 end-usage=1 target-bitrate=$VBITRATE undershoot=95 keyframe-max-dist=999999 max-quantizer=56 deadline=5000 static-threshold=500 ! video/x-vp8 ! $QUEUE ! rtpvp8pay",
  52. },
  53. RAW => {
  54. VENC => "rtpvrawpay",
  55. },
  56. );
  57. my %AFORMAT = (
  58. AMR => {
  59. AENC => "amrnbenc ! $QUEUE ! rtpamrpay",
  60. },
  61. OPUS => {
  62. AENC => "opusenc ! $QUEUE ! rtpopuspay",
  63. },
  64. RAW => {
  65. AENC => "rtpL16pay",
  66. },
  67. );
  68. our $nextpayload = 0;
  69. sub cam {
  70. my $device = shift;
  71. my $payload = "pay" . $nextpayload++;
  72. my $factory = Gst::RTSPMediaFactory->new();
  73. $factory->set_launch("( v4l2src device=$device ! $QUEUE ! videoconvert ! $VCAPS ! $QUEUE ! $VFORMAT{$VFORMAT}{'VENC'} name=$payload )");
  74. $factory->set_shared(TRUE);
  75. say "media ($device): " . $factory->get_launch();
  76. # $factory->set_latency(5);
  77. #say "latency ($device): " . $factory->get_latency();
  78. return $factory;
  79. }
  80. sub mic {
  81. my $device = shift;
  82. my $payload = "pay" . $nextpayload++;
  83. my $factory = Gst::RTSPMediaFactory->new();
  84. $factory->set_launch("( alsasrc device=$device buffer-time=$ABUFFERS ! $QUEUE ! audioconvert ! $QUEUE ! $AFORMAT{$AFORMAT}{'AENC'} name=$payload )");
  85. $factory->set_shared(TRUE);
  86. #say "media ($device): " . $factory->get_launch();
  87. # $factory->set_latency(5);
  88. #say "latency ($device): " . $factory->get_latency();
  89. return $factory;
  90. }
  91. Gst::init([ $0, @ARGV ]);
  92. my $loop = Glib::MainLoop->new( undef, FALSE );
  93. # create a server instance
  94. my $server = Gst::RTSPServer->new();
  95. $server->set_address($ADDRESS);
  96. $server->set_service($PORT);
  97. # get the mount points for this server, every server has a default
  98. # object that be used to map uri mount points to media factories
  99. my $mounts = $server->get_mount_points();
  100. # attach media to URIs
  101. my @mounts;
  102. for my $i ( 0 .. $#VDEVICES ) {
  103. my $mount = "/cam$i";
  104. $mounts->add_factory($mount, cam($VDEVICES[$i]));
  105. push @mounts, $mount;
  106. };
  107. for my $i ( 0 .. $#ADEVICES ) {
  108. my $mount = "/mic$i";
  109. $mounts->add_factory($mount, mic($ADEVICES[$i]));
  110. push @mounts, $mount;
  111. };
  112. # don't need the ref to the mapper anymore
  113. undef $mounts;
  114. # attach the server to the default maincontext
  115. my $retval = $server->attach(undef);
  116. # start serving
  117. say "streams ready at the following URLs:";
  118. for (@mounts) {
  119. say "rtsp://$ADDRESS:$PORT$_";
  120. }
  121. $loop->run;