/*
* Copyright (C) 2010-2011 Mamadou Diop.
*
* Contact: Mamadou Diop <diopmamadou(at)doubango.org>
*	
* This file is part of Open Source Doubango Framework.
*
* DOUBANGO 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.
*	
* DOUBANGO 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 DOUBANGO.
*
*/

/**@file tdav_codec_theora.c
 * @brief Theora codec plugin
 * RTP payloader/depayloader follows draft-barbato-avt-rtp-theora-01.
 * For more information about Theora, http://www.theora.org/doc/Theora.pdf.
 * @author Mamadou Diop <diopmamadou(at)doubango.org>
 *

 */
#include "tinydav/codecs/theora/tdav_codec_theora.h"

#if HAVE_FFMPEG && (!defined(HAVE_THEORA) || HAVE_THEORA)

#include "tinydav/video/tdav_converter_video.h"

#include "tinyrtp/rtp/trtp_rtp_packet.h"

#include "tinymedia/tmedia_params.h"

#include "tsk_string.h"
#include "tsk_buffer.h"
#include "tsk_time.h"
#include "tsk_params.h"
#include "tsk_memory.h"
#include "tsk_debug.h"

#include <libavcodec/avcodec.h>

#define THEORA_RTP_PAYLOAD_SIZE		900
#define THEORA_GOP_SIZE_IN_SECONDS	25
#define THEORA_PAYLOAD_HEADER_SIZE	4 /* 2.2. Payload Header */
#define THEORA_PAYLOAD_LENGTH_SIZE	2 /* 2.2. Payload Header */
#define THEORA_IDENT_HEADER_SIZE	42 /* 6.2 Identification Header Decode */
#define THEORA_CONF_SEND_COUNT		10 /* at 250ms, 500ms, 1000ms, ....  */

typedef struct tdav_codec_theora_s
{
	TMEDIA_DECLARE_CODEC_VIDEO;

	struct{
		uint8_t* ptr;
		tsk_size_t size;
	} rtp;

	// Encoder
	struct{
		AVCodec* codec;
		AVCodecContext* context;
		AVFrame* picture;
		void* buffer;

		uint64_t conf_last;
		int conf_count;
		tsk_bool_t force_idr;
		int quality;
		int rotation;
	} encoder;
	
	// decoder
	struct{
		AVCodec* codec;
		AVCodecContext* context;
		AVFrame* picture;

		tsk_bool_t opened;
		uint8_t conf_ident[3];
		tsk_buffer_t* conf_pkt;
		
		void* accumulator;
		uint8_t ebit;
		tsk_size_t accumulator_pos;
		uint16_t last_seq;
	} decoder;
}
tdav_codec_theora_t;


/* 2.2. Payload Header filed 'F'*/
typedef enum theora_frag_type_e{
	Not_Fragmented = 0,
	Start_Fragment = 1,
	Continuation_Fragment = 2,
	End_Fragment = 3,
}
theora_frag_type_t;

/* 2.2. Payload Header field 'TDT'*/
typedef enum theora_datatype_e{
	Raw_Theora_payload = 0,
	Theora_Packed_Configuration_payload = 1,
	Legacy_Theora_Comment_payload = 2,
	Reserved = 3,
}
theora_datatype_t;

static int tdav_codec_theora_open_encoder(tdav_codec_theora_t* self);
static int tdav_codec_theora_open_decoder(tdav_codec_theora_t* self);
static int tdav_codec_theora_close_encoder(tdav_codec_theora_t* self);
static int tdav_codec_theora_close_decoder(tdav_codec_theora_t* self);

static int tdav_codec_theora_send(tdav_codec_theora_t* self, const uint8_t* data, tsk_size_t size, theora_datatype_t tdt);
static void tdav_codec_theora_rtp_callback(tdav_codec_theora_t *self, const void *data, tsk_size_t size, tsk_bool_t marker);

static void tdav_codec_theora_encap(tdav_codec_theora_t* theora, const uint8_t* pdata, tsk_size_t size);

/* ============ Theora Plugin interface functions ================= */

