Wednesday, August 17, 2011

Transcoding video to H.264 using xuggler mediatool

On tutorial, it use ffmpeg command to transcode videos. Also on Mediatool tutorial, there isn't an example to transcode video to another format. Or it use more primitive API at "How to Grow Some Balls" example.

So I create MediaToolAdapter and override onAddStream to change the codec.
public class SetVideoTranscodeListener extends MediaToolAdapter {
    @Override
    public void onAddStream(IAddStreamEvent event) {
        int streamIndex = event.getStreamIndex();
        IStreamCoder streamCoder = event.getSource().getContainer().getStream(streamIndex).getStreamCoder();
		
        if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) {
            streamCoder.setCodec(ICodec.findEncodingCodec(ICodec.ID.CODEC_ID_H264));
            int retval = Configuration.configure("/usr/local/xuggler/share/ffmpeg/libx264-superfast.ffpreset", streamCoder);
            if (retval<0)
                throw new RuntimeException("cound not cofigure coder from preset file");
        }
        super.onAddStream(event);
    }
}
And add an instance of this class as a listener to writer
    writer.addListener(new SetVideoTranscodeListener());

Thursday, July 21, 2011

Ant: create a property from a list of the part of property name

I tried to create a property from a list. Input properties are:
<property name "servers"="aaa,bbb,ccc"/>
<property name "aaa.ip"="10.0.0.1"/>
<property name "bbb.ip"="10.0.0.2"/>
<property name "ccc.ip"="10.0.0.3"/>
From above properties, I want to create a property whose value is "10.0.0.1,10.0.0.2,10.0.0.3".

I first tried propertyregex:
<propertyregex property="server.list"
    input="${servers}"
    regexp="([^,]+)"
    replace="${\1.ip}"
    global="true" />
<echo message="${server.list}"/>

But it does not work. The "server.list" becomes "${aaa.ip},${bbb.ip},${ccc.ip}"

Next I tried var and for:
<var name="server.list" value=""/>
<for list="${servers}" param="server">
    <sequential>
        <if>
            <equals arg1="${server.list}" arg2=""/>
            <then>
                <var name="server.list" value="${@{server}.ip}"/>
            </then>
            <else>
                <var name="server.list" value="${server.list},${@{server}.ip}"/>
            </else>
        </if>
    </sequential>
</for>

Yes, this works. Now I get what I want.

Friday, June 10, 2011

Conflict settings of flash player

This is just a note. If anyone know a setting which satisfy both, let me know.

1. Jquery + Flash
I'm using jquery's sliding menu and flash object in the same page. The problem is the same as this. As suggested in the page, I use wmode=transparent.

2. Linux + Firefox + Flash
When I tried this configuration, "Settings..." menu is disabled. Adobe says it is a specification (see this), since it works on Windows.

How can I solve two issues with one configuration?

Compile Red5 on Eclipse

When I tried to compile red5 at Eclipse from the source checked out from the svn, I got the compile error.

BUILD FAILED
C:\opt\workspace\red5_server\build.xml:225: The following error occurred while executing this line:
C:\opt\workspace\red5_server\build.xml:246: Error running javac.exe compiler

To fix this issue, I changed following two. First, change the JRE System Library to jdk. And Second change the ant's JRE to jdk.

To do first, right click the "JRE System Library", and select "Properties". Then you can see the image shown below. Select "Alternate JRE" and select the jdk.


Next, select build.xml, right click it, select "Run As" -> "Ant Build...". Then you can see the following image shown below. Select "JRE" tab, and select "Separate JRE" and chose jdk.

Wednesday, June 1, 2011

RTMP sampler for JMeter to test flash with http

I want to have an integrated performance test environment for both HTTP and RTMP. For HTTP, JMeter is a well known tool, and has a lot of good features. But the problem is that it does not have RTMP sampler.

So I create the RTMP sampler for JMeter. I create the proxy methods using Java Sampler, and from those methods RTMPclient methods are invoked.

I use Red5 to create RTMPclient. There is a good tutorial for customizing the RTMPclient.

public class RTMPJavaSampler extends AbstractJavaSamplerClient {
 
    private static HashMap<String, MyRTMPClient> clientMap = new HashMap<String, MyRTMPClient>();

    public SampleResult runTest(JavaSamplerContext ctx) {
        JMeterVariables vars = JMeterContextService.getContext().getVariables();
        SampleResult sampleResult = new SampleResult();
        boolean ret = false;
        if (ctx.getParameter("MethodName").equals("connect")){
            ret = connect(ctx.getParameter("SessionId"), ctx.getParameter("HostIp"), ctx.getParameter("AppName"), ctx.getParameter("Arg1"), ctx.getParameter("Arg2"));
        } else if (ctx.getParameter("MethodName").equals("disconnect")){
            ret = disconnect(ctx.getParameter("SessionId"));
        }
        if (ret) {
            getLogger().info("Invoke " + ctx.getParameter("MethodName") + " done." );
            sampleResult.setResponseData("test success".getBytes());
            sampleResult.setDataType(SampleResult.TEXT);
            sampleResult.setSuccessful(true);
        } else {
            getLogger().error("Failed to invoke " + ctx.getParameter("MethodName") + ".");
            sampleResult.setResponseData("test failed".getBytes());
            sampleResult.setDataType(SampleResult.TEXT);
            sampleResult.setSuccessful(false);
        }

        sampleResult.setResponseCodeOK();
        sampleResult.setResponseMessageOK();
        return sampleResult;
    }

