X893

Use Red5 and FLV conversion service on CentOS as CommunityServer attachments storage

Install Red5 server

  • Download Java Linux RPM (32 or 64) and run it.
    chmod a+x jre-<version>-rpm.bin
    ./jre-<version>-rpm.bin
    rpm -iv jre-<version>.rpm
    java -version
  • Install Red5
    cd /usr/src
    mkdir red5
    cd red5
    wget http://red5.nl/installer/red5-0.7.0.tar.gz
    tar zxf red5-0.7.0.tar.gz
    mkdir dist
    cp red5.jar dist/
    cp red5.sh dist/
    cp -r conf/ dist/
    cp -r lib/ dist/
    cp -r webapps/ dist/
    make install
    make clean
    cd /usr/lib/red5
    ./red5.sh
  • Verify that Red5 start correctly and stop it (Ctrl-C). Create service startup file
    nano /etc/init.d/red5
    
    #!/bin/sh
    #
    # red5:         Red 5 Server
    #
    # chkconfig:    - 26 89
    # description:  Red 6 Stream Server.
    #
    
    # Source function library.
    . /etc/rc.d/init.d/functions
    
    [ -f /etc/sysconfig/network ] && . /etc/sysconfig/network
    
    [ "${NETWORKING}" = "yes" ] || exit 0
    
    RED5_DIR=/usr/lib/red5
    test -x $RED5_DIR/red5.sh || exit 5
    
    start()
    {
        echo -n $"Starting Red5 Service: "
        cd $RED5_DIR
        daemon /bin/bash -c "$RED5_DIR/red5.sh >/dev/nul &"
        sleep 2
        echo
    }
    
    stop()
    {
        echo -n $"Shutting down Red5"
        killall -q java
        sleep 2
        echo
    }
    
    # See how we were called.
    case "$1" in
        start)
            start
            ;;
        stop)
            stop
            ;;
        restart)
            stop
            start
            ;;
        *)
            echo $"Usage: $0 {start|stop|restart}"
            exit 1
    esac
    
    chmod 755 /etc/init.d/red5
    chkconfig --add red5
    chkconfig --level 345 red5 on
    service red5 start
    service red5 stop
    Change default ports

nano /usr/lib/red5/conf/red5.properties
# HTTP
http.host=0.0.0.0
http.port=80
#https.port=443
# RTMP
rtmp.host=0.0.0.0
rtmp.port=1935
rtmp.event_threads_core=16
rtmp.event_threads_max=64
# event threads queue: -1 unbounded, 0 direct (no queue), n bounded queue
rtmp.event_threads_queue=0
rtmp.event_threads_keepalive=60
rtmp.send_buffer_size=271360
rtmp.receive_buffer_size=65536
rtmp.ping_interval=5000
rtmp.max_inactivity=60000
rtmp.tcp_nodelay=true
# RTMPT
rtmpt.host=0.0.0.0
rtmpt.port=8088
rtmpt.ping_interval=5000
rtmpt.max_inactivity=60000
# MRTMP
mrtmp.host=0.0.0.0
mrtmp.port=9035
mrtmp.event_threads_core=4
mrtmp.event_threads_max=32
# event threads queue: -1 unbounded, 0 direct (no queue), n bounded queue
mrtmp.event_threads_queue=0
mrtmp.event_threads_keepalive=60
mrtmp.send_buffer_size=271360
mrtmp.receive_buffer_size=65536
mrtmp.ping_interval=5000
mrtmp.max_inactivity=60000
mrtmp.tcp_nodelay=true
# Debug proxy (needs to be activated in red5-core.xml)
proxy.source_host=127.0.0.1
proxy.source_port=1936
proxy.destination_host=127.0.0.1
proxy.destination_port=1935

  • Create Files folder and configure it

