#include "release.h"
#if (IS_DEBUG)
//#define DEBUG_LOGD true
#define DEBUG_LOGD false
//#define DEBUG_LOGD_DUMP true
#define DEBUG_LOGD_DUMP false
//#define DEBUG_FFMPEG_LOG_LEVEL AV_LOG_INFO
#define DEBUG_FFMPEG_LOG_LEVEL AV_LOG_DEBUG
#else
#define DEBUG_LOGD false
#define DEBUG_LOGD_DUMP false
#define DEBUG_FFMPEG_LOG_LEVEL AV_LOG_ERROR
#endif

#define AUDIO_BUFFER_DIRECT_ALLOC true
//#define AUDIO_BUFFER_DIRECT_ALLOC false

#define  LOG_TAG    "NicoRoJNI"
#define  LOG_TAG_FFMPEG    "NicoRoJNI_FFMPEG"
#if (DEBUG_LOGD)
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#else
#define  LOGD(...)
#endif /* DEBUG_LOGD */
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#include "NicoroFFmpegPlayer.h"

#include <unistd.h>
#include <sys/stat.h>

#include <android/log.h>

#include <libavutil/log.h>

class AutoDetachCurrentThread {
public:
	AutoDetachCurrentThread(JavaVM* vm) : mVM(vm), mExecDetach(false) {}
	~AutoDetachCurrentThread() {
		if (mExecDetach) {
			jint result = mVM->DetachCurrentThread();
			assert(result == 0);
		}
	}
	void setExecDetach(bool b) {
		mExecDetach = b;
	}
private:
	JavaVM* mVM;
	bool mExecDetach;
};

#if (AUDIO_BUFFER_DIRECT_ALLOC)
IMPL_JFIELD_CLASS_SIG(mAudioBuffer, "Ljava/nio/ByteBuffer;")
#else
IMPL_JFIELD_CLASS_SIG(mAudioBuffer, "[S")
#endif
IMPL_JFIELD_CLASS_SIG(mAudioBufferSize, "I")
IMPL_JFIELD_CLASS_SIG(mTimeNumVideo, "I")
IMPL_JFIELD_CLASS_SIG(mTimeDenVideo, "I")
IMPL_JFIELD_CLASS_SIG(mTimeNumAudio, "I")
IMPL_JFIELD_CLASS_SIG(mTimeDenAudio, "I")
IMPL_JFIELD_CLASS_SIG(mFrameRateNumVideo, "I")
IMPL_JFIELD_CLASS_SIG(mFrameRateDenVideo, "I")
IMPL_JFIELD_CLASS_SIG(mWidth, "I")
IMPL_JFIELD_CLASS_SIG(mHeight, "I")
IMPL_JMETHOD_CLASS_SIG(createAudioTrackFromNativeCallback, "(III)V")
IMPL_JMETHOD_CLASS_SIG(createDrawBufferFromNativeCallback, "(II)V")

IMPL_JMETHOD_CLASS_SIG(seekFromNativeCallback, "(JI)J")
IMPL_JMETHOD_CLASS_SIG(readFromNativeCallback, "(I[B)I")

extern "C" {
	static void logCallback(void* ptr, int level, const char* fmt, va_list vl) {
		if (level > DEBUG_FFMPEG_LOG_LEVEL) {
			return;
		}

		int prio;
		switch (level) {
		case AV_LOG_PANIC:
		case AV_LOG_FATAL:
			prio = ANDROID_LOG_FATAL;
			break;
		case AV_LOG_ERROR:
			prio = ANDROID_LOG_ERROR;
			break;
		case AV_LOG_WARNING:
			prio = ANDROID_LOG_WARN;
			break;
		case AV_LOG_INFO:
			prio = ANDROID_LOG_INFO;
			break;
		case AV_LOG_VERBOSE:
			prio = ANDROID_LOG_DEBUG;
			break;
		case AV_LOG_DEBUG:
			prio = ANDROID_LOG_VERBOSE;
			break;
		case AV_LOG_QUIET:
			prio = ANDROID_LOG_DEBUG;
			break;
		default:
			prio = ANDROID_LOG_FATAL;
			break;
		}
		__android_log_vprint(prio, LOG_TAG_FFMPEG, fmt, vl);
	}
}

NicoroFFmpegPlayer::NicoroFFmpegPlayer(JavaVM* vm)
: mpInputFormat(NULL), mpFormatContext(NULL)
, mStreamIndexVideo(-1), mStreamIndexAudio(-1)
, mfpFileStream(NULL)
, mVM(vm), mEnv(NULL), mThiz(NULL), mReadBuffer(NULL)
, mIOCallback(NULL), mLastSkipVideoFrame(CODE_SKIP_VIDEO_FRAME_NONE)
, mSurfaceSwsFlags(SWS_POINT)
{
	memset(&mPacket, 0, sizeof(mPacket));
	memset(mStreamBuffer, 0, sizeof(mStreamBuffer));
	memset(&mByteIOContext, 0, sizeof(mByteIOContext));
	memset(mProbeDataBuffer, 0, sizeof(mProbeDataBuffer));

	av_register_all();

	av_log_set_level(DEBUG_FFMPEG_LOG_LEVEL);
	av_log_set_callback(&logCallback);
}

NicoroFFmpegPlayer::~NicoroFFmpegPlayer() {
	if (mfpFileStream != NULL) {
		fclose(mfpFileStream);
	}
}

bool NicoroFFmpegPlayer::loadFile(const char* file) {
	LOGD("loadFile");
	if (av_open_input_file(&mpFormatContextFile.get(), file, NULL, 0, NULL) != 0) {
		LOGE("av_open_input_file NG");
		return false;
	}
	mpFormatContext = mpFormatContextFile.get();
	LOGD("av_open_input_file OK: mpFormatContext=%p", mpFormatContext);
	return loadCommon();
}

