Stream Video from the Raspberry Pi Camera to Web Browsers, Even on iOS and Android

Posted by
on under

I've been excited about the Raspberry Pi Camera Module since it was announced last year, so I went and ordered one from Element14 as soon as it came on sale.

I have a few ideas for cool things to build with this camera and I will be blogging about them as I get to develop them. Today, I will show you how to transform the Raspberry Pi into a webcam server. You will be able to watch the video stream from the camera on any device that has a web browser. And yes, this includes the iPad/iPhone and Android devices!

The official streaming method

The introductory article about the camera module in the Raspberry Pi blog shows a method to stream video from the Raspberry Pi to another computer. This method essentially works as follows:

  • On the Pi the raspivid utility is used to encode H.264 video from the camera
  • The video stream is piped to the nc utility, which pushes it out to the network address where the video player is.
  • On the player computer nc receives the stream and pipes it into mplayer to play.

This is an efficient method of streaming video from the Pi to another computer, but it has a few problems:

  • The Raspberry Pi needs to know the address of the computer that is playing the video
  • The playing computer needs to have an advanced player that can play a raw H.264 video stream. No mobile device that I know can do this, for example.
  • Since this system relies on a direct connection between the Pi and the player, it is impossible to have the player computer connect and/or disconnect from the stream, the connection needs to be on at all times.
  • What if there are two or three concurrent players? Things get awfully complicated for the Pi.

This ad hoc solution that the Raspberry Pi Camera team proposes isn't that useful to me, so I went to search for better options.

Streaming protocols

I think an important requirement for a streaming camera is that you can view it with ease. To me, this means that the stream should be playable from a web browser. Having to run a custom player is a complication, and puts it out of reach of most mobile devices.

There are a few modern streaming protocols for web browsers out there. For example, HLS is Apple's choice, so it has great support on iDevices but not much elsewhere. Another one, called Fragmented MP4 is supported by Adobe and Microsoft, but requires browser plugins from these companies on the player computer, so Windows and Mac computers can do it, but Linux and mobile cannot. HTML5 video is also based on the MP4 format but support is not that great.

Besides, for all the streaming protocols listed above there is a need to have a streaming server that prepares the video for streaming by segmenting it and packaging it, and while there are several open source utilities that can do this for a static video stream, I haven't found any that can do it on a live stream. Note: I have been corrected on this statement, more recent releases of ffmpeg than the binary available for Raspbian can generate an HLS live stream.

So what other options are there?

Motion JPEG to the rescue

I then investigated how IP webcams do it, and a lot of them use an older streaming protocol called Motion JPEG or MJPEG.

What is Motion JPEG? Pretty simple, it's just a stream of individual JPEG pictures, one after another. I was surprised to find that most modern browsers can play MJPEG streams natively.

The down side of MJPEG streams is that they are not as efficient as H.264, which greatly improves quality and reduces size by encoding only the differences from one frame to the next. With MJPEG each frame is encoded as an entire JPEG picture. For my needs this isn't a concern, though.

Continuing with my research I stumbled upon MJPG-streamer, a small open source MJPEG streaming server written in C that I was easily able to compile for the Raspberry Pi.

The following sections describe how I've used this tool to create a very flexible, play anywhere, streaming server for my Raspberry Pi camera.

Installing MJPEG-streamer

UPDATE: This section is outdated. Please use the instructions on my updated guide to build and install MJPG-Streamer.

Unfortunately there isn't a package for MJPEG-streamer that can be installed with apt-get, so it needs to be compiled from source.

MJPEG-streamer is hosted at sourceforge.net, so head over to the project's download page to get the source tarball.

To compile this application I used the following commands:

$ sudo apt-get install libjpeg8-dev
$ sudo apt-get install imagemagick
$ tar xvzf mjpg-streamer-r63.tar.gz
$ cd mjpg-streamer-r63
$ make

This tool requires libjpeg and the convert tool from ImageMagick, so I had to install those as well.

The makefile does not include an installer, if you want to have this utility properly installed you will need to copy the mjpg_streamer and its plugins input_*.so and output_*.so to a directory that is in the path, like /usr/local/bin. It is also possible to run this tool directly from the build directory.

Setting up the JPEG source stream

The streaming server needs a sequence of JPEG files to stream, and for this we are going to use the raspistill utility that is part of Raspbian. For those that are concerned about performance, keep in mind that the JPEG encoder used by raspistill runs in the GPU, the load required to generate JPEGs is pretty small.

To setup a constant stream of JPEG images the command is as follows:

$ mkdir /tmp/stream
$ raspistill -w 640 -h 480 -q 5 -o /tmp/stream/pic.jpg -tl 100 -t 9999999 -th 0:0:0 &

