Tuesday, November 16, 2010

Xuggler Problem: could not find input codec id.

Introduction
Recently I have worked with Xuggler and its MediaTool API to build a video transcoder.
Everything went smoothly until I tried my code with input videos with wmv3 codec (windows media player) or vp6f codec (flv video).
At first I thought that the videos I was using for my tests had some kind of problems that prevented FFMPEG (the library wrapped by Xuggler) from transcoding them correctly.
To my surprise, I found that using FFMPEG directly from the command line didn't cause any kind of problem.
Searching with google I have found people who had the same problem but nobody that had solved it or at least explained it.
With some debugging, I have been able to isolate the problem and find a workaround  for it, at least in my case.
If you are facing a "java.lang.IllegalArgumentException: could not find input codec id" you'll probably want to read on.

Preliminary steps
If you want to repeat the steps described in the rest of this post, you will need:
  • Java and Xuggler installed on your machine: the version used here is Xuggler LGPL 3.4 - "Forrest"
  • Xuggler source code. 
  • The demo trancoder class, TranscodeAudioAndVideo.java, that you can find here.
  • A wmv video with wvm3 video codec. I'll be using this one.

Bug Hunting
First of all let's try to transcode the video with FFMPEG, that comes bundled to the Xuggler distribution.
Run the following command from the command line.
ffmpeg -i kungfu.wmv kungfu.flv
This produces a flv file and proves that the file can be transcoded by FFMPEG.
Now we have to make sure that the Xuggler version we are using reports wmv3 among the supported codecs. This can be done following the instructions in this page.
In the "decodable codecs" section of the output you should find the line

CODEC_TYPE_VIDEO CODEC_ID_WMV3 (wmv3): Windows Media Video 9

Ok then, everything should work just fine. Let's give it a try!
If you run the demo transcoder passing as arguments the input video and the output video paths you'll stumble on the following exception

Exception in thread "main" java.lang.IllegalArgumentException: could not find input codec id
 at com.xuggle.xuggler.IContainerFormat.establishOutputCodecId(IContainerFormat.java:358)
 at com.xuggle.xuggler.IContainerFormat.establishOutputCodecId(IContainerFormat.java:292)
 at com.xuggle.xuggler.IContainerFormat.establishOutputCodecId(IContainerFormat.java:265)
 at com.xuggle.mediatool.MediaWriter.addStreamFromContainer(MediaWriter.java:1141)
 at com.xuggle.mediatool.MediaWriter.getStream(MediaWriter.java:1046)
 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)
        ....
 The exception is raised in the establishOutputCodecId method of the IContainerFormat class

inputCodec = ICodec.findEncodingCodec(inputCodecId);
if (inputCodec == null)
    throw new IllegalArgumentException("could not find input codec id");


Note that the line numbers for what concerns the IContainerFormat class differ a little between the stack trace and the code. That's not a big issue.
It seems that ICodec.findEncodingCodec(inputCodecId) cannot find the codec when called with inputCodecId set to CODEC_ID_WMV3. Why does this happen?

public static ICodec findEncodingCodec(ICodec.ID id) {
    long cPtr = XugglerJNI.ICodec_findEncodingCodec(id.swigValue());
    return (cPtr == 0) ? null : new ICodec(cPtr, false);
}

It is the native static method XugglerJNI.ICodec_findEncodingCodec(id.swigValue()) that return 0 when its argument is set to the swig value of the CODEC_ID_WMV3(74) . I presume that this happens for a bug in the C++ code.
This seems to be confirmed by following class

import com.xuggle.xuggler.ICodec;
public class CodecMess {
public static void main(String[] args) {
   for (ICodec codec : ICodec.getInstalledCodecs()) {
      try{
         if(ICodec.ID.CODEC_ID_WMV3.equals(codec.getID())){
        System.out.println(String.format("Codec found!! Name: %s. id: %d. Description: %s", codec.getName(), codec.getIDAsInt(), codec.getLongName()));
         }
      } catch (Exception e){}
   }  
}
}


The output is:

Codec found!! Name: wmv3. id: 74. Description: Windows Media Video 9

which confirms that apparently there are no good reasons for the method ICodec.findEncodingCodec to return null.

Workaround
Now that we have understood a little more in details what's going on, is time to think to the countermeasures.
If we look at the stack trace again, we can see that code that causes the problem is invoked in the method MediaWriter.addStreamFromContainer. Let's take a look:

addVideoStream(inputStream.getIndex(),
            inputStream.getId(),
            format.establishOutputCodecId(inputID),
            inputCoder.getFrameRate(),
            inputCoder.getWidth(),
            inputCoder.getHeight());


In my case, I didn't need the call to format.establishOutputCodecId(inputID), because in the end what I wanted was the output codec to be always flv.
So what I wanted to do was to extend the MediaWriter class and modify its addStreamFromContainer method. Unfortunately the method is private, and the method that calls it is private. So, even if it is very unelegant, I wrote my own MediaWriter class modifying the code excerpt to

addVideoStream(inputStream.getIndex(),
            inputStream.getId(),
            ICodec.ID.CODEC_ID_FLV1,
            inputCoder.getFrameRate(),
            inputCoder.getWidth(),
            inputCoder.getHeight());


That worked for me and solved the issue with all the videos that caused problems before. It is not elegant, I am not even sure it is formally correct to do that, but after a long fight that was the pragmatic compromise I settled by.
There is a different workaround described here. Unfortunately it doesn't work if you need to change the bitrate of the output video.

Conclusions 
I find this problem with Xuggler very strange and even if I found a workaround, I'm not happy about it at all. If you have encountered the same problem, I suggest you to vote for this bug to be solved here. If this is not a bug, and the Xuggler team rejects it, maybe at least they will clarify where we are going wrong.