Friday, November 29, 2013

Pulling H264 video from an IP camera using Python

IP cameras have come a long ways, and recently I upgraded some old cameras to these new Lorex cameras (model LNB2151/LNB2153) and I'm very impressed.

These cameras record 1080p wide-angle video at 30 frames per second, use power over ethernet (PoE), can see when it's dark using builtin infrared LEDs and are weather-proof. The video quality is impressive and they are surprisingly inexpensive. The camera can deliver two streams at once, so you can pull a lower resolution stream for preview, motion detection, etc., and simultaneously pull the higher resolution stream to simply record it for later scrutinizing.

After buying a few of these cameras I needed a simple way to pull the raw H264 video from them, and with some digging I discovered the cameras speak RTSP and RTP which are standard protocols for streaming video and audio from IP cameras. Many IP cameras have adopted these standards.

Both VLC and MPlayer can play RTSP/RTP video streams; for the Lorex cameras the default URL is:

  rtsp://admin:000000@<hostname>/PSIA/Streaming/channels/1.

After more digging I found the nice open-source (LGPL license) Live555 project, which is a C++ library for all sorts of media related protocols, including RTSP, RTP and RTCP. VLC and MPlayer use this library for their RTSP support. Perfect!

My C++ is a bit rusty, and I really don't understand all of Live555's numerous APIs, but I managed to cobble together a simple Python extension module, derived from Live555's testRTSPClient.cpp example, that seems to work well.

I've posted my current source code in a new Google code project named pylive555. It provides a very simple API (only 3 functions!) to pull frames from a remote camera via RTSP/RTP; Live555 has many, many other APIs that I haven't exposed.

The code is thread-friendly (releases the global interpreter lock when invoking the Live555 APIs).

I've included a simple example.py Python program, that shows how to load H264 video frames from the camera and save them to a local file. You could start from this example and modify it to do other things, for example use the ffmpeg H264 codec to decode individual frames, use a motion detection library to trigger recording, parse each frame's metadata to find the keyframes, etc. Here's the current example.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import time
import sys
import live555
import threading

# Shows how to use live555 module to pull frames from an RTSP/RTP
# source.  Run this (likely first customizing the URL below:

# Example: python3 example.py 10.17.4.118 1 10 out.264 
if len(sys.argv) != 5:
  print()
  print('Usage: python3 example.py cameraIP channel seconds fileOut')
  print()
  sys.exit(1)
  
cameraIP = sys.argv[1]
channel = sys.argv[2]
seconds = float(sys.argv[3])
fileOut = sys.argv[4]

# NOTE: the username & password, and the URL path, will vary from one
# camera to another!  This URL path works with the Lorex LNB2153:
url = 'rtsp://admin:000000@%s/PSIA/Streaming/channels/%s' % (cameraIP, channel)

fOut = open(fileOut, 'wb')

def oneFrame(codecName, bytes, sec, usec, durUSec):
  print('frame for %s: %d bytes' % (codecName, len(bytes)))
  fOut.write(b'\0\0\0\1' + bytes)

# Starts pulling frames from the URL, with the provided callback:
useTCP = False
live555.startRTSP(url, oneFrame, useTCP)

# Run Live555's event loop in a background thread:
t = threading.Thread(target=live555.runEventLoop, args=())
t.setDaemon(True)
t.start()

endTime = time.time() + seconds
while time.time() < endTime:
  time.sleep(0.1)

# Tell Live555's event loop to stop:
live555.stopEventLoop()

# Wait for the background thread to finish:
t.join()


Installation is very easy; see the README.txt. I've only tested on Linux with Python3.2 and with the Lorex LNB2151 cameras.

I'm planning on installing one of these Lorex cameras inside a bat house that I'll build with the kids this winter. If we're lucky we'll be able to view live bats in the summer!