static int tdav_codec_theora_set(tmedia_codec_t* self, const tmedia_param_t* param)
{
	tdav_codec_theora_t* theora = (tdav_codec_theora_t*)self;
	if(!self->opened){
		TSK_DEBUG_ERROR("Codec not opened");
		return -1;
	}
	if(param->value_type == tmedia_pvt_int32){
		if(tsk_striequals(param->key, "action")){
			tmedia_codec_action_t action = (tmedia_codec_action_t)TSK_TO_INT32((uint8_t*)param->value);
			switch(action){
				case tmedia_codec_action_encode_idr:
					{
						theora->encoder.force_idr = tsk_true;
						break;
					}
				case tmedia_codec_action_bw_down:
					{
						theora->encoder.quality = TSK_CLAMP(1, (theora->encoder.quality + 1), 31);
						theora->encoder.context->global_quality = FF_QP2LAMBDA * theora->encoder.quality;
						break;
					}
				case tmedia_codec_action_bw_up:
					{
						theora->encoder.quality = TSK_CLAMP(1, (theora->encoder.quality - 1), 31);
						theora->encoder.context->global_quality = FF_QP2LAMBDA * theora->encoder.quality;
						break;
					}
			}
		}
		// FIXME: not working as expected
		/*else if(tsk_striequals(param->key, "rotation")){
			int rotation = *((int32_t*)param->value);
			if(theora->encoder.rotation != rotation){
				if(self->opened){
					int ret;
					theora->encoder.rotation = rotation;
					if((ret = tdav_codec_theora_close_encoder(theora))){
						return ret;
					}
					if((ret = tdav_codec_theora_open_encoder(theora))){
						return ret;
					}
				}
			}
			return 0;
		}*/
	}
	return -1;
}