bool NicoroFFmpegPlayer::loadFileUseStream(const char* file) {
	LOGD("loadFileUseStream: file=%s", file);
	initPutByteFile();

	mfpFileStream = fopen(file, "rb");
	if (mfpFileStream == NULL) {
		LOGE("fopen NG");
		return false;
	}

	AVProbeData probeData = { 0 };
	probeData.filename = "";
//	probeData.filename = file;
	static uint8_t bufTemp[1024*32 + AVPROBE_PADDING_SIZE];
	probeData.buf = bufTemp;
	probeData.buf_size = fread(bufTemp, 1, 1024*32, mfpFileStream);
	fseek(mfpFileStream, SEEK_SET, 0);

	AVInputFormat* pInputFormat = av_probe_input_format(&probeData, 1);
	if (pInputFormat == NULL) {
		LOGE("av_probe_input_format NG");
		return false;
	}
	LOGD("av_probe_input_format OK");
	mpInputFormat = pInputFormat;

	if (av_open_input_stream(&mpFormatContextStream.get(), &mByteIOContext, "" /*file*/,
			pInputFormat, NULL) != 0) {
		LOGE("av_open_input_stream NG");
		return false;
	}
	mpFormatContext = mpFormatContextStream.get();
	LOGD("av_open_input_stream OK: mpFormatContext=%p", mpFormatContext);

	return loadCommon();
}

bool NicoroFFmpegPlayer::loadStream(JNIEnv * env, jobject thiz, jbyteArray buffer,
		jboolean isOpened, jobject ioCallback, jobject data) {
	int avRet = -1;
	LOGD("loadStream");

	mReadBuffer = buffer;
	mIOCallback = ioCallback;

	initPutByteStream();

	jclass jcFFmpegIOCallback = GET_MEMBER_JCLASS(mJniFFmpegIOCallback, FFmpegIOCallback).getObjectClass(env, ioCallback);

	jmethodID jmSeekFromNativeCallback = GET_MEMBER_JMETHOD(mJniFFmpegIOCallback, seekFromNativeCallback).getMethodID(env, jcFFmpegIOCallback);
	if (jmSeekFromNativeCallback == NULL) {
		LOGE("GetMethodID seekFromNativeCallback NG");
		return false;
	}
	jlong seekRet = GET_MEMBER_JMETHOD(mJniFFmpegIOCallback, seekFromNativeCallback).callReturnMethod(env, ioCallback,
			createJValueArgs(static_cast<jlong>(0LL), static_cast<jint>(SEEK_SET)));
	LOGD("seekFromNativeCallback return=%lld", seekRet);

	jmethodID jmReadFromNativeCallback = GET_MEMBER_JMETHOD(mJniFFmpegIOCallback, readFromNativeCallback).getMethodID(env, jcFFmpegIOCallback);
	if (jmReadFromNativeCallback == NULL) {
		LOGE("GetMethodID readFromNativeCallback NG");
		return false;
	}
	jint readBufSize = GET_MEMBER_JMETHOD(mJniFFmpegIOCallback, readFromNativeCallback).callReturnMethod(env, ioCallback,
			createJValueArgs(PROBE_DATA_BUFFER_SIZE, buffer));
	LOGD("readFromNativeCallback return=%d", readBufSize);

	JByteArray jaBufferForNative(env,
			buffer,
			JNI_ABORT);
	jbyte* pBufferForNative = jaBufferForNative.getElements();
	LOGD("pBufferForNative=%p", pBufferForNative);

	memcpy(mProbeDataBuffer, pBufferForNative, readBufSize);

	AVProbeData probeData = { 0 };
	probeData.filename = "";
	probeData.buf = mProbeDataBuffer;
	probeData.buf_size = readBufSize;

	AVInputFormat* pInputFormat = av_probe_input_format(&probeData, isOpened);

	jaBufferForNative.release();

	if (pInputFormat == NULL) {
		LOGE("av_probe_input_format NG");
		return false;
	}
	LOGD("av_probe_input_format OK: pInputFormat=%p", pInputFormat);
	mpInputFormat = pInputFormat;

	if ((avRet = av_open_input_stream(&mpFormatContextStream.get(), &mByteIOContext, "" /*file*/,
			pInputFormat, NULL)) != 0) {
		LOGE("av_open_input_stream NG: %d", avRet);
		return false;
	}
	mpFormatContext = mpFormatContextStream.get();
	LOGD("av_open_input_stream OK: mpFormatContext=%p", mpFormatContext);

	bool ret = loadCommon();

	jclass jcFFmpegData = GET_MEMBER_JCLASS(mJniFFmpegData, FFmpegData).getObjectClass(env, data);
	GET_MEMBER_JFIELD(mJniFFmpegData, mFrameRateNumVideo).getFieldID(env, jcFFmpegData);
	GET_MEMBER_JFIELD(mJniFFmpegData, mFrameRateDenVideo).getFieldID(env, jcFFmpegData);
	GET_MEMBER_JFIELD(mJniFFmpegData, mFrameRateNumVideo).setField(
			env, data, mpFormatContext->streams[mStreamIndexVideo]->r_frame_rate.num);
	GET_MEMBER_JFIELD(mJniFFmpegData, mFrameRateDenVideo).setField(
			env, data, mpFormatContext->streams[mStreamIndexVideo]->r_frame_rate.den);
	LOGD("r_frame_rate=%d/%d", mpFormatContext->streams[mStreamIndexVideo]->r_frame_rate.num, mpFormatContext->streams[mStreamIndexVideo]->r_frame_rate.den);

	return ret;
}

bool NicoroFFmpegPlayer::loadStreamMP3(JNIEnv * env, jobject thiz, jbyteArray buffer, jobject ioCallback,
		int timeBaseNumerator, int timeBaseDenominator,
		int sampleRate, int channels) {
	LOGD("loadStreamAudioMP3");

	return loadStreamAudioCommon(env, thiz, buffer, ioCallback,
			timeBaseNumerator, timeBaseDenominator,
			sampleRate, channels,
			"mp3");
}

bool NicoroFFmpegPlayer::loadStreamADPCM(JNIEnv * env, jobject thiz, jbyteArray buffer, jobject ioCallback,
		int timeBaseNumerator, int timeBaseDenominator,
		int sampleRate, int channels) {
	LOGD("loadStreamAudioADPCM");

	return loadStreamAudioCommon(env, thiz, buffer, ioCallback,
			timeBaseNumerator, timeBaseDenominator,
			sampleRate, channels,
			"adpcm");
}