mkdir /usr/lib/red5/webapps/Files
mkdir /usr/lib/red5/webapps/Files/WEB-INF
cd /usr/lib/red5/webapps/Files/WEB-INF
nano red5-web.properties
webapp.contextPath=/Files
webapp.virtualHosts=*, localhost:88, localhost, localhost:80, 127.0.0.1:80, 127.0.0.1:88
nano red5-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="/WEB-INF/red5-web.properties" />
    </bean>
    <bean id="web.context" class="org.red5.server.Context" autowire="byType" />
    <bean id="web.scope" class="org.red5.server.WebScope"
         init-method="register">
        <property name="server" ref="red5.server" />
        <property name="parent" ref="global.scope" />
        <property name="context" ref="web.context" />
        <property name="handler" ref="web.handler" />
        <property name="contextPath" value="${webapp.contextPath}" />
        <property name="virtualHosts" value="${webapp.virtualHosts}" />
    </bean>
    <bean id="web.handler" class="org.red5.server.adapter.ApplicationAdapter" singleton="true" />
</beans>
nano web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app
   xmlns="http://java.sun.com/xml/ns/j2ee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
   version="2.4">
    <display-name>Video Library</display-name>
    <context-param>
        <param-name>globalScope</param-name>
        <param-value>default</param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/red5-*.xml</param-value>
    </context-param>
    <context-param>
        <param-name>locatorFactorySelector</param-name>
        <param-value>red5.xml</param-value>
    </context-param>
    <context-param>
        <param-name>parentContextKey</param-name>
        <param-value>default.context</param-value>
    </context-param>   
    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>/Files</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <mime-mapping>
        <extension>flv</extension>
        <mime-type>video/flv</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>jpg</extension>
        <mime-type>image/jpeg</mime-type>
    </mime-mapping>
</web-app>
 

Install samba server and configure to anonymous access (disable SELINUX). Use separate network share server (OpenFiler) to CommunityServer/Red5 farm.

yum -y install samba
nano /etc/samba/smb.conf
[global]
workgroup = WORKGROUP
netbios name = VIDEO
security = share
wins support = yes

[data]
comment = Data
path = /usr/lib/red5/webapps
force user = root
force group = root
read only = No
guest ok = Yes
chkconfig --level 345 smb on
service smb start

  • Check access to network share \\VIDEO\data

Make changes in CommunityServer 2008 (CS2008.5_4.1.30912.2823)

  • Change CommunityServer PostAttachment storage location to network share (communityserver.config)

<fileStore name="CommunityServer.Components.PostAttachments" basePath="~/filestorage/"
type="CommunityServer.Components.FileSystemFileStorageProvider, CommunityServer.Components"
...

to

<fileStore name="CommunityServer.Components.PostAttachments" basePath="\\VIDEO\data\Files"
type="Custom.Components.FileSystemFileStorageProvider, Custom.Components"
externalUrl="http://external_Red5_FQDN/Files"
...

or

<fileStore name="CommunityServer.Components.PostAttachments" basePath="\\ip_address\data\Files"
type="Custom.Components.FileSystemFileStorageProvider, Custom.Components"
externalUrl="http://external_Red5_ip_address/Files"
...

  • Copy Custom.Components.dll to bin folder and restart IIS
  • Try to upload file to MediaGallery and check it on network share (\\VIDEO\data\Files)

If you need convert video files to FLV format make conversion service on samba server (OpenFiler or Red5)

Install ffmpeg and flvtool2 from dag repository on Centos i386 and x86_64 platform.

  • Add dag.repo to /etc/yum.repos.d

nano /etc/yum.repos.d/dag.repo
[dag]
name=Dag RPM Repository for Red Hat Enterprise Linux
baseurl=http://apt.sw.be/redhat/el$releasever/en/$basearch/dag
gpgcheck=0
enabled=1

  • Install ffmpeg, flvtool2 and perl libwww package

yum -y install ffmpeg flvtool2 perl-libwww-perl

  • Check ffmpeg and flvtool2

ffmpeg
flvtool2

  • Create conversion service

nano /usr/sbin/flvconv.pl
#!/usr/bin/perl
use strict;
use LWP;
use File::Basename;

