Active Topics

 


Reply
Thread Tools
Posts: 3 | Thanked: 4 times | Joined on Dec 2007
#1
I did some searching and didn't find any good solutions, so I thought I'd post what I've managed to get working regarding viewing my network camera on my N800.

The camera is a SkyIPCam500 by Airlink. It has sound and nightvision, and I've got it set up as a baby cam. You access it through a webpage, and there's a Java or ActiveX viewer. Unfortunately, I can't use either of those on the N800. Even if I installed Debian for Java, the sound only works with the ActiveX control. I did some sniffing with Wireshark and found the URL's that ActiveX is calling. One is for a MJPEG stream. I can plug that into MicroB, but it only displays the first frame. However, I did get it working with mplayer, here is the command line I used:

Code:
mplayer -demuxer lavf http://admin:admin@xxx.xxx.xxx.xxx/cgi/mjpg/mjpeg.cgi?foo.mjpg
The camera requires a login and password, and admin are the defaults. Replace the x's with your camera's IP. You have to specify the lavf demuxer since it won't detect that it's a mjpeg by default. Also, the "?foo.mjpg" on the end isn't used by the camera, it just tricks mplayer into realizing the stream is an mjpeg. After I run that command line, I get a nice full screen window showing the cam. It's even playing it at 640x480 with no problems as far as I can tell. (Might be dropping a few frames, I can't tell, the cam outputs 30fps at that size). I'm very happy with the results.

I'm still having some issues with the audio though. The stream is raw PCM data, so I had some trouble figuring out the details. Thankfully, the ActiveX control has an option to record to an AVI file, and using that I figured out it was 8000Hz mono at 128K bitrate. This command line decodes that:

Code:
mplayer -rawaudio channels=1:rate=8000:bitrate=128 -demuxer rawaudio http://admin:admin@xxx.xxx.xxx.xxx/cgi/audio/audio.cgi
I can actually hear what the cam is recording, but there is popping over it about every half second or so. I'm not sure if there is some extra data in the stream that needs to be ignored or what. It's too bad because it's so close. The audio does seem to be behind a few seconds, but that's not a big deal with a baby monitor.

Anyway, just wanted to share what I've got and see if anyone has any suggestions. It will be very cool to just prop the N800 on my nightstand when I go to bed and be able to watch the baby (when it gets here in a few weeks). Even if I don't get the audio working I'm guessing I'll be able to hear the baby screaming without it.
 

The Following 4 Users Say Thank You to Spastic For This Useful Post:
Posts: 2,102 | Thanked: 1,309 times | Joined on Sep 2006
#2
I can actually hear what the cam is recording, but there is popping over it about every half second or so. I'm not sure if there is some extra data in the stream that needs to be ignored or what. It's too bad because it's so close. The audio does seem to be behind a few seconds, but that's not a big deal with a baby monitor.
Hmm, assuming it is some extra data in between (the audio's not sent in frames, with some http header info between is it?), I suppose you'd need to write your own demuxer to strip that info out.
 
Posts: 3 | Thanked: 4 times | Joined on Dec 2007
#3
I got some time to mess with this again today, and you're right lardman, it did have extra data in there. The video and audio streams are broken up by Content-Length headers. After I figured out the format of the header, I wrote a Python script to try and extract out the unnecessary data. Unfortunately, even after stripping out the header I was still getting popping. The beginning of the data after the header doesn't have anything resembling a WAV header, so I assume it's some custom control data the ActiveX program uses (sync data?). Anyway, as a hack I just stripped out extra bytes until the popping stopped. Not really the best solution, but the audio seems to sound right now.

Once I got that working I tried using the same code to extract the video data, to make things easier on the decoder. Unfortunately, it didn't work. For some reason mplayer thinks it ran out of data before it even gets any. Setting the cache size didn't seem to help. So, in the interest of getting something working I spawned off a process letting mplayer handle the video stream, while I piped the cleaned up audio data to another instance. It worked, but it's pretty rough. I tried running mplayer streaming the video feed overnight last week, and when I woke up in the morning it was desynched an not rendering anymore. That's why I was hoping to process the video data myself too, so I could catch that and restart it.

Anyway, at this point I'm not too confident that I'll get something solid working. I've got a Aspire One on order, and I may end up just using that so I can use the ActiveX crap. It's too bad these camera manufacturers don't settle on some standard format. Flash would be pretty sweet, because then I could play it on my Chumby.

I'm including my very rough code below, in case anyone else ends up trying to do something like this.

Code:
import urllib2
import subprocess

ServerAddr = '192.168.1.100'
UserName = 'admin'
Password = 'admin'

AudioUrl = 'http://' + ServerAddr + '/cgi/audio/audio.cgi'
VideoUrl = 'http://' + ServerAddr + '/cgi/mjpg/mjpeg.cgi'

VideoCmd = 'mplayer -demuxer lavf -'
AudioCmd = 'mplayer -rawaudio channels=1:rate=8000:bitrate=128 -demuxer rawaudio -'

# Passing the username and password with the URL doesn't seem to work
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, ServerName, UserName, Password)
authhandler = urllib2.HTTPBasicAuthHandler(passman)
opener = urllib2.build_opener(authhandler)
urllib2.install_opener(opener)

def playStream(url, cmdLine, strip=0):
    print 'Opening ' + url
    stream = urllib2.urlopen(url)

    goodData = True

    p = subprocess.Popen(cmdLine, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    while goodData:
        boundaryStr = stream.readline()
        if boundaryStr != '--myboundary\r\n':
            print 'Bad boundary: ' + boundaryStr
            break

        lengthStr = stream.readline()
        if not lengthStr.startswith('Content-Length: '):
            print 'Bad length: ' + lengthStr
            break

        length = int(lengthStr.strip().split()[1])

        emptyStr = stream.readline()

        print 'Good frame, length %d' % length
        rawData = stream.read(length)
        if len(rawData) == length:
            try:
                p.stdin.write(rawData[strip:])
            except IOError:
                break
        else:
            print 'Bad read length: %d' % len(rawData)
            break

    p.stdin.close()
    print p.stdout.read()
    p.stdout.close()
    p.wait()

videoCmd = 'mplayer -demuxer lavf http://%s:%s@%s/cgi/mjpg/mjpeg.cgi?foo.mjpg' % (UserName, Password, ServerAddr)
p = subprocess.Popen(videoCmd, shell=True)
#playStream(VideoUrl, VideoCmd)
playStream(AudioUrl, AudioCmd, 24)
 
Posts: 51 | Thanked: 0 times | Joined on Apr 2008 @ Ontario, Canada
#4
That's a great idea!

Originally Posted by Spastic View Post
It will be very cool to just prop the N800 on my nightstand when I go to bed and be able to watch the baby
I don't know how we survived, our parents not having all this essential tech! Actually, the audio is rather important, and you'll need to be able to turn it up. Just to check and make sure they are still breathing. They can lie so still sometimes, it's scary. But they're so adorable when they're sleeping!

(when it gets here in a few weeks).
Congrats in advance, and best wishes for a safe delivery.

Even if I don't get the audio working I'm guessing I'll be able to hear the baby screaming without it.
You better believe it. Which is you don't leave the volume turned up for long! ;-) Baby screams are tuned to the exact frequency - and those lovely harmonic over-tones - which will pierce their parents exhausted unconscious state, rattle your bone-weary nervous system and eject you from a dead-to-the-world sleep into wide-awake-panic-mode and completing a 50-yard dash in 1.5 seconds! (Not to mention prompting the neighbours to either turn you into the police for abuse or threaten to sue for keeping them awake too.)

Let me give you my standard advice.... get ALL the sleep you can get NOW. You have a 95% chance of getting about 2 hours a night... for about the next decade or so.
 
Posts: 16 | Thanked: 1 time | Joined on Apr 2008
#5
I just picked up a similar IP cam and tried using your mplayer commands and it seemed to work just as you described. The video looks great but the audio isn't working quite right. I can hear it but it's pretty choppy.

Did you ever figure out how to clean up your audio stream?

Also, sorry if this is a dumb question, but how can I use your code? I'm new to the n800 world so I'm not sure if that's just a script or if I need to compile it somehow.

Thanks!
 
Posts: 16 | Thanked: 1 time | Joined on Apr 2008
#6
Update:
VLC may work. This worked great from VLC on my pc.

rtsp://admin:admin@xxx.xxx.xxx.xxx/mpeg4

I thought I saw somewhere there was a VLC player for the n800. Will try that next.
 
Posts: 1,096 | Thanked: 760 times | Joined on Dec 2008
#7
dude, it might be cool for you to be ABLE to have that on your nightstand for your own personal hobby and fun but just a word of advice after going throught that with my first one(but not any others ever since)

a) babies scream, you can hear them from a ways away and your hearing gets a lot better as soon as you hear it scream the first time.

b) when a baby screams into a cheap microphone placed conveniently next to its head the effects on the receiving end can be deafening.

