/*
 * Java port of ffmpeg DIVX decoder.
 * Copyright (c) 2004 Jonathan Hueber.
 *
 * Copyright (c) 2001 Fabrice Bellard.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * See Credits file and Readme for details
 * 1a39e335700bec46ae31a38e2156a898
 */
package net.sourceforge.jffmpeg.codecs.video.mpeg4.divx;

import javax.media.Codec;
import javax.media.Format;
import javax.media.format.VideoFormat;
import javax.media.Buffer;
import javax.media.format.RGBFormat;
import javax.media.format.YUVFormat;
import java.awt.Dimension;

import net.sourceforge.jffmpeg.JMFCodec;

import net.sourceforge.jffmpeg.codecs.video.mpeg4.Mpeg4Exception;
import net.sourceforge.jffmpeg.codecs.utils.FFMpegException;
import net.sourceforge.jffmpeg.codecs.utils.BitStream;
import net.sourceforge.jffmpeg.codecs.utils.VLCTable;

import net.sourceforge.jffmpeg.codecs.video.mpeg.DisplayOutput;
import net.sourceforge.jffmpeg.codecs.video.mpeg4.divx.vlc.*;
import net.sourceforge.jffmpeg.codecs.video.mpeg4.divx.rltables.*;
import net.sourceforge.jffmpeg.codecs.video.mpeg4.divx.tables.ScanTable;
import net.sourceforge.jffmpeg.codecs.video.mpeg4.div3.rltables.RLTable;

/**
 * This is a JMF Video Codec.
 * This is a port from ffmpeg - This version targets H263
 */
public class DIVXCodec implements Codec, JMFCodec {
    public static final boolean debug = false;
    public static final boolean debug2 = false;

    /**
     * Width and height
     */
    int width, height;
    int mbWidth, mbHeight;

    /**
     * Current picture cache
     */
    private int[] mb_type;
    private int[] cbp_table;
    private int[] qscale_table;
    private int[][] motion_val;
    private int[] ac_val;
    private int[] dc_val;
    private int[] pred_dir_table;
    private int[] mbintra_table;

    /**
     * mb_type (used in BFrame)
     */
    private int[] mb_type_b_frame;
    private int[] mb_type_ip_frame;

    private int[][] next_motion_val;

    /**
     * The negotiated InputFormat
     */
    private VideoFormat inputFormat;
    private DisplayOutput displayOutput;

    /**
     * Input handler
     */
    protected BitStream in = new BitStream();

    private boolean[][] skipTable;
    private boolean[][] mb_skiptable;

    /**
     * Unquantize DCT values for B frames
     */
    protected void dct_unquantize_h263( int blockNumber ) {
        int i = 0;
        
        if ( mb_intra ) {
            block[ blockNumber ][ 0 ] *= (blockNumber < 4 ) ? y_dc_scale : c_dc_scale;
            i = 1;
        } 

        int qmul = qscale << 1;
        int qadd = (qscale - 1) | 1;
        for ( ; i < 64; i++ ) {
            if ( block[ blockNumber ][ i ] > 0 ) {
                block[ blockNumber ][ i ] = block[ blockNumber ][ i ] * qmul + qadd;
            } else if ( block[ blockNumber ][ i ] < 0 ) {
                block[ blockNumber ][ i ] = block[ blockNumber ][ i ] * qmul - qadd;
            }
        }
    }

    /*
     * Display a Macroblock
     *    - DCT for I macroblocks
     *    - motion followed by addition of DCT for P macroblocks
     */
    protected void MPV_decode_mb( int x, int y ) {
        int pel_motionX = mv[0][0][0];
        int pel_motionY = mv[0][0][1];
        boolean h263_pred = true;
        boolean h263_aic  = false;

        qscale_table[ x + y * mbWidth ] = qscale;
        if ( !mb_intra ) {
            /*
             * Predicted macroblock
             */
            if ( h263_pred || h263_aic ) {
                if ( mbintra_table[ y * mbWidth + x ] != 0 ) {
		    mbintra_table[ y * mbWidth + x ] = 0;
                    /** ff_clean_intra_table_entries */
                    int xy = blockIndex[0];
                    int wrap = blockWrap[0];

                    dc_val[ xy            ] = 0x400;
                    dc_val[ xy + 1        ] = 0x400;
                    dc_val[ xy     + wrap ] = 0x400;
                    dc_val[ xy + 1 + wrap ] = 0x400;

                    /** ac_val */
                    System.arraycopy( blank, 0, ac_val, xy * 16 + mbWidth * 16, 32 );
                    System.arraycopy( blank, 0, ac_val, (xy + wrap)*16 + mbWidth * 16, 32 );

                    /** Chroma */
                    dc_val[blockIndex[4]] = 0x400;
                    dc_val[blockIndex[5]] = 0x400;

                    /* ac pred */
		    if ( debug2 ) DisplayOutput.debug.println( "ac_pred_clean" );
                    System.arraycopy( blank, 0, ac_val, blockIndex[4]*16 + mbWidth * 16, 16 );
                    System.arraycopy( blank, 0, ac_val, blockIndex[5]*16 + mbWidth * 16, 16 );
                }
            }

            /* Store skipped MBs (for use in BFrame) */
            if ( pict_type == I_TYPE || pict_type == P_TYPE ) 
            {
                mb_skiptable[ x ][ y ] = mb_skipped;
            }
            
            /* Can we skip this mb? - note frame buffer */
	    mb_skipped = false;
            if ( !(mb_skipped && skipTable[ x ][ y ]) ) {
                skipTable[ x ][ y ] = mb_skipped;

                /* Motion code */
                if ( pict_type == B_TYPE ) {
                    if ( (mv_dir & MV_DIR_FORWARD) != 0 ) {   //testing
                        if ( mv_type == MV_TYPE_16X16 ) {
                            displayOutput.move( x, y, pel_motionX, pel_motionY, !no_rounding );
                        } else if ( mv_type == MV_TYPE_FIELD ) {
                            displayOutput.moveField( x, y, mv[0][0][0], mv[0][0][1], !no_rounding, false);
                            displayOutput.moveField( x, y, mv[0][1][0], mv[0][1][1], !no_rounding, true );
                        } else if ( mv_type == MV_TYPE_8X8 ) {
                            displayOutput.move8x8( x, y, mv[0][0][0], mv[0][0][1], !no_rounding, 0, 0 );
                            displayOutput.move8x8( x, y, mv[0][1][0], mv[0][1][1], !no_rounding, 8, 0 );
                            displayOutput.move8x8( x, y, mv[0][2][0], mv[0][2][1], !no_rounding, 0, 8 );
                            displayOutput.move8x8( x, y, mv[0][3][0], mv[0][3][1], !no_rounding, 8, 8 );
                        }
                    }

                    /* Backward motion (B Frames) */
                    if ( (mv_dir & MV_DIR_BACKWARD) != 0 ) {
                        boolean merge = (mv_dir & MV_DIR_FORWARD) != 0;
                        if ( mv_type == MV_TYPE_16X16 ) {
                            displayOutput.moveFromNext( x, y, mv[1][0][0], mv[1][0][1], !no_rounding, merge );
                        } else if ( mv_type == MV_TYPE_FIELD ) {
                            displayOutput.moveFieldFromNext( x, y, mv[1][0][0], mv[1][0][1], !no_rounding, false, merge);
                            displayOutput.moveFieldFromNext( x, y, mv[1][1][0], mv[1][1][1], !no_rounding, true, merge );
                        } else if ( mv_type == MV_TYPE_8X8 ) {
                            displayOutput.move8x8FromNext( x, y, mv[1][0][0], mv[1][0][1], !no_rounding, 0, 0, merge );
                            displayOutput.move8x8FromNext( x, y, mv[1][1][0], mv[1][1][1], !no_rounding, 8, 0, merge );
                            displayOutput.move8x8FromNext( x, y, mv[1][2][0], mv[1][2][1], !no_rounding, 0, 8, merge );
                            displayOutput.move8x8FromNext( x, y, mv[1][3][0], mv[1][3][1], !no_rounding, 8, 8, merge );
                       }
		    }
                } else {
                    /* PFrame */
                    if ( mv_type == MV_TYPE_16X16 ) {
                        displayOutput.moveFromIPFrame( x, y, pel_motionX, pel_motionY, !no_rounding );
                    } else if ( mv_type == MV_TYPE_FIELD ) {
                        displayOutput.moveFieldFromIPFrame( x, y, mv[0][0][0], mv[0][0][1], !no_rounding, false);
                        displayOutput.moveFieldFromIPFrame( x, y, mv[0][1][0], mv[0][1][1], !no_rounding, true );
                    } else if ( mv_type == MV_TYPE_8X8 ) {
                        displayOutput.move8x8FromIPFrame( x, y, mv[0][0][0], mv[0][0][1], !no_rounding, 0, 0 );
                        displayOutput.move8x8FromIPFrame( x, y, mv[0][1][0], mv[0][1][1], !no_rounding, 8, 0 );
                        displayOutput.move8x8FromIPFrame( x, y, mv[0][2][0], mv[0][2][1], !no_rounding, 0, 8 );
                        displayOutput.move8x8FromIPFrame( x, y, mv[0][3][0], mv[0][3][1], !no_rounding, 8, 8 );
                    }
                }

                displayOutput.addLuminanceIdct( x * 2,     y * 2,     block[ 0 ] );
                displayOutput.addLuminanceIdct( x * 2 + 1, y * 2,     block[ 1 ] );
                displayOutput.addLuminanceIdct( x * 2,     y * 2 + 1, block[ 2 ] );
                displayOutput.addLuminanceIdct( x * 2 + 1, y * 2 + 1, block[ 3 ] );
                displayOutput.addBlueIdct( x, y, block[ 4 ] );
                displayOutput.addRedIdct( x, y, block[ 5 ] );
            }
        } else {
            /**
             * I type macroblock
             */
            skipTable[ x ][ y ] = false;
            if ( h263_pred || h263_aic ) {
                mbintra_table[ y * mbWidth + x ] = 1;
            }
            
            /**
             * Not motion
             */
            mv_type = MV_TYPE_16X16;
            pel_motionX = 0;
            pel_motionY = 0;
        
	    /* Display Macro block */
            int mbX = x;
            int mbY = y;
            dct_unquantize_h263( 0 );
	    dct_unquantize_h263( 1 );
	    dct_unquantize_h263( 2 );
	    dct_unquantize_h263( 3 );
	    dct_unquantize_h263( 4 );
	    dct_unquantize_h263( 5 ); 
            displayOutput.putLuminanceIdct( x * 2,     y * 2,     block[ 0 ] );
            displayOutput.putLuminanceIdct( x * 2 + 1, y * 2,     block[ 1 ] );
            displayOutput.putLuminanceIdct( x * 2,     y * 2 + 1, block[ 2 ] );
            displayOutput.putLuminanceIdct( x * 2 + 1, y * 2 + 1, block[ 3 ] );
            displayOutput.putBlueIdct( x, y, block[ 4 ] );
            displayOutput.putRedIdct( x, y, block[ 5 ] );
        }
        
        /**
         * Motion cache
         */
        if ( pict_type != B_TYPE && debug ) System.out.println( "mv_type " + mv_type );
        if ( pict_type != B_TYPE && mv_type != MV_TYPE_8X8) {
if ( debug ) System.out.println( "motion_x " + pel_motionX + " motion_y " + pel_motionY + " xy " + (blockIndex[0]-82) );
            for ( int i = 0; i < 4; i++ ) {
                motion_val[blockIndex[ i ]][0] = pel_motionX;
                motion_val[blockIndex[ i ]][1] = pel_motionY;
            } 
        }

        /**
         * Clear blocks for next round
         */
        System.arraycopy( blank, 0, block[0], 0, 64 );
        System.arraycopy( blank, 0, block[1], 0, 64 );
        System.arraycopy( blank, 0, block[2], 0, 64 );
        System.arraycopy( blank, 0, block[3], 0, 64 );
        System.arraycopy( blank, 0, block[4], 0, 64 );
        System.arraycopy( blank, 0, block[5], 0, 64 );
    }