#
# Post processing statuses:
#    0    OK
#    1    unknown error during processing
#    2    file not exists
#    3    file not readable
#    4    ffmpeg fail
#    5    flvtool2 fail
#    6    ffmpeg fail on preview processing
#    7    ffmpeg fail on thumbnail processing
#    8    ffmpeg fail on small thumbnail processing
#    9    can't rename original file to xxxx.org file

my $retry_per_instance    = 2;                        # request retry per instance

my $request_def            = "http://#server#/api/ConversionService.aspx";
my $response_def        = "http://#server#/api/ConversionService.aspx?Complete=&TaskID=#taskID#&PostID=#postID#&PostStatus=#status#";

my $size_def            = "480x360";                # default video size
my $aspect_def            = "4:3";                     # default video aspect
my $thumbnail_def        = "120x90";           # default thumbnail size or empty to disable generation
my $thumbnail_small_def = "60x45";        # default small thumbnail size or empty to disable generation
my $watermark_def        = "";                        # default watermark file
my $watermark_tmpl        = "'/usr/lib/vhook/watermark.so -m 1 -t 000000 -f #image#'";
# my $watermark_tmpl        = "'/usr/lib64/vhook/watermark.so -m 1 -t 000000 -f #image#'";
#
# site instances definition
#
my @instances_def = (
        [
                "cs_site_0",                            # servers list of this instance or cs_site_0;cs_site_1[;...]
                $request_def,                        # template for server request
                $response_def,                    # template for server response
                "360x270",                              # video size
                "120x90",                                # thumbnail size
                "80x60",                                  # thumbnail small size
                "",                                             # add watermark
                $aspect_def,                         # ascept ratio
        ],
        );

my $pathCS      = '^\\\\\\\\VIDEO\\\\data\\\\';    # Windows network share (see communityserver.config CentralizedFileStorage section)
# or
# my $pathCS      = '^\\\\\\\\ip_address\\\\data\\\\';    # ip_address = 10\.0\.0\.1
my $pathLocal   = '/usr/lib/red5/webapps/';            # Linux path to files
my $pathDelimCS = '\\\\';                                            # Windows path separator    \
my $pathDelimLocal = '/';                                           # Linux path separator        /

#
# Build internal servers array from instances
#
my @instances = ();
foreach(@instances_def) {
        next unless( $_ );
        my @p = @{$_};
        next unless( $p[0] );
        my @srv = (
                0,
                [split(";", $p[0])],
                ($p[1] or $request_def),
                ($p[2] or $response_def),
                ($p[3] or $size_def),
                ($p[4] or $thumbnail_def),
                ($p[5] or $thumbnail_small_def),
                ($p[6] or $watermark_def),
                ($p[7] or $aspect_def)
                );
        push(@instances, \@srv);
}
@instances or die "No instances define.";

my $browser    = LWP::UserAgent->new;
my $status    = 0;
my $run        = 1;
my $error;
my @args;
my $response;
my $url;
my $content;
my $original;
my $fileFlv;
my $url_request;
my $url_response;
my $video_size;
my $thumbnail_size;
my $thumbnail_small_size;
my $watermark;
my $aspect;
my $server;