c) no matter what scheme ypu have cooked up for monitoring the baby with your gadgets, if it hiccups once it is ****. just get the exact baby monitor she wants, it doesn't matter if your solution does infrared at 30fps with stereo audio, it doesnt say monitor for babies on the package

d) sleep is so precious in that beginning time, why put a speaker on your nightstand.

good luck, ypu are gonna need it.
 
Posts: 3 | Thanked: 4 times | Joined on Dec 2007
#8
Hey, been a while since I checked this. Quipper, I'll have to disagree with you on always being able to hear the baby. He's in a room upstairs, and if either door is closed it can be hard to hear. I did end up buying a regular audio monitor like you suggested though. After my wireless crapped out a few times I figured I needed something more reliable. Thing is, the audio monitor isn't perfect either. Once a month or so it will be totally blown out by random static (ham radio operator or something). That's when it's nice to just use the webcam. Also, the video feed is nice if you hear something through the audio monitor and just wanna see what's up (usually he's eating his blanket).

I did actually improve the script a lot. I switched to pygame due to all my problems with mplayer. Unfortunately, on the N800 neither pygame or PIL have JPEG support compiled in. (PIL not having it is a big WTF.) So, my script won't work on the N800, I run it on my Aspire One. Also, even with perfectly decoded wave data pygame still has some issue playing the audio for more than a second or so (it's not really set up for streaming the way I'm trying to do it). Here's the latest code though, with misc crap scattered around, for anyone who wants to play with it.

Code:
import urllib2
import socket
import pygame
from pygame.locals import *
import cStringIO
import sys
import wave
import struct

ServerAddr = '192.168.1.13'
UserName = 'admin'
Password = 'admin'

AudioUrl = 'http://' + ServerAddr + '/cgi/audio/audio.cgi'
VideoUrl = 'http://' + ServerAddr + '/cgi/mjpg/mjpeg.cgi'

# Passing the username and password with the URL doesn't seem to work, so setup a password manager
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, ServerAddr, UserName, Password)
authhandler = urllib2.HTTPBasicAuthHandler(passman)
opener = urllib2.build_opener(authhandler)
urllib2.install_opener(opener)