Let's go over the arguments to raspistill one by one:

  • -w sets the image width. For an HD stream use 1920 here.
  • -h sets the image height. For an HD stream use 1080 here.
  • -q sets the JPEG quality level, from 0 to 100. I use a pretty low quality, better quality generates bigger pictures, which reduces the frame rate.
  • -o sets the output filename for the JPEG pictures. I'm sending them to a temp directory. The same file will be rewritten with updated pictures.
  • -tl sets the timelapse interval, in milliseconds. With a value of 100 you get 10 frames per second.
  • -t sets the time the program will run. I put a large number here, that amounts to about two hours of run time.
  • -th sets the thumbnail picture options. Since I want the pictures to be as small as possible I disabled the thumbnails by setting everything to zero.
  • & puts the application to run in the background.

Starting the streaming server

Okay, so now we have a background task that is writing JPEGs from the camera at a rate of ten per second. All that is left is to start the streaming server. Assuming you are running it from the build directory the command is as follows:

$ LD_LIBRARY_PATH=./ ./mjpg_streamer -i "input_file.so -f /tmp/stream -n pic.jpg" -o "output_http.so -w ./www"

Let's break this command down to understand it:

  • LD_LIBRARY_PATH sets the path for dynamic link libraries to the current directory. This is so that the application can find the plugins, which are in the same directory.
  • -i sets the input plugin. We are using a plugin called input_file.so. This plugin watches a directory and any time it detects a JPEG file was written to it it streams that file. The folder and file to watch are given as the -f and -n arguments.
  • -o sets the output plugin. We are using the HTTP streaming plugin, which starts a web server that we can connect to to watch the video. The root directory of the web server is given as the -w argument. We will use the default web pages that come with the application for now, these can be changed and customized as necessary.

Watching the stream

Now everything is ready. Go to any device that has a web browser and connect to the following website:

http://<IP-address>:8080

Where IP-address is the IP address or hostname of your Raspberry Pi.

The default website served by the streaming server provides access to several players to watch the stream. I've found that the "Stream" option worked on most devices I tried. For a few that "Stream" didn't show video I went to "Javascript" and I was able to play the video just fine.

I tested playback of the stream from an iPad, an Android smartphone and a variety of web browsers on Windows and OS X, and I was able to play the stream in all of them.

I hope you find this method useful. Let me know in the comments below if you have a different method of streaming.

Miguel

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