    @Override
    public Arguments getDefaultParameters() {
        Arguments params = new Arguments();
        params.addArgument("MethodName", "");
        params.addArgument("SessionId", "");
        params.addArgument("HostIp", "");
        params.addArgument("AppName", "");
        params.addArgument("Arg1","");
        params.addArgument("Arg2","");
        return params;
    }

    public boolean connect(String sessionId, String hostIp, String appName, String arg1, String arg2){
        MyRTMPClient client = new MyRTMPClient();  
        client.connect(hostIp, appName, arg1, arg2);
        clientMap.put(sessionId, client);

        while(client.getConnected()==null);
  
        return (client.getConnected());
    }
 
    public boolean disconnect(String sessionId){
        MyRTMPClient client = clientMap.remove(sessionId);
        client.disconnect();
        return true;
    }
}

public class MyRTMPClient extends RTMPClient implements IPendingServiceCallback, INetStreamEventHandler, IEventDispatcher, ClientExceptionHandler {
    private static final Logger logger = LoggingManager.getLoggerForClass();
    private Boolean connected;
    
    public MyRTMPClient(){
        setServiceProvider(this);
        setExceptionHandler(this);
        setStreamEventDispatcher(this);
    }
    
    public void resultReceived(IPendingServiceCall call) {
        Object result = call.getResult();
        if ("connect".equals(call.getServiceMethodName())) {
            if (result instanceof ObjectMap){
                @SuppressWarnings("unchecked")
                String code = ((ObjectMap<String, String>) result).get("code");
                if (StatusCodes.NC_CONNECT_SUCCESS.equals(code)){
                    logger.debug("Connection Success.");
                    connected=true;
                } else {
                    logger.error("resultsReceived: " + code);
                    connected=false;
                }
            }
        } else if ("createStream".equals(call.getServiceMethodName())){
            if (result instanceof Integer) {
                Integer streamIdInt = (Integer) result;
                // onStreamCreated(streamIdInt);
            } else {
                logger.error("Unexpected response for createStream " + result.toString());
            }
        } else {
            logger.info(call.getServiceMethodName());
        }
    }
 
    public void connect(String host, String app, String arg1, String arg2){
        Map<String, Object> defParams = makeDefaultConnectionParams(host, 1935, app);
        HashMap<String, Object>authParams = new HashMap<String, Object>();
        authParams.put("arg1", arg1);
        authParams.put("arg2", arg2);
        Object[] params = new Object[] {authParams};
        connect(host, 1935, defParams, this, params);
        logger.info("Connect done: " + app);
    }
 
    public void onStatus(Object obj){
        @SuppressWarnings("unchecked")
        ObjectMap<String, String> map = (ObjectMap<String, String>) obj;
        if ("status".equals(map.get("level"))){
            logger.debug("onStatus: " + obj.toString());
        } else {
            logger.info("onStatus: " + obj.toString());
        }
    }

    public void onMetaData(Object obj){
        logger.info("onMetaData: " + obj.toString());
    }
 
    public void onStreamEvent(Notify notify) {
        logger.info("onStreamEvent");
    }

    public void dispatchEvent(IEvent event) {
        if (!(event instanceof IRTMPEvent)) {  
            logger.debug("skipping non rtmp event: " + event);  
            return;  
        }  
        IRTMPEvent rtmpEvent = (IRTMPEvent) event;  

        if (rtmpEvent instanceof VideoData) {  
        } else if (rtmpEvent instanceof AudioData) {  
        } else if (rtmpEvent instanceof Notify){
        }        
    }

    public Boolean getConnected() {
        return connected;
    }
}

Wednesday, April 13, 2011

How to write the Hibernate Transaction code on READ COMMITTED isolation level ?

I guess writing transaction codes with hibernate on READ COMMITTED isolation level is a well-tried combination. However why there isn't a good document for tips? Since the READ COMMITTED means Transaction is not atomic, we must care how transactions affect each other.

For example, if several concurrent transactions touch the same row and increment or decrement a value of a column, the value becomes inconsistent if I use save.
@Transactional
public void inc(Long id){
    E e = eDao.get(id);
    int val = e.getCount();
    e.setCount(val+1);
    eDao.save(e);
}

Since on the following sequence:

ThreadA: val = e.getCount();
ThreadB: val = e.getCount();
ThreadA: e.setCount(val+1);
ThreadB: e.setCount(val+1);
ThreadA: eDao.save(e);
ThreadB: eDao.save(e);

The "count" column is just +1 not +2.

I think there are two solutions, though I only tried the first.

First, use an "update" hcl statement. I add following methods at eDao class, and replace above codes to eDao.incCount(id);.
public void incCount(Long id) {
    getSession().createQuery("update E e set e.count = e.count + 1 where e.id=:id")
        .setParameter("id",id)
        .executeUpdate();
}
Since the update statement is an atomic operation (I guess), the above problem does not happen.

Second, replace eDao.save(e) to eDao.update(e).
@Transactional
public void inc(Long id){
    E e = eDao.get(id);
    int val = e.getCount();
    e.setCount(val+1);
    eDao.update(e); // not save!
}
It will cause "StaleStateException" when above sequence happens. Then retry transaction when the exception happens. For example,
int retry=0;
while(retry < MAX_RETRY){
    try {
        eAccessServices.inc(id);
        break;
    } catch (StaleStateException e) {
        retry++;
    }
}
The above code should be written where eAccessServices.inc(id) is called.

