/*
 * Copyright (c) 2010-2019 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include "mediastreamer-config.h"
#endif

#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif

#include "ffmpeg-priv.h"

#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/msvideo.h"
#include "mediastreamer2/msticker.h"
#include "rfc2429.h"

#if LIBAVCODEC_VERSION_MAJOR >= 57

#ifdef _MSC_VER
#pragma warning(disable : 4996)
#else
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

#endif


extern void ms_ffmpeg_check_init(void);


typedef struct DecState{
	AVCodecContext av_context;
	AVCodec *av_codec;
	enum CodecID codec;
	mblk_t *input;
	YuvBuf outbuf;
	MSYuvBufAllocator *allocator;
	struct SwsContext *sws_ctx;
	enum AVPixelFormat output_pix_fmt;
	uint8_t dci[512];
	int dci_size;
	MSAverageFPS fps;
	AVFrame* orig;
	bool_t snow_initialized;
	bool_t first_image_decoded;
}DecState;


static void dec_init(MSFilter *f, enum CodecID cid){
	DecState *s=(DecState *)ms_new0(DecState,1);
	ms_ffmpeg_check_init();

	avcodec_get_context_defaults3(&s->av_context, NULL);
	s->allocator = ms_yuv_buf_allocator_new();
	s->av_codec=NULL;
	s->codec=cid;
	s->input=NULL;
	s->output_pix_fmt=AV_PIX_FMT_YUV420P;
	s->snow_initialized=FALSE;
	s->outbuf.w=0;
	s->outbuf.h=0;
	s->sws_ctx=NULL;
	f->data=s;

	s->av_codec=avcodec_find_decoder(s->codec);
	if (s->av_codec==NULL){
		ms_error("Could not find decoder %i!",s->codec);
	}
	s->orig = av_frame_alloc();
	if (!s->orig) {
		ms_error("Could not allocate frame");
	}

	/*
	s->av_context.width=MS_VIDEO_SIZE_QCIF_W;
	s->av_context.height=MS_VIDEO_SIZE_QCIF_H;
	*/
}

static void dec_h263_init(MSFilter *f){
	dec_init(f,CODEC_ID_H263);
}

static void dec_mpeg4_init(MSFilter *f){
	dec_init(f,CODEC_ID_MPEG4);
}

static void dec_mjpeg_init(MSFilter *f){
	dec_init(f,CODEC_ID_MJPEG);
}
static void dec_snow_init(MSFilter *f){
#if HAVE_ACVODEC_SNOW
	dec_init(f,CODEC_ID_SNOW);
#endif
}
static void dec_uninit(MSFilter *f){
	DecState *s=(DecState*)f->data;
	if (s->orig) {
		av_frame_free(&s->orig);
		s->orig = NULL;
	}
	if (s->av_context.codec!=NULL){
		avcodec_close(&s->av_context);
		s->av_context.codec=NULL;
	}
	ms_yuv_buf_allocator_free(s->allocator);
	if (s->input!=NULL) freemsg(s->input);
	if (s->sws_ctx!=NULL){
		sws_freeContext(s->sws_ctx);
		s->sws_ctx=NULL;
	}
	ms_free(s);
}

static int dec_add_fmtp(MSFilter *f, void *data){
	const char *fmtp=(const char*)data;
	DecState *s=(DecState*)f->data;
	char config[512];
	if (fmtp_get_value(fmtp,"config",config,sizeof(config))){
		/*convert hexa decimal config string into a bitstream */
		size_t i;
		int j;
		size_t max = strlen(config);
		char octet[3];
		octet[2]=0;
		for(i=0,j=0;i<max;i+=2,++j){
			octet[0]=config[i];
			octet[1]=config[i+1];
			s->dci[j]=(uint8_t)strtol(octet,NULL,16);
		}
		s->dci_size=j;
		ms_message("Got mpeg4 config string: %s",config);
	}
	return 0;
}