int tdav_codec_theora_open(tmedia_codec_t* self)
{
	int ret;
	
	tdav_codec_theora_t* theora = (tdav_codec_theora_t*)self;

	if(!theora){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	/* the caller (base class) already checked that the codec is not opened */

	
	//	Encoder
	if((ret = tdav_codec_theora_open_encoder(theora))){
		return ret;
	}
	
	//	Decoder
	if((ret = tdav_codec_theora_open_decoder(theora))){
		return ret;
	}

	return 0;
}

int tdav_codec_theora_close(tmedia_codec_t* self)
{
	tdav_codec_theora_t* theora = (tdav_codec_theora_t*)self;

	if(!theora){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	/* the caller (base class) already checked that the codec is opened */

	
	//	Encoder
	tdav_codec_theora_close_encoder(theora);
	
	//	Decoder
	tdav_codec_theora_close_decoder(theora);

	return 0;
}

//#include "tsk_time.h"
tsk_size_t tdav_codec_theora_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size)
{
	int ret;
	int size;

	tdav_codec_theora_t* theora = (tdav_codec_theora_t*)self;

	if(!self || !in_data || !in_size || !out_data){
		TSK_DEBUG_ERROR("Invalid parameter");
		return 0;
	}

	// wrap yuv420 buffer
	size = avpicture_fill((AVPicture *)theora->encoder.picture, (uint8_t*)in_data, PIX_FMT_YUV420P, theora->encoder.context->width, theora->encoder.context->height);
	if(size != in_size){
		/* guard */
		TSK_DEBUG_ERROR("Invalid size");
		return 0;
	}
	
	// Encode data
#if LIBAVCODEC_VERSION_MAJOR <= 53
	theora->encoder.picture->pict_type = theora->encoder.force_idr ? FF_I_TYPE : 0;
#else
    theora->encoder.picture->pict_type = theora->encoder.force_idr ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE;
#endif
	theora->encoder.picture->pts = AV_NOPTS_VALUE;
	theora->encoder.picture->quality = theora->encoder.context->global_quality;
	ret = avcodec_encode_video(theora->encoder.context, theora->encoder.buffer, size, theora->encoder.picture);
	if(ret > 0){
		tdav_codec_theora_encap(theora, theora->encoder.buffer, (tsk_size_t)ret);
	}
	theora->encoder.force_idr = tsk_false;

	return 0;
}

tsk_size_t tdav_codec_theora_decode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size, const tsk_object_t* proto_hdr)
{
	const uint8_t* pdata = in_data;
	int pkts;
	const uint8_t* pay_ptr;
	tsk_size_t pay_size;
	//tsk_size_t hdr_size;
	tsk_size_t xsize, retsize = 0;
	int got_picture_ptr;
	int ret;

	tdav_codec_theora_t* theora = (tdav_codec_theora_t*)self;
	const trtp_rtp_header_t* rtp_hdr = proto_hdr;

	if(!self || !in_data || (in_size<(THEORA_PAYLOAD_HEADER_SIZE + THEORA_PAYLOAD_LENGTH_SIZE)) || !out_data || !theora->decoder.context){
		TSK_DEBUG_ERROR("Invalid parameter");
		return 0;
	}

	/* Packet lost? */
	if(theora->decoder.last_seq != (rtp_hdr->seq_num - 1) && theora->decoder.last_seq){
		if(theora->decoder.last_seq == rtp_hdr->seq_num){
			// Could happen on some stupid emulators
			//TSK_DEBUG_INFO("Packet duplicated, seq_num=%d", rtp_hdr->seq_num);
			return 0;
		}
		TSK_DEBUG_INFO("Packet lost, seq_num=%d", rtp_hdr->seq_num);
	}
	theora->decoder.last_seq = rtp_hdr->seq_num;

	xsize = avpicture_get_size(theora->decoder.context->pix_fmt, theora->decoder.context->width, theora->decoder.context->height);

	/* 2.2. Payload Header 
		0                   1                   2                   3
		0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
		+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		|               Configuration Ident             | F |TDT|# pkts.|
		+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	*/
	/*	2.3. Payload Data
		 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
		+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		|        Payload Length         |          Theora Data         ..
		+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	*/
	pkts = (pdata[3] & 0x0F);
	pay_ptr = (pdata + THEORA_PAYLOAD_HEADER_SIZE);

	do{ /* pkts=0 for fragmented packets */
		
		pay_size = pay_ptr[0], pay_size<<=8, pay_size |= pay_ptr[1]; /* Big Endian read */
		pay_ptr += THEORA_PAYLOAD_LENGTH_SIZE;
		/* check size validity */
		if((pay_ptr + pay_size)>(pdata + in_size)){
			TSK_DEBUG_ERROR("Too short");
			break;
		}

		switch((pdata[3]>>4) & 0x03){
			case Raw_Theora_payload:
				{	/* ====== Theora data (2.2. Payload Header, 2.3. Payload Data) ====== */
					/* append buffer */
					if((int)(theora->decoder.accumulator_pos + pay_size) <= xsize){
						memcpy(&((uint8_t*)theora->decoder.accumulator)[theora->decoder.accumulator_pos], pay_ptr, pay_size);
						theora->decoder.accumulator_pos += pay_size;
					}
					else{
						TSK_DEBUG_WARN("Buffer overflow");
						theora->decoder.accumulator_pos = 0;
						break;
					}
					/* only take care if last packet (What about the RTP marker?) */
					if(((pdata[3]>>6) == Not_Fragmented || (pdata[3]>>6) == End_Fragment /*|| rtp_hdr->marker*/) && theora->decoder.opened){
						AVPacket packet;
						/* Perform decoding */
						av_init_packet(&packet);
						packet.size = theora->decoder.accumulator_pos;
						packet.data = theora->decoder.accumulator;
						ret = avcodec_decode_video2(theora->decoder.context, theora->decoder.picture, &got_picture_ptr, &packet);
						
						if(ret < 0){
							TSK_DEBUG_WARN("Failed to decode the buffer with error code = %d", ret);
							if(TMEDIA_CODEC_VIDEO(self)->in.callback){
								TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_error;
								TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr;
								TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result);
							}
						}
						else if(got_picture_ptr){
							retsize = xsize;
							TMEDIA_CODEC_VIDEO(theora)->in.width = theora->decoder.context->width;
							TMEDIA_CODEC_VIDEO(theora)->in.height = theora->decoder.context->height;

							/* allocate buffer */
							if(*out_max_size <xsize){
								if((*out_data = tsk_realloc(*out_data, xsize))){
									*out_max_size = xsize;
								}
								else{
									TSK_DEBUG_ERROR("Failed to allocate new buffer");
									*out_max_size = 0;
									return 0;
								}
							}
							/* copy picture into a linear buffer */
							avpicture_layout((AVPicture *)theora->decoder.picture, theora->decoder.context->pix_fmt, theora->decoder.context->width, theora->decoder.context->height,
								*out_data, retsize);
						}
						/* in all cases: reset accumulator */
						theora->decoder.accumulator_pos = 0;
					}
					break;
				}
			case Theora_Packed_Configuration_payload:
				{/* ====== Configuration packet (3.1.1. Packed Configuration) ====== */
					static uint8_t __theora_comment_hdr[] = {0x81, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61,
						0x00, 0x00, 0x00, 0x08, /* 4-byte length */
						'd', 'o', 'u', 'b', 'a', 'n', 'g', 'o', /* UTF-8 encoded string */
					};

					/*	http://www.theora.org/doc/Theora.pdf - Chapter 6
					A Theora bitstream begins with three header packets. The header packets
					are, in order, the identifcation header, the comment header, and the setup
					header. All are required for decode compliance. An end-of-packet condition
					encountered while decoding the identification or setup header packets renders
					the stream undecodable. An end-of-packet condition encountered while decode
					the comment header is a non-fatal error condition, and MAY be ignored by a
					decoder.

					Decode continues according to HEADERTYPE. The identification header
					is type 0x80, the comment header is type 0x81, and the setup header is type
					0x82.
					*/
					/*TSK_DEBUG_INFO("Theora_Packed_Configuration_payload");*/

					if(!theora->decoder.opened /*|| (conf_ident changed)*/){
						if(!theora->decoder.conf_pkt){
							theora->decoder.conf_pkt = tsk_buffer_create(pay_ptr, pay_size);
						}
						else{
							tsk_buffer_append(theora->decoder.conf_pkt, pay_ptr, pay_size);
						}

						if((pdata[3]>>6) == Not_Fragmented || (pdata[3]>>6) == End_Fragment || rtp_hdr->marker){
							if(theora->decoder.conf_pkt->size > THEORA_IDENT_HEADER_SIZE){
								const uint8_t* conf_ptr = theora->decoder.conf_pkt->data;
								int setup_size = theora->decoder.conf_pkt->size - THEORA_IDENT_HEADER_SIZE;
								int extradata_size = (2 + THEORA_IDENT_HEADER_SIZE) + (2 + setup_size) + (2 + sizeof(__theora_comment_hdr));
								if(conf_ptr[0] == 0x80 && conf_ptr[THEORA_IDENT_HEADER_SIZE] == 0x82){ /* Do not check for 't'h'e'o'r'a' */
									/* save configration identification */
									memcpy(theora->decoder.conf_ident, &pdata[0], sizeof(theora->decoder.conf_ident));
									if(theora->decoder.context->extradata){
										TSK_FREE(theora->decoder.context->extradata);
									}
									if((theora->decoder.context->extradata = tsk_calloc(extradata_size + FF_INPUT_BUFFER_PADDING_SIZE, 1))){
										int index = 0;
										/* Because of endianess pb. do not use uint16_t or uint32_t */
										theora->decoder.context->extradata[index++] = 0x00;
										theora->decoder.context->extradata[index++] = THEORA_IDENT_HEADER_SIZE;
										memcpy(&theora->decoder.context->extradata[index], &conf_ptr[0], THEORA_IDENT_HEADER_SIZE);
										index += THEORA_IDENT_HEADER_SIZE;

										theora->decoder.context->extradata[index++] = (setup_size >>8) & 0xFF;
										theora->decoder.context->extradata[index++] = (setup_size & 0xFF);
										memcpy(&theora->decoder.context->extradata[index], &conf_ptr[THEORA_IDENT_HEADER_SIZE], setup_size);
										index+=setup_size;

										theora->decoder.context->extradata[index++] = 0x00;
										theora->decoder.context->extradata[index++] = sizeof(__theora_comment_hdr);/* <0xFF */
										memcpy(&theora->decoder.context->extradata[index], __theora_comment_hdr, sizeof(__theora_comment_hdr));

										theora->decoder.context->extradata_size = extradata_size;

										if((ret = avcodec_open(theora->decoder.context, theora->decoder.codec)) == 0){
											theora->decoder.opened = tsk_true;
										}
										else{
											TSK_DEBUG_ERROR("Failed to open theora decoder %d", ret);
											TSK_FREE(theora->decoder.context->extradata);
											theora->decoder.context->extradata_size = 0;
										}
									}
								}
								else{
									TSK_DEBUG_ERROR("Invalid configuration packet");
								}
							}
							else{
								TSK_DEBUG_ERROR("Too short");
							}
							tsk_buffer_cleanup(theora->decoder.conf_pkt);
						}
					}
					break;
				}
			case Legacy_Theora_Comment_payload:
				/*TSK_DEBUG_INFO("Legacy_Theora_Comment_payload");*/
				break;
			case Reserved:
				/*TSK_DEBUG_INFO("Reserved");*/
				break;
		}
	}
	while(--pkts>0);

	

	return retsize;
}