    /**
     * Set the width and height in pixels for this video stream
     */
    protected void initialise( int width, int height ) {
        this.width = width;
        this.height = height;
        mbWidth= (width+15) / 16;
        mbHeight = (height+15) / 16;
        mb_stride = mbWidth;
        blockWrap[ 0 ] = mbWidth * 2 + 1;
        blockWrap[ 1 ] = mbWidth * 2 + 1;
        blockWrap[ 2 ] = mbWidth * 2 + 1;
        blockWrap[ 3 ] = mbWidth * 2 + 1;
        blockWrap[ 4 ] = mbWidth     + 1;
        blockWrap[ 5 ] = mbWidth     + 1;

        mb_type_b_frame  = new int[ mbWidth * mbHeight * 2 ];
        mb_type_ip_frame = new int[ mbWidth * mbHeight * 2 ];
        cbp_table    = new int[ mbWidth * mbHeight * 2 ];
        qscale_table = new int[ mbWidth * mbHeight * 2 ];
        for ( int i = 0; i < qscale_table.length; i++ ) {
            qscale_table[i] = 4;  /* 3 or 4 TODO figure this out!!! */
        }
        motion_val = new int[ mbWidth * mbHeight * 4 * 2][ 2 ];
        ac_val       = new int[ mbWidth * (mbHeight+2) * 4 * 16 * 2 ];
        dc_val       = new int[ mbWidth * mbHeight * 4 * 2 ];
        pred_dir_table  = new int[ mbWidth * mbHeight * 2];
        skipTable = new boolean[ mbWidth * 2 ][mbHeight * 2];
        mb_skiptable = new boolean[ mbWidth * 2 ][mbHeight * 2];
        mbintra_table = new int[ mbWidth * mbHeight * 2 ];
        /* TODO move this to the correct place */
        for ( int i = 0; i < dc_val.length; i++ ) dc_val[i] = 1024;
        displayOutput = new DisplayOutput( mbWidth, mbHeight );
    }

    /**
     * Construct the codec.  Initialization of the codec
     * should be performced via setInputFormat. 
     */
    public DIVXCodec() {
        super();
	System.out.println( "DIVXCodec" );
    }

    /**
     * Construct the codec with the width and height of the 
     * video in pixels.  This is the least data required to
     * use the codec.
     */
    public DIVXCodec(int width, int height) {
        super();
        initialise( width, height );
    }
    
    /**
     * Retrieve the supported input formats.  Currently "DIVX" 
     *
     * @return Format[] the supported input formats
     */
    public Format[] getSupportedInputFormats() {
        return new Format[] { new VideoFormat( "DIVX" ) };
    }
    
    /**
     * Retrieve the supported output formats.  Currently RGBVideo
     *
     * @return Format[] the supported output formats
     */
    public Format[] getSupportedOutputFormats(Format format) {
	System.out.println( "GetOutputFormats" );
        return new Format[] { new RGBFormat() };
    }
    
    /**
     * Negotiate the format for the input data.  
     *
     * Only the width and height entries are used
     *
     * @return Format the negotiated input format
     */
    public Format setInputFormat( Format format ) {
	System.out.println( "SetInputFormats "+format );
        inputFormat = (VideoFormat)format;
        initialise( (int)inputFormat.getSize().getWidth(), 
                    (int)inputFormat.getSize().getHeight() );
        return format;
    }
    
    /**
     * Negotiate the format for screen display renderer.  
     *
     * Only the frame rate entry is used.  All the other 
     * values are populated using the negotiated input formnat.
     *
     * @return Format RGBFormat to supply to display renderer.
     */
    public Format setOutputFormat( Format format ) {
	System.out.println( "SetOutputFormats "+format );
        return new RGBFormat( new Dimension( mbWidth * 16, mbHeight * 16 ), 
                             -1, (new int[0]).getClass(),         // array
                             inputFormat.getFrameRate(),          // Frames/sec
                             32, 0xff0000, 0x00ff00, 0x0000ff) ;  //Colours 
    }


    private static final int mid_pred( int a, int b, int c ) {
        if ( a > b ) {
            if ( c > b ) {
                if ( c > a ) b=a;
                else         b=c;
            }
        } else {
            if ( b > c ) {
                if ( c > a ) b=c;
                else         b=a;
            }
        }
        return b;
    }

    private int h263_predMotionX;
    private int h263_predMotionY;
    private void h263_pred_motion( int blockNumber ) {
        boolean h263_pred = true;
        int[] off = new int[] {2, 1, 1, -1};

        int mot_val_offset = blockIndex[ blockNumber ];
        int[] a;
        int[] b;
        int[] c;
        if ( debug ) {
            b = new int[2];
            c = new int[2];
        }
        int wrap = blockWrap[blockNumber];

        a = motion_val[ mot_val_offset - 1];
        if ( !first_slice_line || blockNumber >= 3 ) {
            /* Normal operation */
	    if ( debug ) System.out.println( "Normal " + (blockIndex[ blockNumber ]-82) );
            b = motion_val[ mot_val_offset- wrap];
            c = motion_val[ mot_val_offset + off[ blockNumber ] - wrap];
            h263_predMotionX = mid_pred(a[0], b[0], c[0]);
            h263_predMotionY = mid_pred(a[1], b[1], c[1]);
        } else {
            /* Edge cases */
            if ( blockNumber == 0 ) {
                if( mb_x  == resync_mb_x ) {
                    h263_predMotionX = 0;
                    h263_predMotionY = 0;
                } else if ( mb_x + 1 == resync_mb_x && h263_pred ) {
                    c = motion_val[ mot_val_offset + off[ blockNumber ] - wrap];
                    if ( mb_x == 0 ) {
                        h263_predMotionX = c[0];
                        h263_predMotionY = c[1];
                    } else {
                        h263_predMotionX = mid_pred(a[0], 0, c[0]);
                        h263_predMotionY = mid_pred(a[1], 0, c[1]);
                    }
                } else {
                    h263_predMotionX = a[0];
                    h263_predMotionY = a[1];
                }
            } else if ( blockNumber == 1 ) {
                if ( mb_x + 1 == resync_mb_x && h263_pred) {
                    c = motion_val[ mot_val_offset + off[ blockNumber ] - wrap];
                    h263_predMotionX = mid_pred(a[0], 0, c[0]);
                    h263_predMotionY = mid_pred(a[1], 0, c[1]);
                }else{
                    h263_predMotionX = a[0];
                    h263_predMotionY = a[1];
                }
            } else {
                /* blockNumber == 2 */
                b = motion_val[ mot_val_offset - wrap];
                c = motion_val[ mot_val_offset + off[ blockNumber ] - wrap];
                if( mb_x == resync_mb_x) {
                    a[0] = 0;
                    a[1] = 0;
                }
                h263_predMotionX = mid_pred(a[0], b[0], c[0]);
                h263_predMotionY = mid_pred(a[1], b[1], c[1]);
            }
        }
        if ( debug ) System.out.println( "A:" + a[0] + " B:" + b[0] + " C:" + c[0] + " " + h263_predMotionX + " " + h263_predMotionY );

    }

    private VLCTable mv_vlc = new MVTable();
    private int h263_decode_motion( int pred, int f_code ) throws FFMpegException {
        int code = in.getVLC( mv_vlc );
        if (code == 0)
            return pred;

        boolean sign = in.getTrueFalse();
        int shift = f_code - 1;
        int val = code;
        if ( debug ) System.out.println( "Sign " + (sign?1:0) + " " + shift + " " + val );
        if ( shift != 0 ) {
            val = (val - 1) << shift;
            val |= in.getBits(shift);
            val++;
        }
        if (sign)
            val = -val;
        val += pred;

        /* modulo decoding */
        boolean h263_long_vectors = false;
        if (!h263_long_vectors) {
            int l = 32 - 5 - f_code;
            val = ((val<<l)&0xffffffff)>>l;
        } else {
            /* horrible h263 long vector mode */
            if (pred < -31 && val < -63)
                val += 64;
            if (pred > 32 && val > 63)
                val -= 64;
        }
        return val;
    }

