├── ffmpeg_command.txt ├── uncompressor.rb ├── Siri.txt ├── siriServer.old.rb ├── siriDissector.rb ├── README.md ├── tcpProxy.rb ├── sampleenc.c ├── fileCutter.rb ├── sampledec.c ├── eventMachineGuzzoni.rb ├── speexEnc.m ├── siriServer.rb ├── speexDec.m ├── Siri.rb ├── siriClient.rb ├── Siri.old.inline.rb ├── zpipe.c ├── siriClient.inline.rb ├── Siri.old.rb └── speexenc.c /ffmpeg_command.txt: -------------------------------------------------------------------------------- 1 | ffmpeg -i tentative.aiff -acodec pcm_s16le -f rawvideo out.raw -------------------------------------------------------------------------------- /uncompressor.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'zlib' 3 | Zlib::Inflate.inflate(File.open('in.dump.truncated').read) 4 | 5 | # 63 6 | # 65 7 | 8 | # 0x63 = 0b 0110 0011 9 | # 0x65 = 0b 0110 0101 10 | # 11 | # 0x78 = 0b 0111 1000 12 | # CM = 1000 = 8, OK 13 | # CINFO = 7 : 32k 14 | # 0xDA = 0b 1101 1010 15 | # FCHECK = 1010 16 | # fdict = 1 17 | # flevel = 11 = 3 18 | -------------------------------------------------------------------------------- /Siri.txt: -------------------------------------------------------------------------------- 1 | -> Uses HTTPS 2 | -> host is guzzoni.apple.com 3 | -> Request is an ACE http request (custom HTTP method) 4 | -> Requests and responses are *streamed* (no luck trying to re-use any existing HTTP tools. HTTP content-lenght is ~20 Gb) 5 | -> Body of request is as follow : 6 | 0xAACCEE02 : Magic number (notice the 'ACE' again) 7 | *** = zlib compressed data. (header is 0x78DA). Once uncompressed, you get : 8 | 0x030000ABAB : "ping" command. You can ignore. ABAB is the ping id. It's autoincremented, starts at 1 9 | 0x020000ABCD : Packet marker. ABCD = packet length 10 | *** packet content. This is actually a binary property list 11 | -- loop for some more packets 12 | 13 | 14 | Audio data flows in as speex_wideband by default. You can decompress using the speex library. Managed to uncompress using the following parameters : 15 | - framesize = 320 16 | - packet size = 139 (For some reason the audio packets in the plist are 695 bytes long, and 695 = 139*5, so they probably cram 5 speex packets in each plist) 17 | - sample rate is 16Khz 18 | -------------------------------------------------------------------------------- /siriServer.old.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'webrick' 3 | require 'webrick/https' 4 | require 'openssl' 5 | require 'pp' 6 | 7 | pkey = cert = cert_name = nil 8 | 9 | begin 10 | pkey = OpenSSL::PKey::RSA.new(File.open("server.key").read) 11 | cert = OpenSSL::X509::Certificate.new(File.open("server.crt").read) 12 | end 13 | 14 | class Simple < WEBrick::HTTPServlet::AbstractServlet 15 | 16 | def do_ACE(request, response) 17 | puts "ACE request !" 18 | puts request.body 19 | pp request 20 | #status, content_type, body = do_stuff_with(request) 21 | 22 | #response.status = status 23 | #response['Content-Type'] = content_type 24 | response.body = "Blah" 25 | end 26 | end 27 | 28 | server = WEBrick::HTTPServer.new( 29 | :Port => 443, 30 | :Logger => WEBrick::Log::new($stderr, WEBrick::Log::DEBUG), 31 | :DocumentRoot => "/ruby/htdocs", 32 | :SSLEnable => true, 33 | :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, 34 | :SSLCertificate => cert, 35 | :SSLPrivateKey => pkey 36 | #:SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ] 37 | ) 38 | 39 | server.mount "/ace", Simple 40 | 41 | 42 | server.start 43 | -------------------------------------------------------------------------------- /siriDissector.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'webrick' 3 | require 'webrick/https' 4 | require 'openssl' 5 | require 'cfpropertylist' 6 | require 'zlib' 7 | 8 | require 'pp' 9 | 10 | pkey = cert = cert_name = nil 11 | 12 | begin 13 | pkey = OpenSSL::PKey::RSA.new(File.open("server.key").read) 14 | cert = OpenSSL::X509::Certificate.new(File.open("server.crt").read) 15 | end 16 | 17 | class Simple < WEBrick::HTTPServlet::AbstractServlet 18 | 19 | def do_ACE(request, response) 20 | puts "ACE request !" 21 | puts request.body 22 | pp request 23 | puts "Coule" 24 | #status, content_type, body = do_stuff_with(request) 25 | 26 | #response.status = status 27 | #response['Content-Type'] = content_type 28 | response.body = "Blah" 29 | end 30 | end 31 | 32 | server = WEBrick::HTTPServer.new( 33 | :Port => 443, 34 | :Logger => WEBrick::Log::new($stderr, WEBrick::Log::DEBUG), 35 | :DocumentRoot => "/ruby/htdocs", 36 | :SSLEnable => true, 37 | :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, 38 | :SSLCertificate => cert, 39 | :SSLPrivateKey => pkey 40 | #:SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ] 41 | ) 42 | 43 | server.mount "/ace", Simple 44 | 45 | 46 | server.start 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Here are the tools we wrote to reverse-engineer Siri. 2 | 3 | The code is extremely dirty as it was written, erased, written again, and is a pure product of a trial-and-error process. 4 | Anyway, here's a simple how-to if you want to have fun with it : 5 | 6 | How to get the necessary bits 7 | ----------------------------- 8 | 9 | - Generate a certificate authority 10 | - Add it to your iPhone 11 | - Sign a certificate for "guzzoni.apple.com" using that authority. This should produce the ".crt" and ".key" files your server will need. 12 | - Setup a fake DNS server that resolves "guzzoni.apple.com" to your own machine, and configure your iPhone to use it. 13 | - Start the "siriServer.rb" server. You will need some ruby gem installed. I have tested it only on Mac OS X 10.7.2 with Ruby 1.9.2. 14 | - Make Siri dictation request, for example from the Notes.app application. On the server, this will dump all the "interesting" bits (X-Ace-Host identifier, sessionData and such). 15 | - Use them to replace instances of "COMMENTED_OUT" in the code 16 | 17 | How to do speech-to-text using a non-iPhone4S machine 18 | ----------------------------------------------------- 19 | 20 | - Record your voice into whatever format you like 21 | - Use ffmpeg to convert the sound to raw sound samples (see the text file for the exact command line). Name it "tentative.raw" 22 | - Install the speex library and its header. On Mac OS X, "brew install speex" once you've setup Homebrew 23 | - Compile the speexEnc.m file (gcc speexEnc.m -lspeex -framework Foundation -o speexEnc) 24 | - Run ./speexEnc. It will produce a input.sif file with speex packets the Ruby script will be able to read 25 | - Run the "Siri.old.inline.rb" script. Et voilà ! 26 | -------------------------------------------------------------------------------- /tcpProxy.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "socket" 4 | require "openssl" 5 | require "thread" 6 | 7 | listeningPort = 443 8 | 9 | server = TCPServer.new(listeningPort) 10 | sslContext = OpenSSL::SSL::SSLContext.new 11 | sslContext.key = OpenSSL::PKey::RSA.new(File.open("/Users/romain/server.key").read) 12 | sslContext.cert = OpenSSL::X509::Certificate.new(File.open("/Users/romain/server.crt").read) 13 | sslServer = OpenSSL::SSL::SSLServer.new(server, sslContext) 14 | 15 | puts "Listening on port #{listeningPort}" 16 | 17 | bufferSize = 16 18 | 19 | loop do 20 | connection = sslServer.accept 21 | Thread.new do 22 | socket = TCPSocket.new('guzzoni.apple.com', 443) 23 | ssl = OpenSSL::SSL::SSLSocket.new(socket) 24 | ssl.sync_close = true 25 | ssl.connect 26 | 27 | # Thread.new do 28 | # begin 29 | # while lineIn = ssl.gets 30 | # lineIn = lineIn.chomp 31 | # $stdout.puts lineIn 32 | # end 33 | # rescue 34 | # $stderr.puts "Error in input loop: " + $! 35 | # end 36 | # end 37 | 38 | # while (lineOut = $stdin.gets) 39 | # lineOut = lineOut.chomp 40 | # ssl.puts lineOut 41 | # end 42 | 43 | 44 | begin 45 | while (buffer = connection.read(bufferSize)) 46 | ssl.write buffer 47 | inBuffer = ssl.read 48 | 49 | lineIn = lineIn.chomp 50 | $stdout.puts "=> " + lineIn 51 | lineOut = "You said: " + lineIn 52 | $stdout.puts "<= " + lineOut 53 | connection.puts lineOut 54 | end 55 | rescue 56 | $stderr.puts $! 57 | end 58 | end 59 | end 60 | 61 | # ssl : proxy <-> Guzzoni 62 | # connection : iPhone <-> proxy 63 | # 64 | # ssl.read -> récupère de guzzoni 65 | # ssl.write -> envoie à guzzoni 66 | # connection.read -> récupère de l'iPhone 67 | # connection.write -> envoi à l'iPhone 68 | -------------------------------------------------------------------------------- /sampleenc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /*The frame size in hardcoded for this sample code but it doesn't have to be*/ 5 | #define FRAME_SIZE 160 6 | int main(int argc, char **argv) 7 | { 8 | char *inFile; 9 | FILE *fin; 10 | short in[FRAME_SIZE]; 11 | float input[FRAME_SIZE]; 12 | char cbits[200]; 13 | int nbBytes; 14 | /*Holds the state of the encoder*/ 15 | void *state; 16 | /*Holds bits so they can be read and written to by the Speex routines*/ 17 | SpeexBits bits; 18 | int i, tmp; 19 | 20 | /*Create a new encoder state in narrowband mode*/ 21 | state = speex_encoder_init(&speex_nb_mode); 22 | 23 | /*Set the quality to 8 (15 kbps)*/ 24 | tmp=8; 25 | speex_encoder_ctl(state, SPEEX_SET_QUALITY, &tmp); 26 | 27 | inFile = argv[1]; 28 | fin = fopen(inFile, "r"); 29 | 30 | /*Initialization of the structure that holds the bits*/ 31 | speex_bits_init(&bits); 32 | while (1) 33 | { 34 | /*Read a 16 bits/sample audio frame*/ 35 | fread(in, sizeof(short), FRAME_SIZE, fin); 36 | if (feof(fin)) 37 | break; 38 | /*Copy the 16 bits values to float so Speex can work on them*/ 39 | for (i=0;i First byte = 0x85 11 | # 12 | # Second byte. Let's say we don't want no dict 13 | # 14 | # bit 5 = 0 15 | # bit 6 and 7 = 11 (max compression ?) 16 | # 17 | # 18 | 19 | def generateZlibHeader(log_window_size, compression_level) 20 | cm = 8 21 | cinfo = log_window_size 22 | raise if log_window_size > 7 23 | 24 | cmf = (cinfo << 4) + cm 25 | 26 | fcheck = 0 27 | fdict = 0 28 | flevel = compression_level 29 | raise if compression_level > 4 30 | 31 | flg = 0 32 | (0..31).each do |potential_fcheck| 33 | fcheck = potential_fcheck 34 | flg = fcheck + (fdict << 5) + (flevel<<6) 35 | break if (cmf*256+flg).gcd(31) == 31 36 | end 37 | 38 | #puts "Using fcheck = #{fcheck}" 39 | #puts "CMF = #{cmf}, FLG = #{flg}" 40 | # puts "Header = #{cmf.to_s(16).upcase}#{flg.to_s(16).upcase}" 41 | return [cmf, flg].pack('cc') 42 | end 43 | 44 | 45 | file = File.open('in.dump').read.force_encoding('ASCII-8BIT') 46 | 47 | (103..115).each do |position| 48 | #puts "POSITION = #{position}" 49 | #puts `dd if=in.dump bs=1 skip=2` 50 | #puts `dd if=in.dump bs=1 skip=#{position} 2>/dev/null | file -` 51 | #puts `dd if=in.dump bs=1 skip=#{position} 2>/dev/null | gunzip` 52 | #puts `dd if=in.dump bs=1 skip=#{position} 2>/dev/null` 53 | puts `dd if=in.dump bs=1 skip=#{position} 2>/dev/null | ./zpipe -d` 54 | (0..7).each do |log_win_size| 55 | (0..3).each do |comp_level| 56 | begin 57 | puts "#{position}x#{log_win_size}x#{comp_level}" 58 | truncated = generateZlibHeader(log_win_size, comp_level) 59 | truncated << file[position..-1] 60 | #deflated = Zlib::Inflate.inflate(truncated) 61 | File.open("test.bin", 'w') {|f| f.write(truncated)} 62 | deflated = `cat test.bin | ./zpipe -d`.force_encoding('ASCII-8BIT') 63 | puts deflated if deflated.match(/plist/) 64 | rescue Exception => e 65 | puts e 66 | end 67 | end 68 | end 69 | end 70 | 71 | # Summary : 020000018F or something similar is a "weird" marker between plist items 72 | # 73 | -------------------------------------------------------------------------------- /sampledec.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /*The frame size in hardcoded for this sample code but it doesn't have to be*/ 5 | #define FRAME_SIZE 320 6 | int main(int argc, char **argv) 7 | { 8 | char *outFile; 9 | FILE *fout; 10 | /*Holds the audio that will be written to file (16 bits per sample)*/ 11 | short * out; 12 | /*Speex handle samples as float, so we need an array of floats*/ 13 | float * output; 14 | char cbits[4096]; 15 | int nbBytes; 16 | /*Holds the state of the decoder*/ 17 | void *state; 18 | /*Holds bits so they can be read and written to by the Speex routines*/ 19 | SpeexBits bits; 20 | int i, tmp; 21 | 22 | /*Create a new decoder state in wideband mode*/ 23 | state = speex_decoder_init(&speex_wb_mode); 24 | 25 | outFile = argv[1]; 26 | fout = fopen(outFile, "w"); 27 | 28 | /*Initialization of the structure that holds the bits*/ 29 | speex_bits_init(&bits); 30 | 31 | 32 | int frame_size; 33 | speex_decoder_ctl(state, SPEEX_GET_FRAME_SIZE, &frame_size); 34 | 35 | printf("Frame size = %d\n", frame_size); 36 | 37 | out = malloc(sizeof(short)*frame_size); 38 | output = malloc(sizeof(float)*frame_size); 39 | 40 | while (1) 41 | { 42 | /*Read the size encoded by sampleenc, this part will likely be 43 | different in your application*/ 44 | //fread(&nbBytes, sizeof(int), 1, stdin); 45 | nbBytes = 695; 46 | 47 | fprintf (stderr, "nbBytes: %d\n", nbBytes); 48 | if (feof(stdin)) 49 | break; 50 | 51 | /*Read the "packet" encoded by sampleenc*/ 52 | fread(cbits, 1, nbBytes, stdin); 53 | /*Copy the data into the bit-stream struct*/ 54 | speex_bits_read_from(&bits, cbits, nbBytes); 55 | 56 | /*Decode the data*/ 57 | speex_decode(state, &bits, output); 58 | 59 | /*Copy from float to short (16 bits) for output*/ 60 | for (i=0;i "./server.passless.crt", 21 | :private_key_file => "./server.passless.key", 22 | :verify_peer => false) 23 | end 24 | 25 | def receive_data(data) 26 | puts "Pipe length : #{@data_to_send.length}" 27 | Output.write data 28 | puts data 29 | puts "Receieved data from iPhone : #{data.length}" 30 | if guzzoni_connection.error? 31 | puts "Guzzoni connection not established" 32 | return 33 | end 34 | begin 35 | puts "Sending data to guzzoni" 36 | @data_to_send << data 37 | if guzzoni_connection.ssled 38 | guzzoni_connection.send_data(@data_to_send) 39 | @data_to_send = "" 40 | else 41 | puts "Couldn NOT forward, not SSLED" 42 | end 43 | rescue Exception => e 44 | puts "Error sending notification : #{e.inspect}" 45 | end 46 | end 47 | end 48 | 49 | class Guzzoni < EventMachine::Connection 50 | attr_accessor :proxy_server 51 | attr_accessor :ssled 52 | 53 | def connection_completed 54 | self.ssled = false 55 | puts "Guzzoni TCP connection established. Setting up SSL layer" 56 | start_tls(:verify_peer => false) 57 | end 58 | 59 | def receive_data data 60 | puts "Guzzoni received data #{data.length}" 61 | puts data 62 | Input.write(data) 63 | proxy_server.send_data(data) 64 | end 65 | 66 | def ssl_handshake_completed 67 | self.ssled = true 68 | puts "SSL layer to Guzzoni established !" 69 | end 70 | 71 | def unbind 72 | puts "ERROR !!!!! Guzzoni connection closed !" 73 | #EM.add_timer(5) do 74 | #self.reconnect('gateway.sandbox.push.apple.com', 2195) 75 | #end 76 | end 77 | end 78 | 79 | EventMachine.run do 80 | EventMachine::start_server '0.0.0.0', 443, ProxyServer 81 | end 82 | -------------------------------------------------------------------------------- /speexEnc.m: -------------------------------------------------------------------------------- 1 | #include 2 | #import 3 | 4 | #define MAX_FRAME_SIZE 1024 5 | 6 | @interface NSData (HexadecimalRepresentation) 7 | - (NSString *)hexadecimalRepresentation; 8 | @end 9 | 10 | @implementation NSData (HexadecimalRepresentation) 11 | - (NSString *)hexadecimalRepresentation { 12 | static const char * hexChars = "0123456789ABCDEF"; 13 | 14 | const unsigned char * src = (const unsigned char *)[self bytes]; 15 | int srcLength = [self length]; 16 | 17 | char * hexString = malloc(2*srcLength+1); 18 | if (hexString == NULL) { 19 | return nil; 20 | } 21 | int i = 0; 22 | for (i=0; i> 4]; 25 | hexString[2*i+1] = hexChars[currentByte & 0xF]; 26 | } 27 | hexString[2*srcLength] = 0; // NULL-terminated string 28 | 29 | NSString * hexRep = [NSString stringWithUTF8String:hexString]; 30 | free(hexString); 31 | 32 | return hexRep; 33 | } 34 | @end 35 | 36 | int main(int argc, char * argv[]) { 37 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 38 | 39 | SpeexBits bits; 40 | void *enc_state; 41 | 42 | speex_bits_init(&bits); 43 | enc_state = speex_encoder_init(&speex_wb_mode); 44 | 45 | int quality = 8; 46 | speex_encoder_ctl(enc_state, SPEEX_SET_QUALITY, &quality); 47 | 48 | int frame_size; 49 | speex_encoder_ctl(enc_state, SPEEX_GET_FRAME_SIZE, &frame_size); 50 | //frame_size = 3200; 51 | 52 | //NSLog(@"Frame size is %d", frame_size); 53 | 54 | 55 | NSData * rawData = [NSData dataWithContentsOfFile:@"tentative.raw"]; 56 | 57 | char * outputFrameData = malloc(MAX_FRAME_SIZE); 58 | 59 | 60 | int i = 0; 61 | int total_size = 0; 62 | for (i = 0; i < [rawData length]/(sizeof(short)*frame_size); i++) { 63 | speex_bits_reset(&bits); 64 | short * pointer = (short *)[rawData bytes]; 65 | speex_encode_int(enc_state, pointer+i*frame_size, &bits); 66 | int nbBytes = speex_bits_write(&bits, outputFrameData, MAX_FRAME_SIZE); 67 | total_size += nbBytes; 68 | //printf("%d\n", nbBytes); 69 | NSData * frameData = [NSData dataWithBytes:outputFrameData 70 | length:nbBytes]; 71 | printf("%s\n", [[frameData hexadecimalRepresentation] UTF8String]); 72 | } 73 | //NSLog(@"Total size = %d", total_size); 74 | speex_bits_destroy(&bits); 75 | speex_encoder_destroy(enc_state); 76 | [pool drain]; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /siriServer.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'eventmachine' 4 | require 'zlib' 5 | require 'cfpropertylist' 6 | require 'pp' 7 | 8 | class String 9 | def remove_leading_hex(hex_string) 10 | length = hex_string.length/2 11 | return self[length..-1] if self[0...length].unpack('H*').first == hex_string 12 | self 13 | end 14 | end 15 | 16 | module SiriServer 17 | include EventMachine::Protocols::LineText2 18 | def ssl_handshake_completed 19 | puts "SSL proxy layer established !" 20 | @zstream = Zlib::Inflate.new 21 | @stream = "" 22 | end 23 | 24 | def post_init 25 | start_tls(:cert_chain_file => "./server.passless.crt", 26 | :private_key_file => "./server.passless.key", 27 | :verify_peer => false) 28 | end 29 | 30 | def receive_binary_data(data) 31 | #puts data.bytes.to_a.map{|i| i.to_s(16).rjust(2, '0')}.join(" ") 32 | data = data.remove_leading_hex('0d0a') # Remove heading newline 33 | data = data.remove_leading_hex('aaccee02') # Remove ACE header 34 | @stream << @zstream.inflate(data) 35 | parse 36 | end 37 | 38 | def unbind 39 | #@zstream.finish 40 | @zstream.close 41 | end 42 | 43 | def receive_line(line) 44 | puts line 45 | set_binary_mode if line.match(/X-Ace-Host/) 46 | end 47 | 48 | def parse 49 | 50 | if @stream[0...5].unpack('H*').first.match(/030000..../) # Ignore 030000xxxx commands 51 | puts "#####################################################" 52 | puts "* PING ? : #{@stream[0...5].unpack('H*').first.match(/030000(....)/)[1].to_i(16)}" 53 | @stream = @stream[5..-1] 54 | end 55 | 56 | chunk_size = @stream[0...5].unpack('H*').first.match(/0200(......)/)[1].to_i(16) rescue 1000000 57 | if (chunk_size < @stream.length+5) 58 | plist_data = @stream[5...5+chunk_size] 59 | plist = CFPropertyList::List.new(:data => plist_data) 60 | puts "#####################################################" 61 | plist_object = CFPropertyList.native_types(plist.value) 62 | pp plist_object 63 | (plist_object["properties"]["packets"] || []).each do |packet| 64 | puts packet.length 65 | File.open("data.spx", "a"){|f| f.write(packet)} 66 | end 67 | #self.send_data "Received an audio chunk !" 68 | @stream = @stream[chunk_size+5..-1] 69 | end 70 | end 71 | end 72 | 73 | EventMachine.run do 74 | EventMachine::start_server '0.0.0.0', 443, SiriServer 75 | end 76 | -------------------------------------------------------------------------------- /speexDec.m: -------------------------------------------------------------------------------- 1 | #include 2 | #import 3 | 4 | @interface NSData (Hexadecimal) 5 | - (NSData *)initWithHexadecimalString:(NSString *)string; 6 | + (NSData *)dataWithHexadecimalString:(NSString *)string; 7 | @end 8 | 9 | unsigned char _hexCharToInteger(unsigned char hexChar) { 10 | if (hexChar >= '0' && hexChar <= '9') { 11 | return (hexChar - '0') & 0xF; 12 | } else { 13 | return ((hexChar - 'A')+10) & 0xF; 14 | } 15 | } 16 | 17 | @implementation NSData (Hexadecimal) 18 | - (id)initWithHexadecimalString:(NSString *)string { 19 | const char * hexstring = [string UTF8String]; 20 | int dataLength = [string length] / 2; 21 | unsigned char * data = malloc(dataLength); 22 | if (data == nil) { 23 | return nil; 24 | } 25 | int i = 0; 26 | for (i = 0; i < dataLength; i++) { 27 | unsigned char firstByte = hexstring[2*i]; 28 | unsigned char secondByte = hexstring[2*i+1]; 29 | unsigned char byte = (_hexCharToInteger(firstByte) << 4) + _hexCharToInteger(secondByte); 30 | data[i] = byte; 31 | } 32 | self = [self initWithBytes:data length:dataLength]; 33 | free(data); 34 | return self; 35 | } 36 | 37 | + (NSData *)dataWithHexadecimalString:(NSString *)string { 38 | return [[[self alloc] initWithHexadecimalString:string] autorelease]; 39 | } 40 | @end 41 | 42 | int main(int argc, char * argv[]) { 43 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 44 | SpeexBits bits; 45 | void *dec_state; 46 | speex_bits_init(&bits); 47 | dec_state = speex_decoder_init(&speex_wb_mode); 48 | 49 | int frame_size = 0; 50 | speex_decoder_ctl(dec_state, SPEEX_GET_FRAME_SIZE, &frame_size); 51 | 52 | 53 | NSString * fileContents = [NSString stringWithContentsOfFile:@"input.sif" encoding:NSUTF8StringEncoding error:NULL]; 54 | 55 | NSMutableData * decodedRawAudio = [[NSMutableData alloc] init]; 56 | spx_int16_t * output = malloc(sizeof(spx_int16_t)*frame_size); 57 | 58 | for (NSString * frame in [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]) { 59 | NSLog(@"frame = %@", frame); 60 | NSData * frameData = [NSData dataWithHexadecimalString:frame]; 61 | 62 | speex_bits_read_from(&bits, ((char *)[frameData bytes]), [frameData length]); 63 | 64 | while (speex_decode_int(dec_state, &bits, output) == 0) { 65 | NSData * decodedFrame = [NSData dataWithBytes:(const void *)output 66 | length:frame_size*sizeof(spx_int16_t)]; 67 | [decodedRawAudio appendData:decodedFrame]; 68 | } 69 | } 70 | 71 | NSLog(@"Done ! decoded size = %d", decodedRawAudio.length); 72 | [decodedRawAudio writeToFile:@"output.raw" atomically:NO]; 73 | 74 | free(output); 75 | 76 | speex_bits_destroy(&bits); 77 | speex_decoder_destroy(dec_state); 78 | [pool drain]; 79 | } 80 | -------------------------------------------------------------------------------- /Siri.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'stringio' 3 | require 'rubygems' 4 | require 'eventmachine' 5 | require 'zlib' 6 | require 'cfpropertylist' 7 | require 'uuidtools' 8 | require 'pp' 9 | 10 | class String 11 | def to_hex 12 | self.unpack('H*').first 13 | end 14 | 15 | def from_hex 16 | [self].pack('H*') 17 | end 18 | end 19 | 20 | module Siri 21 | class Client 22 | end 23 | 24 | class Server 25 | end 26 | 27 | class AceObject 28 | def self.has_header(header) 29 | @@headers ||= {} 30 | @@header = header.to_s[1..-1].to_i(16) 31 | @@headers[@@header] = self 32 | puts @@headers 33 | end 34 | 35 | def self.from_binary(stream) 36 | header = stream.readbyte 37 | puts "Instantiating item with header #{header}" 38 | obj_class = @@headers[header] 39 | obj_class.from_binary(stream) 40 | end 41 | 42 | def to_binary 43 | @@header.to_s(16).rjust(2, '0').from_hex + self.binary_payload 44 | end 45 | end 46 | 47 | class Notice < AceObject 48 | has_header :h03 49 | 50 | def initialize(index) 51 | @index = index 52 | end 53 | 54 | def self.from_binary(stream) 55 | self.new(stream.read(4).to_hex.to_i(16)) 56 | end 57 | 58 | def binary_payload 59 | @index.to_s(16).rjust(8, '0').from_hex 60 | end 61 | end 62 | 63 | class Ping < Notice 64 | has_header :h03 65 | end 66 | 67 | class Pong < AceObject 68 | has_header :h04 69 | end 70 | 71 | class Payload < AceObject 72 | has_header :h02 73 | attr_accessor :object 74 | 75 | def initialize(object) 76 | self.object = object 77 | end 78 | 79 | def self.from_binary(stream) 80 | plist_data_length = stream.read(4).to_hex.to_i(16) 81 | puts "Chunk length = #{plist_data_length}" 82 | plist = CFPropertyList::List.new(:data => stream.read(plist_data_length)) 83 | self.new(CFPropertyList.native_types(plist.value)) 84 | end 85 | 86 | def binary_payload 87 | plist = CFPropertyList::List.new 88 | plist.value = CFPropertyList.guess(self.object) 89 | plist_data = plist.to_str(CFPropertyList::List::FORMAT_BINARY) 90 | 91 | plist_data.length.to_s(16).rjust(8, '0') + plist_data 92 | end 93 | end 94 | 95 | class SerializerStream 96 | def initialize 97 | @zstream = Zlib::Deflate.new(Zlib::BEST_COMPRESSION) 98 | end 99 | 100 | def <<(object) 101 | @zstream.deflate(object.to_binary, Zlib::NO_FLUSH) 102 | 103 | object.to_binary 104 | end 105 | 106 | def data 107 | # Returns the available data 108 | end 109 | end 110 | 111 | class DeserializerStream 112 | def initialize 113 | @inflate = Zlib::Inflate.new 114 | @stream = "" 115 | end 116 | 117 | def <<(data) 118 | @stream << @zstream.inflate(data) 119 | end 120 | 121 | def objects 122 | # Returns an array of parsed Siri objects 123 | end 124 | end 125 | end 126 | 127 | stream = StringIO.new(["020000FFFF"].pack('H*')) 128 | puts Siri::AceObject.from_binary(stream) 129 | 130 | -------------------------------------------------------------------------------- /siriClient.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'eventmachine' 4 | require 'zlib' 5 | require 'cfpropertylist' 6 | require 'uuidtools' 7 | require 'pp' 8 | 9 | def plist_blob(string) 10 | string = [string].pack('H*') 11 | string.blob = true 12 | string 13 | end 14 | 15 | class SiriClient < EventMachine::Connection 16 | def connection_completed 17 | puts "Guzzoni TCP connection established. Setting up SSL layer" 18 | start_tls(:verify_peer => false) 19 | end 20 | 21 | def ssl_handshake_completed 22 | puts "SSL layer to Guzzoni established !" 23 | @zstream = Zlib::Deflate.new(Zlib::BEST_COMPRESSION) 24 | @ping = 1 25 | 26 | send_http_headers 27 | send_content_header 28 | 29 | send_ping 30 | 31 | @ping_timer = EventMachine::PeriodicTimer.new(1) do 32 | send_ping 33 | end 34 | 35 | send_object( 36 | { :class => 'LoadAssistant', 37 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 38 | :group => 'com.apple.ace.system', 39 | :properties => 40 | { :speechId => 'COMMENTED_OUT', 41 | :assistantId => 'COMMENTED_OUT', 42 | :sessionValidationData => plist_blob("COMMENTED_OUT") 43 | } 44 | } 45 | ) 46 | 47 | speech_session_ace_id = UUIDTools::UUID.random_create.to_s.upcase 48 | 49 | send_object( 50 | { :class => "StartSpeechDictation", 51 | :aceId => speech_session_ace_id, 52 | :group => "com.apple.ace.speech", 53 | :properties => 54 | { :keyboardType => "Default", 55 | :applicationName => "com.apple.mobilenotes", 56 | :applicationVersion => "1.0", 57 | :fieldLabel => "", 58 | :prefixText => "", 59 | :language => "fr-FR", 60 | :censorSpeech => false, 61 | :selectedText => "", 62 | :codec => "Speex_WB_Quality8", 63 | :audioSource => "BuiltInMic", 64 | :region => "fr_FR", 65 | :postfixText => "", 66 | :keyboardReturnKey => "Default", 67 | :interactionId => UUIDTools::UUID.random_create.to_s.upcase, 68 | :fieldId => "UIWebDocumentView0, NoteTextView1, NoteContentLayer0, NotesBackgroundView0, UIViewControllerWrapperView0, UINavigationTransitionView0, UILayoutContainerView0, UIWindow" 69 | } 70 | } 71 | ) 72 | 73 | speech_packets = [ 74 | ] 75 | 76 | index = 0 77 | File.open("input.sif", 'r').each_line do |packet| 78 | index += 1 79 | send_object( 80 | { :class => "SpeechPacket", 81 | :refId => speech_session_ace_id, 82 | :group => "com.apple.ace.speech", 83 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 84 | :properties => 85 | { :packets => 86 | [ 87 | plist_blob(packet) 88 | ], 89 | :packetNumber => index 90 | } 91 | } 92 | ) 93 | end 94 | 95 | send_object( 96 | { :class => "FinishSpeech", 97 | :refId => speech_session_ace_id, 98 | :group => "com.apple.ace.speech", 99 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 100 | :properties => 101 | { :packetCount => speech_packets.length } 102 | } 103 | ) 104 | 105 | flush_and_close_connection 106 | end 107 | 108 | def send_http_headers 109 | send_data ["ACE /ace HTTP/1.0", "Host: guzzoni.apple.com", "User-Agent: Assistant(iPhone/iPhone4,1; iPhone OS/5.0/9A334) Ace/1.0", "Content-Length: 2000000000", "X-Ace-Host: COMMENTED_OUT"].join("\r\n") 110 | send_data "\r\n\r\n" 111 | end 112 | 113 | def send_content_header 114 | #send_data "\r\n" 115 | send_data ["aaccee02"].pack('H*') 116 | end 117 | 118 | def send_object(object) 119 | plist = CFPropertyList::List.new 120 | plist.value = CFPropertyList.guess object 121 | plist_data = plist.to_str(CFPropertyList::List::FORMAT_BINARY) 122 | header = [(0x0200000000 + plist_data.length).to_s(16).rjust(10, '0')].pack('H*') 123 | 124 | send_data @zstream.deflate(header, Zlib::NO_FLUSH) 125 | send_data @zstream.deflate(plist_data, Zlib::SYNC_FLUSH) 126 | 127 | puts "Sent object" 128 | end 129 | 130 | def flush_and_close_connection 131 | #send_data @zstream.flush 132 | #@zstream.close 133 | # @ping_timer.cancel 134 | #self.close_connection_after_writing 135 | end 136 | 137 | def send_ping 138 | chunk = [(0x0300000000 + @ping).to_s(16).rjust(10, '0')].pack('H*') 139 | send_data @zstream.deflate(chunk, Zlib::SYNC_FLUSH) 140 | @ping +=1 141 | puts "Sent ping" 142 | end 143 | 144 | def receive_data data 145 | puts "Guzzoni received data #{data.length}" 146 | puts data 147 | end 148 | 149 | def unbind 150 | puts "Guzzoni connection closed !" 151 | end 152 | end 153 | 154 | EventMachine.run do 155 | EventMachine.connect('guzzoni.apple.com', 443, SiriClient) 156 | #EventMachine.connect('localhost', 443, SiriClient) 157 | end 158 | -------------------------------------------------------------------------------- /Siri.old.inline.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'eventmachine' 4 | require 'zlib' 5 | require 'cfpropertylist' 6 | require 'uuidtools' 7 | require 'pp' 8 | 9 | class String 10 | def to_blob 11 | string = [self].pack('H*') 12 | string.blob = true 13 | string 14 | end 15 | 16 | def remove_leading_hex(hex_string) 17 | length = hex_string.length/2 18 | return self[length..-1] if self[0...length].unpack('H*').first == hex_string 19 | self 20 | end 21 | end 22 | 23 | module Siri 24 | class OutputStream 25 | def initialize 26 | @zstream = Zlib::Deflate.new(Zlib::BEST_COMPRESSION) 27 | @ping = 0 28 | end 29 | 30 | def content_header 31 | ["aaccee02"].pack('H*') 32 | end 33 | 34 | def object(object) 35 | plist = CFPropertyList::List.new 36 | plist.value = CFPropertyList.guess object 37 | plist_data = plist.to_str(CFPropertyList::List::FORMAT_BINARY) 38 | header = [(0x0200000000 + plist_data.length).to_s(16).rjust(10, '0')].pack('H*') 39 | 40 | @zstream.deflate(header, Zlib::NO_FLUSH) + @zstream.deflate(plist_data, Zlib::SYNC_FLUSH) 41 | end 42 | 43 | def ping 44 | @ping +=1 45 | chunk = [(0x0300000000 + @ping).to_s(16).rjust(10, '0')].pack('H*') 46 | @zstream.deflate(chunk, Zlib::SYNC_FLUSH) 47 | end 48 | end 49 | 50 | class InputStream 51 | def initialize 52 | @zstream = Zlib::Inflate.new 53 | @stream = "" 54 | end 55 | 56 | def <<(data) 57 | data = data.remove_leading_hex('0d0a') # Remove heading newline 58 | data = data.remove_leading_hex('aaccee02') # Remove ACE header 59 | @stream << @zstream.inflate(data) 60 | end 61 | 62 | def parse 63 | if @stream[0...5].unpack('H*').first.match(/040000..../) # Ignore 040000xxxx commands, those are PONGs 64 | puts "#####################################################" 65 | puts "* PONG : #{@stream[0...5].unpack('H*').first.match(/040000(....)/)[1].to_i(16)}" 66 | @stream = @stream[5..-1] 67 | end 68 | 69 | chunk_size = @stream[0...5].unpack('H*').first.match(/0200(......)/)[1].to_i(16) rescue 1000000 70 | if (chunk_size < @stream.length+5) 71 | plist_data = @stream[5...5+chunk_size] 72 | plist = CFPropertyList::List.new(:data => plist_data) 73 | puts "#####################################################" 74 | plist_object = CFPropertyList.native_types(plist.value) 75 | pp plist_object 76 | (plist_object["properties"]["packets"] || []).each do |packet| 77 | puts packet.length 78 | File.open("data.spx", "a"){|f| f.write(packet)} 79 | end 80 | @stream = @stream[chunk_size+5..-1] 81 | end 82 | end 83 | end 84 | end 85 | 86 | class SiriClient < EventMachine::Connection 87 | def connection_completed 88 | puts "Guzzoni TCP connection established. Setting up SSL layer" 89 | start_tls(:verify_peer => false) 90 | end 91 | 92 | def ssl_handshake_completed 93 | puts "SSL layer to Guzzoni established !" 94 | @siriOutputStream = Siri::OutputStream.new 95 | @siriInputStream = Siri::InputStream.new 96 | 97 | send_data ["ACE /ace HTTP/1.0", "Host: guzzoni.apple.com", "User-Agent: Assistant(iPhone/iPhone4,1; iPhone OS/5.0/9A334) Ace/1.0", "Content-Length: 2000000000", "X-Ace-Host: COMMENTED_OUT"].join("\r\n") + "\r\n\r\n" 98 | puts "Sent HTTP headers" 99 | send_data @siriOutputStream.content_header 100 | puts "Sent content header !" 101 | send_data @siriOutputStream.ping 102 | 103 | @ping_timer = EventMachine::PeriodicTimer.new(1) do 104 | send_data @siriOutputStream.ping 105 | end 106 | 107 | send_data @siriOutputStream.object( 108 | { :class => 'LoadAssistant', 109 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 110 | :group => 'com.apple.ace.system', 111 | :properties => 112 | { :speechId => 'COMMENTED_OUT', 113 | :assistantId => 'COMMENTED_OUT', 114 | :sessionValidationData => "COMMENTED_OUT".to_blob 115 | } 116 | } 117 | ) 118 | 119 | speech_session_ace_id = UUIDTools::UUID.random_create.to_s.upcase 120 | 121 | send_data @siriOutputStream.object( 122 | { :class => "StartSpeechDictation", 123 | :aceId => speech_session_ace_id, 124 | :group => "com.apple.ace.speech", 125 | :properties => 126 | { :keyboardType => "Default", 127 | :applicationName => "com.apple.mobilenotes", 128 | :applicationVersion => "1.0", 129 | :fieldLabel => "", 130 | :prefixText => "", 131 | :language => "fr-FR", 132 | :censorSpeech => false, 133 | :selectedText => "", 134 | :codec => "Speex_WB_Quality8", 135 | :audioSource => "BuiltInMic", 136 | :region => "fr_FR", 137 | :postfixText => "", 138 | :keyboardReturnKey => "Default", 139 | :interactionId => UUIDTools::UUID.random_create.to_s.upcase, 140 | :fieldId => "UIWebDocumentView0, NoteTextView1, NoteContentLayer0, NotesBackgroundView0, UIViewControllerWrapperView0, UINavigationTransitionView0, UILayoutContainerView0, UIWindow" 141 | } 142 | } 143 | ) 144 | 145 | 146 | index = 0 147 | File.open("input.sif", 'r').each_line do |packet| 148 | send_data @siriOutputStream.object( 149 | { :class => "SpeechPacket", 150 | :refId => speech_session_ace_id, 151 | :group => "com.apple.ace.speech", 152 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 153 | :properties => 154 | { :packets => 155 | [ 156 | packet.to_blob 157 | ], 158 | :packetNumber => index 159 | } 160 | } 161 | ) 162 | puts "Sent speech packet" 163 | index += 1 164 | end 165 | 166 | send_data @siriOutputStream.object( 167 | { :class => "FinishSpeech", 168 | :refId => speech_session_ace_id, 169 | :group => "com.apple.ace.speech", 170 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 171 | :properties => 172 | { :packetCount => index } 173 | } 174 | ) 175 | 176 | #flush_and_close_connection 177 | end 178 | 179 | def flush_and_close_connection 180 | #send_data @zstream.flush 181 | #@zstream.close 182 | # @ping_timer.cancel 183 | #self.close_connection_after_writing 184 | end 185 | 186 | def receive_data data 187 | #puts "Received data from Guzzoni : #{data}" 188 | unless data.match(/Server/) 189 | @siriInputStream << data 190 | @siriInputStream.parse 191 | end 192 | end 193 | 194 | def unbind 195 | puts "Guzzoni connection closed !" 196 | end 197 | end 198 | 199 | EventMachine.run do 200 | EventMachine.connect('guzzoni.apple.com', 443, SiriClient) 201 | #EventMachine.connect('localhost', 443, SiriClient) 202 | end 203 | -------------------------------------------------------------------------------- /zpipe.c: -------------------------------------------------------------------------------- 1 | /* zpipe.c: example of proper use of zlib's inflate() and deflate() 2 | Not copyrighted -- provided to the public domain 3 | Version 1.4 11 December 2005 Mark Adler */ 4 | 5 | /* Version history: 6 | 1.0 30 Oct 2004 First version 7 | 1.1 8 Nov 2004 Add void casting for unused return values 8 | Use switch statement for inflate() return values 9 | 1.2 9 Nov 2004 Add assertions to document zlib guarantees 10 | 1.3 6 Apr 2005 Remove incorrect assertion in inf() 11 | 1.4 11 Dec 2005 Add hack to avoid MSDOS end-of-line conversions 12 | Avoid some compiler warnings for input and output buffers 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include "zlib.h" 19 | 20 | #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) 21 | # include 22 | # include 23 | # define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) 24 | #else 25 | # define SET_BINARY_MODE(file) 26 | #endif 27 | 28 | #define CHUNK 16384 29 | 30 | /* Compress from file source to file dest until EOF on source. 31 | def() returns Z_OK on success, Z_MEM_ERROR if memory could not be 32 | allocated for processing, Z_STREAM_ERROR if an invalid compression 33 | level is supplied, Z_VERSION_ERROR if the version of zlib.h and the 34 | version of the library linked do not match, or Z_ERRNO if there is 35 | an error reading or writing the files. */ 36 | int def(FILE *source, FILE *dest, int level) 37 | { 38 | int ret, flush; 39 | unsigned have; 40 | z_stream strm; 41 | unsigned char in[CHUNK]; 42 | unsigned char out[CHUNK]; 43 | 44 | /* allocate deflate state */ 45 | strm.zalloc = Z_NULL; 46 | strm.zfree = Z_NULL; 47 | strm.opaque = Z_NULL; 48 | ret = deflateInit(&strm, level); 49 | if (ret != Z_OK) 50 | return ret; 51 | 52 | /* compress until end of file */ 53 | do { 54 | strm.avail_in = fread(in, 1, CHUNK, source); 55 | if (ferror(source)) { 56 | (void)deflateEnd(&strm); 57 | return Z_ERRNO; 58 | } 59 | flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; 60 | strm.next_in = in; 61 | 62 | /* run deflate() on input until output buffer not full, finish 63 | compression if all of source has been read in */ 64 | do { 65 | strm.avail_out = CHUNK; 66 | strm.next_out = out; 67 | ret = deflate(&strm, flush); /* no bad return value */ 68 | assert(ret != Z_STREAM_ERROR); /* state not clobbered */ 69 | have = CHUNK - strm.avail_out; 70 | if (fwrite(out, 1, have, dest) != have || ferror(dest)) { 71 | (void)deflateEnd(&strm); 72 | return Z_ERRNO; 73 | } 74 | } while (strm.avail_out == 0); 75 | assert(strm.avail_in == 0); /* all input will be used */ 76 | 77 | /* done when last data in file processed */ 78 | } while (flush != Z_FINISH); 79 | assert(ret == Z_STREAM_END); /* stream will be complete */ 80 | 81 | /* clean up and return */ 82 | (void)deflateEnd(&strm); 83 | return Z_OK; 84 | } 85 | 86 | /* Decompress from file source to file dest until stream ends or EOF. 87 | inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be 88 | allocated for processing, Z_DATA_ERROR if the deflate data is 89 | invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and 90 | the version of the library linked do not match, or Z_ERRNO if there 91 | is an error reading or writing the files. */ 92 | int inf(FILE *source, FILE *dest) 93 | { 94 | int ret; 95 | unsigned have; 96 | z_stream strm; 97 | unsigned char in[CHUNK]; 98 | unsigned char out[CHUNK]; 99 | 100 | /* allocate inflate state */ 101 | strm.zalloc = Z_NULL; 102 | strm.zfree = Z_NULL; 103 | strm.opaque = Z_NULL; 104 | strm.avail_in = 0; 105 | strm.next_in = Z_NULL; 106 | ret = inflateInit(&strm); 107 | if (ret != Z_OK) 108 | return ret; 109 | 110 | /* decompress until deflate stream ends or end of file */ 111 | do { 112 | strm.avail_in = fread(in, 1, CHUNK, source); 113 | if (ferror(source)) { 114 | (void)inflateEnd(&strm); 115 | return Z_ERRNO; 116 | } 117 | if (strm.avail_in == 0) 118 | break; 119 | strm.next_in = in; 120 | 121 | /* run inflate() on input until output buffer not full */ 122 | do { 123 | strm.avail_out = CHUNK; 124 | strm.next_out = out; 125 | ret = inflate(&strm, Z_NO_FLUSH); 126 | assert(ret != Z_STREAM_ERROR); /* state not clobbered */ 127 | switch (ret) { 128 | case Z_NEED_DICT: 129 | ret = Z_DATA_ERROR; /* and fall through */ 130 | case Z_DATA_ERROR: 131 | case Z_MEM_ERROR: 132 | (void)inflateEnd(&strm); 133 | return ret; 134 | } 135 | have = CHUNK - strm.avail_out; 136 | if (fwrite(out, 1, have, dest) != have || ferror(dest)) { 137 | (void)inflateEnd(&strm); 138 | return Z_ERRNO; 139 | } 140 | } while (strm.avail_out == 0); 141 | 142 | /* done when inflate() says it's done */ 143 | } while (ret != Z_STREAM_END); 144 | 145 | /* clean up and return */ 146 | (void)inflateEnd(&strm); 147 | return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; 148 | } 149 | 150 | /* report a zlib or i/o error */ 151 | void zerr(int ret) 152 | { 153 | fputs("zpipe: ", stderr); 154 | switch (ret) { 155 | case Z_ERRNO: 156 | if (ferror(stdin)) 157 | fputs("error reading stdin\n", stderr); 158 | if (ferror(stdout)) 159 | fputs("error writing stdout\n", stderr); 160 | break; 161 | case Z_STREAM_ERROR: 162 | fputs("invalid compression level\n", stderr); 163 | break; 164 | case Z_DATA_ERROR: 165 | fputs("invalid or incomplete deflate data\n", stderr); 166 | break; 167 | case Z_MEM_ERROR: 168 | fputs("out of memory\n", stderr); 169 | break; 170 | case Z_VERSION_ERROR: 171 | fputs("zlib version mismatch!\n", stderr); 172 | } 173 | } 174 | 175 | /* compress or decompress from stdin to stdout */ 176 | int main(int argc, char **argv) 177 | { 178 | int ret; 179 | 180 | /* avoid end-of-line conversions */ 181 | SET_BINARY_MODE(stdin); 182 | SET_BINARY_MODE(stdout); 183 | 184 | /* do compression if no arguments */ 185 | if (argc == 1) { 186 | ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION); 187 | if (ret != Z_OK) 188 | zerr(ret); 189 | return ret; 190 | } 191 | 192 | /* do decompression if -d specified */ 193 | else if (argc == 2 && strcmp(argv[1], "-d") == 0) { 194 | ret = inf(stdin, stdout); 195 | if (ret != Z_OK) 196 | zerr(ret); 197 | return ret; 198 | } 199 | 200 | /* otherwise, report usage */ 201 | else { 202 | fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr); 203 | return 1; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /siriClient.inline.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'eventmachine' 4 | require 'zlib' 5 | require 'cfpropertylist' 6 | require 'uuidtools' 7 | require 'pp' 8 | 9 | def plist_blob(string) 10 | string = [string].pack('H*') 11 | string.blob = true 12 | string 13 | end 14 | 15 | class SiriClient < EventMachine::Connection 16 | def connection_completed 17 | puts "Guzzoni TCP connection established. Setting up SSL layer" 18 | start_tls(:verify_peer => false) 19 | end 20 | 21 | def ssl_handshake_completed 22 | puts "SSL layer to Guzzoni established !" 23 | @zstream = Zlib::Deflate.new(Zlib::BEST_COMPRESSION) 24 | @ping = 1 25 | 26 | send_http_headers 27 | send_content_header 28 | 29 | send_ping 30 | 31 | @ping_timer = EventMachine::PeriodicTimer.new(1) do 32 | send_ping 33 | end 34 | 35 | send_object( 36 | { :class => 'LoadAssistant', 37 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 38 | :group => 'com.apple.ace.system', 39 | :properties => 40 | { :speechId => 'COMMENTED_OUT', 41 | :assistantId => 'COMMENTED_OUT', 42 | :sessionValidationData => plist_blob("COMMENTED_OUT") 43 | } 44 | } 45 | ) 46 | 47 | speech_session_ace_id = UUIDTools::UUID.random_create.to_s.upcase 48 | 49 | send_object( 50 | { :class => "StartSpeechDictation", 51 | :aceId => speech_session_ace_id, 52 | :group => "com.apple.ace.speech", 53 | :properties => 54 | { :keyboardType => "Default", 55 | :applicationName => "com.apple.mobilenotes", 56 | :applicationVersion => "1.0", 57 | :fieldLabel => "", 58 | :prefixText => "", 59 | :language => "fr-FR", 60 | :censorSpeech => false, 61 | :selectedText => "", 62 | :codec => "Speex_WB_Quality8", 63 | :audioSource => "BuiltInMic", 64 | :region => "fr_FR", 65 | :postfixText => "", 66 | :keyboardReturnKey => "Default", 67 | :interactionId => UUIDTools::UUID.random_create.to_s.upcase, 68 | :fieldId => "UIWebDocumentView0, NoteTextView1, NoteContentLayer0, NotesBackgroundView0, UIViewControllerWrapperView0, UINavigationTransitionView0, UILayoutContainerView0, UIWindow" 69 | } 70 | } 71 | ) 72 | 73 | speech_packets = [ 74 | "37748e774c000068e8e8e8e8e8e8e88401f474745e7ffc3c4013d1945623c83137b1b247eebbf8d6c8b8c8d885db3b64ababababab1ab74958d6d0cd4d8dc26b0cdd956f34931d6533bec50cdd74ea985b116c43ba5aea8bdeeead9a7a0835d9f66d2eb8e87d2c7a0c70cf8883618883710f4db93f0c5edd6018e0c3d571a79f0d68d9356783abababc22835f20b0e68a7f3fb7baea6468900c1ff9da1baaf466eb28d009880d8823d27b3f33c6a69cfdbc1718e9c2ab3ef1bbb62f30dababab16b6da7c7d6249434972003b4ebb10b4235ff09a2add756596e2b98c88201dc3433c19e5917306f646d097e484d4cdb0d77bcb431e2b61f108b49496baf5be6828e27c98e741b680b4b1ab21934abebc730d4fb7d64235f8fba2addeb7b5bfdf7ee14cc08500231f5c5319a43060d622828ef64805566f601e25764f4d18a983bf18243bc5f3abadb8d6c9347d5d98a8b3324a0234fd4d6d9ccfec337217ac74b1f0b4167d3791c36466f5a0f825bb4e7cbaedb955043da67923800fb3c44d7afdb2423db7d66575b0be7e5f8dac0bbc02d4b156978829b391e725a13e29c98e9f35f063636b4e672eb4d689665d6ac4eb7b3cddc210cb8ae450d5c7cb72fa089167ee07a6cfed571105d535c8400bb8716512818e784d6c83d6d402bc3c62394920ced10206d356aec13ed608510c641716d9806f1e2273cdc73911670ae904afc5cd940c05538399cdf7e79a5ede916103017dbf9f32c98b8b1aa0edc03bc04906df83956ab049abab427330510827ef53f295beb20940c616cdefe762de76b08b68977cc9fcd691b0548f39a042129ee39f3020ba2e97082be5804249bd0d4200abdc2428000dc2bdc9490d6428d6b49357e9c116eb17fdfc606c15ea021d4d53db0febcc67310b3286a87b08b4f31acae2999b5c3cf2a10d51c901fdc6be580abab2749140ababab426b00e5649abab08e8d42c93d", 75 | "357dac112dadc332f241595a317334d6bf31a3cc439c755e6591c7cf3ad19b5a0c2622a8effb2da82c629fd219cbc580c28abd271c089bdbde5db0ad80566b8b044c34de7ab35ec630e6f492bdbbbf82b98995588a48fe9732439a4b6dad45257d3b4223736d06c5cc91774243eb301957b4d1bf840d632495827438c9abbc7505afed6ab2006b812dac5535f3350e6f4a82ad05447c9b6d514f239d40835dee5c009d0a52316452e2ee10a39d624938610b9340a8c4da55dbf8e0c990a773120fe3b141f9c09cef5e3d930e75678d9693562ef4590497fc1665a3638ed34892671837e9333428d1a6693b1d7038593528046d169dfed777321a2a7e4ddabba700d532d495d075ed9ca09d0fe2df80dbc1b4340a39e3373842be8f4fdf492e66f4e62eea0728efe5597354953586a25407ff3ba439cb828c2caa97f9214786712d7f0b2bba703e930156560d694420df30846beb0e4d0f38d84bdcd376e49146e570f43336bd208192a2eceabb8e81246466ebabf53d1b4d0a4c4336dea9e931ed8ccd578ad24cdc96bb9f0fa493abc0e0f8d630f8550b17e12029c06d490d8e1031283e52d14bc1eac531169cae31784e33fc34646ca0cc745d736dcd59ddfecc00663af37e3587321d8ba8991bab2af038d6d90e0004249abab3c084abbdab0e0b775f3c2f8377fc0734edee2933b018639fd4485acaf513902632532768255d18428d1815cbecff44a43c6800e1e20d8b34d2b38f0548e8d42c90844d840e49040ebbd0d6305f80ad06cd3774a6bd52551d218505d8e141f5c69b9b869b3f340675fd389566e607e63a4a8e3d7c1a9f64ae00ae1f3f594bab41f00e0d3ded0e0ab0e94c9b80b88d8d789200d6d6d948e357aede2522bfb87063cb3b169077fb3bf19ed4ff12bc3bff5999c8dca8a338df00ef72d3fca6decb5f6ee8163fbf9f0f339275ebb04a7af3ab07079206dc7ac0a91b5ac7bd", 76 | "356ecfe37068df821f9b087226f7f8b58fa21d11aa26a91f8d1a1ef23cba8f5a260bee2d6be0d945067716e4bd8bf9f0140e7f8e8a0a463b282270f38a630dc20cdfed949e5357aa1e4916ae2cf384ad81faf2f867d4f81b00ec93cc446741c05b95d8dbe2dc4eb46ee17e039132d1c6d6b9e3b38f06c01d27a73020a000d402069010a59bd084ad6d9fa037761cbaf172beb19e5937695f44683aa3733fe4a94fd46ff5dcdf907a322eaa3d1961ade3fdb1b14d25c20bfd0bbac020a980ad9d0c8a765de033ab8e3294bd09cb28171a7377dc8b9516d7f077812b74f6ae4f46cf17b3be37d3ceb2fb75007c3f4bb8a414e212a9ee8e286e102d83901662bfad180cf1b26c407a62b0c94908e0e3dba670c5eb598e0e3572cf310e31fa491b60985ecf17a8cffde5d5da2dddb51b83b4d5bde10e972c4b5fcd7ce8e6e13b3ee7e29c50fbfbc0a008adf11e294dcfb7a622e10aab73380abab0acdd635dbac2ef2a999e1f068e9799daf33bae2d5135ba0b4fc2bf003dd487b90fe9a55243f1db6fd1eb4c8990118565b5d80bd42d6615d0ef80bda7040808d4000231ed923d46b931c2e1d2f12bfb736b4708e05f558cf6aefab1a6905a6d338855f6c2ac394b0d2c57560acdce97fee97f80eba27b8c8043121245d91a70bd309d00a16e4eb0561d574d310af33839ceeb0630aa58cbccd6a7a36d3ceab8605ae26f8c4058f7897c9958a01c250506c7c2beeb63329b882587a2b4581ba69d88b100597b8b07391e3d9da28700ea803083c831c648cb50d56f79ad3bca43f8f09b6cbf70d6210e1e3732a69838e9c76db4374e1232313460db72d5ef7d8d2f9b8d81e52f65c3d415739fe17350503b44a94217a00d9498c37767d645068c6d2ae545cf5b17a12b8cdda7cfc41d69b3c695c5ece79a2820a2d6d6c6e63fd2f93169b2b5f7e6b61f0c389cdd6ab0bccdededcd04deb04bdc90c2c93d3d3d", 77 | "377648bab0735f91e8f91dad2fe98a39afeb8217afcda5bf9d1d72cc172706588a6f3deefc787f0f2d09a6d78bfbde50c2ababc2780b83d94753808e56ab0d400edab0d149c377691654f741f8bd9ff685b66b7aab95fcf038b89389d8b170933db981c6a799e2f9e4de8f04cc31f5efa062f5b3e50002d0f91ac0930862939022d1272754d1b78b01e56b3383edfcf01ef66588b1af69564cceb0374fad28f410fda416bf1dfb4a229b621882f9751c673ee578babd084f0b3df327129d32e4478d280dece3593cceb4d34ba92a0f8d432869dabd1281ee32f3d35842c9eaa2cd993860e2883b6d32e8b63d81b5a520d82176d97fd6fac99a178c62f884be55314d4844a14200f39d9e7e49ede786bea2af18a05d3136842b93f1746ea95fdc51536d43e4829fb9402417fca8c1280ac2b6e677e31dbb55ba8bccebe6ef24929173ecab9f837448d481432e82d3cce2d168c0249c4d05c1c3856d9368412d150b58f050f002596e628329e5b7ff993d4ef428b6a920ea801dfa33644f95669d6dce568377ee6eb982be6d053777a97550a956145a6d0ebbebdcdcd0948d276d5633d62962ae430f2e3d937b635abc06ba85a16ad7332274f5b8cd70c718a349459736984ed0c414cb1ca210c5fdbbfbf09c409c4249025be7a9c0a0f36dcd9b2d0af9e471a3433eac715909285c335b1dedfc5636d0243bc765fc87141af7f4fc7db52834885207c5e8d16ded3a69f3435cd75fbfbf1fa84da9f5d0c9d6cdd62708e27dad2100ef6db8d6cf372e30835062bf4e09225a7d28f9e9c57b3e7f86dd351723705b2ec9326367cb1223304dde6f9b5bdec9a3742f9be650d46787590e0db7efaf1140e21381404905e9e1bcebb372e248c506f6f245d2b601af2fc50bc63b1428f345698cc74de07c0a51b96092bf1dc8f087e32f6aaa1f1688d0be6f12dd4d24d0a256c0384d371017ead1c5d05e2794b8d4", 78 | "372e688f51a2b68e27c21ddc24c9eebd63a83ab21af498864e9eb8a69cfc63368f85e6cfabe5a6e32d265523b71be450b10b6d1c04049c9f856d905201c0d21408ebd8d6d31372903f7507ce358c04a45452baa693e918179545bbfa3c527df37b335580dc00a40f90fafd3aec91f0cb661cf1bbbc0aa3256499c0aba7560d8e06d8000bd0a038498dab423577ee452e267f56c960efa077883bba3fb8bd4dc686b3873b1e0fae3b9608d37273a02e476b302f3a4c59609d3bbbf0a78d30e27301cb86ddecd0ad5ac7cede0ab5934fac937716e72ac726fa5d24a014670b56636bfed37348b8435aace5f1fc259a3536bad9181adff52ca6916962dae98abbbf0b14f5f0dbd00a00ed015405e12ad207e21b0d000334373330934ba6f74177e2ed2bceb09805d1b49e3552e9b1bb7d0770c64a496b59ac6d205878d7c2c8500d92b00f4bbbf120b97af399157791fd6bb11467d878df20a548a65fe372c24910ab97f3d410e7a4f9cfbb0b70feaf45b7afc84669bde97d31f18ae0a47f9f92e28d82ff27d49001bd61bbbf28054a4fc4d56b0e94f88f2b990548e4939d849e9f9c30d33e3f687e1f1adeff5116d944f63ee1fcfadb1942b391db95fcf4cfbfbc433083cfeb6ae50f35f1940540ab8be7f51c3d149b5d5cef73ad6424b7b342347a8145b02495a35dcedbc8f87cfb5b2274cfcaf6b7b82fc814a7f73a81a31bdff7c38fc0b644b6dfb1e59c8adcac3f9a990d1134bbbf3f6a74fc2ab34400a70ac24ca14be8e5a49b89867a5637610e774bc356281f448575e6f0af119b6f8110fb2029cb1b8029a9ec24d9e2bf4fe93a3e6ef8a91686b891d50bbbf5698e0c59c03ade7b0c5e54a70081521c20d40adb04e376b77124d15362c9e4f086f628eefbb9b704b8de8b4b5ca7f57fcd8acc8c5972a744e8946c3a1686785c11ca8abb9c1c26b49a0390a701bd92e500affad9ced00e59889f54", 79 | "31db0639eac47fc5f9fa760b3ab93ae537ebdddc4cc63b3473b0fec048c2eb9a36e91a741ee9b9f113d830b0574bbb4094ed6273840bd56143d130a0008055890bdd6420e5631caec168a13d362bd2bdf6216eb7075bfa1731949e277fd9d310eb738daa252028b38d796db40fc1f9c81172c8bb8703dfe42ad940e7d2a756420c9978eeb490c9aba71442329c22a54a93f3adff55f6455178e25f37936666ba47f000b2ab41d5b59d6bfccd0294140eebe4870b279dcc637be6500eeb6ddeed00e42edebc00812d4d08ed042adf80dad329700a1aa3a0b41d4151230aaef03c5ab4389925d25f3ab5b5f01a8063a4ae52956e1b6a6d3962b16f4c538a02be7f0f152781c9b0e89e3094eb1c9830983c21eb788e9852339b6e10aad3932f46e9084dffcc968ff5bcc460a66fe00dc0294dc1b15d9e99cc0593e90dd007a71bbdf0a5a77bbfc042802930b8013808e8e6d1fbc86d6df7209af2dd32d329900a6a9ef874a106b9889b0406dd043973798baafe7815268c1d35bc5701ad59aa4fe66dea1000b021dcdec9be4f21c842db0db480271d5fc2292345d344a32e396b710031c8b30d48be669ba1bbafea429804c75b708a0db7a7579b7d3e559fef4731fc704083240ece2093e4bf704374bbbbf4471090f1712e9ac1c93ba275927a926d2462d62083b31da6d77894d9a4efbaddbe0f261e126ed40de1a90100f7a418f76c0de8981605bfd20c048e1dda0e62a41e5111b3bf12d310201730000e2deb1c049ebab3d2708e568480b131c8f30a275687af34091a5bca2e185023a26fab768ff50f824641d5c9628aa64633a5b316d28c012ab77303794b3870f3106b31e50bbbb762d9b0348da7000a1d98ac9edfa31caa90b08f6b6635f0e66d2f9e6ddd3f9ad0103a399bb26f46819b1c367f6096b0a2873be5808df1728a2d5490b3bf000123297c40fad41f52bd0207e499c040cd2d0d62c3" 80 | ] 81 | 82 | speech_packets.each_with_index do |packet, index| 83 | sleep 0.5 84 | send_object( 85 | { :class => "SpeechPacket", 86 | :refId => speech_session_ace_id, 87 | :group => "com.apple.ace.speech", 88 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 89 | :properties => 90 | { :packets => 91 | [ 92 | plist_blob(packet) 93 | ], 94 | :packetNumber => index 95 | } 96 | } 97 | ) 98 | end 99 | 100 | send_object( 101 | { :class => "FinishSpeech", 102 | :refId => speech_session_ace_id, 103 | :group => "com.apple.ace.speech", 104 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 105 | :properties => 106 | { :packetCount => speech_packets.length } 107 | } 108 | ) 109 | 110 | flush_and_close_connection 111 | end 112 | 113 | def send_http_headers 114 | send_data ["ACE /ace HTTP/1.0", "Host: guzzoni.apple.com", "User-Agent: Assistant(iPhone/iPhone4,1; iPhone OS/5.0/9A334) Ace/1.0", "Content-Length: 2000000000", "X-Ace-Host: COMMENTED_OUT"].join("\r\n") 115 | send_data "\r\n\r\n" 116 | end 117 | 118 | def send_content_header 119 | #send_data "\r\n" 120 | send_data ["aaccee02"].pack('H*') 121 | end 122 | 123 | def send_object(object) 124 | plist = CFPropertyList::List.new 125 | plist.value = CFPropertyList.guess object 126 | plist_data = plist.to_str(CFPropertyList::List::FORMAT_BINARY) 127 | header = [(0x0200000000 + plist_data.length).to_s(16).rjust(10, '0')].pack('H*') 128 | 129 | send_data @zstream.deflate(header, Zlib::NO_FLUSH) 130 | send_data @zstream.deflate(plist_data, Zlib::SYNC_FLUSH) 131 | 132 | puts "Sent object" 133 | end 134 | 135 | def flush_and_close_connection 136 | #send_data @zstream.flush 137 | #@zstream.close 138 | # @ping_timer.cancel 139 | #self.close_connection_after_writing 140 | end 141 | 142 | def send_ping 143 | chunk = [(0x0300000000 + @ping).to_s(16).rjust(10, '0')].pack('H*') 144 | send_data @zstream.deflate(chunk, Zlib::SYNC_FLUSH) 145 | @ping +=1 146 | puts "Sent ping" 147 | end 148 | 149 | def receive_data data 150 | puts "Guzzoni received data #{data.length}" 151 | puts data 152 | end 153 | 154 | def unbind 155 | puts "Guzzoni connection closed !" 156 | end 157 | end 158 | 159 | EventMachine.run do 160 | EventMachine.connect('guzzoni.apple.com', 443, SiriClient) 161 | #EventMachine.connect('localhost', 443, SiriClient) 162 | end 163 | -------------------------------------------------------------------------------- /Siri.old.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'eventmachine' 4 | require 'zlib' 5 | require 'cfpropertylist' 6 | require 'uuidtools' 7 | require 'pp' 8 | 9 | class String 10 | def to_blob 11 | string = [self].pack('H*') 12 | string.blob = true 13 | string 14 | end 15 | 16 | def remove_leading_hex(hex_string) 17 | length = hex_string.length/2 18 | return self[length..-1] if self[0...length].unpack('H*').first == hex_string 19 | self 20 | end 21 | end 22 | 23 | module Siri 24 | class OutputStream 25 | def initialize 26 | @zstream = Zlib::Deflate.new(Zlib::BEST_COMPRESSION) 27 | @ping = 0 28 | end 29 | 30 | def content_header 31 | ["aaccee02"].pack('H*') 32 | end 33 | 34 | def object(object) 35 | plist = CFPropertyList::List.new 36 | plist.value = CFPropertyList.guess object 37 | plist_data = plist.to_str(CFPropertyList::List::FORMAT_BINARY) 38 | header = [(0x0200000000 + plist_data.length).to_s(16).rjust(10, '0')].pack('H*') 39 | 40 | @zstream.deflate(header, Zlib::NO_FLUSH) + @zstream.deflate(plist_data, Zlib::SYNC_FLUSH) 41 | end 42 | 43 | def ping 44 | @ping +=1 45 | chunk = [(0x0300000000 + @ping).to_s(16).rjust(10, '0')].pack('H*') 46 | @zstream.deflate(chunk, Zlib::SYNC_FLUSH) 47 | end 48 | end 49 | 50 | class InputStream 51 | def initialize 52 | @zstream = Zlib::Inflate.new 53 | @stream = "" 54 | end 55 | 56 | def <<(data) 57 | data = data.remove_leading_hex('0d0a') # Remove heading newline 58 | data = data.remove_leading_hex('aaccee02') # Remove ACE header 59 | @stream << @zstream.inflate(data) 60 | end 61 | 62 | def parse 63 | if @stream[0...5].unpack('H*').first.match(/040000..../) # Ignore 040000xxxx commands, those are PONGs 64 | puts "#####################################################" 65 | puts "* PONG : #{@stream[0...5].unpack('H*').first.match(/040000(....)/)[1].to_i(16)}" 66 | @stream = @stream[5..-1] 67 | end 68 | 69 | chunk_size = @stream[0...5].unpack('H*').first.match(/0200(......)/)[1].to_i(16) rescue 1000000 70 | if (chunk_size < @stream.length+5) 71 | plist_data = @stream[5...5+chunk_size] 72 | plist = CFPropertyList::List.new(:data => plist_data) 73 | puts "#####################################################" 74 | plist_object = CFPropertyList.native_types(plist.value) 75 | pp plist_object 76 | (plist_object["properties"]["packets"] || []).each do |packet| 77 | puts packet.length 78 | File.open("data.spx", "a"){|f| f.write(packet)} 79 | end 80 | @stream = @stream[chunk_size+5..-1] 81 | end 82 | end 83 | end 84 | end 85 | 86 | class SiriClient < EventMachine::Connection 87 | def connection_completed 88 | puts "Guzzoni TCP connection established. Setting up SSL layer" 89 | start_tls(:verify_peer => false) 90 | end 91 | 92 | def ssl_handshake_completed 93 | puts "SSL layer to Guzzoni established !" 94 | @siriOutputStream = Siri::OutputStream.new 95 | @siriInputStream = Siri::InputStream.new 96 | 97 | send_data ["ACE /ace HTTP/1.0", "Host: guzzoni.apple.com", "User-Agent: Assistant(iPhone/iPhone4,1; iPhone OS/5.0/9A334) Ace/1.0", "Content-Length: 2000000000", "X-Ace-Host: COMMENTED_OUT"].join("\r\n") + "\r\n\r\n" 98 | puts "Sent HTTP headers" 99 | send_data @siriOutputStream.content_header 100 | puts "Sent content header !" 101 | send_data @siriOutputStream.ping 102 | 103 | @ping_timer = EventMachine::PeriodicTimer.new(1) do 104 | send_data @siriOutputStream.ping 105 | end 106 | 107 | send_data @siriOutputStream.object( 108 | { :class => 'LoadAssistant', 109 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 110 | :group => 'com.apple.ace.system', 111 | :properties => 112 | { :speechId => 'COMMENTED_OUT', 113 | :assistantId => 'COMMENTED_OUT', 114 | :sessionValidationData => "COMMENTED_OUT".to_blob 115 | } 116 | } 117 | ) 118 | 119 | speech_session_ace_id = UUIDTools::UUID.random_create.to_s.upcase 120 | 121 | send_data @siriOutputStream.object( 122 | { :class => "StartSpeechDictation", 123 | :aceId => speech_session_ace_id, 124 | :group => "com.apple.ace.speech", 125 | :properties => 126 | { :keyboardType => "Default", 127 | :applicationName => "com.apple.mobilenotes", 128 | :applicationVersion => "1.0", 129 | :fieldLabel => "", 130 | :prefixText => "", 131 | :language => "fr-FR", 132 | :censorSpeech => false, 133 | :selectedText => "", 134 | :codec => "Speex_WB_Quality8", 135 | :audioSource => "BuiltInMic", 136 | :region => "fr_FR", 137 | :postfixText => "", 138 | :keyboardReturnKey => "Default", 139 | :interactionId => UUIDTools::UUID.random_create.to_s.upcase, 140 | :fieldId => "UIWebDocumentView0, NoteTextView1, NoteContentLayer0, NotesBackgroundView0, UIViewControllerWrapperView0, UINavigationTransitionView0, UILayoutContainerView0, UIWindow" 141 | } 142 | } 143 | ) 144 | 145 | speech_packets = [ 146 | "37748e774c000068e8e8e8e8e8e8e88401f474745e7ffc3c4013d1945623c83137b1b247eebbf8d6c8b8c8d885db3b64ababababab1ab74958d6d0cd4d8dc26b0cdd956f34931d6533bec50cdd74ea985b116c43ba5aea8bdeeead9a7a0835d9f66d2eb8e87d2c7a0c70cf8883618883710f4db93f0c5edd6018e0c3d571a79f0d68d9356783abababc22835f20b0e68a7f3fb7baea6468900c1ff9da1baaf466eb28d009880d8823d27b3f33c6a69cfdbc1718e9c2ab3ef1bbb62f30dababab16b6da7c7d6249434972003b4ebb10b4235ff09a2add756596e2b98c88201dc3433c19e5917306f646d097e484d4cdb0d77bcb431e2b61f108b49496baf5be6828e27c98e741b680b4b1ab21934abebc730d4fb7d64235f8fba2addeb7b5bfdf7ee14cc08500231f5c5319a43060d622828ef64805566f601e25764f4d18a983bf18243bc5f3abadb8d6c9347d5d98a8b3324a0234fd4d6d9ccfec337217ac74b1f0b4167d3791c36466f5a0f825bb4e7cbaedb955043da67923800fb3c44d7afdb2423db7d66575b0be7e5f8dac0bbc02d4b156978829b391e725a13e29c98e9f35f063636b4e672eb4d689665d6ac4eb7b3cddc210cb8ae450d5c7cb72fa089167ee07a6cfed571105d535c8400bb8716512818e784d6c83d6d402bc3c62394920ced10206d356aec13ed608510c641716d9806f1e2273cdc73911670ae904afc5cd940c05538399cdf7e79a5ede916103017dbf9f32c98b8b1aa0edc03bc04906df83956ab049abab427330510827ef53f295beb20940c616cdefe762de76b08b68977cc9fcd691b0548f39a042129ee39f3020ba2e97082be5804249bd0d4200abdc2428000dc2bdc9490d6428d6b49357e9c116eb17fdfc606c15ea021d4d53db0febcc67310b3286a87b08b4f31acae2999b5c3cf2a10d51c901fdc6be580abab2749140ababab426b00e5649abab08e8d42c93d", 147 | "357dac112dadc332f241595a317334d6bf31a3cc439c755e6591c7cf3ad19b5a0c2622a8effb2da82c629fd219cbc580c28abd271c089bdbde5db0ad80566b8b044c34de7ab35ec630e6f492bdbbbf82b98995588a48fe9732439a4b6dad45257d3b4223736d06c5cc91774243eb301957b4d1bf840d632495827438c9abbc7505afed6ab2006b812dac5535f3350e6f4a82ad05447c9b6d514f239d40835dee5c009d0a52316452e2ee10a39d624938610b9340a8c4da55dbf8e0c990a773120fe3b141f9c09cef5e3d930e75678d9693562ef4590497fc1665a3638ed34892671837e9333428d1a6693b1d7038593528046d169dfed777321a2a7e4ddabba700d532d495d075ed9ca09d0fe2df80dbc1b4340a39e3373842be8f4fdf492e66f4e62eea0728efe5597354953586a25407ff3ba439cb828c2caa97f9214786712d7f0b2bba703e930156560d694420df30846beb0e4d0f38d84bdcd376e49146e570f43336bd208192a2eceabb8e81246466ebabf53d1b4d0a4c4336dea9e931ed8ccd578ad24cdc96bb9f0fa493abc0e0f8d630f8550b17e12029c06d490d8e1031283e52d14bc1eac531169cae31784e33fc34646ca0cc745d736dcd59ddfecc00663af37e3587321d8ba8991bab2af038d6d90e0004249abab3c084abbdab0e0b775f3c2f8377fc0734edee2933b018639fd4485acaf513902632532768255d18428d1815cbecff44a43c6800e1e20d8b34d2b38f0548e8d42c90844d840e49040ebbd0d6305f80ad06cd3774a6bd52551d218505d8e141f5c69b9b869b3f340675fd389566e607e63a4a8e3d7c1a9f64ae00ae1f3f594bab41f00e0d3ded0e0ab0e94c9b80b88d8d789200d6d6d948e357aede2522bfb87063cb3b169077fb3bf19ed4ff12bc3bff5999c8dca8a338df00ef72d3fca6decb5f6ee8163fbf9f0f339275ebb04a7af3ab07079206dc7ac0a91b5ac7bd", 148 | "356ecfe37068df821f9b087226f7f8b58fa21d11aa26a91f8d1a1ef23cba8f5a260bee2d6be0d945067716e4bd8bf9f0140e7f8e8a0a463b282270f38a630dc20cdfed949e5357aa1e4916ae2cf384ad81faf2f867d4f81b00ec93cc446741c05b95d8dbe2dc4eb46ee17e039132d1c6d6b9e3b38f06c01d27a73020a000d402069010a59bd084ad6d9fa037761cbaf172beb19e5937695f44683aa3733fe4a94fd46ff5dcdf907a322eaa3d1961ade3fdb1b14d25c20bfd0bbac020a980ad9d0c8a765de033ab8e3294bd09cb28171a7377dc8b9516d7f077812b74f6ae4f46cf17b3be37d3ceb2fb75007c3f4bb8a414e212a9ee8e286e102d83901662bfad180cf1b26c407a62b0c94908e0e3dba670c5eb598e0e3572cf310e31fa491b60985ecf17a8cffde5d5da2dddb51b83b4d5bde10e972c4b5fcd7ce8e6e13b3ee7e29c50fbfbc0a008adf11e294dcfb7a622e10aab73380abab0acdd635dbac2ef2a999e1f068e9799daf33bae2d5135ba0b4fc2bf003dd487b90fe9a55243f1db6fd1eb4c8990118565b5d80bd42d6615d0ef80bda7040808d4000231ed923d46b931c2e1d2f12bfb736b4708e05f558cf6aefab1a6905a6d338855f6c2ac394b0d2c57560acdce97fee97f80eba27b8c8043121245d91a70bd309d00a16e4eb0561d574d310af33839ceeb0630aa58cbccd6a7a36d3ceab8605ae26f8c4058f7897c9958a01c250506c7c2beeb63329b882587a2b4581ba69d88b100597b8b07391e3d9da28700ea803083c831c648cb50d56f79ad3bca43f8f09b6cbf70d6210e1e3732a69838e9c76db4374e1232313460db72d5ef7d8d2f9b8d81e52f65c3d415739fe17350503b44a94217a00d9498c37767d645068c6d2ae545cf5b17a12b8cdda7cfc41d69b3c695c5ece79a2820a2d6d6c6e63fd2f93169b2b5f7e6b61f0c389cdd6ab0bccdededcd04deb04bdc90c2c93d3d3d", 149 | "377648bab0735f91e8f91dad2fe98a39afeb8217afcda5bf9d1d72cc172706588a6f3deefc787f0f2d09a6d78bfbde50c2ababc2780b83d94753808e56ab0d400edab0d149c377691654f741f8bd9ff685b66b7aab95fcf038b89389d8b170933db981c6a799e2f9e4de8f04cc31f5efa062f5b3e50002d0f91ac0930862939022d1272754d1b78b01e56b3383edfcf01ef66588b1af69564cceb0374fad28f410fda416bf1dfb4a229b621882f9751c673ee578babd084f0b3df327129d32e4478d280dece3593cceb4d34ba92a0f8d432869dabd1281ee32f3d35842c9eaa2cd993860e2883b6d32e8b63d81b5a520d82176d97fd6fac99a178c62f884be55314d4844a14200f39d9e7e49ede786bea2af18a05d3136842b93f1746ea95fdc51536d43e4829fb9402417fca8c1280ac2b6e677e31dbb55ba8bccebe6ef24929173ecab9f837448d481432e82d3cce2d168c0249c4d05c1c3856d9368412d150b58f050f002596e628329e5b7ff993d4ef428b6a920ea801dfa33644f95669d6dce568377ee6eb982be6d053777a97550a956145a6d0ebbebdcdcd0948d276d5633d62962ae430f2e3d937b635abc06ba85a16ad7332274f5b8cd70c718a349459736984ed0c414cb1ca210c5fdbbfbf09c409c4249025be7a9c0a0f36dcd9b2d0af9e471a3433eac715909285c335b1dedfc5636d0243bc765fc87141af7f4fc7db52834885207c5e8d16ded3a69f3435cd75fbfbf1fa84da9f5d0c9d6cdd62708e27dad2100ef6db8d6cf372e30835062bf4e09225a7d28f9e9c57b3e7f86dd351723705b2ec9326367cb1223304dde6f9b5bdec9a3742f9be650d46787590e0db7efaf1140e21381404905e9e1bcebb372e248c506f6f245d2b601af2fc50bc63b1428f345698cc74de07c0a51b96092bf1dc8f087e32f6aaa1f1688d0be6f12dd4d24d0a256c0384d371017ead1c5d05e2794b8d4", 150 | "372e688f51a2b68e27c21ddc24c9eebd63a83ab21af498864e9eb8a69cfc63368f85e6cfabe5a6e32d265523b71be450b10b6d1c04049c9f856d905201c0d21408ebd8d6d31372903f7507ce358c04a45452baa693e918179545bbfa3c527df37b335580dc00a40f90fafd3aec91f0cb661cf1bbbc0aa3256499c0aba7560d8e06d8000bd0a038498dab423577ee452e267f56c960efa077883bba3fb8bd4dc686b3873b1e0fae3b9608d37273a02e476b302f3a4c59609d3bbbf0a78d30e27301cb86ddecd0ad5ac7cede0ab5934fac937716e72ac726fa5d24a014670b56636bfed37348b8435aace5f1fc259a3536bad9181adff52ca6916962dae98abbbf0b14f5f0dbd00a00ed015405e12ad207e21b0d000334373330934ba6f74177e2ed2bceb09805d1b49e3552e9b1bb7d0770c64a496b59ac6d205878d7c2c8500d92b00f4bbbf120b97af399157791fd6bb11467d878df20a548a65fe372c24910ab97f3d410e7a4f9cfbb0b70feaf45b7afc84669bde97d31f18ae0a47f9f92e28d82ff27d49001bd61bbbf28054a4fc4d56b0e94f88f2b990548e4939d849e9f9c30d33e3f687e1f1adeff5116d944f63ee1fcfadb1942b391db95fcf4cfbfbc433083cfeb6ae50f35f1940540ab8be7f51c3d149b5d5cef73ad6424b7b342347a8145b02495a35dcedbc8f87cfb5b2274cfcaf6b7b82fc814a7f73a81a31bdff7c38fc0b644b6dfb1e59c8adcac3f9a990d1134bbbf3f6a74fc2ab34400a70ac24ca14be8e5a49b89867a5637610e774bc356281f448575e6f0af119b6f8110fb2029cb1b8029a9ec24d9e2bf4fe93a3e6ef8a91686b891d50bbbf5698e0c59c03ade7b0c5e54a70081521c20d40adb04e376b77124d15362c9e4f086f628eefbb9b704b8de8b4b5ca7f57fcd8acc8c5972a744e8946c3a1686785c11ca8abb9c1c26b49a0390a701bd92e500affad9ced00e59889f54", 151 | "31db0639eac47fc5f9fa760b3ab93ae537ebdddc4cc63b3473b0fec048c2eb9a36e91a741ee9b9f113d830b0574bbb4094ed6273840bd56143d130a0008055890bdd6420e5631caec168a13d362bd2bdf6216eb7075bfa1731949e277fd9d310eb738daa252028b38d796db40fc1f9c81172c8bb8703dfe42ad940e7d2a756420c9978eeb490c9aba71442329c22a54a93f3adff55f6455178e25f37936666ba47f000b2ab41d5b59d6bfccd0294140eebe4870b279dcc637be6500eeb6ddeed00e42edebc00812d4d08ed042adf80dad329700a1aa3a0b41d4151230aaef03c5ab4389925d25f3ab5b5f01a8063a4ae52956e1b6a6d3962b16f4c538a02be7f0f152781c9b0e89e3094eb1c9830983c21eb788e9852339b6e10aad3932f46e9084dffcc968ff5bcc460a66fe00dc0294dc1b15d9e99cc0593e90dd007a71bbdf0a5a77bbfc042802930b8013808e8e6d1fbc86d6df7209af2dd32d329900a6a9ef874a106b9889b0406dd043973798baafe7815268c1d35bc5701ad59aa4fe66dea1000b021dcdec9be4f21c842db0db480271d5fc2292345d344a32e396b710031c8b30d48be669ba1bbafea429804c75b708a0db7a7579b7d3e559fef4731fc704083240ece2093e4bf704374bbbbf4471090f1712e9ac1c93ba275927a926d2462d62083b31da6d77894d9a4efbaddbe0f261e126ed40de1a90100f7a418f76c0de8981605bfd20c048e1dda0e62a41e5111b3bf12d310201730000e2deb1c049ebab3d2708e568480b131c8f30a275687af34091a5bca2e185023a26fab768ff50f824641d5c9628aa64633a5b316d28c012ab77303794b3870f3106b31e50bbbb762d9b0348da7000a1d98ac9edfa31caa90b08f6b6635f0e66d2f9e6ddd3f9ad0103a399bb26f46819b1c367f6096b0a2873be5808df1728a2d5490b3bf000123297c40fad41f52bd0207e499c040cd2d0d62c3" 152 | ] 153 | 154 | speech_packets.each_with_index do |packet, index| 155 | sleep 0.5 156 | send_data @siriOutputStream.object( 157 | { :class => "SpeechPacket", 158 | :refId => speech_session_ace_id, 159 | :group => "com.apple.ace.speech", 160 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 161 | :properties => 162 | { :packets => 163 | [ 164 | packet.to_blob 165 | ], 166 | :packetNumber => index 167 | } 168 | } 169 | ) 170 | puts "Sent speech packet" 171 | end 172 | 173 | send_data @siriOutputStream.object( 174 | { :class => "FinishSpeech", 175 | :refId => speech_session_ace_id, 176 | :group => "com.apple.ace.speech", 177 | :aceId => UUIDTools::UUID.random_create.to_s.upcase, 178 | :properties => 179 | { :packetCount => speech_packets.length } 180 | } 181 | ) 182 | 183 | #flush_and_close_connection 184 | end 185 | 186 | def flush_and_close_connection 187 | #send_data @zstream.flush 188 | #@zstream.close 189 | # @ping_timer.cancel 190 | #self.close_connection_after_writing 191 | end 192 | 193 | def receive_data data 194 | #puts "Received data from Guzzoni : #{data}" 195 | unless data.match(/Server/) 196 | @siriInputStream << data 197 | @siriInputStream.parse 198 | end 199 | end 200 | 201 | def unbind 202 | puts "Guzzoni connection closed !" 203 | end 204 | end 205 | 206 | EventMachine.run do 207 | EventMachine.connect('guzzoni.apple.com', 443, SiriClient) 208 | #EventMachine.connect('localhost', 443, SiriClient) 209 | end 210 | -------------------------------------------------------------------------------- /speexenc.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2002-2006 Jean-Marc Valin 2 | File: speexenc.c 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | - Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | - Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | - Neither the name of the Xiph.org Foundation nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 23 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | #ifdef HAVE_CONFIG_H 33 | # include "config.h" 34 | #endif 35 | 36 | #include 37 | #if !defined WIN32 && !defined _WIN32 38 | #include 39 | #endif 40 | #ifdef HAVE_GETOPT_H 41 | #include 42 | #endif 43 | #ifndef HAVE_GETOPT_LONG 44 | #include "getopt_win.h" 45 | #endif 46 | #include 47 | #include 48 | #include 49 | 50 | #include 51 | #include 52 | #include "wav_io.h" 53 | #include 54 | #include 55 | #include 56 | 57 | #if defined WIN32 || defined _WIN32 58 | /* We need the following two to set stdout to binary */ 59 | #include 60 | #include 61 | #endif 62 | 63 | #include "skeleton.h" 64 | 65 | 66 | void comment_init(char **comments, int* length, char *vendor_string); 67 | void comment_add(char **comments, int* length, char *tag, char *val); 68 | 69 | 70 | /*Write an Ogg page to a file pointer*/ 71 | int oe_write_page(ogg_page *page, FILE *fp) 72 | { 73 | int written; 74 | written = fwrite(page->header,1,page->header_len, fp); 75 | written += fwrite(page->body,1,page->body_len, fp); 76 | 77 | return written; 78 | } 79 | 80 | #define MAX_FRAME_SIZE 2000 81 | #define MAX_FRAME_BYTES 2000 82 | 83 | /* Convert input audio bits, endians and channels */ 84 | static int read_samples(FILE *fin,int frame_size, int bits, int channels, int lsb, short * input, char *buff, spx_int32_t *size) 85 | { 86 | unsigned char in[MAX_FRAME_BYTES*2]; 87 | int i; 88 | short *s; 89 | int nb_read; 90 | 91 | if (size && *size<=0) 92 | { 93 | return 0; 94 | } 95 | /*Read input audio*/ 96 | if (size) 97 | *size -= bits/8*channels*frame_size; 98 | if (buff) 99 | { 100 | for (i=0;i<12;i++) 101 | in[i]=buff[i]; 102 | nb_read = fread(in+12,1,bits/8*channels*frame_size-12, fin) + 12; 103 | if (size) 104 | *size += 12; 105 | } else { 106 | nb_read = fread(in,1,bits/8*channels* frame_size, fin); 107 | } 108 | nb_read /= bits/8*channels; 109 | 110 | /*fprintf (stderr, "%d\n", nb_read);*/ 111 | if (nb_read==0) 112 | return 0; 113 | 114 | s=(short*)in; 115 | if(bits==8) 116 | { 117 | /* Convert 8->16 bits */ 118 | for(i=frame_size*channels-1;i>=0;i--) 119 | { 120 | s[i]=(in[i]<<8)^0x8000; 121 | } 122 | } else 123 | { 124 | /* convert to our endian format */ 125 | for(i=0;iextra_headers; 173 | fp.granule_rate_n = header->rate; 174 | fp.granule_rate_d = 1; 175 | fp.start_granule = 0; 176 | fp.preroll = 3; 177 | fp.granule_shift = 0; 178 | 179 | add_message_header_field(&fp, "Content-Type", "audio/x-speex"); 180 | 181 | add_fisbone_to_stream(os, &fp); 182 | } 183 | 184 | void version() 185 | { 186 | const char* speex_version; 187 | speex_lib_ctl(SPEEX_LIB_GET_VERSION_STRING, (void*)&speex_version); 188 | printf ("speexenc (Speex encoder) version %s (compiled " __DATE__ ")\n", speex_version); 189 | printf ("Copyright (C) 2002-2006 Jean-Marc Valin\n"); 190 | } 191 | 192 | void version_short() 193 | { 194 | const char* speex_version; 195 | speex_lib_ctl(SPEEX_LIB_GET_VERSION_STRING, (void*)&speex_version); 196 | printf ("speexenc version %s\n", speex_version); 197 | printf ("Copyright (C) 2002-2006 Jean-Marc Valin\n"); 198 | } 199 | 200 | void usage() 201 | { 202 | printf ("Usage: speexenc [options] input_file output_file\n"); 203 | printf ("\n"); 204 | printf ("Encodes input_file using Speex. It can read the WAV or raw files.\n"); 205 | printf ("\n"); 206 | printf ("input_file can be:\n"); 207 | printf (" filename.wav wav file\n"); 208 | printf (" filename.* Raw PCM file (any extension other than .wav)\n"); 209 | printf (" - stdin\n"); 210 | printf ("\n"); 211 | printf ("output_file can be:\n"); 212 | printf (" filename.spx Speex file\n"); 213 | printf (" - stdout\n"); 214 | printf ("\n"); 215 | printf ("Options:\n"); 216 | printf (" -n, --narrowband Narrowband (8 kHz) input file\n"); 217 | printf (" -w, --wideband Wideband (16 kHz) input file\n"); 218 | printf (" -u, --ultra-wideband \"Ultra-wideband\" (32 kHz) input file\n"); 219 | printf (" --quality n Encoding quality (0-10), default 8\n"); 220 | printf (" --bitrate n Encoding bit-rate (use bit-rate n or lower)\n"); 221 | printf (" --vbr Enable variable bit-rate (VBR)\n"); 222 | printf (" --vbr-max-bitrate Set max VBR bit-rate allowed\n"); 223 | printf (" --abr rate Enable average bit-rate (ABR) at rate bps\n"); 224 | printf (" --vad Enable voice activity detection (VAD)\n"); 225 | printf (" --dtx Enable file-based discontinuous transmission (DTX)\n"); 226 | printf (" --comp n Set encoding complexity (0-10), default 3\n"); 227 | printf (" --nframes n Number of frames per Ogg packet (1-10), default 1\n"); 228 | printf (" --denoise Denoise the input before encoding\n"); 229 | printf (" --agc Apply adaptive gain control (AGC) before encoding\n"); 230 | printf (" --skeleton Outputs ogg skeleton metadata (may cause incompatibilities)\n"); 231 | printf (" --comment Add the given string as an extra comment. This may be\n"); 232 | printf (" used multiple times\n"); 233 | printf (" --author Author of this track\n"); 234 | printf (" --title Title for this track\n"); 235 | printf (" -h, --help This help\n"); 236 | printf (" -v, --version Version information\n"); 237 | printf (" -V Verbose mode (show bit-rate)\n"); 238 | printf ("Raw input options:\n"); 239 | printf (" --rate n Sampling rate for raw input\n"); 240 | printf (" --stereo Consider raw input as stereo\n"); 241 | printf (" --le Raw input is little-endian\n"); 242 | printf (" --be Raw input is big-endian\n"); 243 | printf (" --8bit Raw input is 8-bit unsigned\n"); 244 | printf (" --16bit Raw input is 16-bit signed\n"); 245 | printf ("Default raw PCM input is 16-bit, little-endian, mono\n"); 246 | printf ("\n"); 247 | printf ("More information is available from the Speex site: http://www.speex.org\n"); 248 | printf ("\n"); 249 | printf ("Please report bugs to the mailing list `speex-dev@xiph.org'.\n"); 250 | } 251 | 252 | 253 | int main(int argc, char **argv) 254 | { 255 | int nb_samples, total_samples=0, nb_encoded; 256 | int c; 257 | int option_index = 0; 258 | char *inFile, *outFile; 259 | FILE *fin, *fout; 260 | short input[MAX_FRAME_SIZE]; 261 | spx_int32_t frame_size; 262 | int quiet=0; 263 | spx_int32_t vbr_enabled=0; 264 | spx_int32_t vbr_max=0; 265 | int abr_enabled=0; 266 | spx_int32_t vad_enabled=0; 267 | spx_int32_t dtx_enabled=0; 268 | int nbBytes; 269 | const SpeexMode *mode=NULL; 270 | int modeID = -1; 271 | void *st; 272 | SpeexBits bits; 273 | char cbits[MAX_FRAME_BYTES]; 274 | int with_skeleton = 0; 275 | struct option long_options[] = 276 | { 277 | {"wideband", no_argument, NULL, 0}, 278 | {"ultra-wideband", no_argument, NULL, 0}, 279 | {"narrowband", no_argument, NULL, 0}, 280 | {"vbr", no_argument, NULL, 0}, 281 | {"vbr-max-bitrate", required_argument, NULL, 0}, 282 | {"abr", required_argument, NULL, 0}, 283 | {"vad", no_argument, NULL, 0}, 284 | {"dtx", no_argument, NULL, 0}, 285 | {"quality", required_argument, NULL, 0}, 286 | {"bitrate", required_argument, NULL, 0}, 287 | {"nframes", required_argument, NULL, 0}, 288 | {"comp", required_argument, NULL, 0}, 289 | {"denoise", no_argument, NULL, 0}, 290 | {"agc", no_argument, NULL, 0}, 291 | {"skeleton",no_argument,NULL, 0}, 292 | {"help", no_argument, NULL, 0}, 293 | {"quiet", no_argument, NULL, 0}, 294 | {"le", no_argument, NULL, 0}, 295 | {"be", no_argument, NULL, 0}, 296 | {"8bit", no_argument, NULL, 0}, 297 | {"16bit", no_argument, NULL, 0}, 298 | {"stereo", no_argument, NULL, 0}, 299 | {"rate", required_argument, NULL, 0}, 300 | {"version", no_argument, NULL, 0}, 301 | {"version-short", no_argument, NULL, 0}, 302 | {"comment", required_argument, NULL, 0}, 303 | {"author", required_argument, NULL, 0}, 304 | {"title", required_argument, NULL, 0}, 305 | {0, 0, 0, 0} 306 | }; 307 | int print_bitrate=0; 308 | spx_int32_t rate=0; 309 | spx_int32_t size; 310 | int chan=1; 311 | int fmt=16; 312 | spx_int32_t quality=-1; 313 | float vbr_quality=-1; 314 | int lsb=1; 315 | ogg_stream_state os; 316 | ogg_stream_state so; /* ogg stream for skeleton bitstream */ 317 | ogg_page og; 318 | ogg_packet op; 319 | int bytes_written=0, ret, result; 320 | int id=-1; 321 | SpeexHeader header; 322 | int nframes=1; 323 | spx_int32_t complexity=3; 324 | const char* speex_version; 325 | char vendor_string[64]; 326 | char *comments; 327 | int comments_length; 328 | int close_in=0, close_out=0; 329 | int eos=0; 330 | spx_int32_t bitrate=0; 331 | double cumul_bits=0, enc_frames=0; 332 | char first_bytes[12]; 333 | int wave_input=0; 334 | spx_int32_t tmp; 335 | SpeexPreprocessState *preprocess = NULL; 336 | int denoise_enabled=0, agc_enabled=0; 337 | spx_int32_t lookahead = 0; 338 | 339 | speex_lib_ctl(SPEEX_LIB_GET_VERSION_STRING, (void*)&speex_version); 340 | snprintf(vendor_string, sizeof(vendor_string), "Encoded with Speex %s", speex_version); 341 | 342 | comment_init(&comments, &comments_length, vendor_string); 343 | 344 | /*Process command-line options*/ 345 | while(1) 346 | { 347 | c = getopt_long (argc, argv, "nwuhvV", 348 | long_options, &option_index); 349 | if (c==-1) 350 | break; 351 | 352 | switch(c) 353 | { 354 | case 0: 355 | if (strcmp(long_options[option_index].name,"narrowband")==0) 356 | { 357 | modeID = SPEEX_MODEID_NB; 358 | } else if (strcmp(long_options[option_index].name,"wideband")==0) 359 | { 360 | modeID = SPEEX_MODEID_WB; 361 | } else if (strcmp(long_options[option_index].name,"ultra-wideband")==0) 362 | { 363 | modeID = SPEEX_MODEID_UWB; 364 | } else if (strcmp(long_options[option_index].name,"vbr")==0) 365 | { 366 | vbr_enabled=1; 367 | } else if (strcmp(long_options[option_index].name,"vbr-max-bitrate")==0) 368 | { 369 | vbr_max=atoi(optarg); 370 | if (vbr_max<1) 371 | { 372 | fprintf (stderr, "Invalid VBR max bit-rate value: %d\n", vbr_max); 373 | exit(1); 374 | } 375 | } else if (strcmp(long_options[option_index].name,"abr")==0) 376 | { 377 | abr_enabled=atoi(optarg); 378 | if (!abr_enabled) 379 | { 380 | fprintf (stderr, "Invalid ABR value: %d\n", abr_enabled); 381 | exit(1); 382 | } 383 | } else if (strcmp(long_options[option_index].name,"vad")==0) 384 | { 385 | vad_enabled=1; 386 | } else if (strcmp(long_options[option_index].name,"dtx")==0) 387 | { 388 | dtx_enabled=1; 389 | } else if (strcmp(long_options[option_index].name,"quality")==0) 390 | { 391 | quality = atoi (optarg); 392 | vbr_quality=atof(optarg); 393 | } else if (strcmp(long_options[option_index].name,"bitrate")==0) 394 | { 395 | bitrate = atoi (optarg); 396 | } else if (strcmp(long_options[option_index].name,"nframes")==0) 397 | { 398 | nframes = atoi (optarg); 399 | if (nframes<1) 400 | nframes=1; 401 | if (nframes>10) 402 | nframes=10; 403 | } else if (strcmp(long_options[option_index].name,"comp")==0) 404 | { 405 | complexity = atoi (optarg); 406 | } else if (strcmp(long_options[option_index].name,"denoise")==0) 407 | { 408 | denoise_enabled=1; 409 | } else if (strcmp(long_options[option_index].name,"agc")==0) 410 | { 411 | agc_enabled=1; 412 | } else if (strcmp(long_options[option_index].name,"skeleton")==0) 413 | { 414 | with_skeleton=1; 415 | } else if (strcmp(long_options[option_index].name,"help")==0) 416 | { 417 | usage(); 418 | exit(0); 419 | } else if (strcmp(long_options[option_index].name,"quiet")==0) 420 | { 421 | quiet = 1; 422 | } else if (strcmp(long_options[option_index].name,"version")==0) 423 | { 424 | version(); 425 | exit(0); 426 | } else if (strcmp(long_options[option_index].name,"version-short")==0) 427 | { 428 | version_short(); 429 | exit(0); 430 | } else if (strcmp(long_options[option_index].name,"le")==0) 431 | { 432 | lsb=1; 433 | } else if (strcmp(long_options[option_index].name,"be")==0) 434 | { 435 | lsb=0; 436 | } else if (strcmp(long_options[option_index].name,"8bit")==0) 437 | { 438 | fmt=8; 439 | } else if (strcmp(long_options[option_index].name,"16bit")==0) 440 | { 441 | fmt=16; 442 | } else if (strcmp(long_options[option_index].name,"stereo")==0) 443 | { 444 | chan=2; 445 | } else if (strcmp(long_options[option_index].name,"rate")==0) 446 | { 447 | rate=atoi (optarg); 448 | } else if (strcmp(long_options[option_index].name,"comment")==0) 449 | { 450 | if (!strchr(optarg, '=')) 451 | { 452 | fprintf (stderr, "Invalid comment: %s\n", optarg); 453 | fprintf (stderr, "Comments must be of the form name=value\n"); 454 | exit(1); 455 | } 456 | comment_add(&comments, &comments_length, NULL, optarg); 457 | } else if (strcmp(long_options[option_index].name,"author")==0) 458 | { 459 | comment_add(&comments, &comments_length, "author=", optarg); 460 | } else if (strcmp(long_options[option_index].name,"title")==0) 461 | { 462 | comment_add(&comments, &comments_length, "title=", optarg); 463 | } 464 | 465 | break; 466 | case 'n': 467 | modeID = SPEEX_MODEID_NB; 468 | break; 469 | case 'h': 470 | usage(); 471 | exit(0); 472 | break; 473 | case 'v': 474 | version(); 475 | exit(0); 476 | break; 477 | case 'V': 478 | print_bitrate=1; 479 | break; 480 | case 'w': 481 | modeID = SPEEX_MODEID_WB; 482 | break; 483 | case 'u': 484 | modeID = SPEEX_MODEID_UWB; 485 | break; 486 | case '?': 487 | usage(); 488 | exit(1); 489 | break; 490 | } 491 | } 492 | if (argc-optind!=2) 493 | { 494 | usage(); 495 | exit(1); 496 | } 497 | inFile=argv[optind]; 498 | outFile=argv[optind+1]; 499 | 500 | /*Initialize Ogg stream struct*/ 501 | srand(time(NULL)); 502 | if (ogg_stream_init(&os, rand())==-1) 503 | { 504 | fprintf(stderr,"Error: stream init failed\n"); 505 | exit(1); 506 | } 507 | if (with_skeleton && ogg_stream_init(&so, rand())==-1) 508 | { 509 | fprintf(stderr,"Error: stream init failed\n"); 510 | exit(1); 511 | } 512 | 513 | if (strcmp(inFile, "-")==0) 514 | { 515 | #if defined WIN32 || defined _WIN32 516 | _setmode(_fileno(stdin), _O_BINARY); 517 | #elif defined OS2 518 | _fsetmode(stdin,"b"); 519 | #endif 520 | fin=stdin; 521 | } 522 | else 523 | { 524 | fin = fopen(inFile, "rb"); 525 | if (!fin) 526 | { 527 | perror(inFile); 528 | exit(1); 529 | } 530 | close_in=1; 531 | } 532 | 533 | { 534 | fread(first_bytes, 1, 12, fin); 535 | if (strncmp(first_bytes,"RIFF",4)==0 && strncmp(first_bytes,"RIFF",4)==0) 536 | { 537 | if (read_wav_header(fin, &rate, &chan, &fmt, &size)==-1) 538 | exit(1); 539 | wave_input=1; 540 | lsb=1; /* CHECK: exists big-endian .wav ?? */ 541 | } 542 | } 543 | 544 | if (modeID==-1 && !rate) 545 | { 546 | /* By default, use narrowband/8 kHz */ 547 | modeID = SPEEX_MODEID_NB; 548 | rate=8000; 549 | } else if (modeID!=-1 && rate) 550 | { 551 | mode = speex_lib_get_mode (modeID); 552 | if (rate>48000) 553 | { 554 | fprintf (stderr, "Error: sampling rate too high: %d Hz, try down-sampling\n", rate); 555 | exit(1); 556 | } else if (rate>25000) 557 | { 558 | if (modeID != SPEEX_MODEID_UWB) 559 | { 560 | fprintf (stderr, "Warning: Trying to encode in %s at %d Hz. I'll do it but I suggest you try ultra-wideband instead\n", mode->modeName , rate); 561 | } 562 | } else if (rate>12500) 563 | { 564 | if (modeID != SPEEX_MODEID_WB) 565 | { 566 | fprintf (stderr, "Warning: Trying to encode in %s at %d Hz. I'll do it but I suggest you try wideband instead\n", mode->modeName , rate); 567 | } 568 | } else if (rate>=6000) 569 | { 570 | if (modeID != SPEEX_MODEID_NB) 571 | { 572 | fprintf (stderr, "Warning: Trying to encode in %s at %d Hz. I'll do it but I suggest you try narrowband instead\n", mode->modeName , rate); 573 | } 574 | } else { 575 | fprintf (stderr, "Error: sampling rate too low: %d Hz\n", rate); 576 | exit(1); 577 | } 578 | } else if (modeID==-1) 579 | { 580 | if (rate>48000) 581 | { 582 | fprintf (stderr, "Error: sampling rate too high: %d Hz, try down-sampling\n", rate); 583 | exit(1); 584 | } else if (rate>25000) 585 | { 586 | modeID = SPEEX_MODEID_UWB; 587 | } else if (rate>12500) 588 | { 589 | modeID = SPEEX_MODEID_WB; 590 | } else if (rate>=6000) 591 | { 592 | modeID = SPEEX_MODEID_NB; 593 | } else { 594 | fprintf (stderr, "Error: Sampling rate too low: %d Hz\n", rate); 595 | exit(1); 596 | } 597 | } else if (!rate) 598 | { 599 | if (modeID == SPEEX_MODEID_NB) 600 | rate=8000; 601 | else if (modeID == SPEEX_MODEID_WB) 602 | rate=16000; 603 | else if (modeID == SPEEX_MODEID_UWB) 604 | rate=32000; 605 | } 606 | 607 | if (!quiet) 608 | if (rate!=8000 && rate!=16000 && rate!=32000) 609 | fprintf (stderr, "Warning: Speex is only optimized for 8, 16 and 32 kHz. It will still work at %d Hz but your mileage may vary\n", rate); 610 | 611 | if (!mode) 612 | mode = speex_lib_get_mode (modeID); 613 | 614 | speex_init_header(&header, rate, 1, mode); 615 | header.frames_per_packet=nframes; 616 | header.vbr=vbr_enabled; 617 | header.nb_channels = chan; 618 | 619 | { 620 | char *st_string="mono"; 621 | if (chan==2) 622 | st_string="stereo"; 623 | if (!quiet) 624 | fprintf (stderr, "Encoding %d Hz audio using %s mode (%s)\n", 625 | header.rate, mode->modeName, st_string); 626 | } 627 | /*fprintf (stderr, "Encoding %d Hz audio at %d bps using %s mode\n", 628 | header.rate, mode->bitrate, mode->modeName);*/ 629 | 630 | /*Initialize Speex encoder*/ 631 | st = speex_encoder_init(mode); 632 | 633 | if (strcmp(outFile,"-")==0) 634 | { 635 | #if defined WIN32 || defined _WIN32 636 | _setmode(_fileno(stdout), _O_BINARY); 637 | #endif 638 | fout=stdout; 639 | } 640 | else 641 | { 642 | fout = fopen(outFile, "wb"); 643 | if (!fout) 644 | { 645 | perror(outFile); 646 | exit(1); 647 | } 648 | close_out=1; 649 | } 650 | 651 | speex_encoder_ctl(st, SPEEX_GET_FRAME_SIZE, &frame_size); 652 | speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &complexity); 653 | speex_encoder_ctl(st, SPEEX_SET_SAMPLING_RATE, &rate); 654 | 655 | if (quality >= 0) 656 | { 657 | if (vbr_enabled) 658 | { 659 | if (vbr_max>0) 660 | speex_encoder_ctl(st, SPEEX_SET_VBR_MAX_BITRATE, &vbr_max); 661 | speex_encoder_ctl(st, SPEEX_SET_VBR_QUALITY, &vbr_quality); 662 | } 663 | else 664 | speex_encoder_ctl(st, SPEEX_SET_QUALITY, &quality); 665 | } 666 | if (bitrate) 667 | { 668 | if (quality >= 0 && vbr_enabled) 669 | fprintf (stderr, "Warning: --bitrate option is overriding --quality\n"); 670 | speex_encoder_ctl(st, SPEEX_SET_BITRATE, &bitrate); 671 | } 672 | if (vbr_enabled) 673 | { 674 | tmp=1; 675 | speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp); 676 | } else if (vad_enabled) 677 | { 678 | tmp=1; 679 | speex_encoder_ctl(st, SPEEX_SET_VAD, &tmp); 680 | } 681 | if (dtx_enabled) 682 | speex_encoder_ctl(st, SPEEX_SET_DTX, &tmp); 683 | if (dtx_enabled && !(vbr_enabled || abr_enabled || vad_enabled)) 684 | { 685 | fprintf (stderr, "Warning: --dtx is useless without --vad, --vbr or --abr\n"); 686 | } else if ((vbr_enabled || abr_enabled) && (vad_enabled)) 687 | { 688 | fprintf (stderr, "Warning: --vad is already implied by --vbr or --abr\n"); 689 | } 690 | if (with_skeleton) { 691 | fprintf (stderr, "Warning: Enabling skeleton output may cause some decoders to fail.\n"); 692 | } 693 | 694 | if (abr_enabled) 695 | { 696 | speex_encoder_ctl(st, SPEEX_SET_ABR, &abr_enabled); 697 | } 698 | 699 | speex_encoder_ctl(st, SPEEX_GET_LOOKAHEAD, &lookahead); 700 | 701 | if (denoise_enabled || agc_enabled) 702 | { 703 | preprocess = speex_preprocess_state_init(frame_size, rate); 704 | speex_preprocess_ctl(preprocess, SPEEX_PREPROCESS_SET_DENOISE, &denoise_enabled); 705 | speex_preprocess_ctl(preprocess, SPEEX_PREPROCESS_SET_AGC, &agc_enabled); 706 | lookahead += frame_size; 707 | } 708 | 709 | /* first packet should be the skeleton header. */ 710 | 711 | if (with_skeleton) { 712 | add_fishead_packet(&so); 713 | if ((ret = flush_ogg_stream_to_file(&so, fout))) { 714 | fprintf (stderr,"Error: failed skeleton (fishead) header to output stream\n"); 715 | exit(1); 716 | } else 717 | bytes_written += ret; 718 | } 719 | 720 | /*Write header*/ 721 | { 722 | int packet_size; 723 | op.packet = (unsigned char *)speex_header_to_packet(&header, &packet_size); 724 | op.bytes = packet_size; 725 | op.b_o_s = 1; 726 | op.e_o_s = 0; 727 | op.granulepos = 0; 728 | op.packetno = 0; 729 | ogg_stream_packetin(&os, &op); 730 | free(op.packet); 731 | 732 | while((result = ogg_stream_flush(&os, &og))) 733 | { 734 | if(!result) break; 735 | ret = oe_write_page(&og, fout); 736 | if(ret != og.header_len + og.body_len) 737 | { 738 | fprintf (stderr,"Error: failed writing header to output stream\n"); 739 | exit(1); 740 | } 741 | else 742 | bytes_written += ret; 743 | } 744 | 745 | op.packet = (unsigned char *)comments; 746 | op.bytes = comments_length; 747 | op.b_o_s = 0; 748 | op.e_o_s = 0; 749 | op.granulepos = 0; 750 | op.packetno = 1; 751 | ogg_stream_packetin(&os, &op); 752 | } 753 | 754 | /* fisbone packet should be write after all bos pages */ 755 | if (with_skeleton) { 756 | add_fisbone_packet(&so, os.serialno, &header); 757 | if ((ret = flush_ogg_stream_to_file(&so, fout))) { 758 | fprintf (stderr,"Error: failed writing skeleton (fisbone )header to output stream\n"); 759 | exit(1); 760 | } else 761 | bytes_written += ret; 762 | } 763 | 764 | /* writing the rest of the speex header packets */ 765 | while((result = ogg_stream_flush(&os, &og))) 766 | { 767 | if(!result) break; 768 | ret = oe_write_page(&og, fout); 769 | if(ret != og.header_len + og.body_len) 770 | { 771 | fprintf (stderr,"Error: failed writing header to output stream\n"); 772 | exit(1); 773 | } 774 | else 775 | bytes_written += ret; 776 | } 777 | 778 | free(comments); 779 | 780 | /* write the skeleton eos packet */ 781 | if (with_skeleton) { 782 | add_eos_packet_to_stream(&so); 783 | if ((ret = flush_ogg_stream_to_file(&so, fout))) { 784 | fprintf (stderr,"Error: failed writing skeleton header to output stream\n"); 785 | exit(1); 786 | } else 787 | bytes_written += ret; 788 | } 789 | 790 | 791 | speex_bits_init(&bits); 792 | 793 | if (!wave_input) 794 | { 795 | nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, first_bytes, NULL); 796 | } else { 797 | nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, &size); 798 | } 799 | if (nb_samples==0) 800 | eos=1; 801 | total_samples += nb_samples; 802 | nb_encoded = -lookahead; 803 | /*Main encoding loop (one frame per iteration)*/ 804 | while (!eos || total_samples>nb_encoded) 805 | { 806 | id++; 807 | /*Encode current frame*/ 808 | if (chan==2) 809 | speex_encode_stereo_int(input, frame_size, &bits); 810 | 811 | if (preprocess) 812 | speex_preprocess(preprocess, input, NULL); 813 | 814 | speex_encode_int(st, input, &bits); 815 | 816 | nb_encoded += frame_size; 817 | if (print_bitrate) { 818 | int tmp; 819 | char ch=13; 820 | speex_encoder_ctl(st, SPEEX_GET_BITRATE, &tmp); 821 | fputc (ch, stderr); 822 | cumul_bits += tmp; 823 | enc_frames += 1; 824 | if (!quiet) 825 | { 826 | if (vad_enabled || vbr_enabled || abr_enabled) 827 | fprintf (stderr, "Bitrate is use: %d bps (average %d bps) ", tmp, (int)(cumul_bits/enc_frames)); 828 | else 829 | fprintf (stderr, "Bitrate is use: %d bps ", tmp); 830 | } 831 | 832 | } 833 | 834 | if (wave_input) 835 | { 836 | nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, &size); 837 | } else { 838 | nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, NULL); 839 | } 840 | if (nb_samples==0) 841 | { 842 | eos=1; 843 | } 844 | if (eos && total_samples<=nb_encoded) 845 | op.e_o_s = 1; 846 | else 847 | op.e_o_s = 0; 848 | total_samples += nb_samples; 849 | 850 | if ((id+1)%nframes!=0) 851 | continue; 852 | speex_bits_insert_terminator(&bits); 853 | nbBytes = speex_bits_write(&bits, cbits, MAX_FRAME_BYTES); 854 | speex_bits_reset(&bits); 855 | op.packet = (unsigned char *)cbits; 856 | op.bytes = nbBytes; 857 | op.b_o_s = 0; 858 | /*Is this redundent?*/ 859 | if (eos && total_samples<=nb_encoded) 860 | op.e_o_s = 1; 861 | else 862 | op.e_o_s = 0; 863 | op.granulepos = (id+1)*frame_size-lookahead; 864 | if (op.granulepos>total_samples) 865 | op.granulepos = total_samples; 866 | /*printf ("granulepos: %d %d %d %d %d %d\n", (int)op.granulepos, id, nframes, lookahead, 5, 6);*/ 867 | op.packetno = 2+id/nframes; 868 | ogg_stream_packetin(&os, &op); 869 | 870 | /*Write all new pages (most likely 0 or 1)*/ 871 | while (ogg_stream_pageout(&os,&og)) 872 | { 873 | ret = oe_write_page(&og, fout); 874 | if(ret != og.header_len + og.body_len) 875 | { 876 | fprintf (stderr,"Error: failed writing header to output stream\n"); 877 | exit(1); 878 | } 879 | else 880 | bytes_written += ret; 881 | } 882 | } 883 | if ((id+1)%nframes!=0) 884 | { 885 | while ((id+1)%nframes!=0) 886 | { 887 | id++; 888 | speex_bits_pack(&bits, 15, 5); 889 | } 890 | nbBytes = speex_bits_write(&bits, cbits, MAX_FRAME_BYTES); 891 | op.packet = (unsigned char *)cbits; 892 | op.bytes = nbBytes; 893 | op.b_o_s = 0; 894 | op.e_o_s = 1; 895 | op.granulepos = (id+1)*frame_size-lookahead; 896 | if (op.granulepos>total_samples) 897 | op.granulepos = total_samples; 898 | 899 | op.packetno = 2+id/nframes; 900 | ogg_stream_packetin(&os, &op); 901 | } 902 | /*Flush all pages left to be written*/ 903 | while (ogg_stream_flush(&os, &og)) 904 | { 905 | ret = oe_write_page(&og, fout); 906 | if(ret != og.header_len + og.body_len) 907 | { 908 | fprintf (stderr,"Error: failed writing header to output stream\n"); 909 | exit(1); 910 | } 911 | else 912 | bytes_written += ret; 913 | } 914 | 915 | speex_encoder_destroy(st); 916 | speex_bits_destroy(&bits); 917 | ogg_stream_clear(&os); 918 | 919 | if (close_in) 920 | fclose(fin); 921 | if (close_out) 922 | fclose(fout); 923 | return 0; 924 | } 925 | 926 | /* 927 | Comments will be stored in the Vorbis style. 928 | It is describled in the "Structure" section of 929 | http://www.xiph.org/ogg/vorbis/doc/v-comment.html 930 | 931 | The comment header is decoded as follows: 932 | 1) [vendor_length] = read an unsigned integer of 32 bits 933 | 2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets 934 | 3) [user_comment_list_length] = read an unsigned integer of 32 bits 935 | 4) iterate [user_comment_list_length] times { 936 | 5) [length] = read an unsigned integer of 32 bits 937 | 6) this iteration's user comment = read a UTF-8 vector as [length] octets 938 | } 939 | 7) [framing_bit] = read a single bit as boolean 940 | 8) if ( [framing_bit] unset or end of packet ) then ERROR 941 | 9) done. 942 | 943 | If you have troubles, please write to ymnk@jcraft.com. 944 | */ 945 | 946 | #define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \ 947 | ((buf[base+2]<<16)&0xff0000)| \ 948 | ((buf[base+1]<<8)&0xff00)| \ 949 | (buf[base]&0xff)) 950 | #define writeint(buf, base, val) do{ buf[base+3]=((val)>>24)&0xff; \ 951 | buf[base+2]=((val)>>16)&0xff; \ 952 | buf[base+1]=((val)>>8)&0xff; \ 953 | buf[base]=(val)&0xff; \ 954 | }while(0) 955 | 956 | void comment_init(char **comments, int* length, char *vendor_string) 957 | { 958 | int vendor_length=strlen(vendor_string); 959 | int user_comment_list_length=0; 960 | int len=4+vendor_length+4; 961 | char *p=(char*)malloc(len); 962 | if(p==NULL){ 963 | fprintf (stderr, "malloc failed in comment_init()\n"); 964 | exit(1); 965 | } 966 | writeint(p, 0, vendor_length); 967 | memcpy(p+4, vendor_string, vendor_length); 968 | writeint(p, 4+vendor_length, user_comment_list_length); 969 | *length=len; 970 | *comments=p; 971 | } 972 | void comment_add(char **comments, int* length, char *tag, char *val) 973 | { 974 | char* p=*comments; 975 | int vendor_length=readint(p, 0); 976 | int user_comment_list_length=readint(p, 4+vendor_length); 977 | int tag_len=(tag?strlen(tag):0); 978 | int val_len=strlen(val); 979 | int len=(*length)+4+tag_len+val_len; 980 | 981 | p=(char*)realloc(p, len); 982 | if(p==NULL){ 983 | fprintf (stderr, "realloc failed in comment_add()\n"); 984 | exit(1); 985 | } 986 | 987 | writeint(p, *length, tag_len+val_len); /* length of comment */ 988 | if(tag) memcpy(p+*length+4, tag, tag_len); /* comment */ 989 | memcpy(p+*length+4+tag_len, val, val_len); /* comment */ 990 | writeint(p, 4+vendor_length, user_comment_list_length+1); 991 | 992 | *comments=p; 993 | *length=len; 994 | } 995 | #undef readint 996 | #undef writeint 997 | --------------------------------------------------------------------------------