bool NicoroFFmpegPlayer::loadStreamAudioCommon(JNIEnv * env, jobject thiz, jbyteArray buffer, jobject ioCallback,
		int timeBaseNumerator, int timeBaseDenominator,
		int sampleRate, int channels,
		const char* formatName) {
	int avRet = -1;
	LOGD("loadStreamAudioCommon");

	mReadBuffer = buffer;
	mIOCallback = ioCallback;

	initPutByteStream();

	AVInputFormat* pInputFormat = av_find_input_format(formatName);
	if (pInputFormat == NULL) {
		LOGE("av_find_input_format NG");
		return false;
	}
	LOGD("av_find_input_format OK: pInputFormat=%p", pInputFormat);
	mpInputFormat = pInputFormat;

	AVFormatParameters formatParameters = { 0 };
//	formatParameters.time_base.num = timeBaseNumerator;
//	formatParameters.time_base.den = timeBaseDenominator;
//	formatParameters.sample_rate = sampleRate;
//	formatParameters.channels = channels;

	if ((avRet = av_open_input_stream(&mpFormatContextStream.get(), &mByteIOContext, "" /*file*/,
			pInputFormat, &formatParameters)) != 0) {
		LOGE("av_open_input_stream NG: %d", avRet);
		return false;
	}
	mpFormatContext = mpFormatContextStream.get();
	LOGD("av_open_input_stream OK: mpFormatContext=%p", mpFormatContext);

//	mpFormatContext->flags |= CODEC_FLAG_LOW_DELAY;		// Force low delay.

	return loadCommon();
}

bool NicoroFFmpegPlayer::reopenInputStream(jbyteArray buffer, jobject ioCallback) {
	int avRet = -1;
	LOGD("reopenInputStream");

	if (mpInputFormat == NULL) {
		LOGE("logic error: mpInputFormat is NULL");
		return false;
	}

	mReadBuffer = buffer;
	mIOCallback = ioCallback;

	mpFormatContextStream.release();
	mpFormatContext = NULL;
	mpAutoCodecContextVideo.release();
	mpAutoCodecContextAudio.release();

	initPutByteStream();

	if ((avRet = av_open_input_stream(&mpFormatContextStream.get(), &mByteIOContext, "" /*file*/,
			mpInputFormat, NULL)) != 0) {
		LOGE("av_open_input_stream NG: %d", avRet);
		return false;
	}
	mpFormatContext = mpFormatContextStream.get();
	LOGD("av_open_input_stream OK: mpFormatContext=%p", mpFormatContext);

//	return true;
	return loadCommon();
}

bool NicoroFFmpegPlayer::loadCommon() {
	LOG_DUMP_AVFORMATCONTEXT(*mpFormatContext);

	int avRet = -1;
	if ((avRet = av_find_stream_info(mpFormatContext)) < 0) {
		LOGE("av_find_stream_info NG: %d", avRet);
		return false;
	}
	LOGD("av_find_stream_info OK");

	AVCodecContext* pCodecContextVideo = NULL;
	AVCodecContext* pCodecContextAudio = NULL;
	AVCodec* pCodecVideo = NULL;
	AVCodec* pCodecAudio = NULL;
	mStreamIndexVideo = -1;
	mStreamIndexAudio = -1;
	for (int i = 0; i < mpFormatContext->nb_streams; ++i) {
		AVCodecContext* pCodecContext = mpFormatContext->streams[i]->codec;
		LOGD("stream #%d\n", i);
		LOG_DUMP_AVSTREAM(*mpFormatContext->streams[i]);
		LOG_DUMP_AVCODECCONTEXT(*pCodecContext);

		CodecID codec_id = pCodecContext->codec_id;
		if (pCodecVideo == NULL && pCodecContext->codec_type == CODEC_TYPE_VIDEO) {
			pCodecVideo = avcodec_find_decoder(codec_id);
			pCodecContextVideo = pCodecContext;
			mStreamIndexVideo = i;
		} else if (pCodecAudio == NULL && pCodecContext->codec_type == CODEC_TYPE_AUDIO) {
			pCodecAudio = avcodec_find_decoder(codec_id);
			pCodecContextAudio = pCodecContext;
			mStreamIndexAudio = i;
		}

		if (pCodecVideo != NULL && pCodecAudio != NULL) {
			break;
		}
	}
//	if (pCodecVideo == NULL || pCodecAudio == NULL) {
	if (pCodecVideo == NULL && pCodecAudio == NULL) {
		LOGE("avcodec_find_decoder NG");
		return false;
	}
	LOGD("avcodec_find_decoder OK");

	if (pCodecVideo != NULL) {
		if (pCodecVideo->capabilities & CODEC_CAP_TRUNCATED) {
			pCodecContextVideo->flags |= CODEC_FLAG_TRUNCATED;
		}

		if (avcodec_open(pCodecContextVideo, pCodecVideo) < 0) {
			LOGE("avcodec_open NG");
			return false;
		}
		LOGD("avcodec_open OK: pCodecContextVideo=%p", pCodecContextVideo);
		mpAutoCodecContextVideo.set(pCodecContextVideo);

		mpFrame.set(avcodec_alloc_frame());
		if (mpFrame.get() == NULL) {
			LOGE("avcodec_alloc_frame NG");
			return false;
		}
		LOGD("avcodec_alloc_frame OK");

		mpFrameBitmap.set(avcodec_alloc_frame());
		if (mpFrameBitmap.get() == NULL) {
			LOGE("avcodec_alloc_frame NG");
			return false;
		}
		LOGD("avcodec_alloc_frame OK");
	}

	if (pCodecAudio != NULL) {
		if (avcodec_open(pCodecContextAudio, pCodecAudio) < 0) {
			LOGE("avcodec_open NG");
			return false;
		}
		LOGD("avcodec_open OK: pCodecContextAudio=%p", pCodecContextAudio);
		mpAutoCodecContextAudio.set(pCodecContextAudio);
	}

	return true;
}