    private static final int ROUNDED_DIV( int a, int b ) {
        return (((a)>0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b));
    }

    private void mpeg4_pred_ac( int[] block, int n ) {
        if ( debug ) System.out.println( "mpeg4_pred_ac" );
	if(debug2)        DisplayOutput.debug.println( "mpeg4_pred_ac" );
        int i;
        /* find prediction */
        int ac_val_offset  = blockIndex[n] * 16 + mbWidth * 16;
        int ac_val_offset1 = ac_val_offset;
        if (ac_pred) {
            if (!dc_pred_dir) {
                int xy= mb_x - 1 + mb_y * mbWidth -1;  /*note extra -1 here */
                /* left prediction */
                ac_val_offset -= 16;
            
                if(mb_x==0 || qscale == qscale_table[ xy+1] || n==1 || n==3 ) {
                     /* same qscale */
if (debug2) DisplayOutput.debug.println( "A" );
                    for(i=1;i<8;i++) {
                        block[ i<<3 ] += ac_val[ i + ac_val_offset ];
			if ( debug2) DisplayOutput.debug.print( Integer.toHexString( block[ i<<3 ] )+ " " + Integer.toHexString(ac_val[ i + ac_val_offset ]) + " ");
                    }
                } else {
                    /* different qscale, we must rescale */
if (debug2) DisplayOutput.debug.println( "B" );
                    for( i = 1; i < 8; i++ ) {
                        block[ i<<3 ] += ROUNDED_DIV(ac_val[ i + ac_val_offset ] * qscale_table[xy + 1], qscale);
			if ( debug2 ) DisplayOutput.debug.print( Integer.toHexString( block[ i<<3 ] )+ " " + Integer.toHexString(ac_val[ i + ac_val_offset ]) + " " );
                    }
                }
            } else {
                int xy= mb_x + (mb_y - 1)* mbWidth-1;

                /* top prediction */
                ac_val_offset -= 16 * blockWrap[n];

                if ( mb_y == 0 || qscale == qscale_table[ xy+1] || n==2 || n==3) {
                    /* same qscale */
                    for( i = 1; i < 8; i++ ) {
                        block[i] += ac_val[ i + 8 + ac_val_offset ];
			if (debug2) DisplayOutput.debug.print( Integer.toHexString( block[ i ] )+ " " );
                    }
                } else {
                    /* different qscale, we must rescale */
                    for ( i = 1; i < 8; i++ ) {
                        block[i] += ROUNDED_DIV(ac_val[i + 8 + ac_val_offset ]*qscale_table[xy+1], qscale);
			if (debug2 ) DisplayOutput.debug.print( Integer.toHexString( block[ i ] )+ " " );
                    }
                }
            }
        }
        /* left copy */
        for( i = 1; i < 8; i++ ) {
            ac_val[i + ac_val_offset1 ] = block[i<<3];
	    //DisplayOutput.debug.print( block[i<<3] + " " );
	}
        /* top copy */
        for(i=1;i<8;i++) {
            ac_val[8 + i + ac_val_offset1] = block[i];
	    //DisplayOutput.debug.print( block[i] + " " );
        }
	if (debug2 ) DisplayOutput.debug.println();
    }

    private VLCTable dc_lumTable = new dcLuminanceVlc();
    private VLCTable dc_chromTable = new dcChrominanceVlc();

    private int mpeg4_decode_dc( int n ) throws FFMpegException {
        int level = 0;

        int code;
        if ( n < 4 ) {
            code = in.getVLC( dc_lumTable );
        } else {
            code = in.getVLC( dc_chromTable );
        }
        if ( code != 0 ) {
            /* get_xbits() */
            level = in.getBits( code );
            if ( (level & (1<< (code-1))) == 0 ) {
                /* Negative (assume top bit) */
                level = (-1<<code) | (level) + 1;  
            }
            if ( code > 8 ) in.getTrueFalse();
        }
	if ( debug ) System.out.println( "l1 " + level );

        /* ff_mpeg4_pred_dc */
        int scale = (n<4)?y_dc_scale:c_dc_scale;
        int dc_offset = blockIndex[n];
        int wrap = blockWrap[n];

        int a = dc_val[ dc_offset - 1        ];
        int b = dc_val[ dc_offset - 1 - wrap ];
        int c = dc_val[ dc_offset     - wrap ];

        if ( first_slice_line && n != 3 ) {
            if ( n != 2 ) {
                b = 1024;
                c = 1024;
            }
            if ( n != 1 && mb_x == resync_mb_x ) {
                a = 1024;
                b = 1024;
            }
        }
        if ( mb_x == resync_mb_x && mb_y == (resync_mb_y + 1) ) {
            if ( n == 0 || n == 4 || n == 5 ) {
                b = 1024;
            }
        }
        if ( debug ) System.out.println( "offset " + (dc_offset-blockWrap[0]-1) + " a:" + a +" b:" + b + " c:" + c + " fsl " + (first_slice_line?1:0));

        int pred;
        if ( (a-b)*(a-b) < (b-c)*(b-c) ) {
            pred = c;
            dc_pred_dir = true; 
        } else {
            pred = a;
            dc_pred_dir = false; 
        }
        pred = (pred + (scale>>1))/scale;
	if ( debug ) System.out.println( "pred " + pred );
        /* end ff_mpeg4_pred_dc */

        level += pred;
        if ( level < 0 ) level = 0;

        dc_val[ dc_offset ] = level * ((n<4)?y_dc_scale:c_dc_scale);
        return level;
    }

    private RLTable rvlc_rl_intra = new RLRVlcRLIntra();
    private RLTable rl_intra      = new RLIntra();
    private RLTable rvlc_rl_inter = new RLRVlcRLInter();
    private RLTable rl_inter      = new RLInter();

    private boolean dc_pred_dir;
    private void mpeg4_decode_block( int[] block, int n, boolean coded, boolean intra ) throws FFMpegException {
        boolean rvlc = false;
        if ( debug ) System.out.println( "mpeg4_decode_block");
        int[] scan_table = intra_scantable;
        RLTable rl = null;
        int level;
        int i = 0;
        int qmul = 1;
        int qadd = 0;
        if ( intra ) {
            if(qscale < intra_dc_threshold){
                if ( partitioned_frame ) {
                    level = dc_val[ blockIndex[ n ] ];
                    if ( n < 4 ) {
		        level = (level + (y_dc_scale>>1))/y_dc_scale;
                    } else {
		        level = (level + (c_dc_scale>>1))/c_dc_scale;
                    }
                    dc_pred_dir = (0 != ((pred_dir_table[ mb_x + mb_y * mb_stride ] << n) & 32));
                } else {
                   level = mpeg4_decode_dc( n );
                }
                block[0] = level;
                i = 0;
                if ( debug ) System.out.println( "Level " + level );
            } else {
                i = -1;
            }

            if ( coded ) {
                rl = rvlc ? rvlc_rl_intra : rl_intra;

                if ( ac_pred ) {
                    if (!dc_pred_dir) 
                        scan_table = intra_v_scantable;
                    else
                        scan_table = intra_h_scantable;
                } else {
                    scan_table = intra_scantable;
                }
                qmul = 1;
                qadd = 0;
            }
        } else {
            if ( debug ) System.out.println( "Inter" );
            i = -1;
            if (!coded) {
                block_last_index[ n ] = i;
                return;
            }
            rl = rvlc ? rvlc_rl_inter : rl_inter;
   
            scan_table = intra_scantable;

            if(mpeg_quant) {
                qmul=1;
                qadd=0;
            }else{
                qmul = qscale << 1;
                qadd = (qscale - 1) | 1;
            }
        }

        /* Decode DCTELEM */
        if ( debug ) if ( coded ) System.out.println( "Code " + Integer.toHexString( in.showBits(24) ) );
        while ( coded ) {
            int code = in.getVLC( rl );
            level = rl.getLevel( code );
            int run   = rl.getRun( code );
            boolean last = false;
            if ( level == 0 ) {
                if ( rvlc ) {
                } else {
		    if ( in.getTrueFalse() ) {
                        if ( in.getTrueFalse() ) {
                            last = in.getTrueFalse();
                            run  = in.getBits(6);
                            in.getTrueFalse();
                            level = in.getBits(12); //TODO signed
                            if ( (level & (1<<11)) != 0 ) {
                                level |= (-1)<<11;
                            }
                            in.getTrueFalse();
                            level = level * qmul + ((level > 0)?qadd:-qadd);
                            i += run + 1;
                            if ( last ) i += 192;
                        } else {
                            code = in.getVLC( rl );
                            level = rl.getLevel( code );
                            run = rl.getRun( code );
                            i += run + rl.getMaxRun()[run>>7][level] + 1;
                            level = level * qmul + qadd;
                            if ( in.getTrueFalse() ) level = -level;
                        }
                    } else {
                        code = in.getVLC( rl );
                        run  = rl.getRun( code );
                        level = rl.getLevel( code ) * qmul + qadd;
                        i += run;
                        level = level + rl.getMaxLevel()[run>>7][(run-1)&63]*qmul;
                        if ( in.getTrueFalse() ) level = -level;
                    }
                }
            } else {
                i += run;
                level = level * qmul + qadd;
                if ( in.getTrueFalse() ) level = -level;
            }
//	    System.out.print( Integer.toHexString(level) + " " );
            if ( debug ) System.out.println( "Level " + level + " run " + run + " i " + i);
            if ( i > 62 ) {
                i -= 192;
                block[scan_table[i]] = level;
                break;
            }
            block[scan_table[i]] = level;
        }
        /* Add prediction */
        if ( mb_intra ) {
            if( qscale >= intra_dc_threshold ) {
                System.out.println( "TODO preddc" );
//                block[0] = ff_mpeg4_pred_dc(s, n, block[0], &dc_pred_dir, 0);
            
                if(i == -1) i=0;
            }

//            System.out.println( "predac" );
            mpeg4_pred_ac( block, n );
            if ( ac_pred ) i = 63;
        }
        block_last_index[ n ] = i;
//	System.out.println();
    }


