rtsp_server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/ChangeLog.rdoc +74 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.rdoc +20 -0
  6. data/README.rdoc +152 -0
  7. data/Rakefile +23 -0
  8. data/bin/rtsp_client +133 -0
  9. data/features/client_changes_state.feature +58 -0
  10. data/features/client_requests.feature +27 -0
  11. data/features/control_streams_as_client.feature +26 -0
  12. data/features/step_definitions/client_changes_state_steps.rb +52 -0
  13. data/features/step_definitions/client_requests_steps.rb +68 -0
  14. data/features/step_definitions/control_streams_as_client_steps.rb +34 -0
  15. data/features/support/env.rb +50 -0
  16. data/features/support/hooks.rb +3 -0
  17. data/lib/ext/logger.rb +8 -0
  18. data/lib/rtsp/client.rb +520 -0
  19. data/lib/rtsp/common.rb +148 -0
  20. data/lib/rtsp/error.rb +6 -0
  21. data/lib/rtsp/global.rb +63 -0
  22. data/lib/rtsp/helpers.rb +28 -0
  23. data/lib/rtsp/message.rb +272 -0
  24. data/lib/rtsp/request.rb +39 -0
  25. data/lib/rtsp/response.rb +47 -0
  26. data/lib/rtsp/server.rb +303 -0
  27. data/lib/rtsp/socat_streaming.rb +309 -0
  28. data/lib/rtsp/stream_server.rb +37 -0
  29. data/lib/rtsp/transport_parser.rb +96 -0
  30. data/lib/rtsp/version.rb +4 -0
  31. data/lib/rtsp.rb +6 -0
  32. data/rtsp.gemspec +42 -0
  33. data/spec/rtsp/client_spec.rb +326 -0
  34. data/spec/rtsp/helpers_spec.rb +53 -0
  35. data/spec/rtsp/message_spec.rb +420 -0
  36. data/spec/rtsp/response_spec.rb +306 -0
  37. data/spec/rtsp/transport_parser_spec.rb +137 -0
  38. data/spec/rtsp_spec.rb +27 -0
  39. data/spec/spec_helper.rb +88 -0
  40. data/spec/support/fake_rtsp_server.rb +123 -0
  41. data/tasks/roodi.rake +9 -0
  42. data/tasks/roodi_config.yaml +14 -0
  43. data/tasks/stats.rake +12 -0
  44. metadata +280 -0