I guess there should be better way to solve the issue. If you know it, please let me know.

Updated (04/26/2011):
This is not related to READ COMMITTED isolation level.

Thanks.

Tuesday, April 5, 2011

Wicket: wicket-ajax Channel busy postponing

I got above error when using Wicket + wiQuery + wicket-push(cometd). I struggled for a day and finally, solved it.

In my case, the issue appeared on Firefox, but not happened on Chrome. Also When the page was first loaded, it worked fine. However, after reload the page, it happened. Any wicket's ajax request was blocked. And in the Wicket-Ajax-Debug window, "INFO: Channel busy postponing" message appeared.

The problem is caused by, **I guess**, wicket-ajax.js + jquery. When some jquery javascript start earlier than something, it will harm wicket-ajax.js. In my case, by cometd javascript, (see this) $.cometd.configure() and $.cometd.handshake() starts without waiting loading whole contents in the page. So I fix as follows.

$(window).load(function(){
    $.cometd.configure();
    $.cometd.handshake();
});

This will delay the two functions to start. And problem was gone.

I'm not sure this is a right way to do. Please leave comment if you find the better solution for the same problem.

How to use FlashPlayer 10.2 and 10.3 feature with Flash CS5

I want to use the new features introduced by FlashPlayer 10.2 and 10.3. More specifically, stage video and acoustic echo cancellation. Since adobe does not offer the update for Flash CS5, I have to find the other way to do it.

What I did is follow the instruction specified in the link. In this case, I set up FlashPlayer 10.3.

However there are two missing things:
  • FlashPlayer10_3.xml: In the player node, change the version from "10" to "12". This works as "-swf-version=12" on Flex Compiler.
  • Publish Settings: In the flash tab, change the "Hardware Acceleration" from "None" to "Level 2 - GPU".

The first enables to use new features, and the second enables to use stage video.

Friday, March 25, 2011

How to listen JMS disconnection?

I refere this.
public class JmsContainer extends DefaultMessageListenerContainer {
 
    private static final Logger logger = Logger.getLogger(JmsContainer.class);

    public void start(){
        startAdvisoryListener();
        super.start();
    }
 
    private void startAdvisoryListener(){
        javax.jms.Connection connection;
        try {
            connection = getConnectionFactory().createConnection();
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            connection.start();
            Destination topic = session.createTopic("ActiveMQ.Advisory..>");
            MessageConsumer consumerAdvisory = session.createConsumer(topic);
            consumerAdvisory.setMessageListener(new MessageListener() {
                public void onMessage(Message message) {
                    if (message instanceof ActiveMQMessage) {
                        ActiveMQMessage activeMessage = (ActiveMQMessage) message;
                        Object command = activeMessage.getDataStructure();
                        if (command instanceof ConsumerInfo) {
                            logger.info("A consumer subscribed to a topic or queue: " + command);
                        } else if (command instanceof RemoveInfo) {
                            RemoveInfo removeInfo = (RemoveInfo) command;
                            if (removeInfo.isConsumerRemove()) {
                                logger.info("A consumer unsubscribed from a topic or queue");
                            } else {
                                logger.info("RemoveInfo, a connection was closed: " + command);
                            }
                        } else if (command instanceof ConnectionInfo) {
                            logger.info("ConnectionInfo, a new connection was made: " + command);
                        } else {
                            logger.info("Unknown command: " + command);
                        }
                    }
                }
            });
        } catch (JMSException e) {
            e.printStackTrace();
        }  
    }
}

Wednesday, February 16, 2011

Wicket push reload support

When I look the cometd's instruction page here, it seems the the based source code do not support reload. As shown in the example, it requires a client side javascript code. But it was not written anywhere, as far as I saw.

I start supporting reload extension on my custom wicketstuff-push (see the post). Let's see the code.

public abstract class CometdAbstractBehavior extends AbstractDefaultAjaxBehavior {
    private static final long serialVersionUID = 1L;
 
    // FIXME: put this in application scope, we may have several webapp using
    // CometdBehavior in the same web container!
    private final static String cometdServletPath = getCometdServletPath();
  
    private static final ResourceReference COMETD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "org/cometd.js");  
    private static final ResourceReference JQ_JSON2 =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/json2.js");
    private static final ResourceReference JQ_COMETD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/jquery.cometd.js");

    private static final ResourceReference COMETD_RELOAD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "org/cometd/ReloadExtension.js");
    private static final ResourceReference JQ_COOKIE =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/jquery.cookie.js");
    private static final ResourceReference JQ_COMETD_RELOAD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/jquery.cometd-reload.js");

    @Override
    public void renderHead(final IHeaderResponse response) {
        super.renderHead(response);
        if (channelId == null) {
            throw new IllegalArgumentException("ChannelId in a CometdBehavior can not be null");
        }
        response.renderJavascriptReference(COMETD);
        response.renderJavascriptReference(JQ_JSON2);
        response.renderJavascriptReference(JQ_COMETD);

        response.renderJavascriptReference(COMETD_RELOAD);
        response.renderJavascriptReference(JQ_COOKIE);
        response.renderJavascriptReference(JQ_COMETD_RELOAD);
 
        response.renderJavascript(getInitCometdScript(), "initCometd");
        final String cometdInterceptorScript = getCometdInterceptorScript();
        if (cometdInterceptorScript != null) {
            response.renderJavascript(cometdInterceptorScript, "Interceptor"
                    + getBehaviorMarkupId());
        }
        response.renderJavascript(getSubscriberScript(), "Subscribe"
                + getBehaviorMarkupId());
    }

    protected final CharSequence getInitCometdScript() {
        return new PackagedTextTemplate(CometdBehavior.class, "CometdReloadInit.js").getString() +
            getConfigureCometdScript() + getHandshakeCometdScript(); 
    }