    private int get_amv( int i ) throws FFMpegException {
	//        System.out.println( "get_amv " + i );
        return 0;
    }

    private boolean mb_intra;
    private int mv_dir;
    private int mv_type;
    private boolean mcsel;
    private boolean mb_skipped;
    private int motion_pred_x;
    private int motion_pred_y;
    private boolean interlaced_dct;

    private void ff_mpeg4_decode_mb() throws FFMpegException {
        boolean mpeg4_decode_block_intra = false;
        int cbp = 0;
        int cbpc = 0;
        int dquant = 0;
        if ( debug ) System.out.println( "ff_mpeg4_decode_mb" );
        int xy = mb_x + mb_y * mbWidth;
        if ( pict_type == P_TYPE || pict_type == S_TYPE ) {
            if ( debug ) System.out.println( "P/S_TYPE" );
            do {
                if ( in.getTrueFalse() ) {
                    mb_intra = false;
                    for ( int i = 0; i < NUMBER_OF_BLOCKS; i++ ) {
                        block_last_index[i] = -1;
                    }
                    mv_dir = MV_DIR_FORWARD;
                    mv_type = MV_TYPE_16X16;
                    if ( pict_type == S_TYPE && vol_sprite_usage == GMC_SPRITE ) {
                        mb_type[xy] =MB_TYPE_SKIP | MB_TYPE_GMC | MB_TYPE_16x16 | MB_TYPE_L0;
                        mcsel = true;
                        mv[0][0][0] = get_amv(0);
                        mv[0][0][1] = get_amv(1);
                        mb_skipped = false;
                    } else {
                        mb_type[xy] =MB_TYPE_SKIP | MB_TYPE_16x16 | MB_TYPE_L0;
                        mcsel = false;
                        mv[0][0][0] = 0;
                        mv[0][0][1] = 0;
                        mb_skipped = true;
                    }
//System.out.println( "Go to end" );  //TODO mpeg4_resync
                    return;
    //                throw new Error( "Code goto end" );
                }
                cbpc = in.getVLC( inter_MCBPC );

//System.out.println( "CBPC: " + cbpc);
            } while ( cbpc == 20 );
            dquant = cbpc & 8;
            mb_intra = (cbpc & 4) != 0;

            if ( !mb_intra ) {   //REM goto intra:
                int mx = 0, my = 0;
//System.out.println( "No goto intra" );
                mcsel = false;
                if(    pict_type == S_TYPE 
                    && vol_sprite_usage == GMC_SPRITE 
                    && (cbpc & 16) == 0 ) {
                    mcsel = in.getTrueFalse();
                }
                int cbpy = in.getVLC( cbpyVlc )^0xf;

                if ( debug ) System.out.println( "cbpy " + cbpy );
                cbp = (cbpc & 3) | (cbpy << 2);
                if ( dquant != 0 ) {
                    int[] quant_tab = new int[] { -1, -2, 1, 2 };
                    ff_set_qscale( qscale + quant_tab[ in.getBits(2) ] );
                }
                if(!progressive_sequence && cbp != 0) {
                    boolean interlaced_dct= in.getTrueFalse();
                } 

                mv_dir = MV_DIR_FORWARD;
                if ((cbpc & 16) == 0) {
                    if(mcsel){
                        if ( debug ) System.out.println( "Motion 1" );
                        mb_type[xy]= MB_TYPE_GMC | MB_TYPE_16x16 | MB_TYPE_L0;

                        /* 16x16 global motion prediction */
                        mv_type = MV_TYPE_16X16;
                        mx= get_amv(0);
                        my= get_amv(1);
                        mv[0][0][0] = mx;
                        mv[0][0][1] = my;
                        if ( debug ) System.out.println( "mx: " + mx + " my:" + my );
                    } else if((!progressive_sequence) && in.getTrueFalse() ){
                        if ( debug ) System.out.println( "Motion 2" );
    
                        mb_type[xy]= MB_TYPE_16x8|MB_TYPE_L0|MB_TYPE_INTERLACED;

                        /* 16x8 field motion prediction */
                        mv_type= MV_TYPE_FIELD;

                        boolean[][] field_select = new boolean[ 2 ][ 2 ];
                        field_select[0][0] = in.getTrueFalse();
                        field_select[0][1] = in.getTrueFalse();

                        h263_pred_motion(0);
                
                        for(int i=0; i<2; i++){
                            mx = h263_decode_motion(h263_predMotionX, f_code);
                            my = h263_decode_motion(h263_predMotionY/2, f_code);
                            mv[0][i][0] = mx;
                            mv[0][i][1] = my;
                            if ( debug ) System.out.println( "mx: " + mx + " my:" + my );
                        }
                    } else {
                        if ( debug ) System.out.println( "Motion 3" );
                        mb_type[xy]= MB_TYPE_16x16 | MB_TYPE_L0; 
                        /* 16x16 motion prediction */
                        mv_type = MV_TYPE_16X16;
                        h263_pred_motion( 0 );
                        mx = h263_decode_motion(h263_predMotionX, f_code);
                        my = h263_decode_motion(h263_predMotionY, f_code);
            
                        mv[0][0][0] = mx;
                        mv[0][0][1] = my;
                        if ( debug ) System.out.println( "mx: " + mx + " my:" + my );
                    }
                } else {
                     if ( debug ) System.out.println( "Motion 4" );
                     mb_type[xy]= MB_TYPE_8x8 | MB_TYPE_L0; 
                     mv_type = MV_TYPE_8X8;
                     for( int i = 0; i < 4; i++ ) {
                         h263_pred_motion( i );
                         mx = h263_decode_motion(h263_predMotionX, f_code);
                         my = h263_decode_motion(h263_predMotionY, f_code);

                         mv[0][i][0] = mx;
                         mv[0][i][1] = my;

                         motion_val[ blockIndex[ i ] ][0] = mx;
                         motion_val[ blockIndex[ i ] ][1] = my;
 if ( debug ) System.out.println( "8X8 " + (blockIndex[i]-82) + ", mx: " + mx + " my:" + my );
                     }
                }
//System.out.println( "mx " + mx + " my " + my );
            }
        } else if ( pict_type == B_TYPE ) {
            /* BFrames in XVID only */
            if ( debug ) System.out.println( "B_TYPE" );
            mb_intra = false;

            int mx = 0;
            int my = 0;
            if( mb_x == 0 ) {
                for( int i=0; i < 2; i++ ) {
                    last_mv[i][0][0]= 0;
                    last_mv[i][0][1]= 0;
                    last_mv[i][1][0]= 0;
                    last_mv[i][1][1]= 0;
                }
            }

            /* if we skipped it in the future P Frame than skip it now too */
            if( mb_skiptable[ mb_x ][ mb_y ] ) {
                /* skip mb */
                 mv_dir = MV_DIR_FORWARD;
                 mv_type = MV_TYPE_16X16;
                 mv[0][0][0] = 0;
                 mv[0][0][1] = 0;
                 mv[1][0][0] = 0;
                 mv[1][0][1] = 0;
                 mb_type[xy]= MB_TYPE_SKIP | MB_TYPE_16x16 | MB_TYPE_L0; 
	         if ( debug ) System.out.println( "BSKIP" );
                 return;
            }


            boolean modb1 = in.getTrueFalse();
            if( modb1 ) {
                mb_type[xy] =  MB_TYPE_DIRECT2 | MB_TYPE_SKIP | MB_TYPE_L0L1;
                cbp=0;
            } else {
                boolean modb2 = in.getTrueFalse();
                mb_type[xy] = in.getVLC( mb_type_b );
                mb_type[xy] = mb_type_b_map[ mb_type[xy] ];
                if ( modb2 ) {
                    cbp= 0;
                } else {
                    cbp= in.getBits( 6 );
		}

                if ( ((IS_DIRECT_MASK & mb_type[xy]) == 0) && cbp != 0 ) {
                    if ( in.getTrueFalse() ) {
                        ff_set_qscale( qscale + in.getBits( 1 ) * 4 - 2);
                    }
                }

                if ( !progressive_sequence ) {
                    if ( cbp != 0 ) {
                        interlaced_dct= in.getTrueFalse();
                    }

                    if( ((IS_DIRECT_MASK & mb_type[xy]) == 0) && in.getTrueFalse() ) {
                        mb_type[xy] |= MB_TYPE_16x8 | MB_TYPE_INTERLACED;
                        mb_type[xy] &= ~MB_TYPE_16x16;

                        if(USES_LIST(mb_type[xy], 0)){
                            field_select[0][0]= in.getBits(1);
                            field_select[0][1]= in.getBits(1);
                        }
                        if(USES_LIST(mb_type[xy], 1)){
                            field_select[1][0]= in.getBits(1);
                            field_select[1][1]= in.getBits(1);
                        }
                    }
                }

                mv_dir = 0;
                if ( (mb_type[xy] & (MB_TYPE_DIRECT2|MB_TYPE_INTERLACED)) == 0 ) {
                    mv_type = MV_TYPE_16X16;

                    if ( USES_LIST(mb_type[xy], 0) ) {
                        mv_dir = MV_DIR_FORWARD;
if (debug) System.out.println( "BMotion 1" );
                        mx = h263_decode_motion( last_mv[0][0][0], f_code );
                        my = h263_decode_motion( last_mv[0][0][1], f_code );
                        last_mv[0][1][0] = mx;
                        last_mv[0][0][0] = mx;
                        mv[0][0][0]      = mx;

                        last_mv[0][1][1] = my;
       	                last_mv[0][0][1] = my;
                        mv[0][0][1]      = my;
                    }
    
                    if ( USES_LIST(mb_type[xy], 1)){
                        mv_dir |= MV_DIR_BACKWARD;

if (debug) System.out.println( "BMotion 2" );
                        mx = h263_decode_motion( last_mv[1][0][0], b_code );
                        my = h263_decode_motion( last_mv[1][0][1], b_code );
                        last_mv[1][1][0] = mx;
                        last_mv[1][0][0] = mx;
                        mv[1][0][0]      = mx;

                        last_mv[1][1][1] = my;
                        last_mv[1][0][1] = my;
                        mv[1][0][1]      = my;
                    }
                } else if( ((IS_DIRECT_MASK & mb_type[xy]) == 0) ) {
                    mv_type= MV_TYPE_FIELD;

                    if ( USES_LIST(mb_type[xy], 0) ) {
                        mv_dir = MV_DIR_FORWARD;
                
if (debug) System.out.println( "BMotion 3" );
                        for( int i = 0; i < 2; i++ ) {
                            mx = h263_decode_motion(last_mv[0][i][0],  f_code);
                            my = h263_decode_motion(last_mv[0][i][1]/2, f_code);
                            last_mv[0][i][0] = mx;
                            mv[0][i][0] = mx;
                            last_mv[0][i][1] = my * 2;
                            mv[0][i][1] = my;
                        }
                    }
    
                    if ( USES_LIST(mb_type[xy], 1 ) ) {
                        mv_dir |= MV_DIR_BACKWARD;

if (debug) System.out.println( "BMotion 4" );
                        for( int i = 0; i < 2; i++ ) {
                            mx = h263_decode_motion(last_mv[1][i][0], b_code);
                            my = h263_decode_motion(last_mv[1][i][1]/2, b_code);
                            last_mv[1][i][0] = mx;
                            mv[1][i][0] = mx;
                            last_mv[1][i][1] = my * 2;
                            mv[1][i][1] = my;
                        }
                    }
                }
            }
          
            if ( debug ) System.out.println( "mb_type " + mb_type[xy] );
            if( ((IS_DIRECT_MASK & mb_type[xy]) != 0) ){
                if( (IS_SKIP_MASK & mb_type[xy]) != 0 ) {
                    mx = 0;
                    my = 0;
                } else {
if (debug) System.out.println( "BMotion 5" );
                    mx = h263_decode_motion(0, 1);
                    my = h263_decode_motion(0, 1);
                }
 
                mv_dir = MV_DIR_FORWARD | MV_DIR_BACKWARD | MV_DIRECT;
                mb_type[xy] |= ff_mpeg4_set_direct_mv(mx, my);
            }
//System.out.println( "mx " + mx + " my " + my );
        } else {
            if ( debug ) System.out.println( "I_TYPE" );
            do {
                cbpc = in.getVLC( intra_MCBPC );
            } while ( cbpc == 8 );
            dquant = cbpc & 4;
            mb_intra = true;
        }

/* intra: */
        if ( mb_intra ) {
            ac_pred = in.getTrueFalse();

            mb_type[ xy ] = MB_TYPE_INTRA;
            if ( ac_pred ) {
                mb_type[ xy ] = MB_TYPE_INTRA | MB_TYPE_ACPRED;
            }
            if ( debug ) System.out.println( "cbpc " + cbpc );

            int cbpy = in.getVLC( cbpyVlc );

            if ( debug ) System.out.println( "cbpy " + cbpy );

            cbp = (cbpc & 3) | (cbpy << 2);
            if ( dquant != 0 ) {
                ff_set_qscale( qscale + quant_tab[ in.getBits(2) ] );
            }
            if(!progressive_sequence) {
                interlaced_dct= in.getTrueFalse();
            } 
            mpeg4_decode_block_intra = true;
        }

        for ( int i = 0; i < NUMBER_OF_BLOCKS; i++ ) {
            mpeg4_decode_block(block[i], i, (cbp & 32) != 0, mpeg4_decode_block_intra);
            cbp <<= 1;
        }
        /* End of slice */
    }
                int[] quant_tab = new int[] { -1, -2, 1, 2 };

