package jp.sourceforge.nicoro;

import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;

import dalvik.system.VMRuntime;


import static jp.sourceforge.nicoro.Log.LOG_TAG;
import jp.sourceforge.nicoro.R;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageView;

public class NicoroFFmpegPlayer extends AbstractNicoroPlayer implements FFmpegInfoCallback {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG && false;
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;
	
	private static final int VIDEO_BUFFER_CACHE_SIZE = 20;
	
	private static final int MSG_ID_VIDEO_CACHED = MSG_ID_SUB_OFFSET + 0;
	private static final int MSG_ID_SURFACE_CREATED = MSG_ID_SUB_OFFSET + 1;
	private static final int MSG_ID_SURFACE_DESTROYED = MSG_ID_SUB_OFFSET + 2;
	
	public static final int CODE_DECODE_FRAME_INVALID = 0x80000001;
	public static final int CODE_DECODE_FRAME_VIDEO = 0;
	public static final int CODE_DECODE_FRAME_AUDIO = 1;
	public static final int CODE_DECODE_FRAME_VIDEO_SKIP = 2;
	public static final int CODE_DECODE_FRAME_SKIP = 3;
	public static final int CODE_DECODE_FRAME_ERROR_OR_END = -1;
	
	public static final int CODE_FRAME_TYPE_VIDEO = 0;
	public static final int CODE_FRAME_TYPE_AUDIO = 1;
	public static final int CODE_FRAME_TYPE_UNKNOWN = -1;
	public static final int CODE_FRAME_TYPE_ERROR_OR_END = -2;
	
	public static final int CODE_SKIP_VIDEO_FRAME_NONE = 0;
	public static final int CODE_SKIP_VIDEO_FRAME_NONREF = 1;
	public static final int CODE_SKIP_VIDEO_FRAME_BIDIR = 2;
	public static final int CODE_SKIP_VIDEO_FRAME_NONKEY = 3;
	
	private SurfaceView mSurfaceView;
	private SurfaceHolder mSurfaceHolder;
//	private ImageView mIconPause;
	/*private*/ volatile boolean mIsFinish;
	/*private*/ volatile boolean mIsDecodedComplete;
	/*private*/ StreamAudioPlayer mStreamAudioPlayer = new StreamAudioPlayer();
	
	private boolean mIsSurfaceOk;
	private boolean mIsVideoCachedOk;
	
	private int mWidthSurface = 0;
	private int mHeightSurface = 0;

    private int mDrawOffsetY = 0;

	private Thread mThreadDecode = null;
	private Thread mThreadPlay = null;

    private Matrix mMatrixScale = new Matrix();
    
    private Paint mPaint = new Paint();
    
    /*private*/ ConcurrentLinkedQueue<VideoBuffer> mVideoBufferPool =
    	new ConcurrentLinkedQueue<VideoBuffer>();
    // TODO mVideoBufferEmptyPoolに戻し切れていないインスタンスがある
    /*private*/ ConcurrentLinkedQueue<VideoBuffer> mVideoBufferEmptyPool =
    	new ConcurrentLinkedQueue<VideoBuffer>();
    /*private*/ ConcurrentLinkedQueue<VideoBuffer> mVideoBufferCached =
    	new ConcurrentLinkedQueue<VideoBuffer>();
	
//    private boolean mSkipTest;
//    private int mSkipDrawTest = 0;
    
    private int mFrameCountPlay;
    private int mFrameCountDecode;
    private int mAudioDecodedBytes;
	private long mStartFrameTime;
	private volatile int mFrameSkipDecode;
	private Rational mRationalVideoPlay = new Rational();
	private Rational mRationalUpdateStartFrameTime = new Rational();
	
	private Rational mRationalDebugLog = (DEBUG_LOGV || DEBUG_LOGD) ? new Rational() : null;
    
    private volatile boolean mIsPlaying = false;
    
    private boolean mIsPause = false;
    private volatile boolean mIsCaching = false;
    private volatile boolean mIsWaitingPlay = false;
    
    private boolean mCacheWhenChoppy;
    
	private byte[] mReadBuffer = new byte[1024*24];
	
	private volatile int mRequestSeekSecond = -1;
	private volatile boolean mRequestSeekOnDecoder = false;
	
	private int mVideoOriginalWidth = 0;
	private int mVideoOriginalHeight = 0;
    
    // NativeのNicoroFFmpegPlayerクラスのポインタ
    /*private*/ volatile long mNativeInstance;
	
    // Nativeからアクセスされるもの
    FFmpegData mFFmpegData = new FFmpegData();
//	private int mWidth = 0;
//	private int mHeight = 0;
//	/*private*/ short[] mAudioBuffer;
//	/*private*/ int mAudioBufferSize;
//	/** 動画デコード時間 分子 */
//	/*private*/ int mTimeNumVideo = 0;
//	/** 動画デコード時間 分母 */
//	/*private*/ int mTimeDenVideo = 0;
//	/** 音声デコード時間 分子 */
//	/*private*/ int mTimeNumAudio = 0;
//	/** 音声デコード時間 分母 */
//	/*private*/ int mTimeDenAudio = 0;
    
    private class DecoderThread implements Runnable {
		@Override
		public void run() {
			mFFmpegData.mAudioBufferSize = 0;
	    	
			// キャッシュ
			mIsCaching = true;
//			cacheDecodeFirst();
			cacheDecodeFirst2();
			mIsCaching = false;
			
			if (mIsFinish) {
				return;
			}
			
			// キャッシュぶんの音声再生開始
			mStreamAudioPlayer.flushBufferToAudioTrack();
			mStreamAudioPlayer.flushAudioTrack();
			
			// 再生開始
			assert mThreadPlay == null;
			mThreadPlay = new Thread(new PlayerThread());
			mThreadPlay.start();
			
			// TODO: とりあえずAudioTrack開始までポーリング
			mStreamAudioPlayer.waitRealStart();
			
//			decodeMain();
			decodeMain2();
		}
    }
    
