/*
*			GPAC - Multimedia Framework C SDK
*
*			Authors: Romain Bouqueau, Jean Le Feuvre
*			Copyright (c) 2014-2016 GPAC Licensing
*			Copyright (c) 2016-2020 Telecom Paris
*					All rights reserved
*
*  This file is part of GPAC / Dektec SDI video output filter
*
*  GPAC is free software; you can redistribute it and/or modify
*  it under the terms of the GNU Lesser General Public License as published by
*  the Free Software Foundation; either version 2, or (at your option)
*  any later version.
*
*  GPAC is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; see the file COPYING.  If not, write to
*  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/

#include "dektec_video.h"

#ifdef GPAC_HAS_DTAPI

#ifndef FAKE_DT_API

static void OnNewFrameAudio(DtMxData *pData, const DtCbkCtx *ctx)
{
	// Must have a valid frame
	DtMxRowData &OurRow = pData->m_Rows[0];
	if (OurRow.m_CurFrame->m_Status != DT_FRMSTATUS_OK) {
		return; // Frame is not valid so we have to assume the frame buffers are unusable
	}

	// Get frame audio data
	DtMxAudioData&  AudioData = OurRow.m_CurFrame->m_Audio;

	// Init channel status word for all (valid) audio channels
	AudioData.InitChannelStatus();
}

static void OnNewFrameVideo(DtMxData *pData, const DtCbkCtx  *cbck)
{
	u64 now = gf_sys_clock_high_res();
	const DtMxRowData&  OurRow = pData->m_Rows[0];
	GF_DTOutCtx *ctx = cbck->ctx;
	GF_FilterPacket *pck;
	
	if (!ctx->is_configured || ctx->is_eos) return;
	if (pData->m_NumSkippedFrames > 0) {
		GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] [%lld] #skipped frames=%d\n", pData->m_Frame, pData->m_NumSkippedFrames));
	}

	if (!ctx->init_clock) {
		ctx->init_clock = ctx->last_frame_time = now;
	}
	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] output frame %d at %d ms - %d since last\n", ctx->frameNum, (u32)(now - ctx->init_clock) / 1000, now - ctx->last_frame_time));

	ctx->last_frame_time = now;

	if (!cbck->video) {
		GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] no connected pid !\n"));
		return;
	}

	// Must have a valid frame
	if (OurRow.m_CurFrame->m_Status != DT_FRMSTATUS_OK)
	{
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Not valid frame 4k\n"));
		return; // Frame is not valid so we have to assume the frame buffers are unusable
	}
	if (pData->m_NumSkippedFrames > 0) {
		GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] [%lld] #skipped frames=%d\n", pData->m_Frame, pData->m_NumSkippedFrames));
	}

	pck = gf_filter_pid_get_packet(cbck->video);
	if (!pck) {
		if (gf_filter_pid_is_eos(cbck->video)) {
			ctx->is_eos = GF_TRUE;
		} else {
			GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] no packet ready !\n"));
		}
		return;
	}
	ctx->is_eos = GF_FALSE;

	u8 *pY, *pU, *pV;
	u32 pck_size = 0;
	u32 stride = ctx->stride, stride_uv= ctx->stride_uv;
	pU = pV = NULL;
	pY = (u8 *) gf_filter_pck_get_data(pck, &pck_size);
	if (!pY) {
		GF_FilterFrameInterface *hwframe = gf_filter_pck_get_frame_interface(pck);
		if (hwframe) {
			hwframe->get_plane(hwframe, 0, (const u8 **) &pY, &stride);
			if (ctx->nb_planes>1)
				hwframe->get_plane(hwframe, 1, (const u8 **) &pU, &stride_uv);
			if (ctx->nb_planes>2)
				hwframe->get_plane(hwframe, 2, (const u8 **) &pV, &stride_uv);
		}
	}
	if (!pY) {
		gf_filter_pid_drop_packet(cbck->video);
		GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] No data associated with packet\n"));
		return;
	}
	if (!pU && !pV) {
		if (ctx->nb_planes>1)
			pU = pY + stride * ctx->height;
		if (ctx->nb_planes>2)
			pV = pU + stride_uv * ctx->uv_height;
	}

	//TODO - move all this into color convertion code in libgpac !

	const DtMxVideoBuf&  VideoBuf = OurRow.m_CurFrame->m_Video[0];
	u8* pDstY = VideoBuf.m_Planes[0].m_pBuf;
	u8* pDstU = VideoBuf.m_Planes[1].m_pBuf;
	u8* pDstV = VideoBuf.m_Planes[2].m_pBuf;

	if (ctx->is_10b) {
		u32 nb_pix = 2 * ctx->width*ctx->height;
		int lineC = 2 * ctx->width / 2;
		unsigned char *pSrcY = pY;
		unsigned char *pSrcU = pU;
		unsigned char *pSrcV = pV;

		if (ctx->clip) {
			const u16 nYRangeMin = 4;
			const u16 nYRangeMax = 1019;
			const __m128i mMin = _mm_set1_epi16(nYRangeMin);
			const __m128i mMax = _mm_set1_epi16(nYRangeMax);

			for (u32 i = 0; i < nb_pix; i += 16) {
				_mm_storeu_si128((__m128i *)&pDstY[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcY[i]))));
			}

			for (u32 h = 0; h < ctx->height / 2; h++) {
				for (u32 i = 0; i < ctx->width; i += 16) {
					_mm_storeu_si128((__m128i *)&pDstU[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcU[i]))));
				}
				//420->422: copy over each U and V line
				memcpy(pDstU + lineC, pDstU, lineC);
				pSrcU += lineC;
				pDstU += 2 * lineC;
			}

			//NV12 formats
			if (!pSrcV) pSrcV = pU + 1;

			for (u32 h = 0; h < ctx->height / 2; h++) {
				for (u32 i = 0; i < ctx->width; i += 16) {
					_mm_storeu_si128((__m128i *)&pDstV[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcV[i]))));
				}
				memcpy(pDstV + lineC, pDstV, lineC);
				pSrcV += lineC;
				pDstV += 2 * lineC;
			}
		}
		else {
			memcpy(pDstY, pY, stride * ctx->height);

			//420->422: copy over each U and V line
			for (u32 h = 0; h < ctx->height / 2; h++) {
				memcpy(pDstU, pSrcU, lineC);
				memcpy(pDstU + lineC, pSrcU, lineC);
				pSrcU += lineC;
				pDstU += 2 * lineC;
			}
			for (u32 h = 0; h < ctx->height / 2; h++) {
				memcpy(pDstV, pSrcV, lineC);
				memcpy(pDstV + lineC, pSrcV, lineC);
				pSrcV += lineC;
				pDstV += 2 * lineC;
			}
		}
	}
	else {
		const int lineC = ctx->width / 2;
		u32 nb_pix = ctx->width*ctx->height;
		unsigned char *pSrcY = pY;
		unsigned char *pSrcU = pU;
		unsigned char *pSrcV = pV;

		const u16 nYRangeMin = 4;
		const u16 nYRangeMax = 1019;

		for (u32 i = 0; i < nb_pix; i++) {
			u16 srcy = ((u16)(*pSrcY)) << 2;
			if (ctx->clip) {
				if (srcy < nYRangeMin)      srcy = nYRangeMin;
				else if (srcy > nYRangeMax) srcy = nYRangeMax;
			}
			*(short *)pDstY = srcy;

			pSrcY++;
			pDstY += sizeof(short); //char type but short buffer
		}

		u32 hw = ctx->width / 2;
		u32 hh = ctx->height / 2;

		for (u32 i = 0; i < hh; i++) {
			//420->422: copy over each U and V line
			u8 *p_dst_u = pDstU + sizeof(short) * lineC * i * 2;
			u8 *p_dst_v = pDstV + sizeof(short) * lineC * i * 2;
			u8 *p_src_u = NULL;
			u8 *p_src_v = NULL;
			u8 *op_dst_u = p_dst_u;
			u8 *op_dst_v = p_dst_v;

			//NV12 formats
			if (!pV) {
				p_src_u = pSrcU + 2 * ctx->stride_uv * i;
				p_src_v = p_src_u + 1;
			}
			else {
				p_src_u = pSrcU + ctx->stride_uv * i;
				p_src_v = pSrcV + ctx->stride_uv * i;
			}

			for (u32 j = 0; j < hw; j++) {
				u16 srcu = ((u16)(*p_src_u)) << 2;
				u16 srcv = ((u16)(*p_src_v)) << 2;

				if (ctx->clip) {
					if (srcu < nYRangeMin)      srcu = nYRangeMin;
					else if (srcu > nYRangeMax) srcu = nYRangeMax;

					if (srcv < nYRangeMin)      srcv = nYRangeMin;
					else if (srcv > nYRangeMax) srcv = nYRangeMax;
				}

				*(short *)p_dst_u = srcu;
				p_src_u += 2;
				p_dst_u += sizeof(short); //char type but short buffer

				*(short *)p_dst_v = srcv;
				p_src_v += 2;
				p_dst_v += sizeof(short); //char type but short buffer
			}
			memcpy(op_dst_u + lineC * sizeof(short), op_dst_u, lineC * sizeof(short));
			memcpy(op_dst_v + lineC * sizeof(short), op_dst_v, lineC * sizeof(short));
		}
	}

	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] wrote YUV data in "LLU" us\n", gf_sys_clock_high_res() - now));
	gf_filter_pid_drop_packet(cbck->video);
	ctx->frameNum++;
}

static void OnNewFrame(DtMxData *pData, void *pOpaque) {
	const DtCbkCtx *ctx = ((const DtCbkCtx*)pOpaque);
	if (ctx->video)
		OnNewFrameVideo(pData, ctx);
	else if (ctx->audio)
		OnNewFrameAudio(pData, ctx);
}

static void dtout_disconnect(GF_DTOutCtx *ctx)
{
	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] Disconnecting\n"));
	DtMxProcess *Matrix = ctx->dt_matrix;
	ctx->is_configured = GF_FALSE;

	DTAPI_RESULT res = Matrix->Stop();
	if (res != DTAPI_OK)
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't stop transmission: %s\n", DtapiResult2Str(res)));

	Matrix->Reset();
	ctx->dt_device->Detach();
	ctx->is_sending = GF_FALSE;
}


static GF_Err dtout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
	DTAPI_RESULT res;
	int port = 1;
	Bool send_play = GF_FALSE;
	Bool is4K = GF_FALSE;
	Bool is_audio = GF_FALSE;
	GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter);
	const GF_PropertyValue *p;
	DtDevice *dt_device = ctx->dt_device;

	if (is_remove) {
		if (ctx->audio_cbk.audio == pid) {
			ctx->audio_cbk.audio = NULL;
		}
		else if (ctx->video_cbk.video == pid) {
			ctx->video_cbk.video = NULL;
		}
		if (ctx->is_sending && !ctx->audio_cbk.audio && !ctx->video_cbk.video) {
			dtout_disconnect(ctx);
		}
		return GF_OK;
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
	if (!p) return GF_NOT_SUPPORTED;
	if (p->value.uint == GF_STREAM_VISUAL) {
		if (!ctx->video_cbk.video) {
			ctx->video_cbk.video = pid;
			send_play = GF_TRUE;
		}
		else if (ctx->video_cbk.video != pid) return GF_REQUIRES_NEW_INSTANCE;
	}
	else if (p->value.uint == GF_STREAM_AUDIO) {
		if (!ctx->audio_cbk.audio) {
			ctx->audio_cbk.audio = pid;
			send_play = GF_TRUE;
		}
		else if (ctx->audio_cbk.audio != pid) return GF_REQUIRES_NEW_INSTANCE;
		is_audio=GF_TRUE;
	}
	else {
		return GF_NOT_SUPPORTED;
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
	if (!p || (p->value.uint != GF_CODECID_RAW)) return GF_NOT_SUPPORTED;


	if (send_play) {
		GF_FilterEvent fevt;
		//set a minimum buffer (although we don't buffer)
		GF_FEVT_INIT(fevt, GF_FEVT_BUFFER_REQ, pid);
		fevt.buffer_req.max_buffer_us = 200000;
		gf_filter_pid_send_event(pid, &fevt);

		gf_filter_pid_init_play_event(pid, &fevt, ctx->start, 1.0, "DekTecOut");
		gf_filter_pid_send_event(pid, &fevt);
	}

	if (is_audio) {
		//todo
		return GF_NOT_SUPPORTED;
	}

	u32 width = 0;
	u32 height = 0;
	u32 pix_fmt = 0;
	u32 stride = 0;
	u32 stride_uv = 0;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
	if (p) width = p->value.uint;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
	if (p) height = p->value.uint;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT);
	if (p) pix_fmt = p->value.uint;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE);
	if (p) stride = p->value.uint;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV);
	if (p) stride_uv = p->value.uint;

	//not configured yet
	if (!width || !height || !pix_fmt) return GF_OK;

	Bool is_10_bits = GF_FALSE;
	switch (pix_fmt) {
	case GF_PIXEL_YUV:
		break;
	case GF_PIXEL_YUV_10:
		is_10_bits = GF_TRUE;
		break;
	case GF_PIXEL_YUV422:
		break;
	case GF_PIXEL_YUV422_10:
		is_10_bits = GF_TRUE;
		break;
	case GF_PIXEL_YUV444:
		break;
	case GF_PIXEL_YUV444_10:
		is_10_bits = GF_TRUE;
		break;
	case GF_PIXEL_NV12:
	case GF_PIXEL_NV21:
		break;
	case GF_PIXEL_NV12_10:
	case GF_PIXEL_NV21_10:
		is_10_bits = GF_TRUE;
		break;
	default:
		GF_PropertyValue val;
		val.type = GF_PROP_UINT;
		val.value.uint = GF_PIXEL_YUV;
		gf_filter_pid_negociate_property(pid, GF_PROP_PID_PIXFMT, &val);
		return GF_OK;
	}
	if (!stride) stride = width * (is_10_bits ? 2 : 1);
	if (!stride_uv) stride_uv = stride / 2;

	GF_Fraction fps = { 30, 1 };
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS);
	if (p) fps = p->value.frac;

	if ((ctx->width == width) && (ctx->height == height) && (ctx->pix_fmt == pix_fmt) && (ctx->framerate.num * fps.den == ctx->framerate.den * fps.num) && (ctx->stride==stride) && ctx->is_configured)
		return GF_OK;

	ctx->width = width;
	ctx->height = height;
	ctx->pix_fmt = pix_fmt;
	ctx->framerate = fps;
	ctx->is_10b = is_10_bits;
	ctx->stride = stride;
	ctx->stride_uv = stride_uv;

	gf_pixel_get_size_info((GF_PixelFormat) pix_fmt, width, height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height);

	int dFormat = -1, linkStd = -1;

	GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Reconfigure to %dx%d @ %g FPS %d/%d bits src - SDI clipping: %s\n", width, height, fps.num, fps.den, is_10_bits ? "10" : "8", ctx->clip ? "yes" : "no"));
	if (ctx->is_configured) {
		DtMxProcess *Matrix = ctx->dt_matrix;
		if (ctx->is_sending) {
			res = Matrix->Stop();
			if (res != DTAPI_OK) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't stop transmission: %s\n", DtapiResult2Str(res)));
			}
		}
		Matrix->Reset();
		ctx->is_configured = GF_FALSE;
		ctx->is_sending = GF_FALSE;
	}

	if (fps.num == 30 * fps.den) {
		ctx->frame_scale = 30;
		ctx->frame_dur = 1;
		if ((ctx->width == 1280) && (ctx->height == 720)) {
			dFormat = DTAPI_IOCONFIG_720P30;
		}
		else if ((ctx->width == 1920) && (ctx->height == 1080)) {
			dFormat = DTAPI_IOCONFIG_1080P30;
		}
		else if ((ctx->width == 3840) && (ctx->height == 2160)) {
			dFormat = DTAPI_VIDSTD_2160P30;
		}
	}
	else if (fps.num == 50 * fps.den) {
		ctx->frame_scale = 50;
		ctx->frame_dur = 1;
		if ((ctx->width == 1280) && (ctx->height == 720)) {
			dFormat = DTAPI_IOCONFIG_720P50;
		}
		else if ((ctx->width == 1920) && (ctx->height == 1080)) {
			dFormat = DTAPI_IOCONFIG_1080P50;
		}
		else if ((ctx->width == 3840) && (ctx->height == 2160)) {
			dFormat = DTAPI_VIDSTD_2160P50;
		}
	}
	else if (fps.num == 60 * fps.den) {
		ctx->frame_scale = 60;
		ctx->frame_dur = 1;
		if ((ctx->width == 1280) && (ctx->height == 720)) {
			dFormat = DTAPI_IOCONFIG_720P60;
		}
		else if ((ctx->width == 1920) && (ctx->height == 1080)) {
			dFormat = DTAPI_IOCONFIG_1080P60;
		}
		else if ((ctx->width == 3840) && (ctx->height == 2160)) {
			dFormat = DTAPI_VIDSTD_2160P60;
		}
		GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] Dealing with 60 fps video is not stable, use with caution.\n"));
	}
	else if (fps.num*100 == 5994 * fps.den) {
		ctx->frame_scale = 3000;
		ctx->frame_dur = 1001;

		if ((ctx->width == 1280) && (ctx->height == 720)) {
			dFormat = DTAPI_IOCONFIG_720P59_94;
		}
		else if ((ctx->width == 1920) && (ctx->height == 1080)) {
			dFormat = DTAPI_IOCONFIG_1080P59_94;
		}
		else if ((ctx->width == 3840) && (ctx->height == 2160)) {
			dFormat = DTAPI_VIDSTD_2160P59_94;
		}
	}
	else if (fps.num == 24 * fps.den) {
		ctx->frame_scale = 24;
		ctx->frame_dur = 1;
		if ((ctx->width == 1280) && (ctx->height == 720)) {
			dFormat = DTAPI_IOCONFIG_720P24;
		}
		else if ((ctx->width == 1920) && (ctx->height == 1080)) {
			dFormat = DTAPI_IOCONFIG_1080P24;
		}
		else if ((ctx->width == 3840) && (ctx->height == 2160)) {
			dFormat = DTAPI_VIDSTD_2160P24;
		}
	}
	else if (fps.num == 25 * fps.den) {
		ctx->frame_scale = 25;
		ctx->frame_dur = 1;
		if ((ctx->width == 1280) && (ctx->height == 720)) {
			dFormat = DTAPI_IOCONFIG_720P25;
		}
		else if ((ctx->width == 1920) && (ctx->height == 1080)) {
			dFormat = DTAPI_IOCONFIG_1080P25;
		}
		else if ((ctx->width == 3840) && (ctx->height == 2160)) {
			dFormat = DTAPI_VIDSTD_2160P25;
		}
	}

	if (dFormat == -1) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unsupported frame rate %d/%d / resolution %dx%d\n", fps.num, fps.den, ctx->width, ctx->height));
		return GF_FILTER_NOT_SUPPORTED;
	}

	if ((ctx->width == 3840) && (ctx->height == 2160)) {
		is4K = GF_TRUE;
		linkStd = DTAPI_VIDLNK_4K_SMPTE425B;
	}

	port = ctx->port ? ctx->port : 1;

	DtMxPort mxPort;
	if (!is4K)
	{
		res = dt_device->SetIoConfig(port, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT);
		if (res != DTAPI_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config for the device: %s\n", DtapiResult2Str(res)));
			return GF_BAD_PARAM;
		}

		int IoStdValue = -1, IoStdSubValue = -1;
		res = DtapiVidStd2IoStd(dFormat, IoStdValue, IoStdSubValue);
		if (res != DTAPI_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unknown VidStd: %s\n", DtapiResult2Str(res)));
			return GF_BAD_PARAM;
		}

		res = dt_device->SetIoConfig(port, DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue);
		if (res != DTAPI_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config: %s\n", DtapiResult2Str(res)));
			return GF_BAD_PARAM;
		}
		res = mxPort.AddPhysicalPort(dt_device, port);
		if (res != DTAPI_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to add port %d to logical port", port));
			return GF_BAD_PARAM;
		}
	}
	else
	{
		DtMxPort mxPort4k(dFormat, linkStd);
		for (int i = 0; i < 4; i++)
		{
			res = dt_device->SetIoConfig(i + 1, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT);
			if (res != DTAPI_OK) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config for the device: %s\n", DtapiResult2Str(res)));
				return GF_BAD_PARAM;
			}
		}

		int IoStdValue = -1, IoStdSubValue = -1;
		res = DtapiVidStd2IoStd(dFormat, linkStd, IoStdValue, IoStdSubValue);
		if (res != DTAPI_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unknown VidStd: %s\n", DtapiResult2Str(res)));
			return GF_BAD_PARAM;
		}

		for (int i = 0; i < 4; i++)
		{
			res = dt_device->SetIoConfig(i + 1, DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue);
			if (res != DTAPI_OK) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config: %s\n", DtapiResult2Str(res)));
				return GF_BAD_PARAM;
			}
		}
		for (int i = 0; i < 4; i++)
		{
			res = mxPort4k.AddPhysicalPort(dt_device, i + 1);
			if (res != DTAPI_OK) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to add port %d to logical 4k port", (i + 1)));
				return GF_BAD_PARAM;
			}
		}

		DtIoConfig  Cfg;
		std::vector<DtIoConfig>  IoConfigs;
		DtHwFuncDesc*  pHwf = dt_device->m_pHwf;
		for (int i = 0; i < dt_device->m_pHwf->m_DvcDesc.m_NumPorts; i++, pHwf++)
		{
			// Check if the port has GENNREF capability
			if ((pHwf->m_Flags & DTAPI_CAP_GENREF) == 0)
				continue;   // No GENREF cap

			Cfg.m_Port = pHwf->m_Port;
			Cfg.m_Group = DTAPI_IOCONFIG_GENREF;

			// Check if this is the internal GENREF (has DTAPI_CAP_VIRTUAL)?
			if ((pHwf->m_Flags & DTAPI_CAP_VIRTUAL) != 0)
				Cfg.m_Value = DTAPI_IOCONFIG_TRUE;   // The internal => enable as GENREF
			else
				Cfg.m_Value = DTAPI_IOCONFIG_FALSE;  // Not the internal => disable as GENREF
			Cfg.m_SubValue = -1;
			Cfg.m_ParXtra[0] = Cfg.m_ParXtra[1] = -1;
			// Add to list
			IoConfigs.push_back(Cfg);

			// Set the IO-standard for 'THE' GENREF port
			if (Cfg.m_Value != DTAPI_IOCONFIG_TRUE)
				continue;
			Cfg.m_Group = DTAPI_IOCONFIG_IOSTD;
			// Set a standard with matching frame rate
			DtVidStdInfo  VidInfo;
			res = ::DtapiGetVidStdInfo(dFormat, linkStd, VidInfo);
			Cfg.m_Value = DTAPI_IOCONFIG_HDSDI;
			switch ((int)VidInfo.m_Fps)
			{
			case 23: Cfg.m_SubValue = DTAPI_IOCONFIG_720P23_98; break;
			case 24: Cfg.m_SubValue = DTAPI_IOCONFIG_720P24; break;
			case 25: Cfg.m_SubValue = DTAPI_IOCONFIG_720P25; break;
			case 29: Cfg.m_SubValue = DTAPI_IOCONFIG_720P29_97; break;
			case 30: Cfg.m_SubValue = DTAPI_IOCONFIG_720P30; break;
			case 50: Cfg.m_SubValue = DTAPI_IOCONFIG_720P50; break;
			case 59: Cfg.m_SubValue = DTAPI_IOCONFIG_720P59_94; break;
			case 60: Cfg.m_SubValue = DTAPI_IOCONFIG_720P60; break;
			}
			Cfg.m_ParXtra[0] = Cfg.m_ParXtra[1] = -1;
			// Add to list
			IoConfigs.push_back(Cfg);
		}
		res = dt_device->SetIoConfig(IoConfigs.data(), (int)IoConfigs.size());
		if (res != DTAPI_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to apply genref\n"));
			return GF_BAD_PARAM;
		}
		mxPort = mxPort4k;
	}

	res = ctx->dt_matrix->AttachRowToOutput(0, mxPort);
	if (res != DTAPI_OK) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't attach to port %d: %s\n", port, DtapiResult2Str(res)));
		return GF_BAD_PARAM;
	}

	/* --- Set row configuration --- */
	DtMxRowConfig  RowConfig;
	RowConfig.m_Enable = true;
	RowConfig.m_RowSize = 1;       // Keep a history of one frame

	// Enable the video
	RowConfig.m_VideoEnable = true;
	//todo, make this configurable !
	RowConfig.m_Video.m_PixelFormat = DT_PXFMT_YUV422P_16B;  // Use a planar format (16-bit)
	RowConfig.m_Video.m_StartLine1 = RowConfig.m_Video.m_StartLine2 = 1;
	RowConfig.m_Video.m_NumLines1 = RowConfig.m_Video.m_NumLines2 = -1;
	RowConfig.m_Video.m_Scaling = DTAPI_SCALING_OFF;   // No scaling for this row
	RowConfig.m_Video.m_LineAlignment = 8; // Want an 8-byte alignment

	// Enable audio
	RowConfig.m_AudioEnable = true;
	// We will add four channels (2x stereo)
	for (int i = 0; i < 4; i++)
	{
		DtMxAudioConfig  AudioConfig;
		AudioConfig.m_Index = i;
		AudioConfig.m_DeEmbed = false;  // Nothing to de-embed for an output only row
		AudioConfig.m_OutputMode = DT_OUTPUT_MODE_ADD;  // We want to add audio samples
		AudioConfig.m_Format = DT_AUDIO_SAMPLE_PCM;     // We will add PCM samples

		// Add to config list
		RowConfig.m_Audio.push_back(AudioConfig);
	}

	// Apply the row configuration
	res = ctx->dt_matrix->SetRowConfig(0, RowConfig);
	if (res != DTAPI_OK) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to set row 0 config"));
		return GF_BAD_PARAM;
	}

	/* --- Final Configuration --- */
	// Determine the default recommended end-to-end delay for this configuration

	int  DefEnd2EndDelay = 0;
	double CbFrames = 0.0;    // Time available for the call-back method
	ctx->dt_matrix->GetDefEndToEndDelay(DefEnd2EndDelay, CbFrames);
	GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut]  Default end-to-end delay = %d Frames\n", DefEnd2EndDelay));
	GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut]  Time-for-Callback = %.1f Frame Period\n", CbFrames));
	// Apply the default end-to-end delay
	res = ctx->dt_matrix->SetEndToEndDelay(DefEnd2EndDelay);
	if (res != DTAPI_OK) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to set the end-to-end delay (delay=%d)", DefEnd2EndDelay));
		return GF_BAD_PARAM;
	}

	res = ctx->dt_matrix->AddMatrixCbFunc(OnNewFrame, &ctx->audio_cbk);
	if (res != DTAPI_OK) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed register audio callback function"));
		return GF_BAD_PARAM;
	}
	res = ctx->dt_matrix->AddMatrixCbFunc(OnNewFrame, &ctx->video_cbk);
	if (res != DTAPI_OK) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed register video callback function"));
		return GF_BAD_PARAM;
	}

	ctx->frameNum = 1;
	ctx->is_configured = GF_TRUE;
	// Start transmission right now - this function is called during the blit(), and the object clock is initialized
	//after the blit, starting now will reduce the number of skipped frames at the dektec startup
	res = ctx->dt_matrix->Start();
	if (res != DTAPI_OK) {
		GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] Can't start transmission: %s.\n", DtapiResult2Str(res)));
	}
	else {
		GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Transmission started.\n"));
		ctx->is_sending = GF_TRUE;
	}
	return GF_OK;
}