static void dec_preprocess(MSFilter *f){
	DecState *s=(DecState*)f->data;
	int error;

	s->first_image_decoded = FALSE;
	ms_average_fps_init(&s->fps, "Video decoder: FPS: %f");
	if (s->av_context.codec==NULL){
		/* we must know picture size before initializing snow decoder*/
#if HAVE_AVCODEC_SNOW
		if (s->codec!=CODEC_ID_SNOW){
#endif
			error=avcodec_open2(&s->av_context, s->av_codec, NULL);
			if (error!=0) ms_error("avcodec_open() failed: %i",error);
			if (s->codec==CODEC_ID_MPEG4 && s->dci_size>0){
				s->av_context.extradata=s->dci;
				s->av_context.extradata_size=s->dci_size;
			}
#if HAVE_AVCODEC_SNOW
		}
#endif

	}
}

static void dec_postprocess(MSFilter *f){
}

static mblk_t * skip_rfc2190_header(mblk_t *inm){
	uint8_t *ph = inm->b_rptr;
	uint8_t sbit = (ph[0] >> 3) & 0x07;
	//unsigned int ebit = ph[0] & 0x7;
	bool_t isIFrame=0;
	unsigned hdrLen;
	char mode;
	if (msgdsize(inm) < 5 ) {
		ms_warning("RFC2190 packet too small (size %d) to scan!", (int)msgdsize(inm));
		freemsg(inm);
		return NULL;
	}
	if ((ph[0] & 0x80) == 0) {
		isIFrame = (ph[1] & 0x10) == 0;
		hdrLen = 4;
		mode = 'A';
	} else if ((ph[0] & 0x40) == 0) {
		isIFrame = (ph[4] & 0x80) == 0;
		hdrLen = 8;
		mode = 'B';
	} else {
		isIFrame = (ph[4] & 0x80) == 0;
		hdrLen = 12;
		mode = 'C';
	}
	if (msgdsize(inm) > hdrLen) {
		inm->reserved2 |= (sbit << 11);
		inm->b_rptr += hdrLen;
	} else {
		ms_warning("RFC2190 packet mode:%c%s too small (size %d)", mode, isIFrame ?
				" (I-Frame)":"", (int)msgdsize(inm));
		freemsg(inm);
		inm=NULL;
	}
	return inm;
}

static mblk_t * skip_rfc2429_header(mblk_t *inm){
	if (msgdsize(inm) >= 2){
		/*uint32_t *p = (uint32_t*)inm->b_rptr;*/
		uint8_t *ph=inm->b_rptr;
		int PLEN;
		/*int gob_num;*/
		bool_t P;

		P=rfc2429_get_P(ph);
		PLEN=rfc2429_get_PLEN(ph);
		/*printf("receiving new packet; P=%i; V=%i; PLEN=%i; PEBIT=%i\n",P,rfc2429_get_V(ph),PLEN,rfc2429_get_PEBIT(ph));
		*/
		/*gob_num = (ntohl(*p) >> 10) & 0x1f;*/
		/*ms_message("gob %i, size %i", gob_num, msgdsize(inm));
		ms_message("ms_AVdecoder_process: received %08x %08x", ntohl(p[0]), ntohl(p[1]));*/

		/* remove H.263 Payload Header */
		if (PLEN>0){
			/* we ignore the redundant picture header and
			directly go to the bitstream */
			inm->b_rptr+=PLEN;
		}
		if (P){
			inm->b_rptr[0]=inm->b_rptr[1]=0;
		}else{
			/* no PSC omitted */
			inm->b_rptr+=2;
		}
		return inm;
	}else freemsg(inm);
	return NULL;
}
#if HAVE_AVCODEC_SNOW
static mblk_t * parse_snow_header(DecState *s,mblk_t *inm){
	if (msgdsize(inm) >= 4){
		uint32_t h = ntohl(*(uint32_t*)inm->b_rptr);
		if (!s->snow_initialized){
			int error;
			s->av_context.width=h>>16;
			s->av_context.height=h&0xffff;
			error=avcodec_open2(&s->av_context, s->av_codec, NULL);
			if (error!=0) ms_error("avcodec_open() failed for snow: %i",error);
			else {
				s->snow_initialized=TRUE;
				ms_message("Snow decoder initialized,size=%ix%i",
				s->av_context.width,
				s->av_context.height);
			}
		}
		inm->b_rptr+=4;
		return inm;
	}else {
		freemsg(inm);
		return NULL;
	}
}
#endif
struct jpeghdr {
	//unsigned int tspec:8;   /* type-specific field */
	unsigned int off:32;    /* fragment byte offset */
	uint8_t type;            /* id of jpeg decoder params */
	uint8_t q;               /* quantization factor (or table id) */
	uint8_t width;           /* frame width in 8 pixel blocks */
	uint8_t height;          /* frame height in 8 pixel blocks */
};