    private class PlayerThread implements Runnable {
		@Override
		public void run() {
			
			// TODO: とりあえずAudioTrack開始までポーリング
			mStreamAudioPlayer.waitRealStart();
			
			while (!mIsFinish) {
				mIsWaitingPlay = false;
				if (mIsPause || mIsCaching || mRequestSeekOnDecoder) {
					mIsWaitingPlay = true;
					try {
						Thread.sleep(10L);
					} catch (InterruptedException e) {
					}
					continue;
				}
				
				VideoBuffer videoBuffer = mVideoBufferCached.poll();
				if (videoBuffer == null) {
					if (mIsDecodedComplete) {
						// デコード完了済みで再生するものも残っておらず
						break;
					}
					// TODO: スリープ時間適当
	        		try {
						Thread.sleep(10L);
					} catch (InterruptedException e) {
					}
				} else {
					boolean frameSkip = judgeFrameSkipAtPlayerThread();
					
//					assert videoBuffer.frame == mFrameCountPlay;
					if (videoBuffer.frame != mFrameCountPlay) {
						if (DEBUG_LOGD) {
							Log.d(LOG_TAG, Log.buf().append("PlayerThread: not match")
									.append(" videoBuffer.frame=").append(videoBuffer.frame)
									.append(" mFrameCountPlay=").append(mFrameCountPlay).toString());
						}
					}
					CacheReference<int[]> drawBufferRef = videoBuffer.buffer;
					int[] drawBuffer;
					if (drawBufferRef != null) {
						drawBuffer = drawBufferRef.strengthen();
						assert drawBuffer != null;
					} else {
						drawBuffer = null;
					}
			    	
					if (drawBuffer != null) {
						if (!frameSkip) {
							if (DEBUG_LOGV) {
								Log.v(LOG_TAG, "PlayerThread: drawBufferToSurface");
							}
				        	drawBufferToSurface(drawBuffer);
						}
						if (mFrameCountPlay == 0) {
							updateStartFrameTime();
						}
						assert drawBufferRef != null;
						drawBufferRef.weaken();
						mVideoBufferPool.add(videoBuffer);
					} else {
						// デコードスキップ
						mVideoBufferEmptyPool.add(videoBuffer);
					}
					++mFrameCountPlay;
				}
				
				if (DEBUG_LOGV) {
					Log.v(LOG_TAG, Log.buf().append("PlayerThread: mVideoBufferCached.size=")
							.append(mVideoBufferCached.size()).append(" mVideoBufferPool.size=")
							.append(mVideoBufferPool.size()).toString());
				}
			}
			
			// 音声データ残っていたら待つ
			mStreamAudioPlayer.waitPlayEnd();
			mStreamAudioPlayer.releaseAll();
		}
    }
    
    private static class VideoBuffer {
    	public int frame;
    	public CacheReference<int[]> buffer;
    }
    
    public NicoroFFmpegPlayer() {
    	mHandler = new MessageHandler() {
    		@Override
    		public void handleMessage(Message msg) {
    			if (mHandler == null) {
    				if (DEBUG_LOGD) {
    					Log.d(LOG_TAG, Log.buf().append("Activity was destroyed. ignore message=")
    							.append(msg.toString()).toString());
    				}
    				return;
    			}
    			switch (msg.what) {
    			case MSG_ID_VIDEO_CACHED:
    				mIsVideoCachedOk = true;
    				if (canStartPlay()) {
    					startPlay();
    				}
    				break;
    			case MSG_ID_SURFACE_CREATED:
    				mIsSurfaceOk = true;
    				if (canStartPlay()) {
    					startPlay();
    				}
    				break;
    			case MSG_ID_SURFACE_DESTROYED:
    				mIsSurfaceOk = false;
    				break;
    			default:
    				super.handleMessage(msg);
    				break;
    			}
    		}
    	};
    }
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
//    	VMRuntime.getRuntime().setMinimumHeapSize(Long.MAX_VALUE);
        
        Intent intent = getIntent();
        mVideoLoader = createVideoLoader(intent, getApplicationContext());
        if (mVideoLoader != null) {
        	mVideoLoader.setEventListener(new VideoLoader.EventListener() {
				@Override
				public void onCached(VideoLoader videoLoader) {
					if (mHandler != null) {
						mHandler.sendEmptyMessage(MSG_ID_VIDEO_CACHED);
					}
				}
				@Override
				public void onFinished(VideoLoader videoLoader) {
					// TODO 自動生成されたメソッド・スタブ
					
				}
				@Override
				public void onOccurredError(VideoLoader videoLoader, String errorMessage) {
					if (mHandler != null) {
						Message message = mHandler.obtainMessage(MSG_ID_VIDEO_OCCURRED_ERROR, errorMessage);
						mHandler.sendMessage(message);
					}
				}
				@Override
				public void onNotifyProgress(int num, int den) {
					if (mHandler != null) {
						if (!mIsPlaying) {
							mHandler.removeMessages(MSG_ID_VIDEO_NOTIFY_PROGRESS);
							Message message = mHandler.obtainMessage(MSG_ID_VIDEO_NOTIFY_PROGRESS, num, den);
							mHandler.sendMessage(message);
						}
					}
				}
        	});
        	mVideoLoader.startLoad();
        }

        mIsSurfaceOk = false;
        mIsVideoCachedOk = false;
        
        setContentView(R.layout.nicoro_ffmpegplayer);
        mSurfaceView = (SurfaceView) findViewById(R.id.surface);
		DisplayMetrics metrics = new DisplayMetrics();
		getWindowManager().getDefaultDisplay().getMetrics(metrics);
		mSurfaceView.getLayoutParams().width =
//			(int) (metrics.heightPixels * 4 / 3 / metrics.density);
			(int) (metrics.heightPixels * 4 / 3);
		mSurfaceView.getLayoutParams().height =
//			(int) (metrics.heightPixels / metrics.density);
			(int) (metrics.heightPixels);
		mSurfaceView.requestLayout();
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
			@Override
			public void surfaceChanged(SurfaceHolder holder, int format,
					int width, int height) {
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, Log.buf().append("surfaceChanged: format=").append(format)
							.append(" width=").append(width).append(" height=").append(height).toString());
				}
				mWidthSurface = width;
				mHeightSurface = height;
				setScaleMatrix();
			}
			@Override
			public void surfaceCreated(SurfaceHolder holder) {
				if (mHandler != null) {
					mHandler.sendEmptyMessage(MSG_ID_SURFACE_CREATED);
				}
			}
			@Override
			public void surfaceDestroyed(SurfaceHolder holder) {
				if (mHandler != null) {
					mHandler.sendEmptyMessage(MSG_ID_SURFACE_DESTROYED);
				}
				mIsFinish = true;
//				mStreamAudioPlayer.finish();
			}
        });
        
