/*******************************************************************************
(c) NewTec GmbH 2015
$Author: dirr $
*******************************************************************************/

#include <stdlib.h>
#include "VideoCoder.h"

#define STREAM_FRAME_RATE	(25) /* 25 images/s */
#define STREAM_PIX_FMT		AV_PIX_FMT_YUV422P10 /* output pix_fmt */
#define	CONTAINER_EXTENSION	".nut"
#define	STREAM_CODECID	AV_CODEC_ID_FFV1	/* FF Video 1 Codec Id */
//#define	STREAM_CODECID	AV_CODEC_ID_V210	/* V210 Codec Id */

#ifdef DOINFOLOG
#define	THE_AV_LOGLEVEL	(AV_LOG_VERBOSE)	/* Loglevel */
#else
//#define	THE_AV_LOGLEVEL	(AV_LOG_INFO)		/* Loglevel (default) */
#define	THE_AV_LOGLEVEL	(AV_LOG_WARNING)	/* Loglevel Warning */
#endif

	VideoCoder::VideoCoder (void)
{
	memset(this, 0, sizeof(VideoCoder));
        //av_log_set_level(AV_LOG_DEBUG);
	av_log_set_level(THE_AV_LOGLEVEL);
}

	VideoCoder::~VideoCoder (void)
{
}