I add a javascript file. This code is written based on the reload example for jQuery (here).
/* handshake listener to report client IDs */
$.cometd.addListener("/meta/handshake", function(message)
{
    if (message.successful)
    {
        $('#previous').html(org.cometd.COOKIE.get('demoLastCometdID'));
        $('#current').html(message.clientId);
        org.cometd.COOKIE.set('demoLastCometdID', message.clientId, {
            'max-age': 300,
            path : '/',
            expires: new Date(new Date().getTime() + 300 * 1000)
        });
    }
    else
    {
        $('#previous').html('Handshake Failed');
        $('#current').html('Handshake Failed');
    }
});

/* Setup reload extension */
$(window).unload(function()
{
    $.cometd.reload();
});


Be care that this works on not only browser reloads, but also loading the same page with different parameters. It means, subscribing to the different channel also happens without handshake.

Saturday, February 12, 2011

Wicket push

Wicketstuff push has dramatically changed at 1.4.13. But the API changes so much at that version, and I cannot make it work. Since the 1.4.12 uses very OLD dojo libraries, I want to migrate to the new version.

While looking the release documents of Cometd 2.1.0, I find that it supports jQuery bindings and Dojo bindings. So, I try to change the bindings to jQuery based on 1.4.12.

The files I have changed are two, CometdAbstractBehavior.java, CometdBehavior.java. And import javascripts from cometd-javascript-jquery-2.1.0.war.

public abstract class CometdAbstractBehavior extends AbstractDefaultAjaxBehavior {
    private static final long serialVersionUID = 1L;

    // FIXME: put this in application scope, we may have several webapp using
    // CometdBehavior in the same web container!
    private final static String cometdServletPath = getCometdServletPath();
 
    private static final ResourceReference COMETD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "org/cometd.js");  
    private static final ResourceReference JQ_JSON2 =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/json2.js");
    private static final ResourceReference JQ_COMETD =
        new CompressedResourceReference(CometdAbstractBehavior.class, "jquery/jquery.cometd.js");

    @Override
    public void renderHead(final IHeaderResponse response) {
        super.renderHead(response);
        if (channelId == null) {
            throw new IllegalArgumentException("ChannelId in a CometdBehavior can not be null");
        }
        response.renderJavascriptReference(COMETD);
        response.renderJavascriptReference(JQ_JSON2);
        response.renderJavascriptReference(JQ_COMETD);

        response.renderJavascript(getInitCometdScript(), "initCometd");
        final String cometdInterceptorScript = getCometdInterceptorScript();
        if (cometdInterceptorScript != null) {
            response.renderJavascript(cometdInterceptorScript, "Interceptor"
                    + getBehaviorMarkupId());
        }
        response.renderJavascript(getSubscriberScript(), "Subscribe"
                + getBehaviorMarkupId());
    }

    protected final CharSequence getInitCometdScript() {
        return getConfigureCometdScript() + getHandshakeCometdScript(); 
    }

    protected final String getConfigureCometdScript() {
        return "$.cometd.configure('" + cometdServletPath + "')\n";
    }
 
    protected String getHandshakeCometdScript() {
        return "$.cometd.handshake()\n";
    }
 
    public final CharSequence getSubscriberScript() {
        return "$.cometd.subscribe('/" + getChannelId() + "', "
                + getPartialSubscriber() + ");\n";
    }

public class CometdBehavior extends CometdAbstractBehavior {

    @Override
    public final String getCometdInterceptorScript() {
  
        final Map map = new HashMap();
        map.put("behaviorMarkupId", getBehaviorMarkupId());
        map.put("url", getCallbackUrl().toString());
  
        return new PackagedTextTemplate(CometdBehavior.class, "CometdDefaultBehaviorTemplate.js").asString(map);
    }

    @Override
    public final CharSequence getPartialSubscriber() {
        return "onEventFor" + getBehaviorMarkupId();
    }

Yes, it works. Thanks for chrome's developer tool. It helps me lot! And I also refer this.

Friday, February 4, 2011

Wicket: NavigatorLabel for GridView

It seems that the NavigatorLabel do not support GridView. So I make it based on the Original NavigatorLabel. It's very simple.

/**
 * Label that provides Showing x to y of z message given for a DataTable. The message can be
 * overridden using the <code>NavigatorLabel</code> property key, the default message is used is of
 * the format <code>Showing ${from} to ${to} of ${of}</code>. The message can also be configured
 * pragmatically by setting it as the model object of the label.
 * 
 * @author Igor Vaynberg (ivaynberg)
 * 
 */
public class GridNavigatorLabel extends Label
{
    private static final long serialVersionUID = 1L;

    // TODO Factor this interface out and let dataview/datatable implement it
    private static interface PageableComponent extends IClusterable
    {
        /**
         * @return total number of rows across all pages
         */
        int getRowCount();

        /**
         * @return current page
         */
        int getCurrentPage();

        /**
         * @return rows per page
         */
        int getRowsPerPage();

        int getColumnsPerRow();
    }