//        mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
//			@Override
//			public boolean onTouch(View v, MotionEvent event) {
//				if (event.getAction() == MotionEvent.ACTION_DOWN) {
//					return switchPause();
//				}
//				return false;
//			}
//		});
		
//		mIconPause = (ImageView) findViewById(R.id.icon_pause);
//		mIconPause.setAlpha(192);
        
		initializeView();
        
		mIsPlaying = false;
		mIsPause = false;
		mIsCaching = false;
		
    	SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
    	boolean bitmapFilter = sharedPreferences.getBoolean(
				getString(R.string.pref_key_ffmpeg_bitmap_filter), false);
		mPaint.setFilterBitmap(bitmapFilter);
		mCacheWhenChoppy = sharedPreferences.getBoolean(
				getString(R.string.pref_key_ffmpeg_cache_when_choppy), false);
    }
    
    @Override
    protected void onDestroy() {
    	mIsFinish = true;
    	mStreamAudioPlayer.finish();
    	if (mThreadPlay != null) {
	    	try {
				mThreadPlay.join(1000L * 2);
			} catch (InterruptedException e) {
	    		Log.d(LOG_TAG, e.getMessage(), e);
			}
			mThreadPlay = null;
    	}
    	if (mThreadDecode != null) {
	    	try {
				mThreadDecode.join(1000L * 2);
			} catch (InterruptedException e) {
	    		Log.d(LOG_TAG, e.getMessage(), e);
			}
			mThreadDecode = null;
    	}
    	synchronized (this) {
			destroyNativeInstance();
		}
    	super.onDestroy();
    	mSurfaceView = null;
    	mSurfaceHolder = null;
//    	mStreamAudioPlayer = null;
    	mMatrixScale = null;
    	mPaint = null;
    	mVideoBufferPool = null;
    	mVideoBufferCached = null;
    	mFFmpegData.mAudioBuffer = null;
    }
    
    @Override
    protected void finalize() throws Throwable {
    	try {
    		super.finalize();
    	} finally {
        	mIsFinish = true;
        	mStreamAudioPlayer.finish();
        	try {
        		if (mThreadPlay != null) {
        			mThreadPlay.join(1000L * 2);
        		}
    		} catch (InterruptedException e) {
        		Log.d(LOG_TAG, e.getMessage(), e);
    		}
        	try {
        		if (mThreadDecode != null) {
        			mThreadDecode.join(1000L * 2);
        		}
    		} catch (InterruptedException e) {
        		Log.d(LOG_TAG, e.getMessage(), e);
    		}
    		synchronized (this) {
    			destroyNativeInstance();
			}
    	}
    }
    
	@Override
	protected boolean canStartPlay() {
		return mIsSurfaceOk && mIsVideoCachedOk && mMessageData.mIsMessageOk && mMessageDataFork.mIsMessageOk;
	}
    
	@Override
    protected void startPlay() {
		super.startPlay();
    	System.gc();
    	
		destroyNativeInstance();
        mNativeInstance = createNative();
        
    	loadStream(mNativeInstance, mReadBuffer, mVideoLoader, mFFmpegData);
    	if (DEBUG_LOGD) {
    		Log.d(LOG_TAG, Log.buf().append("FrameRate=").append(mFFmpegData.mFrameRateNumVideo)
    				.append("/").append(mFFmpegData.mFrameRateDenVideo).toString());
    	}

        createAudioTrack(mNativeInstance, this);
		createDrawBuffer(mNativeInstance, this);
		
		int audioBufferByteSize = mStreamAudioPlayer.getAudioBufferByteSize();
		mFFmpegData.mAudioBuffer = new short[audioBufferByteSize/2];
		mFFmpegData.mAudioBufferSize = 0;
		
		mFrameCountPlay = 0;
		mFrameCountDecode = 0;
		mAudioDecodedBytes = 0;
		mStartFrameTime = 0L;
		mFrameSkipDecode = CODE_SKIP_VIDEO_FRAME_NONE;
        
    	mIsPlaying = true;
    	mIsPause = false;
    	mIsCaching = false;
		mIsFinish = false;
		mRequestSeekSecond = -1;
		mRequestSeekOnDecoder = false;
		mStreamAudioPlayer.prepareStart();
		mIsDecodedComplete = false;
		assert mThreadDecode == null;
		mThreadDecode = new Thread(new DecoderThread());
		mThreadDecode.setPriority(
				mThreadDecode.getThreadGroup().getMaxPriority());
		mThreadDecode.start();
		
		mHandler.sendEmptyMessage(MSG_ID_INFO_PLAY_DATA_UPDATE);
    }
    
	@Override
	protected StringBuilder appendCurrentPlayTime(StringBuilder builder) {
		if (DEBUG_LOGV) {
			Rational r = mRationalDebugLog;
			getCurrentPositionVideoPlay(r);
			if (r.den == 0) { r.den = 1; }
			Log.v(LOG_TAG, Log.buf().append("VideoPlayTime: ")
					.append(r.num).append("/").append(r.den).append(" ")
					.append(r.getDivideFloat()).toString());
			getCurrentPositionAudioPlay(r);
			if (r.den == 0) { r.den = 1; }
			Log.v(LOG_TAG, Log.buf().append("AudioPlayTime: ")
					.append(r.num).append("/").append(r.den).append(" ")
					.append(r.getDivideFloat()).toString());
			getCurrentPositionVideoDecode(r);
			if (r.den == 0) { r.den = 1; }
			Log.v(LOG_TAG, Log.buf().append("VideoDecodeTime: ")
					.append(r.num).append("/").append(r.den).append(" ")
					.append(r.getDivideFloat()).toString());
			getCurrentPositionAudioDecode(r);
			if (r.den == 0) { r.den = 1; }
			Log.v(LOG_TAG, Log.buf().append("AudioDecodeTime: ")
					.append(r.num).append("/").append(r.den).append(" ")
					.append(r.getDivideFloat()).toString());
		}
		
		getCurrentPositionVideoPlay(mRatinalCurrentPlayTime);
		final int posNum = mRatinalCurrentPlayTime.num;
		final int posDen = mRatinalCurrentPlayTime.den;
		return appendCurrentPlayTimeCommon(builder, posNum, posDen);
	}
	
	@Override
	protected void getCurrentPositionVideoPlay(Rational rational) {
		rational.num = mFrameCountPlay * mFFmpegData.mFrameRateDenVideo;
		rational.den = mFFmpegData.mFrameRateNumVideo;
	}
	@Override
	protected void getCurrentPositionAudioPlay(Rational rational) {
		mStreamAudioPlayer.getCurrentPosition(rational);
	}
	@Override
	protected void getCurrentPositionVideoDecode(Rational rational) {
		rational.num = mFFmpegData.mTimeNumVideo;
		rational.den = mFFmpegData.mTimeDenVideo;
	}
	@Override
	protected void getCurrentPositionAudioDecode(Rational rational) {
		rational.num = mAudioDecodedBytes;
		rational.den = mStreamAudioPlayer.getSampleRate() * mStreamAudioPlayer.getAudioSampleByteSize();
	}
	
	@Override
	protected boolean switchPausePlay() {
		if (mStreamAudioPlayer.isInDummyData()) {
			return false;
		}
		
		if (!mIsPause) {
			pausePlay();
		} else {
			restartPlay();
		}
		setButtonPauseImage();
		return true;
	}
	
	@Override
	protected void pausePlay() {
//		mIconPause.setVisibility(View.VISIBLE);
		mStreamAudioPlayer.pause();
		mIsPause = true;
	}
	
	@Override
	protected void restartPlay() {
//		mIconPause.setVisibility(View.INVISIBLE);
		updateStartFrameTime();
		mStreamAudioPlayer.restart();
		mIsPause = false;
	}
	
	@Override
	protected boolean isPausePlay() {
		if (mStreamAudioPlayer.isInDummyData()) {
			// 都合上true返す
			return true;
		}
		return mIsPause;
	}
	
	@Override
	protected void seekBySecond(int second) {
		mRequestSeekSecond = second;
		mRequestSeekOnDecoder = true;
	}
	
	@Override
	protected StringBuilder appendVideoResolution(StringBuilder builder) {
		return builder.append(mVideoOriginalWidth)
			.append('×')
			.append(mVideoOriginalHeight);
	}

	@Override
	protected StringBuilder appendPlayerInfo(StringBuilder builder) {
		return builder.append(getString(R.string.info_play_data_ffmpeg));
	}

	private int getFrameDurationMs() {
		return 1 * 1000 * mFFmpegData.mFrameRateDenVideo / mFFmpegData.mFrameRateNumVideo;
	}
	
    private void setScaleMatrix() {
    	if (mMatrixScale == null) {
    		return;
    	}
    	mMatrixScale.reset();
    	if (mFFmpegData.mWidth == 0 || mFFmpegData.mHeight == 0 || mWidthSurface == 0 || mHeightSurface == 0) {
    		return;
    	}
    	float scaleWidth = (float) mWidthSurface / (float) mFFmpegData.mWidth;
    	float scaleHeight = (float) mHeightSurface / (float) mFFmpegData.mHeight;
    	float scale;
    	if (scaleWidth < scaleHeight) {
    		scale = scaleWidth;
        	mDrawOffsetY = (int) ((mHeightSurface / scale - mFFmpegData.mHeight) / 2);
    	} else {
    		scale = scaleHeight;
        	mDrawOffsetY = 0;
    	}
        mMatrixScale.postScale(scale, scale);
    }
    
    /*private*/ int[] createDrawBuffer() {
    	assert (mFFmpegData.mWidth > 0 && mFFmpegData.mHeight > 0);
    	Runtime runtime = Runtime.getRuntime();
    	long freeSize = runtime.maxMemory() - runtime.totalMemory() + runtime.freeMemory();
//    	final int MARGIN_SIZE = 6 * 1024 * 1024;
    	final long MARGIN_SIZE = runtime.maxMemory() / 2;
    	if (freeSize > (mFFmpegData.mWidth * mFFmpegData.mHeight * 4) + MARGIN_SIZE) {
    		return new int[mFFmpegData.mWidth * mFFmpegData.mHeight];
    	} else {
    		return null;
    	}
    }
    
    /*private*/ void drawBufferToSurface(int[] drawBuffer) {
//		// デコードじゃなくて描画のスキップテスト
//		mSkipTest = !mSkipTest;
//		if (mSkipTest) {
//			return;
//		}
//    	++mSkipDrawTest;
//    	if (mSkipDrawTest < 10) {
//    		return;
//    	} else {
//    		mSkipDrawTest = 0;
//    	}

    	Canvas canvas = mSurfaceHolder.lockCanvas();
        if (canvas == null) {
        	Log.d(LOG_TAG, "lockCanvas NG");
        	return;
        }
        
        try {
	        // 一応外側消去
	        canvas.drawColor(0xFF000000);
	        canvas.setMatrix(mMatrixScale);
	        canvas.drawBitmap(drawBuffer, 0, mFFmpegData.mWidth,
	        		0, mDrawOffsetY,
	        		mFFmpegData.mWidth, mFFmpegData.mHeight,
	        		false, mPaint);
			getCurrentPositionVideoPlay(mRationalVideoPlay);
        	canvas.setMatrix(null);
	        drawMessage(canvas,
	        		mMessageData,
	        		mMessageDataFork,
	        		mRationalVideoPlay.getVpos(),
//	        		mWidth, mHeight);
	        		canvas.getWidth(), canvas.getHeight());
        } finally {
        	canvas.setMatrix(null);
        	mSurfaceHolder.unlockCanvasAndPost(canvas);
        }
    }
    
    /*private*/ void writeBufferToAudioTrack() {
    	if (DEBUG_LOGV) {
    		Log.v(LOG_TAG, Log.buf().append("writeBufferToAudioTrack: mAudioBufferSize=")
    				.append(mFFmpegData.mAudioBufferSize).toString());
    	}
    	if (!mStreamAudioPlayer.canPlay()) {
    		Log.d(LOG_TAG, "mAudioTrack is null");
    		return;
    	}
		
		mAudioDecodedBytes += mFFmpegData.mAudioBufferSize;
    	mStreamAudioPlayer.writeBuffer(mFFmpegData.mAudioBuffer, mFFmpegData.mAudioBufferSize / 2);
		
//		if (mAudioBufferSecondOffset > 44100 * 2 / 10) {
//		if (mAudioBufferSecondOffset > mAudioBufferSecond.length / 2) {
    		mStreamAudioPlayer.flushBufferToAudioTrack();
//			mAudioTrack.flush();
//		}
    }
    
    private synchronized void updateStartFrameTime() {
    	mStartFrameTime = SystemClock.elapsedRealtime();
    	// 既に進んでいるフレーム数ぶんだけ調整
		getCurrentPositionVideoPlay(mRationalUpdateStartFrameTime);
    	final int posVideoMs = mRationalUpdateStartFrameTime.getMs();
    	mStartFrameTime -= posVideoMs;
    }
    
    /*private*/ void cacheDecodeFirst2() {
    	boolean getDrawBufferFailed = false;
		while (!mIsFinish) {
			if (mStreamAudioPlayer.hasEnoughCache()) {
				// キャッシュ終了
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, "cache complete");
				}
				break;
			}
			