tsk_bool_t tdav_codec_theora_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value)
{
	tsk_bool_t ret = tsk_true; // accept decoding any size

	if(tsk_striequals(att_name, "fmtp")){
		tsk_params_L_t* params;
		if((params = tsk_params_fromstring(att_value, ";", tsk_true))){
			int pref_width, pref_height;
			int prop_width = tsk_params_get_param_value_as_int(params, "width");
			int prop_height = tsk_params_get_param_value_as_int(params, "height");		
			
			if(prop_width > 0 && prop_height > 0){
				if(tmedia_video_get_size(TMEDIA_CODEC_VIDEO(codec)->pref_size, (unsigned *)&pref_width, (unsigned *)&pref_height) != 0){
					TSK_OBJECT_SAFE_FREE(params);
					return tsk_false;
				}
				TMEDIA_CODEC_VIDEO(codec)->in.width = TMEDIA_CODEC_VIDEO(codec)->out.width = TSK_MIN(pref_width, prop_width);
				TMEDIA_CODEC_VIDEO(codec)->in.height = TMEDIA_CODEC_VIDEO(codec)->out.height = TSK_MIN(pref_height, prop_height);
			}
			TSK_OBJECT_SAFE_FREE(params);
		}
	}
	else if(tsk_striequals(att_name, "imageattr")){
		unsigned in_width, in_height, out_width, out_height;
		if(tmedia_parse_video_imageattr(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &in_width, &in_height, &out_width, &out_height) != 0){
			return tsk_false;
		}
		TMEDIA_CODEC_VIDEO(codec)->in.width = in_width;
		TMEDIA_CODEC_VIDEO(codec)->in.height = in_height;
		TMEDIA_CODEC_VIDEO(codec)->out.width = out_width;
		TMEDIA_CODEC_VIDEO(codec)->out.height = out_height;
	}

	return ret;
}

