rtsp 0.0.1.alpha → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +3 -0
- data/.infinity_test +1 -1
- data/.yardopts +4 -0
- data/ChangeLog.rdoc +9 -0
- data/Gemfile +15 -6
- data/Gemfile.lock +78 -40
- data/LICENSE.rdoc +20 -0
- data/PostInstall.txt +0 -3
- data/README.rdoc +85 -36
- data/Rakefile +33 -49
- data/bin/rtsp_client +129 -0
- data/features/client_changes_state.feature +58 -0
- data/features/client_requests.feature +27 -0
- data/features/control_streams_as_client.feature +26 -0
- data/features/step_definitions/client_changes_state_steps.rb +46 -0
- data/features/step_definitions/client_requests_steps.rb +74 -0
- data/features/step_definitions/control_streams_as_client_steps.rb +34 -0
- data/features/support/env.rb +31 -29
- data/features/support/hooks.rb +3 -0
- data/gemspec.yml +30 -0
- data/lib/ext/logger.rb +8 -0
- data/lib/rtsp.rb +3 -6
- data/lib/rtsp/capturer.rb +105 -0
- data/lib/rtsp/client.rb +446 -204
- data/lib/rtsp/error.rb +6 -0
- data/lib/rtsp/global.rb +63 -0
- data/lib/rtsp/helpers.rb +28 -0
- data/lib/rtsp/message.rb +270 -0
- data/lib/rtsp/response.rb +89 -29
- data/lib/rtsp/transport_parser.rb +64 -0
- data/lib/rtsp/version.rb +4 -0
- data/nsm_test.rb +26 -0
- data/rtsp.gemspec +284 -0
- data/sarix_test.rb +23 -0
- data/soma_test.rb +39 -0
- data/spec/rtsp/client_spec.rb +302 -27
- data/spec/rtsp/helpers_spec.rb +53 -0
- data/spec/rtsp/message_spec.rb +420 -0
- data/spec/rtsp/response_spec.rb +144 -58
- data/spec/rtsp/transport_parser_spec.rb +54 -0
- data/spec/rtsp_spec.rb +3 -3
- data/spec/spec_helper.rb +66 -7
- data/spec/support/fake_rtsp_server.rb +123 -0
- data/tasks/metrics.rake +27 -0
- data/tasks/roodi_config.yml +14 -0
- data/tasks/stats.rake +12 -0
- metadata +174 -183
- data/.autotest +0 -23
- data/History.txt +0 -4
- data/Manifest.txt +0 -26
- data/bin/rtsp +0 -121
- data/features/step_definitions/stream_steps.rb +0 -50
- data/features/stream.feature +0 -17
- data/features/support/common.rb +0 -1
- data/features/support/world.rb +0 -1
- data/lib/rtsp/request_messages.rb +0 -104
- data/lib/rtsp/status_code.rb +0 -7
data/bin/rtsp_client
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib/')
|
8
|
+
require 'rtsp/client'
|
9
|
+
|
10
|
+
optparse = OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: #{__FILE__} [options] url"
|
12
|
+
|
13
|
+
#----------------------------------------------------------------------------
|
14
|
+
# Turn on logging
|
15
|
+
RTSP::Client.configure { |c| c.log = false }
|
16
|
+
opts.on('-d', '--debug', "Turn on RTSP::Client logging.") do
|
17
|
+
RTSP::Client.configure { |c| c.log = true }
|
18
|
+
end
|
19
|
+
|
20
|
+
#----------------------------------------------------------------------------
|
21
|
+
# Get description
|
22
|
+
opts.on('--describe [URL]', "Get description from the given URL.") do |url|
|
23
|
+
if url.nil?
|
24
|
+
puts "Must pass in a URL."
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
|
28
|
+
rtsp_client = RTSP::Client.new(url)
|
29
|
+
p rtsp_client.describe.body.inspect
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
#----------------------------------------------------------------------------
|
34
|
+
# Show available tracks
|
35
|
+
opts.on('--show-tracks [URL]', "Show available tracks from the given URL.") do |url|
|
36
|
+
if url.nil?
|
37
|
+
puts "Must pass in a URL."
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
|
41
|
+
rtsp_client = RTSP::Client.new(url)
|
42
|
+
rtsp_client.describe
|
43
|
+
puts "Aggregate control track: #{rtsp_client.aggregate_control_track}"
|
44
|
+
puts "Media control tracks: #{rtsp_client.media_control_tracks}"
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
|
48
|
+
#----------------------------------------------------------------------------
|
49
|
+
# Stream
|
50
|
+
opts.on('--stream [URL]', "Show available tracks from the given URL.") do |url|
|
51
|
+
if url.nil?
|
52
|
+
puts "Must pass in a URL."
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
rtsp_client = RTSP::Client.new(url) do |connection, capturer|
|
57
|
+
capturer.rtp_file = File.open("#{Dir.pwd}/rtsp_client_stream.rtp", "wb")
|
58
|
+
end
|
59
|
+
|
60
|
+
begin
|
61
|
+
puts "Getting server options..."
|
62
|
+
response = rtsp_client.options
|
63
|
+
puts "\tServer: #{response.server}"
|
64
|
+
puts "\tSupported methods: #{response.public}"
|
65
|
+
puts ""
|
66
|
+
|
67
|
+
puts "Getting description..."
|
68
|
+
response = rtsp_client.describe
|
69
|
+
puts "\tcache_control: #{response.cache_control}" if response.respond_to? "cache_control"
|
70
|
+
puts "\tdate: #{response.date}"
|
71
|
+
puts "\texpires: #{response.expires}" if response.respond_to? "expires"
|
72
|
+
puts "\tcontent_type: #{response.content_type}"
|
73
|
+
puts "\tcontent_base: #{response.content_base}"
|
74
|
+
puts "\tAggregate control track: #{rtsp_client.aggregate_control_track}"
|
75
|
+
puts "\tMedia control tracks: #{rtsp_client.media_control_tracks}"
|
76
|
+
puts ""
|
77
|
+
|
78
|
+
puts "Setting up #{rtsp_client.media_control_tracks.first}"
|
79
|
+
response = rtsp_client.setup rtsp_client.media_control_tracks.first
|
80
|
+
puts "\ttransport: #{response.transport}"
|
81
|
+
puts ""
|
82
|
+
|
83
|
+
if response.message == "OK"
|
84
|
+
puts "Playing #{rtsp_client.aggregate_control_track}"
|
85
|
+
r = rtsp_client.play rtsp_client.aggregate_control_track
|
86
|
+
puts "\trange: #{r.range}"
|
87
|
+
puts "\trtp_info: #{r.rtp_info}"
|
88
|
+
puts ""
|
89
|
+
|
90
|
+
|
91
|
+
1...5.times do
|
92
|
+
print "."
|
93
|
+
sleep 1
|
94
|
+
end
|
95
|
+
puts ""
|
96
|
+
|
97
|
+
if rtsp_client.capturer.rtp_file.size.zero?
|
98
|
+
puts "Captured 0 bytes. Your firewall might be blocking the RTP traffic on port #{rtsp_client.capturer.rtp_port}."
|
99
|
+
else
|
100
|
+
puts "RTP data captured to file: #{rtsp_client.capturer.rtp_file}"
|
101
|
+
end
|
102
|
+
|
103
|
+
puts "Tearing down..."
|
104
|
+
r2 = rtsp_client.teardown(rtsp_client.aggregate_control_track)
|
105
|
+
puts "\tconnection: #{r2.connection}" if r2.respond_to? "connection"
|
106
|
+
else
|
107
|
+
puts "Play call returned: #{response.message}. Exiting."
|
108
|
+
exit
|
109
|
+
end
|
110
|
+
rescue RTSP::Error => ex
|
111
|
+
puts ex.backtrace
|
112
|
+
puts ex.message
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#----------------------------------------------------------------------------
|
117
|
+
# Help
|
118
|
+
opts.on('-h', '--help', "You're looking at it.") do
|
119
|
+
puts opts
|
120
|
+
exit
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if ARGV.size.zero?
|
125
|
+
puts optparse.help if ARGV.size.zero?
|
126
|
+
exit
|
127
|
+
end
|
128
|
+
optparse.parse!
|
129
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
Feature: Client changes state
|
2
|
+
As an RTSP client user
|
3
|
+
I want to monitor the state of my client
|
4
|
+
So that I can be sure of what my client is doing at any time
|
5
|
+
|
6
|
+
Scenario Outline: State doesn't change after certain requests
|
7
|
+
Given I haven't made any RTSP requests
|
8
|
+
When I issue an "<request_type>" request with "<parameters>"
|
9
|
+
Then the state stays the same
|
10
|
+
Examples:
|
11
|
+
| request_type | parameters |
|
12
|
+
| options | |
|
13
|
+
| describe | |
|
14
|
+
|
15
|
+
Scenario Outline: State changes from Init
|
16
|
+
Given I haven't made any RTSP requests
|
17
|
+
When I issue an "<request_type>" request with "<parameters>"
|
18
|
+
Then the state changes to "<state_result>"
|
19
|
+
Examples:
|
20
|
+
| request_type | parameters | state_result |
|
21
|
+
| setup | url | ready |
|
22
|
+
| teardown | url | init |
|
23
|
+
|
24
|
+
Scenario Outline: State changes from Ready
|
25
|
+
Given I have set up a stream
|
26
|
+
When I issue an "<request_type>" request with "<parameters>"
|
27
|
+
Then the state changes to "<state_result>"
|
28
|
+
Examples:
|
29
|
+
| request_type | parameters | state_result |
|
30
|
+
| play | url | playing |
|
31
|
+
| record | url | recording |
|
32
|
+
| teardown | url | init |
|
33
|
+
| setup | url | ready |
|
34
|
+
|
35
|
+
Scenario Outline: State changes from Playing
|
36
|
+
Given I have set up a stream
|
37
|
+
And I have started playing a stream
|
38
|
+
When I issue an "<request_type>" request with "<parameters>"
|
39
|
+
Then the state changes to "<state_result>"
|
40
|
+
Examples:
|
41
|
+
| request_type | parameters | state_result |
|
42
|
+
| pause | url | ready |
|
43
|
+
| teardown | url | init |
|
44
|
+
| play | url | playing |
|
45
|
+
| setup | url | playing |
|
46
|
+
|
47
|
+
Scenario Outline: State changes from Recording
|
48
|
+
Given I have set up a stream
|
49
|
+
And I have started recording a stream
|
50
|
+
When I issue an "<request_type>" request with "<parameters>"
|
51
|
+
Then the state changes to "<state_result>"
|
52
|
+
Examples:
|
53
|
+
| request_type | parameters | state_result |
|
54
|
+
| pause | url | ready |
|
55
|
+
| teardown | url | init |
|
56
|
+
| record | url | recording |
|
57
|
+
| setup | url | recording |
|
58
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Feature: Client request messages
|
2
|
+
As an RTSP client API user
|
3
|
+
I want to make RTSP requests
|
4
|
+
So that I can build a client using these request messages
|
5
|
+
|
6
|
+
Scenario: OPTIONS
|
7
|
+
Given a known RTSP server
|
8
|
+
When I make a "options" request
|
9
|
+
Then I should receive an RTSP response to that OPTIONS request
|
10
|
+
|
11
|
+
Scenario: DESCRIBE
|
12
|
+
Given a known RTSP server
|
13
|
+
When I make a "describe" request
|
14
|
+
Then I should receive an RTSP response to that DESCRIBE request
|
15
|
+
|
16
|
+
Scenario: ANNOUNCE
|
17
|
+
Given a known RTSP server
|
18
|
+
When I make a "announce" request
|
19
|
+
Then I should receive an RTSP response to that ANNOUNCE request
|
20
|
+
|
21
|
+
Scenario: SETUP
|
22
|
+
Given a known RTSP server
|
23
|
+
When I make a "setup" request with headers:
|
24
|
+
| header | value |
|
25
|
+
| transport | RTP/AVP |
|
26
|
+
Then I should receive an RTSP response to that SETUP request
|
27
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Control stream from an RTSP server
|
2
|
+
As an RTSP consumer
|
3
|
+
I want to be able to control RTSP streams from a server
|
4
|
+
So that I can view its contents as I desire
|
5
|
+
|
6
|
+
@wip
|
7
|
+
Scenario: Play single stream
|
8
|
+
Given an RTSP server at "10.221.222.235" and port 9010 and URL ""
|
9
|
+
When I play a stream from that server
|
10
|
+
Then I should not receive any errors
|
11
|
+
And I should receive data on the same port
|
12
|
+
|
13
|
+
Scenario: Play then pause single stream
|
14
|
+
Given an RTSP server at "10.221.222.235" and port 9010 and URL ""
|
15
|
+
When I play a stream from that server for 10 seconds
|
16
|
+
And I pause that stream
|
17
|
+
Then I should not receive data on the same port
|
18
|
+
|
19
|
+
Scenario: Play two streams individually and simultaneously
|
20
|
+
|
21
|
+
Scenario: Play then pause two streams individually and simultaneously
|
22
|
+
|
23
|
+
Scenario: Play two streams using the aggregate control URL
|
24
|
+
|
25
|
+
Scenario: Play then pause two streams using the aggregate control URL
|
26
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Given /^I haven't made any RTSP requests$/ do
|
2
|
+
RTSP::Client.configure { |config| config.log = false }
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^I have set up a stream$/ do
|
6
|
+
@url = "rtsp://fake-rtsp-server/some_path"
|
7
|
+
@client = RTSP::Client.new(@url) do |connection|
|
8
|
+
connection.socket = @fake_server
|
9
|
+
connection.timeout = 3
|
10
|
+
end
|
11
|
+
@client.setup @url
|
12
|
+
@client.session_state.should == :ready
|
13
|
+
end
|
14
|
+
|
15
|
+
Given /^I have started (playing|recording) a stream$/ do |method|
|
16
|
+
if method == "playing"
|
17
|
+
@client.setup @url
|
18
|
+
@client.play @url
|
19
|
+
elsif method == "recording"
|
20
|
+
@client.record @url
|
21
|
+
end
|
22
|
+
@client.session_state.should == method.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
When /^I issue an "([^"]*)" request with "([^"]*)"$/ do |request_type, params|
|
26
|
+
unless @client
|
27
|
+
url = "rtsp://fake-rtsp-server/some_path"
|
28
|
+
@client = RTSP::Client.new(url) do |connection|
|
29
|
+
connection.socket = @fake_server
|
30
|
+
connection.timeout = 3
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@initial_state = @client.session_state
|
35
|
+
params = params.empty? ? {} : params
|
36
|
+
|
37
|
+
@client.send(request_type.to_sym, params)
|
38
|
+
end
|
39
|
+
|
40
|
+
Then /^the state stays the same$/ do
|
41
|
+
@client.session_state.should == @initial_state
|
42
|
+
end
|
43
|
+
|
44
|
+
Then /^the state changes to "([^"]*)"$/ do |new_state|
|
45
|
+
@client.session_state.should == new_state.to_sym
|
46
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
Given /^a known RTSP server$/ do
|
2
|
+
@server_url = "rtsp://64.202.98.91:554/sa.sdp"
|
3
|
+
@client = RTSP::Client.new(@server_url) do |connection|
|
4
|
+
connection.socket = @fake_server
|
5
|
+
connection.timeout = 3
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^I make a "([^"]*)" request$/ do |method|
|
10
|
+
=begin
|
11
|
+
@response = RTSP::Request.execute({
|
12
|
+
:method => method.to_sym,
|
13
|
+
:resource_url => @server_url }
|
14
|
+
)
|
15
|
+
=end
|
16
|
+
@response = @client.send(method.to_sym)
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^I make a "([^"]*)" request with headers:$/ do |method, headers_table|
|
20
|
+
# table is a Cucumber::Ast::Table
|
21
|
+
headers = {}
|
22
|
+
|
23
|
+
headers_table.hashes.each do |hash|
|
24
|
+
header_type = hash["header"].to_sym
|
25
|
+
headers[header_type] = hash["value"]
|
26
|
+
end
|
27
|
+
|
28
|
+
=begin
|
29
|
+
@response = RTSP::Request.execute({
|
30
|
+
:method => method.to_sym,
|
31
|
+
:headers => headers,
|
32
|
+
:resource_url => @server_url }
|
33
|
+
)
|
34
|
+
=end
|
35
|
+
@response = @client.send(method.to_sym, headers)
|
36
|
+
end
|
37
|
+
|
38
|
+
Then /^I should receive an RTSP response to that OPTIONS request$/ do
|
39
|
+
@response.is_a?(RTSP::Response).should be_true
|
40
|
+
@response.code.should == 200
|
41
|
+
@response.message.should == "OK"
|
42
|
+
@response.cseq.should == 1
|
43
|
+
@response.public.should == "DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"
|
44
|
+
@response.body.should be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
Then /^I should receive an RTSP response to that DESCRIBE request$/ do
|
48
|
+
@response.is_a?(RTSP::Response).should be_true
|
49
|
+
@response.code.should == 200
|
50
|
+
@response.message.should == "OK"
|
51
|
+
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
52
|
+
@response.cseq.should == 1
|
53
|
+
@response.body.is_a?(SDP::Description).should be_true
|
54
|
+
@response.body.protocol_version.should == "0"
|
55
|
+
@response.body.username.should == "-"
|
56
|
+
end
|
57
|
+
|
58
|
+
Then /^I should receive an RTSP response to that ANNOUNCE request$/ do
|
59
|
+
@response.is_a?(RTSP::Response).should be_true
|
60
|
+
@response.code.should == 200
|
61
|
+
@response.message.should == "OK"
|
62
|
+
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
63
|
+
@response.cseq.should == 1
|
64
|
+
end
|
65
|
+
|
66
|
+
Then /^I should receive an RTSP response to that SETUP request$/ do
|
67
|
+
@response.is_a?(RTSP::Response).should be_true
|
68
|
+
@response.code.should == 200
|
69
|
+
@response.message.should == "OK"
|
70
|
+
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
71
|
+
@response.cseq.should == 1
|
72
|
+
@response.transport.should == ""
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Given /^an RTSP server at "([^"]*)" and port (\d+)$/ do |ip_address, port|
|
2
|
+
@rtp_port = port.to_i
|
3
|
+
@client = RTSP::Client.new(ip_address) do |connection, capturer|
|
4
|
+
capturer.rtp_port = @rtp_port
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
Given /^an RTSP server at "([^"]*)" and port (\d+) and URL "([^"]*)"$/ do |ip_address, port, path|
|
9
|
+
uri = "rtsp://#{ip_address}:#{port}#{path}"
|
10
|
+
@rtp_port = port
|
11
|
+
@client = RTSP::Client.new(uri) do |connection, capturer|
|
12
|
+
capturer.rtp_port = @rtp_port
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
When /^I play a stream from that server$/ do
|
17
|
+
@play_result = lambda { @client.play }
|
18
|
+
end
|
19
|
+
|
20
|
+
Then /^I should not receive any errors$/ do
|
21
|
+
@play_result.should_not raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
Then /^I should receive data on the same port$/ do
|
25
|
+
@client.capturer.rtp_file.should_not be_empty
|
26
|
+
end
|
27
|
+
|
28
|
+
Given /^I know what the describe response looks like$/ do
|
29
|
+
@response_text = @fake_server.describe
|
30
|
+
end
|
31
|
+
|
32
|
+
When /^I ask the server to describe$/ do
|
33
|
+
puts @client.describe
|
34
|
+
end
|
data/features/support/env.rb
CHANGED
@@ -1,37 +1,39 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'socket'
|
3
3
|
require 'timeout'
|
4
|
+
require 'cucumber/rspec/doubles'
|
4
5
|
|
5
6
|
$:.unshift(File.dirname(__FILE__) + '/../../lib')
|
6
7
|
require 'rtsp/client'
|
7
8
|
|
9
|
+
$:.unshift(File.dirname(__FILE__) + '/../../spec/support')
|
10
|
+
require 'fake_rtsp_server'
|
8
11
|
|
9
|
-
DESCRIBE_RESPONSE =
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
x-Accept-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
a=x-qt-text-
|
29
|
-
a=x-qt-text-
|
30
|
-
a=
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
a=
|
35
|
-
a=
|
36
|
-
|
37
|
-
RESP
|
12
|
+
DESCRIBE_RESPONSE = %Q{ RTSP/1.0 200 OK\r
|
13
|
+
Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r
|
14
|
+
Cseq: 2\r
|
15
|
+
Cache-Control: no-cache\r
|
16
|
+
Content-length: 406\r
|
17
|
+
Date: Sun, 23 Jan 2011 00:36:45 GMT\r
|
18
|
+
Expires: Sun, 23 Jan 2011 00:36:45 GMT\r
|
19
|
+
Content-Type: application/sdp\r
|
20
|
+
x-Accept-Retransmit: our-retransmit\r
|
21
|
+
x-Accept-Dynamic-Rate: 1\r
|
22
|
+
Content-Base: rtsp://64.202.98.91:554/gs.sdp/\r
|
23
|
+
\r\n\r
|
24
|
+
v=0\r
|
25
|
+
o=- 545877020 467920391 IN IP4 127.0.0.1\r
|
26
|
+
s=Groove Salad from SomaFM [aacPlus]\r
|
27
|
+
i=Downtempo Ambient Groove\r
|
28
|
+
c=IN IP4 0.0.0.0\r
|
29
|
+
t=0 0\r
|
30
|
+
a=x-qt-text-cmt:Orban Opticodec-PC\r\n
|
31
|
+
a=x-qt-text-nam:Groove Salad from SomaFM [aacPlus]\r
|
32
|
+
a=x-qt-text-inf:Downtempo Ambient Groove\r
|
33
|
+
a=control:*\r
|
34
|
+
m=audio 0 RTP/AVP 96\r
|
35
|
+
b=AS:48\r
|
36
|
+
a=rtpmap:96 MP4A-LATM/44100/2\r
|
37
|
+
a=fmtp:96 cpresent=0;config=400027200000\r
|
38
|
+
a=control:trackID=1\r
|
39
|
+
\r\n}
|