#define DEBUG_LOGD false
#define DEBUG_LOGD_DUMP false

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

#include "NicoroFFmpegPlayer.h"

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

#include <android/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;
};

//IMPL_JFIELD_CLASS_SIG(mVideoLoader, "Ljp/sourceforge/nicoro/VideoLoader;")
IMPL_JFIELD_CLASS_SIG(mAudioBuffer, "[S")
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")

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

	av_register_all();
}

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) {
		LOGD("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);
	init_put_byte(&mByteIOContext, mStreamBuffer, STREAM_BUFFER_SIZE, 0,
			this, &readFuncFile, /*&writeFuncFile*/ NULL, &seekFuncFile);

	mfpFileStream = fopen(file, "rb");
	if (mfpFileStream == NULL) {
		LOGD("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) {
		LOGD("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) {
		LOGD("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, jobject ioCallback, jobject data) {
	int avRet = -1;
	LOGD("loadStream");

	mReadBuffer = buffer;
	mIOCallback = ioCallback;

	init_put_byte(&mByteIOContext, mStreamBuffer, STREAM_BUFFER_SIZE, 0,
			this, &readFuncStream, /*&writeFuncFile*/ NULL, &seekFuncStream);

//	jclass jcNicoroFFmpegPlayer = GET_MEMBER_JCLASS(mJniNicoroFFmpegPlayer, NicoroFFmpegPlayer).getObjectClass(env, thiz);
//	jfieldID jfVideoLoader = GET_MEMBER_JFIELD(mJniNicoroFFmpegPlayer, mVideoLoader).getFieldID(env, jcNicoroFFmpegPlayer);
//	if (jfVideoLoader == NULL) {
//		LOGD("GetFieldID mVideoLoader NG");
//		return false;
//	}
//	jobject joVideoLoader = GET_MEMBER_JFIELD(mJniNicoroFFmpegPlayer, mVideoLoader).getField(env, thiz);
	jclass jcFFmpegIOCallback = GET_MEMBER_JCLASS(mJniFFmpegIOCallback, FFmpegIOCallback).getObjectClass(env, ioCallback);

	jmethodID jmSeekFromNativeCallback = GET_MEMBER_JMETHOD(mJniFFmpegIOCallback, seekFromNativeCallback).getMethodID(env, jcFFmpegIOCallback);
	if (jmSeekFromNativeCallback == NULL) {
		LOGD("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) {
		LOGD("GetMethodID readFromNativeCallback NG");
		return false;
	}
	jint readBufSize = GET_MEMBER_JMETHOD(mJniFFmpegIOCallback, readFromNativeCallback).callReturnMethod(env, ioCallback,
			createJValueArgs(1024 * 24, buffer));
	LOGD("readFromNativeCallback return=%d", readBufSize);

//	jfieldID jfBufferForNative = GET_MEMBER_JFIELD(mJniVideoLoader, mBufferForNative).getFieldID(env, jcVideoLoader);
//	if (jfBufferForNative == NULL) {
//		LOGD("GetFieldID mBufferForNative NG");
//		return false;
//	}
//	JByteArray jaBufferForNative(env,
//			GET_MEMBER_JFIELD(mJniVideoLoader, mBufferForNative).getField(env, joVideoLoader),
//			JNI_ABORT);
	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, 1);

	jaBufferForNative.release();

	if (pInputFormat == NULL) {
		LOGD("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) {
		LOGD("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;

	init_put_byte(&mByteIOContext, mStreamBuffer, STREAM_BUFFER_SIZE, 0,
			this, &readFuncStream, /*&writeFuncFile*/ NULL, &seekFuncStream);

	AVInputFormat* pInputFormat = av_find_input_format(formatName);
	if (pInputFormat == NULL) {
		LOGD("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) {
		LOGD("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) {
		LOGD("logic error: mpInputFormat is NULL");
		return false;
	}

	mReadBuffer = buffer;
	mIOCallback = ioCallback;

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

	init_put_byte(&mByteIOContext, mStreamBuffer, STREAM_BUFFER_SIZE, 0,
			this, &readFuncStream, /*&writeFuncFile*/ NULL, &seekFuncStream);

	if ((avRet = av_open_input_stream(&mpFormatContextStream.get(), &mByteIOContext, "" /*file*/,
			mpInputFormat, NULL)) != 0) {
		LOGD("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) {
		LOGD("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) {
		LOGD("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) {
			LOGD("avcodec_open NG");
			return false;
		}
		LOGD("avcodec_open OK: pCodecContextVideo=%p", pCodecContextVideo);
		mpAutoCodecContextVideo.set(pCodecContextVideo);

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

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

	if (pCodecAudio != NULL) {
		if (avcodec_open(pCodecContextAudio, pCodecAudio) < 0) {
			LOGD("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);
	jmethodID jmCreateDrawBufferFromNativeCallback = GET_MEMBER_JMETHOD(
			mJniFFmpegInfoCallback, createDrawBufferFromNativeCallback)
			.getMethodID(env, jcFFmpegInfoCallback);
	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:
		LOGD("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) {
		LOGD("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;
	switch (skipVideoFrame) {
	case CODE_SKIP_VIDEO_FRAME_NONE:
		// skip_*を戻すタイミングは別
		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;
		decodeOnly = 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;
		decodeOnly = 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;
		decodeOnly = true;
		break;
	default:
		LOGD("unknown skipVideoFrame=%d", skipVideoFrame);
		return CODE_DECODE_FRAME_ERROR_OR_END;
	}

	int retDecodeVideo = decodeVideoMain(env, thiz, drawBuffer, data, decodeOnly);

	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);

	return retDecodeVideo;
}

int NicoroFFmpegPlayer::decodeVideoMain(JNIEnv * env, jobject thiz,
		jintArray drawBuffer, jobject data, bool decodeOnly) {
	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) {
		LOGD("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 (decodeOnly) {
		return CODE_DECODE_FRAME_VIDEO_SKIP;
	}

	assert(drawBuffer != NULL);

	if (got_picture != 0) {
		jclass jcFFmpegData = GET_MEMBER_JCLASS(mJniFFmpegData, FFmpegData).getObjectClass(env, data);

		JIntArray jaDrawBuffer(env, drawBuffer, 0);
		jint* pDrawBuffer = jaDrawBuffer.getElements();
		if (pDrawBuffer == NULL) {
			LOGD("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 widthBGRA = GET_MEMBER_JFIELD(mJniFFmpegData, mWidth).getField(env, data);
		int heightBGRA = GET_MEMBER_JFIELD(mJniFFmpegData, mHeight).getField(env, data);
		avpicture_fill(reinterpret_cast<AVPicture*>(mpFrameBGRA.get()),
				reinterpret_cast<uint8_t*>(pDrawBuffer),
				SURFACE_PIXEL_FORMAT,
				widthBGRA,
				heightBGRA);
		LOGD("avpicture_fill OK");
		LOG_DUMP_AVFRAME(*(mpFrameBGRA.get()));

		static const int sws_flags = SWS_BICUBIC;
		SwsContext* img_convert_context = sws_getContext(
				mpAutoCodecContextVideo.get()->width,
				mpAutoCodecContextVideo.get()->height,
				mpAutoCodecContextVideo.get()->pix_fmt,
				widthBGRA,
				heightBGRA,
				SURFACE_PIXEL_FORMAT,
				SURFACE_SWS_FLAGS, NULL, NULL, NULL);
		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,
					mpFrameBGRA.get()->data, mpFrameBGRA.get()->linesize);
			LOGD("sws_scale OK: height=%d", height);
			sws_freeContext(img_convert_context);
			LOGD("sws_freeContext OK");
		} else {
			LOGD("sws_getContext NG");
		}

		jaDrawBuffer.release();
		return CODE_DECODE_FRAME_VIDEO;
	} else {
		return CODE_DECODE_FRAME_VIDEO_SKIP;
	}
}

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 jfAudioBuffer = GET_MEMBER_JFIELD(mJniFFmpegData, mAudioBuffer).getFieldID(env, jcFFmpegData);
	LOGD("jfAudioBuffer=%p", jfAudioBuffer);
	jshortArray jsaAudioBuffer = GET_MEMBER_JFIELD(mJniFFmpegData, mAudioBuffer).getField(env, data);
	LOGD("jsaAudioBuffer=%p", jsaAudioBuffer);
	JShortArray jaAudioBuffer(env, jsaAudioBuffer, 0);

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

	jshort* pAudioBuffer = jaAudioBuffer.getElements();
	LOGD("pAudioBuffer=%p", pAudioBuffer);
	if (pAudioBuffer == NULL) {
		return false;
	}
	int audioBufferLength = static_cast<int>(jaAudioBuffer.getArrayLength());
	audioBufferLength *= sizeof(jshort);
	LOGD("audioBufferLength=%d", audioBufferLength);
	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 {
		LOGD("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) {
		LOGD("avformat_seek_file NG: %d", ret);
		return ret;
	}
	LOGD("avformat_seek_file OK");
	return mpAutoCodecContextVideo.get()->frame_number;
}

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) {
		LOGD("readFuncStream is called outside JNI!!!");
		return -1;
	}
	if (readBuffer == NULL) {
		LOGD("readFuncStream() readBuffer is NULL!!!");
		return -1;
	}
	if (ioCallback == NULL) {
		LOGD("readFuncStream() ioCallback is NULL!!!");
		return -1;
	}

//	jclass jcNicoroFFmpegPlayer = GET_MEMBER_JCLASS(pNicoroFFmpegPlayer->mJniNicoroFFmpegPlayer, NicoroFFmpegPlayer).getObjectClass(env, thiz);
//	jfieldID jfVideoLoader = GET_MEMBER_JFIELD(pNicoroFFmpegPlayer->mJniNicoroFFmpegPlayer, mVideoLoader).getFieldID(env, jcNicoroFFmpegPlayer);
//	jobject joVideoLoader = GET_MEMBER_JFIELD(pNicoroFFmpegPlayer->mJniNicoroFFmpegPlayer, mVideoLoader).getField(env, thiz);
//	if (joVideoLoader == 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) {
//		jfieldID jfBufferForNative = GET_MEMBER_JFIELD(pNicoroFFmpegPlayer->mJniVideoLoader, mBufferForNative).getFieldID(env, jcVideoLoader);
//		jbyteArray jbaBufferForNative = GET_MEMBER_JFIELD(pNicoroFFmpegPlayer->mJniVideoLoader, mBufferForNative).getField(env, joVideoLoader);
//		JByteArray jaBufferForNative(env, jbaBufferForNative, JNI_ABORT);
		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) {
		LOGD("seekFuncStream is called outside JNI!!!");
		return -1;
	}
	if (ioCallback == NULL) {
		LOGD("readFuncStream() ioCallback is NULL!!!");
		return -1;
	}

//	jclass jcNicoroFFmpegPlayer = GET_MEMBER_JCLASS(pNicoroFFmpegPlayer->mJniNicoroFFmpegPlayer, NicoroFFmpegPlayer).getObjectClass(env, thiz);
//	LOGD("jclass NicoroFFmpegPlayer=%p", jcNicoroFFmpegPlayer);
//	jfieldID jfVideoLoader = GET_MEMBER_JFIELD(pNicoroFFmpegPlayer->mJniNicoroFFmpegPlayer, mVideoLoader).getFieldID(env, jcNicoroFFmpegPlayer);
//	LOGD("jfieldID mVideoLoader=%p", jfVideoLoader);
//	jobject joVideoLoader = GET_MEMBER_JFIELD(pNicoroFFmpegPlayer->mJniNicoroFFmpegPlayer, mVideoLoader).getField(env, thiz);
//	LOGD("jobject mVideoLoader=%p", joVideoLoader);
//	if (joVideoLoader == 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;
}