//			// Audioデータの残りサイズチェック
//			if (mStreamAudioPlayer.getRemainBufferByteSize() < mFFmpegData.mAudioBuffer.length * 2) {
//				// キャッシュ終了
//				break;
//			}
			
			// Videoデータの残りサイズチェック
			VideoBuffer videoBuffer = mVideoBufferPool.poll();
			CacheReference<int[]> drawBufferRef;
			if (videoBuffer == null) {
				videoBuffer = mVideoBufferEmptyPool.poll();
				if (videoBuffer == null) {
					videoBuffer = new VideoBuffer();
				}
				drawBufferRef = null;
			} else {
				drawBufferRef = videoBuffer.buffer;
			}
			int[] drawBuffer;
			if (drawBufferRef != null) {
				drawBuffer = drawBufferRef.strengthen();
			} else {
				drawBuffer = null;
			}
			if (drawBuffer == null && !getDrawBufferFailed) {
				try {
					drawBuffer = createDrawBuffer();
					if (drawBuffer == null) {
						getDrawBufferFailed = true;
//						// キャッシュ終了
//						break;
					} else {
						if (drawBufferRef != null) {
							drawBufferRef.reset(drawBuffer);
						} else {
							drawBufferRef = new CacheReference<int[]>(drawBuffer);
						}
					}
				} catch (OutOfMemoryError e) {
					getDrawBufferFailed = true;
//					// キャッシュ終了
//					break;
				}
			}
			
			int frameType = readFrame(mNativeInstance, mReadBuffer, mVideoLoader);
			assert videoBuffer != null;
