summaryrefslogtreecommitdiffstats
path: root/tinyDAV/src/codecs/theora/tdav_codec_theora.c
diff options
context:
space:
mode:
Diffstat (limited to 'tinyDAV/src/codecs/theora/tdav_codec_theora.c')
-rw-r--r--tinyDAV/src/codecs/theora/tdav_codec_theora.c862
1 files changed, 862 insertions, 0 deletions
diff --git a/tinyDAV/src/codecs/theora/tdav_codec_theora.c b/tinyDAV/src/codecs/theora/tdav_codec_theora.c
new file mode 100644
index 0000000..01072c3
--- /dev/null
+++ b/tinyDAV/src/codecs/theora/tdav_codec_theora.c
@@ -0,0 +1,862 @@
+/*
+* 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
+
+#include "tinydav/video/tdav_converter_video.h"
+
+#include "tinyrtp/rtp/trtp_rtp_packet.h"
+
+#include "tinymedia/tmedia_params.h"
+#include "tinymedia/tmedia_defaults.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;
+ int32_t max_bw_kpbs;
+ } 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 = (int)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, (int)theora->decoder.context->width, (int)theora->decoder.context->height,
+ *out_data, (int)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 = (int)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;
+ theora->encoder.max_bw_kpbs = tmedia_defaults_get_bandwidth_video_upload_max();
+ }
+ 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 (width, height, fps)*/
+ {176, 144, 0},// fps is @deprecated
+
+ 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;
+ int32_t max_bw_kpbs;
+ 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'
+ max_bw_kpbs = TSK_CLAMP(
+ 0,
+ tmedia_get_video_bandwidth_kbps_2(TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height, TMEDIA_CODEC_VIDEO(self)->out.fps),
+ self->encoder.max_bw_kpbs
+ );
+ self->encoder.context->bit_rate = (max_bw_kpbs * 1024);// bps
+#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;
+
+ TSK_DEBUG_INFO("[THEORA] bitrate=%d bps", self->encoder.context->bit_rate);
+
+ 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] = (uint8_t)(pay_size >> 8), pay_hdr[5] = (uint8_t)(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;
+}
+
+tsk_bool_t tdav_codec_ffmpeg_theora_is_supported()
+{
+ return (avcodec_find_encoder(CODEC_ID_THEORA) && avcodec_find_decoder(CODEC_ID_THEORA));
+}
+
+#endif /* HAVE_FFMPEG */ \ No newline at end of file
OpenPOWER on IntegriCloud