char* tdav_codec_theora_sdp_att_get(const tmedia_codec_t* codec, const char* att_name)
{
	if(tsk_striequals(att_name, "fmtp")){
		char* fmtp = tsk_null;
		tsk_sprintf(&fmtp, "sampling=YCbCr-4:2:0; width=%u; height=%u", TMEDIA_CODEC_VIDEO(codec)->out.width, TMEDIA_CODEC_VIDEO(codec)->out.height);
		return fmtp;
	}
	else if(tsk_striequals(att_name, "imageattr")){
		return tmedia_get_video_imageattr(TMEDIA_CODEC_VIDEO(codec)->pref_size, 
			TMEDIA_CODEC_VIDEO(codec)->in.width, TMEDIA_CODEC_VIDEO(codec)->in.height, TMEDIA_CODEC_VIDEO(codec)->out.width, TMEDIA_CODEC_VIDEO(codec)->out.height);
	}
	return tsk_null;
}



/* constructor */
static tsk_object_t* tdav_codec_theora_ctor(tsk_object_t * self, va_list * app)
{
	tdav_codec_theora_t *theora = self;
	if(theora){
		/* init base: called by tmedia_codec_create() */
		/* init self */
		theora->encoder.quality = 1;
	}
	return self;
}
/* destructor */
static tsk_object_t* tdav_codec_theora_dtor(tsk_object_t * self)
{ 
	tdav_codec_theora_t *theora = self;
	if(theora){
		/* deinit base */
		tmedia_codec_video_deinit(self);
		/* deinit self */
		TSK_OBJECT_SAFE_FREE(theora->decoder.conf_pkt);
		TSK_FREE(theora->rtp.ptr);
		theora->rtp.size = 0;
	}

	return self;
}
/* object definition */
static const tsk_object_def_t tdav_codec_theora_def_s = 
{
	sizeof(tdav_codec_theora_t),
	tdav_codec_theora_ctor, 
	tdav_codec_theora_dtor,
	tmedia_codec_cmp, 
};
/* plugin definition*/
static const tmedia_codec_plugin_def_t tdav_codec_theora_plugin_def_s = 
{
	&tdav_codec_theora_def_s,

	tmedia_video,
	tmedia_codec_id_theora,
	"theora",
	"Theora Codec",
	TMEDIA_CODEC_FORMAT_THEORA,
	tsk_true,
	90000, // rate
	
	/* audio */
	{ 0 },

	/* video */
	{176, 144, 15},

	tdav_codec_theora_set,
	tdav_codec_theora_open,
	tdav_codec_theora_close,
	tdav_codec_theora_encode,
	tdav_codec_theora_decode,
	tdav_codec_theora_sdp_att_match,
	tdav_codec_theora_sdp_att_get
};
const tmedia_codec_plugin_def_t *tdav_codec_theora_plugin_def_t = &tdav_codec_theora_plugin_def_s;



