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.