static GF_Err dtout_initialize(GF_Filter *filter)
{
	GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter);
	DTAPI_RESULT res;

	ctx->dt_device = new DtDevice;
	ctx->dt_matrix = new DtMxProcess;
	if (!ctx->dt_matrix || !ctx->dt_device) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] DTA API couldn't be initialized.\n"));
		if (ctx->dt_device) delete ctx->dt_device;
		if (ctx->dt_matrix) delete ctx->dt_matrix;
		ctx->dt_device = NULL;
		ctx->dt_matrix = NULL;
		return GF_IO_ERR;
	}

	ctx->audio_cbk.ctx = ctx;
	ctx->video_cbk.ctx = ctx;

	if ((ctx->bus >= 0) && (ctx->slot >= 0)) {
		res = ctx->dt_device->AttachToSlot(ctx->bus, ctx->slot);
		if (res != DTAPI_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] No DTA found on PIC bus %d slot %d: %s - trying enumerating cards\n", ctx->bus, ctx->slot, DtapiResult2Str(res)));
		}
		else {
			return GF_OK;
		}
	}

	res = ctx->dt_device->AttachToType(2174);
	if (res != DTAPI_OK) res = ctx->dt_device->AttachToType(2154);

	if (res != DTAPI_OK) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] No DTA 2174 or 2154 in system: %s\n", DtapiResult2Str(res)));
		return GF_BAD_PARAM;
	}
	return GF_OK;
}

static void dtout_finalize(GF_Filter *filter)
{
	GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter);

	if (ctx->is_sending) {
		dtout_disconnect(ctx);
	}
	delete(ctx->dt_matrix);
	delete(ctx->dt_device);
}

#endif //FAKE_DT_API

static GF_Err dtout_process(GF_Filter *filter)
{
	GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter);
	if (!ctx->is_configured && !ctx->is_sending) {
#ifndef FAKE_DT_API
		if (ctx->audio_cbk.audio) gf_filter_pid_get_packet(ctx->audio_cbk.audio);
		if (ctx->video_cbk.video) gf_filter_pid_get_packet(ctx->video_cbk.video);
#endif
	}
	if (ctx->is_eos)
		return GF_EOS;
	gf_filter_ask_rt_reschedule(filter, 200000);
	return GF_OK;
}

#endif //GPAC_HAS_DTAPI

#ifdef __cplusplus
extern "C" {
#endif

#include "dektec_video_decl.c"


#ifdef __cplusplus
}
#endif