VideoCoder::Ret		VideoCoder::open (
	int		fWidth,
	int		fHeight,
	const char	*ftpAddress,
	const char	*ftpUser,
	const char	*ftpPasswd,
	const char	*fileNameBase)
{
	#define	FTPSPEC_SIZE	(100)
	char	aFtpspec[FTPSPEC_SIZE];
	int	ret;

	mWidth = fWidth;
	mHeight = fHeight;

#if 0	// use FTP
	/*FA Build FTP-Spec. */
	// ftp://[user[:password]@]server[:port]/path/to/remote/resource.mpeg
	if (FTPSPEC_SIZE <= snprintf(
		aFtpspec, FTPSPEC_SIZE, "ftp://%s:%s@%s/%s" CONTAINER_EXTENSION,
		ftpUser, ftpPasswd, ftpAddress, fileNameBase))
	{
		return RET_EPARAM;
	}
#else	// use local file
	/*FA Build File-Spec. */
	if (FTPSPEC_SIZE <= snprintf(
		aFtpspec, FTPSPEC_SIZE, "%s" CONTAINER_EXTENSION, fileNameBase))
	{
		return RET_EPARAM;
	}
#endif	// use FTP

	/*FA Initialize libavcodec, and register all codecs and formats. */
	av_register_all();

	/*FA Initialize network for formats. */
	if (0 > avformat_network_init())
	{
		return RET_EINIT;
	}

	// Find encoder.
	mCodec = avcodec_find_encoder(STREAM_CODECID);
	if (!mCodec)
	{
		av_log(NULL, AV_LOG_ERROR, "%s[%d]no codec\n",
		       __FILE__, __LINE__);
		return RET_EFORMAT;
	}


	/*FA Allocate output media context (should use NUT Muxer). */
	avformat_alloc_output_context2(&mFormatCtx, NULL, NULL, aFtpspec);
	if (!mFormatCtx)
	{
		av_log(NULL, AV_LOG_ERROR, "%s[%d]no output format context\n",
		       __FILE__, __LINE__);
		return RET_EFORMAT;
	}

	/*FA Set Codec */
        av_format_set_video_codec(mFormatCtx, mCodec);

	/*FA Add Stream to OutputFormat. */
	mVideoStream = avformat_new_stream(mFormatCtx, mCodec);
	if (!mVideoStream)
	{
		av_log(NULL, AV_LOG_ERROR, "%s[%d]no stream in output format\n",
		       __FILE__, __LINE__);
		return RET_EFORMAT;
	}

	/*FA Set Stream-Properties. */
	mVideoStream->sample_aspect_ratio = (AVRational){ 1, 1 };
	//mVideoStream->time_base = (AVRational){ 1, STREAM_FRAME_RATE };
	mVideoStream->time_base.num = 1;
	mVideoStream->time_base.den = STREAM_FRAME_RATE;
	/* For fixed-fps content,
	 * timebase should be 1/framerate and timestamp increments should be
	 * identical to 1. */
	mVideoStream->id = mFormatCtx->nb_streams-1; // Stream-ID

	/*FA Set Codec-Context in Stream. */
	mCodecCtx = mVideoStream->codec;
	mCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;	// ?
	mCodecCtx->codec_id = STREAM_CODECID;		// ?
	mCodecCtx->width = fWidth;
	mCodecCtx->height = fHeight;
	mCodecCtx->time_base = mVideoStream->time_base;
	mCodecCtx->gop_size = 0;
	mCodecCtx->pix_fmt = STREAM_PIX_FMT;
	mCodecCtx->sample_aspect_ratio = (AVRational){ 1, 1 };

	/*FA Open Codec. */
	if (0 > avcodec_open2(mCodecCtx, mCodec, NULL))
	{
		av_log(NULL, AV_LOG_ERROR, "%s[%d]err open codec_ctx\n",
		       __FILE__, __LINE__);
		return RET_ECONVERT;
	}

	/*FA Init Frame-Counter (== PTS). */
	mFrameCounter = 0;

	/*FA Open the output file, if needed. */
	if (!(mFormatCtx->oformat->flags & AVFMT_NOFILE))
	{
		ret = avio_open(&mFormatCtx->pb, aFtpspec, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			av_log(NULL, AV_LOG_ERROR, "%s[%d]Could not open '%s': %s\n",
			       __FILE__, __LINE__, aFtpspec, av_err2str(ret));
			return RET_EFORMAT;
		}
	}

	/*FA Allocate frame for feeding image to codec. */
	mFrame = av_frame_alloc();
	if (!mFrame)
	{
		av_log(NULL, AV_LOG_ERROR, "%s[%d]Could not allocate video frame\n",
		       __FILE__, __LINE__);
		return RET_EMEM;
	}

	/*FA Set Frame Properties. */
	mFrame->format = STREAM_PIX_FMT;
	mFrame->width = mWidth;
	mFrame->height = mHeight;
	mFrame->pict_type = AV_PICTURE_TYPE_I;
	mFrame->key_frame = 1;
	mFrame->interlaced_frame = 0;
	mFrame->sample_aspect_ratio = (AVRational){ 1, 1 };
	ret = av_frame_get_buffer(mFrame, 32);
	if (ret < 0)
	{
		av_log(NULL, AV_LOG_ERROR,
		       "%s[%d]Could not allocate raw picture buffer\n",
		       __FILE__, __LINE__);
		return RET_EMEM;
	}

	/*FA Some formats want stream headers to be separate. */
	if (mFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
	{
		mCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}

	av_dump_format(mFormatCtx, mVideoStream->id, aFtpspec, 1);

	/*FA init muxer, write output file header */
	if (avformat_write_header(mFormatCtx, NULL) < 0)
	{
		av_log(NULL, AV_LOG_ERROR, "%s[%d]Error open output file\n",
		       __FILE__, __LINE__);
		return RET_EFORMAT;
	}

	av_log_set_level(THE_AV_LOGLEVEL);

	return RET_OK;
}

VideoCoder::Ret		VideoCoder::putFrame (void)
{
	/*FA Convert V410-Array to Frame. */
	if (RET_OK != decodeV410(mFrame))
	{
		av_log(NULL, AV_LOG_ERROR, "%s[%d]err decode V410 array\n",
		       __FILE__, __LINE__);
		return RET_ECONVERT;
	}

	/*FA Set Timestamp (pts). */
	mFrame->pts = mFrameCounter++;

	/*FA Encode the Frame to the Output Muxer. */
	if (RET_OK != encode_write_frame(mFrame, NULL))
	{
		return RET_EFORMAT;
	}

	return RET_OK;
}

VideoCoder::Ret		VideoCoder::close (void)
{
	VideoCoder::Ret	aRet;
	int		gotData;

	/*FA Flush remainig data in codec to output muxer. */
	do
	{
		aRet = encode_write_frame(NULL, &gotData);
	} while ((RET_OK == aRet) && gotData);

	/*FA Write End-Data to Output. */
	if (0 != av_write_trailer(mFormatCtx))
	{
		/* ... */
	}

	/*FA Is output file to be closed? */
	if (!(mFormatCtx->oformat->flags & AVFMT_NOFILE))
	{
		/*FA Close the output file. */
		avio_close(mFormatCtx->pb);
	}

	// User is required to call avcodec_close() and avformat_free_context()
	// to clean up the allocation by avformat_new_stream().

	/*FA Close Codec. */
	if (0 != avcodec_close(mCodecCtx))
	{
		/* ... */
	}

	/*FA Close Muxer. */
	avformat_free_context(mFormatCtx);
	{
		/* ... */
	}

	/*FA Free the Frame. */
	//av_freep(&mFrame->data[0]);
	av_frame_free(&mFrame);

	/*FA Deinitialize network for formats. */
	if (0 > avformat_network_deinit())
	{
		return RET_EINIT;
	}

	return RET_OK;
}

VideoCoder::Ret		VideoCoder::encode_write_frame(
	AVFrame		*outFrame,
	int		*gotData)
{
	int		ret;
	int		gotOutput;
	AVPacket	enc_pkt;

	av_log(NULL, AV_LOG_DEBUG, "%s[%d]Encoding frame\n", __FILE__, __LINE__);

	if (0 == gotData)
	{
		gotData = &gotOutput;
	}
	*gotData = 0;

	/*FA Flush only? */
	if (NULL == outFrame)
	{
		/*FA Flush remaining data in codec to output muxer. */
		if (!(mFormatCtx->streams[0]->codec->codec->capabilities
		      & CODEC_CAP_DELAY))
		{
			av_log(NULL, AV_LOG_VERBOSE,
			       "%s[%d]Flushing no delayed data\n",
			       __FILE__, __LINE__);
			/*FA No data remaining in codec. */
			return RET_OK;
		}
		av_log(NULL, AV_LOG_VERBOSE, "%s[%d]Flushing w/o frame\n",
		       __FILE__, __LINE__);
	}

	/*FA Prepare new Packet */
	enc_pkt.data = NULL;
	enc_pkt.size = 0;
	av_init_packet(&enc_pkt);

	/*FA Encode frame to packet. */
	ret = avcodec_encode_video2(
		mFormatCtx->streams[0]->codec, &enc_pkt, outFrame, gotData);
	if (ret < 0)
	{
		return RET_ECONVERT;
	}
	if (!(*gotData))
	{
		return RET_OK;
	}

	/*FA Set Stream-Index and adjust Timestamp in packet for muxing. */
	enc_pkt.stream_index = 0;
	#if 1 // noch nicht in Version 2.2.x
	av_packet_rescale_ts(&enc_pkt,	// ? noetig ?
			     mFormatCtx->streams[0]->codec->time_base,
			     mFormatCtx->streams[0]->time_base);
	av_pkt_dump_log2(NULL, AV_LOG_VERBOSE, &enc_pkt, 0, mVideoStream);
	//log_packet(mFormatCtx, &enc_pkt);
	#else
	rescale_timestamps(&mCodecCtx->time_base, mVideoStream, &enc_pkt);
	//log_packet(mFormatCtx, &enc_pkt);
	#endif // noch nicht in Version 2.2.x

	/*FA Feed packet to output muxer. */
	av_log(NULL, AV_LOG_DEBUG, "%s[%d]Muxing frame\n", __FILE__, __LINE__);
	ret = av_interleaved_write_frame(mFormatCtx, &enc_pkt);
	if (ret < 0)
	{
		return RET_EFORMAT;
	}

	return RET_OK;
}

VideoCoder::Ret		VideoCoder::decodeV410 (
	AVFrame		*avFrame)
{
	int		aYPos, aXPos, aWidth;
	uint16_t	*y, *u, *v;
	const uint32_t	*src;

	if (0 > av_frame_make_writable(avFrame))
	{
		av_log(NULL, AV_LOG_ERROR, "%s[%d]not writable\n",
		       __FILE__, __LINE__);
	}

	// Round down width to multiples of 6 (Pixel).
	aWidth = (mWidth / 6) * 6;

	// BT.709 10-Bit-Range
	#define	YMIN	(64)
	#define	YMAX	(940)
	#define	UVMIN	(64)
	#define	UVMAX	(960)
	#define	UVGREY	(512)

	#define NEXTY(y)	((y) >= YMAX ? (y) = YMIN : (y)++)
	#define NEXTUV(uv)	((uv) >= UVMAX ? (uv) = UVMIN : (uv)++)

	static	uint16_t	testy = YMIN;
	static	uint16_t	testuv = UVGREY;

	uint16_t	ystart = testy;
	uint16_t	uvstart = testuv;

	NEXTY(testy);
	NEXTUV(testuv);

	/*FA Fill Dummy-Data to Frame. */
	for (aYPos = 0; aYPos < mHeight; aYPos++)
	{
		uint16_t	yval, uvval;
		unsigned char	*aPtr;

		yval = ystart; NEXTY(ystart);
		uvval = uvstart; NEXTUV(uvstart);

		/*FA Set Pointer to Component Planes. */
		#if 1
		aPtr = avFrame->data[0] + aYPos * avFrame->linesize[0];
		y = (uint16_t *)aPtr;
		aPtr = avFrame->data[1] + aYPos * avFrame->linesize[1];
		u = (uint16_t *)aPtr;
		aPtr = avFrame->data[2] + aYPos * avFrame->linesize[2];
		v = (uint16_t *)aPtr;
		#else
		y = (uint16_t*)avFrame->data[0]
			+ aYPos * (avFrame->linesize[0] / 2);
		u = (uint16_t*)avFrame->data[1]
			+ aYPos * (avFrame->linesize[1] / 2);
		v = (uint16_t*)avFrame->data[2]
			+ aYPos * (avFrame->linesize[2] / 2);
		#endif

		for (aXPos = 0; aXPos < aWidth - 5; aXPos += 6)
		{
			// 1. 32-Bit (uyv)
			*u++ = uvval;
			*y++ = yval;
			*v++ = uvval;
			//NEXTUV(uvval);
			NEXTY(yval);

			// 2. 32-Bit (-y-)
			*y++ = yval;
			NEXTY(yval);

			// 3. 32-Bit (uyv)
			*u++ = uvval;
			*y++ = yval;
			*v++ = uvval;
			//NEXTUV(uvval);
			NEXTY(yval);

			// 4. 32-Bit (-y-)
			*y++ = yval;
			NEXTY(yval);

			// 5. 32-Bit (uyv)
			*u++ = uvval;
			*y++ = yval;
			*v++ = uvval;
			//NEXTUV(uvval);
			NEXTY(yval);

			// 6. 32-Bit (-y-)
			*y++ = yval;
			NEXTY(yval);
		} // Loop aXPos

		// Handle remaining pixels of line
		aWidth = mWidth - aWidth;
		if (aWidth > 0)
		{
			aWidth--; // Pix. 0
			// 1. 32-Bit (uyv)
			*u++ = uvval;
			*y++ = yval;
			*v++ = uvval;
			//NEXTUV(uvval);
			NEXTY(yval);
		}
		if (aWidth > 0)
		{
			aWidth--; // Pix. 1
			// 2. 32-Bit (-y-)
			*y++ = yval;
			NEXTY(yval);
		}
		if (aWidth > 0)
		{
			aWidth--; // Pix. 2
			// 3. 32-Bit (uyv)
			*u++ = uvval;
			*y++ = yval;
			*v++ = uvval;
			//NEXTUV(uvval);
			NEXTY(yval);
		}
		if (aWidth > 0)
		{
			aWidth--; // Pix. 3
			// 4. 32-Bit (-y-)
			*y++ = yval;
			NEXTY(yval);
		}
		if (aWidth > 0)
		{
			aWidth--; // Pix. 4
			// 5. 32-Bit (uyv)
			*u++ = uvval;
			*y++ = yval;
			*v++ = uvval;
			//NEXTUV(uvval);
			NEXTY(yval);
		}
	} // Loop aYPos

	return RET_OK;
}