    /**
     * @param id
     *            component id
     * @param table
     *            table
     */
    public GridNavigatorLabel(final String id, final GridView<?> table)
    {
        this(id, new PageableComponent()
        {

            /**
             * 
             */
            private static final long serialVersionUID = 1L;

            public int getCurrentPage()
            {
                return table.getCurrentPage();
            }

            public int getRowCount()
            {
                return table.getRowCount();
            }

            public int getRowsPerPage()
            {
                return table.getRows();
            }
   
            public int getColumnsPerRow()
            {
                return table.getColumns();
            }

        });

    }

    private GridNavigatorLabel(final String id, final PageableComponent table)
    {
        super(id);
        setDefaultModel(new StringResourceModel("NavigatorLabel", this,
            new Model<LabelModelObject>(new LabelModelObject(table)),
            "Showing ${from} to ${to} of ${of}"));
    }

    private class LabelModelObject implements IClusterable
    {
        private static final long serialVersionUID = 1L;
        private final PageableComponent table;

        /**
         * Construct.
         * 
         * @param table
         */
        public LabelModelObject(PageableComponent table)
        {
            this.table = table;
        }

        /**
         * @return "z" in "Showing x to y of z"
         */
        public int getOf()
        {
            return table.getRowCount();
        }

        /**
         * @return "x" in "Showing x to y of z"
         */
        public int getFrom()
        {
            if (getOf() == 0)
            {
                return 0;
            }
            return (table.getCurrentPage() * table.getRowsPerPage() * table.getColumnsPerRow()) + 1;
        }

        /**
         * @return "y" in "Showing x to y of z"
         */
        public int getTo()
        {
            if (getOf() == 0)
            {
                return 0;
            }
            return Math.min(getOf(), getFrom() + table.getRowsPerPage()* table.getColumnsPerRow() - 1);
        }

    }
}

Tuesday, February 1, 2011

Xuggler: resizing video using MediaTool API

There is a full source code for resizing video with MediaTool API. Look this. Great, thanks!

I added two lines.
// resize
IMediaReader reader = ToolFactory.makeReader(baseURL+this.streamName);
reader.open();
reader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);
IMediaTool videoResizeTool = new VideoResizeTool(80,60);
IMediaWriter writer = ToolFactory.makeWriter(baseURL+this.transcodedStreamName, reader);
SetVideoSizeListener setVideoSizeListener = new SetVideoSizeListener(80, 60);
   
// pipeline reader -> VideoResizeTool -> writer -> setVideoSize
reader.addListener(videoResizeTool);
videoResizeTool.addListener(writer);
writer.addListener(setVideoSizeListener);
while (reader.readPacket() == null){
    do {} while(false);
} 

The line 3 is what I mentioned at the previous post. The line 4 is a little bit tricky. I'm not sure why it works, but without the line 4, I got following errors.
13:15:22.483 [Thread-76] WARN  com.xuggle.xuggler - Got error: picture is not of the same width as this Coder (../../../../../../../csrc/com/xuggle/xuggler/StreamCoder.cpp:1251)
Exception in thread "Thread-76" java.lang.RuntimeException: failed to encode video
        at com.xuggle.mediatool.MediaWriter.encodeVideo(MediaWriter.java:771)
        at com.xuggle.mediatool.MediaWriter.encodeVideo(MediaWriter.java:790)
        at com.xuggle.mediatool.MediaWriter.onVideoPicture(MediaWriter.java:1441)
        at com.xuggle.mediatool.AMediaToolMixin.onVideoPicture(AMediaToolMixin.java:166)
        at com.xuggle.mediatool.MediaToolAdapter.onVideoPicture(MediaToolAdapter.java:169)
        at com.mycompany.xuggler.VideoResizeTool.onVideoPicture(VideoResizeTool.java:35)
        at com.xuggle.mediatool.AMediaToolMixin.onVideoPicture(AMediaToolMixin.java:166)
        at com.xuggle.mediatool.MediaReader.dispatchVideoPicture(MediaReader.java:610)
        at com.xuggle.mediatool.MediaReader.decodeVideo(MediaReader.java:519)
        at com.xuggle.mediatool.MediaReader.readPacket(MediaReader.java:475)
        at com.mycompany.xuggler.MediaProcessingThread.startMediaProcessing(MediaProcessingThread.java:96)
        at com.mycompany.xuggler.MediaProcessingThread.run(MediaProcessingThread.java:30)
Is it because of the difference of versions? I'm using the latest revision 1065 not release 3.4.

Xuggler: Using MediaTool API

I see the tutorial. And going to test MediaTool API for transcoding. I follow the section "How To Transcode Media From One Format To Another".

IMediaReader reader = ToolFactory.makeReader(baseURL+this.streamName);
IMediaWriter writer = ToolFactory.makeWriter(baseURL+this.transcodedStreamName, reader);
reader.addListener(writer);
while (reader.readPacket() == null){
    do {} while(false);
}

Greate very simple. Let's run....