141 comments
  • #76 Jorge said

    Hello Miguel,
    thanks for the tutorial, but i'm having a problem.

    When I start the server I got the next message:

    bash: ./mjpg-streamer is a directory

    And when I tried to to watch the streaming I got the error: server not found

  • #77 Miguel Grinberg said

    @Jorge: there is an updated article with more detailed instructions: http://blog.miguelgrinberg.com/post/how-to-build-and-run-mjpg-streamer-on-the-raspberry-pi

  • #78 Julian said

    I have been looking for an eclectic blend between this and ZoneMinder. I'd prefer something that didnt tag the drive with files so much.

  • #79 Chris Hinshaw said

    A little word of advice is to create a mem disk. I saw an improvement in quality and lag my creating a 2MB memdisk in temp.

    sudo mount -t tmpfs -o size=2M tmpfs /tmp/stream

    This keeps from having to write to the flash atleast on my version of raspian. I noticed a performance improvement when doing this.

  • #80 Joe said

    Hi Miguel,
    Thanks for putting this info together. I have a few problems I can't get my head around as a linux novice.

    The streamer seems to crash after running for several hours, e.g. overnight. Re-booting the pi fixes the problem but I would like to get a handle on why it is crashing.

    I put both the raspistill and the mjpeg-streamer into separate daemons which start from /etc/init.d/

    Here are the two scripts I have created...
    raspistill-daemon

    <hr /> <h1>! /bin/sh</h1> <h3>BEGIN INIT INFO</h3> <h1>Provides: raspistill-daemon</h1> <h1>Required-Start:</h1> <h1>Required-Stop:</h1> <h1>Default-Start: 2 3 4 5</h1> <h1>Default-Stop: 0 1 6</h1> <h1>Short-Description: Start raspistill as a daemon to stream mjpeg video</h1> <h1>Description: This file should be used to construct scripts to be</h1> <h1>placed in /etc/init.d. Run sudo update-rc.d raspistill-daemon defaults</h1> <h3>END INIT INFO</h3> <h1>Author: Joe</h1> <h1>/etc/init.d/raspistill-daemon</h1>

    PATH=/sbin:/usr/sbin:/bin:/usr/bin

    export HOME
    case "$1" in
    start)
    echo “Starting raspistill-daemon”
    mkdir /tmp/stream
    raspistill --nopreview -w 640 -h 480 -q 5 -o /tmp/stream/pic.jpg -tl 100 -t 9999999 -th 0:0:0 &
    ps auxwww | grep raspistill | head -1
    ;;
    stop)
    echo “Stopping raspistill-daemon”
    ps auxwww | grep raspistill | head -1 | awk '{print $2}' | xargs kill -9
    rm /tmp/stream/
    rmdir /tmp/stream
    ;;
    restart)
    echo “Restarting raspistill-daemon ...”
    echo “ ... stopping ... “
    ps auxwww | grep raspistill | head -1 | awk '{print $2}' | xargs kill -9
    echo “ ... starting”
    raspistill --nopreview -w 640 -h 480 -q 5 -o /tmp/stream/pic.jpg -tl 100 -t 9999999 -th 0:0:0 &
    ps auxwww | grep raspistill | head -1
    ;;
    status)
    echo “Raspistill-daemon by Joe Molnar ...”
    ps auxwww | grep raspistill | head -1
    exit 1
    ;;
    )
    echo “Usage: raspistill-daemon { start | stop | restart | status }”
    exit 1
    ;;
    esac
    exit 0

    <hr /> <h2>and the streamer-deamon ...</h2> <h1>! /bin/sh</h1> <h3>BEGIN INIT INFO</h3> <h1>Provides: streamer-daemon</h1> <h1>Required-Start: raspistill-daemon</h1> <h1>Required-Stop:</h1> <h1>Default-Start: 2 3 4 5</h1> <h1>Default-Stop: 0 1 6</h1> <h1>Short-Description: Start mjpg-streamer as a daemon to stream mjpeg video</h1> <h1>Description: This file should be used to construct scripts to be</h1> <h1>placed in /etc/init.d.</h1> <h3>END INIT INFO</h3> <h1>Author: Joe</h1> <h1>/etc/init.d/streamer-daemon</h1>

    PATH=/sbin:/usr/sbin:/bin:/usr/bin

    export HOME
    case "$1" in
    start)
    echo “Starting streamer-daemon”
    LD_LIBRARY_PATH=/usr/local/lib /home/pi/mjpg-streamer-code-182/mjpg-streamer/mjpg_streamer -i "input_file.so -f /tmp/stream -n pic.jpg" -o "output_http.so -w /usr/local/www -p 8083" &
    ;;
    stop)
    echo “Stopping streamer-daemon”
    ps auxwww | grep mjpg_streamer | head -1 | awk '{print $2}' | xargs kill -9
    ;;
    esac
    exit 0

    <hr />

    But when I try to stop then re-start the mjpg-streamer, this is what I get...
    pi@raspberrypi3 ~ $ /etc/init.d/streamer-daemon start
    “Starting streamer-daemon”
    pi@raspberrypi3 ~ $ MJPG Streamer Version: svn rev:
    i: folder to watch...: /tmp/stream/
    i: forced delay......: 0
    i: delete file.......: no, do not delete
    i: filename must be..: pic.jpg
    o: www-folder-path...: /usr/local/www/
    o: HTTP TCP port.....: 8083
    o: username:password.: disabled
    o: commands..........: enabled
    bind: Address already in use
    o: server_thread():ls /tmp/stream/
    pic.jpg

    It says "address already in use". Any idea what I have to do to overcome this once the streamer crashes without needing to restart the pi.

    More importantly, is there something I can do to make this more stable so it doesn't crash after several hours?

    Thank you,
    Joe

  • #81 Miguel Grinberg said

    @Joe: the "address is in use" error suggests the server is still running, at least in some capacity. Take a look at the process list when the Pi gets into this state and kill anything related to mjpg_streamer before you restart it.

  • #82 Mike Moore said

    Just one question - how can I use a port other than 8080? 8080 is used by another app on my Pi.

  • #83 Christopher Mans said

    thank you very much.. it works brilliantly !!

  • #84 Miguel Grinberg said

    @Mike: Sure, instead of

    -o "output_http.so -w ./www"

    use:

    -o "output_http.so -w ./www -p 8081"

  • #85 Ayush said

    Thanks Miguel for the instructions.

    I want to stream an IP camera MJPEG feed through this.
    I am using wget on the feed to get the MJPEG sequence.
    The server comes up but keeps on loading with no image/stream showing up at all.

    Any inputs on that would be great !
    Thanks.

  • #86 Miguel Grinberg said

    @Ayush: what's your wget command?

  • #87 pramod sakhare said

    sir,
    To setup a constant stream of JPEG images the command is as:"raspistill -w 640 -h 480 -q 5 -o /tmp/stream/pic.jpg -tl 100 -t 9999999 -th 0:0:0 &".
    But we are getting an error:"Invalid command line option (-t1)".
    Please suggest a solution for that.

  • #88 Miguel Grinberg said

    @pramod: I suspect you have -t1 (dash, letter T, number 1) right after pic.jpg. The correct option is -tl (dash, letter T, letter L).

  • #89 gijs said

    Is there a way to connect your raspberry to youre mobile hotspot and stream direct to tour phone without internet acces?

  • #90 Ian said

    I have a cloud server that I want to stream mjpg-streamer file to directly instead of the pi. Is this possible over ssh perhaps? Instead of the destination of the jpeg being a file on the pi, can it be a file on the cloud server? I want to save the images as they come up and display multiple streams on that web server. The server has the ability to grow while the pi does not.

  • #91 Miguel Grinberg said

    @Ian: See my recent article on streaming using Python: http://blog.miguelgrinberg.com/post/video-streaming-with-flask

  • #92 s thrugood said

    $ raspistill -w 640 -h 480 -q 5 -o /tmp/stream/pic.jpg -tl 100 -t 9999999 -th 0:0:0 &

    What does the 9999999 parameter mean?

    Thank you

  • #93 Miguel Grinberg said

    @s_thrugood: that's the time the application will run. It's just a big number.

  • #94 Mack said

    Trying to run the command:
    LD_LIBRARY_PATH=./ ./mjpg_streamer -i "input_file.so -f /tmp/stream" -o "output_http.so -w ./www"

    But I get the error "Permission Denied" I have the directory setup so that the folder "mjpg_streamer" contains all of the subdirectories. How are your directories setup?

  • #95 Miguel Grinberg said

    @Mack: you will need to investigate what is exactly failing. It's probably file permission related. Have you done anything with file permissions?

  • #96 Revanth said

    Hi. It is an awesome tutorial. I have an issue. In my case i will be sitting somewhere in the world controlling the camera over the Internet. Since i cant access the PI (camera) (due to static IP issues) i thought of another idea, i will setup a cloud in between which can be accessed with a global IP and the PI will contact the cloud regularly to get the commands (pan, zoom etc) and in turn will upload the video on to the cloud and i will fetch the video from cloud. As in your case the video will be uploaded on to a streamer that is in the PI but i want the video to be uploaded on to the cloud,FIRSTLY IS THAT POSSIBLE? SECONDLY HOW TO DO THAT IF IT IS POSSIBLE?
    Thanks in advance.

  • #97 Miguel Grinberg said

    @Revanth: not sure there is an existing software to do this. You can write a script in the Pi that uploads the jpegs to your cloud server, then run mjpg-streamer in the cloud.

  • #98 steve gale said

    Hi Miguel,
    I am enjoying your articles, yesterday I tried streaming using flask and I have realised why I had a large image displayed, it is the preview window.
    So today I thought I would try this example.

    I have downloaded the source and built mjpg_streamer. I am not seeing any output and I get the following error when I run it from the build directory.
    pi@coderbot ~/Downloads/mjpg-streamer-code-182/mjpg-streamer $ LD_LIBRARY_PATH=./ ./mjpg_streamer -i "input_file.so -f /tmp/stream" -o "output_http.so -w ./www"
    MJPG Streamer Version: svn rev:
    i: folder to watch...: /tmp/stream/
    i: forced delay......: 0
    i: delete file.......: no, do not delete
    i: filename must be..: -no filter for certain filename set-
    o: www-folder-path...: ./www/
    o: HTTP TCP port.....: 8080
    o: username:password.: disabled
    o: commands..........: enabled
    could not open file for reading: No such file or directory

    Images are being captured in /tmp/stream
    pi@coderbot ~ $ ls /tmp/stream/
    pic.jpg pic.jpg~

    any ideas as to what it could be?

    cheers
    Steve

  • #99 Miguel Grinberg said

    @steve: try adding a "-n pic.jpg" right after "-f /tmp/stream". I did not need to do that in my tests, but some people reported that this addressed the error you are having.

  • #100 Justin cullins said

    Hey guys I'm having some trouble with starting up mjpg streamer onto my new raspberry pi b+ for a video project. I put in the following to start up $ sudo apt-get install libjpeg8-dev $ sudo apt-get install imagemagick $ tar xvzf mjpg-streamer-r63.tar.gz $ cd mjpg-streamer-r63 $ make but when I put $ tar xvzf mjpg-streamer-r63.tar.gz I get and error saying tar (child): mjpg-streamer-r63.tar.gz tar (child): error is not recoverable:exiting now tar: child returned status 2 tar: error is not recoverable: exiting now.
    I've been trying to set up a the camera module for awhile now and I seem to have the video working it's just the lag is pretty bad, so i looked up on how to get the video stream a lot faster, and found mjpg streamer but now stuck.

    anyone have any ideas???

Leave a Comment