class ScreenHelper:
    screen        = None
    fullScreen    = True
    windowedRes   = (640, 480)
    fullScreenRes = windowedRes
    curScreenRes  = windowedRes

    def __init__(self):
        pygame.display.set_caption('CamViewer')

        # If the current screen res is a fullscreen mode, set our fullscreen res to that
        info = pygame.display.Info()
        if (info.current_w, info.current_h) in pygame.display.list_modes():
            self.fullScreenRes = (info.current_w, info.current_h)

        self.toggleFullscreen()

    def toggleFullscreen(self):
        if self.fullScreen:
            self.screen = pygame.display.set_mode(self.windowedRes)
            self.curScreenRes = self.windowedRes
            self.fullScreen = False
        else:
            self.screen = pygame.display.set_mode(self.fullScreenRes, pygame.FULLSCREEN)
            self.curScreenRes = self.fullScreenRes
            self.fullScreen = True

    def blit(self, surface):
        offset = (0, 0)

        # If the surface doesn't match our res, scale it, maintaining aspect ratio
        if surface.get_size() != self.curScreenRes:
            widthRatio = float(self.curScreenRes[0]) / float(surface.get_width())
            heightRatio = float(self.curScreenRes[1]) / float(surface.get_height())

            scaledSize = None
            if widthRatio > heightRatio:
                scaledSize = (int(surface.get_width() * heightRatio), int(surface.get_height() * heightRatio))
                offset = ((self.curScreenRes[0] - scaledSize[0]) / 2, 0)
            else:
                scaledSize = (int(surface.get_width() * widthRatio), int(surface.get_height() * widthRatio))
                offset = (0, (self.curScreenRes[1] - scaledSize[1]) / 2)

            surface = pygame.transform.smoothscale(surface, scaledSize)

        # Blit the surface and update the display
        self.screen.blit(surface, offset)
        pygame.display.update()

