News:

Registered a URL and setup a forum as the IPCam stuff really needed its own site vs my irregular blog posts about IPCam hacking at http://www.computersolutions.cn/blog

Author Topic: 2-way audio, sending data  (Read 5274 times)

July 12, 2013, 02:23:21 pm
Hey there,

I'm trying to send audio to my vstarcam f6836w, to let it play on an attached external speaker.
I captured some traffic with wireshark when playing around with the IE activex plugin.
Using the 'Bseries_VideoAudioAccessProtocol.pdf' I managed to decipher some of the packets, but either
my cam's protocol is slightly different or I'm misinterpreting the data, I'm kinda new to this...

So far I've manged to sent a login request, getting a response back, sending the actual login info and getting a valid response with 0 back indicating the login was successful. I then send a talk_start_request
(getting unspecified packages 0 and MO_O,28 back) followed by a talk_start_response (so i'm assuming everything is fine).
I then start sending talk messages(with data i captured previously)
but nothing but silence :(

Anyone here with some more experience in network traffic and serializing or better yet a working application that does this on linux? :D

I leave you with my messy code and the wireshark files as attachment.

Thank you for your help!
Code: [Select]
#!/usr/bin/env python
from struct import *
import binascii
import time
import socket
import logging

'''
Header          BINARY_STREAM[4]    s
Operation Code  INT16               h
Reserved        INT8                b
Reserved        BINARY_STREAM[8]    s
Text length     INT32               i
Reserved        INT32
Text            BINARY_STREAM[n]    s
'''

class Camera:
  STRUCTURE = '!4s b b 8s i i s'
  UNPACK = '!4s b b 6s i i'
  BUFFER_SIZE = 1024
 
  def __init__(self, ip, port):
    self.logger = logging.getLogger('Camera')
    self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.s.connect((ip, port))

  def __del__(self):
    self.s.close()
 
  def login(self, user_name, password):
    self._login_request()
    data = self.s.recv(Camera.BUFFER_SIZE)
    data = self._handle_login_response(data)
    if data[6] == 0:
      self.s.send(self._verify_request(user_name, password))
      data = self.s.recv(Camera.BUFFER_SIZE)
      data = self._handle_verify_response(data)
      if data[6] == 0:
        self.s.send(self._talk_start_request())
        self.handle()
      elif data[6] == 1:
         raise Exception('User error')
      elif data[6] == 5:
         raise Exception('Pwd error')
    else:
      raise Exception('Too many open connections!?')
   
  def handle(self):
    while True:
      data = self.s.recv(Camera.BUFFER_SIZE)
      data = self._handle_talk_start_response(data)
      if data[1] == 12:
        break;
      else:
        print 'Got wrong msg', data[0], data[1]
   
   
    dataConnectionId = data[-1]
    print dataConnectionId
   
    #self.s.send(self._login_request_video(dataConnectionId))
 
  def _login_request(self):
    '''
      Operation Code: 0
    '''
    data = pack(Camera.STRUCTURE, 'MO_O', 0, 0, '', 0, 0, '')
    self.s.send(data)
   
  def _handle_login_response(self, binary_string):
    '''
      Operation Code: 1
    Result: INT16                               (0: ok, 2: too many connections)
    Camera ID: BINARY_STREAM[13]                (Only exists when result == 0)
    Reserved: BINARY_STREAM[4]                  (Only exists when result == 0)
    Reserved: BINARY_STREAM[4]                  (Only exists when result == 0)
    Camera firmware version: BINARY_STREAM[4]   (Only exists when result == 0)
    '''
    format = Camera.UNPACK + 'h 13s 4s 4s 4s'
    data =  self._unpack_data(format, binary_string)
    return data
     
  def _verify_request(self, user, password):
    '''
      Operation Code: 2
    User: BINARY_STREAM[13]
    Password: BINARY_STREAM[13]
    '''
    data = pack('!4s b b 6s i i 11s 18s', 'MO_O', 2, 0, '', 26, 26, user, password)
    return data
   
  def _handle_verify_response(self, binary_string):
    '''
      Operation Code: 3
    result: INT16   (0: correct, 1: user error, 5:pwd error)
    Reserved: INT8  (Only exists when result == 0)
    '''
    format = Camera.UNPACK + 'h b'
    data = self._unpack_data(format, binary_string)
    return data
   
  def _talk_start_request(self):
    '''
      Operation Code: 11
    Camera audio playback buffer(seconds): INT8 (>= 1)
    '''
    print 'talk_start_request'
    data = pack('!4s b b 6s i i i', 'MO_O', 11, 0, '', 1, 1, 1)
    print '1', binascii.hexlify(data)
    hex_string = ''.join(r'4d:4f:5f:4f:0b:00:00:00:00:00:00:00:00:00:00:01:00:00:00:01:00:00:00:01'.split(':'))
    print '2', hex_string
    return data

  def _handle_talk_start_response(self, binary_string):
    '''
      Operation Code: 12
    result: INT16              (0: agree, 2: too many connections)
    Data connection ID: INT32  (Only exists when result == 0)
    '''
    format = Camera.UNPACK + 'h i'
    data = self._unpack_data(format, binary_string)
    return data   
   
  def _login_request_video(self, dataConnectionId):
    '''
      Header: MO_V
      Operation Code: 0
    Data connection ID: INT32 (connection drops when id incorrect)
    '''
    print 'login_request_video', dataConnectionId
    data = pack('!4s b b 6s i i i', 'MO_V', 0, 0, '', 4, 4, dataConnectionId)
    print '1', binascii.hexlify(data)
    hex_string = ''.join(r'4d:4f:5f:56:00:00:00:00:00:00:00:00:00:00:00:04:00:00:00:04:00:00:00:28:ae:0d:24'.split(':'))
    print '2', hex_string
    return data

  def talk(self, d):
    '''
      Header: MO_V
      Operation Code: 3
    Timestamp (1ms): INT32            (Can use GetTickCount())
    Package serial Number: INT32      (ascends from 0)
    Collection time (seconds): INT32  (Seconds since epoch)
    Audio format: INT8                (=0 adpcm)
    Data length: INT32                (=160)
    Data content: BINARY_STREAM[n]
    '''
    print 'talk'
    hex_string = ''.join(d.split(':'))
    data = binascii.unhexlify(hex_string)
    #s = unpack_from('!4s b b 6s I I'+'I I b I 160s', data)
    s = unpack_from('!4s b b 6s I I 177s', data)
    d = s[6]
    s = unpack_from('!b h 160s', d)
    print s
    import datetime
    print datetime.datetime.fromtimestamp(s[3])
    #self.s.send(data)
   
  def _unpack_data(self, format, binary_string):
    #print binascii.hexlify(binary_string)
    data = unpack_from(format, binary_string)
    self.logger.debug('hex data: %s', binascii.hexlify(binary_string))
    return data




if __name__ == '__main__':
  camera = Camera('192.168.1.32', 81)
  camera.login('\x00\x00\x00admin\x00\x00\x00\x00', 'i\x00m\x00a\x00\x00g\x00e\x00s\x00/\x00x\x004')

  raws = [
    r'4d:4f:5f:56:03:00:00:00:00:00:00:00:00:00:00:b1:00:00:00:b1:00:00:00:74:f4:d6:53:00:00:00:00:9f:11:df:51:00:a0:00:00:00:77:f5:0d:b2:61:2d:11:ab:91:c6:08:89:51:99:88:a8:48:28:08:a9:6b:d2:00:29:91:af:93:90:59:0a:11:82:c9:28:21:e1:08:1a:70:b4:0b:92:0e:02:90:88:01:3a:c1:a3:d5:2c:41:99:ab:a5:92:5c:a3:02:9a:0b:01:10:8e:23:c9:28:b0:27:29:99:b3:73:9e:88:0a:41:a8:88:22:c0:92:94:9e:80:28:97:89:10:b1:d8:48:38:aa:a9:62:82:b9:82:8a:84:2c:c3:59:b0:1a:a8:11:3f:b5:03:0b:d9:20:93:60:80:9e:01:11:ab:b1:85:01:9d:02:05:1a:9a:1a:32:c5:b8:0d:83:08:83:3f:90:08:26:99:83:94:a8:3c:92:f9:11:98:19:b5:30:9d:01:a2:29:0a:17',
    r'4d:4f:5f:56:03:00:00:00:00:00:00:00:00:00:00:b1:00:00:00:b1:00:00:00:a3:f4:d6:53:01:00:00:00:9f:11:df:51:00:a0:00:00:00:8a:3d:82:10:b2:29:bf:00:c1:95:30:1c:08:98:c0:11:1a:f1:88:31:99:71:0a:80:02:a8:d1:1d:86:99:12:8a:2a:e2:19:88:b3:79:19:b0:2a:81:01:0b:c3:08:31:88:67:10:9b:0b:91:83:2f:f1:1c:11:90:92:39:9a:84:13:0d:48:09:02:ad:b0:18:a7:2c:91:18:80:89:19:a6:8c:18:23:0f:01:12:c0:16:19:aa:02:2f:c2:0b:23:a1:c8:85:2b:0b:22:21:99:cc:11:9c:25:85:10:bc:18:91:b1:12:6b:a0:20:a8:e3:18:99:70:99:b0:18:4f:91:95:00:10:91:1c:88:98:68:80:ba:b3:5b:a4:20:2b:e0:32:b9:0b:c1:17:3a:8a:88:21:90:f0:98:9b:75:91:0a:19:01',
  r'4d:4f:5f:56:03:00:00:00:00:00:00:00:00:00:00:b1:00:00:00:b1:00:00:00:c2:f4:d6:53:02:00:00:00:9f:11:df:51:00:a0:00:00:00:a3:1f:b8:49:08:81:19:12:2a:c0:33:bc:d9:12:d1:18:18:70:09:02:9c:29:20:b2:dd:16:00:8a:b8:22:3c:90:ba:60:d0:2c:48:01:81:b8:81:e8:10:19:70:93:89:bd:33:84:ca:98:38:00:94:0f:01:0b:09:84:89:20:72:9c:a0:83:2f:20:a3:81:ac:82:a8:18:03:04:b9:78:5a:b1:8b:a8:05:8a:99:70:80:12:8b:b2:6c:a3:ab:11:43:8c:00:8d:31:80:f9:03:59:10:98:a3:ac:21:0f:00:a3:01:20:80:81:9f:b9:51:92:3b:b1:f1:69:18:90:a3:9f:92:10:88:b2:5b:b3:1a:5b:33:f0:3a:18:08:01:8b:21:5b:d9:00:95:94:0f:82:81:2b:a0:8b:23:ae:81:a3:22:27',
  ]
  for d in raws:
    camera.talk(d)


July 17, 2013, 02:48:19 pm
Well success  ;D
Feed it a PCM S16 LE (araw), channels:mono, sample rate: 8kHZ, bits per sample: 16

Code: [Select]
#!/usr/bin/env python
from struct import *
import binascii
import time
import socket
import logging
import datetime

'''
Header          BINARY_STREAM[4]    s
Operation Code  INT16               h
Reserved        INT8                b
Reserved        BINARY_STREAM[8]    s
Text length     INT32               i
Reserved        INT32
Text            BINARY_STREAM[n]    s
'''

class Camera:
  STRUCTURE = '<4s h b 8s I I'
  UNPACK = '<4s h b 8s I I'
  BUFFER_SIZE = 2048
 
  UNPACK_LENGTH = 6
 
  def __init__(self, ip, port):
    self.logger = logging.getLogger('Camera')
    self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.s.connect((ip, port))
   
    self._ip = ip
    self._port = port

  def __del__(self):
    self.s.close()
 
  def login(self, user_name, password):
    self._login_request()
    data = self.s.recv(Camera.BUFFER_SIZE)
    data = self._handle_login_response(data)
    if data[6] == 0:
      self.s.send(self._verify_request(user_name, password))
      data = self.s.recv(Camera.BUFFER_SIZE)
      data = self._handle_verify_response(data)
      if data[6] == 0:
        self.s.send(self._talk_start_request())
        return self.handle()
      elif data[6] == 1:
         raise Exception('User error')
      elif data[6] == 5:
         raise Exception('Pwd error')
    else:
      raise Exception('Too many open connections!?')
   
  def handle(self):
    while True:
      binary_string = self.s.recv(Camera.BUFFER_SIZE)
      data = unpack_from(Camera.UNPACK, binary_string)
      if data[1] == 12:
        return self._handle_talk_start_response(binary_string)
      else:
        print 'Got wrong msg', data[0], data[1]
 
  def _login_request(self):
    '''
      Operation Code: 0
    '''
    data = pack(Camera.STRUCTURE, 'MO_O', 0, 0, '', 0, 0)
    self.s.send(data)
   
  def _handle_login_response(self, binary_string):
    '''
      Operation Code: 1
    Result: INT16                               (0: ok, 2: too many connections)
    Camera ID: BINARY_STREAM[13]                (Only exists when result == 0)
    Reserved: BINARY_STREAM[4]                  (Only exists when result == 0)
    Reserved: BINARY_STREAM[4]                  (Only exists when result == 0)
    Camera firmware version: BINARY_STREAM[4]   (Only exists when result == 0)
    '''
    format = Camera.UNPACK + 'h 13s 4s 4s 4s'
    data =  self._unpack_data(format, binary_string)
    return data
     
  def _verify_request(self, user, password):
    '''
      Operation Code: 2
    User: BINARY_STREAM[13]
    Password: BINARY_STREAM[13]
    '''
    data = pack('<4s h b 8s I I 13s 13s', 'MO_O', 2, 0, '', 26, 26, user, password)
    return data
   
  def _handle_verify_response(self, binary_string):
    '''
      Operation Code: 3
    result: INT16   (0: correct, 1: user error, 5:pwd error)
    Reserved: INT8  (Only exists when result == 0)
    '''
    format = Camera.UNPACK + 'h b'
    data = self._unpack_data(format, binary_string)
    return data
   
  def _talk_start_request(self):
    '''
      Operation Code: 11
    Camera audio playback buffer(seconds): INT8 (>= 1)
    '''
    data = pack('<4s h b 8s I I b', 'MO_O', 11, 0, '', 1, 1, 1)
    return data

  def _handle_talk_start_response(self, binary_string):
    '''
      Operation Code: 12
    result: INT16              (0: agree, 2: too many connections)
    Data connection ID: INT32  (Only exists when result == 0)
    '''
    format = Camera.UNPACK + 'h I'
    data = self._unpack_data(format, binary_string)
    return data[Camera.UNPACK_LENGTH]==0, data[Camera.UNPACK_LENGTH+1]   
   
  def _login_request_data(self, dataConnectionId):
    '''
      Header: MO_V
      Operation Code: 0
    Data connection ID: INT32 (connection drops when id incorrect)
    '''
    data = pack('<4s h b 8s I I I', 'MO_V', 0, 0, '', 4, 4, dataConnectionId)
    return data

  def create_talk_data(self, serial, adpcm_data):
    '''
      Header: MO_V
      Operation Code: 3
    Timestamp (1ms): INT32            (Can use GetTickCount())
    Package serial Number: INT32      (ascends from 0)
    Collection time (seconds): INT32  (Seconds since epoch)
    Audio format: INT8                (=0 adpcm)
    Data length: INT32                (=160)
    Data content: BINARY_STREAM[n]
    '''
    d = [None]*12
    d[0] = 'MO_V'
    d[1] = 3
    d[2] = 0
    d[3] = ''
    d[4] = 177
    d[5] = 177
    d[6] = (serial*40)
    d[7] = serial
    d[8] = int(time.mktime(datetime.datetime.now().timetuple()))
    d[9] = 0
    d[10] = 160
    d[11] = adpcm_data
    data = pack('<4s h b 8s I I I I I b I 160s', *d)
    return data
   
  def _unpack_data(self, format, binary_string):
    self.logger.debug('hex data: %s', binascii.hexlify(binary_string))
    data = unpack(format, binary_string)
    return data


  def create_data_connection(self, dataConnectionId):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((self._ip, self._port))

    s.send(self._login_request_data(dataConnectionId))
    return s
 
  def  send_wav(self, dataConnectionId, fileName):
    import threading
    thread = threading.Thread(target=self._send_wav, args=(dataConnectionId, fileName))
    thread.start()
    return thread
   
  def _send_wav(self, dataConnectionId, fileName):
    s = self.create_data_connection(dataConnectionId)
   
    import wave
    import audioop
    #ffmpeg -i Turret_turret_active_8.wav -ar 8k Turret_turret_active_8_8000.wav
    waveFile = wave.open(fileName, 'rb')

    length = waveFile.getnframes()
    state = None
    state_ratecv=None
    serial = 0
    bytes = []
    for i in range(0,length):
        waveData = waveFile.readframes(2)
        adpcmfrag, state = audioop.lin2adpcm(waveData, 2, state)
        bytes.append(adpcmfrag)
        if len(bytes) == 160:
          #print bytes
          time.sleep(0.02)  # 20ms per fragment?

          data = camera.create_talk_data(serial, ''.join(bytes))
          serial += 1
          bytes = []

          s.send(data)

    if len(bytes):
      data = camera.create_talk_data(serial, ''.join(bytes))
      s.send(data)
     
    waveFile.close()
    time.sleep(1) #Wait for the audio to finish

if __name__ == '__main__':
  camera = Camera('192.168.1.32', 81)
  success, dataConnectionId = camera.login('admin\x00\x00\x00i\x00m\x00a', '\x00\x00g\x00e\x00s\x00/\x00x\x004')
  print 'success, dataConnectionId', success, dataConnectionId
 
  handle = camera.send_wav(dataConnectionId, 'Turret_turret_active_8_8000.wav')
  handle.join()