int tdav_codec_theora_open_encoder(tdav_codec_theora_t* self)
{
	int ret, size;
	if(!self->encoder.codec && !(self->encoder.codec = avcodec_find_encoder(CODEC_ID_THEORA))){
		TSK_DEBUG_ERROR("Failed to find Theora encoder");
		return -1;
	}
	if(self->encoder.context){
		TSK_DEBUG_ERROR("Encoder already initialized");
		return -1;
	}
	self->encoder.context = avcodec_alloc_context();
	avcodec_get_context_defaults(self->encoder.context);
	
	self->encoder.context->pix_fmt		= PIX_FMT_YUV420P;
	self->encoder.context->time_base.num  = 1;
	self->encoder.context->time_base.den  = TMEDIA_CODEC_VIDEO(self)->out.fps;
	self->encoder.context->width = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.height : TMEDIA_CODEC_VIDEO(self)->out.width;
	self->encoder.context->height = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.width : TMEDIA_CODEC_VIDEO(self)->out.height;
	self->encoder.context->mb_decision = FF_MB_DECISION_RD;
	
	// Theoraenc doesn't honor 'CODEC_FLAG_QSCALE'
	self->encoder.context->bit_rate = ((TMEDIA_CODEC_VIDEO(self)->out.width * TMEDIA_CODEC_VIDEO(self)->out.height * 256 / 320 / 240) * 1000);
#if LIBAVCODEC_VERSION_MAJOR <= 53
	self->encoder.context->rc_lookahead = 0;
#endif
	self->encoder.context->global_quality = FF_QP2LAMBDA * self->encoder.quality;

	self->encoder.context->thread_count = 0;
	self->encoder.context->rtp_payload_size = THEORA_RTP_PAYLOAD_SIZE;
	self->encoder.context->opaque = tsk_null;
	self->encoder.context->gop_size = (TMEDIA_CODEC_VIDEO(self)->out.fps * THEORA_GOP_SIZE_IN_SECONDS);

	// Picture (YUV 420)
	if(!(self->encoder.picture = avcodec_alloc_frame())){
		TSK_DEBUG_ERROR("Failed to create encoder picture");
		return -2;
	}
	avcodec_get_frame_defaults(self->encoder.picture);
	
	size = avpicture_get_size(PIX_FMT_YUV420P, self->encoder.context->width, self->encoder.context->height);
	if(!(self->encoder.buffer = tsk_calloc(size, sizeof(uint8_t)))){
		TSK_DEBUG_ERROR("Failed to allocate encoder buffer");
		return -2;
	}
	
	// Open encoder
	if((ret = avcodec_open(self->encoder.context, self->encoder.codec)) < 0){
		TSK_DEBUG_ERROR("Failed to open Theora encoder");
		return ret;
	}

	self->encoder.conf_last = 0;
	self->encoder.conf_count = 0;

	return ret;
}