11:41:29.983 [Thread-70] ERROR com.xuggle.xuggler - Could not find output format (../../../../../../../csrc/com/xuggle/xuggler/Container.cpp:338)
Exception in thread "Thread-70" java.lang.IllegalArgumentException: could not open: rtmp://127.0.0.1/live/myStream
        at com.xuggle.mediatool.MediaWriter.open(MediaWriter.java:1289)
        at com.xuggle.mediatool.MediaWriter.getStream(MediaWriter.java:998)
        at com.xuggle.mediatool.MediaWriter.encodeVideo(MediaWriter.java:749)
        at com.xuggle.mediatool.MediaWriter.encodeVideo(MediaWriter.java:790)
        at com.xuggle.mediatool.MediaWriter.onVideoPicture(MediaWriter.java:1441)
        at com.xuggle.mediatool.AMediaToolMixin.onVideoPicture(AMediaToolMixin.java:166)
        at com.xuggle.mediatool.MediaReader.dispatchVideoPicture(MediaReader.java:610)
        at com.xuggle.mediatool.MediaReader.decodeVideo(MediaReader.java:519)
        at com.xuggle.mediatool.MediaReader.readPacket(MediaReader.java:475)
        at com.mycompany.xuggler.H264TranscoderThread.testRTMPPublishH264(H264TranscoderThread.java:68)
        at com.mycompany.xuggler.H264TranscoderThread.run(H264TranscoderThread.java:77)

Hm... It fails.... O.K. look into the source code.
public void open()
{
    // open the container

    if (getContainer().open(getUrl(), IContainer.Type.WRITE, mContainerFormat,
          true, false) < 0)
        throw new IllegalArgumentException("could not open: " + getUrl());

    // inform listeners

    super.onOpen(new OpenEvent(this));
    
    // note that we should close the container opened here

    setShouldCloseContainer(true);
}
Guess from the messages, look into the mContainerFormat more deeper..
MediaWriter(String url, IContainer inputContainer)
{
    super(url, IContainer.make());

    // verify that the input container is a readable type

    if (inputContainer.getType() != IContainer.Type.READ)
        throw new IllegalArgumentException(
        "inputContainer is improperly must be of type readable.");

    // verify that no streams will be added dynamically

    if (inputContainer.canStreamsBeAddedDynamically())
        throw new IllegalArgumentException(
            "inputContainer is improperly configured to allow " + 
            "dynamic adding of streams.");

    // record the input container and url

    mInputContainer = inputContainer;

    // create format 

    mContainerFormat = IContainerFormat.make();
    mContainerFormat.setOutputFormat(mInputContainer.getContainerFormat().
        getInputFormatShortName(), getUrl(), null);
}
Hm, it getting from the reader..... Then maybe....., change the code.
IMediaReader reader = ToolFactory.makeReader(baseURL+this.streamName);
reader.open();
IMediaWriter writer = ToolFactory.makeWriter(baseURL+this.transcodedStreamName, reader);
reader.addListener(writer);
while (reader.readPacket() == null){
    do {} while(false);
}
Yes it works.

Monday, January 31, 2011

Live Transcoding using Xuggler with Wowza

I tried to use xuggler for live transcoding with Wowza. Good startpoint is explained at here. Finnaly, it works, but I struggled for a day.

The problem I had was that the ffmpeg do not publish any stream. I saw the Wowza log and found that it connected to server and started getting the stream. But the publishing did not happen.

I used command "ffprobe" to find the issue that ffmpeg did not find audio codec. Followings were messages when I run it. It took around 10 minutes to see the error message.
> ffprobe rtmp://127.0.0.1/live/myStream
FFprobe version SVN-r26402-xuggle-4.0.revision.sh, Copyright (c) 2007-2011 the FFmpeg developers
  built on Jan 31 2011 15:10:13 with gcc 4.1.2 20080704 (Red Hat 4.1.2-48)
  configuration: --prefix=/usr/local/xuggler --extra-version=xuggle-4.0.revision.sh --extra-cflags=-I/usr/local/xuggler/include --extra-ldflags=-L/usr/local/xuggler/lib --enable-shared --enable-gpl --enable-nonfree --enable-version3 --enable-libx264 --enable-libmp3lame --enable-libvorbis --enable-libtheora --enable-libspeex --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-pthreads
  libavutil     50.36. 0 / 50.36. 0
  libavcore      0.16. 1 /  0.16. 1
  libavcodec    52.108. 0 / 52.108. 0
  libavformat   52.93. 0 / 52.93. 0
  libavdevice   52. 2. 3 / 52. 2. 3
  libavfilter    1.74. 0 /  1.74. 0
  libswscale     0.12. 0 /  0.12. 0
[flv @ 0xf816210] Estimating duration from bitrate, this may be inaccurate
Input #0, flv, from 'rtmp://127.0.0.1/live/myStream':
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0.0: Video: flv, yuv420p, 320x240, 1k tbr, 1k tbn, 1k tbc
    Stream #0.1: Audio: [0][0][0][0] / 0x0000, 0 channels
Unsupported codec (id=0) for input stream 1

Oh, I got it. I'm not streaming audio when I start publishing video. And that is the reason why ffmpeg do not start publish any stream. So, I start publishing audio and video at the same time. Yes, it works.

> ffprobe rtmp://127.0.0.1/live/myStream 
FFprobe version SVN-r26402-xuggle-4.0.revision.sh, Copyright (c) 2007-2011 the FFmpeg developers
  built on Jan 31 2011 15:10:13 with gcc 4.1.2 20080704 (Red Hat 4.1.2-48)
  configuration: --prefix=/usr/local/xuggler --extra-version=xuggle-4.0.revision.sh --extra-cflags=-I/usr/local/xuggler/include --extra-ldflags=-L/usr/local/xuggler/lib --enable-shared --enable-gpl --enable-nonfree --enable-version3 --enable-libx264 --enable-libmp3lame --enable-libvorbis --enable-libtheora --enable-libspeex --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-pthreads
  libavutil     50.36. 0 / 50.36. 0
  libavcore      0.16. 1 /  0.16. 1
  libavcodec    52.108. 0 / 52.108. 0
  libavformat   52.93. 0 / 52.93. 0
  libavdevice   52. 2. 3 / 52. 2. 3
  libavfilter    1.74. 0 /  1.74. 0
  libswscale     0.12. 0 /  0.12. 0