while( $run ) {

    foreach( @instances ) {
        my $index = $_->[0];

        my $retry = $retry_per_instance;
        my $error = 0;

        while(--$retry >= 0) {
            my @srvs = @{$_->[1]};
            $server                    = $srvs[$index];            # current server to request
            $url_request            = $_->[2];
            $url_response            = $_->[3];
            $video_size                = $_->[4];
            $thumbnail_size            = $_->[5];
            $thumbnail_small_size   = $_->[6];
            $watermark                = $_->[7];
            $aspect                    = $_->[8];

            if( $watermark ) {
                my $ws = $watermark_tmpl;
                $ws =~ s/#image#/$watermark/g;
                $watermark = $ws;
            }

            $url = $url_request;
            $url =~ s/#server#/$server/g;                        # prepare request url
            $response = $browser->get( $url );

            $error = 0;
            unless( $response->is_success ) {
                print "\nGet error. ".$url."\n\tresponse: ".$response->status_line."\n";
                $error = 1;
                if( @srvs > 1 ) {                                # if more servers presents for this instance then
                    $index++;                                    # switch to next server and retry
                    $index < @srvs or $index = 0;
                    $_->[0] = $index;                            # store server index
                    next;
                }
                last;                                            # only one server in instance - process next instance
            }

            unless( $response->content_type eq 'text/plain' ) {
                print "\nNot a text/plain response -- ".$response->content_type."\n";
                $error = 1;
                if(@srvs > 1) {
                    $index++;
                    $index < @srvs or $index = 0;
                    $_->[0] = $index;
                    next;
                }
                last;                                            # only one server in instance - process next instance
            }
            last;                                                # process request
        }

        unless( $error ) {

            $content = $response->content;
            $content =~ s/\r//g;

            foreach (split(/\n+/, $content)) {                    # convert response to lines array
                next unless $_;                                    # skip empty line

                $status = 1;
                my($taskID, $postID, $postType, $fileName) = split(/\t/, $_);

                $fileName =~ s/$pathCS/$pathLocal/g;            # change CS path to local path
                $fileName =~ s/$pathDelimCS/$pathDelimLocal/g;  # also change directory separator

                $original = $fileName;
                my $fileWoExt = $fileName;
                $fileWoExt =~ s/\.[^.]*$//;
                $fileFlv = $fileWoExt.".tmp";

                print "Convert ".$original."\n     to ".$fileFlv."\n";
                while( $status == 1 ) {
                    if( $postType == 1 ) {                        # = 1 don't convert file - only thumbnail
                        unless( -e $original ) {
                            print "File not exist -- $original\n";
                            $status = 2;
                            last;
                        }
                        unless( -r $original ) {
                            print "File not readable -- $original\n";
                            $status = 3;
                            last;
                        }

                        print "Process file -- $original\n";
                        @args = ("flvconv", $original, $fileFlv, $video_size,  $aspect); #, $watermark);
                        system(@args);
                        if( $? != 0 ) {
                            print "Fail ffmpeg: ",$?,"\n";
                            $status = 4;
                            last;
                        }
                        @args = ("flvtool2", "-U", $fileFlv);
                        system(@args);
                        if( $? != 0 ) {
                            print "Fail flv2tool: ",$?,"\n";
                            $status = 5;
                            last;
                        }
                    }

                    # make video thumbnail
                    @args = ("ffmpeg", "-y", "-i", $fileFlv, "-vframes", "1", "-ss", "00:00:01", "-an", "-s", $video_size, "-qmin", "1", "-f", "mjpeg", $fileWoExt.".jpg");
                    system(@args);
                    if( $? != 0 ) {
                        print "Fail ffmpeg (preview): ".$?." ".$fileFlv."\n";
                        $status = 6;
                        last;
                    }

                    if( $thumbnail_size ) {
                        @args = ("ffmpeg", "-y", "-i", $fileFlv, "-vframes", "1", "-ss", "00:00:01", "-an", "-s", $thumbnail_size, "-qmin", "1", "-f", "mjpeg", $fileWoExt."-".$thumbnail_size.".jpg");
                        system(@args);
                        if( $? != 0 ) {
                            print "Fail ffmpeg (thumbnail): ".$?." ".$fileFlv."\n";
                            $status = 7;
                            last;
                        }
                    }
                   
                    if( $thumbnail_small_size ) {
                        @args = ("ffmpeg", "-y", "-i", $fileFlv, "-vframes", "1", "-ss", "00:00:01", "-an", "-s", $thumbnail_small_size, "-qmin", "1", "-f", "mjpeg", $fileWoExt."-".$thumbnail_small_size.".jpg");
                        system(@args);
                        if($? != 0) {
                            print "Fail ffmpeg (small): ".$?." ".$fileFlv."\n";
                            $status = 8;
                            last;
                        }
                    }
                    $status = 0;
                    last;
                }

                $url = $url_response;
                $url =~ s/#server#/$server/g;
                $url =~ s/#taskID#/$taskID/g;
                $url =~ s/#postID#/$postID/g;
                $url =~ s/#status#/$status/g;

                if( $status ) {
                    print "Process Error $status -- ".$url."\n";
                } else {
                    print "Success -- ".$url."\n";
                }

                $response = $browser->get( $url );
                unless( $response->is_success ) {
                    print "Can't get $url -- $response->status_line\n";
                    next;
                }
            }
        }
        if(@instances == 1) {
                sleep(5);               # one instance, 5 sec. delay
        } else {
                sleep(1);               # more instance, 1 sec. delay before next instance
        }
    }
    $run = 0;
}
nano /usr/sbin/flvconv
#!/bin/bash

