rtp 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/Gemfile +0 -3
  2. data/History.rdoc +42 -0
  3. data/README.rdoc +60 -15
  4. data/Rakefile +9 -4
  5. data/lib/rtp.rb +0 -3
  6. data/lib/rtp/logger.rb +11 -0
  7. data/lib/rtp/packet.rb +39 -3
  8. data/lib/rtp/receiver.rb +255 -152
  9. data/lib/rtp/version.rb +1 -1
  10. data/rtp.gemspec +2 -1
  11. data/spec/rtp/coverage/assets/0.7.1/application.css +1110 -0
  12. data/spec/rtp/coverage/assets/0.7.1/application.js +626 -0
  13. data/spec/rtp/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  14. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  15. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  16. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  17. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  18. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  19. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  20. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  21. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  22. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  23. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  24. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  25. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  26. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  27. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  28. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  29. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  30. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  31. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  32. data/spec/rtp/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  33. data/spec/rtp/coverage/assets/0.7.1/favicon_green.png +0 -0
  34. data/spec/rtp/coverage/assets/0.7.1/favicon_red.png +0 -0
  35. data/spec/rtp/coverage/assets/0.7.1/favicon_yellow.png +0 -0
  36. data/spec/rtp/coverage/assets/0.7.1/loading.gif +0 -0
  37. data/spec/rtp/coverage/assets/0.7.1/magnify.png +0 -0
  38. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  39. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  40. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  41. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  42. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  43. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  44. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  45. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  46. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
  47. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  48. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
  49. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
  50. data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  51. data/spec/rtp/coverage/index.html +146 -0
  52. data/spec/rtp/receiver_spec.rb +241 -258
  53. data/spec/rtp_spec.rb +1 -1
  54. data/spec/spec_helper.rb +4 -13
  55. metadata +152 -23
  56. data/tasks/roodi.rake +0 -10
  57. data/tasks/roodi_config.yaml +0 -14
  58. data/tasks/rspec.rake +0 -10
  59. data/tasks/stats.rake +0 -12
  60. data/tasks/yard.rake +0 -8
data/Gemfile CHANGED
@@ -1,6 +1,3 @@
1
1
  source :rubygems
2
2
  gemspec
3
3
 
4
- group :test do
5
- gem 'rcov', '~> 0.9'
6
- end
data/History.rdoc CHANGED
@@ -1,3 +1,45 @@
1
+ === 0.1.0 / 2012-11-14
2
+
3
+ Lots of big changes this release! Only a minor bump though, since I'm assuming
4
+ that I'm about the only one using this at this point and I'm not going to mess
5
+ anyone else up by making these changes. This is how the initial release should
6
+ have been.
7
+
8
+ New features:
9
+ * RTP::Receiver#start (used to be #run--see below) now takes an optional block
10
+ that will yield a parsed RTP::Packet. This allows for inspecting packets
11
+ as they come in. If a block is given, no data will be written to the capture
12
+ file, so as to conserve I/O.
13
+ * Added ability to receive on multicast sockets. To do so, just set the
14
+ :ip_address option on init to the multicast address you want to use.
15
+ * Turned logging off by default. To turn on, just do <tt>RTP::Logger = true</tt>.
16
+ * Updated RTP::Logger to use log_switch 0.4.0's log_class_name feature.
17
+ * Setting RTP::Receiver#rtp_port will now set #rtcp_port. There's no
18
+ functionality behind the #rtcp_port yet; just a numerical placeholder, really.
19
+ * Use Kernel#at_exit to make sure capture files get closed and deleted.
20
+
21
+ Refactorings:
22
+ * RTP::Receiver.new now takes an options Hash instead of a bunch of params.
23
+ * Merged RTP::Receiver#run and RTP::Receiver#start_listener into
24
+ RTP::Receiver#start. There wasn't any benefit of having them separate.
25
+ * Removed all packet sorting provisions--pretty sure this shouldn't happen at
26
+ this level. This means RTP::Receiver#write_buffer_to_file is gone.
27
+ * Added a @packets Queue, dedicated to writing packets out to the capture file.
28
+ Besides getting out of the way of the receiving I/O, this also abstracts away
29
+ the writing to file logic from the receiving and parsing logic. In a related
30
+ manner...
31
+ * #strip_headers is an RTP::Receiver accessor now, so you can set
32
+ this to true if you only want to capture/yield the RTP payload.
33
+ * RTP::Receiver#init_server is now RTP::Receiver#init_socket.
34
+ * Removed port retrying for RTP::Receiver; if the port requested for use by the
35
+ Receiver is in use, I don't think it should be the role of the Receiver to try
36
+ to keep trying--you asked for port X--if it's not available, you should be
37
+ told so. Thus, users of Receivers should now implement this if they so desire.
38
+ * RTP::Receiver users are no longer required to tell the object what type of
39
+ IP addressing to use--this is now inferred from the IP address given to use.
40
+ Default is set to 0.0.0.0; if an address in the range 224.0.0.0 -
41
+ 239.255.255.255 is given, multicasat settings are assumed.
42
+
1
43
  === 0.0.1 / 2012-03-02