    private int ff_mpeg4_set_direct_mv( int mx, int my ) {
        int time_pb = pb_time;
        int time_pp = pp_time;
        int mb_index = mb_x + mb_y * mbWidth;
        int colocated_mb_type = mb_type_ip_frame[mb_index];
        int xy = blockIndex[0];

	if ( debug) System.out.println( "colocated_mb_type " + colocated_mb_type );
        if ( debug ) System.out.println( "set_direct" );
    
        next_motion_val = motion_val;  /* This works as this function is only called in BFrames */
        if( (IS_8X8_MASK & colocated_mb_type) != 0 ) {
            mv_type = MV_TYPE_8X8;
            for( int i = 0; i < 4; i++ ) {
                xy = blockIndex[i];
                mv[0][i][0] = (next_motion_val[xy][0] * time_pb)/time_pp + mx;
                mv[0][i][1] = (next_motion_val[xy][1] * time_pb)/time_pp + my;
                mv[1][i][0] = (mx == 0) ? (mv[0][i][0] - next_motion_val[xy][0])
                                        : next_motion_val[xy][0]* (time_pb - time_pp) / time_pp;
                mv[1][i][1] = (my == 0) ? (mv[0][i][1] - next_motion_val[xy][1])
                                        : next_motion_val[xy][1]* (time_pb - time_pp) / time_pp;
            }
            return MB_TYPE_DIRECT2 | MB_TYPE_8x8 | MB_TYPE_L0L1;
        } else if ( (IS_INTERLACED_MASK & colocated_mb_type) != 0 ) {
/* TODO Code me *
            mv_type = MV_TYPE_FIELD;
            for ( int i = 0; i < 2; i++ ) {
                int field_select= s->next_picture.ref_index[0][s->block_index[2*i]];
            if(s->top_field_first){
                time_pp= s->pp_field_time - field_select + i;
                time_pb= s->pb_field_time - field_select + i;
            }else{
                time_pp= s->pp_field_time + field_select - i;
                time_pb= s->pb_field_time + field_select - i;
            }
            s->mv[0][i][0] = s->p_field_mv_table[i][0][mb_index][0]*time_pb/time_pp + mx;
            s->mv[0][i][1] = s->p_field_mv_table[i][0][mb_index][1]*time_pb/time_pp + my;
            s->mv[1][i][0] = mx ? s->mv[0][i][0] - s->p_field_mv_table[i][0][mb_index][0]
                                : s->p_field_mv_table[i][0][mb_index][0]*(time_pb - time_pp)/time_pp;
            s->mv[1][i][1] = my ? s->mv[0][i][1] - s->p_field_mv_table[i][0][mb_index][1] 
                                : s->p_field_mv_table[i][0][mb_index][1]*(time_pb - time_pp)/time_pp;
            }
*/
            System.out.println( "TODO:  IS_INTERLACED" );
            return MB_TYPE_DIRECT2 | MB_TYPE_16x8 | MB_TYPE_L0L1 | MB_TYPE_INTERLACED;
        } else {
            int t = (next_motion_val[xy][0] * time_pb)/time_pp + mx;
            mv[0][0][0] = t;
            mv[0][1][0] = t;
            mv[0][2][0] = t;
            mv[0][3][0] = t;

            t = (next_motion_val[xy][1] * time_pb)/time_pp + my;
            mv[0][0][1] = t;
            mv[0][1][1] = t;
            mv[0][2][1] = t;
            mv[0][3][1] = t;

            t = (mx != 0) ? mv[0][0][0] - next_motion_val[xy][0]
                          : next_motion_val[xy][0]*(time_pb - time_pp)/time_pp;
            mv[1][0][0] = t;
            mv[1][1][0] = t;
            mv[1][2][0] = t;
            mv[1][3][0] = t;

            t = (my != 0) ? mv[0][0][1] - next_motion_val[xy][1] 
                          : next_motion_val[xy][1] * (time_pb - time_pp)/time_pp;
            mv[1][0][1] = t;
            mv[1][1][1] = t;
            mv[1][2][1] = t;
            mv[1][3][1] = t;
/*System.out.println( "direct " + mv[0][0][0] + " " + mv[0][0][1] + " "
                              + mv[1][0][0] + " " + mv[1][0][1] + " " + next_motion_val[xy][0] + " "
                              + mx + " " + my + " " + time_pb + " " + time_pp  );
*/
            if( !quarter_sample ) {
                mv_type= MV_TYPE_16X16;
            } else {
                mv_type= MV_TYPE_8X8;
            }
            return MB_TYPE_DIRECT2 | MB_TYPE_16x16 | MB_TYPE_L0L1; //Note see prev line
        }
    }

    /**
     * set qscale and update qscale dependant variables.
     */
    private void ff_set_qscale(int qscale) {
        if (qscale < 1)
            qscale = 1;
        else if (qscale > 31)
            qscale = 31;
        
        this.qscale = qscale;
        int chroma_qscale = chroma_qscale_table[qscale];

        y_dc_scale= ff_mpeg4_y_dc_scale_table[ qscale ];
        c_dc_scale= ff_mpeg4_c_dc_scale_table[ chroma_qscale ];
    }

    private int[] chroma_qscale_table = ff_default_chroma_qscale_table;

    private static int[] ff_default_chroma_qscale_table = new int[] {
        //  0  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
        0, 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
    };


    private VLCTable inter_MCBPC = new InterMcbpc();
    private VLCTable intra_MCBPC = new IntraMcbpc();
    private VLCTable cbpyVlc = new CbpyVlc();