int tdav_codec_theora_open_decoder(tdav_codec_theora_t* self)
{
	int size;
	if(!self->decoder.codec && !(self->decoder.codec = avcodec_find_decoder(CODEC_ID_THEORA))){
		TSK_DEBUG_ERROR("Failed to find Theora decoder");
		return -1;
	}
	if(self->decoder.context){
		TSK_DEBUG_ERROR("Decoder already opened");
		return -1;
	}
	self->decoder.context = avcodec_alloc_context();
	avcodec_get_context_defaults(self->decoder.context);
	
	self->decoder.context->pix_fmt = PIX_FMT_YUV420P;
	self->decoder.context->width = TMEDIA_CODEC_VIDEO(self)->in.width;
	self->decoder.context->height = TMEDIA_CODEC_VIDEO(self)->in.height;

	// Picture (YUV 420)
	if(!(self->decoder.picture = avcodec_alloc_frame())){
		TSK_DEBUG_ERROR("Failed to create decoder picture");
		return -2;
	}
	avcodec_get_frame_defaults(self->decoder.picture);

	size = avpicture_get_size(PIX_FMT_YUV420P, self->decoder.context->width, self->decoder.context->height);
	if(!(self->decoder.accumulator = tsk_calloc((size + FF_INPUT_BUFFER_PADDING_SIZE), sizeof(uint8_t)))){
		TSK_DEBUG_ERROR("Failed to allocate decoder buffer");
		return -2;
	}

	if(!(self->decoder.accumulator = tsk_calloc((size + FF_INPUT_BUFFER_PADDING_SIZE), sizeof(uint8_t)))){
		TSK_DEBUG_ERROR("Failed to allocate decoder buffer");
		return -2;
	}

	// Open decoder
	//if((ret = avcodec_open(self->decoder.context, self->decoder.codec)) < 0){
	//	TSK_DEBUG_ERROR("Failed to open Theora decoder");
	//	return ret;
	//}

	return 0;
}

int tdav_codec_theora_close_encoder(tdav_codec_theora_t* self)
{
	if(self->encoder.context){
		avcodec_close(self->encoder.context);
		av_free(self->encoder.context);
		self->encoder.context = tsk_null;
	}
	if(self->encoder.picture){
		av_free(self->encoder.picture);
		self->encoder.picture = tsk_null;
	}
	if(self->encoder.buffer){
		TSK_FREE(self->encoder.buffer);
	}
	return 0;
}

int tdav_codec_theora_close_decoder(tdav_codec_theora_t* self)
{
	if(self->decoder.context){
		avcodec_close(self->decoder.context);
		if(self->decoder.context->extradata){
			TSK_FREE(self->decoder.context->extradata);
			self->decoder.context->extradata_size = 0;
		}
		av_free(self->decoder.context);
		self->decoder.context = tsk_null;
	}
	if(self->decoder.picture){
		av_free(self->decoder.picture);
		self->decoder.picture = tsk_null;
	}
	if(self->decoder.accumulator){
		TSK_FREE(self->decoder.accumulator);
	}
	return 0;
}