void NicoroFFmpegPlayer::createAudioTrack(JNIEnv * env, jobject thiz, jobject infoCallback) {
	jclass jcFFmpegInfoCallback = GET_MEMBER_JCLASS(mJniFFmpegInfoCallback, FFmpegInfoCallback).getObjectClass(env, infoCallback);
	LOGD("jclass FFmpegInfoCallback=%p", jcFFmpegInfoCallback);
	jmethodID jmCreateAudioTrackFromNativeCallback = GET_MEMBER_JMETHOD(
			mJniFFmpegInfoCallback, createAudioTrackFromNativeCallback)
			.getMethodID(env, jcFFmpegInfoCallback);
	LOGD("jmethodID CreateAudioTrackFromNativeCallback=%p", jmCreateAudioTrackFromNativeCallback);
	if (jmCreateAudioTrackFromNativeCallback != NULL) {
		GET_MEMBER_JMETHOD(mJniFFmpegInfoCallback, createAudioTrackFromNativeCallback)
		.callVoidMethod(env, infoCallback,
				createJValueArgs(
						mpAutoCodecContextAudio.get()->sample_rate,
						mpAutoCodecContextAudio.get()->channels,
						mpAutoCodecContextAudio.get()->sample_fmt));
	}
}

void NicoroFFmpegPlayer::createDrawBuffer(JNIEnv * env, jobject thiz, jobject infoCallback) {
	jclass jcFFmpegInfoCallback = GET_MEMBER_JCLASS(mJniFFmpegInfoCallback, FFmpegInfoCallback).getObjectClass(env, infoCallback);
	LOGD("jclass FFmpegInfoCallback=%p", jcFFmpegInfoCallback);
	jmethodID jmCreateDrawBufferFromNativeCallback = GET_MEMBER_JMETHOD(
			mJniFFmpegInfoCallback, createDrawBufferFromNativeCallback)
			.getMethodID(env, jcFFmpegInfoCallback);
	LOGD("jmethodID createDrawBufferFromNativeCallback=%p", jmCreateDrawBufferFromNativeCallback);
	if (jmCreateDrawBufferFromNativeCallback != NULL) {
		GET_MEMBER_JMETHOD(mJniFFmpegInfoCallback, createDrawBufferFromNativeCallback)
		.callVoidMethod(env, infoCallback,
				createJValueArgs(
						mpAutoCodecContextVideo.get()->width,
						mpAutoCodecContextVideo.get()->height));
	}
}

int NicoroFFmpegPlayer::decodeFrame(JNIEnv * env, jobject thiz, jintArray drawBuffer, jint skipVideoFrame, jbyteArray readBuffer, jobject ioCallback, jobject data) {
	LOGD("decodeFrame");

	int frameType = readFrame(env, thiz, readBuffer, ioCallback);
	int ret = CODE_DECODE_FRAME_INVALID;
	switch (frameType) {
	case CODE_FRAME_TYPE_VIDEO:
		ret = decodeVideo(env, thiz, drawBuffer, data, skipVideoFrame);
		break;
	case CODE_FRAME_TYPE_AUDIO:
		if (decodeAudio(env, thiz, data)) {
			ret = CODE_DECODE_FRAME_AUDIO;
		} else {
			ret = CODE_DECODE_FRAME_ERROR_OR_END;
		}
		break;
	case CODE_FRAME_TYPE_UNKNOWN:
		LOGD("stream index is not video or audio, skip");
		ret = CODE_DECODE_FRAME_SKIP;
		mAutoPacket.release();
		break;
	case CODE_FRAME_TYPE_ERROR_OR_END:
		ret = CODE_FRAME_TYPE_ERROR_OR_END;
		mAutoPacket.release();
		break;
	default:
		LOGE("readFrame return unknown value=%d", frameType);
		ret = CODE_FRAME_TYPE_ERROR_OR_END;
		mAutoPacket.release();
		break;
	}
	return ret;
}

int NicoroFFmpegPlayer::readFrame(JNIEnv * env, jobject thiz,
		jbyteArray readBuffer, jobject ioCallback) {
	LOGD("readFrame");

	mReadBuffer = readBuffer;
	mIOCallback = ioCallback;

	mAutoPacket.release();
	av_init_packet(&mPacket);

	int retAvReadFrame = av_read_frame(mpFormatContext, &mPacket);
			LOG_DUMP_AVPACKET(mPacket);
	if (retAvReadFrame < 0) {
		LOGE("av_read_frame NG: %d", retAvReadFrame);
		return CODE_FRAME_TYPE_ERROR_OR_END;
	}
	LOGD("av_read_frame OK");
	mAutoPacket.set(&mPacket);

	int ret = CODE_FRAME_TYPE_ERROR_OR_END;
	if (mPacket.stream_index == mStreamIndexVideo) {
		ret = CODE_FRAME_TYPE_VIDEO;
	} else if (mPacket.stream_index == mStreamIndexAudio) {
		ret = CODE_FRAME_TYPE_AUDIO;
	} else {
		LOGD("stream index is not video or audio");
		ret = CODE_FRAME_TYPE_UNKNOWN;
		mAutoPacket.release();
	}
	return ret;
}

int NicoroFFmpegPlayer::decodeVideo(JNIEnv * env, jobject thiz,
		jintArray drawBuffer, jobject data, jint skipVideoFrame) {
	LOGD("decodeVideo");

	AutoPacketRelease apr(mAutoPacket);

	bool decodeOnly = false;
	if (!decodeVideoPrepareSkip(skipVideoFrame, &decodeOnly)) {
		return CODE_DECODE_FRAME_ERROR_OR_END;
	}
	int retDecodeVideo = decodeVideoMain(env, thiz);
	if (retDecodeVideo == CODE_DECODE_FRAME_PROGRESS) {
		if (decodeOnly) {
			retDecodeVideo = CODE_DECODE_FRAME_VIDEO_SKIP;
		} else {
			assert(drawBuffer != NULL);
			retDecodeVideo = decodeVideoConvert(env, thiz, drawBuffer, data);
		}
	}
	decodeVideoSetData(env, thiz, data);
	return retDecodeVideo;
}