struct jpeghdr_rst {
	uint16_t dri;
	unsigned int f:1;
	unsigned int l:1;
	unsigned int count:14;
};

struct jpeghdr_qtable {
	uint8_t  mbz;
	uint8_t  precision;
	uint16_t length;
};


static u_char lum_dc_codelens[] = {
        0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
};

static u_char lum_dc_symbols[] = {
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
};

static u_char lum_ac_codelens[] = {
        0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d,
};

static u_char lum_ac_symbols[] = {
        0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
        0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
        0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
        0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
        0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
        0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
        0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
        0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
        0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
        0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
        0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
        0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
        0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
        0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
        0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
        0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
        0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
        0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
        0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
        0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
        0xf9, 0xfa,
};

static u_char chm_dc_codelens[] = {
        0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
};

static u_char chm_dc_symbols[] = {
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
};

static u_char chm_ac_codelens[] = {
        0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77,
};

static u_char chm_ac_symbols[] = {
        0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
        0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
        0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
        0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
        0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
        0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
        0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
        0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
        0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
        0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
        0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
        0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
        0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
        0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
        0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
        0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
        0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
        0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
        0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
        0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
        0xf9, 0xfa,
};

static u_char *
MakeQuantHeader(u_char *p, u_char *qt, int tableNo, int table_len)
{
        *p++ = 0xff;
        *p++ = 0xdb;            /* DQT */
        *p++ = 0;               /* length msb */
        *p++ = table_len+3;              /* length lsb */
        *p++ = tableNo;
        memcpy(p, qt, table_len);
        return (p + table_len);
}

static u_char *
MakeHuffmanHeader(u_char *p, u_char *codelens, int ncodes,
                  u_char *symbols, int nsymbols, int tableNo,
                  int tableClass)
{
        *p++ = 0xff;
        *p++ = 0xc4;            /* DHT */
        *p++ = 0;               /* length msb */
        *p++ = 3 + ncodes + nsymbols; /* length lsb */
        *p++ = (tableClass << 4) | tableNo;
        memcpy(p, codelens, ncodes);
        p += ncodes;
        memcpy(p, symbols, nsymbols);
        p += nsymbols;
        return (p);
}

static u_char *
MakeDRIHeader(u_char *p, u_short dri) {
        *p++ = 0xff;
        *p++ = 0xdd;            /* DRI */
        *p++ = 0x0;             /* length msb */
        *p++ = 4;               /* length lsb */
        *p++ = dri >> 8;        /* dri msb */
        *p++ = dri & 0xff;      /* dri lsb */
        return (p);
}

/*
 *  Arguments:
 *    type, width, height: as supplied in RTP/JPEG header
 *    lqt, cqt: quantization tables as either derived from
 *         the Q field using MakeTables() or as specified
 *         in section 4.2.
 *    dri: restart interval in MCUs, or 0 if no restarts.
 *
 *    p: pointer to return area
 *
 *  Return value:
 *    The length of the generated headers.
 *
 *    Generate a frame and scan headers that can be prepended to the
 *    RTP/JPEG data payload to produce a JPEG compressed image in
 *    interchange format (except for possible trailing garbage and
 *    absence of an EOI marker to terminate the scan).
 */