    private static final int IS_INTRA_MASK  = 7;
    private static final int IS_DIRECT_MASK = 0x100;
    private static final int IS_SKIP_MASK = 0x800;
    private static final int IS_8X8_MASK  = 0x40;
    private static final int IS_INTERLACED_MASK = 0x80;

    private static final int MB_TYPE_DIRECT2 = 0x100; 
    private static final int MB_TYPE_ACPRED = 0x200; 
    private static final int MB_TYPE_GMC    = 0x400;
    private static final int MB_TYPE_SKIP   = 0x800;
    private static final int MB_TYPE_INTRA  = 0x1;
    private static final int MB_TYPE_INTERLACED   = 0x80;
    private static final int MB_TYPE_8x8    = 0x40;
    private static final int MB_TYPE_16x8    = 0x10;
    private static final int MB_TYPE_16x16  = 0x8;
    private static final int MB_TYPE_P0L0   = 0x1000;
    private static final int MB_TYPE_P1L0   = 0x2000;
    private static final int MB_TYPE_P0L1   = 0x4000;
    private static final int MB_TYPE_P1L1   = 0x8000;
    private static final int MB_TYPE_L0     = MB_TYPE_P0L0|MB_TYPE_P1L0;
    private static final int MB_TYPE_L1     = MB_TYPE_P0L1 | MB_TYPE_P1L1;
    private static final int MB_TYPE_L0L1   = MB_TYPE_L0   | MB_TYPE_L1;

    private static final int MV_TYPE_FIELD  = 3;
    private static final int MV_DIR_FORWARD = 2;
    private static final int MV_DIR_BACKWARD = 1;
    private static final int MV_DIRECT       = 4;
    private static final int MV_TYPE_8X8    = 1;
    private static final int MV_TYPE_16X16  = 0;

    private static final boolean USES_LIST( int a, int list) {
        return ((a) & ((MB_TYPE_P0L0|MB_TYPE_P1L0)<<(2*(list)))) != 0;
        ///< does this mb use listX, note doesnt work if subMBs
    }


    /* XVID only */
    private VLCTable mb_type_b = new MbTypeBTable();
    private static final int[] mb_type_b_map = new int[] {
        MB_TYPE_DIRECT2 | MB_TYPE_L0L1,
        MB_TYPE_L0L1 | MB_TYPE_16x16,
        MB_TYPE_L1 | MB_TYPE_16x16,
        MB_TYPE_L0 | MB_TYPE_16x16,
    };



    private int[][][] mv = new int[ 2 ][ 4 ][ 2 ];
    private int[][][] last_mv = new int[ 2 ][ 4 ][ 2 ];

    private boolean ac_pred;

    private void mpeg4_decode_partitioned_mb() throws FFMpegException {
        if ( debug ) System.out.println( "mpeg4_decode_partitioned_mb" );
        int mv_dir, mv_type;

        int xy = mb_x + mb_y * mbWidth;
        int mb_type = this.mb_type[ xy ];
        int cbp = cbp_table[ xy ];
        if ( qscale != qscale_table[ xy ] ) {
            ff_set_qscale( qscale_table[ xy ] );
        }
        if ( pict_type == P_TYPE || pict_type == S_TYPE ) {
            for ( int i = 0; i < 4; i++ ) {
                mv[0][i][0] = motion_val[ blockIndex[i] ][0];
                mv[0][i][1] = motion_val[ blockIndex[i] ][1];
            }
            mb_intra = (IS_INTRA_MASK & mb_type) != 0;
            boolean isSkip = (MB_TYPE_SKIP & mb_type) != 0;
            if ( isSkip ) {
                for ( int i = 0; i < NUMBER_OF_BLOCKS; i++ ) {
                    block_last_index[i] = -1;
                }
                mv_dir  = MV_DIR_FORWARD;
                mv_type = MV_TYPE_16X16;

                boolean mcsel = false;
                mb_skipped = true; 
                if ( pict_type == S_TYPE && vol_sprite_usage == GMC_SPRITE ) {
                    mcsel = true;
                    mb_skipped = false;
                }
	    } else if ( mb_intra ) {
                ac_pred = (MB_TYPE_ACPRED & mb_type)!= 0;
                /* Not relevant */
                mv_type = MV_TYPE_16X16;
            } else if ( !mb_intra ) {
                mv_dir = MV_DIR_FORWARD;
                mv_type = MV_TYPE_16X16;
                if ( (MB_TYPE_8x8 & mb_type) != 0 ) {
                    mv_type = MV_TYPE_8X8;
		    if ( debug ) System.out.println( "MV_TYPE_8X8" );
                }
            }
        } else {
            /* IFRAME */
            mb_intra = true;
            ac_pred = (MB_TYPE_ACPRED & mb_type) != 0;
        }

        boolean isSkip = (MB_TYPE_SKIP & mb_type) != 0;
        if ( !isSkip ) {
            for ( int i = 0; i < 6; i++ ) {
//              mpeg4_decode_block( block[i], i, cbp & 32, mb_intra, rvlc );
                cbp <<= 1;
            }
        }

        /* TODO Slice end prediction */
    }
 
    private int[] ff_mpeg4_y_dc_scale_table = new int[] {
        0, 8, 8, 8, 8,10,12,14,16,17,18,19,20,
        21,22,23,24,25,26,27,28,29,30,31,32,34,36,38,40,42,44,46
    };

    private int[] ff_mpeg4_c_dc_scale_table = new int[] {
        0, 8, 8, 8, 8, 9, 9,10,10,11,11,12,12,
        13,13,14,14,15,15,16,16,17,17,18,18,19,20,21,22,23,24,25
    };


    private static final int NUMBER_OF_BLOCKS = 6;
    private int[] blockWrap  = new int[ NUMBER_OF_BLOCKS ];
    private int[] blockIndex = new int[ NUMBER_OF_BLOCKS ];
    private int[][] block    = new int[ NUMBER_OF_BLOCKS ][ 64 ];
    private int[] block_last_index = new int[ NUMBER_OF_BLOCKS ];
    private int[] blank = new int[ 64 ];
    private int y_dc_scale;
    private int c_dc_scale;
    private boolean first_slice_line;
    private int resync_mb_x;
    private int resync_mb_y;

    private void decode_slice() throws FFMpegException {
        int last_resync_gb = in.getPos();
        first_slice_line = true;
        resync_mb_x = mb_x;
        resync_mb_y = mb_y;

        /* TODO chose tables dynamically? */
        ff_set_qscale( qscale );
        if ( debug ) System.out.println( "y_dc_scale " + y_dc_scale );
        if ( debug ) System.out.println( "c_dc_scale " + c_dc_scale );

        for ( ; mb_y < mbHeight; mb_y++ ) {
            /**
             * Initialise the quick lookup arrays
             */
            blockIndex[ 0 ] =   blockWrap[ 0 ] * ( mb_y * 2 + 1 ) - 1;
            blockIndex[ 1 ] =   blockIndex[ 0 ] + 1;
            blockIndex[ 2 ] =   blockIndex[ 0 ] + blockWrap[ 0 ];
            blockIndex[ 3 ] =   blockIndex[ 2 ] + 1;
            blockIndex[ 4 ] =   blockWrap[ 0 ] * ( mbHeight * 2 + 1)
                              + blockWrap[ 4 ] * ( mb_y + 1 );
            blockIndex[ 5 ] =   blockIndex[ 4 ] + blockWrap[ 4 ] * (mbHeight + 1);
            for ( ; mb_x < mbWidth; mb_x++ ) {
                if ( debug ) System.out.println( "XY" + mb_x + "," + mb_y );
                /**
                 * Increment block index
                 */
                blockIndex[ 0 ] += 2;
                blockIndex[ 1 ] += 2;
                blockIndex[ 2 ] += 2;
                blockIndex[ 3 ] += 2;
                blockIndex[ 4 ]++;
                blockIndex[ 5 ]++;

                if (    resync_mb_x == mb_x
       	             && resync_mb_y == mb_y - 1 ) first_slice_line = false;

                /**
                 * Clear blocks
                 */
                for ( int i = 0; i < NUMBER_OF_BLOCKS; i++ ) {
                    System.arraycopy( blank, 0, block[i], 0, blank.length );
                }
                int mv_dir = 0;  //MV_DIR_FORWARD
                int mv_type = 0; //MV_TYPE_16X16
                if ( decode_mb == 0 ) {
                    //ff_h263_decode_mb
                    ff_mpeg4_decode_mb();
//                    throw new FFMpegException( "codeme" );
                } else {
                    mpeg4_decode_partitioned_mb();
                }
                MPV_decode_mb( mb_x, mb_y );
            }
            mb_x = 0;
        }
        if ( debug ) System.out.println( "End of Screen" );
    }

    private int vo_type;
    private int vo_ver_id;
    private int shape;
    private static int FF_ASPECT_EXTENDED = 15;

    private static final int RECT_SHAPE = 0;
    private static final int BIN_ONLY_SHAPE = 2;
    private static final int GRAY_SHAPE = 3;

    private static final int STATIC_SPRITE = 1;
    private static final int GMC_SPRITE = 2;

    private int[] intra_matrix = new int[ 64 ];
    private int[] chroma_intra_matrix = new int[ 64 ];
    private int[] inter_matrix = new int[ 64 ];
    private int[] chroma_inter_matrix = new int[ 64 ];

    private boolean data_partitioning;
    private int time_increment_resolution;
    private int time_increment_bits;
    private boolean vol_control_parameters;
    private int quant_precision;
    private int qscale;
    private boolean mpeg_quant;