static void tdav_codec_theora_encap(tdav_codec_theora_t* theora, const uint8_t* pdata, tsk_size_t size)
{
	if((theora->encoder.conf_count < THEORA_CONF_SEND_COUNT) && theora->encoder.context && theora->encoder.context->extradata){
		if((theora->encoder.conf_last + (250 *theora->encoder.conf_count)) < tsk_time_now()){
			int hdr_size, i, exd_size = theora->encoder.context->extradata_size, conf_pkt_size = 0;
			uint8_t *conf_pkt_ptr = tsk_null, *exd_ptr = theora->encoder.context->extradata;
			for(i=0; i<3 && exd_size; i++){
				hdr_size = exd_ptr[0], hdr_size<<=8, hdr_size |= exd_ptr[1];
				exd_ptr += 2;
				exd_size -= 2;
				if(hdr_size > exd_size){
					TSK_DEBUG_ERROR("Invalid extradata");
					TSK_FREE(conf_pkt_ptr);
					conf_pkt_size = 0;
				}

				if(exd_ptr[0] == 0x80 || exd_ptr[0] == 0x82){ /* Ignore 'comment' which is equal to '0x81' */
					if((conf_pkt_ptr = tsk_realloc(conf_pkt_ptr, (conf_pkt_size + hdr_size)))){
						memcpy((conf_pkt_ptr + conf_pkt_size), exd_ptr, hdr_size);
						conf_pkt_size += hdr_size;
					}
				}
				exd_size -= hdr_size;
				exd_ptr += hdr_size;
			}

			/* Send the conf pack */
			if(conf_pkt_ptr && conf_pkt_size){
				/*TSK_DEBUG_INFO("Sending Configuration Packet");*/
				tdav_codec_theora_send(theora, conf_pkt_ptr, conf_pkt_size, Theora_Packed_Configuration_payload);
				TSK_FREE(conf_pkt_ptr);
			}

			theora->encoder.conf_last = tsk_time_now();
			theora->encoder.conf_count++;
		}
	}

	/* Send Theora Raw data */
	tdav_codec_theora_send(theora, pdata, size, Raw_Theora_payload);
}

int tdav_codec_theora_send(tdav_codec_theora_t* self, const uint8_t* data, tsk_size_t size, theora_datatype_t tdt)
{	 
	/* 2.2. Payload Header 
		0                   1                   2                   3
		0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
		+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		|               Configuration Ident             | F |TDT|# pkts.|
		+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	*/
	uint8_t pay_hdr[THEORA_PAYLOAD_HEADER_SIZE/*4*/ + THEORA_PAYLOAD_LENGTH_SIZE/*2*/] = {0x01, 0x19, 0x83, 0x00, 0x00, 0x00};
	//uint8_t* pay_ptr = tsk_null;
	tsk_size_t pay_size;
	tsk_bool_t frag, first = tsk_true;
		
	pay_hdr[3] = (tdt & 0xFF) <<4;
	
	/* whether the packet will be fragmented or not */
	frag = (size > THEORA_RTP_PAYLOAD_SIZE);

	while(size){
		pay_size = TSK_MIN(THEORA_RTP_PAYLOAD_SIZE, size);
		pay_hdr[4] = pay_size>>8, pay_hdr[5] = pay_size & 0xFF;

		if(frag){
			if(first){
				first = tsk_false;
				pay_hdr[3] &= 0x3F, pay_hdr[3] |= (Start_Fragment <<6);
			}
			else{ /* could not be 'first' and 'last' */
				if(size<=THEORA_RTP_PAYLOAD_SIZE){
					/* Last frag */
					pay_hdr[3] &= 0x3F, pay_hdr[3] |= (End_Fragment <<6);
				}
				else{
					/* Continuation frag */
					pay_hdr[3] &= 0x3F, pay_hdr[3] |= (Continuation_Fragment <<6);
				}
			}
		}
		else{
			pay_hdr[3] |= 0x01; /* 'pkts' */
			pay_hdr[3] &= 0x3F, pay_hdr[3] |= (Not_Fragmented <<6);
		}
		
		if(self->rtp.size < (pay_size + sizeof(pay_hdr))){
			if(!(self->rtp.ptr = tsk_realloc(self->rtp.ptr, (pay_size + sizeof(pay_hdr))))){
				TSK_DEBUG_ERROR("Failed to allocate new buffer");
				return -2;
			}
			self->rtp.size = (pay_size + sizeof(pay_hdr));
		}

		memcpy(self->rtp.ptr, pay_hdr, sizeof(pay_hdr));
		memcpy((self->rtp.ptr + sizeof(pay_hdr)), data, pay_size);
		data += pay_size;
		size -= pay_size;

		// Send data over the network
		if(TMEDIA_CODEC_VIDEO(self)->out.callback){
			TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = self->rtp.ptr;
			TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = (pay_size + sizeof(pay_hdr));
			TMEDIA_CODEC_VIDEO(self)->out.result.duration =  (1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate;
			TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = (size == 0);
			TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result);			
		}
	}

	return 0;
}

#endif /* HAVE_FFMPEG */