static int MakeHeaders(u_char *p, int type, int w, int h, u_char *lqt,
                u_char *cqt, unsigned table_len, u_short dri)
{
        u_char *start = p;

        /* convert from blocks to pixels */
        w <<= 3;
        h <<= 3;

        *p++ = 0xff;
        *p++ = 0xd8;            /* SOI */

		if (table_len>64)
		{
			p = MakeQuantHeader(p, lqt, 0, table_len/2);
	        p = MakeQuantHeader(p, cqt, 1, table_len/2);
		}
		else
		{
			p = MakeQuantHeader(p, lqt, 0, table_len);
			//p = MakeQuantHeader(p, lqt, 1, table_len);
		}
        if (dri != 0)
                p = MakeDRIHeader(p, dri);

        *p++ = 0xff;
        *p++ = 0xc0;            /* SOF */
        *p++ = 0;               /* length msb */
        *p++ = 17;              /* length lsb */
        *p++ = 8;               /* 8-bit precision */
        *p++ = h >> 8;          /* height msb */
        *p++ = h;               /* height lsb */
        *p++ = w >> 8;          /* width msb */
        *p++ = w;               /* wudth lsb */
        *p++ = 3;               /* number of components */
        *p++ = 0;               /* comp 0 */
        if (type == 0)
                *p++ = 0x21;    /* hsamp = 2, vsamp = 1 */
        else
                *p++ = 0x22;    /* hsamp = 2, vsamp = 2 */
        *p++ = 0;               /* quant table 0 */
        *p++ = 1;               /* comp 1 */
        *p++ = 0x11;            /* hsamp = 1, vsamp = 1 */
        *p++ = table_len <= 64 ? 0x00 : 0x01; //1   /* quant table 1 */
        *p++ = 2;               /* comp 2 */
        *p++ = 0x11;            /* hsamp = 1, vsamp = 1 */
        *p++ = table_len <= 64 ? 0x00 : 0x01; //1;  /* quant table 1 */
        p = MakeHuffmanHeader(p, lum_dc_codelens,
                              sizeof(lum_dc_codelens),
                              lum_dc_symbols,
                              sizeof(lum_dc_symbols), 0, 0);
        p = MakeHuffmanHeader(p, lum_ac_codelens,
                              sizeof(lum_ac_codelens),
                              lum_ac_symbols,
                              sizeof(lum_ac_symbols), 0, 1);
        p = MakeHuffmanHeader(p, chm_dc_codelens,
                              sizeof(chm_dc_codelens),
                              chm_dc_symbols,
                              sizeof(chm_dc_symbols), 1, 0);
        p = MakeHuffmanHeader(p, chm_ac_codelens,
                              sizeof(chm_ac_codelens),
                              chm_ac_symbols,
                              sizeof(chm_ac_symbols), 1, 1);

        *p++ = 0xff;
        *p++ = 0xda;            /* SOS */
        *p++ = 0;               /* length msb */
        *p++ = 12;              /* length lsb */
        *p++ = 3;               /* 3 components */
        *p++ = 0;               /* comp 0 */
        *p++ = 0;               /* huffman table 0 */
        *p++ = 1;               /* comp 1 */
        *p++ = 0x11;            /* huffman table 1 */
        *p++ = 2;               /* comp 2 */
        *p++ = 0x11;            /* huffman table 1 */
        *p++ = 0;               /* first DCT coeff */
        *p++ = 63;              /* last DCT coeff */
        *p++ = 0;               /* sucessive approx. */

        return (p - start);
};


/*
 * Table K.1 from JPEG spec.
 */
static const int jpeg_luma_quantizer[64] = {
	16, 11, 10, 16, 24, 40, 51, 61,
	12, 12, 14, 19, 26, 58, 60, 55,
	14, 13, 16, 24, 40, 57, 69, 56,
	14, 17, 22, 29, 51, 87, 80, 62,
	18, 22, 37, 56, 68, 109, 103, 77,
	24, 35, 55, 64, 81, 104, 113, 92,
	49, 64, 78, 87, 103, 121, 120, 101,
	72, 92, 95, 98, 112, 100, 103, 99
};

/*
 * Table K.2 from JPEG spec.
 */
