862 lines
28 KiB
C
862 lines
28 KiB
C
/*
|
|
* 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 = 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;
|
|
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 */
|
|
{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;
|
|
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] = 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;
|
|
}
|
|
|
|
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 */ |