    private void decode_vol_header() throws FFMpegException {
        if ( debug ) System.out.println( "decode_vol_header" );
        in.getTrueFalse();
        vo_type = in.getBits( 8 );
        vo_ver_id = 1;
        if ( in.getTrueFalse() ) {
            vo_ver_id = in.getBits( 4 );
            in.getBits( 3 );
        }
        int aspect_ratio_info = in.getBits( 4 );
        if ( aspect_ratio_info == FF_ASPECT_EXTENDED ) {
            int aspected_width  = in.getBits(8);
            int aspected_height = in.getBits(8);
        } else {
            /* TODO lookup table */
        }
        vol_control_parameters = in.getTrueFalse();
        if ( vol_control_parameters ) {
            int chroma_format = in.getBits( 2 );
            if ( chroma_format != 1 ) throw new FFMpegException( "Illegal Chroma" );
            boolean low_delay = in.getTrueFalse();
            if ( in.getTrueFalse() ) {
                in.getBits( 15 );
                in.getTrueFalse();
                in.getBits( 15 );
                in.getTrueFalse();
                in.getBits( 15 );
                in.getTrueFalse();
                in.getBits( 3 );
                in.getBits( 11 );
                in.getTrueFalse();
                in.getBits( 15 );
                in.getTrueFalse();
            }
        } else {
           int low_delay = 0;
        }

        shape = in.getBits( 2 );
        if ( shape != RECT_SHAPE ) if ( debug ) System.out.println( "Not RECT_SHAPE" );
        if ( shape == GRAY_SHAPE ) {
            in.getBits(4);
        }
if ( debug ) System.out.println( "shape: " + shape );
        in.getTrueFalse();
        time_increment_resolution = in.getBits(16);
if ( debug ) System.out.println( "time_increment_resolution: " + time_increment_resolution );
        time_increment_bits = av_log2( time_increment_resolution - 1 );
if ( debug ) System.out.println( "time_increment_bits: " + time_increment_bits );
        if ( time_increment_bits < 1 ) time_increment_bits = 1;
        in.getTrueFalse();
        if ( in.getTrueFalse() ) in.getBits( time_increment_bits );
        if ( shape != BIN_ONLY_SHAPE ) {
            if ( shape == RECT_SHAPE ) {
                in.getTrueFalse();
                int w = in.getBits(13);
                in.getTrueFalse();
                int h = in.getBits(13);
                in.getTrueFalse();
                if ( w != 0 && h != 0 ) {
                    width = w;
                    height = h;
                }
            }
            progressive_sequence = !in.getTrueFalse();
            in.getTrueFalse();
            vol_sprite_usage = in.getBits( vo_ver_id == 1 ? 1:2 );
            if (    vol_sprite_usage == STATIC_SPRITE
                 || vol_sprite_usage == GMC_SPRITE ) {
                if ( vol_sprite_usage == STATIC_SPRITE ) {
                    int sprite_width = in.getBits(13);
                    in.getTrueFalse();
                    int sprite_height = in.getBits(13);
                    in.getTrueFalse();
                    int sprite_left = in.getBits(13);
                    in.getTrueFalse();
                    int sprite_top = in.getBits(13);
                    in.getTrueFalse();
                }
                num_sprite_warping_points = in.getBits(6);
                sprite_warping_accuracy = in.getBits(2);
                boolean brightness_change = in.getTrueFalse();
                if ( vol_sprite_usage == STATIC_SPRITE ) {
                    boolean low_latency_sprite = in.getTrueFalse();
                }
            }
            quant_precision = 5;
            if ( in.getTrueFalse() ) {
                quant_precision = in.getBits(4);
                in.getBits(4);
            }
if ( debug ) System.out.println( "quant_precision: " + quant_precision );

            mpeg_quant = in.getTrueFalse();
            if ( mpeg_quant ) {
                /* Default matricies */
                for ( int i = 0; i < 64; i++ ) {
                    intra_matrix[i] = Tables.ff_mpeg4_default_intra_matrix[i];
                    chroma_intra_matrix[i] = Tables.ff_mpeg4_default_intra_matrix[i];
                    inter_matrix[i] = Tables.ff_mpeg4_default_non_intra_matrix[i];
                    chroma_inter_matrix[i] = Tables.ff_mpeg4_default_non_intra_matrix[i];
                }
                if ( in.getTrueFalse() ) {
                    int last = 0;
                    int i = 0;
                    for ( ; i < 64; i++ ) {
                        int v = in.getBits( 8 );
                        if ( v == 0 ) break;
                        last = v;
                        intra_matrix[ i ] = v;
                        chroma_intra_matrix[i ] = v;
                    }
                    for ( ; i < 64; i++ ) {
                        intra_matrix[ i ] = last;
                        chroma_intra_matrix[i ] = last;
                    }
                }
                if ( in.getTrueFalse() ) {
                    int last = 0;
                    int i = 0;
                    for ( ; i < 64; i++ ) {
                        int v = in.getBits( 8 );
                        if ( v == 0 ) break;
                        last = v;
                        inter_matrix[ i ] = v;
                        chroma_inter_matrix[i ] = v;
                    }
                    for ( ; i < 64; i++ ) {
                        inter_matrix[ i ] = last;
                        chroma_inter_matrix[i ] = last;
                    }
                }
            }

            quarter_sample = false;
            if ( vo_ver_id != 1) quarter_sample = in.getTrueFalse();
            displayOutput.setQuarterSample( quarter_sample );
            in.getTrueFalse();
            resync_marker = !in.getTrueFalse();
            data_partitioning = in.getTrueFalse();
            if ( data_partitioning ) { 
                boolean rvlc = in.getTrueFalse();
            }
            boolean new_pred = false;
            boolean reduced_res_vop = false;
            if ( vo_ver_id != 1 ) {
                new_pred = in.getTrueFalse();
                if (new_pred) {
                   in.getBits(3);  //Not supported
                }
                reduced_res_vop = in.getTrueFalse();
            }
            scalability = in.getTrueFalse();
            if ( scalability ) {
                boolean hierarchy_type = in.getTrueFalse();
                int ref_layer_id = in.getBits(4);
                boolean ref_layer_sampling_dir = in.getTrueFalse();
                int h_sampling_factor_n = in.getBits(5);
                int h_sampling_factor_m = in.getBits(5);
                int v_sampling_factor_n = in.getBits(5);
                int v_sampling_factor_m = in.getBits(5);
                enhancement_type = in.getTrueFalse();
            }
        }
        if ( debug ) System.out.println( "decode_vol_header " + Integer.toHexString( in.showBits( 24 ) ) );
    }

    private static final int av_log2( int a ) {
	int n = 0;
        while ( a != 0 ) {
            a = (a >> 1) &0xffff;
            n++;
        }
        return n;
    }

    private void decode_user_data() throws FFMpegException {
        char[] buffer = new char[ 256 ];
        int i;
        buffer[0] = (char)in.getBits(8);
        for ( i = 1; i < 256; i++ ) {
            buffer[i] = (char)in.showBits(8);
            if ( buffer[i] == 0 ) break;
            in.getBits(8);
        }
        String version = new String( buffer, 0, i );
        if ( debug ) System.out.println( "User data: " + version );
    }

    private void mpeg4_decode_gop_header() throws FFMpegException {
        int hours = in.getBits(5);
        int minutes = in.getBits(6);
        in.getTrueFalse();
        int seconds = in.getBits(6);
        in.getTrueFalse();
        in.getTrueFalse();
        if ( debug ) System.out.println( "" + hours + ":" + minutes + ":" + seconds );
    }

    private static int I_TYPE = 1;
    private static int P_TYPE = 2;
    private static int B_TYPE = 3;
    private static int S_TYPE = 4;
    private boolean progressive_sequence;
    private boolean alternate_scan;

    private int vol_sprite_usage;
    private boolean scalability;
    private boolean enhancement_type;
    private boolean top_field_first;
    private boolean quarter_sample;
    private boolean resync_marker;
    private int num_sprite_warping_points;
    private int sprite_warping_accuracy;
    private int intra_dc_threshold = 0;
    private int decode_mb;
    private int pict_type;
    private boolean partitioned_frame;
    private boolean no_rounding;
    private int f_code;
    private int b_code;
    private int[][] field_select = new int[2][2];

    private int time_base;
    private int last_time_base;
    private int last_non_b_time;
    private int pp_time;
    private int pb_time;

    private int[] inter_scantable;
    private int[] intra_scantable;
    private int[] intra_h_scantable;
    private int[] intra_v_scantable;

    private static final int[] ff_zigzag_direct = ScanTable.getZigZagDirectTable();
    private static final int[] ff_alternate_horizontal_scan = ScanTable.getAlternativeHScanTable();
    private static final int[] ff_alternate_vertical_scan = ScanTable.getAlternativeVScanTable();

    private static final int[] mpeg4_dc_threshold = new int[] {
        99, 13, 15, 17, 19, 21, 23, 0
    };

    private VLCTable sprite_trajectory = new SpriteTrajectoryTable();
    private void mpeg4_decode_sprite_trajectory()  throws FFMpegException{
        int x, y;
        for ( int i = 0; i < num_sprite_warping_points; i++ ) {
            x = 0; y = 0;
            int length = in.getVLC( sprite_trajectory );
            if ( length != 0 ) x= in.getBits( length );
            in.getTrueFalse();
            length = in.getVLC( sprite_trajectory );
            if ( length != 0 ) y= in.getBits( length );
            in.getTrueFalse();
	    //	    System.out.println( "XXXsprite " + x + " " + y );
        }
    }
    