static const int jpeg_chroma_quantizer[64] = {
	17, 18, 24, 47, 99, 99, 99, 99,
	18, 21, 26, 66, 99, 99, 99, 99,
	24, 26, 56, 99, 99, 99, 99, 99,
	47, 66, 99, 99, 99, 99, 99, 99,
	99, 99, 99, 99, 99, 99, 99, 99,
	99, 99, 99, 99, 99, 99, 99, 99,
	99, 99, 99, 99, 99, 99, 99, 99,
	99, 99, 99, 99, 99, 99, 99, 99
};

/*
 * Call MakeTables with the Q factor and two u_char[64] return arrays
 */
static void MakeTables(int q, u_char *lqt, u_char *cqt)
{
	int i;
	int factor = q;

	if (q < 1) factor = 1;
	if (q > 99) factor = 99;
	if (q < 50)
		q = 5000 / factor;
	else
		q = 200 - factor*2;

	for (i=0; i < 64; i++) {
		int lq = (jpeg_luma_quantizer[i] * q + 50) / 100;
		int cq = (jpeg_chroma_quantizer[i] * q + 50) / 100;

		/* Limit the quantizers to 1 <= q <= 255 */
		if (lq < 1) lq = 1;
		else if (lq > 255) lq = 255;
		lqt[i] = lq;

		if (cq < 1) cq = 1;
		else if (cq > 255) cq = 255;
		cqt[i] = cq;
	}
}

static mblk_t *
read_rfc2435_header(DecState *s,mblk_t *inm)
{
	if (msgdsize(inm) >= sizeof(struct jpeghdr)) {
		struct jpeghdr *hdr = (struct jpeghdr *)inm->b_rptr;
		uint32_t off = ntohl(*(uint32_t*)inm->b_rptr);
		uint16_t dri=0;
		uint16_t table_len=0;
		int len=0;

		mblk_t *headers=NULL;

		inm->b_rptr += sizeof(struct jpeghdr);
		if (hdr->type>63){
			struct jpeghdr_rst *rsthdr = (struct jpeghdr_rst *)inm->b_rptr;
			dri = ntohs(rsthdr->dri);
			inm->b_rptr += sizeof(struct jpeghdr_rst);
		}

		if (off==0){
			if (hdr->q>=128){
				inm->b_rptr++; /* MBZ */
				inm->b_rptr++; /* Precision */
				table_len = ntohs(*((uint16_t*)(inm->b_rptr)));
				inm->b_rptr++; /* len */
				inm->b_rptr++; /* len */
				headers = allocb(495 + table_len*2 + (dri > 0 ? 6 : 0), 0);
				len = MakeHeaders(headers->b_rptr, hdr->type, hdr->width, hdr->height,
					inm->b_rptr, inm->b_rptr+table_len/2, table_len, dri);
				inm->b_rptr += table_len;
				headers->b_wptr += len;
			}else{
				uint8_t lqt_cqt[128];
				MakeTables(hdr->q, lqt_cqt, lqt_cqt+64);
				table_len=128;
				headers = allocb(495 + table_len + (dri > 0 ? 6 : 0), 0);
				len = MakeHeaders(headers->b_rptr, hdr->type, hdr->width, hdr->height,
					lqt_cqt, lqt_cqt+64, table_len, dri);
				headers->b_wptr += len;
			}
		}

		if (headers!=NULL)
		{
			/* prepend headers to JPEG RTP data */
			if (mblk_get_marker_info(inm))
				mblk_set_marker_info(headers, TRUE);
			headers->b_cont=inm;
			msgpullup(headers, -1);
			return headers;
		}
	} else {
		freemsg(inm);
		inm=NULL;
	}
	return inm;
}