# Reads a chunk of data from an Airlink camera http stream
class CamDataChunk:
    frame    = 0
    size     = 0
    tag1     = 0
    time     = 0
    unknown1 = 0
    tag2     = 0
    unknown2 = 0
    data     = ''
    
    def read(self, stream):
        try:
            boundaryStr = stream.readline().decode()
            if boundaryStr != '--myboundary\r\n':
                print('Bad boundary: ' + boundaryStr)
                return False

            lengthStr = stream.readline().decode()
            if not lengthStr.startswith('Content-Length: '):
                print('Bad length: ' + lengthStr)
                return False

            length = int(lengthStr.strip().split()[1])

            emptyStr = stream.readline()

            #print('Good frame, length %d' % length)

            dataLeft = length
            chunkData = ''

            while dataLeft > 0:
                data = stream.read(dataLeft)
                amtRead = len(data)
                dataLeft -= amtRead

                if amtRead == 0:
                    print('Ran out of data')
                    return False

                chunkData += data

            headerSize = 28

            # Header format:
            # int ~synched, +1 per frame
            # int data size? varies jpg, constant audio
            # int J000 for JPG, A000 for wav            
            # int ~synched, +1 per sec?
            # int ???
            # int constant (0x00990201 video, 0x12d20700 audio)
            # int constant (0 both)
            chunkHeader = struct.unpack('iiiiiii', chunkData[:headerSize])
            self.frame    = chunkHeader[0]
            self.size     = chunkHeader[1]
            self.tag1     = chunkHeader[2]
            self.time     = chunkHeader[3]
            self.unknown1 = chunkHeader[4]
            self.tag2     = chunkHeader[5]
            self.unknown2 = chunkHeader[6]
            self.data     = chunkData[headerSize:]

            return True
        except socket.error, (errno, strerror):
            print "Socket error(%s): %s" % (errno, strerror)
            return False
 
def play(screen):
    videoStream = urllib2.urlopen(VideoUrl)
    #audioStream = urllib2.urlopen(AudioUrl)

    videoChunk = CamDataChunk()
    #audioChunk = CamDataChunk()

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                return

            if event.type == pygame.KEYDOWN:
                if event.key == K_f:
                    screen.toggleFullscreen()
                elif event.key == K_ESCAPE:
                    return

        if videoChunk.read(videoStream) == False:
            print 'Bad video chunk'
            return
        #if audioChunk.read(audioStream) == False:
        #    print 'Bad audio chunk'
        #    return
        
        # Display the current frame
        frameSurface = pygame.image.load(cStringIO.StringIO(videoChunk.data)).convert()
        screen.blit(frameSurface)

        # Play the current audio sample
        #soundObj = pygame.mixer.Sound(waveFile.getvalue())
        #soundObj.play()

pygame.init()

screen = ScreenHelper()

play(screen)

pygame.display.quit()
 
Reply


 
Forum Jump


All times are GMT. The time now is 06:22.