35 comments:

  1. This is awesome. Great job. I have been using live555 with ctypes in Python. I like what you did, it's a better solution.

    ReplyDelete
  2. This is one of the best security camera. I am also using ip camera.

    ReplyDelete
  3. I downloaded the live555 CPP library, then installed it and everything worked fine. Then I downloaded your python library, put the files inside my "live" folder, and when I run the "python3 setup.py build" it says that the file "liveMedia.hh" does not exist (even though it does) and then gives me an error. How can I fix that?

    Thanks for your help in advanced!

    ReplyDelete
  4. well, I guess this error when I try to compile it with python3:

    module.cpp:22:20: fatal error: Python.h: No such file or directory

    ReplyDelete
  5. You hit that compilation error when running "python3 setup.py build"? That's strange: setup.py takes care of setting up the flags for the compiler. What compile lines are actually executed? Where is Python.h on your system?

    Maybe you don't have the python-dev package installed?

    ReplyDelete
  6. I still can't fix it even though I have python-dev already. I don't know if it might be a compatibility issue between pylive555 and the newest version of live555 ( live.2014.02.04.tar.gz - 04-Feb-2014 <--- date)?
    Maybe you can email me your live555 version?

    Also, let me make sure from the instructions:
    Download pylive555, then download live555 and put the folder "live" inside the pylive555 folder? Or is the other way around?

    Thank you!

    ReplyDelete
    Replies
    1. Hi Luis,

      Can you post the compile lines that setup.py is running? Somehow it's not finding your Python.h header.

      You had it right the first time: download/checkout pylive555, then download live555 and unpack it into the "live" sub-directory.

      My version of live555 is from 11/28/2013, but I don't think that's your problem (yet). You need to solve the missing Python.h header first.

      Delete
  7. Hi Michael,

    Really looking forward to trying your code out but......

    I seem to be having the same problem with not finding python.h

    root@raspbx:~/live# python3 setup.py build
    running build
    running build_ext
    building 'live555' extension
    creating build
    creating build/temp.linux-armv6l-3.2
    gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -D_FORTIFY_SOURCE =2 -g -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-securi ty -fPIC -I./live/liveMedia/include -I./live/BasicUsageEnvironment/include -I./l ive/UsageEnvironment/include -I./live/groupsock/include -I/usr/include/python3.2 mu -c module.cpp -o build/temp.linux-armv6l-3.2/module.o
    cc1plus: warning: command line option ‘-Wstrict-prototypes’ is valid for Ada/C/O bjC but not for C++ [enabled by default]
    module.cpp:22:20: fatal error: Python.h: No such file or directory
    compilation terminated.
    error: command 'gcc' failed with exit status 1

    Hope you can help.

    Thanks Gerry


    ReplyDelete
    Replies
    1. Hi Gerry,

      I think you need to install the python-dev package on your box?

      Delete
  8. Michael,

    I did install python-dev but got same error. I think I have been playing around with some many different applications, it's best I do a clean install.

    Get back with my results later

    ReplyDelete
  9. Hi Michael,

    I think I have misread your instructions somewhere as now I have a bigger problem.

    My directory structure is as follows:
    home/pi/pylive555 (here are readme.txt, setup.py, module.cpp, example.py and live555-latest.tar.gz)
    including the other directories:- live, build and git

    I started from
    /pylive555/live $ ./genMakefiles linux
    /pylive555/live $ export CPPFLAGS=-fPIC CFLAGS=-fPIC
    /pylive555/live $ make install
    After many lines.....
    I got these errors
    install -d /usr/local/include/liveMedia /usr/local/lib
    install: cannot change permissions of ' /usr/local(include/liveMedia' : No such file or directory
    install: cannot change permissions of ' /usr/local/lib' : Operation not permitted
    make[1] : *** [install1] Error 1
    make[1] : Leaving directory ' /home/pi/pylive555/live/liveMedia'
    make: *** [install] Error 2
    back at /pylive555/live

    Not sure where to go from here.

    /Gerry

    ReplyDelete
    Replies
    1. You should do "sudo make install", ie that step must be done as root. See if that works?

      Delete
  10. If I run python3 example.py, I get in traceback ImportError: No module named live555

    ReplyDelete
  11. Hi Michael,
    Finally got it installed without any errors. Checked my RTSP string using VLC to my Grandstream camera before changing example.py. Get the video file created but unfortunately cannot open it with VLC.
    Below is a copy of 1st line from video file:
    tns1:AudioEncoder

    Not sure what I have done wrong.

    /Gerry

    ReplyDelete
    Replies
    1. Hmm, I'm able to play my file using mplayer. When I try to use VNC, it does have trouble, unless I force it to open a raw H264 file ... these instructions worked for me: https://forum.videolan.org/viewtopic.php?f=14&t=12530

      Try that?

      Delete
  12. sorry but this blog has removed my copied text from video file

    ReplyDelete
  13. I used mplayer and managed to play the files, unfortunately footage is nearly all green with some form of image in the background. At least now I know I am getting stream from the camera and will play a little more with the RTSP string as there are a number available for the Grandstream cameras.

    ReplyDelete
  14. Could this be adapted to run on a Raspberry Pi and Foscam cameras?

    ReplyDelete
    Replies
    1. Hi Gilson,

      I don't have any experience with Raspberry Pi nor Foscam, so I'm really not sure. This is only useful if the Foscam speaks RTSP/RTP, which I believe is more and more common in IP cameras these days. But I think the more typically approach with the Pi is to pull frames directly from an attached camera (not via an ethernet connection)? I.e.: http://www.raspberrypi.org/camera

      Delete
  15. Hi Michael,

    I understand that script produces a binary file with H264 frames...
    Is it possible to create some proper container like AVI or MP4 or MKV so the file is playable with media player?

    ReplyDelete
    Replies
    1. Hi Anton,

      This is definitely possible; I think there are python bindings around ffmpeg so you could go that route. In my usage, I just spawn a sub-process and invoke ffmpeg from the command line, something like:

      ffmpeg -i - -vcodec copy -f mp4 video.mp4

      Then I send the bytes to that pipe, close it, and video.mp4 holds the H264 in an MP4 container.

      Delete
  16. Hi Michael,

    I can't solve this problem in raspberry pi env..
    please comment..

    pi@raspberrypi ~/pylive555 $ python3 example.py 192.168.0.200 1 10 out.264
    Traceback (most recent call last):
    File "example.py", line 3, in
    import live555
    ImportError: /usr/local/lib/python3.2/dist-packages/live555.cpython-32mu.so: undefined symbol: _ZN18BasicTaskScheduler9createNewEj
    pi@raspberrypi ~/pylive555 $


    ReplyDelete
    Replies
    1. Something went wrong when you build the live555.cpython-32mu.so, because this symbol is defined in libBasicUsageEnvironment.a, which is one of the libraries we link against (in setup.py).

      When I run "nm live555.cpython-32mu.so" in my build I can see that symbol is defined.

      Delete
    2. Thanks for your quick reply..

      I run 'nm' command but that simbol is 'undefinded'

      pi@raspberrypi /usr/local/lib/python3.2/dist-packages $ nm live555.cpython-32mu.so | grep _ZN18BasicTaskScheduler9createNewEj
      U _ZN18BasicTaskScheduler9createNewEj

      what can i do?

      Delete
    3. Can you remove your "build" subdirectory, run python3 build.py build, and post the commands here? Somehow it's not linking properly against the live555 libs.

      Delete
    4. pi@raspberrypi ~ $ cd pylive555
      pi@raspberrypi ~/pylive555 $ ls
      build example.py live module.cpp README.txt setup.py
      pi@raspberrypi ~/pylive555 $ sudo rm -rf build
      pi@raspberrypi ~/pylive555 $ ls
      example.py live module.cpp README.txt setup.py
      pi@raspberrypi ~/pylive555 $ python3 setup.py build
      running build
      running build_ext
      building 'live555' extension
      creating build
      creating build/temp.linux-armv6l-3.2
      gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -D_FORTIFY_SOURCE=2 -g -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fPIC -I./live/liveMedia/include -I./live/BasicUsageEnvironment/include -I./live/UsageEnvironment/include -I./live/groupsock/include -I/usr/include/python3.2mu -c module.cpp -o build/temp.linux-armv6l-3.2/module.o
      cc1plus: warning: command line option ‘-Wstrict-prototypes’ is valid for Ada/C/ObjC but not for C++ [enabled by default]
      creating build/lib.linux-armv6l-3.2
      g++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro build/temp.linux-armv6l-3.2/module.o -L./live/liveMedia -L./live/UsageEnvironment -L./live/groupsock -lliveMedia -lgroupsock -lBasicUsageEnvironment -lUsageEnvironment -o build/lib.linux-armv6l-3.2/live555.cpython-32mu.so
      pi@raspberrypi ~/pylive555 $

      Delete
    5. That looks correct; I can see in the last line that it's linking against BasicUsageEnvironment. Can you confirm you have a libBasicUsageEnvironment.a, and that it does in fact define that symbol?

      Delete
    6. ok, I run this..

      pi@raspberrypi ~ $ sudo find / -name 'libBasicUsageEnvironment.a'
      /home/pi/pylive555/live/BasicUsageEnvironment/libBasicUsageEnvironment.a
      /usr/local/lib/libBasicUsageEnvironment.a
      /usr/lib/libBasicUsageEnvironment.a

      and then open editor and search '_ZN18BasicTaskScheduler9createNewEj'.
      capture that result screen.

      pi@raspberrypi ~ $ sudo nano /home/pi/pylive555/live/BasicUsageEnvironment/libBasicUsageEnvironment.a
      http://screencast.com/t/Sj1jVnVWqJA
      http://screencast.com/t/WWGf5pG1g3fW

      pi@raspberrypi ~ $ sudo nano /usr/local/lib/libBasicUsageEnvironment.a
      http://screencast.com/t/DTwq0OvM
      http://screencast.com/t/3hjnbOlPJEBu

      pi@raspberrypi ~ $ sudo nano /usr/lib/libBasicUsageEnvironment.a
      http://screencast.com/t/qAC61Sd5

      Delete
    7. so I rename '/usr/lib/libBasicUsageEnvironment.a' as /usr/lib/libBasicUsageEnvironment.a.bak.
      and then I run build command.
      Now example.py runs successfully!

      Thank you for your kind and quick response

      Delete
    8. Ahh, so you had an older libBasicUsageEnvironment.a in your path? Phew, thanks for bringing closure :) I'm glad you solved it.

      Delete
    9. Adding this comment since Blogger's spam filters seem to have removed it:

      Hi Michael

      I have another problem.

      My ip camera is ATTN ops-bip720p like this (http://attn.ph/shop/step1.php?number=54)
      How can I find camera's rtsp url like example code (rtsp://admin:ecofence@%s/PSIA/Streaming/channels/%s)

      Buyer don't know about this and I can't find manufacturer ATTN web site or Technical Information.

      my question is that
      Where do you find rtsp url path of your camera Lorex LNB2153?

      Is it possible to get only from manufacturer?

      Thanks

      Delete
    10. I don't know much about RTSP; perhaps the URL path that the camera must accept is part of the standard?

      In my case, after some Googling, I found documents that listed these paths for my camera ...

      Delete
  17. Dear friends, may I ask for help regarding this topic? I tried to run the example and it does something but after all the result file is 0byte sized... Do you think where could be the problem? The output of the script is below.

    root@pb4540s:/home/karl/Downloads# python3 example.py 88.101.39.191 1 10 test12.avi
    [URL:"rtsp://admin:admin@88.101.39.191/cam/realmonitor?channel=1&subtype=0/"]: Initiated the "video/H264" subsession (client ports 49430-49431)
    [URL:"rtsp://admin:admin@88.101.39.191/cam/realmonitor?channel=1&subtype=0/"]: Set up the "video/H264" subsession (client ports 49430-49431)
    [URL:"rtsp://admin:admin@88.101.39.191/cam/realmonitor?channel=1&subtype=0/"]: Created a data sink for the "video/H264" subsession
    [URL:"rtsp://admin:admin@88.101.39.191/cam/realmonitor?channel=1&subtype=0/"]: Started playing session...

    ReplyDelete
  18. Will this work with Lorex HR118000? the cameras don't have an IP just the DRV. Running Linux Mint.

    ReplyDelete
  19. Sorry, I don't know anything about the Lorex HR118000.

    ReplyDelete