[flv @ 0x10056210] Estimating duration from bitrate, this may be inaccurate
Input #0, flv, from 'rtmp://127.0.0.1/live/myStream':
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0.0: Video: flv, yuv420p, 320x240, 1k tbr, 1k tbn, 1k tbc
    Stream #0.1: Audio: nellymoser, 11025 Hz, mono, s16

Hm, the issue remains is how to support muting mic. Maybe some tricks are required.

How to make Xuggler which is checkouted by window's svn in CentOS

When installing Xuggler from repository, I got an error while building it.
[exec] ../libtool: line 399: 
     [exec] : command not found../libtool: line 402: 
     [exec] : command not found../libtool: line 406: 
     [exec] : command not found../libtool: line 427: 
     [exec] : command not found../libtool: line 470: 
     [exec] : command not found../libtool: line 476: 
     [exec] : command not found../libtool: line 486: syntax error near unexpected token `newline' 
     [exec] ../libtool: line 486: `  case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac 
Some people suggest to checkout on a Linux machine.
http://groups.google.com/group/xuggler-users/browse_thread/thread/1b01b8364fff172c/1e1fbc118a97d1a1?lnk=gst&q=libtools+newline#1e1fbc118a97d1a1

But I can't. So I fix them by replacing CRLF to LF.
find . -name "*.sh" -print -exec dos2unix {} \;
find . -name "*.txt" -print -exec dos2unix {} \;
find . -name "*.in" -print -exec dos2unix {} \;
find . -name "*.am" -print -exec dos2unix {} \;
find . -name "*.c" -print -exec dos2unix {} \;
find . -name "*.h" -print -exec dos2unix {} \;
find . -name "configure" -print -exec dos2unix {} \;
find . -name "Makefile" -print -exec dos2unix {} \;

Then xuggler build successfully. But not perfect yet...

How to install PostgreSQL 9.0 in CentOS 5

When installing postgreSQL in CentOS 5.0, you could follow the instruction show in the link http://yum.pgrpms.org/howtoyum.php.

But you could see the error like these...
apr-util-1.2.7-11.el5_5.1.x86_64 from installed has depsolving problems
  --> Missing Dependency: libpq.so.4()(64bit) is needed by package apr-util-1.2.7-11.el5_5.1.x86_64 (installed)
Error: Missing Dependency: libpq.so.4()(64bit) is needed by package apr-util-1.2.7-11.el5_5.1.x86_64 (installed)
 You could try using --skip-broken to work around the problem
 You could try running: package-cleanup --problems
                        package-cleanup --dupes
                        rpm -Va --nofiles --nodigest
The program package-cleanup is found in the yum-utils package.

Hm, so important things are you should install two compat libraries.
sudo yum remove postgresql-libs
wget http://yum.pgsqlrpms.org/reporpms/9.0/pgdg-centos-9.0-2.noarch.rpm
wget http://yum.pgrpms.org/9.0/redhat/rhel-5.0-x86_64/compat-postgresql-libs-4-1PGDG.rhel5.x86_64.rpm
wget http://yum.pgrpms.org/9.0/redhat/rhel-5.0-x86_64/compat-postgresql-libs-4-1PGDG.rhel5.i686.rpm
sudo rpm -ivh ./pgdg-centos-9.0-2.noarch.rpm
sudo rpm -ivh ./compat-postgresql-libs-4-1PGDG.rhel5.x86_64.rpm
sudo rpm -ivh ./compat-postgresql-libs-4-1PGDG.rhel5.i686.rpm
sudo yum install postgresql-server

Saturday, January 29, 2011

How to make an audio activity level meter per NetStream?

The NetStream class do not have any method to get audio activity level. It seems Microphone class has acitivityLevel. It means, players can not get the audio level from the NetStream class, but a publisher can make the audio level from the Microphone class. So it is a solution. The publisher make and send it to players. And they show audio levels on their video screens.

Add highlighted code to the MyNetConnection.

        public function onConnect(event:NetStatusEvent):void { 
            client = new MyNetConnectionClient();
            setupPingTimer();
        }

Then create MyNetConnectionClient. This is a publisher side code.

package my.project.flash {
 
    public class MyNetConnectionClient {

        private var camera:Camera;
        private var microphone:Microphone;
        private var nsPublish:NetStream;
    
        public function MyNetConnectionClient(netConnection:NetConnection) {
            camera = Camera.getCamera();
            microphone = Microphone.getMicrophone();   
            nsPublish = new NetStream(netConnection);
            nsPublish.publish("myPublishStreamId");
            nsPublish.attachCamera(camera);
            nsPublish.attachAudio(microphone);
            microphone.addEventListener(SampleDataEvent.SAMPLE_DATA, nsPublishSampleAudioFrame);
        }
  
        public function nsPublishSampleAudioFrame(event:SampleDataEvent):void {
            var metaData:Object = new Object();
            metaData.audioActivityLevel = microphone.activityLevel;
            nsPublish.send("setAudioActivityLevel", metaData);
        }
    }
}

Then the player side client code. "setAudioActivityLevel" should be the method on a NetStream.client. So you can have audio activity level per stream.

var stream:Stream = new NetStream();
    stream.client = new MyNetStreamClient();
    stream.play("myPublishStreamId");