int NicoroFFmpegPlayer::decodeVideo16bit(JNIEnv * env, jobject thiz,
		jobject drawByteBuffer, jobject data, jint skipVideoFrame) {
	LOGD("decodeVideo16bit");

	if (mLastSkipVideoFrame != CODE_SKIP_VIDEO_FRAME_UNTIL_KEY_FRAME) {
		mLastSkipVideoFrame = skipVideoFrame;
	}

	AutoPacketRelease apr(mAutoPacket);

	bool decodeOnly = false;
	if (!decodeVideoPrepareSkip(skipVideoFrame, &decodeOnly)) {
		return CODE_DECODE_FRAME_ERROR_OR_END;
	}
	int retDecodeVideo = decodeVideoMain(env, thiz);
	if (retDecodeVideo == CODE_DECODE_FRAME_PROGRESS) {
		if (decodeOnly) {
			retDecodeVideo = CODE_DECODE_FRAME_VIDEO_SKIP;
		} else {
			assert(drawByteBuffer != NULL);
			retDecodeVideo = decodeVideoConvert16bit(env, thiz, drawByteBuffer, data);
		}
	}
	decodeVideoSetData(env, thiz, data);
	return retDecodeVideo;
}

bool NicoroFFmpegPlayer::decodeVideoPrepareSkip(jint skipVideoFrame, bool* pDecodeOnly) {
	switch (skipVideoFrame) {
	case CODE_SKIP_VIDEO_FRAME_NONE:
		// skip_*を戻すタイミングは別
		if (mLastSkipVideoFrame == CODE_SKIP_VIDEO_FRAME_UNTIL_KEY_FRAME) {
			*pDecodeOnly = true;
		} else {
			*pDecodeOnly = false;
		}
		break;
	case CODE_SKIP_VIDEO_FRAME_NONREF:
		mpAutoCodecContextVideo.get()->skip_loop_filter = AVDISCARD_NONREF;
		mpAutoCodecContextVideo.get()->skip_idct = AVDISCARD_NONREF;
		mpAutoCodecContextVideo.get()->skip_frame = AVDISCARD_NONREF;
		*pDecodeOnly = true;
		break;
	case CODE_SKIP_VIDEO_FRAME_BIDIR:
		mpAutoCodecContextVideo.get()->skip_loop_filter = AVDISCARD_BIDIR;
		mpAutoCodecContextVideo.get()->skip_idct = AVDISCARD_BIDIR;
		mpAutoCodecContextVideo.get()->skip_frame = AVDISCARD_BIDIR;
		*pDecodeOnly = true;
		break;
	case CODE_SKIP_VIDEO_FRAME_NONKEY:
		mpAutoCodecContextVideo.get()->skip_loop_filter = AVDISCARD_NONKEY;
		mpAutoCodecContextVideo.get()->skip_idct = AVDISCARD_NONKEY;
		mpAutoCodecContextVideo.get()->skip_frame = AVDISCARD_NONKEY;
		*pDecodeOnly = true;
		break;
	case CODE_SKIP_VIDEO_FRAME_SIMPLE:
		// TODO 一部動画で問題あり
		mpAutoCodecContextVideo.get()->skip_loop_filter = AVDISCARD_NONKEY;
		mpAutoCodecContextVideo.get()->skip_idct = AVDISCARD_NONREF;
//		mpAutoCodecContextVideo.get()->skip_frame = AVDISCARD_BIDIR;
		mpAutoCodecContextVideo.get()->skip_frame = AVDISCARD_NONREF;
		*pDecodeOnly = true;
		break;
	case CODE_SKIP_VIDEO_FRAME_UNTIL_KEY_FRAME:
		mpAutoCodecContextVideo.get()->skip_loop_filter = AVDISCARD_NONKEY;
		mpAutoCodecContextVideo.get()->skip_idct = AVDISCARD_NONKEY;
//		mpAutoCodecContextVideo.get()->skip_idct = AVDISCARD_NONREF;
//		mpAutoCodecContextVideo.get()->skip_frame = AVDISCARD_NONKEY;
		mpAutoCodecContextVideo.get()->skip_frame = AVDISCARD_BIDIR;
		*pDecodeOnly = true;
		break;
	default:
		LOGE("unknown skipVideoFrame=%d", skipVideoFrame);
		return false;
	}
	return true;
}

int NicoroFFmpegPlayer::decodeVideoMain(JNIEnv * env, jobject thiz) {
	mPacket.flags |= AV_PKT_FLAG_KEY;
	LOGD("coded_width=%d, coded_height=%d",
			mpAutoCodecContextVideo.get()->coded_width,
			mpAutoCodecContextVideo.get()->coded_height);

	int got_picture = 0;
	int len = avcodec_decode_video2(mpAutoCodecContextVideo.get(), mpFrame.get(),
			&got_picture, &mPacket);
	LOG_DUMP_AVFRAME(*(mpFrame.get()));
	if (len < 0) {
		LOGE("avcodec_decode_video2 NG: len=%d", len);
		return CODE_DECODE_FRAME_ERROR_OR_END;
	}
	LOGD("avcodec_decode_video2 OK: len=%d, got_picture=%d", len, got_picture);

	LOG_DUMP_AVCODECCONTEXT(*mpAutoCodecContextVideo.get());

	if (mpFrame.get()->key_frame != 0) {
		// キーフレームに来たらスキップ指定をいったん戻す
		mpAutoCodecContextVideo.get()->skip_loop_filter = AVDISCARD_DEFAULT;
		mpAutoCodecContextVideo.get()->skip_idct = AVDISCARD_DEFAULT;
		mpAutoCodecContextVideo.get()->skip_frame = AVDISCARD_DEFAULT;
		if (mLastSkipVideoFrame == CODE_SKIP_VIDEO_FRAME_UNTIL_KEY_FRAME) {
			mLastSkipVideoFrame = CODE_SKIP_VIDEO_FRAME_NONE;
		}
	}

	if (got_picture != 0) {
		return CODE_DECODE_FRAME_PROGRESS;
	} else {
		return CODE_DECODE_FRAME_VIDEO_SKIP;
	}
}