//			assert drawBufferRef != null;
			videoBuffer.buffer = drawBufferRef;
			switch (frameType) {
			case CODE_FRAME_TYPE_VIDEO:
				int skipVideoFrame;
				if (drawBuffer == null) {
					skipVideoFrame = CODE_SKIP_VIDEO_FRAME_NONREF;
				} else {
					skipVideoFrame = CODE_SKIP_VIDEO_FRAME_NONE;
				}
				int decodeFrame = decodeVideo(mNativeInstance,
						drawBuffer, mFFmpegData, skipVideoFrame);
				if (decodeFrame == CODE_DECODE_FRAME_VIDEO) {
					assert drawBufferRef != null;
					videoBuffer.frame = mFrameCountDecode;
					mVideoBufferCached.add(videoBuffer);
					++mFrameCountDecode;
				} else if (decodeFrame == CODE_DECODE_FRAME_VIDEO_SKIP) {
					videoBuffer.buffer = null;
					videoBuffer.frame = mFrameCountDecode;
					mVideoBufferCached.add(videoBuffer);
					++mFrameCountDecode;
				} else {
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, Log.buf().append("decodeVideo failed=")
								.append(decodeFrame).toString());
					}
					if (drawBufferRef == null) {
						mVideoBufferEmptyPool.add(videoBuffer);	// 空としてpool
					} else {
						drawBufferRef.weaken();
						mVideoBufferPool.add(videoBuffer);	// 戻す
					}
				}
				break;
			case CODE_FRAME_TYPE_AUDIO:
				if (drawBufferRef == null) {
					mVideoBufferEmptyPool.add(videoBuffer);	// 空としてpool
				} else {
					drawBufferRef.weaken();
					mVideoBufferPool.add(videoBuffer);	// 戻す
				}
				if (decodeAudio(mNativeInstance, mFFmpegData)) {
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG,
								Log.buf().append("AudioCache: second buffer=")
								.append(mStreamAudioPlayer.getCachedSize())
								.append(" mFFmpegData.mAudioBufferSize=")
								.append(mFFmpegData.mAudioBufferSize).toString());
					}
					mAudioDecodedBytes += mFFmpegData.mAudioBufferSize;
					mStreamAudioPlayer.writeBuffer(mFFmpegData.mAudioBuffer, mFFmpegData.mAudioBufferSize / 2);
				} else {
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, "decodeAudio failed");
					}
				}
				break;
			default:	// some error
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, Log.buf().append("readFrame return value=")
							.append(frameType).toString());
				}
				if (drawBufferRef == null) {
					mVideoBufferEmptyPool.add(videoBuffer);	// 空としてpool
				} else {
					drawBufferRef.weaken();
					mVideoBufferPool.add(videoBuffer);	// 戻す
				}
				break;
			}
		}
    }
    
    /*private*/ void decodeMain2() {
		MAIN_LOOP : while (!mIsFinish) {
			if (mRequestSeekOnDecoder) {
				assert mRequestSeekSecond >= 0;
				// PlayerThreadが止まるまでちょっと待つ
				while (!mIsFinish) {
					if (mIsWaitingPlay) {
						break;
					}
					try {
						Thread.sleep(5L);
					} catch (InterruptedException e) {
					}
				}
				seekBySecondCommon(mRequestSeekSecond);
				
				// キャッシュにたまっているバッファをクリア
				while (true) {
					VideoBuffer videoBuffer = mVideoBufferCached.poll();
					if (videoBuffer == null) {
						break;
					}
					CacheReference<int[]> drawBufferRef = videoBuffer.buffer;
					if (drawBufferRef == null) {
						mVideoBufferEmptyPool.add(videoBuffer);
					} else {
						drawBufferRef.weaken();
						mVideoBufferPool.add(videoBuffer);
					}
				}
				mFFmpegData.mAudioBufferSize = 0;
				mStreamAudioPlayer.clearBuffer();
				
				// 切り上げ
				int den = mFFmpegData.mFrameRateNumVideo;
				int num = mFrameCountDecode * mFFmpegData.mFrameRateDenVideo + den - 1;
				int currentSecond = num /den;
				if (DEBUG_LOGV) {
					Log.v(LOG_TAG, Log.buf().append("seek current=").append(currentSecond)
							.append(" to=").append(mRequestSeekSecond).toString());
				}
				if (mRequestSeekSecond < currentSecond) {
					// 過去へのseekなら最初からやり直してスキップ
					
					// TODO flvだとseek効果無し
					final int newFrameCount = seekFrameBySecond(mNativeInstance, 0);
					if (newFrameCount < 0) {
						if (DEBUG_LOGD) {
							Log.d(LOG_TAG, Log.buf()
									.append("seekFrameBySecond return=").append(newFrameCount)
									.toString());
						}
						// seek失敗の場合は最初から
						reopenInputStream(mNativeInstance, mReadBuffer, mVideoLoader);
					}
					
					// TODO 排他制御
					mFrameCountPlay = 0;
					
					mFrameCountDecode = 0;
					
					mFFmpegData.mTimeNumVideo = 0;
					mFFmpegData.mTimeNumAudio = 0;
				}
				
				while (!mIsFinish) {
					// 切り上げ
					den = mFFmpegData.mFrameRateNumVideo;
					num = mFrameCountDecode * mFFmpegData.mFrameRateDenVideo + den - 1;
					currentSecond = num /den;
					if (DEBUG_LOGV) {
						Log.v(LOG_TAG, Log.buf().append("seek current=").append(currentSecond)
								.append(" to=").append(mRequestSeekSecond).toString());
					}
					if (mRequestSeekSecond <= currentSecond) {
						break;
					}
					
					// TODO かなり無駄だがある程度デコード処理やらないと計算合わなくなる？
					// TODO frameだけ読むならkeyframeのタイミングで次の描画再開を行わないと画面化けする
					// →ちゃんとやるなら数秒前からkeyframeを探し出して数秒後までの間に見つけたポイントが便宜上のシーク位置
					int frameType = readFrame(mNativeInstance, mReadBuffer, mVideoLoader);
					switch (frameType) {
					case CODE_FRAME_TYPE_VIDEO:
						if (mRequestSeekSecond - currentSecond <= 2) {
				    		int decodeFrame = decodeVideo(mNativeInstance, null, mFFmpegData, CODE_SKIP_VIDEO_FRAME_NONREF);
							if (decodeFrame != CODE_DECODE_FRAME_VIDEO_SKIP) {
								if (DEBUG_LOGD) {
									Log.d(LOG_TAG, Log.buf().append("decodeVideo unexpected result=")
											.append(decodeFrame).toString());
								}
							}
							
	//						VideoBuffer videoBuffer = mVideoBufferEmptyPool.poll();
	//						if (videoBuffer == null) {
	//							videoBuffer = new VideoBuffer();
	//						}
	//						videoBuffer.buffer = null;
	//						videoBuffer.frame = mFrameCountDecode;
	//						mVideoBufferCached.add(videoBuffer);
						}
						
						// TODO 排他制御
						++mFrameCountPlay;
						
						++mFrameCountDecode;
						break;
					case CODE_FRAME_TYPE_AUDIO:
//						if (decodeAudio(mNativeInstance, mFFmpegData)) {
//						} else {
//							if (DEBUG_LOGD) {
//								Log.d(LOG_TAG, "decodeAudio failed");
//							}
//						}
						break;
					default:	// some error
						if (DEBUG_LOGD) {
							Log.d(LOG_TAG, Log.buf().append("readFrame return value=")
									.append(frameType).toString());
						}
						break;
					}
				}
				
//				final int newFrameCount = seekFrameBySecond(mNativeInstance, mRequestSeekSecond);
//				if (DEBUG_LOGD) {
//					Log.d(LOG_TAG, Log.buf()
//							.append("seekFrameBySecond return=").append(newFrameCount)
//							.append(" second=").append(mRequestSeekSecond).toString());
//				}
//				if (newFrameCount >= 0) {
//					mFrameCountDecode = newFrameCount;
//				}
				
				updateStartFrameTime();
				
				mRequestSeekSecond = -1;
				mRequestSeekOnDecoder = false;
				
				// シークバー再有効化
				if (mHandler != null) {
					mHandler.sendEmptyMessage(MSG_ID_ENABLE_SEEK_BAR);
				}
				
				continue;
			}
			
			if (mCacheWhenChoppy) {
				if (mStreamAudioPlayer.getRemainDataSizeOnDeviceMs() == 0) {
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, "audio choppy: start cache");
					}
					// TODO 音声のない動画への対応
	//				mStreamAudioPlayer.pause();
					mIsCaching = true;
					cacheDecodeFirst2();
					mIsCaching = false;
					if (mIsFinish) {
						break;
					}
	//				mStreamAudioPlayer.restart();
					writeBufferToAudioTrack();
	//				mStreamAudioPlayer.flushAudioTrack();
					// 仕切り直し
					updateStartFrameTime();
				}
			}
			
			int frameType = readFrame(mNativeInstance, mReadBuffer, mVideoLoader);
			switch (frameType) {
			case CODE_FRAME_TYPE_VIDEO:
				DRAW_BUFFER_LOOP : while (!mIsFinish) {
					VideoBuffer videoBuffer = mVideoBufferPool.poll();
					CacheReference<int[]> drawBufferRef;
					if (videoBuffer == null) {
						videoBuffer = mVideoBufferEmptyPool.poll();
						if (videoBuffer == null) {
							videoBuffer = new VideoBuffer();
						}
						drawBufferRef = null;
					} else {
						drawBufferRef = videoBuffer.buffer;
					}
					int[] drawBuffer;
					if (drawBufferRef != null) {
						drawBuffer = drawBufferRef.strengthen();
					} else {
						drawBuffer = null;
					}
					assert videoBuffer != null;
		    		judgeFrameSkipAtDecoderThread();
			    	if (drawBuffer == null) {
						if (mFrameSkipDecode == CODE_SKIP_VIDEO_FRAME_NONE) {
							try {
								drawBuffer = createDrawBuffer();
								if (drawBuffer == null) {
						    		// バッファが来るまで待つ
									try {
										Thread.sleep(1L);
									} catch (InterruptedException e2) {
									}
									mVideoBufferEmptyPool.add(videoBuffer);
						    		continue;
								} else {
									if (drawBufferRef != null) {
										drawBufferRef.reset(drawBuffer);
									} else {
										drawBufferRef = new CacheReference<int[]>(drawBuffer);
									}
								}
							} catch (OutOfMemoryError e) {
					    		// バッファが来るまで待つ
								try {
									Thread.sleep(1L);
								} catch (InterruptedException e2) {
								}
								mVideoBufferEmptyPool.add(videoBuffer);
					    		continue;
							}
						}
			    	}
			    	assert (drawBuffer == null) ? (mFrameSkipDecode != CODE_SKIP_VIDEO_FRAME_NONE) : true;
		    		int decodeFrame = decodeVideo(mNativeInstance, drawBuffer, mFFmpegData, mFrameSkipDecode);
//					assert drawBufferRef != null;
					videoBuffer.buffer = drawBufferRef;
					if (decodeFrame == CODE_DECODE_FRAME_VIDEO) {
						assert drawBufferRef != null;
						if (DEBUG_LOGV) {
							Log.v(LOG_TAG, Log.buf().append("Decode time Video: ")
									.append(mFFmpegData.mTimeNumVideo).append("/")
									.append(mFFmpegData.mTimeDenVideo).append(" = ")
									.append(((mFFmpegData.mTimeDenVideo != 0)
											? ((float)mFFmpegData.mTimeNumVideo / (float)mFFmpegData.mTimeDenVideo)
													: "NAN")).toString());
						}
						videoBuffer.frame = mFrameCountDecode;
						mVideoBufferCached.add(videoBuffer);
						++mFrameCountDecode;
					} else if (decodeFrame == CODE_DECODE_FRAME_VIDEO_SKIP) {
						if (drawBuffer == null) {
							videoBuffer.buffer = null;
							videoBuffer.frame = mFrameCountDecode;
							mVideoBufferCached.add(videoBuffer);
							++mFrameCountDecode;
						} else {
							if (drawBufferRef != null) {
								drawBufferRef.weaken();
							}
							mVideoBufferPool.add(videoBuffer);	// 戻す
							
							VideoBuffer videoBufferEmpty = mVideoBufferEmptyPool.poll();
							if (videoBufferEmpty == null) {
								videoBufferEmpty = new VideoBuffer();
							}
							videoBufferEmpty.frame = mFrameCountDecode;
							mVideoBufferCached.add(videoBufferEmpty);
							++mFrameCountDecode;
						}
					} else {
						if (DEBUG_LOGD) {
							Log.d(LOG_TAG, Log.buf().append("decodeVideo failed=")
									.append(decodeFrame).toString());
						}
						if (drawBufferRef != null) {
							drawBufferRef.weaken();
						}
						mVideoBufferPool.add(videoBuffer);	// 戻す
					}
		    		break;
				}
				break;
			case CODE_FRAME_TYPE_AUDIO:
				if (mStreamAudioPlayer.getRemainBufferByteSize() < mFFmpegData.mAudioBuffer.length * 2) {
					mStreamAudioPlayer.flushBufferToAudioTrack();
					AUDIO_BUFFER_LOOP : while (!mIsFinish) {
						if (mStreamAudioPlayer.getRemainBufferByteSize() < mFFmpegData.mAudioBuffer.length * 2) {
				    		// バッファが来るまで待つ
							try {
								Thread.sleep(1L);
							} catch (InterruptedException e2) {
							}
							mStreamAudioPlayer.flushBufferToAudioTrack();
				    		continue;
						} else {
							break;
						}
					}
				}
				
				if (decodeAudio(mNativeInstance, mFFmpegData)) {
					if (DEBUG_LOGV) {
						Log.v(LOG_TAG, Log.buf().append("Decode time Audio: ")
								.append(mFFmpegData.mTimeNumAudio).append("/")
								.append(mFFmpegData.mTimeDenAudio).append(" = ")
								.append(((mFFmpegData.mTimeDenAudio != 0)
										? ((float)mFFmpegData.mTimeNumAudio / (float)mFFmpegData.mTimeDenAudio)
												: "NAN")).toString());
					}
					writeBufferToAudioTrack();
				} else {
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, "decodeAudio failed");
					}
				}
				break;
			default:	// some error
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, Log.buf().append("readFrame return value=")
							.append(frameType).toString());
				}
				break;
			}
		}
    }
    
    /*private*/ boolean judgeFrameSkipAtPlayerThread() {
    	boolean frameSkip = false;
		if (mFrameCountPlay > 0) {
        	final long currentTime = SystemClock.elapsedRealtime();
        	assert currentTime >= mStartFrameTime;
        	
        	getCurrentPositionVideoPlay(mRationalVideoPlay);
        	final int posVideoMs = mRationalVideoPlay.getMs();
        	final int realPos = (int) (currentTime - mStartFrameTime);
        	final int diffPos = posVideoMs - realPos;
        	final int frameDuration = getFrameDurationMs();
        	assert posVideoMs >= 0;
        	assert realPos >= 0;
        	assert frameDuration > 0;
        	if (DEBUG_LOGV) {
        		Log.v(LOG_TAG, Log.buf().append("DrawBuffer: PlayVideoTime=")
        				.append(posVideoMs).append(" RealTime=").append(realPos)
        				.append(" FrameDuration=").append(frameDuration).toString());
        	}
        	// TODO パラメータ調整
        	if (diffPos < -frameDuration*3) {
				// 処理落ちが大きくなったら仕切り直し
				updateStartFrameTime();
        	} else if (diffPos < -frameDuration*2) {
        		frameSkip = true;
        	} else if (diffPos > frameDuration*2) {
        		try {
					Thread.sleep(frameDuration);
				} catch (InterruptedException e) {
				}
        	}
		}
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append("judgeFrameSkipAtPlayerThread() return=")
					.append(frameSkip).toString());
		}
    	return frameSkip;
    }
    
    /*private*/ void judgeFrameSkipAtDecoderThread() {
    	// 音声基準
    	// TODO 音声のない動画への対応
    	final int audioRemainMs = mStreamAudioPlayer.getRemainDataSizeOnDeviceMs();
    	final int frameDurationMs = getFrameDurationMs();
    	
    	int frameSkipDecode = CODE_SKIP_VIDEO_FRAME_NONE;
    	// TODO パラメータ調整
    	if (audioRemainMs == 0) {
    		if (!mCacheWhenChoppy) {
				// 処理落ちが大きくなったら仕切り直し
				updateStartFrameTime();
    		}
    	}
    	final int skipJudgeMs = Math.min(frameDurationMs*4, 250);
    	if (audioRemainMs < skipJudgeMs) {
    		frameSkipDecode = CODE_SKIP_VIDEO_FRAME_NONREF;
    	}
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append("judgeFrameSkipAtDecoderThread() frameSkipDecode=")
					.append(frameSkipDecode).toString());
		}
    	mFrameSkipDecode = frameSkipDecode;
    }
    
    // Nativeからアクセスされるもの
    
    @Override
    public void createAudioTrackFromNativeCallback(int sample_rate, int channels, int sample_fmt) {
    	if (DEBUG_LOGD) {
    		Log.d(LOG_TAG, Log.buf().append("createAudioTrackFromNativeCallback: sample_rate=")
    				.append(sample_rate).append(" channels=").append(channels)
    				.append(" sample_fmt=").append(sample_fmt).toString());
    	}
    	mStreamAudioPlayer.startByFFmpeg(sample_rate, channels, sample_fmt);
    }

    @Override
    public void createDrawBufferFromNativeCallback(int width, int height) {
    	if (DEBUG_LOGD) {
    		Log.d(LOG_TAG, Log.buf().append("createDrawBufferFromNativeCallback: width=")
    				.append(width).append(" height=").append(height).toString());
    	}
    	mVideoOriginalWidth = width;
    	mVideoOriginalHeight = height;
    	
//    	final Display display = getWindowManager().getDefaultDisplay();
//    	int displayWidth = display.getWidth();
//    	int displayHeight = display.getHeight();
    	int displayWidth = mSurfaceView.getLayoutParams().width;
    	int displayHeight = mSurfaceView.getLayoutParams().height;
    	if (width > displayWidth || height > displayHeight) {
    		if (displayWidth * height < displayHeight * width) {
    			// widthで合わせる
    			mFFmpegData.mWidth = displayWidth;
    			mFFmpegData.mHeight = height * displayWidth / width;
    		} else {
    			// heightで合わせる
    			mFFmpegData.mWidth = width * displayHeight / height;
    			mFFmpegData.mHeight = displayHeight;
    		}
    	} else {
    		mFFmpegData.mWidth = width;
    		mFFmpegData.mHeight = height;
    	}
    	if (DEBUG_LOGD) {
    		Log.d(LOG_TAG, Log.buf().append("mWidth=").append(mFFmpegData.mWidth)
    				.append(" mHeight=").append(mFFmpegData.mHeight).toString());
    	}
    	
    	final Runtime runtime = Runtime.getRuntime();
    	int videoBufferCacheSize = (int) (runtime.freeMemory()
    			/ (mFFmpegData.mWidth * mFFmpegData.mHeight * 4));
    	videoBufferCacheSize = Math.min(videoBufferCacheSize, VIDEO_BUFFER_CACHE_SIZE);
    	videoBufferCacheSize = Math.max(videoBufferCacheSize, 1);
    	if (DEBUG_LOGD) {
    		Log.d(LOG_TAG, Log.buf().append("videoBufferCacheSize=")
    				.append(videoBufferCacheSize).toString());
    	}
    	
//    	try {
//	    	for (int i = 0; i < videoBufferCacheSize; ++i) {
//	    		mVideoBufferPool.add(createDrawBuffer());
//	    	}
//    	} catch (OutOfMemoryError e) {
//    		Log.w(LOG_TAG, "OutOfMemoryError at caching video buffer");
//    	}
    	
    	setScaleMatrix();
    }
    
    private void destroyNativeInstance() {
		long nativeInstance = mNativeInstance;
		mNativeInstance = 0L;
		destroyNative(nativeInstance);
    }
 
    // Nativeメソッド
    
    private native long createNative();
    private native void destroyNative(long nativeInstance);
    private native void loadFile(long nativeInstance, String file);
    private native void loadStream(long nativeInstance, byte[] buffer, FFmpegIOCallback ioCallback, FFmpegData data);
    private native void createAudioTrack(long nativeInstance, FFmpegInfoCallback infoCallback);
    private native void createDrawBuffer(long nativeInstance, FFmpegInfoCallback infoCallback);
    /*private*/ native int decodeFrame(long nativeInstance,
    		int[] drawBuffer, int skipVideoFrame, byte[] readBuffer, FFmpegIOCallback ioCallback, FFmpegData data);
    /*private*/ native int readFrame(long nativeInstance,
    		byte[] readBuffer, FFmpegIOCallback ioCallback);
    /*private*/ native int decodeVideo(long nativeInstance,
    		int[] drawBuffer, FFmpegData data, int skipVideoFrame);
    /*private*/ native boolean decodeAudio(long nativeInstance,
    		FFmpegData data);
    private native int seekFrameBySecond(long nativeInstance, int second);
    private native void reopenInputStream(long nativeInstance, byte[] buffer, FFmpegIOCallback ioCallback);
    
    static {
    	System.loadLibrary("nicoro-jni");
    }
    
    
 }