package my.project.flash {
    public class MyNetStreamClient {
        public function setAudioActivityLevel(param:Object):void {
            doSomethingToShowAutioActivityLevel(param.audioActivityLevel);
        }
    }
}


Hmmm, I changed a lot from my source code, and I do not check it. So I'm not sure it works or not. However, I hope this will give you some hints.

Thursday, January 27, 2011

How to find the network disconnection at a flash client?

Issue

When the flush client only playing (not publishing) video streams, the network disconnection is not detected. When client send nothing to the server, neither NetConnection or NetStream does not receive any NetStatusEvent.

Solution

It's easy. Just issue some message from client to server.

package my.project.flash {
 
    public class MyNetConnection extends NetConnection {

        public function MyNetConnection(){
            super();
        }

        public override function connect(command:String, ... args):void {
            super.connect(command);
            addEventListener(NetStatusEvent.NET_STATUS, onStatus);
        }

        public function onStatus(event:NetStatusEvent):void {
            if (event.info.code == "NetConnection.Connect.Success"){
                onConnect(event);
            } else {
                onDisconnect(event);
            }
        }

        public function onConnect(event:NetStatusEvent):void { 
            setupPingTimer();
        }
  
        public function onDisconnect(event:NetStatusEvent):void {
            // doSomethingOnDisconnect();
        }

        public function setupPingTimer():void {
            var pingTimer:Timer = new Timer(1000);
            pingTimer.addEventListener(TimerEvent.TIMER, sendPing);
            pingTimer.start();
        }
    
        public function sendPing(event:TimerEvent):void {
            call("ping", null); // (no response)
        }
    }
}

How to start JMS MessageListener latter than wicket application.

Issue

I'm using Spring for DI. I write as follows at applicationContext.xml for Wicket, Hibernate and JMS. The issue is caused by jmsListener start before wicketApplication.

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">  
  
    <!-- The wicket application bean -->
    <bean id="wicketApplication" class="my.project.MyWicketApplication" />

    <!-- specifies the place holder for resources of the project -->
    <bean id="placeholderConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="ignoreUnresolvablePlaceholders" value="false" />
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
        <property name="ignoreResourceNotFound" value="false" />
        <property name="locations">
            <list>
                <value>classpath*:/application.properties</value>
            </list>
        </property>
    </bean>

    <!-- JMS beans -->
    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616"/>
    </bean>
 
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
    </bean>
 
    <bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="ServerReceiveQueue"/>
    </bean>
 
    <bean id="jmsListener" class="my.project.jms.JmsListener"/>
  
    <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="jmsListener"/>
    </bean>
  
    <!-- Data source, specifies the jdbc connection-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName">
            <value>${jdbc.driver}</value>
        </property>
        <property name="url">
            <value>${jdbc.url}</value>
        </property>
        <property name="username">
            <value>${jdbc.username}</value>
        </property>
        <property name="password">
            <value>${jdbc.password}</value>
        </property>
    </bean>

    <!-- Transaction manager -->
    <tx:annotation-driven transaction-manager="txManager" />
 
    <!-- setup transaction manager  -->
    <bean id="txManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>
 
    <!-- Hibernate session factory -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.connection.pool_size">5</prop>
                <prop key="hibernate.show_sql">false</prop>
            </props>
        </property>
          
        <property name="packagesToScan">
            <list>
                <value>my.project.domain</value>
            </list>
        </property>
    </bean>

    <context:component-scan base-package="my.project" />

</beans>

The detail of issues..

"jmsListener" is instanciated before PropertyPlaceholderConfigurer start. When jmsListener start it receives some messages. And register some information at another class which has @Service annotation. But, after wicket application start, *maybe* Spring DI by PropertyPlaceholderConfigurer also instanciate several beans which have @Service annotation. And two instance which is instanciated not by PropertyPlaceholderConfigurer and by PropertyPlaceholderConfigurer seems to have different name. That means, even if the data was stored in a static object, it is not shared among them. So the wicket application can not access the data which jmsListener receives.

Solution

I delay the start of jmsListener. Set autoStartup to "false" and call start() inside WicketApplication#init().

<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="jmsListener"/>
        <property name="autoStartup" value="false"/>
    </bean>

public class MyWicketApplication extends WebApplication
{     
    @SpringBean
    private DefaultMessageListenerContainer jmsContainer;
 
    @Override
    protected void init() {
        super.init();
        InjectorHolder.getInjector().inject(this);
        jmsContainer.start();
    }
}

Are there more smarter ways to solve the issue?

How to use Syntax Highlighter on Blogger

I'm trying to insert code in the post. And follow the instruction.
http://www.commonitman.com/2010/09/how-to-use-syntax-highlighter-3-in.html

1. Insert codes between <header> and </header>

2. Try it
<!-- JMS beans -->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
    <property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
 
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="connectionFactory" />
</bean>
  
<bean id="jmsListener" class="my.project.jms.jmsListener" />
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory" />
    <property name="destinationName" value="ServerReceiveQueue" />
    <property name="messageListener" ref="jmsListener" />
    <property name="autoStartup" value="false" />
</bean>

3. Issue
Why </property>s are automatically added?

4. Solved
I need to replace < to &lt; between <pre> and </pre>

Wednesday, January 26, 2011

What I will post

I'm programming a web application. The frameworks I use are Wicket, Spring, JMS and Hibernate. And those run on Jetty, ActiveMQ, and Postgresql. I also use Wowza and Flash for video streamings. I will write tips which I find with this project.