int NicoroFFmpegPlayer::decodeVideoConvert(JNIEnv * env, jobject thiz,
		jintArray drawBuffer, jobject data) {
	jclass jcFFmpegData = GET_MEMBER_JCLASS(mJniFFmpegData, FFmpegData).getObjectClass(env, data);

	JIntArray jaDrawBuffer(env, drawBuffer, 0);
	jint* pDrawBuffer = jaDrawBuffer.getElements();
	if (pDrawBuffer == NULL) {
		LOGE("GetIntArrayElements NG: pDrawBuffer=%p", pDrawBuffer);
		return CODE_DECODE_FRAME_ERROR_OR_END;
	}

	GET_MEMBER_JFIELD(mJniFFmpegData, mWidth).getFieldID(env, jcFFmpegData);
	GET_MEMBER_JFIELD(mJniFFmpegData, mHeight).getFieldID(env, jcFFmpegData);
	int widthBitmap = GET_MEMBER_JFIELD(mJniFFmpegData, mWidth).getField(env, data);
	int heightBitmap = GET_MEMBER_JFIELD(mJniFFmpegData, mHeight).getField(env, data);

	return decodeVideoConvertMain(env, thiz,
			reinterpret_cast<uint8_t*>(pDrawBuffer),
			widthBitmap, heightBitmap, SURFACE_PIXEL_FORMAT);
}

int NicoroFFmpegPlayer::decodeVideoConvert16bit(JNIEnv * env, jobject thiz,
		jobject drawByteBuffer, jobject data) {
	jclass jcFFmpegData = GET_MEMBER_JCLASS(mJniFFmpegData, FFmpegData).getObjectClass(env, data);

	void* pDrawBuffer = env->GetDirectBufferAddress(drawByteBuffer);
	if (pDrawBuffer == NULL) {
		LOGE("GetDirectBufferAddress NG: pDrawBuffer=%p", pDrawBuffer);
		return CODE_DECODE_FRAME_ERROR_OR_END;
	}

	GET_MEMBER_JFIELD(mJniFFmpegData, mWidth).getFieldID(env, jcFFmpegData);
	GET_MEMBER_JFIELD(mJniFFmpegData, mHeight).getFieldID(env, jcFFmpegData);
	int widthBitmap = GET_MEMBER_JFIELD(mJniFFmpegData, mWidth).getField(env, data);
	int heightBitmap = GET_MEMBER_JFIELD(mJniFFmpegData, mHeight).getField(env, data);

	jlong bufferCapacity = env->GetDirectBufferCapacity(drawByteBuffer);
	if (bufferCapacity < widthBitmap * heightBitmap * 2) {
		LOGE("GetDirectBufferCapacity: size too small. bufferCapacity=%ld", bufferCapacity);
		return CODE_DECODE_FRAME_ERROR_OR_END;
	}

	return decodeVideoConvertMain(env, thiz,
			static_cast<uint8_t*>(pDrawBuffer),
			widthBitmap, heightBitmap, SURFACE_PIXEL_FORMAT_16BIT);
}

int NicoroFFmpegPlayer::decodeVideoConvertMain(JNIEnv * env, jobject thiz,
		uint8_t* pDrawBuffer, int widthBitmap, int heightBitmap,
		PixelFormat surfacePixelFormat) {
	avpicture_fill(reinterpret_cast<AVPicture*>(mpFrameBitmap.get()),
			pDrawBuffer,
			surfacePixelFormat,
			widthBitmap,
			heightBitmap);
	LOGD("avpicture_fill OK");
	LOG_DUMP_AVFRAME(*(mpFrameBitmap.get()));

	SwsContext* img_convert_context = mpAutoSwsContext.get();
	if (img_convert_context == NULL) {
		img_convert_context = sws_getContext(
				mpAutoCodecContextVideo.get()->width,
				mpAutoCodecContextVideo.get()->height,
				mpAutoCodecContextVideo.get()->pix_fmt,
				widthBitmap,
				heightBitmap,
				surfacePixelFormat,
				mSurfaceSwsFlags, NULL, NULL, NULL);
		mpAutoSwsContext.set(img_convert_context);
	}
	if (img_convert_context != NULL) {
		LOGD("sws_getContext OK");
		int height = sws_scale(img_convert_context, mpFrame.get()->data, mpFrame.get()->linesize,
				0, mpAutoCodecContextVideo.get()->height,
				mpFrameBitmap.get()->data, mpFrameBitmap.get()->linesize);
		LOGD("sws_scale OK: height=%d", height);
//		sws_freeContext(img_convert_context);
//		LOGD("sws_freeContext OK");
	} else {
		LOGD("sws_getContext NG");
	}

	return CODE_DECODE_FRAME_VIDEO;
}

void NicoroFFmpegPlayer::decodeVideoSetData(JNIEnv * env, jobject thiz,
		jobject data) {
	jclass jcFFmpegData = GET_MEMBER_JCLASS(mJniFFmpegData, FFmpegData).getObjectClass(env, data);
	// 逆数になるのでr_frame_rateのdenとnumは入れ替えて使用
	GET_MEMBER_JFIELD(mJniFFmpegData, mTimeNumVideo).getFieldID(env, jcFFmpegData);
	GET_MEMBER_JFIELD(mJniFFmpegData, mTimeDenVideo).getFieldID(env, jcFFmpegData);
	GET_MEMBER_JFIELD(mJniFFmpegData, mTimeNumVideo).setField(
			env, data,
			mpAutoCodecContextVideo.get()->frame_number * mpFormatContext->streams[mStreamIndexVideo]->r_frame_rate.den);
	GET_MEMBER_JFIELD(mJniFFmpegData, mTimeDenVideo).setField(
			env, data,
			mpFormatContext->streams[mStreamIndexVideo]->r_frame_rate.num);
}