@@ -0,0 +1,96 @@
1
+ require 'parslet'
2
+
3
+ module RTSP
4
+
5
+ # Used for parsing the Transport header--mainly as the response from the
6
+ # SETUP request. The values from this are used to determine what to use for
7
+ # other requests.
8
+ class TransportParser < Parslet::Parser
9
+ rule(:transport_specifier) do
10
+ match('[A-Za-z]').repeat(3).as(:streaming_protocol) >> forward_slash >>
11
+ match('[A-Za-z]').repeat(3).as(:profile) >>
12
+ (forward_slash >> match('[A-Za-z]').repeat(3).as(:transport_protocol)).maybe
13
+ end
14
+
15
+ rule(:broadcast_type) do
16
+ str('unicast') | str('multicast')
17
+ end
18
+
19
+ rule(:destination) do
20
+ str('destination=') >> ip_address.as(:destination)
21
+ end
22
+
23
+ rule(:source) do
24
+ str('source=') >> ip_address.as(:source)
25
+ end
26
+
27
+ rule(:client_port) do
28
+ str('client_port=') >> number.as(:rtp) >> dash >> number.as(:rtcp)
29
+ end
30
+
31
+ rule(:server_port) do
32
+ str('server_port=') >> number.as(:rtp) >> dash >> number.as(:rtcp)
33
+ end
34
+
35
+ rule(:interleaved) do
36
+ str('interleaved=') >> number.as(:rtp_channel) >> dash >>
37
+ number.as(:rtcp_channel)
38
+ end
39
+
40
+ rule(:ttl) do
41
+ str('ttl=') >> match('[\d]').repeat(1,3).as(:ttl)
42
+ end
43
+
44
+ rule(:port) do
45
+ str('port=') >> match('[\d]').repeat(1,5).as(:rtp) >>
46
+ dash.maybe >> match('[\d]').repeat(1,5).as(:rtcp).maybe
47
+ end
48
+
49
+ rule(:ssrc) do
50
+ str('ssrc=') >> match('[0-9A-Fa-f]').repeat(8).as(:ssrc)
51
+ end
52
+
53
+ rule(:channel) do
54
+ str('channel=') >> match('[\w]').repeat(1,3).as(:channel)
55
+ end
56
+
57
+ rule(:address) do
58
+ str('address=') >> match('[\S]').repeat.as(:address)
59
+ end
60
+
61
+ rule(:mode) do
62
+ str('mode=') >> str('"').maybe >> match('[A-Za-z]').repeat.as(:mode) >>
63
+ str('"').maybe
64
+ end
65
+
66
+ rule(:ip_address) do
67
+ match('[\d]').repeat(1,3) >> str('.') >>
68
+ match('[\d]').repeat(1,3) >> str('.') >>
69
+ match('[\d]').repeat(1,3) >> str('.') >>
70
+ match('[\d]').repeat(1,3)
71
+ end
72
+
73
+ rule(:number) { match('[\d]').repeat }
74
+ rule(:forward_slash) { match('[/]') }
75
+ rule(:semi_colon) { match('[;]') }
76
+ rule(:dash) { match('[-]') }
77
+
78
+ rule(:header_field) do
79
+ transport_specifier >>
80
+ (semi_colon >> broadcast_type.as(:broadcast_type)).maybe >>
81
+ (semi_colon >> destination).maybe >>
82
+ (semi_colon >> source).maybe >>
83
+ (semi_colon >> client_port.as(:client_port)).maybe >>
84
+ (semi_colon >> server_port.as(:server_port)).maybe >>
85
+ (semi_colon >> interleaved.as(:interleaved)).maybe >>
86
+ (semi_colon >> ttl).maybe >>
87
+ (semi_colon >> port.as(:port)).maybe >>
88
+ (semi_colon >> ssrc).maybe >>
89
+ (semi_colon >> channel).maybe >>
90
+ (semi_colon >> address).maybe >>
91
+ (semi_colon >> mode).maybe
92
+ end
93
+
94
+ root :header_field
95
+ end
96
+ end
@@ -0,0 +1,4 @@
1
+ module RTSP
2
+ # rtsp version
3
+ VERSION = "0.0.1"
4
+ end
data/lib/rtsp.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'pathname'
2
+ require_relative 'rtsp/version'
3
+
4
+ # This base module simply defines properties about the library. See child
5
+ # classes/modules for the meat.
6
+ module RTSP; end
data/rtsp.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('lib/', File.dirname(__FILE__))
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ require 'rtsp/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "rtsp_server"
9
+ s.version = RTSP::VERSION
10
+
11
+ s.homepage = %q{https://github.com/turboladen/rtsp}
12
+ s.authors = ["Steve Loveless, Mike Kirby", "Sujin Philip"]
13
+ s.summary = %q{Library to allow RTSP streaming from RTSP-enabled devices.}
14
+ s.description = %q{This library intends to follow the RTSP RFC document (2326)
15
+ to allow for working with RTSP servers. At this point, it's up to you to parse
16
+ the data from a play call, but we'll get there. ...eventually.
17
+ For more information see: http://www.ietf.org/rfc/rfc2326.txt}
18
+ s.email = %w{steve.loveless@gmail.com}
19
+ s.licenses = %w{MIT}
20
+
21
+ s.executables = %w{rtsp_client}
22
+ s.files = Dir.glob("{lib,bin,spec,tasks}/**/*") + Dir.glob("*.rdoc") +
23
+ %w(.gemtest rtsp.gemspec Gemfile Rakefile)
24
+ s.extra_rdoc_files = %w{ChangeLog.rdoc LICENSE.rdoc README.rdoc}
25
+ s.require_paths = %w{lib}
26
+ s.rubygems_version = %q{1.7.2}
27
+ s.test_files = Dir.glob("{spec,features}/**/*")
28
+
29
+ s.add_runtime_dependency(%q<parslet>, [">= 1.1.0"])
30
+ s.add_runtime_dependency(%q<rtp>, [">= 0.0.1"])
31
+ s.add_runtime_dependency(%q<sdp>, ["~> 0.2.6"])
32
+ s.add_runtime_dependency(%q<sys-proctable>, ["> 0"])
33
+
34
+ s.add_development_dependency(%q<bundler>)
35
+ s.add_development_dependency(%q<code_statistics>, ["~> 0.2.13"])
36
+ s.add_development_dependency(%q<cucumber>, [">= 1.1.0"])
37
+ s.add_development_dependency(%q<roodi>, [">= 2.1.0"])
38
+ s.add_development_dependency(%q<rake>, [">= 0.8.7"])
39
+ s.add_development_dependency(%q<rspec>, [">= 2.5.0"])
40
+ s.add_development_dependency(%q<simplecov>, [">= 0.4.0"])
41
+ s.add_development_dependency(%q<yard>, [">= 0.6.0"])
42
+ end
@@ -0,0 +1,326 @@
1
+ require 'sdp'
2
+ require_relative '../spec_helper'
3
+ require 'rtsp/client'
4
+ require_relative '../support/fake_rtsp_server'
5
+
6
+ describe RTSP::Client do
7
+ def setup_client_at(url)
8
+ fake_rtsp_server = FakeRTSPServer.new
9
+ mock_logger = double 'MockLogger', :send => nil
10
+
11
+ client = RTSP::Client.new(url) do |connection|
12
+ connection.socket = fake_rtsp_server
13
+ end
14
+
15
+ RTSP::Client.reset_config!
16
+ RTSP::Client.configure { |config| config.log = false }
17
+ client.logger = mock_logger
18
+
19
+ client
20
+ end
21
+
22
+ before do
23
+ RTP::Receiver.any_instance.stub(:run)
24
+ RTP::Receiver.any_instance.stub(:stop)
25
+ end
26
+
27
+ describe "#initialize" do
28
+ before :each do
29
+ mock_socket = double 'MockSocket'
30
+ @client = RTSP::Client.new("rtsp://localhost") do |connection|
31
+ connection.socket = mock_socket
32
+ end
33
+ end
34
+
35
+ it "sets @cseq to 1" do
36
+ @client.instance_variable_get(:@cseq).should == 1
37
+ end
38
+
39
+ it "sets @session_state to :init" do
40
+ @client.instance_variable_get(:@session_state).should == :init
41
+ end
42
+
43
+ it "sets @server_uri to be a URI containing the first init param + 554" do
44
+ @client.instance_variable_get(:@server_uri).should be_a(URI)
45
+ @client.instance_variable_get(:@server_uri).to_s.should ==
46
+ "rtsp://localhost:554"
47
+ end
48
+ end
49
+
50
+ describe ".configure" do
51
+ around do |example|
52
+ RTSP::Client.reset_config!
53
+ example.run
54
+ RTSP::Client.reset_config!
55
+ RTSP::Client.log = false
56
+ end
57
+
58
+ before :each do
59
+ mock_socket = double 'MockSocket'
60
+
61
+ @client = RTSP::Client.new("rtsp://localhost") do |connection|
62
+ connection.socket = mock_socket
63
+ end
64
+ end
65
+
66
+ describe "log" do
67
+ it "should default to true" do
68
+ RTSP::Client.log?.should be_true
69
+ end
70
+
71
+ it "should set whether to log RTSP requests/responses" do
72
+ RTSP::Client.configure { |config| config.log = false }
73
+ RTSP::Client.log?.should be_false
74
+ end
75
+ end
76
+
77
+ describe "logger" do
78
+ it "should set the logger to use" do
79
+ MyLogger = Class.new
80
+ RTSP::Client.configure { |config| config.logger = MyLogger }
81
+ RTSP::Client.logger.should == MyLogger
82
+ end
83
+
84
+ it "should default to Logger writing to STDOUT" do
85
+ RTSP::Client.logger.should be_a(Logger)
86
+ end
87
+ end
88
+
89
+ describe "log_level" do
90
+ it "should default to :debug" do
91
+ RTSP::Client.log_level.should == :debug
92
+ end
93
+
94
+ it "should set the log level to use" do
95
+ RTSP::Client.configure { |config| config.log_level = :info }
96
+ RTSP::Client.log_level.should == :info
97
+ end
98
+ end
99
+ end
100
+
101
+ it "handles empty non-existent CSeq header" do
102
+ pending
103
+ end
104
+
105
+ context "#server_url" do
106
+ before :each do
107
+ @client = setup_client_at "rtsp://localhost"
108
+ end
109
+
110
+ it "allows for changing the server URL on the fly" do
111
+ @client.server_uri.to_s.should == "rtsp://localhost:554"
112
+ @client.server_url = "rtsp://localhost:8080"
113
+ @client.server_uri.to_s.should == "rtsp://localhost:8080"
114
+ end
115
+
116
+ it "raises when passing in something other than a String" do
117
+ @client.server_uri.to_s.should == "rtsp://localhost:554"
118
+ lambda { @client.server_url = [] }.should raise_error
119
+ end
120
+ end
121
+
122
+ describe "#options" do
123
+ before :each do
124
+ @client = setup_client_at "rtsp://localhost"
125
+ end
126
+
127
+ it "extracts the server's supported methods" do
128
+ @client.options
129
+ @client.supported_methods.should ==
130
+ [:describe, :setup, :teardown, :play, :pause]
131
+ end
132
+
133
+ it "returns a Response" do
134
+ response = @client.options
135
+ response.is_a?(RTSP::Response).should be_true
136
+ end
137
+ end
138
+
139
+ describe "#describe" do
140
+ before do
141
+ @client = setup_client_at "rtsp://localhost"
142
+ @response = @client.describe
143
+ end
144
+
145
+ it "extracts the aggregate control track" do
146
+ @client.aggregate_control_track.should == "rtsp://64.202.98.91:554/sa.sdp/"
147
+ end
148
+
149
+ it "extracts the media control tracks" do
150
+ @client.media_control_tracks.should == ["rtsp://64.202.98.91:554/sa.sdp/trackID=1"]
151
+ end
152
+
153
+ it "extracts the SDP object" do
154
+ @client.instance_variable_get(:@session_description).should ==
155
+ @response.body
156
+ end
157
+
158
+ it "extracts the Content-Base header" do
159
+ @client.instance_variable_get(:@content_base).should ==
160
+ URI.parse("rtsp://64.202.98.91:554/sa.sdp/")
161
+ end
162
+
163
+ it "returns a Response" do
164
+ @response.is_a?(RTSP::Response).should be_true
165
+ end
166
+ end
167
+
168
+ describe "#announce" do
169
+ it "returns a Response" do
170
+ client = setup_client_at "rtsp://localhost"
171
+ sdp = SDP::Description.new
172
+ client.setup("rtsp://localhost/another_track")
173
+ response = client.announce("rtsp://localhost/another_track", sdp)
174
+ response.is_a?(RTSP::Response).should be_true
175
+ end
176
+ end
177
+
178
+ describe "#setup" do
179
+ before :each do
180
+ @client = setup_client_at "rtsp://localhost"
181
+ end
182
+
183
+ it "extracts the session number" do
184
+ @client.session.should be_empty
185
+ @client.setup("rtsp://localhost/some_track")
186
+ @client.session[:session_id].should == 1234567890
187
+ end
188
+
189
+ it "changes the session_state to :ready" do
190
+ @client.setup("rtsp://localhost/some_track")
191
+ @client.session_state.should == :ready
192
+ end
193
+
194
+ it "extracts the transport header info" do
195
+ @client.instance_variable_get(:@transport).should be_nil
196
+ @client.setup("rtsp://localhost/some_track")
197
+ @client.instance_variable_get(:@transport).should_not be_nil
198
+ end
199
+
200
+ it "returns a Response" do
201
+ response = @client.setup("rtsp://localhost/some_track")
202
+ response.is_a?(RTSP::Response).should be_true
203
+ end
204
+ end
205
+
206
+ describe "#play" do
207
+ before :each do
208
+ @client = setup_client_at "rtsp://localhost"
209
+ end
210
+
211
+ after :each do
212
+ @client.teardown "rtsp://localhost"
213
+ end
214
+
215
+ it "changes the session_state to :playing" do
216
+ @client.setup("rtsp://localhost/some_track")
217
+ @client.play("rtsp://localhost/some_track")
218
+ @client.session_state.should == :playing
219
+ end
220
+
221
+ it "returns a Response" do
222
+ @client.setup("rtsp://localhost/some_track")
223
+ response = @client.play("rtsp://localhost/some_track")
224
+ response.is_a?(RTSP::Response).should be_true
225
+ end
226
+ end
227
+
228
+ describe "#pause" do
229
+ before :each do
230
+ @client = setup_client_at "rtsp://localhost"
231
+ @client.setup("rtsp://localhost/some_track")
232
+ end
233
+
234
+ after :each do
235
+ @client.teardown "rtsp://localhost"
236
+ end
237
+
238
+ it "changes the session_state from :playing to :ready" do
239
+ @client.play("rtsp://localhost/some_track")
240
+ @client.pause("rtsp://localhost/some_track")
241
+ @client.session_state.should == :ready
242
+ end
243
+
244
+ it "changes the session_state from :recording to :ready" do
245
+ @client.record("rtsp://localhost/some_track")
246
+ @client.pause("rtsp://localhost/some_track")
247
+ @client.session_state.should == :ready
248
+ end
249
+
250
+ it "returns a Response" do
251
+ response = @client.pause("rtsp://localhost/some_track")
252
+ response.is_a?(RTSP::Response).should be_true
253
+ end
254
+ end
255
+
256
+ describe "#teardown" do
257
+ before :each do
258
+ @client = setup_client_at "rtsp://localhost"
259
+ @client.setup("rtsp://localhost/some_track")
260
+ end
261
+
262
+ it "changes the session_state to :init" do
263
+ @client.session_state.should_not == :init
264
+ @client.teardown("rtsp://localhost/some_track")
265
+ @client.session_state.should == :init
266
+ end
267
+
268
+ it "changes the session_id back to 0" do
269
+ @client.session.should_not be_empty
270
+ @client.teardown("rtsp://localhost/some_track")
271
+ @client.session.should be_empty
272
+ end
273
+
274
+ it "returns a Response" do
275
+ response = @client.teardown("rtsp://localhost/some_track")
276
+ response.is_a?(RTSP::Response).should be_true
277
+ end
278
+ end
279
+
280
+ describe "#get_parameter" do
281
+ before :each do
282
+ @client = setup_client_at "rtsp://localhost"
283
+ end
284
+
285
+ it "returns a Response" do
286
+ response = @client.get_parameter("rtsp://localhost/some_track", "ping!")
287
+ response.is_a?(RTSP::Response).should be_true
288
+ end
289
+ end
290
+
291
+ describe "#set_parameter" do
292
+ before :each do
293
+ @client = setup_client_at "rtsp://localhost"
294
+ end
295
+
296
+ it "returns a Response" do
297
+ response = @client.set_parameter("rtsp://localhost/some_track", "ping!")
298
+ response.is_a?(RTSP::Response).should be_true
299
+ end
300
+ end
301
+
302
+ describe "#record" do
303
+ before :each do
304
+ @client = setup_client_at "rtsp://localhost"
305
+ @client.setup("rtsp://localhost/some_track")
306
+ end
307
+
308
+ it "returns a Response" do
309
+ response = @client.record("rtsp://localhost/some_track")
310
+ response.is_a?(RTSP::Response).should be_true
311
+ end
312
+
313
+ it "changes the session_state to :recording" do
314
+ @client.session_state.should == :ready
315
+ @client.record("rtsp://localhost/some_track")
316
+ @client.session_state.should == :recording
317
+ end
318
+ end
319
+
320
+ describe "#send_message" do
321
+ it "raises if the send takes longer than the timeout" do
322
+ pending "until I figure out how to test the time out raises"
323
+ @client = setup_client_at "rtsp://localhost"
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,53 @@
1
+ require_relative '../spec_helper'
2
+ require 'rtsp/helpers'
3
+
4
+ class HelperTest
5
+ include RTSP::Helpers
6
+ end
7
+
8
+ describe RTSP::Helpers do
9
+ describe "#build_resource_uri_from" do
10
+ before do
11
+ @my_object = HelperTest.new
12
+ end
13
+
14
+ context "parses the resource URL to a URI" do
15
+ it "with scheme, IP, path; port defaults to 554" do
16
+ uri = @my_object.build_resource_uri_from "rtsp://64.202.98.91/sa.sdp"
17
+ uri.scheme.should == "rtsp"
18
+ uri.host.should == "64.202.98.91"
19
+ uri.port.should == 554
20
+ uri.path.should == "/sa.sdp"
21
+ end
22
+
23
+ it "with IP, path; port defaults to 554; scheme defaults to 'rtsp'" do
24
+ uri = @my_object.build_resource_uri_from "64.202.98.91/sa.sdp"
25
+ uri.scheme.should == "rtsp"
26
+ uri.host.should == "64.202.98.91"
27
+ uri.port.should == 554
28
+ uri.path.should == "/sa.sdp"
29
+ end
30
+
31
+ it "with scheme, IP, port" do
32
+ uri = @my_object.build_resource_uri_from "rtsp://64.202.98.91"
33
+ uri.scheme.should == "rtsp"
34
+ uri.host.should == "64.202.98.91"
35
+ uri.port.should == 554
36
+ uri.path.should == ""
37
+ uri.to_s.should == "rtsp://64.202.98.91:554"
38
+ end
39
+
40
+ it "handles passing in a URI" do
41
+ uri = @my_object.build_resource_uri_from "rtsp://64.202.98.91"
42
+ lambda { @my_object.build_resource_uri_from uri
43
+ }.should raise_error
44
+ end
45
+
46
+ it "raises if not given a String" do
47
+ lambda do
48
+ @my_object.build_resource_uri_from URI.parse "rtsp://64.202.98.91"
49
+ end.should raise_exception RTSP::Error
50
+ end
51
+ end
52
+ end
53
+ end