Monday, August 18, 2014

Video record from Foscam Wireless IP Camera with libvlc in C++

I have a Foscam Wireless IP Camera and I had a MS-Windows batch file that stores the streaming video as a AVI file. But I stopped using it because it didn't give me a control of when to stop recording. So I have been wanting to make a C/C++ program that does the same thing but that can give me more control of when to stop recording.

pi@desktoppi ~/prog/MycURL $ sudo apt-get install vlc libvlc-dev
pi@desktoppi ~/prog/MycURL $ find / -name "vlc.h" 2> /dev/null
/usr/include/vlc/vlc.h
pi@desktoppi ~/prog/MycURL $ 
You need to install two packages, vlc and libvlc-dev. The install command is: sudo apt-get install vlc libvlc-dev
Make sure you have vlc.h because that's what you are going to use in your C++ program.

 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
// MyVLC.hpp written by Jae Hyuk Kwak
namespace MyVLCNS
{
    class MyVLC
    {
        unique_ptr< libvlc_instance_t, decltype( libvlc_release )* > vlc_;
    public:
        MyVLC( size_t argc, const char * const *argv )
            : vlc_( libvlc_new( argc, argv ), libvlc_release )
        {}

        libvlc_instance_t *native() { return vlc_.get(); }
    };

    class MyMediaPlayer
    {
        libvlc_instance_t *const vlc_;
        unique_ptr< libvlc_media_player_t, decltype( libvlc_media_player_release )* > mediaPlayer_;
    public:
        MyMediaPlayer( MyVLC *myVLC, const string &url, const string &option )
            : vlc_( myVLC->native() )
            , mediaPlayer_( nullptr, libvlc_media_player_release )
        {
            unique_ptr< libvlc_media_t, decltype( libvlc_media_release )* > m( libvlc_media_new_location( vlc_, url.c_str() ), libvlc_media_release );
            libvlc_media_add_option( m.get(), option.c_str() );
            mediaPlayer_.reset( libvlc_media_player_new_from_media( m.get() ) );
        }
        libvlc_media_player_t *native() { return mediaPlayer_.get(); }
    };

    namespace MyMediaPlayerNS
    {
        class Play
        {
            libvlc_media_player_t *const mediaPlayer_;
        public:
            Play( MyMediaPlayer *mp )
                : mediaPlayer_( mp->native() )
            {
                libvlc_media_player_play( mediaPlayer_ );
            }
            ~Play()
            {
                libvlc_media_player_stop( mediaPlayer_ );
            }
        };
    }
}
The code above shows my wrapping classes for libvlc. The library, libvlc, was surprisingly easy to use, although it took some time for me to figure out how to use the "sout" option.

 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
// myvlc.cpp written by Jae Hyuk Kwak
#include "stdafx.h"
#include "MyVLC.hpp"
#include "config.hpp"
using namespace Configuration;
using namespace MyVLCNS;
using namespace MyVLCNS::MyMediaPlayerNS;
int main( int argc, char **argv )
{
    const string mo = [&]() // c++11 lamda
    {
        if ( 2 != argc ) throw runtime_error( "output filename is not provided." );
        const string outfile = argv[1];

        static const string extType = ".avi";
        if ( outfile.length() < 4 || outfile.substr( outfile.length() - 4 ) != extType )
            throw runtime_error( "output file extension must be: " + extType );

        string mo = mediaOption;
        mo.replace( mo.find( filenamePlaceholder ), filenamePlaceholder.length(), outfile );
        return mo;
    }();

    const char *const vlcArgv[] = { // "--verbose=2",
        "--sout", transcodeOption.c_str() };
    const size_t vlcArgc = sizeof( vlcArgv ) / sizeof( *vlcArgv );

    MyVLC vlc( vlcArgc, vlcArgv );
    MyMediaPlayer mp( &vlc, URL, mo );
    {
        Play play( &mp );   // stop playing on destruction
        this_thread::sleep_for( chrono::seconds( 10 ) );    // record for 10 seconds.
    }
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// config.hpp written by Jae Hyuk Kwak
namespace Configuration
{
    // You need to change this...
    static const string URL = "http://192.168.1.80:80/videostream.asf?user=MyId&pwd=MyPassword";

    // check more VLC options from http://www.videolan.org/doc/streaming-howto/en/ch03.html
    static const string transcodeOption = "#transcode{vcodec=h264,acodec=mpga,channels=1}";
    static const string mediaOption // C++11 style raw-string
        = R"(:sout=#standard{mux="avi",access="file",dst="${filename}"})";
    static const string filenamePlaceholder = "${filename}";
}
1
2
3
4
5
6
7
8
// stdafx.h written by Jae Hyuk Kwak
#include <string>
#include <memory>	// unique_ptr
#include <stdexcept>	// runtime_error
#include <thread>	// this_thread::sleep_for
#include <chrono>	// chrono::seconds
#include <vlc/vlc.h>
using namespace std;
# Makefile written by Jae Hyuk Kwak
CC=g++-4.7
CFLAGS=-Wall -g -std=c++11

all: myvlc

myvlc: *.hpp myvlc.cpp stdafx.h.gch
	$(CC) $(CFLAGS) -o myvlc -lvlc myvlc.cpp

stdafx.h.gch: stdafx.h
	$(CC) $(CFLAGS) -c stdafx.h

clean:
	rm stdafx.h.gch myvlc
The screenshot above shows how to use my wrapping classes in C++.

The config.hpp has a few critical settings in string.
  • URL : you will need to figure out what URL you can use on your camera by yourself. The URL on the screen is based on my camera and my IP address but I believe other IP cameras have similar setups.
  • transcodeOption : this option is for transcoding. It means whatever the IP camera gives us as the streaming video, we want to decode it and re-encode to whatever we want to use. I wanted to re-encoded with H.264 and save some SD memory card space but unfortunately there is no H.264 VLC plugin for raspberry pi yet; VLC will use any other plugin available.
  • mediaOption : this took some time for me to figure out how to use. I don't think you need to modify it.

The "main" function checks input arguments and it expects an output filename that ends with ".avi"; otherwise it will throw runtime_error exception; which is one of my favorite exceptions.

For vlc_argv, you can un-comment, "--verbos=2", if you need to debug or need more information.

With "sleep_for( 10 )", it will works like a command:
cvlc http://192.168.1.80/videostream.asf?user=MyId&pwd=MyPassword --run-time=10 --sout=#transcode{vcodec=h264,acodec=mpga,channels=1}:standard{mux="avi",access="file",dst="output.avi"} vlc://quit 
It may be a good idea to try the command above before you start debugging the C++ code in case it doesn't work as easy as it sounds.

The final result will look like the screenshot above. I am not sure what the errors are about but it doesn't seem to affect what I am doing. I did some google searching but I couldn't figure out what it was about. As the final result it gives me 8seconds video file, although it wasn't encoded with H.264 as explained above.

2 comments:

  1. Shard Global Ltd offers intelligent security solutions that enable a smarter, safer world. As the global market leader in network video, Axis is driving the industry by continually launching innovative network products based on an open platform - delivering high value to customers through a global partner network.

    And

    With UK Phone Systems, you get the most advanced and fully featured phone system in the industry. It is a system that is ultra-reliable and supported by a highly trained staff located right here in the UK, a system that will save you money and enable you to focus on your business.

    ReplyDelete

About Me

My photo
Tomorrow may not come, so I want to do my best now.