bool NicoroFFmpegPlayer::decodeAudio(JNIEnv * env, jobject thiz,
		jobject data) {
	LOGD("decodeAudio");

	AutoPacketRelease apr(mAutoPacket);

	jclass jcFFmpegData = GET_MEMBER_JCLASS(mJniFFmpegData, FFmpegData).getObjectClass(env, data);

	jfieldID jfAudioBufferSize = GET_MEMBER_JFIELD(mJniFFmpegData, mAudioBufferSize).getFieldID(env, jcFFmpegData);
	LOGD("jfAudioBufferSize=%p", jfAudioBufferSize);

	jfieldID jfAudioBuffer = GET_MEMBER_JFIELD(mJniFFmpegData, mAudioBuffer).getFieldID(env, jcFFmpegData);
	LOGD("jfAudioBuffer=%p", jfAudioBuffer);
#if (AUDIO_BUFFER_DIRECT_ALLOC)
	jobject joAudioBuffer = GET_MEMBER_JFIELD(mJniFFmpegData, mAudioBuffer).getField(env, data);
	LOGD("joAudioBuffer=%p", joAudioBuffer);
	int audioBufferLength = env->GetDirectBufferCapacity(joAudioBuffer);
	int16_t* pAudioBuffer = (int16_t*) env->GetDirectBufferAddress(joAudioBuffer);
#else
	jshortArray jsaAudioBuffer = GET_MEMBER_JFIELD(mJniFFmpegData, mAudioBuffer).getField(env, data);
	LOGD("jsaAudioBuffer=%p", jsaAudioBuffer);
	JShortArray jaAudioBuffer(env, jsaAudioBuffer, 0);
	int audioBufferLength = static_cast<int>(jaAudioBuffer.getArrayLength());
	audioBufferLength *= sizeof(jshort);
	jshort* pAudioBuffer = jaAudioBuffer.getElements();
#endif
	LOGD("audioBufferLength=%d", audioBufferLength);
	LOGD("pAudioBuffer=%p", pAudioBuffer);
	if (pAudioBuffer == NULL) {
		return false;
	}
	LOGD("mpAutoCodecContextAudio.get()=%p", mpAutoCodecContextAudio.get());
	int decodedSize = avcodec_decode_audio3(mpAutoCodecContextAudio.get(), pAudioBuffer, &audioBufferLength, &mPacket);
	LOGD("decodedSize=%d audioBufferLength=%d", decodedSize, audioBufferLength);
	LOG_DUMP_AVCODECCONTEXT(*mpAutoCodecContextAudio.get());
	if (decodedSize >= 0) {
		LOGD("avcodec_decode_audio3 OK");
		GET_MEMBER_JFIELD(mJniFFmpegData, mAudioBufferSize).setField(env, data, audioBufferLength);

		// TODO Seek実装するとなるとデコードサイズを単に加算するわけにいかなくなる
		GET_MEMBER_JFIELD(mJniFFmpegData, mTimeNumAudio).getFieldID(env, jcFFmpegData);
		GET_MEMBER_JFIELD(mJniFFmpegData, mTimeDenAudio).getFieldID(env, jcFFmpegData);
		int sumAudioDecodedSize = GET_MEMBER_JFIELD(mJniFFmpegData, mTimeNumAudio
				).getField(env, data);
		sumAudioDecodedSize += audioBufferLength;
		GET_MEMBER_JFIELD(mJniFFmpegData, mTimeNumAudio).setField(
				env, data, sumAudioDecodedSize);
		int timeDenAudio = mpAutoCodecContextAudio.get()->sample_rate;
		timeDenAudio *= mpAutoCodecContextAudio.get()->channels;
		switch (mpAutoCodecContextAudio.get()->sample_fmt) {
		case SAMPLE_FMT_U8:
//			timeDenAudio *= 1;
			break;
		case SAMPLE_FMT_S16:
			timeDenAudio *= 2;
			break;
		case SAMPLE_FMT_S32:
		case SAMPLE_FMT_FLT:
			timeDenAudio *= 4;
			break;
		case SAMPLE_FMT_DBL:
			timeDenAudio *= 8;
			break;
		}
		GET_MEMBER_JFIELD(mJniFFmpegData, mTimeDenAudio).setField(
				env, data, timeDenAudio);
	} else {
		LOGE("avcodec_decode_audio3 NG");
		return false;
	}
//	jaAudioBuffer.release();
	return true;
}

int NicoroFFmpegPlayer::seekFrameBySecond(JNIEnv * env, jobject thiz,
		int second) {
	const int num = mpAutoCodecContextVideo.get()->time_base.num;
	const int den = mpAutoCodecContextVideo.get()->time_base.den;
	int64_t avTimeSecond = second * num / den;
	int ret = avformat_seek_file(mpFormatContext, mStreamIndexVideo,
//			(avTimeSecond - AV_TIME_BASE),
//			INT64_MIN,
			(-9223372036854775807LL)-1LL,
			avTimeSecond,
//			(avTimeSecond + AV_TIME_BASE),
//			INT64_MAX,
			9223372036854775807LL,
			0);
	if (ret < 0) {
		LOGE("avformat_seek_file NG: %d", ret);
		return ret;
	}
	LOGD("avformat_seek_file OK");
	return mpAutoCodecContextVideo.get()->frame_number;
}

void NicoroFFmpegPlayer::setRescaleFlag(jint flag) {
	switch (flag) {
	case CODE_RESCALE_POINT:
		mSurfaceSwsFlags = SWS_POINT;
		break;
	case CODE_RESCALE_FAST_BILINEAR:
		mSurfaceSwsFlags = SWS_FAST_BILINEAR;
		break;
	case CODE_RESCALE_BILINEAR:
		mSurfaceSwsFlags = SWS_BILINEAR;
		break;
	case CODE_RESCALE_BICUBLIN:
		mSurfaceSwsFlags = SWS_BICUBLIN;
		break;
	case CODE_RESCALE_BICUBIC:
		mSurfaceSwsFlags = SWS_BICUBIC;
		break;
	case CODE_RESCALE_SPLINE:
		mSurfaceSwsFlags = SWS_SPLINE;
		break;
	case CODE_RESCALE_SINC:
		mSurfaceSwsFlags = SWS_SINC;
		break;
	default:
		LOGE("setRescaleFlag() unknown flag=%d", flag);
		assert(false);
		break;
	}
}

int NicoroFFmpegPlayer::readFuncFile(void *opaque, uint8_t *buf, int buf_size) {
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = static_cast<NicoroFFmpegPlayer*>(opaque);
	int readSize = fread(buf, 1, buf_size, pNicoroFFmpegPlayer->mfpFileStream);
	LOGD("readFuncFile: buf=%p buf_size=%d return=%d", buf, buf_size, readSize);
	return readSize;
}

int NicoroFFmpegPlayer::writeFuncFile(void *opaque, uint8_t *buf, int buf_size) {
	LOGD("writeFuncFile: buf=%p buf_size=%d return=%d", buf, buf_size, 0);
	return 0;
}

