Permalink
Browse files

adding initial code

git-svn-id: svn://rubyforge.org/var/svn/raop/trunk@1 55bd698d-9d04-498c-8937-980560813b27
  • Loading branch information...
aaronp
aaronp committed Jun 13, 2007
0 parents commit e117e9a4ed49d3750872ce24caf2120c78f86bf1
Showing with 808 additions and 0 deletions.
  1. +6 −0 CHANGELOG.txt
  2. +340 −0 LICENSE.txt
  3. +14 −0 Manifest.txt
  4. +32 −0 README.txt
  5. +16 −0 Rakefile
  6. +8 −0 lib/raop.rb
  7. +44 −0 lib/raop/bit_buffer.rb
  8. +124 −0 lib/raop/client.rb
  9. +189 −0 lib/raop/rtsp.rb
  10. +1 −0 test/data/frame.txt
  11. BIN test/data/o.raw
  12. +1 −0 test/data/out_frame.txt
  13. +18 −0 test/test_alac.rb
  14. +15 −0 test/test_raop.rb
@@ -0,0 +1,6 @@
+= Net::RAOP::Client CHANGELOG
+
+== 0.1.0
+
+* Birthday!
+

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -0,0 +1,14 @@
+CHANGELOG.txt
+LICENSE.txt
+Manifest.txt
+README.txt
+Rakefile
+lib/raop.rb
+lib/raop/bit_buffer.rb
+lib/raop/client.rb
+lib/raop/rtsp.rb
+test/data/frame.txt
+test/data/o.raw
+test/data/out_frame.txt
+test/test_alac.rb
+test/test_raop.rb
@@ -0,0 +1,32 @@
+= Net::RAOP::Client
+
+ http://raop.rubyforge.org/
+
+== DESCRIPTION
+
+Net::RAOP::Client is an Airport Express client. It allows you to stream
+music to an Airport Express.
+
+== EXAMPLES
+
+ raop = Net::RAOP::Client.new('192.168.1.173')
+ raop.connect
+ raop.play $stdin
+ raop.disconnect
+
+== TODO
+
+* Add support for decoding OGG, M4P, MP3
+
+== AUTHORS
+
+Copyright (c) 2007 by Aaron Patterson (aaronp@rubyforge.org)
+
+== ACKNOWLEDGMENTS
+
+Most of this code was based on JustePort[http://nanocrew.net/software/justeport/], so Thank You for JustePort!
+
+== LICENSE
+
+This library is distributed under the GPL. Please see the LICENSE[link://files/LICENSE_txt.html] file.
+
@@ -0,0 +1,16 @@
+require 'hoe'
+
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), "lib")
+require 'raop'
+
+Hoe.new('raop-client', Net::RAOP::Client::VERSION) do |p|
+ p.rubyforge_name = 'raop'
+ p.author = 'Aaron Patterson'
+ p.email = 'aaronp@rubyforge.org'
+ p.summary = "Airport Express streaming music client"
+ p.description = p.paragraphs_of('README.txt', 3).join("\n\n")
+ p.url = p.paragraphs_of('README.txt', 1).first.strip
+ p.changes = p.paragraphs_of('CHANGELOG.txt', 0..2).join("\n\n")
+end
+
+
@@ -0,0 +1,8 @@
+module Net
+ class RAOP
+ end
+end
+
+require 'raop/bit_buffer'
+require 'raop/client'
+require 'raop/rtsp'
@@ -0,0 +1,44 @@
+class Net::RAOP::BitBuffer < Array
+ @@masks = [
+ 0x01, 0x03, 0x07, 0x0F,
+ 0x1F, 0x3F, 0x7F, 0xff
+ ]
+
+ def initialize(*args, &block)
+ super(*args, &block)
+ @bit_offset = 0
+ @byte_offset = 0
+ end
+
+ def write_bits(data, numbits)
+ if @bit_offset != 0 && @bit_offset + numbits > 8
+ num_write_bits = 8 - @bit_offset
+ bits_to_write = (data >> (numbits - num_write_bits)) <<
+ (8 - @bit_offset - num_write_bits)
+ self[@byte_offset] |= bits_to_write
+ numbits -= num_write_bits
+ @bit_offset = 0
+ @byte_offset += 1
+ end
+
+ while numbits >= 8
+ bits_to_write = (data >> (numbits - 8)) & 0xFF
+ self[@byte_offset] |= bits_to_write
+ numbits -= 8
+ @bit_offset = 0
+ @byte_offset += 1
+ end
+
+ if numbits > 0
+ bits_to_write = (data & @@masks[numbits]) <<
+ (8 - @bit_offset - numbits)
+ self[ @byte_offset ] |= bits_to_write
+ @bit_offset += numbits
+ if @bit_offset == 8
+ @byte_offset += 1
+ @bit_offst = 0
+ end
+ end
+ end
+end
+
@@ -0,0 +1,124 @@
+require 'openssl'
+require 'socket'
+
+class Net::RAOP::Client
+ ##
+ # The version of Net::RAOP::Client you're using
+ VERSION = '0.1.0'
+
+ def initialize(host)
+ @host = host
+ @aes_crypt = aes_cipher
+ @rtsp_client = nil
+ @session_id = nil
+ @data_socket = nil
+ end
+
+ def connect
+ random_data = Array.new(28) { |x| rand(0xFF) }.pack('C*')
+
+ sid = sprintf('%0#10d', random_data.slice!(0, 4).unpack('L').first)
+ sci = sprintf('%0#18X', random_data.slice!(0, 8).unpack('Q').first)\
+ .slice(2..-1)
+ sac = [random_data].pack('m')
+
+ key = [rsa_encrypt(@aes_key)].pack('m')
+ iv = [@aes_iv].pack('m')
+
+ @rtsp_client = Net::RTSP.new(@host, sid, sci)
+
+ announce = Net::RTSP::Announce.new(sac, key, iv)
+ response = @rtsp_client.request(announce)
+
+ # FIXME Check for audio cable hookup
+
+ response = @rtsp_client.request(Net::RTSP::Setup.new)
+ transport_info = {}
+ response['transport'].split(';').each do |token|
+ k, v = token.split('=', 2)
+ transport_info[k] = v
+ end
+ @data_socket = TCPSocket.open(@host, transport_info['server_port'])
+ @session_id = response['session']
+
+ response = @rtsp_client.request(Net::RTSP::Record.new(@session_id))
+ params = Net::RTSP::SetParameter.new(@session_id,
+ { :volume => -30 }
+ )
+ response = @rtsp_client.request(params)
+ end
+
+ def play(file)
+ while data = file.read(4096 * 2 * 2)
+ send_sample self.class.encode_alac(data)
+ end
+ end
+
+ def disconnect
+ @rtsp_client.request(Net::RTSP::Teardown.new)
+ end
+
+ private
+ def send_sample(sample, pos = 0, count = sample.length)
+ # FIXME do we really need +pos+ or +count+?
+
+ crypt_length = sample.length / 16 * 16
+
+ @aes_crypt.reset
+ data = [
+ 0x24, 0x00, 0x00, 0x00,
+ 0xF0, 0xFF, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ] +
+ # Encryption section
+ @aes_crypt.update(sample.slice(0, crypt_length)).unpack('C*') +
+ sample.slice(crypt_length, sample.length).unpack('C*')
+
+ ab = [count + 12].pack('n').unpack('C*')
+ ab.each_with_index { |x, i| data[i + 2] = x }
+ @data_socket.write(data.pack('C*'))
+ end
+
+ def rsa_encrypt(plain_text)
+ n =
+ "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" +
+ "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" +
+ "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" +
+ "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" +
+ "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" +
+ "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="
+ e = "AQAB"
+
+ pkey = OpenSSL::PKey::RSA.new()
+ pkey.e = OpenSSL::BN.new(e.unpack('m').first, 2)
+ pkey.n = OpenSSL::BN.new(n.unpack('m').first, 2)
+ return pkey.public_encrypt(plain_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
+ end
+
+ def aes_cipher
+ aes_crypt = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
+ aes_crypt.encrypt
+ aes_crypt.key = @aes_key = aes_crypt.random_key
+ aes_crypt.iv = @aes_iv = aes_crypt.random_iv
+ aes_crypt
+ end
+
+ class << self
+ def encode_alac(bits)
+ bits = bits.unpack('C*')
+ new_bits = Net::RAOP::BitBuffer.new(bits.length + 3) { |x| 0 }
+ new_bits.write_bits(1, 3)
+ new_bits.write_bits(0, 4)
+ new_bits.write_bits(0, 12)
+ new_bits.write_bits(0, 1)
+ new_bits.write_bits(0, 2)
+ new_bits.write_bits(1, 1)
+ 0.step(bits.length - 1, 2) do |i|
+ new_bits.write_bits(bits[i + 1], 8)
+ new_bits.write_bits(bits[i], 8)
+ end
+ new_bits.pack('C*')
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit e117e9a

Please sign in to comment.