2
44
 
3
45
  * Initial release.
data/README.rdoc CHANGED
@@ -5,40 +5,85 @@ http://github.com/turboladen/rtp
5
5
  == DESCRIPTION
6
6
 
7
7
  This is a RTP library that provides the most basic features of the Real-Time
8
- Transport Protocol (RTP, {RFC 3550}[http://tools.ietf.org/html/rfc3550]. While
8
+ Transport Protocol (RTP, {RFC 3550}[http://tools.ietf.org/html/rfc3550]). While
9
9
  the protocol allows for extensions that define new ways for transporting encoded
10
10
  audio/video, this library doesn't yet deal with any specific media types. My
11
11
  current goal is simply for transporting a single RTP stream of data and allowing
12
- you to decode its fixed headers. Quite frankly, this first release is just a
13
- break out of the Receiver stuff that was built in to the
14
- {rtsp library}[https://github.com/turboladen/rtsp], with the ability to parse
15
- RTP header data.
12
+ you to inspect its packets.
13
+
14
+ == FEATURES/PROBLEMS
15
+
16
+ Features:
17
+
18
+ * Receive a single RTP stream over UDP or TCP, unicast or multicast
19
+ * Parse RTP packet headers
20
+ * Inspect packets on the fly
16
21
 
17
22
  Future goals will include:
18
23
 
19
- * Implement "sink"--sort RTP packets
20
- * Implementing RTCP
24
+ * RTCP
21
25
  * Multiplexed sessions
22
26
  * Decoding of basic audio/video codec headers
23
27
  * Decoding of some popular A/V extension headers (h.264, et al.)
24
28
  * An easy way to hand over your data to an app that will decode your A/V stream(s)
25
-
26
- == FEATURES/PROBLEMS
27
-
28
- Features:
29
-
30
- * Receive a single RTP stream over UDP
31
- * Parse RTP packet headers
29
+ * Sending an RTP stream
32
30
 
33
31
  == SYNOPSIS
34
32
 
35
- FIX (explain how to use, etc)
33
+ === Streaming to file
34
+
35
+ With its default settings, an {RTP::Receiver} will open a UDP socket on port 6790,
36
+ within a Thread, on 0.0.0.0 and capture the data it receives to a file.
37
+
38
+ receiver = RTP::Receiver.new # Uses a Tempfile by default
39
+ receiver.rtp_port # => 6790
40
+ receiver.transport_protocol # => :UDP
41
+ receiver.multicast? # => false
42
+ receiver.unicast? # => true
43
+ receiver.start
44
+ sleep 5
45
+ receiver.stop
46
+ receiver.capture_file.size # => 20040
47
+
48
+ To init a Receiver with some other options...
49
+
50
+ options = {
51
+ rtp_port: 9000,
52
+ transport_protocol: :TCP,
53
+ ip_address: '239.255.255.251', # Multicast!
54
+ strip_headers: true, # Strips RTP headers before writing to file
55
+ capture_file: File.new('data.rtp', 'wb')
56
+ }
57
+ receiver = RTP::Receiver.new(options)
58
+ receiver.start
59
+ sleep 5
60
+ receiver.stop
61
+ receiver.capture_file.size # => 18848
62
+
63
+ === Packet inspecting
64
+
65
+ To inspect (and do what you like with) packets as they come in...
66
+
67
+ receiver = RTP::Receiver.new
68
+ receiver.start do |packet|
69
+ puts "Sequence number: #{packet.sequence_number}" # => Sequence number: 2931830
70
+ end
71
+ sleep 5
72
+ receiver.stop
73
+ receiver.capture_file.size # => 0
74
+
75
+ Notice how no data was written to file when the block was passed to +#start+. The
76
+ idea there is to save on I/O; if you're inspecting packets, it's not fair to
77
+ assume you want to save the data to a file. If you do, however, in that case,
78
+ you can simply do so within the block.
36
79
 
37
80
  == REQUIREMENTS
38
81
 
39
82
  * (Tested) Rubies
40
83
  * 1.9.2
41
84
  * 1.9.3
85
+ * JRuby 1.9 mode
86
+ * Rubinius 1.9 mode
42
87
  * RubyGems:
43
88
  * bindata
44
89
  * log_switch
data/Rakefile CHANGED
@@ -5,11 +5,16 @@ require 'yard'
5
5
  # Load all extra rake task definitions
6
6
  Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].each { |ext| load ext }
7
7
 
8
- task default: :install
9
8
 
10
- YARD::Rake::YardocTask.new
11
- RSpec::Core::RakeTask.new
9
+ YARD::Rake::YardocTask.new do |t|
10
+ t.files = %w(lib/**/*.rb - *.rdoc)
11
+ t.options = %w[--private --protected --verbose]
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new do |t|
15
+ t.ruby_opts = %w[-w]
16
+ end
12
17
 
13
18
  # Alias for rubygems-test
14
19
  task test: :spec
15
-
20
+ task default: :test
data/lib/rtp.rb CHANGED
@@ -1,7 +1,4 @@
1
1
  require_relative 'rtp/version'
2
- require 'pathname'
3
- require 'log_switch'
4
2
 
5
3
  module RTP
6
- extend LogSwitch
7
4
  end
data/lib/rtp/logger.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'log_switch'
2
+
3
+ module RTP
4
+ class Logger
5
+ extend LogSwitch
6
+ end
7
+ end
8
+
9
+ RTP::Logger.log_class_name = true
10
+ RTP::Logger.log = false
11
+
data/lib/rtp/packet.rb CHANGED
@@ -1,25 +1,56 @@
1
1
  require 'bindata'
2
2
 
3
3
  module RTP
4
+
5
+ # Decodes a single RTP packet into a Hash, so the packet can inspected and
6
+ # used accordingly. Form more info on types, see
7
+ # {bindata}[http://bindata.rubyforge.org].
4
8
  class Packet < BinData::Record
5
9
  endian :big
6
10
 
11
+ #---------------------------------------------------------------------------
12
+ # RTP Header
13
+ #---------------------------------------------------------------------------
14
+
15
+ # @return [BinData::Bit2]
7
16
  bit2 :version
17
+
18
+ # @return [BinData::Bit1]
8
19
  bit1 :padding
20
+
21
+ # @return [BinData::Bit1]
9
22
  bit1 :extension
23
+
24
+ # @return [BinData::Bit4]
10
25
  bit4 :csrc_count
11
26
 
27
+ # @return [BinData::Bit1]
12
28
  bit1 :marker
29
+
30
+ # @return [BinData::Bit7]
13
31
  bit7 :payload_type
14
32
 
33
+ # @return [BinData::Uint16be]
15
34
  uint16 :sequence_number
35
+
36
+ # @return [BinData::Uint32be]
16
37
  uint32 :timestamp
38
+
39
+ # @return [BinData::Uint32be]
17
40
  uint32 :ssrc_id
41
+
42
+ # @return [BinData::Array]
18
43
  array :csrc_ids, :type => :uint32, :initial_length => lambda { csrc_count }
19
44
 
45
+ #---------------------------------------------------------------------------
20
46
  # Extension header is variable length if :extension == 1
21
- #uint16 :extension_id
22
- #uint16 :extension_length
47
+ #---------------------------------------------------------------------------
48
+
49
+ # @return [BinData::Uint16be]
50
+ uint16 :extension_id, onlyif: :has_extension?
51
+
52
+ # @return [BinData::Uint16be]
53
+ uint16 :extension_length, onlyif: :has_extension?
23
54
 
24
55
  =begin
25
56
  # h.264 payload
@@ -37,7 +68,12 @@ module RTP
37
68
  bit5 :nal_unit_payload_type
38
69
  =end
39
70
  count_bytes_remaining :bytes_remaining
71
+
72
+ # @return [BinData::String]
40
73
  string :rtp_payload, read_length: lambda { bytes_remaining }
74
+
75
+ def has_extension?
76
+ extension == 1
77
+ end
41
78
  end
42
79
  end
43
-
data/lib/rtp/receiver.rb CHANGED
@@ -1,23 +1,19 @@
1
1
  require 'tempfile'
2
2
  require 'socket'
3
+ require 'timeout'
3
4
 
4
- require_relative '../rtp'
5
+ require_relative 'logger'
5
6
  require_relative 'error'
6
7
  require_relative 'packet'
7
8
 
9
+
8
10
  module RTP
9
11
 
10
- # Objects of this type can be used with a +RTSP::Client+ object in order to
11
- # capture the RTP data transmitted to the client as a result of an RTSP
12
- # PLAY call.
13
- #
14
- # In this version, objects of this type don't do much other than just capture
15
- # the data to a file; in later versions, objects of this type will be able
16
- # to provide a "sink" and allow for ensuring that the received RTP packets
17
- # will be reassembled in the correct order, as they're written to file
18
- # (objects of this type don't don't currently allow for checking RTP sequence
19
- # numbers on the data that's been received).
12
+ # Objects of this type receive RTP data over a socket and either save them to
13
+ # a file, or yield the packets to a given block. This is useful with other
14
+ # protocols, like RTSP.
20
15
  class Receiver
16
+ include LogSwitch::Mixin
21
17
 
22
18
  # Name of the file the data will be captured to unless #rtp_file is set.
23
19
  DEFAULT_CAPFILE_NAME = "rtp_capture.raw"
@@ -25,196 +21,303 @@ module RTP
25
21
  # Maximum number of bytes to receive on the socket.
26
22
  MAX_BYTES_TO_RECEIVE = 1500
27
23
 
28
- # Maximum times to retry using the next greatest port number.
29
- MAX_PORT_NUMBER_RETRIES = 50
24
+ # TTL value that will be used if receiving on a multicast socket.
25
+ MULTICAST_TTL = 4
26
+
27
+ # @return [File] The file to capture the RTP data to.
28
+ attr_reader :capture_file
30
29
 
31
- # @param [File] rtp_file The file to capture the RTP data to.
32
- # @return [File]
33
- attr_accessor :rtp_file
30
+ # @return [Array<Time>] The packet receipt timestamps.
31
+ attr_reader :packet_timestamps
34
32
 
35
- # @param [Boolean] strip_headers True if you want to strip the RTP headers.
36
- attr_accessor :strip_headers
33
+ # @return [Fixnum] The port on which to capture RTP data.
34
+ attr_reader :rtp_port
37
35
 
38
- # @return [Array<Time>] packet_timestamps The packet receipt timestamps.
39
- attr_accessor :packet_timestamps
40
-
41
- # @param [Fixnum] rtp_port The port on which to capture the RTP data.
42
- # @return [Fixnum]
43
- attr_accessor :rtp_port
36
+ # @return [Fixnum] Added for clarifying the roles of ports; not
37
+ # currently used though.
38
+ attr_reader :rtcp_port
44
39
 
45
- # @param [Symbol] transport_protocol +:UDP+ or +:TCP+.
46
- # @return [Symbol]
40
+ # @return [Symbol] The type of socket to use for capturing the RTP data.
41
+ # +:UDP+ or +:TCP+.
47
42
  attr_accessor :transport_protocol
48
43
 
49
- # @param [Symbol] broadcast_type +:multicast+ or +:unicast+.
50
- # @return [Symbol]
51
- attr_accessor :broadcast_type
52
-
53
- # @param [Symbol] transport_protocol The type of socket to use for capturing
54
- # the data. +:UDP+ or +:TCP+.
55
- # @param [Fixnum] rtp_port The port on which to capture RTP data.
56
- # @param [File] rtp_capture_file The file object to capture the RTP data to.
57
- def initialize(transport_protocol=:UDP, rtp_port=9000, rtp_capture_file=nil)
58
- @transport_protocol = transport_protocol
59
- @rtp_port = rtp_port
60
- @rtp_file = rtp_capture_file || Tempfile.new(DEFAULT_CAPFILE_NAME)
61
- @packet_timestamps = []
44
+ # @return [String] The IP address to receive RTP data on.
45
+ attr_accessor :ip_address
46
+
47
+ # @param [Hash] options
48
+ # @option options [Fixnum] :rtp_port The port on which to capture RTP data.
49
+ # +rtcp_port+ will be set to the next port above this.
50
+ # @option options [Symbol] :transport_protocol The type of socket to use for
51
+ # capturing the data. +:UDP+ or +:TCP+.
52
+ # @option options [String] :ip_address The IP address to open the socket on.
53
+ # If this is a multicast address, multicast options will be set.
54
+ # @option options [Boolean] :strip_headers If set to true, RTP headers will
55
+ # be stripped from packets before they're written to the capture file.
56
+ # @option options [File] :capture_file The file object to capture the RTP
57
+ # data to.
58
+ def initialize(options={})
59
+ @rtp_port = options[:rtp_port] || 6970
60
+ @rtcp_port = @rtp_port + 1
61
+ @transport_protocol = options[:transport_protocol] || :UDP
62
+ @ip_address = options[:ip_address] || '0.0.0.0'
63
+ @strip_headers = options[:strip_headers] || false
64
+ @capture_file = options[:capture_file] ||
65
+ Tempfile.new(DEFAULT_CAPFILE_NAME)
66
+
67
+ at_exit do
68
+ unless @capture_file.closed?
69
+ log "Closing and deleting capture capture file..."
70
+ @capture_file.close
71
+ @capture_file.unlink
72
+ end
73
+ end
74
+
75
+ @socket = nil
62
76
  @listener = nil
63
- @sequence_list = []
64
- @payload_data = []
65
- @strip_headers = false
77
+ @packet_writer = nil
78
+ @packets = Queue.new
79
+ @packet_timestamps = []
66
80
  end
67
81
 
68
- # Initializes a server of the correct socket type.
82
+ # Starts the packet writer (buffer) and listener.
69
83
  #
70
- # @return [UDPSocket, TCPSocket]
71
- # @raise [RTP::Error] If +@transport_protocol was not set to +:UDP+ or
72
- # +:TCP+.
73
- def init_server(protocol, port=9000)
74
- if protocol == :UDP
75
- server = init_udp_server(port)
76
- elsif protocol == :TCP
77
- server = init_tcp_server(port)
78
- else
79
- raise RTP::Error, "Unknown streaming_protocol requested: #{@transport_protocol}"
80
- end
84
+ # If a block is given, this will yield each parsed packet as an RTP::Packet.
85
+ # This lets you inspect packets as they come in:
86
+ # @example
87
+ # receiver = RTP::Receiver.new
88
+ # receiver.start do |packet|
89
+ # puts packet["sequence_number"]
90
+ # end
91
+ #
92
+ # @yield [RTP::Packet] Each parsed packet that comes in over the wire.
93
+ #
94
+ # @return [Boolean] true if started successfully.
95
+ def start(&block)
96
+ return false if running?
97
+ log "Starting receiving on port #{@rtp_port}..."
98
+
99
+ @packet_writer = start_packet_writer(&block)
100
+ @packet_writer.abort_on_exception = true
101
+
102
+ @socket = init_socket(@transport_protocol, @rtp_port, @ip_address)
103
+
104
+ @listener = start_listener(@socket)
105
+ @listener.abort_on_exception = true
106
+
107
+ running?
108
+ end
81
109
 
82
- server
110
+ # Stops the listener and packet writer threads.
111
+ #
112
+ # @return [Boolean] true if stopped successfully.
113
+ def stop
114
+ return false if !running?
115
+
116
+ log "Stopping #{self.class} on port #{@rtp_port}..."
117
+ stop_listener
118
+ log "listening? #{listening?}"
119
+
120
+ stop_packet_writer
121
+ log "writing packets? #{writing_packets?}"
122
+ log "running? #{running?}"
123
+
124
+ !running?
83
125
  end
84
126
 
85
- # Simply calls #start_listener.
86
- def run
87
- RTP.log "Starting #{self.class} on port #{@rtp_port}..."
88
- start_listener
127
+ # @return [Boolean] true if the listener thread is running; false if not.
128
+ def listening?
129
+ !@listener.nil? ? @listener.alive? : false
89
130
  end
90
131
 
132
+ # @return [Boolean] true if ready to write packets to file.
133
+ def writing_packets?
134
+ !@packet_writer.nil? ? @packet_writer.alive? : false
135
+ end
91
136
 
92
- # Starts the +@listener+ thread that starts up the server, then takes the
93
- # data received from the server and pushes it on to the +@@payload_data+.
94
- #
95
- # @return [Thread] The listener thread (+@listener+).
96
- def start_listener
97
- return @listener if listening?
137
+ # @return [Boolean] true if the Receiver is listening and writing packets.
138
+ def running?
139
+ listening? && writing_packets?
140
+ end
98
141
 
99
- @listener = Thread.start do
100
- server = init_server(@transport_protocol, @rtp_port)
142
+ # Updates the rtp_port and sets the rtcp_port to be this +1.
143
+ def rtp_port=(port)
144
+ @rtp_port = port
145
+ @rtcp_port = @rtp_port + 1
146
+ end
101
147
 
148
+ # @return [Boolean] true if +ip_address+ is a multicast address or not.
149
+ def multicast?
150
+ first_octet = @ip_address.match(/^(\d\d\d?)/).to_s.to_i
151
+ first_octet >= 224 && first_octet <= 239
152
+ end
153
+
154
+ # @return [Boolean] true if +ip_address+ is a unicast address or not.
155
+ def unicast?
156
+ !multicast?
157
+ end
158
+
159
+ # @return [Symbol] The IP addressing type to use for capturing the data.
160
+ # +:multicast+ or +:unicast:.
161
+ def ip_addressing_type
162
+ multicast? ? :multicast : :unicast
163
+ end
164
+
165
+ private
166
+
167
+ # This starts a new Thread for reading packets off of the list of packets
168
+ # that has been read in by the listener. If no block is given, this writes
169
+ # all received packets (in the @packets Queue) to the +capture_file+. If a
170
+ # block is given, it yields each packet, parsed as an RTP::Packet. If
171
+ # +strip_headers+ is set, it only writes/yields the RTP payload to the file.
172
+ #
173
+ # @yield [RTP::Packet] Each parsed packet that comes in over the wire.
174
+ # @return [Thread] The packet writer thread.
175
+ def start_packet_writer
176
+ return @packet_writer if @packet_writer
177
+
178
+ # If a block is given for packet inspection, perhaps we should save
179
+ # some I/O ano not write the packet to file?
180
+ Thread.start do
102
181
  loop do
103
- begin
104
- msg = server.recvmsg_nonblock(MAX_BYTES_TO_RECEIVE)
105
- data = msg.first
106
- @packet_timestamps << msg.last.timestamp
107
- RTP.log "received data with size: #{data.size}"
108
- packet = RTP::Packet.read(data)
109
- RTP.log "rtp payload size: #{packet["rtp_payload"].size}"
110
- write_buffer_to_file if @sequence_list.include? packet["sequence_number"].to_i
111
- @sequence_list << packet["sequence_number"].to_i
112
-
113
- if @strip_headers
114
- @payload_data[packet["sequence_number"].to_i] = packet["rtp_payload"]
115
- else
116
- @payload_data[packet["sequence_number"].to_i] = data
117
- end
118
- rescue Errno::EAGAIN;
119
- write_buffer_to_file
120
- end # rescue error when no data is available to read.
182
+ packet = RTP::Packet.read(@packets.pop)
183
+ data_to_write = @strip_headers ? packet.rtp_payload : packet
184
+
185
+ if block_given?
186
+ yield data_to_write
187
+ else
188
+ @capture_file.write(data_to_write)
189
+ end
121
190
  end
122
191
  end
192
+ end
123
193
 
124
- @listener.abort_on_exception = true
125
- end
194
+ # Initializes a socket of the requested type.
195
+ #
196
+ # @param [Symbol] protocol The protocol on which to receive RTP data.
197
+ # @param [Fixnum] port The port on which to receive RTP data.
198
+ # @param [String] ip_address The IP on which to receive RTP data.
199
+ # @return [UDPSocket, TCPServer]
200
+ # @raise [RTP::Error] If +protocol+ was not set to +:UDP+ or +:TCP+.
201
+ def init_socket(protocol, port, ip_address)
202
+ log "Setting up #{protocol} socket on #{ip_address}:#{port}"
126
203
 
127
- # Sorts the sequence numbers and writes the data in the buffer to file.
128
- def write_buffer_to_file
129
- @sequence_list.sort.each do |sequence|
130
- @rtp_file.write @payload_data[sequence]
204
+ if protocol == :UDP
205
+ socket = UDPSocket.open
206
+ socket.bind(ip_address, port)
207
+ elsif protocol == :TCP
208
+ socket = TCPServer.new(ip_address, port)
209
+ else
210
+ raise RTP::Error,
211
+ "Unknown protocol requested: #{protocol}. Options are :TCP or :UDP"
131
212
  end
132
213
 
133
- @sequence_list.clear
134
- @payload_data.clear
135
- end
214
+ set_socket_time_options(socket)
215
+ setup_multicast_socket(socket, multicast_address) if multicast?
136
216
 
137
- # @return [Boolean] true if the +@listener+ thread is running; false if not.
138
- def listening?
139
- !@listener.nil? ? @listener.alive? : false
217
+ @rtp_port = port
218
+ log "#{protocol} server setup to receive on port #{@rtp_port}"
219
+
220
+ socket
140
221
  end
141
222
 
142
- # Returns if the #run loop is in action.
223
+ # Starts the thread that receives the RTP data, then takes that data and
224
+ # pushes it on to +@packets+ for processing.
143
225
  #
144
- # @return [Boolean] true if the run loop is running.
145
- def running?
146
- listening?
147
- end
226
+ # @param [IPSocket] socket The socket to listen on.
227
+ # @return [Thread] The listener thread.
228
+ def start_listener(socket)
229
+ return @listener if @listener
148
230
 
149
- # Breaks out of the run loop.
150
- def stop
151
- RTP.log "Stopping #{self.class} on port #{@rtp_port}..."
152
- stop_listener
153
- RTP.log "listening? #{listening?}"
154
- write_buffer_to_file
155
- RTP.log "running? #{running?}"
231
+ Thread.start(socket) do
232
+ loop do
233
+ msg = socket.recvmsg(MAX_BYTES_TO_RECEIVE)
234
+ data = msg.first
235
+ log "Received data at size: #{data.size}"
236
+
237
+ log "RTP timestamp from socket info: #{msg.last.timestamp}"
238
+ @packet_timestamps << msg.last.timestamp
239
+ @packets << data
240
+ end
241
+ end
156
242
  end
157
243
 
158
244
  # Kills the +@listener+ thread and sets the variable to nil.
245
+ #
246
+ # @return [Boolean] true if it stopped listening.
159
247
  def stop_listener
160
- #@listener.kill if @listener
248
+ log "Stopping listener..."
249
+ @socket.close if @socket
161
250
  @listener.kill if listening?
162
251
  @listener = nil
252
+ log "Listener stopped."
253
+
254
+ !listening?
163
255
  end
164
256
 
165
- # Sets up to receive data on a UDP socket, using +@rtp_port+.
257
+ # Waits for all packets to be written out before killing. AFter 10 seconds
258
+ # it'll force close the writer.
166
259
  #
167
- # @param [Fixnum] port Port number to listen for RTP data on.
168
- # @return [UDPSocket]
169
- def init_udp_server(port)
170
- port_retries = 0
260
+ # @return [Boolean] true if it stopped the packet writer.
261
+ def stop_packet_writer
262
+ log "Stopping packet writer..."
263
+ wait_for = 10
171
264
 
172
265
  begin
173
- server = UDPSocket.open
174
- server.bind('0.0.0.0', port)
175
- server.setsockopt(:SOCKET, :TIMESTAMP, true)
176
- optval = [0, 1].pack("l_2")
177
- server.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval)
178
- rescue Errno::EADDRINUSE
179
- RTP.log "RTP port #{port} in use, trying #{port + 1}..."
180
- port += 1
181
- port_retries += 1
182
- retry until port_retries == MAX_PORT_NUMBER_RETRIES + 1
183
- port = 9000
184
- raise
266
+ timeout(wait_for) do
267
+ sleep 0.2 until @packets.empty?
268
+ end
269
+ rescue Timeout::Error
270
+ log "Packet buffer not empty after #{wait_for} seconds. Trying to stop listener..."
271
+ stop_listener
185
272
  end
186
273
 
187
- @rtp_port = port
188
- RTP.log "UDP server setup to receive on port #{@rtp_port}"
274
+ @packet_writer.kill if writing_packets?
275
+ @packet_writer = nil
276
+ log "Packet writer stopped."
277
+
278
+ @capture_file.close
189
279
 
190
- server
280
+ !writing_packets?
191
281
  end
192
282
 
193
- # Sets up to receive data on a TCP socket, using +@rtp_port+.
283
+ # Sets SO_TIMESTAMP socket option to true. Sets SO_RCVTIMEO to 2.
194
284
  #
195
- # @param [Fixnum] port Port number to listen for RTP data on.
196
- # @return [TCPServer]
197
- def init_tcp_server(port)
198
- port_retries = 0
285
+ # @param [Socket] socket The Socket to set options on.
286
+ def set_socket_time_options(socket)
287
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_TIMESTAMP, true)
288
+ optval = [0, 1].pack("l_2")
289
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval)
290
+ end
199
291
 
200
- begin
201
- server = TCPServer.new(port)
202
- server.setsockopt(:SOCKET, :TIMESTAMP, true)
203
- optval = [0, 1].pack("l_2")
204
- server.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval)
205
- rescue Errno::EADDRINUSE
206
- RTP.log "RTP port #{port} in use, trying #{port + 1}..."
207
- port += 1
208
- port_retries += 1
209
- retry until port_retries == MAX_PORT_NUMBER_RETRIES + 1
210
- port = 9000
211
- raise
212
- end
292
+ # Sets Socket options to allow for multicasting. If ENV["RUBY_UPNP_ENV"] is
293
+ # equal to "testing", then it doesn't turn off multicast looping.
294
+ #
295
+ # @param [Socket] socket The socket to set the options on.
296
+ # @param [String] multicast_address The IP address to set the options on.
297
+ def setup_multicast_socket(socket, multicast_address)
298
+ set_membership(socket,
299
+ IPAddr.new(multicast_address).hton + IPAddr.new('0.0.0.0').hton)
300
+ set_multicast_ttl(socket, MULTICAST_TTL)
301
+ set_ttl(socket, MULTICAST_TTL)
302
+ end
213
303
 
214
- @rtp_port = port
215
- RTP.log "TCP server setup to receive on port #{@rtp_port}"
304
+ # @param [Socket] socket The socket to set the options on.
305
+ # @param [String] membership The network byte ordered String that represents
306
+ # the IP(s) that should join the membership group.
307
+ def set_membership(socket, membership)
308
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, membership)
309
+ end
310
+
311
+ # @param [Socket] socket The socket to set the options on.
312
+ # @param [Fixnum] ttl TTL to set IP_MULTICAST_TTL to.
313
+ def set_multicast_ttl(socket, ttl)
314
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, [ttl].pack('i'))
315
+ end
216
316
 
217
- server
317
+ # @param [Socket] socket The socket to set the options on.
318
+ # @param [Fixnum] ttl TTL to set IP_TTL to.
319
+ def set_ttl(socket, ttl)
320
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, [ttl].pack('i'))
218
321
  end
219
322
  end
220
- end
323
+ end