int64_t NicoroFFmpegPlayer::seekFuncFile(void *opaque, int64_t offset, int whence) {
	int64_t ret = -1;
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = static_cast<NicoroFFmpegPlayer*>(opaque);
	switch (whence) {
	case SEEK_SET:
	case SEEK_CUR:
	case SEEK_END:
		if (fseek(pNicoroFFmpegPlayer->mfpFileStream, offset, whence) == 0) {
			ret = ftell(pNicoroFFmpegPlayer->mfpFileStream);
		}
		break;
	case AVSEEK_SIZE:
		struct stat statBuf;
		if (fstat(fileno(pNicoroFFmpegPlayer->mfpFileStream), &statBuf) == 0) {
			ret = statBuf.st_size;
		}
		break;
	}
	LOGD("seekFuncFile: offset=%lld whence=%d return=%lld", offset, whence, ret);
	return ret;
}

int NicoroFFmpegPlayer::readFuncStream(void *opaque, uint8_t *buf, int buf_size) {
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = static_cast<NicoroFFmpegPlayer*>(opaque);
	jobject thiz = pNicoroFFmpegPlayer->mThiz;
	JNIEnv* env = pNicoroFFmpegPlayer->mEnv;
//	JNIEnv* env = NULL;
//	AutoDetachCurrentThread autoDetach(pNicoroFFmpegPlayer->mVM);
//	jint resultGetEnv = pNicoroFFmpegPlayer->mVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4);
//	switch (resultGetEnv) {
//	case JNI_EDETACHED:
//		pNicoroFFmpegPlayer->mVM->AttachCurrentThread(&env, NULL);
//		assert(env != NULL);
//		autoDetach.setExecDetach(true);
//		LOGD("readFuncStream in another thread");
//		break;
//	case JNI_OK:
//		assert(env != NULL);
//		autoDetach.setExecDetach(false);
//		LOGD("readFuncStream in current thread");
//		break;
//	case JNI_EVERSION:
//	default:
//		LOGD("GetEnv return unexpected value:%d", resultGetEnv);
//		assert(false);
//		return -1;
//	}
	jbyteArray readBuffer = pNicoroFFmpegPlayer->mReadBuffer;
	jobject ioCallback = pNicoroFFmpegPlayer->mIOCallback;

	if (env == NULL || thiz == NULL) {
		LOGE("readFuncStream is called outside JNI!!!");
		return -1;
	}
	if (readBuffer == NULL) {
		LOGE("readFuncStream() readBuffer is NULL!!!");
		return -1;
	}
	if (ioCallback == NULL) {
		LOGE("readFuncStream() ioCallback is NULL!!!");
		return -1;
	}

	jclass jcFFmpegIOCallback = GET_MEMBER_JCLASS(pNicoroFFmpegPlayer->mJniFFmpegIOCallback, FFmpegIOCallback).getObjectClass(env, ioCallback);

	jmethodID jmReadFromNativeCallback = GET_MEMBER_JMETHOD(pNicoroFFmpegPlayer->mJniFFmpegIOCallback, readFromNativeCallback).getMethodID(env, jcFFmpegIOCallback);
	jint read = GET_MEMBER_JMETHOD(pNicoroFFmpegPlayer->mJniFFmpegIOCallback, readFromNativeCallback
			).callReturnMethod(env, ioCallback, createJValueArgs(buf_size, readBuffer));

	if (read > 0) {
		JByteArray jaBufferForNative(env, readBuffer, JNI_ABORT);
		jbyte* pBufferForNative = jaBufferForNative.getElements();
		if (pBufferForNative == NULL) {
			return -1;
		}

		memcpy(buf, pBufferForNative, read);

		jaBufferForNative.release();
	}

	return read;
}

int64_t NicoroFFmpegPlayer::seekFuncStream(void *opaque, int64_t offset, int whence) {
	int64_t ret = -1;
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = static_cast<NicoroFFmpegPlayer*>(opaque);
	jobject thiz = pNicoroFFmpegPlayer->mThiz;
	JNIEnv* env = pNicoroFFmpegPlayer->mEnv;
//	JNIEnv* env = NULL;
//	AutoDetachCurrentThread autoDetach(pNicoroFFmpegPlayer->mVM);
//	jint resultGetEnv = pNicoroFFmpegPlayer->mVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4);
//	switch (resultGetEnv) {
//	case JNI_EDETACHED:
//		pNicoroFFmpegPlayer->mVM->AttachCurrentThread(&env, NULL);
//		assert(env != NULL);
//		autoDetach.setExecDetach(true);
//		LOGD("seekFuncStream in another thread");
//		break;
//	case JNI_OK:
//		assert(env != NULL);
//		autoDetach.setExecDetach(false);
//		LOGD("seekFuncStream in current thread");
//		break;
//	case JNI_EVERSION:
//	default:
//		LOGD("GetEnv return unexpected value:%d", resultGetEnv);
//		assert(false);
//		return -1;
//	}
	jobject ioCallback = pNicoroFFmpegPlayer->mIOCallback;

	if (env == NULL || thiz == NULL) {
		LOGE("seekFuncStream is called outside JNI!!!");
		return -1;
	}
	if (ioCallback == NULL) {
		LOGE("readFuncStream() ioCallback is NULL!!!");
		return -1;
	}

	jclass jcFFmpegIOCallback = GET_MEMBER_JCLASS(pNicoroFFmpegPlayer->mJniFFmpegIOCallback, FFmpegIOCallback).getObjectClass(env, ioCallback);
	LOGD("jclass FFmpegIOCallback=%p", jcFFmpegIOCallback);

	jmethodID jmSeekFromNativeCallback = GET_MEMBER_JMETHOD(pNicoroFFmpegPlayer->mJniFFmpegIOCallback, seekFromNativeCallback).getMethodID(env, jcFFmpegIOCallback);
	LOGD("jmethodID seekFromNativeCallback=%p", jmSeekFromNativeCallback);
	ret = GET_MEMBER_JMETHOD(pNicoroFFmpegPlayer->mJniFFmpegIOCallback, seekFromNativeCallback).callReturnMethod(env, ioCallback,
			createJValueArgs(static_cast<jlong>(offset), static_cast<jint>(whence)));

	LOGD("seekFuncStream: offset=%lld whence=%d return=%lld", offset, whence, ret);
	return ret;
}