static mblk_t *get_as_yuvmsg(MSFilter *f, DecState *s, AVFrame *orig){
	AVCodecContext *ctx=&s->av_context;
	mblk_t *yuv_msg;

	if (ctx->width==0 || ctx->height==0){
		ms_error("%s: wrong image size provided by decoder.",f->desc->name);
		return NULL;
	}
	if (orig->data[0]==NULL){
		ms_error("%s: no image data.",f->desc->name);
		return NULL;
	}
	if (s->outbuf.w!=ctx->width || s->outbuf.h!=ctx->height){
		if (s->sws_ctx!=NULL){
			sws_freeContext(s->sws_ctx);
			s->sws_ctx=NULL;
		}
		s->sws_ctx=sws_getContext(ctx->width,ctx->height,ctx->pix_fmt,
			ctx->width,ctx->height,s->output_pix_fmt,SWS_FAST_BILINEAR,
                	NULL, NULL, NULL);
	}
	if (s->sws_ctx==NULL){
		ms_error("%s: missing rescaling context.",f->desc->name);
		return NULL;
	}
	yuv_msg=ms_yuv_buf_allocator_get(s->allocator,&s->outbuf,ctx->width,ctx->height);
#if LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(0,9,0)
	if (sws_scale(s->sws_ctx,(const uint8_t* const*)orig->data,orig->linesize, 0,
					ctx->height, s->outbuf.planes, s->outbuf.strides)<0){
#else
	if (sws_scale(s->sws_ctx,(uint8_t**)orig->data,orig->linesize, 0,
					ctx->height, s->outbuf.planes, s->outbuf.strides)<0){
#endif
		ms_error("%s: error in ms_sws_scale().",f->desc->name);
	}
	mblk_set_timestamp_info(yuv_msg, (uint32_t)orig->pkt_pts);
	return yuv_msg;
}
/* Bitmasks to select bits of a byte from low side */
static unsigned char smasks[7] = { 0x7f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };

static void dec_process_frame(MSFilter *f, mblk_t *inm){
	DecState *s=(DecState*)f->data;

	int got_picture;
	/* get a picture from the input queue */

	if (f->desc->id==MS_H263_DEC_ID) inm=skip_rfc2429_header(inm);
	else if (f->desc->id==MS_H263_OLD_DEC_ID) inm=skip_rfc2190_header(inm);
#if HAVE_AVCODEC_SNOW
	else if (s->codec==CODEC_ID_SNOW && s->input==NULL) inm=parse_snow_header(s,inm);
#endif
	else if (s->codec==CODEC_ID_MJPEG && f->desc->id==MS_JPEG_DEC_ID) inm=read_rfc2435_header(s,inm);

	if (inm){
		/* accumulate the video packet until we have the rtp markbit*/
		if (s->input==NULL){
			s->input=inm;
		}else{
			uint8_t sbit = (inm->reserved2 >> 11) & 0x7;
			if (sbit!=0) {
				mblk_t *mp = s->input;
				while ( mp->b_cont != NULL ) mp = mp->b_cont;
				mp->b_wptr--;
				mp->b_wptr[0] |= ( inm->b_rptr[0] & smasks[sbit-1] );
				mp->b_wptr++;
				inm->b_rptr++;
			}
			concatb(s->input,inm);
		}

		if (mblk_get_marker_info(inm)){
			mblk_t *frame;
			int remain,len;
			/*ms_message("got marker bit !");*/
			/*append some padding bytes for ffmpeg to safely
			read extra bytes...*/
			msgpullup(s->input,msgdsize(s->input)+8);
			frame=s->input;
			s->input=NULL;
			uint32_t frame_ts = mblk_get_timestamp_info(frame);
			while ( (remain=frame->b_wptr-frame->b_rptr)> 0) {
				AVPacket pkt;
				
				av_init_packet(&pkt);
				pkt.data = frame->b_rptr;
				pkt.size = remain;
				pkt.pts = frame_ts;
				len=avcodec_decode_video2(&s->av_context, s->orig, &got_picture,&pkt);

				if (len<=0) {
					ms_warning("ms_AVdecoder_process: error %i.",len);
					ms_filter_notify_no_arg(f,MS_VIDEO_DECODER_DECODING_ERRORS);
					break;
				}
				if (got_picture) {
					mblk_t *om = get_as_yuvmsg(f,s,s->orig);
					ms_average_fps_update(&s->fps, f->ticker->time);
					if (om!=NULL)
						ms_queue_put(f->outputs[0],om);

					if (!s->first_image_decoded) {
						s->first_image_decoded = TRUE;
						ms_filter_notify_no_arg(f,MS_VIDEO_DECODER_FIRST_IMAGE_DECODED);
					}
					if (s->codec==CODEC_ID_MJPEG)
						break;/*assume one frame per packet for mjpeg, to workaround errors while decoding mjpeg from cameras*/
				}
				frame->b_rptr+=len;
			}
			freemsg(frame);
		}
	}
}

static void dec_process(MSFilter *f){
	mblk_t *inm;
	while((inm=ms_queue_get(f->inputs[0]))!=0){
		dec_process_frame(f,inm);
	}
}

static int reset_first_image(MSFilter* f, void *data) {
	DecState *s=(DecState*)f->data;
	s->first_image_decoded = FALSE;
	return 0;
}

static int dec_get_vsize(MSFilter *f, void *data) {
	DecState *s = (DecState *)f->data;
	MSVideoSize *vsize = (MSVideoSize *)data;
	if (s->first_image_decoded == TRUE) {
		vsize->width = s->outbuf.w;
		vsize->height = s->outbuf.h;
	} else {
		vsize->width = MS_VIDEO_SIZE_UNKNOWN_W;
		vsize->height = MS_VIDEO_SIZE_UNKNOWN_H;
	}
	return 0;
}

static int dec_get_fps(MSFilter *f, void *data){
	DecState *s = (DecState *)f->data;
	*(float*)data=ms_average_fps_get(&s->fps);
	return 0;
}


static MSFilterMethod methods[]={
	{	MS_FILTER_ADD_FMTP		,	dec_add_fmtp	},
	{	MS_VIDEO_DECODER_RESET_FIRST_IMAGE_NOTIFICATION, reset_first_image },
	{	MS_FILTER_GET_VIDEO_SIZE,	dec_get_vsize	},
	{	MS_FILTER_GET_FPS,		dec_get_fps	},
	{	0		,		NULL			}
};

#ifdef _MSC_VER

MSFilterDesc ms_h263_dec_desc={
	MS_H263_DEC_ID,
	"MSH263Dec",
	N_("A H.263 decoder using ffmpeg library"),
	MS_FILTER_DECODER,
	"H263-1998",
	1,
	1,
	dec_h263_init,
	dec_preprocess,
	dec_process,
	dec_postprocess,
	dec_uninit,
	methods
};

MSFilterDesc ms_h263_old_dec_desc={
	MS_H263_OLD_DEC_ID,
	"MSH263OldDec",
	N_("A H.263 decoder using ffmpeg library"),
	MS_FILTER_DECODER,
	"H263",
	1,
	1,
	dec_h263_init,
	dec_preprocess,
	dec_process,
	dec_postprocess,
	dec_uninit,
	methods
};


MSFilterDesc ms_mpeg4_dec_desc={
	MS_MPEG4_DEC_ID,
	"MSMpeg4Dec",
	N_("A MPEG4 decoder using ffmpeg library"),
	MS_FILTER_DECODER,
	"MP4V-ES",
	1,
	1,
	dec_mpeg4_init,
	dec_preprocess,
	dec_process,
	dec_postprocess,
	dec_uninit,
	methods
};

MSFilterDesc ms_jpeg_dec_desc={
	MS_JPEG_DEC_ID,
	"MSJpegDec",
	N_("A RTP/JPEG decoder using ffmpeg library"),
	MS_FILTER_DECODER,
	"JPEG",
	1,
	1,
	dec_mjpeg_init,
	dec_preprocess,
	dec_process,
	dec_postprocess,
	dec_uninit,
	methods
};

MSFilterDesc ms_mjpeg_dec_desc={
	MS_MJPEG_DEC_ID,
	"MSMJpegDec",
	N_("A MJPEG decoder using ffmpeg library"),
	MS_FILTER_DECODER,
	"MJPEG",
	1,
	1,
	dec_mjpeg_init,
	dec_preprocess,
	dec_process,
	dec_postprocess,
	dec_uninit,
	methods
};
MSFilterDesc ms_snow_dec_desc={
	MS_SNOW_DEC_ID,
	"MSSnowDec",
	N_("A snow decoder using ffmpeg library"),
	MS_FILTER_DECODER,
	"snow",
	1,
	1,
	dec_snow_init,
	dec_preprocess,
	dec_process,
	dec_postprocess,
	dec_uninit,
	methods
};
#else

MSFilterDesc ms_h263_dec_desc={
	.id=MS_H263_DEC_ID,
	.name="MSH263Dec",
	.text=N_("A H.263 decoder using ffmpeg library"),
	.category=MS_FILTER_DECODER,
	.enc_fmt="H263-1998",
	.ninputs=1,
	.noutputs=1,
	.init=dec_h263_init,
	.preprocess=dec_preprocess,
	.process=dec_process,
	.postprocess=dec_postprocess,
	.uninit=dec_uninit,
	.methods= methods
};

MSFilterDesc ms_h263_old_dec_desc={
	.id=MS_H263_OLD_DEC_ID,
	.name="MSH263OldDec",
	.text=N_("A H.263 decoder using ffmpeg library"),
	.category=MS_FILTER_DECODER,
	.enc_fmt="H263",
	.ninputs=1,
	.noutputs=1,
	.init=dec_h263_init,
	.preprocess=dec_preprocess,
	.process=dec_process,
	.postprocess=dec_postprocess,
	.uninit=dec_uninit,
	.methods= methods
};


MSFilterDesc ms_mpeg4_dec_desc={
	.id=MS_MPEG4_DEC_ID,
	.name="MSMpeg4Dec",
	.text="A MPEG4 decoder using ffmpeg library",
	.category=MS_FILTER_DECODER,
	.enc_fmt="MP4V-ES",
	.ninputs=1,
	.noutputs=1,
	.init=dec_mpeg4_init,
	.preprocess=dec_preprocess,
	.process=dec_process,
	.postprocess=dec_postprocess,
	.uninit=dec_uninit,
	.methods= methods
};

MSFilterDesc ms_jpeg_dec_desc={
	.id=MS_JPEG_DEC_ID,
	.name="MSJpegDec",
	.text="A RTP/MJEPG decoder using ffmpeg library",
	.category=MS_FILTER_DECODER,
	.enc_fmt="JPEG",
	.ninputs=1,
	.noutputs=1,
	.init=dec_mjpeg_init,
	.preprocess=dec_preprocess,
	.process=dec_process,
	.postprocess=dec_postprocess,
	.uninit=dec_uninit,
	.methods= methods
};

MSFilterDesc ms_mjpeg_dec_desc={
	.id=MS_MJPEG_DEC_ID,
	.name="MSMJpegDec",
	.text="A MJEPG decoder using ffmpeg library",
	.category=MS_FILTER_DECODER,
	.enc_fmt="MJPEG",
	.ninputs=1,
	.noutputs=1,
	.init=dec_mjpeg_init,
	.preprocess=dec_preprocess,
	.process=dec_process,
	.postprocess=dec_postprocess,
	.uninit=dec_uninit,
	.methods= methods
};
MSFilterDesc ms_snow_dec_desc={
	.id=MS_SNOW_DEC_ID,
	.name="MSSnowDec",
	.text="A snow decoder using ffmpeg library",
	.category=MS_FILTER_DECODER,
	.enc_fmt="x-snow",
	.ninputs=1,
	.noutputs=1,
	.init=dec_snow_init,
	.preprocess=dec_preprocess,
	.process=dec_process,
	.postprocess=dec_postprocess,
	.uninit=dec_uninit,
	.methods= methods
};
#endif

MS_FILTER_DESC_EXPORT(ms_mpeg4_dec_desc)
MS_FILTER_DESC_EXPORT(ms_h263_dec_desc)
MS_FILTER_DESC_EXPORT(ms_h263_old_dec_desc)
MS_FILTER_DESC_EXPORT(ms_snow_dec_desc)

/* decode JPEG image with RTP/jpeg headers */
MS_FILTER_DESC_EXPORT(ms_jpeg_dec_desc)
/* decode JPEG image with jpeg headers */
MS_FILTER_DESC_EXPORT(ms_mjpeg_dec_desc)

#if __clang__
#pragma clang diagnostic pop
#endif