if [ -n "$5" ] ; then
    ffmpeg -v 10 -y -i "$1" -acodec mp3 -ab 56k -ac 2 -ar 11025 -f flv -deinterlace -nr 500 -s $3 -aspect $4 -r 20 -b 400k -me_range 25 -i_qfactor 0.71 -g 500 "$2.tmp" 2>&1
    ffmpeg -y -i "$2.tmp" -acodec copy -f flv -vhook '/usr/lib/vhook/watermark.so -m 0 -t 000000 -f /data/U2D/watermark.png' "$2" 2>&1
    rm -f "$2.tmp"
else
    ffmpeg -v 10 -y -i "$1" -acodec mp3 -ab 56k -ac 2 -ar 11025 -f flv -deinterlace -nr 500 -s $3 -aspect $4 -r 20 -b 400k -me_range 25 -i_qfactor 0.71 -g 500 "$2" 2>&1
fi
nano /etc/init.d/flvconv
#!/bin/sh
#
# flvconv:    FLV convert
#
# chkconfig:    345 60 89
# description:    FLV Convert
# processname:    flvconv.pl
# pidfile:    /var/run/flvconv.pid

# Source function library.
. /etc/rc.d/init.d/functions

FLVCONV_DIR=/usr/sbin

test -x $FLVCONV_DIR/flvconv.pl || exit 5

start()
{
        echo -n $"Starting FLV Service: "
        pid_length=`pidof -x flvconv.pl|awk '{print length($0)}'`
        if [ "$pid_length" != "0" -a "$pid_length" != "" ]; then
            RETVAL=1
        else
            daemon "$FLVCONV_DIR/flvconv.pl >/tmp/flvconv.log &"
            RETVAL=$?
            sleep 1
        fi
    [ ${RETVAL} -eq 0 ] && success || failure
        echo
}

stop()
{
        echo -n $"Shutting down FLV Service:"
        killall -q flvconv.pl
        [ $? -eq 0 ] && success || failure
        sleep 1
        echo
}

# See how we were called.
case "$1" in
  start)
    start
        ;;
  stop)
    stop
        ;;
  restart)
    stop
    start
    ;;
  *)
        echo $"Usage: $0 {start|stop|restart}"
        exit 1
esac

exit 0
 

  • Add service
    chmod 755 /etc/init.d/flvconv
    chmod 755 /usr/sbin/flvconv.pl
    chmod 755 /usr/sbin/flvconv
    chkconfig -add flvconv
    chkconfig -level 345 flvconv on
    service flvconv start

Check correct conversion of previously uploaded file.

Conversion service change post status from 0 to 1 after successful conversion and to -1 on error.
Change CS pages to correct display post(s) with PostStatus.

Add pages to CS with live video stream (use link to Red5 server).

0 comment(s) so far

Post your comment

Thanks for your comments

  • Comment

Github
Bitbucket
Codeplex
SF.net
Resume

Subscribe to x893 blog Subscribe to x893 blog