    /* Returns true for full header */
    private boolean decode_vop_header() throws FFMpegException {
        if ( debug ) System.out.println( "decode_vop_header " + Integer.toHexString( in.showBits( 24 ) ) );
        pict_type = in.getBits(2) + I_TYPE;
        if ( debug ) System.out.println( "pict_type " + pict_type );
        partitioned_frame = data_partitioning & (pict_type != B_TYPE);
        decode_mb = partitioned_frame ? 1:0;
        if (time_increment_resolution == 0) {
            time_increment_resolution = 1;
        }
        int time_incr = 0;
        while (in.getTrueFalse()) time_incr++;
        if ( !in.getTrueFalse() ) throw new FFMpegException( "before_time_incr" );
	if ( debug ) System.out.println( "time_incr " + time_incr );
        int time_increment = in.getBits( time_increment_bits );
        if ( pict_type != B_TYPE ) {
            last_time_base = time_base;
            time_base += time_incr;
            int time = time_base * time_increment_resolution + time_increment;
            pp_time = time - last_non_b_time;
//System.out.println( "pp_time " + pp_time + " " + time_base + " " + time_increment_resolution + " " + time_increment );
            last_non_b_time = time;
        } else {
            /* TODO Time stuff */
            int time= (last_time_base + time_incr)*time_increment_resolution + time_increment;
            pb_time = pp_time - (last_non_b_time - time);        
        }
        if ( debug ) System.out.println( "time_increment_bits " + time_increment_bits );
        if ( debug ) System.out.println( "time_increment " + time_increment );

        if ( !in.getTrueFalse() ) throw new FFMpegException( "before_vop_coded" );
        if ( !in.getTrueFalse() ) return false;
        no_rounding = false;
        if (shape != BIN_ONLY_SHAPE
            && ( pict_type == P_TYPE || (pict_type == S_TYPE && vol_sprite_usage==GMC_SPRITE ) ) ) {
	    no_rounding = in.getTrueFalse();
        } 
        if ( shape != RECT_SHAPE ) {
            if ( vol_sprite_usage != 1 || pict_type != I_TYPE ) {
                int width = in.getBits(13);
                in.getTrueFalse();
                int height = in.getBits(13);
                in.getTrueFalse();
                int hor_spat_ref = in.getBits(13);
                in.getTrueFalse();
                int ver_spat_ref = in.getBits(13);
            }
            in.getTrueFalse();
            if( in.getTrueFalse() ) {
                in.getBits(8);
            }
        }

        if ( shape != BIN_ONLY_SHAPE ) {
            int t = in.getBits(3);
            intra_dc_threshold = mpeg4_dc_threshold[ t ];
            alternate_scan = false;
            if ( !progressive_sequence ) {
                top_field_first = in.getTrueFalse();
                alternate_scan = in.getTrueFalse();
            }
        }

        if ( alternate_scan ) {
            /* inter_scantable */
            inter_scantable   = ff_alternate_vertical_scan;
            intra_scantable   = ff_alternate_vertical_scan;
            intra_h_scantable = ff_alternate_vertical_scan;
            intra_v_scantable = ff_alternate_vertical_scan;
        } else {
            inter_scantable   = ff_zigzag_direct;
            intra_scantable   = ff_zigzag_direct;
            intra_h_scantable = ff_alternate_horizontal_scan;
            intra_v_scantable = ff_alternate_vertical_scan;
        }

        if (    pict_type == S_TYPE 
            && (vol_sprite_usage == STATIC_SPRITE || vol_sprite_usage==GMC_SPRITE) ) {
            mpeg4_decode_sprite_trajectory();
        }

        if ( debug ) System.out.println( "Before qscale " + Integer.toHexString( in.showBits( 24 ) ) );
        if ( shape != BIN_ONLY_SHAPE ) {
            if ( debug ) System.out.println( "quant_precision " + quant_precision );
            qscale = in.getBits( quant_precision );
            int choma_qscale = qscale;

            f_code = 1;
	    if ( pict_type != I_TYPE ) {
                f_code = in.getBits(3);
            }

            b_code = 1;
            if ( pict_type == B_TYPE ) {
                b_code = in.getBits(3);
            }


            if ( debug ) System.out.println( "qp:" + qscale + 
                            " fc:" + f_code + "," + b_code +
                            " " + (pict_type == I_TYPE ? "I" : (pict_type == P_TYPE ? "P" : (pict_type == B_TYPE ? "B" : "S"))) +
/*			    " size:" + in.availableBits()  + */
			    " pro:" + (progressive_sequence?1:0) +
			    " alt:" + (alternate_scan?1:0) +
			    " top:" + (top_field_first?1:0) +
                            " " + (quarter_sample?"q":"h") + "pel" +
                            " part:" + (data_partitioning?1:0) + 
                            " resync:" + (resync_marker?1:0) +
                            " w:" + num_sprite_warping_points +
                            " a:" + sprite_warping_accuracy +
                            " rnd:" + (no_rounding?0:1) +
                            " vot:" + vo_type + (vol_control_parameters?" VOLC" :" ") +
		            " dc:" + intra_dc_threshold );

            if ( !scalability ) {
                if ( shape != RECT_SHAPE && pict_type != I_TYPE ) {
                    in.getTrueFalse();
                }
            } else {
                if ( enhancement_type ) {
                    boolean load_backward_shape = in.getTrueFalse();
                    if ( load_backward_shape ) throw new FFMpegException( "backward" );
                }
                in.getBits(2);
            }
        }

        /* y_dc_scale_table */
        return true;
    }


    public static final int VOL_HEADER = 0x120;
    public static final int USER_DATA_STARTCODE = 0x1b2;
    public static final int GOP_STARTCODE = 0x1b3;
    public static final int VOP_STARTCODE = 0x1b6;

    private void ff_mpeg4_decode_picture_header() throws FFMpegException {
        /** Align */
        if ( debug ) System.out.println( "ff_mpeg4_decode_picture_header" );
        in.seek( in.getPos() - ( in.getPos() & 0x7 ) );

        long startCode = 0xff;
        while ( in.availableBits() >= 8 ) {
            startCode = ((startCode << 8) | in.getBits(8)) & 0xffffffff;
            if ( (int)(startCode & 0xffffff00) != 0x100 ) continue;

            if ( debug ) System.out.println( "Start code " + Integer.toHexString((int)startCode & 0x1ff) );
            switch( (int)(startCode & 0x1ff) ) {
                case VOL_HEADER: {
                    decode_vol_header();
                    break;
                }
                case USER_DATA_STARTCODE: {
                    decode_user_data();
                    break;
                }
                case GOP_STARTCODE: {
                    mpeg4_decode_gop_header();
                    break;
                }
                case VOP_STARTCODE: {
                    if ( !decode_vop_header() ) {
                        break;
                    }
                    if ( debug ) System.out.println( "leaving decode_header" );
                    return;
                }
                default: {
                    break;
                }
	    }
            startCode = 0xff;
            in.seek( in.getPos() - ( in.getPos() & 0x7 ) );
        }
    }

    private int mb_x;
    private int mb_y;
    private int mb_stride;

    /**
     * Decode a frame
     */
    protected void decodeFrame( Buffer out ) throws FFMpegException {
        {
            ff_mpeg4_decode_picture_header();
	    if ( pict_type == B_TYPE ) {
                mb_type = mb_type_b_frame;
		if ( debug2 ) DisplayOutput.debug.println( "Picture (B)" );
            } else {
                mb_type = mb_type_ip_frame;
if ( debug2 ) DisplayOutput.debug.println( "Picture (" + (pict_type==I_TYPE?"I":"P") + ")" );
            }

            mb_x = 0;
            mb_y = 0;
            do {
                decode_slice();
            } while ( mb_y < mbHeight );
/*            if ( pict_type != B_TYPE ) {
                displayOutput.endFrame();
                displayOutput.showScreen(out);
            } else {
                displayOutput.endBFrame();
                displayOutput.showScreen(out);
            }
*/
            displayOutput.expandIntoBoarder();
            if ( pict_type != B_TYPE ) {
                displayOutput.showNextScreen(out);
                displayOutput.endIPFrame();
            } else {
                displayOutput.endBFrame();
                displayOutput.showScreen(out);
            }
        }
    }

    /**
     * Converts a byte array Buffer of DIVX video data
     * into an integer array Buffer of video data.
     * 
     * Note: The DIVX codec requires that the input buffer represents
     * exactly one frame
     *
     * @return BUFFER_PROCESSED_OK The output buffer contains a valid frame
     * @return BUFFER_PROCESSED_FAILED A decoding problem was encountered
     */
    public int process( Buffer in, Buffer out ) {
        try {
            /* Extract the input data */
            byte[] data = (byte[])in.getData();
            this.in.addData( data, in.getOffset(), in.getLength() );

            /* Decode and create an output image */
            decodeFrame( out );

            out.setTimeStamp( in.getTimeStamp() );
            out.setFlags( in.getFlags() );
        } catch ( Throwable e ) {
            System.out.println( e );
            e.printStackTrace();
            return BUFFER_PROCESSED_OK;
        }
        return BUFFER_PROCESSED_OK;
    }
    
    /**
     * Initialise the video codec for use.
     */
    public void open() {
	System.out.println( "OPEN!!!" );
    }
    
    /**
     * Deallocate resources, and shutdown.
     */
    public void close() {
    }
    
    /**
     * Reset the internal state of the video codec.
     */
    public void reset() {
    }
    
    /**
     * Retrives the name of this video codec: "DIVX video decoder"
     * @return Codec name
     */
    public String getName() {
        return "DIVX video decoder";
    }
    
    /**
     * This method returns the interfaces that can be used
     * to control this codec.  Currently no interfaces are defined.
     */
    public Object[] getControls() {
        return new Object[ 0 ];
    }

    /**
     * This method returns an interface that can be used
     * to control this codec.  Currently no interfaces are defined.
     */
    public Object getControl( String type ) {
        return null;
    }

    /**
     * Implement the Jffmpeg codec interface
     */
    public boolean isCodecAvailable() {
        return true;
    }

    /**
     * Outofbands video size
     */
    public void setVideoSize( Dimension size ) {
	setInputFormat( new VideoFormat( "DIVX", size, -1, (new byte[ 0 ]).getClass(), 0 ) );
    }

    public void setEncoding( String encoding ) {
    }

    public void setIsRtp( boolean isRtp ) {
    }

    public void setIsTruncated( boolean isTruncated ) {
    }
}
