diff options
Diffstat (limited to 'tinyDAV/src/codecs')
29 files changed, 15804 insertions, 0 deletions
diff --git a/tinyDAV/src/codecs/amr/tdav_codec_amr.c b/tinyDAV/src/codecs/amr/tdav_codec_amr.c new file mode 100644 index 0000000..9304f85 --- /dev/null +++ b/tinyDAV/src/codecs/amr/tdav_codec_amr.c @@ -0,0 +1,816 @@ +/* +* 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_amr.c + * @brief AMR-NB and AMR-WB codecs. + * RTP payloader/depayloader are based on RFC 4867 + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/amr/tdav_codec_amr.h" + +#include "tsk_params.h" +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#include <stdlib.h> /* atoi() */ + +#if HAVE_OPENCORE_AMR + +#if defined(_MSC_VER) +# pragma comment(lib, "..\\thirdparties\\win32\\lib\\opencore\\libopencore-amrnb.a") +#endif + +#define NO_DATA 15 +#define DEFAULT_ENC_MODE ((enum Mode)MR122) /* Higher, could be changed by remote party by using CMR */ + +/* From WmfDecBytesPerFrame in dec_input_format_tab.cpp */ +static const int tdav_codec_amr_nb_sizes[] = { 12, 13, 15, 17, 19, 20, 26, 31, 5, 6, 5, 5, 0, 0, 0, 0 }; +/* From pvamrwbdecoder_api.h, by dividing by 8 and rounding up */ +static const int tdav_codec_amr_wb_sizes[] = { 17, 23, 32, 36, 40, 46, 50, 58, 60, 5, -1, -1, -1, -1, -1, -1 }; + +/* ============ Common ================= */ +static int tdav_codec_amr_init(tdav_codec_amr_t* self, tdav_codec_amr_type_t type, tdav_codec_amr_mode_t mode); +static int tdav_codec_amr_deinit(tdav_codec_amr_t* self); +static tdav_codec_amr_mode_t tdav_codec_amr_get_mode(const char* fmtp); +static int tdav_codec_amr_parse_fmtp(tdav_codec_amr_t* self, const char* fmtp); +static tsk_size_t tdav_codec_amr_oa_decode(tdav_codec_amr_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); +static tsk_size_t tdav_codec_amr_be_decode(tdav_codec_amr_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); +static tsk_size_t tdav_codec_amr_be_encode(tdav_codec_amr_t* amr, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size); +static tsk_size_t tdav_codec_amr_oa_encode(tdav_codec_amr_t* amr, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size); +static uint8_t tdav_codec_amr_bitbuffer_read(const void* bits, tsk_size_t size, tsk_size_t start, tsk_size_t count); + +/* ============ AMR-NB Plugin interface ================= + The AMR codec was originally developed and standardized by the + European Telecommunications Standards Institute (ETSI) for GSM + cellular systems. It is now chosen by the Third Generation + Partnership Project (3GPP) as the mandatory codec for third + generation (3G) cellular systems [1]. + + The AMR codec is a multi-mode codec that supports eight narrow band + speech encoding modes with bit rates between 4.75 and 12.2 kbps. The + sampling frequency used in AMR is 8000 Hz and the speech encoding is + performed on 20 ms speech frames. Therefore, each encoded AMR speech + frame represents 160 samples of the original speech. + + Among the eight AMR encoding modes, three are already separately + adopted as standards of their own. Particularly, the 6.7 kbps mode + is adopted as PDC-EFR [18], the 7.4 kbps mode as IS-641 codec in TDMA + [17], and the 12.2 kbps mode as GSM-EFR [16]. +*/ + +int tdav_codec_amrnb_open(tmedia_codec_t* self) +{ + tdav_codec_amr_t* amrnb = (tdav_codec_amr_t*)self; + + if(!TDAV_CODEC_AMR(amrnb)->encoder){ + if(!(TDAV_CODEC_AMR(amrnb)->encoder = Encoder_Interface_init(0))){ + TSK_DEBUG_ERROR("Failed to initialize AMR-NB encoder"); + return -2; + } + } + + if(!TDAV_CODEC_AMR(amrnb)->decoder){ + if(!(TDAV_CODEC_AMR(amrnb)->decoder = Decoder_Interface_init())){ + TSK_DEBUG_ERROR("Failed to initialize AMR-NB encoder"); + return -2; + } + } + + return 0; +} + +int tdav_codec_amrnb_close(tmedia_codec_t* self) +{ + tdav_codec_amr_t* amrnb = (tdav_codec_amr_t*)self; + + if(TDAV_CODEC_AMR(amrnb)->encoder){ + Encoder_Interface_exit(TDAV_CODEC_AMR(amrnb)->encoder); + TDAV_CODEC_AMR(amrnb)->encoder = tsk_null; + } + + if(TDAV_CODEC_AMR(amrnb)->decoder){ + Decoder_Interface_exit(TDAV_CODEC_AMR(amrnb)->decoder); + TDAV_CODEC_AMR(amrnb)->decoder = tsk_null; + } + + return 0; +} + +tsk_size_t tdav_codec_amrnb_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tdav_codec_amr_t* amr = (tdav_codec_amr_t*)self; + + switch(amr->mode){ + case tdav_codec_amr_mode_be: + return tdav_codec_amr_be_encode(amr, in_data, in_size, out_data, out_max_size); + default: + return tdav_codec_amr_oa_encode(amr, in_data, in_size, out_data, out_max_size); + } + + return 0; +} + +tsk_size_t tdav_codec_amrnb_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) +{ + tdav_codec_amr_t* amr = (tdav_codec_amr_t*)self; + + switch(amr->mode){ + case tdav_codec_amr_mode_be: + return tdav_codec_amr_be_decode(amr, in_data, in_size, out_data, out_max_size, proto_hdr); + default: + return tdav_codec_amr_oa_decode(amr, in_data, in_size, out_data, out_max_size, proto_hdr); + } +} + +char* tdav_codec_amrnb_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) +{ + const tdav_codec_amr_t* amr = (const tdav_codec_amr_t*)codec; + + /* We support all modes, all ... */ + if(amr){ + switch(amr->mode){ + case tdav_codec_amr_mode_be: + return tsk_strdup("octet-align=0"); + default: + return tsk_strdup("octet-align=1"); + } + } + return tsk_null; +} + +tsk_bool_t tdav_codec_amrnb_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + tdav_codec_amr_t* amr; + if(!(amr = (tdav_codec_amr_t*)codec) || !att_name){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + if(amr && tsk_striequals(att_name, "fmtp")){ + /* Match mode */ + if(tdav_codec_amr_get_mode(att_value) != amr->mode){ + TSK_DEBUG_INFO("Failed to match [%s]", att_value); + return tsk_false; + } + /* check parameters validity */ + if(tdav_codec_amr_parse_fmtp(amr, att_value)){ + TSK_DEBUG_INFO("Failed to match [%s]", att_value); + return tsk_false; + } + + return tsk_true; + } + return tsk_false; +} + + +// +// AMR-NB OA Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_amrnb_oa_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_amr_t *amrnb_oa = self; + if(amrnb_oa){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + tdav_codec_amr_init(TDAV_CODEC_AMR(amrnb_oa), tdav_codec_amr_type_nb, tdav_codec_amr_mode_oa); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_amrnb_oa_dtor(tsk_object_t * self) +{ + tdav_codec_amr_t *amrnb_oa = self; + if(amrnb_oa){ + /* deinit base */ + tmedia_codec_audio_deinit(amrnb_oa); + /* deinit self */ + tdav_codec_amr_deinit(TDAV_CODEC_AMR(amrnb_oa)); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_amrnb_oa_def_s = +{ + sizeof(tdav_codec_amr_t), + tdav_codec_amrnb_oa_ctor, + tdav_codec_amrnb_oa_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_amrnb_oa_plugin_def_s = +{ + &tdav_codec_amrnb_oa_def_s, + + tmedia_audio, + tmedia_codec_id_amr_nb_oa, + "AMR", + "AMR Narrow Band - Octet Aligned (libopencore-amr)", + TMEDIA_CODEC_FORMAT_AMR_NB_OA, + tsk_true, + 8000, // rate + + { /* audio */ + 1, // channels + 20 // ptime + }, + + /* video */ + {0}, + + tsk_null, // set() + tdav_codec_amrnb_open, + tdav_codec_amrnb_close, + tdav_codec_amrnb_encode, + tdav_codec_amrnb_decode, + tdav_codec_amrnb_sdp_att_match, + tdav_codec_amrnb_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_amrnb_oa_plugin_def_t = &tdav_codec_amrnb_oa_plugin_def_s; + +// +// AMR-NB BE Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_amrnb_be_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_amr_t *amrnb_be = self; + if(amrnb_be){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + tdav_codec_amr_init(TDAV_CODEC_AMR(amrnb_be), tdav_codec_amr_type_nb, tdav_codec_amr_mode_be); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_amrnb_be_dtor(tsk_object_t * self) +{ + tdav_codec_amr_t *amrnb_be = self; + if(amrnb_be){ + /* deinit base */ + tmedia_codec_audio_deinit(amrnb_be); + /* deinit self */ + tdav_codec_amr_deinit(TDAV_CODEC_AMR(amrnb_be)); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_amrnb_be_def_s = +{ + sizeof(tdav_codec_amr_t), + tdav_codec_amrnb_be_ctor, + tdav_codec_amrnb_be_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_amrnb_be_plugin_def_s = +{ + &tdav_codec_amrnb_be_def_s, + + tmedia_audio, + tmedia_codec_id_amr_nb_be, + "AMR", + "AMR Narrow Band - Bandwidth-Efficient (libopencore-amr)", + TMEDIA_CODEC_FORMAT_AMR_NB_BE, + tsk_true, + 8000, // rate + + { /* audio */ + 1, // channels + 0 // ptime @deprecated + }, + + /* video */ + {0}, + + tsk_null, // set() + tdav_codec_amrnb_open, + tdav_codec_amrnb_close, + tdav_codec_amrnb_encode, + tdav_codec_amrnb_decode, + tdav_codec_amrnb_sdp_att_match, + tdav_codec_amrnb_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_amrnb_be_plugin_def_t = &tdav_codec_amrnb_be_plugin_def_s; + + + + + + + + + +// +// Common functions +// + +static int tdav_codec_amr_init(tdav_codec_amr_t* self, tdav_codec_amr_type_t type, tdav_codec_amr_mode_t mode) +{ + if(self){ + self->type = type; + self->mode = mode; + self->encoder_mode = DEFAULT_ENC_MODE; + + return 0; + } + else{ + TSK_DEBUG_ERROR("Invalid Parameter"); + return -1; + } +} + +static int tdav_codec_amr_deinit(tdav_codec_amr_t* self) +{ + if(self){ + switch(self->type){ + case tdav_codec_amr_type_nb: + { /* AMR-NB */ + if(self->encoder){ + Encoder_Interface_exit(self->encoder); + self->encoder = tsk_null; + } + if(self->decoder){ + Decoder_Interface_exit(self->decoder); + self->decoder = tsk_null; + } + break; + } + case tdav_codec_amr_type_wb: + { /* AMR-WB */ + break; + } + } + + return 0; + } + else{ + TSK_DEBUG_ERROR("Invalid Parameter"); + return -1; + } +} + +static tdav_codec_amr_mode_t tdav_codec_amr_get_mode(const char* fmtp) +{ + /* RFC 4867 - 8.1. AMR Media Type Registration + octet-align: Permissible values are 0 and 1. If 1, octet-aligned + operation SHALL be used. If 0 or if not present, bandwidth-efficient operation is employed. + */ + tdav_codec_amr_mode_t mode = tdav_codec_amr_mode_be; + tsk_size_t size = tsk_strlen(fmtp); + int start, end; + + if((start = tsk_strindexOf(fmtp, size, "octet-align")) !=-1){ + tsk_param_t* param; + if((end = tsk_strindexOf((fmtp+start), (size-start), ";")) == -1){ + end = size; + } + if((param = tsk_params_parse_param((fmtp+start), (end-start)))){ + if(param->value && tsk_strequals(param->value, "1")){ + mode = tdav_codec_amr_mode_oa; + } + TSK_OBJECT_SAFE_FREE(param); + } + } + return mode; +} + +int tdav_codec_amr_parse_fmtp(tdav_codec_amr_t* self, const char* fmtp) +{ + int ret = 0; + int val_int; + const char* val_str; + //--tdav_codec_amr_mode_t mode = self->mode; + tsk_params_L_t* params = tsk_null; + + if((params = tsk_params_fromstring(fmtp, ";", tsk_true))){ + /* Do not check "octet-align" => already done by the caller of this function */ + + /* === mode-set ===*/ + if((val_str = tsk_params_get_param_value(params, "mode-set"))){ + char* modes = tsk_strdup(val_str); + char *pch, *saveptr; + int mode_int; + pch = tsk_strtok_r(modes, ", ", &saveptr); + while(pch){ + mode_int = atoi(pch); + self->modes |= 0x0001 << mode_int; + pch = tsk_strtok_r(tsk_null, ", ", &saveptr); + } + + TSK_FREE(modes); + } + else{ + self->modes = 0xFFFF; + } + + /* === interleaving ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "interleaving")) != -1){ + TSK_DEBUG_WARN("interleaving not supported"); + ret = -1; goto bail; + } + /* === mode-change-period ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "mode-change-period")) != -1){ + if(val_int != 1 && val_int != 2){ + TSK_DEBUG_ERROR("Invalid [mode-change-period]"); + ret = -1; goto bail; + } + self->mcp = (unsigned)val_int; + } + /* === mode-change-capability ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "mode-change-capability")) != -1){ + if(val_int != 1 && val_int != 2){ + TSK_DEBUG_ERROR("Invalid [mode-change-capability]"); + ret = -1; goto bail; + } + self->mcc = (unsigned)val_int; + } + /* === mode-change-neighbor ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "mode-change-neighbor")) != -1){ + if(val_int != 0 && val_int != 1){ + TSK_DEBUG_ERROR("Invalid [mode-change-neighbor]"); + ret = -1; goto bail; + } + self->mcn = (unsigned)val_int; + } + } + +bail: + TSK_OBJECT_SAFE_FREE(params); + return ret; +} + + +/* RFC 4867 - 4.2. Payload Structure + +----------------+-------------------+---------------- + | payload header | table of contents | speech data ... + +----------------+-------------------+---------------- +*/ +/* RFC 4867 - 4.4.2. The Payload Table of Contents and Frame CRCs + The table of contents (ToC) consists of a list of ToC entries, each representing a speech frame. + +---------------------+ + | list of ToC entries | + +---------------------+ + | list of frame CRCs | (optional) + - - - - - - - - - - - + Note, for ToC entries with FT=14 or 15, there will be no + corresponding speech frame or frame CRC present in the payload. +*/ + + +static tsk_size_t tdav_codec_amr_be_encode(tdav_codec_amr_t* amr, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tsk_size_t out_size = 0, i; + int ret_size; + uint8_t ToC; + static uint8_t CMR = NO_DATA /* No interleaving */; + + uint8_t outbuf[60 + 1]; /* enought for both NB and WB at ptime=20ms */ + if(!amr || !in_data || !in_size || !out_data || (amr->mode != tdav_codec_amr_mode_be)){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* Encode */ + if((ret_size = Encoder_Interface_Encode(amr->encoder, amr->encoder_mode, in_data, outbuf, 0)) <= 0){ + TSK_DEBUG_ERROR("Encoder_Interface_Encode() failed"); + goto bail; + } + + + /* allocate output buffer */ + if((int)*out_max_size <ret_size){ + if(!(*out_data = tsk_realloc(*out_data, ret_size))){ + *out_max_size = 0; + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + goto bail; + } + *out_max_size = ret_size; + } + + out_size = ret_size; + + /* CMR (4bits) */ + ((uint8_t*)*out_data)[0] = (CMR<<4); + /* ToC (Always ONE Frame, don't need to test for [F]) (6bits)*/ + ToC = outbuf[0]>>2/*2*[P]*/; + ((uint8_t*)*out_data)[0] |= (ToC >> 2/*[Q],[1-FT]*/) & 0xF; /* 4bits */ + ((uint8_t*)*out_data)[1] = (ToC & 0x3/*[1-FT],[Q]*/)<<6; /* 2bits */ + + /* === THERE ARE 2 EXTRA BITS === */ + + for(i=1; i<out_size-1; i++){ + ((uint8_t*)*out_data)[i] |= outbuf[i]>>2;/* 6bits */ + ((uint8_t*)*out_data)[i+1] = outbuf[i]<<6;/* 2bits */ + } + +bail: + return out_size; +} + +tsk_size_t tdav_codec_amr_be_decode(tdav_codec_amr_t* amr, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size, const tsk_object_t* proto_hdr) +{ + tsk_size_t out_size = 0, pcm_frame_size = 0, index = 0; + const uint8_t* pdata = (const uint8_t*)in_data; + //--const uint8_t* pend = (pdata + in_size); + uint8_t CMR; + int toc_entries = 0, i, k; // ToC entries count + + if(!amr || !in_data || !in_size || !out_data || (amr->mode != tdav_codec_amr_mode_be)){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* compute PCM frame size */ + switch(TDAV_CODEC_AMR(amr)->type){ + case tdav_codec_amr_type_nb: + pcm_frame_size = 160 * sizeof(short); + break; + case tdav_codec_amr_type_wb: + pcm_frame_size = 320 * sizeof(short); + break; + default: + TSK_DEBUG_ERROR("Invalid AMR type"); + return 0; + } + + /* CMR (4bits) */ + CMR = tdav_codec_amr_bitbuffer_read(in_data, (in_size*8), index, 4); + index += 4; + if(CMR != NO_DATA){ + amr->encoder_mode = (enum Mode)CMR; + } + + /* F(1bit), FT(4bits), Q(1bit) */ + /* count ToC entries */ + do{ /* At least ONE ToC */ + ++toc_entries; + ++pdata; + index += 6; + } + while((index < (in_size*8)) && (tdav_codec_amr_bitbuffer_read(in_data, (in_size*8), (index-6), 1)/* F */)); + + for(i = 0; (i<toc_entries && (in_size < (in_size*8))) ; i++){ + int size = -1; + uint8_t* speech_data = tsk_null; + //--int speech_data_size = 0; + uint8_t ToC = tdav_codec_amr_bitbuffer_read(in_data, (in_size*8), 4/*CMR*/ + (i*6), 6); + + switch(TDAV_CODEC_AMR(amr)->type){ + case tdav_codec_amr_type_nb: + size = tdav_codec_amr_nb_sizes[(ToC>>1)&0x0F/* FT */]; + break; + case tdav_codec_amr_type_wb: + size = tdav_codec_amr_wb_sizes[(ToC>>1)&0x0F/* FT */]; + break; + } + + if((speech_data = tsk_calloc((size + 2/* ToC + '\0' */), sizeof(uint8_t)))){ + /* copy ToC */ + speech_data[0] = (ToC & 0x1F)<<2/* 2*[P] */; /* ToC as OA layout */ + /* copy speech data */ + for(k=0; k<size; k++){ + speech_data[1 + k] = tdav_codec_amr_bitbuffer_read(in_data, (in_size*8), index, 8); + index+=8; + if((k==size-1) && (index%8)){ + speech_data[1 + k] <<= (8-(index%8)); //clean + } + } + + /* allocate/reallocate speech data */ + if(*out_max_size <(out_size + pcm_frame_size)){ + if(!(*out_data = tsk_realloc(*out_data, (out_size + pcm_frame_size)))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + TSK_FREE(speech_data); + goto bail; + } + *out_max_size = out_size + pcm_frame_size; + } + + /* decode speech data */ + Decoder_Interface_Decode(amr->decoder, speech_data, &((short*)*out_data)[out_size/sizeof(short)], 0); + out_size += pcm_frame_size, pdata+= size; + + TSK_FREE(speech_data); + } + } + +bail: + return out_size; +} + +static tsk_size_t tdav_codec_amr_oa_encode(tdav_codec_amr_t* amr, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tsk_size_t out_size = 0; + int ret_size; + static uint8_t CMR = NO_DATA /* No interleaving */; + + uint8_t outbuf[60 + 1]; /* enought for both NB and WB at ptime=20ms */ + if(!amr || !in_data || !in_size || !out_data || (amr->mode != tdav_codec_amr_mode_oa)){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* Encode */ + if((ret_size = Encoder_Interface_Encode(amr->encoder, amr->encoder_mode, in_data, outbuf, 0)) <= 0){ + TSK_DEBUG_ERROR("Encoder_Interface_Encode() failed"); + goto bail; + } + + out_size = ret_size + 1 /* CMR without interleaving */; + /* allocate output buffer */ + if(*out_max_size <out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = out_size = 0; + goto bail; + } + *out_max_size = out_size; + } + + /* CMR */ + ((uint8_t*)*out_data)[0] = (CMR << 4); + /* Only ONE ToC --> believe me */ + memcpy(&((uint8_t*)*out_data)[1], outbuf, ret_size); + +bail: + return out_size; +} + +static tsk_size_t tdav_codec_amr_oa_decode(tdav_codec_amr_t* amr, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size, const tsk_object_t* proto_hdr) +{ + tsk_size_t out_size = 0, pcm_frame_size = 0; + const uint8_t* pdata = (const uint8_t*)in_data; + const uint8_t* pend = (pdata + in_size); + uint8_t CMR; + int toc_entries = 0, i; // ToC entries count + + if(!amr || !in_data || !in_size || !out_data || (amr->mode != tdav_codec_amr_mode_oa)){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* compute PCM frame size */ + switch(TDAV_CODEC_AMR(amr)->type){ + case tdav_codec_amr_type_nb: + pcm_frame_size = 160 * sizeof(short); + break; + case tdav_codec_amr_type_wb: + pcm_frame_size = 320 * sizeof(short); + break; + default: + TSK_DEBUG_ERROR("Invalid AMR type"); + return 0; + } + + /* RFC 4867 - 4.4. Octet-Aligned Mode + In octet-aligned mode, the payload header consists of a 4-bit CMR, 4 + reserved bits, and optionally, an 8-bit interleaving header, as shown + below: + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+- - - - - - - - + | CMR |R|R|R|R| ILL | ILP | + +-+-+-+-+-+-+-+-+- - - - - - - - + + CMR (4 bits): same as defined in Section 4.3.1. + + "interleaving" not supported ==> could ignore ILL and ILP (wich are optional) + */ + + CMR = (*pdata++ >> 4); + if(CMR != NO_DATA){ + /* The codec mode request received in the CMR field is valid until the + next codec mode request is received, i.e., a newly received CMR value + corresponding to a speech mode, or NO_DATA overrides the previously + received CMR value corresponding to a speech mode or NO_DATA. */ + amr->encoder_mode = (enum Mode)CMR; // As we support all modes, do not check for validity + } + + /* + A ToC entry takes the following format in octet-aligned mode: + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |F| FT |Q|P|P| + +-+-+-+-+-+-+-+-+ + + F (1 bit): see definition in Section 4.3.2. + FT (4 bits, unsigned integer): see definition in Section 4.3.2. + Q (1 bit): see definition in Section 4.3.2. + P bits: padding bits, MUST be set to zero, and MUST be ignored on reception. + */ + + /* count ToC entries */ + do{ /* At least ONE ToC */ + ++toc_entries; + ++pdata; + } + while(pdata && (pdata < pend) && (pdata[-1] >> 7/* F */)); + + for(i = 0; (i<toc_entries && (pdata < pend)) ; i++){ + int size = -1; + uint8_t* speech_data = tsk_null; + //--int speech_data_size = 0; + uint8_t ToC = ((const uint8_t*)in_data)[1/*CMR...*/ + i]; + switch(TDAV_CODEC_AMR(amr)->type){ + case tdav_codec_amr_type_nb: + size = tdav_codec_amr_nb_sizes[(ToC>>3) & 0x0F/* FT */]; + break; + case tdav_codec_amr_type_wb: + size = tdav_codec_amr_wb_sizes[(ToC>>3) & 0x0F/* FT */]; + break; + } + + /* check size */ + if(size <0 || ((pdata + size) > pend)){ + TSK_DEBUG_ERROR("Invalid size"); + break; + } + + if((speech_data = tsk_calloc((size + 2/* ToC + '\0' */), sizeof(uint8_t)))){ + /* copy ToC */ + *speech_data = ToC & 0x7F/* with 'F'=0 */; + /* copy speech data */ + memcpy((speech_data + 1), pdata, size); + /* allocate/reallocate speech data */ + if(*out_max_size <(out_size + pcm_frame_size)){ + if(!(*out_data = tsk_realloc(*out_data, (out_size + pcm_frame_size)))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + TSK_FREE(speech_data); + goto bail; + } + *out_max_size = (out_size + pcm_frame_size); + } + /* decode speech data */ + Decoder_Interface_Decode(amr->decoder, speech_data, &((short*)*out_data)[out_size/sizeof(short)], 0); + out_size += pcm_frame_size, pdata+= size; + + TSK_FREE(speech_data); + } + } + +bail: + return out_size; +} + + +static uint8_t tdav_codec_amr_bitbuffer_read(const void* bits, tsk_size_t size, tsk_size_t start, tsk_size_t count) +{ + uint8_t byte, left, right, pad; + + if(!bits || !size || count>8){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if((start + count) > size){ + count = (size - start); + } + + pad = start ? (8 - (start % 8)) : count; + left = ((uint8_t*)bits)[start/8] << (8-pad); + right = ((uint8_t*)bits)[((start+count)<size ? (start+count) : start)/8] >> pad; + + if((start && (start % 8) != ((start+count)%8)) || (!start && count>8)){ + /* overlap */ + byte = (left | right) >> (8-count); + } + else{ + byte = (left | right) & (0xFF >> (8-count)); + } + + return byte; +} + +#endif /* HAVE_OPENCORE_AMR */ diff --git a/tinyDAV/src/codecs/bfcp/tdav_codec_bfcp.c b/tinyDAV/src/codecs/bfcp/tdav_codec_bfcp.c new file mode 100644 index 0000000..3495295 --- /dev/null +++ b/tinyDAV/src/codecs/bfcp/tdav_codec_bfcp.c @@ -0,0 +1,104 @@ +/* +* Copyright (C) 2014 Mamadou DIOP. +* +* 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_bfcp.c + * @brief The The Binary Floor Control Protocol (BFCP, rfc4582) session. + */ +#include "tinydav/codecs/bfcp/tdav_codec_bfcp.h" + +#include "tsk_memory.h" +#include "tsk_debug.h" + +/* ============ BFCP Plugin interface ================= */ +#define tdav_codec_bfcp_open tsk_null +#define tdav_codec_bfcp_close tsk_null +#define tdav_codec_bfcp_sdp_att_get tsk_null +#define tdav_codec_bfcp_sdp_att_get tsk_null +#define tdav_codec_bfcp_encode tsk_null +#define tdav_codec_bfcp_decode tsk_null + +static tsk_bool_t tdav_codec_bfcp_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ /* always match */ + return tsk_true; +} + + +// +// BFCP Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_bfcp_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_bfcp_t *bfcp = self; + if (bfcp) { + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_bfcp_dtor(tsk_object_t * self) +{ + tdav_codec_bfcp_t *bfcp = self; + if (bfcp) { + /* deinit base */ + tmedia_codec_bfcp_deinit(bfcp); + /* deinit self */ + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_bfcp_def_s = +{ + sizeof(tdav_codec_bfcp_t), + tdav_codec_bfcp_ctor, + tdav_codec_bfcp_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_bfcp_plugin_def_s = +{ + &tdav_codec_bfcp_def_s, + + tmedia_bfcp, + tmedia_codec_id_none, // fake codec without real id + "application", + "BFCP fake codec", + TMEDIA_CODEC_FORMAT_BFCP, + tsk_false, + 0, // rate + + /* audio */ + {0}, + + /* video */ + {0}, + + tsk_null, // set() + tdav_codec_bfcp_open, + tdav_codec_bfcp_close, + tdav_codec_bfcp_encode, + tdav_codec_bfcp_decode, + tdav_codec_bfcp_sdp_att_match, + tdav_codec_bfcp_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_bfcp_plugin_def_t = &tdav_codec_bfcp_plugin_def_s; diff --git a/tinyDAV/src/codecs/bv/tdav_codec_bv16.c b/tinyDAV/src/codecs/bv/tdav_codec_bv16.c new file mode 100644 index 0000000..21850fb --- /dev/null +++ b/tinyDAV/src/codecs/bv/tdav_codec_bv16.c @@ -0,0 +1,250 @@ +/* +* 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_bv16.c + * @brief BroadVoice16 codec + * The payloader/depayloader follow RFC 4298 + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/bv/tdav_codec_bv16.h" + +#if HAVE_BV16 + +#if defined(_MSC_VER) +# pragma comment(lib, "..\\thirdparties\\win32\\lib\\BroadVoice16\\libbv16.a") +#endif + +#include "tsk_memory.h" +#include "tsk_debug.h" + +#include "typedef.h" +#include "bvcommon.h" +#include "bv16cnst.h" +#include "bv16strct.h" +#include "bv16.h" +#include "utility.h" +#if G192BITSTREAM +#include "g192.h" +#else +#include "bitpack.h" +#endif +#include "memutil.h" + +/* RFC 4298 - 3.1. BroadVoice16 Bit Stream Definition */ +#define TDAV_BV16_FRAME_SIZE 10 +#define FRSZ_IN_U8 (FRSZ*2) + +/* ============ BV16 Plugin interface ================= */ + +#define tdav_codec_bv16_sdp_att_get tsk_null +#define tdav_codec_bv16_fmtp_set tsk_null + +static int sizestate = sizeof(struct BV16_Encoder_State); +static int sizebitstream = sizeof(struct BV16_Bit_Stream); + +int tdav_codec_bv16_open(tmedia_codec_t* self) +{ + tdav_codec_bv16_t* bv16 = (tdav_codec_bv16_t*)self; + + if(!bv16->encoder.state){ + bv16->encoder.state = allocWord16(0, sizestate/2-1); + Reset_BV16_Encoder((struct BV16_Encoder_State*)bv16->encoder.state); + } + if(!bv16->encoder.bs){ + bv16->encoder.bs = allocWord16(0, sizebitstream/2-1); + } + if(!bv16->encoder.x){ + bv16->encoder.x = allocWord16(0, FRSZ-1); + } + + if(!bv16->decoder.state){ + bv16->decoder.state = allocWord16(0, sizestate/2-1); + Reset_BV16_Decoder((struct BV16_Decoder_State*)bv16->decoder.state); + } + if(!bv16->decoder.bs){ + bv16->decoder.bs = allocWord16(0, sizebitstream/2-1); + } + if(!bv16->decoder.x){ + bv16->decoder.x = allocWord16(0, FRSZ-1); + } + + return 0; +} + +int tdav_codec_bv16_close(tmedia_codec_t* self) +{ + tdav_codec_bv16_t* bv16 = (tdav_codec_bv16_t*)self; + + if(bv16->encoder.state){ + deallocWord16(bv16->encoder.state, 0, sizestate/2-1); + bv16->encoder.state = tsk_null; + } + if(bv16->encoder.bs){ + deallocWord16(bv16->encoder.bs, 0, sizebitstream/2-1); + bv16->encoder.bs = tsk_null; + } + if(bv16->encoder.x){ + deallocWord16(bv16->encoder.x, 0, FRSZ-1); + bv16->encoder.x = tsk_null; + } + + if(bv16->decoder.state){ + deallocWord16(bv16->decoder.state, 0, sizestate/2-1); + bv16->decoder.state = tsk_null; + } + if(bv16->encoder.bs){ + deallocWord16(bv16->decoder.bs, 0, sizebitstream/2-1); + bv16->decoder.bs = tsk_null; + } + if(bv16->decoder.x){ + deallocWord16(bv16->decoder.x, 0, FRSZ-1); + bv16->decoder.x = tsk_null; + } + + + + return 0; +} + +tsk_size_t tdav_codec_bv16_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + //tsk_size_t out_size = 0; + tdav_codec_bv16_t* bv16 = (tdav_codec_bv16_t*)self; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + return 0; +} + +tsk_size_t tdav_codec_bv16_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) +{ + tsk_size_t out_size = 0; + int i; + tdav_codec_bv16_t* bv16 = (tdav_codec_bv16_t*)self; + uint8_t mama[600]; + + if(!self || !in_data || !in_size || !out_data || (in_size % TDAV_BV16_FRAME_SIZE)){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + for(i=0; i<(int)in_size; i+=TDAV_BV16_FRAME_SIZE){ + BV16_BitUnPack(mama, (struct BV16_Bit_Stream*)bv16->decoder.bs); + //BV16_BitUnPack(&((UWord8 *)in_data)[i], (struct BV16_Bit_Stream*)bv16->decoder.bs); + BV16_Decode((struct BV16_Bit_Stream*)bv16->decoder.bs, (struct BV16_Decoder_State*)bv16->decoder.state, bv16->decoder.x); + + + if(*out_max_size<(out_size + FRSZ_IN_U8)){ + if(!(*out_data = tsk_realloc(*out_data, (out_size + FRSZ_IN_U8)))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = (out_size + FRSZ_IN_U8); + } + memcpy(&((uint8_t*)* out_data)[out_size], bv16->decoder.x, FRSZ_IN_U8); + out_size += FRSZ_IN_U8; + } + + + return out_size; +} + +tsk_bool_t tdav_codec_bv16_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ /* always match */ + return tsk_true; +} + + +// +// BV16 Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_bv16_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_bv16_t *bv16 = self; + if(bv16){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_bv16_dtor(tsk_object_t * self) +{ + tdav_codec_bv16_t *bv16 = self; + if(bv16){ + /* deinit base */ + tmedia_codec_audio_deinit(bv16); + /* deinit self */ + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_bv16_def_s = +{ + sizeof(tdav_codec_bv16_t), + tdav_codec_bv16_ctor, + tdav_codec_bv16_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_bv16_plugin_def_s = +{ + &tdav_codec_bv16_def_s, + + tmedia_audio, + tmedia_codec_id_bv16, + "BV16", + "BroadVoice16 Rate", + TMEDIA_CODEC_FORMAT_BV16, + tsk_true, + 8000, // rate + + { /* audio */ + 1, // channels + 0 // ptime @deprecated + }, + + /* video */ + {0}, + + tdav_codec_bv16_open, + tdav_codec_bv16_close, + tdav_codec_bv16_encode, + tdav_codec_bv16_decode, + tdav_codec_bv16_sdp_att_match, + tdav_codec_bv16_sdp_att_get, + tdav_codec_bv16_fmtp_set +}; +const tmedia_codec_plugin_def_t *tdav_codec_bv16_plugin_def_t = &tdav_codec_bv16_plugin_def_s; + + +#endif /* HAVE_BV16 */ diff --git a/tinyDAV/src/codecs/bv/tdav_codec_bv32.c b/tinyDAV/src/codecs/bv/tdav_codec_bv32.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tinyDAV/src/codecs/bv/tdav_codec_bv32.c diff --git a/tinyDAV/src/codecs/dtmf/tdav_codec_dtmf.c b/tinyDAV/src/codecs/dtmf/tdav_codec_dtmf.c new file mode 100644 index 0000000..103ac8d --- /dev/null +++ b/tinyDAV/src/codecs/dtmf/tdav_codec_dtmf.c @@ -0,0 +1,126 @@ +/* +* 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_dtmf.c + * @brief DTMF (RFC 4733) codec plugins. + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/dtmf/tdav_codec_dtmf.h" + +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + + +/* ============ DTMF Plugin interface ================= */ + +tsk_size_t tdav_codec_dtmf_fmtp_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + return 0; +} + +tsk_size_t tdav_codec_dtmf_fmtp_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) +{ + return 0; +} + +char* tdav_codec_dtmf_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + if(tsk_striequals(att_name, "fmtp")){ + return tsk_strdup("0-16"); + } + return tsk_null; +} + +tsk_bool_t tdav_codec_dtmf_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ /* always match */ + return tsk_true; +} + + +// +// DTMF Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_dtmf_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_dtmf_t *dtmf = self; + if(dtmf){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_dtmf_dtor(tsk_object_t * self) +{ + tdav_codec_dtmf_t *dtmf = self; + if(dtmf){ + /* deinit base */ + tmedia_codec_audio_deinit(dtmf); + /* deinit self */ + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_dtmf_def_s = +{ + sizeof(tdav_codec_dtmf_t), + tdav_codec_dtmf_ctor, + tdav_codec_dtmf_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_dtmf_plugin_def_s = +{ + &tdav_codec_dtmf_def_s, + + tmedia_audio, + tmedia_codec_id_none, // fake codec without real identifier + "telephone-event", + "DTMF Codec (RFC 4733)", + TMEDIA_CODEC_FORMAT_DTMF, + tsk_true, + 8000, // rate + + { /* audio */ + 1, // channels + 20 // ptime + }, + + /* video */ + {0}, + + tsk_null, // set() + tsk_null, // open + tsk_null, // close + tdav_codec_dtmf_fmtp_encode, + tdav_codec_dtmf_fmtp_decode, + tdav_codec_dtmf_sdp_att_match, + tdav_codec_dtmf_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_dtmf_plugin_def_t = &tdav_codec_dtmf_plugin_def_s; diff --git a/tinyDAV/src/codecs/fec/tdav_codec_red.c b/tinyDAV/src/codecs/fec/tdav_codec_red.c new file mode 100644 index 0000000..2fb6f27 --- /dev/null +++ b/tinyDAV/src/codecs/fec/tdav_codec_red.c @@ -0,0 +1,263 @@ +/* +* Copyright (C) 2012 Doubango Telecom <http://www.doubango.org> +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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_red.c + * @brief RTP Payload for Redundant Audio Data as per RFC 2198 + */ +#include "tinydav/codecs/fec/tdav_codec_red.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tsk_memory.h" +#include "tsk_time.h" +#include "tsk_debug.h" + +typedef struct tdav_codec_red_s +{ + TMEDIA_DECLARE_CODEC_VIDEO; + + tdav_codec_red_rtppacket_cb_f callback; + const void* callback_data; +} +tdav_codec_red_t; + +int tdav_codec_red_set_callback(tdav_codec_red_t *self, tdav_codec_red_rtppacket_cb_f callback, const void* callback_data) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + self->callback = callback; + self->callback_data = callback_data; + + return 0; +} + +static int tdav_codec_red_open(tmedia_codec_t* self) +{ + return 0; +} + +static int tdav_codec_red_close(tmedia_codec_t* self) +{ + return 0; +} + +static tsk_size_t tdav_codec_red_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tdav_codec_red_t *red = (tdav_codec_red_t *)self; + tsk_size_t xsize = (in_size + 1); + static const uint8_t __first_octet = 0x00; // F=1, PT=0. Up to the caller to update this first octet with the right PT. + + if(!red || !in_data || !in_size || !out_data || !out_max_size){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(*out_max_size < xsize){ + if(!(*out_data = tsk_realloc(*out_data, xsize))){ + TSK_DEBUG_ERROR("Failed to realloc data"); + *out_max_size = 0; + } + *out_max_size = xsize; + } + + ((uint8_t*)*out_data)[0] = __first_octet; + memcpy(&((uint8_t*)*out_data)[1], in_data, in_size); + + return xsize; +} + +static tsk_size_t tdav_codec_red_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) +{ + tdav_codec_red_t* red = (tdav_codec_red_t*)self; + const trtp_rtp_header_t* rtp_hdr = proto_hdr; + trtp_rtp_packet_t* red_rtp_pkt = tsk_null; + const uint8_t* pdata = in_data; + const uint8_t* red_hdr = in_data; + tsk_size_t red_hdrs_count, i; + tsk_bool_t last; + uint8_t F; + uint16_t timestamp_offset, block_length; + + if(!red || !in_data || (in_size < TDAV_CODEC_RED_MIN_PKT_SIZE)|| !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(!red->callback){ + TSK_DEBUG_WARN("Not callback installed for RED data"); + return 0; + } + + if((F = (pdata[0] & 0x80)) == 0){ + i = 1; + red_hdrs_count = 1; + } + else{ + for(i = 0, red_hdrs_count = 0; i < in_size; i+= 4, ++red_hdrs_count){ + if((F = (pdata[i] & 0x80)) == 0){ ++i; ++red_hdrs_count; break; } + } + } + + if(i >= in_size){ + TSK_DEBUG_ERROR("Invalid data"); + return 0; + } + + pdata += i; + in_size -= i; + + for(i = 0; i < red_hdrs_count && in_size > 0; ++i){ + TSK_OBJECT_SAFE_FREE(red_rtp_pkt); + if(!(red_rtp_pkt = trtp_rtp_packet_create_null())){ + TSK_DEBUG_ERROR("Failed to create RTP packet"); + continue; + } + if(!(red_rtp_pkt->header = trtp_rtp_header_create(rtp_hdr->ssrc, rtp_hdr->seq_num, rtp_hdr->timestamp, rtp_hdr->payload_type, rtp_hdr->marker))){ + TSK_DEBUG_ERROR("Failed to create RTP header"); + continue; + } + + // Must create an RTP packet for each RED chunck as they will be saved in the JB + last = (i == (red_hdrs_count - 1)); + F = (red_hdr[0] & 0x80); + red_rtp_pkt->header->payload_type = (red_hdr[0] & 0x7F); + + if(last || !F){ + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |0| Block PT | + +-+-+-+-+-+-+-+-+ + */ + block_length = (uint16_t)in_size; + } + else{ + /* + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |1| block PT=7 | timestamp offset | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + timestamp_offset = ((red_hdr[1] << 8) | red_hdr[2]) >> 2; + block_length = ((red_hdr[2] & 0x03) << 8) | red_hdr[3]; + if(block_length > in_size){ + TSK_DEBUG_ERROR("Invalid 'block length'"); + break; + } + red_rtp_pkt->header->timestamp += timestamp_offset; + red_hdr += 4; + } + + // decode + if(red->callback){ + // do not use "data_const" as payload will be saved in the jitter buffer and decoded later (async) + if((red_rtp_pkt->payload.data = tsk_malloc(block_length))){ + memcpy(red_rtp_pkt->payload.data, pdata, block_length); + red_rtp_pkt->payload.size = block_length; + red->callback(red->callback_data, red_rtp_pkt); + } + } + + pdata += block_length; + in_size -= block_length; + } + + TSK_OBJECT_SAFE_FREE(red_rtp_pkt); + + return 0; // must be always zero +} + +static tsk_bool_t tdav_codec_red_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + return tsk_true; +} + +static char* tdav_codec_red_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + return tsk_null; +} + + +/* ============ red object definition ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_red_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_red_t *red = self; + if(red){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_red_dtor(tsk_object_t * self) +{ + tdav_codec_red_t *red = self; + if(red){ + /* deinit base */ + tmedia_codec_video_deinit(red); + /* deinit self */ + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_red_def_s = +{ + sizeof(tdav_codec_red_t), + tdav_codec_red_ctor, + tdav_codec_red_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_red_plugin_def_s = +{ + &tdav_codec_red_def_s, + + (/* tmedia_video | tmedia_audio | */tmedia_t140), // FIXME: for now is only supported with T.140 + tmedia_codec_id_red, + "red", + "red codec", + TMEDIA_CODEC_FORMAT_RED, + tsk_true, + 1000, // rate: FIXME: for now it's only for T.140 + + /* audio */ + { 0 }, + + /* video (defaul width,height,fps) */ + {176, 144, 15}, + + tsk_null, // set() + tdav_codec_red_open, + tdav_codec_red_close, + tdav_codec_red_encode, + tdav_codec_red_decode, + tdav_codec_red_sdp_att_match, + tdav_codec_red_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_red_plugin_def_t = &tdav_codec_red_plugin_def_s; diff --git a/tinyDAV/src/codecs/fec/tdav_codec_ulpfec.c b/tinyDAV/src/codecs/fec/tdav_codec_ulpfec.c new file mode 100644 index 0000000..f492a52 --- /dev/null +++ b/tinyDAV/src/codecs/fec/tdav_codec_ulpfec.c @@ -0,0 +1,424 @@ +/* +* Copyright (C) 2012-2015 Doubango Telecom <http://www.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_ulpfec.c + * @brief Forward Error Correction (FEC) implementation as per RFC 5109 + */ +#include "tinydav/codecs/fec/tdav_codec_ulpfec.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#define TDAV_FEC_PKT_HDR_SIZE 10 + +typedef struct tdav_codec_ulpfec_s +{ + TMEDIA_DECLARE_CODEC_VIDEO; + + struct{ + struct tdav_fec_pkt_s* pkt; + } encoder; +} +tdav_codec_ulpfec_t; + +// +// FEC LEVEL +// +typedef struct tdav_fec_level_s +{ + TSK_DECLARE_OBJECT; + + struct{ // 7.4. FEC Level Header for FEC Packets + uint16_t length; + uint64_t mask; + tsk_size_t mask_size; // in bits + } hdr; + struct{ + uint8_t* ptr; + tsk_size_t size; + }payload; +}tdav_fec_level_t; +typedef tsk_list_t tdav_fec_levels_L_t; +static tsk_object_t* tdav_fec_level_ctor(tsk_object_t * self, va_list * app) +{ + tdav_fec_level_t *level = self; + if (level){ + level->hdr.mask_size = 16; // L=0 + } + return self; +} +static tsk_object_t* tdav_fec_level_dtor(tsk_object_t * self) +{ + tdav_fec_level_t *level = self; + if (level){ + TSK_FREE(level->payload.ptr); + } + + return self; +} +static const tsk_object_def_t tdav_fec_level_def_s = +{ + sizeof(tdav_fec_level_t), + tdav_fec_level_ctor, + tdav_fec_level_dtor, + tsk_null, +}; +const tsk_object_def_t *tdav_fec_level_def_t = &tdav_fec_level_def_s; + + +// +// FEC PACKET +// +typedef struct tdav_fec_pkt_s +{ + TSK_DECLARE_OBJECT; + + struct{ // RFC 5109 - 7.3. FEC Header for FEC Packets + unsigned E : 1; + unsigned L : 1; + unsigned P : 1; + unsigned X : 1; + unsigned CC : 4; + unsigned M : 1; + unsigned PT : 7; + struct{ + uint16_t value; + unsigned set : 1; + }SN_base; + uint32_t TS; + uint16_t length; + }hdr; + + tdav_fec_levels_L_t* levels; +} +tdav_fec_pkt_t; +static tsk_object_t* tdav_fec_pkt_ctor(tsk_object_t * self, va_list * app) +{ + tdav_fec_pkt_t *pkt = self; + if (pkt){ + if (!(pkt->levels = tsk_list_create())){ + TSK_DEBUG_ERROR("Failed to create levels"); + return tsk_null; + } + } + return self; +} +static tsk_object_t* tdav_fec_pkt_dtor(tsk_object_t * self) +{ + tdav_fec_pkt_t *pkt = self; + if (pkt){ + TSK_OBJECT_SAFE_FREE(pkt->levels); + } + + return self; +} +static int tdav_fec_pkt_cmp(const tsk_object_t *_p1, const tsk_object_t *_p2) +{ + const tdav_fec_pkt_t *p1 = _p1; + const tdav_fec_pkt_t *p2 = _p2; + + if (p1 && p2){ + return (int)(p1->hdr.SN_base.value - p2->hdr.SN_base.value); + } + else if (!p1 && !p2) return 0; + else return -1; +} +static const tsk_object_def_t tdav_fec_pkt_def_s = +{ + sizeof(tdav_fec_pkt_t), + tdav_fec_pkt_ctor, + tdav_fec_pkt_dtor, + tdav_fec_pkt_cmp, +}; +const tsk_object_def_t *tdav_fec_pkt_def_t = &tdav_fec_pkt_def_s; + + +tsk_size_t tdav_codec_ulpfec_guess_serialbuff_size(const tdav_codec_ulpfec_t* self) +{ + tsk_size_t size = TDAV_FEC_PKT_HDR_SIZE; + tsk_list_item_t *item; + tdav_fec_level_t* level; + + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + tsk_list_foreach(item, self->encoder.pkt->levels){ + if (!(level = item->data)){ + continue; + } + size += 2 /* Protection length */ + (level->hdr.mask_size >> 3) + level->hdr.length; + } + + return size; +} + +int tdav_codec_ulpfec_enc_reset(tdav_codec_ulpfec_t* self) +{ + tsk_list_item_t *item; + tdav_fec_level_t* level; + + if (!self || !self->encoder.pkt){ + TSK_DEBUG_ERROR("invalid parameter"); + return -1; + } + + // reset packet + memset(&self->encoder.pkt->hdr, 0, sizeof(self->encoder.pkt->hdr)); + + // reset levels + tsk_list_foreach(item, self->encoder.pkt->levels){ + if ((level = item->data)){ + memset(&level->hdr, 0, sizeof(level->hdr)); + if (level->payload.ptr){ + memset(level->payload.ptr, 0, level->payload.size); + } + } + } + return 0; +} + +int tdav_codec_ulpfec_enc_protect(tdav_codec_ulpfec_t* self, const trtp_rtp_packet_t* rtp_packet) +{ + if (!self || !self->encoder.pkt || !rtp_packet || !rtp_packet->header){ + TSK_DEBUG_ERROR("invalid parameter"); + return -1; + } + + // Packet + self->encoder.pkt->hdr.P ^= rtp_packet->header->padding; + self->encoder.pkt->hdr.X ^= rtp_packet->header->extension; + self->encoder.pkt->hdr.CC ^= rtp_packet->header->csrc_count; + self->encoder.pkt->hdr.M ^= rtp_packet->header->marker; + self->encoder.pkt->hdr.PT ^= rtp_packet->header->payload_type; + if (!self->encoder.pkt->hdr.SN_base.set){ + self->encoder.pkt->hdr.SN_base.value = rtp_packet->header->seq_num; + self->encoder.pkt->hdr.SN_base.set = 1; + } + else{ + self->encoder.pkt->hdr.SN_base.value = TSK_MIN(self->encoder.pkt->hdr.SN_base.value, rtp_packet->header->seq_num); + } + self->encoder.pkt->hdr.TS ^= rtp_packet->header->timestamp; + self->encoder.pkt->hdr.length ^= (trtp_rtp_packet_guess_serialbuff_size(rtp_packet) - TRTP_RTP_HEADER_MIN_SIZE); + + // Level + // For now, always single-level protection + { + tdav_fec_level_t* level0 = TSK_LIST_FIRST_DATA(self->encoder.pkt->levels); + const uint8_t* rtp_payload = (const uint8_t*)(rtp_packet->payload.data_const ? rtp_packet->payload.data_const : rtp_packet->payload.data); + tsk_size_t i; + if (!level0){ + tdav_fec_level_t* _level0; + if (!(_level0 = tsk_object_new(tdav_fec_level_def_t))){ + TSK_DEBUG_ERROR("Failed to create level"); + return -2; + } + level0 = _level0; + tsk_list_push_back_data(self->encoder.pkt->levels, (void**)&_level0); + } + if (level0->payload.size < rtp_packet->payload.size){ + if (!(level0->payload.ptr = tsk_realloc(level0->payload.ptr, rtp_packet->payload.size))){ + TSK_DEBUG_ERROR("Failed to realloc size %d", rtp_packet->payload.size); + level0->payload.size = 0; + return -3; + } + level0->payload.size = rtp_packet->payload.size; + } + for (i = 0; i < rtp_packet->payload.size; ++i){ + level0->payload.ptr[i] ^= rtp_payload[i]; + } + level0->hdr.mask_size = self->encoder.pkt->hdr.L ? 48 : 16; + level0->hdr.mask |= (uint64_t)((uint64_t)1 << (level0->hdr.mask_size - (rtp_packet->header->seq_num - self->encoder.pkt->hdr.SN_base.value))); + level0->hdr.length = (uint16_t)(TSK_MAX(level0->hdr.length, rtp_packet->payload.size)); + } + + return 0; +} + +tsk_size_t tdav_codec_ulpfec_enc_serialize(const tdav_codec_ulpfec_t* self, void** out_data, tsk_size_t* out_max_size) +{ + uint8_t* pdata; + tsk_size_t xsize; + int32_t i; + tsk_list_item_t* item; + tdav_fec_level_t* level; + + if (!self || !self->encoder.pkt || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + xsize = tdav_codec_ulpfec_guess_serialbuff_size(self); + + if (*out_max_size < xsize){ + if (!(*out_data = tsk_realloc(*out_data, xsize))){ + TSK_DEBUG_ERROR("Failed to reallocate buffer with size =%d", xsize); + *out_max_size = 0; + return 0; + } + *out_max_size = xsize; + } + pdata = (uint8_t*)*out_data; + + // E(1), L(1), P(1), X(1), CC(4) + pdata[0] = + (self->encoder.pkt->hdr.E << 7) | + (self->encoder.pkt->hdr.L << 6) | + (self->encoder.pkt->hdr.P << 5) | + (self->encoder.pkt->hdr.X << 4) | + (self->encoder.pkt->hdr.CC & 0x0F); + // M(1), PT(7) + pdata[1] = (self->encoder.pkt->hdr.M << 7) | (self->encoder.pkt->hdr.PT & 0x7F); + // SN base (16) + pdata[2] = (self->encoder.pkt->hdr.SN_base.value >> 8); + pdata[3] = (self->encoder.pkt->hdr.SN_base.value & 0xFF); + // TS (32) + pdata[4] = self->encoder.pkt->hdr.TS >> 24; + pdata[5] = (self->encoder.pkt->hdr.TS >> 16) & 0xFF; + pdata[6] = (self->encoder.pkt->hdr.TS >> 8) & 0xFF; + pdata[7] = (self->encoder.pkt->hdr.TS & 0xFF); + // Length (16) + pdata[8] = (self->encoder.pkt->hdr.length >> 8); + pdata[9] = (self->encoder.pkt->hdr.length & 0xFF); + + pdata += 10; + + tsk_list_foreach(item, self->encoder.pkt->levels){ + if (!(level = item->data)){ + continue; + } + // Protection length (16) + pdata[0] = (level->hdr.length >> 8); + pdata[1] = (level->hdr.length & 0xFF); + pdata += 2; + // mask (16 or 48) + for (i = (int32_t)(level->hdr.mask_size - 8); i >= 0; i -= 8){ + *pdata = ((level->hdr.mask >> i) & 0xFF); ++pdata; + } + // payload + memcpy(pdata, level->payload.ptr, level->hdr.length); + } + + return xsize; +} + + + +static int tdav_codec_ulpfec_open(tmedia_codec_t* self) +{ + return 0; +} + +static int tdav_codec_ulpfec_close(tmedia_codec_t* self) +{ + return 0; +} + +static tsk_size_t tdav_codec_ulpfec_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + TSK_DEBUG_ERROR("Not expected to be called"); + return 0; +} + +static tsk_size_t tdav_codec_ulpfec_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) +{ + TSK_DEBUG_ERROR("Not expected to be called"); + return 0; +} + +static tsk_bool_t tdav_codec_ulpfec_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + return tsk_true; +} + +static char* tdav_codec_ulpfec_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + return tsk_null; +} + + +/* ============ ULPFEC object definition ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_ulpfec_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_ulpfec_t *ulpfec = self; + if (ulpfec){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + if (!(ulpfec->encoder.pkt = tsk_object_new(tdav_fec_pkt_def_t))){ + TSK_DEBUG_ERROR("Failed to create FEC packet"); + return tsk_null; + } + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_ulpfec_dtor(tsk_object_t * self) +{ + tdav_codec_ulpfec_t *ulpfec = self; + if (ulpfec){ + /* deinit base */ + tmedia_codec_video_deinit(ulpfec); + /* deinit self */ + TSK_OBJECT_SAFE_FREE(ulpfec->encoder.pkt); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_ulpfec_def_s = +{ + sizeof(tdav_codec_ulpfec_t), + tdav_codec_ulpfec_ctor, + tdav_codec_ulpfec_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_ulpfec_plugin_def_s = +{ + &tdav_codec_ulpfec_def_s, + + tmedia_video, + tmedia_codec_id_none, // fake codec + "ulpfec", + "ulpfec codec", + TMEDIA_CODEC_FORMAT_ULPFEC, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (defaul width,height,fps) */ + { 176, 144, 15 }, + + tsk_null, // set() + tdav_codec_ulpfec_open, + tdav_codec_ulpfec_close, + tdav_codec_ulpfec_encode, + tdav_codec_ulpfec_decode, + tdav_codec_ulpfec_sdp_att_match, + tdav_codec_ulpfec_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_ulpfec_plugin_def_t = &tdav_codec_ulpfec_plugin_def_s;
\ No newline at end of file diff --git a/tinyDAV/src/codecs/g711/g711.c b/tinyDAV/src/codecs/g711/g711.c new file mode 100644 index 0000000..fa7c8be --- /dev/null +++ b/tinyDAV/src/codecs/g711/g711.c @@ -0,0 +1,295 @@ +/* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + * + * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING + * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun source code is provided with no support and without any obligation on + * the part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +/* + * g711.c + * + * u-law, A-law and linear PCM conversions. + */ + +/* + * December 30, 1994: + * Functions linear2alaw, linear2ulaw have been updated to correctly + * convert unquantized 16 bit values. + * Tables for direct u- to A-law and A- to u-law conversions have been + * corrected. + * Borge Lindberg, Center for PersonKommunikation, Aalborg University. + * bli@cpk.auc.dk + * + */ + +#include "tinydav/codecs/g711/g711.h" + +#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */ +#define QUANT_MASK (0xf) /* Quantization field mask. */ +#define NSEGS (8) /* Number of A-law segments. */ +#define SEG_SHIFT (4) /* Left shift for segment number. */ +#define SEG_MASK (0x70) /* Segment field mask. */ + +static short seg_aend[8] = {0x1F, 0x3F, 0x7F, 0xFF, + 0x1FF, 0x3FF, 0x7FF, 0xFFF}; +static short seg_uend[8] = {0x3F, 0x7F, 0xFF, 0x1FF, + 0x3FF, 0x7FF, 0xFFF, 0x1FFF}; + +/* copy from CCITT G.711 specifications */ +unsigned char _u2a[128] = { /* u- to A-law conversions */ + 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 27, 29, 31, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, + 46, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, +/* corrected: + 81, 82, 83, 84, 85, 86, 87, 88, + should be: */ + 80, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128}; + +unsigned char _a2u[128] = { /* A- to u-law conversions */ + 1, 3, 5, 7, 9, 11, 13, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 32, 33, 33, 34, 34, 35, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 48, 49, 49, + 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 64, + 65, 66, 67, 68, 69, 70, 71, 72, +/* corrected: + 73, 74, 75, 76, 77, 78, 79, 79, + should be: */ + 73, 74, 75, 76, 77, 78, 79, 80, + + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}; + +static short search(short val, short *table, short size) +{ + short i; + + for (i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + return (size); +} + +/* + * linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * linear2alaw() accepts an 16-bit integer and encodes it as A-law data. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +unsigned char linear2alaw(short pcm_val) /* 2's complement (16-bit range) */ +{ + short mask; + short seg; + unsigned char aval; + + pcm_val = pcm_val >> 3; + + if (pcm_val >= 0) { + mask = 0xD5; /* sign (7th) bit = 1 */ + } else { + mask = 0x55; /* sign bit = 0 */ + pcm_val = -pcm_val - 1; + } + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_aend, 8); + + /* Combine the sign, segment, and quantization bits. */ + + if (seg >= 8) /* out of range, return maximum value. */ + return (unsigned char) (0x7F ^ mask); + else { + aval = (unsigned char) seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm_val >> 1) & QUANT_MASK; + else + aval |= (pcm_val >> seg) & QUANT_MASK; + return (aval ^ mask); + } +} + +/* + * alaw2linear() - Convert an A-law value to 16-bit linear PCM + * + */ +short alaw2linear(unsigned char a_val) +{ + short t; + short seg; + + a_val ^= 0x55; + + t = (a_val & QUANT_MASK) << 4; + seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108; + break; + default: + t += 0x108; + t <<= seg - 1; + } + return ((a_val & SIGN_BIT) ? t : -t); +} + +#define BIAS (0x84) /* Bias for linear code. */ +#define CLIP 8159 + +/* + * linear2ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +unsigned char linear2ulaw(short pcm_val) /* 2's complement (16-bit range) */ +{ + short mask; + short seg; + unsigned char uval; + + /* Get the sign and the magnitude of the value. */ + pcm_val = pcm_val >> 2; + if (pcm_val < 0) { + pcm_val = -pcm_val; + mask = 0x7F; + } else { + mask = 0xFF; + } + if ( pcm_val > CLIP ) pcm_val = CLIP; /* clip the magnitude */ + pcm_val += (BIAS >> 2); + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_uend, 8); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + if (seg >= 8) /* out of range, return maximum value. */ + return (unsigned char) (0x7F ^ mask); + else { + uval = (unsigned char) (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF); + return (uval ^ mask); + } + +} + +/* + * ulaw2linear() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +short ulaw2linear(unsigned char u_val) +{ + short t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; + + return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + +/* A-law to u-law conversion */ +unsigned char alaw2ulaw(unsigned char aval) +{ + aval &= 0xff; + return (unsigned char) ((aval & 0x80) ? (0xFF ^ _a2u[aval ^ 0xD5]) : + (0x7F ^ _a2u[aval ^ 0x55])); +} + +/* u-law to A-law conversion */ +unsigned char ulaw2alaw(unsigned char uval) +{ + uval &= 0xff; + return (unsigned char) ((uval & 0x80) ? (0xD5 ^ (_u2a[0xFF ^ uval] - 1)) : + (unsigned char) (0x55 ^ (_u2a[0x7F ^ uval] - 1))); +} diff --git a/tinyDAV/src/codecs/g711/tdav_codec_g711.c b/tinyDAV/src/codecs/g711/tdav_codec_g711.c new file mode 100644 index 0000000..fa970e1 --- /dev/null +++ b/tinyDAV/src/codecs/g711/tdav_codec_g711.c @@ -0,0 +1,326 @@ +/* +* 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_g711.c + * @brief G.711u and G.711a (a.k.a PCMU and PCMA) codec plugins. + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/g711/tdav_codec_g711.h" + +#include "tinydav/codecs/g711/g711.h" /* algorithms */ + +#include "tsk_memory.h" +#include "tsk_debug.h" + +/* ============ G.711u Plugin interface ================= */ + +#define tdav_codec_g711u_open tsk_null +#define tdav_codec_g711u_close tsk_null +#define tdav_codec_g711u_sdp_att_get tsk_null + +static tsk_size_t tdav_codec_g711u_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + register tsk_size_t i; + register uint8_t* pout_data; + register int16_t* pin_data; + tsk_size_t out_size; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + out_size = (in_size >> 1); + + if(*out_max_size <out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + + pout_data = *out_data; + pin_data = (int16_t*)in_data; + for(i = 0; i<out_size; i++){ + pout_data[i] = linear2ulaw(pin_data[i]); + } + + return out_size; +} + +static tsk_size_t tdav_codec_g711u_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) +{ + tsk_size_t i; + tsk_size_t out_size; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + out_size = (in_size << 1); + + /* allocate new buffer */ + if(*out_max_size<out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + + for(i = 0; i<in_size; i++){ + ((short*)*out_data)[i] = ulaw2linear(((uint8_t*)in_data)[i]); + } + + return out_size; +} + +static tsk_bool_t tdav_codec_g711u_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ /* always match */ + return tsk_true; +} + + +// +// G.711u Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_g711u_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_g711u_t *g711u = self; + if(g711u){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_g711u_dtor(tsk_object_t * self) +{ + tdav_codec_g711u_t *g711u = self; + if(g711u){ + /* deinit base */ + tmedia_codec_audio_deinit(g711u); + /* deinit self */ + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_g711u_def_s = +{ + sizeof(tdav_codec_g711u_t), + tdav_codec_g711u_ctor, + tdav_codec_g711u_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_g711u_plugin_def_s = +{ + &tdav_codec_g711u_def_s, + + tmedia_audio, + tmedia_codec_id_pcmu, + "PCMU", + "G.711u codec (native)", + TMEDIA_CODEC_FORMAT_G711u, + tsk_false, + 8000, // rate + + { /* audio */ + 1, // channels + 0 // ptime @deprecated + }, + + /* video */ + {0}, + + tsk_null, // set() + tdav_codec_g711u_open, + tdav_codec_g711u_close, + tdav_codec_g711u_encode, + tdav_codec_g711u_decode, + tdav_codec_g711u_sdp_att_match, + tdav_codec_g711u_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_g711u_plugin_def_t = &tdav_codec_g711u_plugin_def_s; + + +/* ============ G.711a Plugin interface ================= */ + +#define tdav_codec_g711a_open tsk_null +#define tdav_codec_g711a_close tsk_null +#define tdav_codec_g711a_sdp_att_get tsk_null + +static tsk_size_t tdav_codec_g711a_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + register tsk_size_t i; + register uint8_t* pout_data; + register int16_t* pin_data; + tsk_size_t out_size; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + out_size = (in_size >> 1); + + if(*out_max_size < out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + + pout_data = *out_data; + pin_data = (int16_t*)in_data; + for(i = 0; i<out_size; i++){ + pout_data[i] = linear2alaw(pin_data[i]); + } + + return out_size; +} + +#if 0 +FILE* file = tsk_null; +int count = 0; +#endif +static tsk_size_t tdav_codec_g711a_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) +{ + tsk_size_t i, out_size; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + out_size = (in_size << 1); +#if 0 + if(!file && count<=1000){ + file = fopen("./g711a.pcm", "wb"); + } +#endif + /* allocate new buffer */ + if(*out_max_size < out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + + for(i = 0; i<in_size; i++){ + ((short*)*out_data)[i] = alaw2linear(((uint8_t*)in_data)[i]); + } +#if 0 + if(++count<=1000){ + fwrite(*out_data, sizeof(short), in_size, file); + } + else if(file){ + fclose(file); + file = tsk_null; + } +#endif + return out_size; +} + +static tsk_bool_t tdav_codec_g711a_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ /* always match */ + return tsk_true; +} + + +// +// G.711a Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_g711a_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_g711a_t *g711a = self; + if(g711a){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_g711a_dtor(tsk_object_t * self) +{ + tdav_codec_g711a_t *g711a = self; + if(g711a){ + /* deinit base */ + tmedia_codec_audio_deinit(g711a); + /* deinit self */ + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_g711a_def_s = +{ + sizeof(tdav_codec_g711a_t), + tdav_codec_g711a_ctor, + tdav_codec_g711a_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_g711a_plugin_def_s = +{ + &tdav_codec_g711a_def_s, + + tmedia_audio, + tmedia_codec_id_pcma, + "PCMA", + "G.711a codec (native)", + TMEDIA_CODEC_FORMAT_G711a, + tsk_false, + 8000, // rate + + { /* audio */ + 1, // channels + 0 // ptime @deprecated + }, + + /* video */ + {0}, + + tsk_null, // set() + tdav_codec_g711a_open, + tdav_codec_g711a_close, + tdav_codec_g711a_encode, + tdav_codec_g711a_decode, + tdav_codec_g711a_sdp_att_match, + tdav_codec_g711a_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_g711a_plugin_def_t = &tdav_codec_g711a_plugin_def_s; diff --git a/tinyDAV/src/codecs/g722/g722_decode.c b/tinyDAV/src/codecs/g722/g722_decode.c new file mode 100644 index 0000000..b6b7830 --- /dev/null +++ b/tinyDAV/src/codecs/g722/g722_decode.c @@ -0,0 +1,400 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * g722_decode.c - The ITU G.722 codec, decode part. + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2005 Steve Underwood + * + * Despite my general liking of the GPL, I place my own contributions + * to this code in the public domain for the benefit of all mankind - + * even the slimy ones who might try to proprietize my work and use it + * to my detriment. + * + * Based in part on a single channel G.722 codec which is: + * + * Copyright (c) CMU 1993 + * Computer Science, Speech Group + * Chengxiang Lu and Alex Hauptmann + * + * $Id: g722_decode.c,v 1.15 2006/07/07 16:37:49 steveu Exp $ + * + * Modifications for WebRtc, 2011/04/28, by tlegrand: + * -Removed usage of inttypes.h and tgmath.h + * -Changed to use WebRtc types + * -Changed __inline__ to __inline + * -Added saturation check on output + */ + +/*! \file */ + +#include <stdio.h> +#include <memory.h> +#include <stdlib.h> + +#include "tinydav/codecs/g722/g722_enc_dec.h" + +#if !defined(FALSE) +#define FALSE 0 +#endif +#if !defined(TRUE) +#define TRUE (!FALSE) +#endif + +static __inline int16_t saturate(int32_t amp) +{ + int16_t amp16; + + /* Hopefully this is optimised for the common case - not clipping */ + amp16 = (int16_t) amp; + if (amp == amp16) + return amp16; + if (amp > TDAV_INT16_MAX) + return TDAV_INT16_MAX; + return TDAV_INT16_MIN; +} +/*- End of function --------------------------------------------------------*/ + +static void block4(g722_decode_state_t *s, int band, int d); + +static void block4(g722_decode_state_t *s, int band, int d) +{ + int wd1; + int wd2; + int wd3; + int i; + + /* Block 4, RECONS */ + s->band[band].d[0] = d; + s->band[band].r[0] = saturate(s->band[band].s + d); + + /* Block 4, PARREC */ + s->band[band].p[0] = saturate(s->band[band].sz + d); + + /* Block 4, UPPOL2 */ + for (i = 0; i < 3; i++) + s->band[band].sg[i] = s->band[band].p[i] >> 15; + wd1 = saturate(s->band[band].a[1] << 2); + + wd2 = (s->band[band].sg[0] == s->band[band].sg[1]) ? -wd1 : wd1; + if (wd2 > 32767) + wd2 = 32767; + wd3 = (s->band[band].sg[0] == s->band[band].sg[2]) ? 128 : -128; + wd3 += (wd2 >> 7); + wd3 += (s->band[band].a[2]*32512) >> 15; + if (wd3 > 12288) + wd3 = 12288; + else if (wd3 < -12288) + wd3 = -12288; + s->band[band].ap[2] = wd3; + + /* Block 4, UPPOL1 */ + s->band[band].sg[0] = s->band[band].p[0] >> 15; + s->band[band].sg[1] = s->band[band].p[1] >> 15; + wd1 = (s->band[band].sg[0] == s->band[band].sg[1]) ? 192 : -192; + wd2 = (s->band[band].a[1]*32640) >> 15; + + s->band[band].ap[1] = saturate(wd1 + wd2); + wd3 = saturate(15360 - s->band[band].ap[2]); + if (s->band[band].ap[1] > wd3) + s->band[band].ap[1] = wd3; + else if (s->band[band].ap[1] < -wd3) + s->band[band].ap[1] = -wd3; + + /* Block 4, UPZERO */ + wd1 = (d == 0) ? 0 : 128; + s->band[band].sg[0] = d >> 15; + for (i = 1; i < 7; i++) + { + s->band[band].sg[i] = s->band[band].d[i] >> 15; + wd2 = (s->band[band].sg[i] == s->band[band].sg[0]) ? wd1 : -wd1; + wd3 = (s->band[band].b[i]*32640) >> 15; + s->band[band].bp[i] = saturate(wd2 + wd3); + } + + /* Block 4, DELAYA */ + for (i = 6; i > 0; i--) + { + s->band[band].d[i] = s->band[band].d[i - 1]; + s->band[band].b[i] = s->band[band].bp[i]; + } + + for (i = 2; i > 0; i--) + { + s->band[band].r[i] = s->band[band].r[i - 1]; + s->band[band].p[i] = s->band[band].p[i - 1]; + s->band[band].a[i] = s->band[band].ap[i]; + } + + /* Block 4, FILTEP */ + wd1 = saturate(s->band[band].r[1] + s->band[band].r[1]); + wd1 = (s->band[band].a[1]*wd1) >> 15; + wd2 = saturate(s->band[band].r[2] + s->band[band].r[2]); + wd2 = (s->band[band].a[2]*wd2) >> 15; + s->band[band].sp = saturate(wd1 + wd2); + + /* Block 4, FILTEZ */ + s->band[band].sz = 0; + for (i = 6; i > 0; i--) + { + wd1 = saturate(s->band[band].d[i] + s->band[band].d[i]); + s->band[band].sz += (s->band[band].b[i]*wd1) >> 15; + } + s->band[band].sz = saturate(s->band[band].sz); + + /* Block 4, PREDIC */ + s->band[band].s = saturate(s->band[band].sp + s->band[band].sz); +} +/*- End of function --------------------------------------------------------*/ + +g722_decode_state_t *g722_decode_init(g722_decode_state_t *s, int rate, int options) +{ + if (s == NULL) + { + if ((s = (g722_decode_state_t *) malloc(sizeof(*s))) == NULL) + return NULL; + } + memset(s, 0, sizeof(*s)); + if (rate == 48000) + s->bits_per_sample = 6; + else if (rate == 56000) + s->bits_per_sample = 7; + else + s->bits_per_sample = 8; + if ((options & G722_SAMPLE_RATE_8000)) + s->eight_k = TRUE; + if ((options & G722_PACKED) && s->bits_per_sample != 8) + s->packed = TRUE; + else + s->packed = FALSE; + s->band[0].det = 32; + s->band[1].det = 8; + return s; +} +/*- End of function --------------------------------------------------------*/ + +int g722_decode_release(g722_decode_state_t *s) +{ + free(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +int g722_decode(g722_decode_state_t *s, int16_t amp[], + const uint8_t g722_data[], int len) +{ + static const int wl[8] = {-60, -30, 58, 172, 334, 538, 1198, 3042 }; + static const int rl42[16] = {0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0 }; + static const int ilb[32] = + { + 2048, 2093, 2139, 2186, 2233, 2282, 2332, + 2383, 2435, 2489, 2543, 2599, 2656, 2714, + 2774, 2834, 2896, 2960, 3025, 3091, 3158, + 3228, 3298, 3371, 3444, 3520, 3597, 3676, + 3756, 3838, 3922, 4008 + }; + static const int wh[3] = {0, -214, 798}; + static const int rh2[4] = {2, 1, 2, 1}; + static const int qm2[4] = {-7408, -1616, 7408, 1616}; + static const int qm4[16] = + { + 0, -20456, -12896, -8968, + -6288, -4240, -2584, -1200, + 20456, 12896, 8968, 6288, + 4240, 2584, 1200, 0 + }; + static const int qm5[32] = + { + -280, -280, -23352, -17560, + -14120, -11664, -9752, -8184, + -6864, -5712, -4696, -3784, + -2960, -2208, -1520, -880, + 23352, 17560, 14120, 11664, + 9752, 8184, 6864, 5712, + 4696, 3784, 2960, 2208, + 1520, 880, 280, -280 + }; + static const int qm6[64] = + { + -136, -136, -136, -136, + -24808, -21904, -19008, -16704, + -14984, -13512, -12280, -11192, + -10232, -9360, -8576, -7856, + -7192, -6576, -6000, -5456, + -4944, -4464, -4008, -3576, + -3168, -2776, -2400, -2032, + -1688, -1360, -1040, -728, + 24808, 21904, 19008, 16704, + 14984, 13512, 12280, 11192, + 10232, 9360, 8576, 7856, + 7192, 6576, 6000, 5456, + 4944, 4464, 4008, 3576, + 3168, 2776, 2400, 2032, + 1688, 1360, 1040, 728, + 432, 136, -432, -136 + }; + static const int qmf_coeffs[12] = + { + 3, -11, 12, 32, -210, 951, 3876, -805, 362, -156, 53, -11, + }; + + int dlowt; + int rlow; + int ihigh; + int dhigh; + int rhigh; + int xout1; + int xout2; + int wd1; + int wd2; + int wd3; + int code; + int outlen; + int i; + int j; + + outlen = 0; + rhigh = 0; + for (j = 0; j < len; ) + { + if (s->packed) + { + /* Unpack the code bits */ + if (s->in_bits < s->bits_per_sample) + { + s->in_buffer |= (g722_data[j++] << s->in_bits); + s->in_bits += 8; + } + code = s->in_buffer & ((1 << s->bits_per_sample) - 1); + s->in_buffer >>= s->bits_per_sample; + s->in_bits -= s->bits_per_sample; + } + else + { + code = g722_data[j++]; + } + + switch (s->bits_per_sample) + { + default: + case 8: + wd1 = code & 0x3F; + ihigh = (code >> 6) & 0x03; + wd2 = qm6[wd1]; + wd1 >>= 2; + break; + case 7: + wd1 = code & 0x1F; + ihigh = (code >> 5) & 0x03; + wd2 = qm5[wd1]; + wd1 >>= 1; + break; + case 6: + wd1 = code & 0x0F; + ihigh = (code >> 4) & 0x03; + wd2 = qm4[wd1]; + break; + } + /* Block 5L, LOW BAND INVQBL */ + wd2 = (s->band[0].det*wd2) >> 15; + /* Block 5L, RECONS */ + rlow = s->band[0].s + wd2; + /* Block 6L, LIMIT */ + if (rlow > 16383) + rlow = 16383; + else if (rlow < -16384) + rlow = -16384; + + /* Block 2L, INVQAL */ + wd2 = qm4[wd1]; + dlowt = (s->band[0].det*wd2) >> 15; + + /* Block 3L, LOGSCL */ + wd2 = rl42[wd1]; + wd1 = (s->band[0].nb*127) >> 7; + wd1 += wl[wd2]; + if (wd1 < 0) + wd1 = 0; + else if (wd1 > 18432) + wd1 = 18432; + s->band[0].nb = wd1; + + /* Block 3L, SCALEL */ + wd1 = (s->band[0].nb >> 6) & 31; + wd2 = 8 - (s->band[0].nb >> 11); + wd3 = (wd2 < 0) ? (ilb[wd1] << -wd2) : (ilb[wd1] >> wd2); + s->band[0].det = wd3 << 2; + + block4(s, 0, dlowt); + + if (!s->eight_k) + { + /* Block 2H, INVQAH */ + wd2 = qm2[ihigh]; + dhigh = (s->band[1].det*wd2) >> 15; + /* Block 5H, RECONS */ + rhigh = dhigh + s->band[1].s; + /* Block 6H, LIMIT */ + if (rhigh > 16383) + rhigh = 16383; + else if (rhigh < -16384) + rhigh = -16384; + + /* Block 2H, INVQAH */ + wd2 = rh2[ihigh]; + wd1 = (s->band[1].nb*127) >> 7; + wd1 += wh[wd2]; + if (wd1 < 0) + wd1 = 0; + else if (wd1 > 22528) + wd1 = 22528; + s->band[1].nb = wd1; + + /* Block 3H, SCALEH */ + wd1 = (s->band[1].nb >> 6) & 31; + wd2 = 10 - (s->band[1].nb >> 11); + wd3 = (wd2 < 0) ? (ilb[wd1] << -wd2) : (ilb[wd1] >> wd2); + s->band[1].det = wd3 << 2; + + block4(s, 1, dhigh); + } + + if (s->itu_test_mode) + { + amp[outlen++] = (int16_t) (rlow << 1); + amp[outlen++] = (int16_t) (rhigh << 1); + } + else + { + if (s->eight_k) + { + amp[outlen++] = (int16_t) (rlow << 1); + } + else + { + /* Apply the receive QMF */ + for (i = 0; i < 22; i++) + s->x[i] = s->x[i + 2]; + s->x[22] = rlow + rhigh; + s->x[23] = rlow - rhigh; + + xout1 = 0; + xout2 = 0; + for (i = 0; i < 12; i++) + { + xout2 += s->x[2*i]*qmf_coeffs[i]; + xout1 += s->x[2*i + 1]*qmf_coeffs[11 - i]; + } + /* We shift by 12 to allow for the QMF filters (DC gain = 4096), less 1 + to allow for the 15 bit input to the G.722 algorithm. */ + /* WebRtc, tlegrand: added saturation */ + amp[outlen++] = saturate(xout1 >> 11); + amp[outlen++] = saturate(xout2 >> 11); + } + } + } + return outlen; +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/ diff --git a/tinyDAV/src/codecs/g722/g722_encode.c b/tinyDAV/src/codecs/g722/g722_encode.c new file mode 100644 index 0000000..68758eb --- /dev/null +++ b/tinyDAV/src/codecs/g722/g722_encode.c @@ -0,0 +1,426 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * g722_encode.c - The ITU G.722 codec, encode part. + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * Despite my general liking of the GPL, I place my own contributions + * to this code in the public domain for the benefit of all mankind - + * even the slimy ones who might try to proprietize my work and use it + * to my detriment. + * + * Based on a single channel 64kbps only G.722 codec which is: + * + ***** Copyright (c) CMU 1993 ***** + * Computer Science, Speech Group + * Chengxiang Lu and Alex Hauptmann + * + * $Id: g722_encode.c,v 1.14 2006/07/07 16:37:49 steveu Exp $ + * + * Modifications for WebRtc, 2011/04/28, by tlegrand: + * -Removed usage of inttypes.h and tgmath.h + * -Changed to use WebRtc types + * -Added option to run encoder bitexact with ITU-T reference implementation + */ + +#include <stdio.h> +#include <memory.h> +#include <stdlib.h> + +#include "tinydav/codecs/g722/g722_enc_dec.h" + +#if !defined(FALSE) +#define FALSE 0 +#endif +#if !defined(TRUE) +#define TRUE (!FALSE) +#endif + +static __inline int16_t saturate(int32_t amp) +{ + int16_t amp16; + + /* Hopefully this is optimised for the common case - not clipping */ + amp16 = (int16_t) amp; + if (amp == amp16) + return amp16; + if (amp > TDAV_INT16_MAX) + return TDAV_INT16_MAX; + return TDAV_INT16_MIN; +} +/*- End of function --------------------------------------------------------*/ + +static void block4(g722_encode_state_t *s, int band, int d) +{ + int wd1; + int wd2; + int wd3; + int i; + + /* Block 4, RECONS */ + s->band[band].d[0] = d; + s->band[band].r[0] = saturate(s->band[band].s + d); + + /* Block 4, PARREC */ + s->band[band].p[0] = saturate(s->band[band].sz + d); + + /* Block 4, UPPOL2 */ + for (i = 0; i < 3; i++) + s->band[band].sg[i] = s->band[band].p[i] >> 15; + wd1 = saturate(s->band[band].a[1] << 2); + + wd2 = (s->band[band].sg[0] == s->band[band].sg[1]) ? -wd1 : wd1; + if (wd2 > 32767) + wd2 = 32767; + wd3 = (wd2 >> 7) + ((s->band[band].sg[0] == s->band[band].sg[2]) ? 128 : -128); + wd3 += (s->band[band].a[2]*32512) >> 15; + if (wd3 > 12288) + wd3 = 12288; + else if (wd3 < -12288) + wd3 = -12288; + s->band[band].ap[2] = wd3; + + /* Block 4, UPPOL1 */ + s->band[band].sg[0] = s->band[band].p[0] >> 15; + s->band[band].sg[1] = s->band[band].p[1] >> 15; + wd1 = (s->band[band].sg[0] == s->band[band].sg[1]) ? 192 : -192; + wd2 = (s->band[band].a[1]*32640) >> 15; + + s->band[band].ap[1] = saturate(wd1 + wd2); + wd3 = saturate(15360 - s->band[band].ap[2]); + if (s->band[band].ap[1] > wd3) + s->band[band].ap[1] = wd3; + else if (s->band[band].ap[1] < -wd3) + s->band[band].ap[1] = -wd3; + + /* Block 4, UPZERO */ + wd1 = (d == 0) ? 0 : 128; + s->band[band].sg[0] = d >> 15; + for (i = 1; i < 7; i++) + { + s->band[band].sg[i] = s->band[band].d[i] >> 15; + wd2 = (s->band[band].sg[i] == s->band[band].sg[0]) ? wd1 : -wd1; + wd3 = (s->band[band].b[i]*32640) >> 15; + s->band[band].bp[i] = saturate(wd2 + wd3); + } + + /* Block 4, DELAYA */ + for (i = 6; i > 0; i--) + { + s->band[band].d[i] = s->band[band].d[i - 1]; + s->band[band].b[i] = s->band[band].bp[i]; + } + + for (i = 2; i > 0; i--) + { + s->band[band].r[i] = s->band[band].r[i - 1]; + s->band[band].p[i] = s->band[band].p[i - 1]; + s->band[band].a[i] = s->band[band].ap[i]; + } + + /* Block 4, FILTEP */ + wd1 = saturate(s->band[band].r[1] + s->band[band].r[1]); + wd1 = (s->band[band].a[1]*wd1) >> 15; + wd2 = saturate(s->band[band].r[2] + s->band[band].r[2]); + wd2 = (s->band[band].a[2]*wd2) >> 15; + s->band[band].sp = saturate(wd1 + wd2); + + /* Block 4, FILTEZ */ + s->band[band].sz = 0; + for (i = 6; i > 0; i--) + { + wd1 = saturate(s->band[band].d[i] + s->band[band].d[i]); + s->band[band].sz += (s->band[band].b[i]*wd1) >> 15; + } + s->band[band].sz = saturate(s->band[band].sz); + + /* Block 4, PREDIC */ + s->band[band].s = saturate(s->band[band].sp + s->band[band].sz); +} +/*- End of function --------------------------------------------------------*/ + +g722_encode_state_t *g722_encode_init(g722_encode_state_t *s, int rate, int options) +{ + if (s == NULL) + { + if ((s = (g722_encode_state_t *) malloc(sizeof(*s))) == NULL) + return NULL; + } + memset(s, 0, sizeof(*s)); + if (rate == 48000) + s->bits_per_sample = 6; + else if (rate == 56000) + s->bits_per_sample = 7; + else + s->bits_per_sample = 8; + if ((options & G722_SAMPLE_RATE_8000)) + s->eight_k = TRUE; + if ((options & G722_PACKED) && s->bits_per_sample != 8) + s->packed = TRUE; + else + s->packed = FALSE; + s->band[0].det = 32; + s->band[1].det = 8; + return s; +} +/*- End of function --------------------------------------------------------*/ + +int g722_encode_release(g722_encode_state_t *s) +{ + free(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +/* WebRtc, tlegrand: + * Only define the following if bit-exactness with reference implementation + * is needed. Will only have any effect if input signal is saturated. + */ +//#define RUN_LIKE_REFERENCE_G722 +#ifdef RUN_LIKE_REFERENCE_G722 +int16_t limitValues (int16_t rl) +{ + + int16_t yl; + + yl = (rl > 16383) ? 16383 : ((rl < -16384) ? -16384 : rl); + + return (yl); +} +#endif + +int g722_encode(g722_encode_state_t *s, uint8_t g722_data[], + const int16_t amp[], int len) +{ + static const int q6[32] = + { + 0, 35, 72, 110, 150, 190, 233, 276, + 323, 370, 422, 473, 530, 587, 650, 714, + 786, 858, 940, 1023, 1121, 1219, 1339, 1458, + 1612, 1765, 1980, 2195, 2557, 2919, 0, 0 + }; + static const int iln[32] = + { + 0, 63, 62, 31, 30, 29, 28, 27, + 26, 25, 24, 23, 22, 21, 20, 19, + 18, 17, 16, 15, 14, 13, 12, 11, + 10, 9, 8, 7, 6, 5, 4, 0 + }; + static const int ilp[32] = + { + 0, 61, 60, 59, 58, 57, 56, 55, + 54, 53, 52, 51, 50, 49, 48, 47, + 46, 45, 44, 43, 42, 41, 40, 39, + 38, 37, 36, 35, 34, 33, 32, 0 + }; + static const int wl[8] = + { + -60, -30, 58, 172, 334, 538, 1198, 3042 + }; + static const int rl42[16] = + { + 0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0 + }; + static const int ilb[32] = + { + 2048, 2093, 2139, 2186, 2233, 2282, 2332, + 2383, 2435, 2489, 2543, 2599, 2656, 2714, + 2774, 2834, 2896, 2960, 3025, 3091, 3158, + 3228, 3298, 3371, 3444, 3520, 3597, 3676, + 3756, 3838, 3922, 4008 + }; + static const int qm4[16] = + { + 0, -20456, -12896, -8968, + -6288, -4240, -2584, -1200, + 20456, 12896, 8968, 6288, + 4240, 2584, 1200, 0 + }; + static const int qm2[4] = + { + -7408, -1616, 7408, 1616 + }; + static const int qmf_coeffs[12] = + { + 3, -11, 12, 32, -210, 951, 3876, -805, 362, -156, 53, -11, + }; + static const int ihn[3] = {0, 1, 0}; + static const int ihp[3] = {0, 3, 2}; + static const int wh[3] = {0, -214, 798}; + static const int rh2[4] = {2, 1, 2, 1}; + + int dlow; + int dhigh; + int el; + int wd; + int wd1; + int ril; + int wd2; + int il4; + int ih2; + int wd3; + int eh; + int mih; + int i; + int j; + /* Low and high band PCM from the QMF */ + int xlow; + int xhigh; + int g722_bytes; + /* Even and odd tap accumulators */ + int sumeven; + int sumodd; + int ihigh; + int ilow; + int code; + + g722_bytes = 0; + xhigh = 0; + for (j = 0; j < len; ) + { + if (s->itu_test_mode) + { + xlow = + xhigh = amp[j++] >> 1; + } + else + { + if (s->eight_k) + { + /* We shift by 1 to allow for the 15 bit input to the G.722 algorithm. */ + xlow = amp[j++] >> 1; + } + else + { + /* Apply the transmit QMF */ + /* Shuffle the buffer down */ + for (i = 0; i < 22; i++) + s->x[i] = s->x[i + 2]; + s->x[22] = amp[j++]; + s->x[23] = amp[j++]; + + /* Discard every other QMF output */ + sumeven = 0; + sumodd = 0; + for (i = 0; i < 12; i++) + { + sumodd += s->x[2*i]*qmf_coeffs[i]; + sumeven += s->x[2*i + 1]*qmf_coeffs[11 - i]; + } + /* We shift by 12 to allow for the QMF filters (DC gain = 4096), plus 1 + to allow for us summing two filters, plus 1 to allow for the 15 bit + input to the G.722 algorithm. */ + xlow = (sumeven + sumodd) >> 14; + xhigh = (sumeven - sumodd) >> 14; + +#ifdef RUN_LIKE_REFERENCE_G722 + /* The following lines are only used to verify bit-exactness + * with reference implementation of G.722. Higher precision + * is achieved without limiting the values. + */ + xlow = limitValues(xlow); + xhigh = limitValues(xhigh); +#endif + } + } + /* Block 1L, SUBTRA */ + el = saturate(xlow - s->band[0].s); + + /* Block 1L, QUANTL */ + wd = (el >= 0) ? el : -(el + 1); + + for (i = 1; i < 30; i++) + { + wd1 = (q6[i]*s->band[0].det) >> 12; + if (wd < wd1) + break; + } + ilow = (el < 0) ? iln[i] : ilp[i]; + + /* Block 2L, INVQAL */ + ril = ilow >> 2; + wd2 = qm4[ril]; + dlow = (s->band[0].det*wd2) >> 15; + + /* Block 3L, LOGSCL */ + il4 = rl42[ril]; + wd = (s->band[0].nb*127) >> 7; + s->band[0].nb = wd + wl[il4]; + if (s->band[0].nb < 0) + s->band[0].nb = 0; + else if (s->band[0].nb > 18432) + s->band[0].nb = 18432; + + /* Block 3L, SCALEL */ + wd1 = (s->band[0].nb >> 6) & 31; + wd2 = 8 - (s->band[0].nb >> 11); + wd3 = (wd2 < 0) ? (ilb[wd1] << -wd2) : (ilb[wd1] >> wd2); + s->band[0].det = wd3 << 2; + + block4(s, 0, dlow); + + if (s->eight_k) + { + /* Just leave the high bits as zero */ + code = (0xC0 | ilow) >> (8 - s->bits_per_sample); + } + else + { + /* Block 1H, SUBTRA */ + eh = saturate(xhigh - s->band[1].s); + + /* Block 1H, QUANTH */ + wd = (eh >= 0) ? eh : -(eh + 1); + wd1 = (564*s->band[1].det) >> 12; + mih = (wd >= wd1) ? 2 : 1; + ihigh = (eh < 0) ? ihn[mih] : ihp[mih]; + + /* Block 2H, INVQAH */ + wd2 = qm2[ihigh]; + dhigh = (s->band[1].det*wd2) >> 15; + + /* Block 3H, LOGSCH */ + ih2 = rh2[ihigh]; + wd = (s->band[1].nb*127) >> 7; + s->band[1].nb = wd + wh[ih2]; + if (s->band[1].nb < 0) + s->band[1].nb = 0; + else if (s->band[1].nb > 22528) + s->band[1].nb = 22528; + + /* Block 3H, SCALEH */ + wd1 = (s->band[1].nb >> 6) & 31; + wd2 = 10 - (s->band[1].nb >> 11); + wd3 = (wd2 < 0) ? (ilb[wd1] << -wd2) : (ilb[wd1] >> wd2); + s->band[1].det = wd3 << 2; + + block4(s, 1, dhigh); + code = ((ihigh << 6) | ilow) >> (8 - s->bits_per_sample); + } + + if (s->packed) + { + /* Pack the code bits */ + s->out_buffer |= (code << s->out_bits); + s->out_bits += s->bits_per_sample; + if (s->out_bits >= 8) + { + g722_data[g722_bytes++] = (uint8_t) (s->out_buffer & 0xFF); + s->out_bits -= 8; + s->out_buffer >>= 8; + } + } + else + { + g722_data[g722_bytes++] = (uint8_t) code; + } + } + return g722_bytes; +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/ diff --git a/tinyDAV/src/codecs/g722/tdav_codec_g722.c b/tinyDAV/src/codecs/g722/tdav_codec_g722.c new file mode 100644 index 0000000..749fa04 --- /dev/null +++ b/tinyDAV/src/codecs/g722/tdav_codec_g722.c @@ -0,0 +1,219 @@ +/* +* Copyright (C) 2011-2015 Doubango Telecom <http://www.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_g722.c + * @brief G.722 codec plugins. + */ +#include "tinydav/codecs/g722/tdav_codec_g722.h" + +#include "tsk_memory.h" +#include "tsk_debug.h" + +typedef struct tdav_codec_g722_s +{ + TMEDIA_DECLARE_CODEC_AUDIO; + + g722_encode_state_t *enc_state; + g722_decode_state_t *dec_state; +} +tdav_codec_g722_t; + +static int tdav_codec_g722_open(tmedia_codec_t* self) +{ + tdav_codec_g722_t* g722 = (tdav_codec_g722_t*)self; + + if (!g722){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + // Initialize the decoder + if (!g722->dec_state){ + if (!(g722->dec_state = tsk_calloc(1, sizeof(g722_decode_state_t)))){ + TSK_DEBUG_ERROR("Failed to create G.722 decoder state"); + return -2; + } + // Create and/or reset the G.722 decoder + // Bitrate 64 kbps and wideband mode (2) + if (!(g722->dec_state = g722_decode_init(g722->dec_state, 64000, 2))){ + TSK_DEBUG_ERROR("g722_decode_init failed"); + return -3; + } + } + + // Initialize the encoder + if (!g722->enc_state){ + if (!(g722->enc_state = tsk_calloc(1, sizeof(g722_encode_state_t)))){ + TSK_DEBUG_ERROR("Failed to create G.722 encoder state"); + return -4; + } + // Create and/or reset the G.722 encoder + // Bitrate 64 kbps and wideband mode (2) + if (!(g722->enc_state = g722_encode_init(g722->enc_state, 64000, 2))){ + TSK_DEBUG_ERROR("g722_encode_init failed"); + return -5; + } + } + + return 0; +} + +static int tdav_codec_g722_close(tmedia_codec_t* self) +{ + tdav_codec_g722_t* g722 = (tdav_codec_g722_t*)self; + + (void)(g722); + + /* resources will be freed by the dctor() */ + + return 0; +} + +static tsk_size_t tdav_codec_g722_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tsk_size_t out_g722_size; + tdav_codec_g722_t* g722 = (tdav_codec_g722_t*)self; + + if (!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + out_g722_size = in_size >> 2; + + if (*out_max_size < out_g722_size){ + if (!(*out_data = tsk_realloc(*out_data, out_g722_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_g722_size; + } + + g722_encode(g722->enc_state, (uint8_t*)*out_data, (int16_t*)in_data, (int)in_size / sizeof(int16_t)); + + return out_g722_size; +} + +static tsk_size_t tdav_codec_g722_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) +{ + tdav_codec_g722_t* g722 = (tdav_codec_g722_t*)self; + + if (!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* allocate new buffer */ + if (*out_max_size < (in_size << 2)){ + if (!(*out_data = tsk_realloc(*out_data, in_size << 2))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = in_size << 2; + } + + g722_decode(g722->dec_state, (int16_t*)*out_data, (uint8_t*)in_data, (int)in_size); + + return (in_size << 2); +} + +static tsk_bool_t tdav_codec_g722_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + return tsk_true; +} + +static char* tdav_codec_g722_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) +{ + return tsk_null; +} + +// +// g722 Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_g722_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_g722_t *g722 = self; + if (g722){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_g722_dtor(tsk_object_t * self) +{ + tdav_codec_g722_t *g722 = self; + if (g722){ + /* deinit base */ + tmedia_codec_audio_deinit(g722); + /* deinit self */ + if (g722->enc_state){ + g722_encode_release(g722->enc_state), g722->enc_state = tsk_null; + } + if (g722->dec_state){ + g722_decode_release(g722->dec_state), g722->dec_state = tsk_null; + } + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_g722_def_s = +{ + sizeof(tdav_codec_g722_t), + tdav_codec_g722_ctor, + tdav_codec_g722_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_g722_plugin_def_s = +{ + &tdav_codec_g722_def_s, + + tmedia_audio, + tmedia_codec_id_g722, + "G722", + "g722 Codec (native)", + TMEDIA_CODEC_FORMAT_G722, + tsk_false, + 16000, + + { /* audio */ + 1, // channels + 0 // ptime @deprecated + }, + + /* video */ + { 0 }, + + tsk_null, // set() + tdav_codec_g722_open, + tdav_codec_g722_close, + tdav_codec_g722_encode, + tdav_codec_g722_decode, + tdav_codec_g722_sdp_att_match, + tdav_codec_g722_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_g722_plugin_def_t = &tdav_codec_g722_plugin_def_s; diff --git a/tinyDAV/src/codecs/g729/tdav_codec_g729.c b/tinyDAV/src/codecs/g729/tdav_codec_g729.c new file mode 100644 index 0000000..8981687 --- /dev/null +++ b/tinyDAV/src/codecs/g729/tdav_codec_g729.c @@ -0,0 +1,466 @@ +/* +* 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_g729.c + * @brief G729ab codec. + * Source from: http://www.itu.int/rec/T-REC-G.729-199610-S!AnnB/en + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + */ +#include "tinydav/codecs/g729/tdav_codec_g729.h" + +#if HAVE_G729 + +#include "g729b/dtx.h" +#include "g729b/octet.h" + +#include "tsk_string.h" +#include "tsk_params.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + + +#if defined(_MSC_VER) +# pragma comment(lib, "../thirdparties/win32/lib/g729b/g729b.a") +#endif + +int16_t bad_lsf; /* bad LSF indicator */ + +#ifndef G729_ENABLE_VAD +# define G729_ENABLE_VAD 0 // FIXME: speexJB not prepared for such feature +#endif + +static int16_t bin2int(int16_t no_of_bits, const int16_t *bitstream); +static void int2bin(int16_t value, int16_t no_of_bits, int16_t *bitstream); + +static void unpack_G729(const uint8_t bitstream[], int16_t bits[], int len); +static void unpack_SID(const uint8_t bitstream[], int16_t bits[]); + +static void pack_G729(const int16_t ituBits[], uint8_t bitstream[]); +static void pack_SID(const int16_t ituBits[], uint8_t bitstream[]); + +/* ============ G.729ab Plugin interface ================= */ + +#define tdav_codec_g729ab_set tsk_null + +static int tdav_codec_g729ab_open(tmedia_codec_t* self) +{ + tdav_codec_g729ab_t* g729a = (tdav_codec_g729ab_t*)self; + + // Initialize the decoder + bad_lsf = 0; + g729a->decoder.synth = (g729a->decoder.synth_buf + M); + + Init_Decod_ld8a(); + Init_Post_Filter(); + Init_Post_Process(); + /* for G.729B */ + Init_Dec_cng(); + + // Initialize the encoder + Init_Pre_Process(); + Init_Coder_ld8a(); + Set_zero(g729a->encoder.prm, PRM_SIZE + 1); + /* for G.729B */ + Init_Cod_cng(); + + + return 0; +} + +static int tdav_codec_g729ab_close(tmedia_codec_t* self) +{ + tdav_codec_g729ab_t* g729a = (tdav_codec_g729ab_t*)self; + + (void)(g729a); + + /* resources will be freed by the dctor() */ + + return 0; +} + +static tsk_size_t tdav_codec_g729ab_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tsk_size_t ex_size, out_size = 0; + tdav_codec_g729ab_t* g729a = (tdav_codec_g729ab_t*)self; + int i, frame_count = (in_size / 160); + + + if(!self || !in_data || !in_size || !out_data || (in_size % 160)){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + ex_size = (frame_count * 10); + + // allocate new buffer if needed + if(*out_max_size <ex_size){ + if(!(*out_data = tsk_realloc(*out_data, ex_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = ex_size; + } + + for(i=0; i<frame_count; i++){ + extern int16_t *new_speech; + + if(g729a->encoder.frame == 32767){ + g729a->encoder.frame = 256; + } + else{ + g729a->encoder.frame++; + } + + memcpy(new_speech, &((uint8_t*)in_data)[i*L_FRAME*sizeof(int16_t)], sizeof(int16_t)*L_FRAME); + + Pre_Process(new_speech, L_FRAME); + Coder_ld8a(g729a->encoder.prm, g729a->encoder.frame, g729a->encoder.vad_enable); + prm2bits_ld8k(g729a->encoder.prm, g729a->encoder.serial); + + if(g729a->encoder.serial[1] == RATE_8000){ + pack_G729(&g729a->encoder.serial[2], &((uint8_t*)(*out_data))[out_size]); + out_size += 10; + } + else if(g729a->encoder.serial[1] == RATE_SID_OCTET){ + pack_SID(&g729a->encoder.serial[2], &((uint8_t*)(*out_data))[out_size]); + out_size += 2; + } + else{ // RATE_0 + //TSK_DEBUG_INFO("G729_RATE_0 - Not transmitted"); + if (!g729a->encoder.vad_enable) { + // silence + memset(&((uint8_t*)(*out_data))[out_size], 0, 10); + out_size += 10; + } + } + } + + return out_size; +} + +static tsk_size_t tdav_codec_g729ab_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) +{ + tsk_size_t out_size = 0; + int i, frame_count; + const uint8_t* data_start = (const uint8_t*)in_data; + const uint8_t* data_end; + tdav_codec_g729ab_t* g729a = (tdav_codec_g729ab_t*)self; + + if(!self || !in_data || !in_size || !out_data || ((in_size % 10) && (in_size % 10 != 2))){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + data_end = (data_start + in_size); + + frame_count = (in_size/10) + ((in_size % 10) ? 1 : 0); + + out_size = 160*frame_count; + + /* allocate new buffer if needed */ + if(*out_max_size <out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + + for(i=0; i<frame_count; i++){ + memset(g729a->decoder.synth_buf, 0, M); + g729a->decoder.synth = g729a->decoder.synth_buf + M; + + if((data_end - data_start) == 2){ + unpack_SID(data_start, g729a->decoder.serial); + data_start += 2; + } + else{ + unpack_G729(data_start, g729a->decoder.serial, 10); + data_start += 10; + } + + bits2prm_ld8k(&g729a->decoder.serial[1], g729a->decoder.parm); + + /* This part was modified for version V1.3 */ + /* for speech and SID frames, the hardware detects frame erasures + by checking if all bits are set to zero */ + /* for untransmitted frames, the hardware detects frame erasures + by testing serial[0] */ + + g729a->decoder.parm[0] = 0; /* No frame erasure */ + if(g729a->decoder.serial[1] != 0) { + int j; + for (j=0; j < g729a->decoder.serial[1]; j++){ + if (g729a->decoder.serial[j+2] == 0){ + g729a->decoder.parm[0] = 1; /* frame erased */ + break; + } + } + } + else if(g729a->decoder.serial[0] != SYNC_WORD){ + g729a->decoder.parm[0] = 1; + } + if(g729a->decoder.parm[1] == 1) { + /* check parity and put 1 in parm[5] if parity error */ + g729a->decoder.parm[5] = Check_Parity_Pitch(g729a->decoder.parm[4], g729a->decoder.parm[5]); + } + + Decod_ld8a(g729a->decoder.parm, g729a->decoder.synth, g729a->decoder.Az_dec, g729a->decoder.T2, &g729a->decoder.Vad); + Post_Filter(g729a->decoder.synth, g729a->decoder.Az_dec, g729a->decoder.T2, g729a->decoder.Vad); /* Post-filter */ + Post_Process(g729a->decoder.synth, L_FRAME); + + memcpy(&((uint8_t*)*out_data)[160*i], g729a->decoder.synth, 160); + } + + + return out_size; +} + +static tsk_bool_t tdav_codec_g729ab_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + tdav_codec_g729ab_t* g729a = (tdav_codec_g729ab_t*)codec; + + if(tsk_striequals(att_name, "fmtp")){ + tsk_params_L_t* params = tsk_null; + const char* val_str; + if((params = tsk_params_fromstring(att_value, ";", tsk_true))){ + if((val_str = tsk_params_get_param_value(params, "annexb"))){ + g729a->encoder.vad_enable &= tsk_strequals(val_str, "yes") ? 1 : 0; + } + TSK_OBJECT_SAFE_FREE(params); + } + } + return tsk_true; +} + +static char* tdav_codec_g729ab_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) +{ + tdav_codec_g729ab_t* g729a = (tdav_codec_g729ab_t*)codec; + + if(tsk_striequals(att_name, "fmtp")){ + if(g729a->encoder.vad_enable){ + return tsk_strdup("annexb=yes"); + } + else{ + return tsk_strdup("annexb=no"); + } + } + return tsk_null; +} + + + + + + +/* ============ Internal functions ================= */ + + +/** +* Converts from bitstream (ITU bits) to int16_t value +* @param no_of_bits number of bits to read +* @param bitstream array containing bits +* @retval decimal value of bit pattern +*/ +static int16_t bin2int(int16_t no_of_bits, const int16_t *bitstream) +{ + int16_t value, i; + int16_t bit; + + value = 0; + for(i = 0; i < no_of_bits; i++){ + value <<= 1; + bit = *bitstream++; + if (bit == BIT_1){ + value += 1; + } + } + return(value); +} + +/*---------------------------------------------------------------------------- + * int2bin convert integer to binary and write the bits bitstream array + *---------------------------------------------------------------------------- + */ + +/** +* Writes int16_t value to bitstream +* @param value decimal value to write +* @param no_of_bits number of bits from value to write +* @param bitstream pointer to the destination stream (ITU bits) +*/ +static void int2bin(int16_t value, int16_t no_of_bits, int16_t *bitstream) +{ + int16_t *pt_bitstream; + int16_t i, bit; + + pt_bitstream = bitstream + no_of_bits; + + for (i = 0; i < no_of_bits; i++){ + bit = value & (int16_t)0x0001; /* get lsb */ + if (bit == 0){ + *--pt_bitstream = BIT_0; + } + else{ + *--pt_bitstream = BIT_1; + } + value >>= 1; + } +} + +/** +* UnPack RTP bitstream as unpacked ITU stream +* @param bitstream RTP bitstream to unpack +* @param bits ITU bitstream used as destination (0 - BIT_0, 1 - BIT_1) +* @param len length of the RTP bitstream +*/ +static void unpack_G729(const uint8_t bitstream[], int16_t bits[], int len) +{ + int16_t i; + *bits++ = SYNC_WORD; /* bit[0], at receiver this bits indicates BFI */ + switch(len){ + case 10: + *bits++ = SIZE_WORD; + break; + case 8: // RATE_6400 + case 15: //RATE_11800 + default: + TSK_DEBUG_ERROR("%d is an invalid lenght value", len); + return; + } + + for(i=0; i<len; i++){ + int2bin(bitstream[i], 8, &bits[i*8]); + } +} + +/** +* UnPack RTP bitstream containing SID frame as unpacked ITU stream +* @param bitstream RTP bitstream to unpack +* @param bits ITU bitstream used as destination (0 - BIT_0, 1 - BIT_1) +*/ +static void unpack_SID(const uint8_t bitstream[], int16_t bits[]) +{ + *bits++ = SYNC_WORD; + *bits++ = RATE_SID_OCTET; + int2bin((int16_t)bitstream[0], 8, &bits[0]); + int2bin((int16_t)bitstream[1], 8, &bits[8]); +} + +/** +* Pack ITU bits into RTP stream +* @param ituBits ITU stream to pack (80 shorts) +* @param bitstream RTP bitstream (80 bits, 5 shorts, 10 bytes) +*/ +static void pack_G729(const int16_t ituBits[], uint8_t bitstream[]) +{ + int16_t word16, i; + for(i=0; i<5; i++){ + word16 = bin2int(16, (int16_t*)&ituBits[i*16]); + bitstream[i*2] = word16>>8, bitstream[(i*2)+1] = (word16 & 0xFF); + } +} + +/** +* Pack ITU bits containing SID frame as RTP stream +* @param ituBits ITU stream to pack +* @param bitstream RTP bitstream (15 bits, 1 short, 2 bytes) +*/ +static void pack_SID(const int16_t ituBits[], uint8_t bitstream[]) +{ + int16_t word16 = bin2int(16, ituBits); + bitstream[0] = word16>>8, bitstream[1] = (word16 & 0xFF); +} + + +// +// g729ab Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_g729ab_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_g729ab_t *g729a = self; + if(g729a){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + g729a->encoder.vad_enable = G729_ENABLE_VAD; // AnnexB + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_g729ab_dtor(tsk_object_t * self) +{ + tdav_codec_g729ab_t *g729a = self; + if(g729a){ + /* deinit base */ + tmedia_codec_audio_deinit(g729a); + /* deinit self */ + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_g729ab_def_s = +{ + sizeof(tdav_codec_g729ab_t), + tdav_codec_g729ab_ctor, + tdav_codec_g729ab_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_g729ab_plugin_def_s = +{ + &tdav_codec_g729ab_def_s, + + tmedia_audio, + tmedia_codec_id_g729ab, + "g729", + "g729ab Codec (libg729)", + TMEDIA_CODEC_FORMAT_G729, + tsk_false, + 8000, // rate + + { /* audio */ + 1, // channels + 0 // ptime @deprecated + }, + + /* video */ + {0}, + + tdav_codec_g729ab_set, + tdav_codec_g729ab_open, + tdav_codec_g729ab_close, + tdav_codec_g729ab_encode, + tdav_codec_g729ab_decode, + tdav_codec_g729ab_sdp_att_match, + tdav_codec_g729ab_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_g729ab_plugin_def_t = &tdav_codec_g729ab_plugin_def_s; + +#endif /* HAVE_G729 */ diff --git a/tinyDAV/src/codecs/gsm/tdav_codec_gsm.c b/tinyDAV/src/codecs/gsm/tdav_codec_gsm.c new file mode 100644 index 0000000..8b5f1bc --- /dev/null +++ b/tinyDAV/src/codecs/gsm/tdav_codec_gsm.c @@ -0,0 +1,209 @@ +/* +* 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_gsm.c + * @brief GSM Full Rate Codec (Based on libgsm) + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/gsm/tdav_codec_gsm.h" + +#if HAVE_LIBGSM + +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#define TDAV_GSM_FRAME_SIZE 33 + +/* ============ GSM Plugin interface ================= */ + +#define tdav_codec_gsm_sdp_att_get tsk_null + +int tdav_codec_gsm_open(tmedia_codec_t* self) +{ + tdav_codec_gsm_t* gsm = (tdav_codec_gsm_t*)self; + + if(!gsm->encoder && !(gsm->encoder = gsm_create())){ + TSK_DEBUG_ERROR("Failed to create GSM encoder"); + return -2; + } + if(!gsm->decoder && !(gsm->decoder = gsm_create())){ + TSK_DEBUG_ERROR("Failed to create GSM decoder"); + return -3; + } + + return 0; +} + +int tdav_codec_gsm_close(tmedia_codec_t* self) +{ + tdav_codec_gsm_t* gsm = (tdav_codec_gsm_t*)self; + + if(gsm->encoder){ + gsm_destroy(gsm->encoder); + gsm->encoder = tsk_null; + } + if(gsm->decoder){ + gsm_destroy(gsm->decoder); + gsm->decoder = tsk_null; + } + + return 0; +} + +tsk_size_t tdav_codec_gsm_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tsk_size_t out_size; + tdav_codec_gsm_t* gsm = (tdav_codec_gsm_t*)self; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + out_size = ((in_size / (TMEDIA_CODEC_PCM_FRAME_SIZE_AUDIO_ENCODING(self) * sizeof(short))) * TDAV_GSM_FRAME_SIZE); + + /* allocate new buffer if needed */ + if(*out_max_size <out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + + gsm_encode(gsm->encoder, (gsm_signal*)in_data, (gsm_byte*)*out_data); + + return out_size; +} + +tsk_size_t tdav_codec_gsm_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) +{ + tsk_size_t out_size; + int ret; + tdav_codec_gsm_t* gsm = (tdav_codec_gsm_t*)self; + + if(!self || !in_data || !in_size || !out_data || (in_size % TDAV_GSM_FRAME_SIZE)){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + out_size = (in_size / TDAV_GSM_FRAME_SIZE) * (TMEDIA_CODEC_PCM_FRAME_SIZE_AUDIO_DECODING(self) * sizeof(short)); + + /* allocate new buffer if needed */ + if(*out_max_size <out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + + ret = gsm_decode(gsm->decoder, (gsm_byte*)in_data, (gsm_signal*)*out_data); + + return out_size; +} + +tsk_bool_t tdav_codec_gsm_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ /* always match */ + return tsk_true; +} + + +// +// GSM Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_gsm_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_gsm_t *gsm = self; + if(gsm){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_gsm_dtor(tsk_object_t * self) +{ + tdav_codec_gsm_t *gsm = self; + if(gsm){ + /* deinit base */ + tmedia_codec_audio_deinit(gsm); + /* deinit self */ + if(gsm->encoder){ + gsm_destroy(gsm->encoder); + } + if(gsm->decoder){ + gsm_destroy(gsm->decoder); + } + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_gsm_def_s = +{ + sizeof(tdav_codec_gsm_t), + tdav_codec_gsm_ctor, + tdav_codec_gsm_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_gsm_plugin_def_s = +{ + &tdav_codec_gsm_def_s, + + tmedia_audio, + tmedia_codec_id_gsm, + "GSM", + "GSM Full Rate (libgsm)", + TMEDIA_CODEC_FORMAT_GSM, + tsk_false, + 8000, // rate + + { /* audio */ + 1, // channels + 0 // ptime @deprecated + }, + + /* video */ + {0}, + + tsk_null, // set() + tdav_codec_gsm_open, + tdav_codec_gsm_close, + tdav_codec_gsm_encode, + tdav_codec_gsm_decode, + tdav_codec_gsm_sdp_att_match, + tdav_codec_gsm_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_gsm_plugin_def_t = &tdav_codec_gsm_plugin_def_s; + + +#endif /* HAVE_LIBGSM */ diff --git a/tinyDAV/src/codecs/h261/tdav_codec_h261.c b/tinyDAV/src/codecs/h261/tdav_codec_h261.c new file mode 100644 index 0000000..27aaab7 --- /dev/null +++ b/tinyDAV/src/codecs/h261/tdav_codec_h261.c @@ -0,0 +1,536 @@ +/* +* 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_h261.c + * @brief H.261 codec plugin. + * RTP payloader follows RFC 4587 + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/h261/tdav_codec_h261.h" + +#if HAVE_FFMPEG + +#include "tinydav/video/tdav_converter_video.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tnet_endianness.h" + +#include "tsk_string.h" +#include "tsk_time.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#define RTP_PAYLOAD_SIZE 700 +#define H261_HEADER_SIZE 4 + +static void *run(void* self); +static void tdav_codec_h261_rtp_callback(tdav_codec_h261_t *self, const void *data, tsk_size_t size, tsk_bool_t marker); +static void tdav_codec_h261_encap(const tdav_codec_h261_t* h261, const uint8_t* pdata, tsk_size_t size); + +/* ============ H.261 Plugin interface ================= */ + +// +// H.261 object definition +// +int tdav_codec_h261_open(tmedia_codec_t* self) +{ + int ret; + int size; + + tdav_codec_h261_t* h261 = (tdav_codec_h261_t*)self; + + if(!h261){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + // + // Encoder + // + if(!(h261->encoder.codec = avcodec_find_encoder(CODEC_ID_H261))){ + TSK_DEBUG_ERROR("Failed to find H.261 encoder"); + return -2; + } + h261->encoder.context = avcodec_alloc_context(); + avcodec_get_context_defaults(h261->encoder.context); + + h261->encoder.context->pix_fmt = PIX_FMT_YUV420P; + h261->encoder.context->time_base.num = 1; + h261->encoder.context->time_base.den = TMEDIA_CODEC_VIDEO(h261)->out.fps; + h261->encoder.context->width = TMEDIA_CODEC_VIDEO(h261)->out.width; + h261->encoder.context->height = TMEDIA_CODEC_VIDEO(h261)->out.height; + + /*h261->encoder.context->mb_qmin =*/ h261->encoder.context->qmin = 4; + /*h261->encoder.context->mb_qmax =*/ h261->encoder.context->qmax = 31; + h261->encoder.context->mb_decision = FF_MB_DECISION_SIMPLE; + + h261->encoder.context->thread_count = 1; + h261->encoder.context->rtp_payload_size = RTP_PAYLOAD_SIZE; + h261->encoder.context->opaque = tsk_null; + h261->encoder.context->bit_rate = (float) (500000) * 0.80f; + h261->encoder.context->bit_rate_tolerance = (int) (500000 * 0.20f); + h261->encoder.context->gop_size = TMEDIA_CODEC_VIDEO(h261)->out.fps*4; /* each 4 seconds */ + + // Picture (YUV 420) + if(!(h261->encoder.picture = avcodec_alloc_frame())){ + TSK_DEBUG_ERROR("Failed to create encoder picture"); + return -2; + } + avcodec_get_frame_defaults(h261->encoder.picture); + //if((ret = avpicture_alloc((AVPicture*)h261->encoder.picture, PIX_FMT_YUV420P, h261->encoder.context->width, h261->encoder.context->height))){ + // TSK_DEBUG_ERROR("Failed to allocate encoder picture"); + // return ret; + //} + + size = avpicture_get_size(PIX_FMT_YUV420P, h261->encoder.context->width, h261->encoder.context->height); + if(!(h261->encoder.buffer = tsk_calloc(size, sizeof(uint8_t)))){ + TSK_DEBUG_ERROR("Failed to allocate encoder buffer"); + return -2; + } + + // Open encoder + if((ret = avcodec_open(h261->encoder.context, h261->encoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open H.261 encoder"); + return ret; + } + + // + // Decoder + // + if(!(h261->decoder.codec = avcodec_find_decoder(CODEC_ID_H261))){ + TSK_DEBUG_ERROR("Failed to find H.261 decoder"); + } + h261->decoder.context = avcodec_alloc_context(); + avcodec_get_context_defaults(h261->decoder.context); + + h261->decoder.context->pix_fmt = PIX_FMT_YUV420P; + h261->decoder.context->width = TMEDIA_CODEC_VIDEO(h261)->in.width; + h261->decoder.context->height = TMEDIA_CODEC_VIDEO(h261)->in.height; + + // Picture (YUV 420) + if(!(h261->decoder.picture = avcodec_alloc_frame())){ + TSK_DEBUG_ERROR("Failed to create decoder picture"); + return -2; + } + avcodec_get_frame_defaults(h261->decoder.picture); + + size = avpicture_get_size(PIX_FMT_YUV420P, h261->decoder.context->width, h261->decoder.context->height); + if(!(h261->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(h261->decoder.context, h261->decoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open H.261 decoder"); + return ret; + } + + return 0; +} + +int tdav_codec_h261_close(tmedia_codec_t* self) +{ + tdav_codec_h261_t* h261 = (tdav_codec_h261_t*)self; + + if(!h261){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is opened */ + + // + // Encoder + // + if(h261->encoder.context){ + avcodec_close(h261->encoder.context); + av_free(h261->encoder.context); + h261->encoder.context = tsk_null; + } + if(h261->encoder.picture){ + av_free(h261->encoder.picture); + } + if(h261->encoder.buffer){ + TSK_FREE(h261->encoder.buffer); + } + + // + // Decoder + // + if(h261->decoder.context){ + avcodec_close(h261->decoder.context); + av_free(h261->decoder.context); + h261->decoder.context = tsk_null; + } + if(h261->decoder.picture){ + av_free(h261->decoder.picture); + h261->decoder.picture = tsk_null; + } + if(h261->decoder.accumulator){ + TSK_FREE(h261->decoder.accumulator); + h261->decoder.accumulator_pos = 0; + } + + return 0; +} + +tsk_size_t tdav_codec_h261_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_h261_t* h261 = (tdav_codec_h261_t*)self; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + // delete old buffer + if(*out_data){ + TSK_FREE(*out_data); + } + + // wrap yuv420 buffer + size = avpicture_fill((AVPicture *)h261->encoder.picture, (uint8_t*)in_data, PIX_FMT_YUV420P, h261->encoder.context->width, h261->encoder.context->height); + if(size != in_size){ + /* guard */ + TSK_DEBUG_ERROR("Invalid size"); + return 0; + } + + // Encode data + h261->encoder.picture->pts = AV_NOPTS_VALUE; + //h261->encoder.picture->pict_type = FF_I_TYPE; + ret = avcodec_encode_video(h261->encoder.context, h261->encoder.buffer, size, h261->encoder.picture); + if(ret > 0){ + tdav_codec_h261_encap(h261, h261->encoder.buffer, (tsk_size_t)ret); + } + + return 0; +} + +tsk_size_t tdav_codec_h261_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) +{ + uint8_t sbit, ebit; + const uint8_t* pdata = in_data; + const uint8_t* pay_ptr; + tsk_size_t pay_size; + tsk_size_t xsize, retsize = 0; + int got_picture_ptr; + int ret; + + tdav_codec_h261_t* h261 = (tdav_codec_h261_t*)self; + const trtp_rtp_header_t* rtp_hdr = proto_hdr; + + if(!self || !in_data || !in_size || !out_data || !h261->decoder.context){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* RFC 4587 + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |SBIT |EBIT |I|V| GOBN | MBAP | QUANT | HMVD | VMVD | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + sbit = *pdata >> 5; + ebit = (*pdata >> 2) & 0x07; + + /* Check size */ + if(in_size < H261_HEADER_SIZE){ + TSK_DEBUG_ERROR("Too short"); + return 0; + } + + pay_ptr = (pdata + H261_HEADER_SIZE); + pay_size = (in_size - H261_HEADER_SIZE); + xsize = avpicture_get_size(h261->decoder.context->pix_fmt, h261->decoder.context->width, h261->decoder.context->height); + + /* Packet lost? */ + if(h261->decoder.last_seq != (rtp_hdr->seq_num - 1) && h261->decoder.last_seq){ + TSK_DEBUG_INFO("Packet lost"); + } + h261->decoder.last_seq = rtp_hdr->seq_num; + + if((int)(h261->decoder.accumulator_pos + pay_size) <= xsize){ + + if((h261->decoder.ebit + sbit) == 8){ /* Perfect one Byte to clean up */ + if(h261->decoder.accumulator_pos){ + ((uint8_t*)h261->decoder.accumulator)[h261->decoder.accumulator_pos-1] = + (((uint8_t*)h261->decoder.accumulator)[h261->decoder.accumulator_pos-1] & (0xFF << h261->decoder.ebit)) | + (*pay_ptr << sbit); + } + pay_ptr++, pay_size--; + } + h261->decoder.ebit = ebit; + + memcpy(&((uint8_t*)h261->decoder.accumulator)[h261->decoder.accumulator_pos], pay_ptr, pay_size); + h261->decoder.accumulator_pos += pay_size; + } + else{ + TSK_DEBUG_WARN("Buffer overflow"); + h261->decoder.accumulator_pos = 0; + return 0; + } + + if(rtp_hdr->marker){ + AVPacket packet; + /* allocate destination buffer */ + if(*out_max_size <xsize){ + if(!(*out_data = tsk_realloc(*out_data, xsize))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + h261->decoder.accumulator_pos = 0; + return 0; + } + *out_max_size = xsize; + } + + /* decode the picture */ + av_init_packet(&packet); + packet.size = (int)h261->decoder.accumulator_pos; + packet.data = h261->decoder.accumulator; + ret = avcodec_decode_video2(h261->decoder.context, h261->decoder.picture, &got_picture_ptr, &packet); + + if(ret <0 || !got_picture_ptr){ + TSK_DEBUG_WARN("Failed to decode the buffer"); + } + else{ + retsize = xsize; + TMEDIA_CODEC_VIDEO(h261)->in.width = h261->decoder.context->width; + TMEDIA_CODEC_VIDEO(h261)->in.height = h261->decoder.context->height; + /* copy picture into a linear buffer */ + avpicture_layout((AVPicture *)h261->decoder.picture, h261->decoder.context->pix_fmt, (int)h261->decoder.context->width, (int)h261->decoder.context->height, + *out_data, (int)retsize); + } + /* in all cases: reset accumulator */ + h261->decoder.accumulator_pos = 0; + } + + return retsize; +} + +tsk_bool_t tdav_codec_h261_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + int ret; + unsigned maxbr, fps, width, height; + tmedia_codec_video_t* h261 = (tmedia_codec_video_t*)codec; + + if(tsk_striequals(att_value, "fmtp")){ + if(!(ret = tmedia_codec_parse_fmtp(att_value, &maxbr, &fps, &width, &height))){ + h261->in.max_br = h261->out.max_br = maxbr * 1000; + h261->in.fps = h261->out.fps = fps; + h261->in.width = h261->out.width = width; + h261->in.height = h261->out.height = height; + return tsk_true; + } + else{ + TSK_DEBUG_WARN("Failed to match fmtp [%s]", att_value); + } + } + return tsk_false; +} + +char* tdav_codec_h261_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ +#if 0 + return tsk_strdup("CIF=2/MaxBR=3840;QCIF=2/MaxBR=1920"); +#else + return tsk_strdup("QCIF=2"); +#endif +} + +/* constructor */ +static tsk_object_t* tdav_codec_h261_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h261_t *h261 = self; + if(h261){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h261_dtor(tsk_object_t * self) +{ + tdav_codec_h261_t *h261 = self; + if(h261){ + /* deinit base */ + tmedia_codec_video_deinit(h261); // will call close() + /* deinit self */ + TSK_FREE(h261->rtp.ptr); + h261->rtp.size = 0; + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h261_def_s = +{ + sizeof(tdav_codec_h261_t), + tdav_codec_h261_ctor, + tdav_codec_h261_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h261_plugin_def_s = +{ + &tdav_codec_h261_def_s, + + tmedia_video, + tmedia_codec_id_h261, + "H261", + "H261 codec (FFmpeg)", + TMEDIA_CODEC_FORMAT_H261, + tsk_false, + 90000, // rate + + /* audio */ + { 0 }, + + /* video */ + {176, 144, 15}, + + tsk_null, // set() + tdav_codec_h261_open, + tdav_codec_h261_close, + tdav_codec_h261_encode, + tdav_codec_h261_decode, + tdav_codec_h261_sdp_att_match, + tdav_codec_h261_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h261_plugin_def_t = &tdav_codec_h261_plugin_def_s; + + + + + + + + + + + + + + + + + + + +/* ============ Callbacks ================= */ + +static void tdav_codec_h261_encap(const tdav_codec_h261_t* h261, const uint8_t* pdata, tsk_size_t size) +{ + uint32_t i, last_index = 0; + + if(size < RTP_PAYLOAD_SIZE){ + goto last; + } + + for(i = 4; i<(size - 4); i++){ + if(pdata[i] == 0x00 && pdata[i+1] == 0x00 && pdata[i+2]>=0x80){ /* PSC or (GBSC) found */ + if((i - last_index) >= RTP_PAYLOAD_SIZE){ + tdav_codec_h261_rtp_callback((tdav_codec_h261_t*)h261, pdata+last_index, + (i - last_index), (last_index == size)); + } + last_index = i; + } + } +last: + if(last_index < size - 3/*PSC/GBSC size*/){ + tdav_codec_h261_rtp_callback((tdav_codec_h261_t*)h261, pdata + last_index, + (size - last_index), tsk_true); + } +} + +//static void *run(void* self) +//{ +// uint32_t i, last_index; +// tsk_list_item_t *curr; +// +// const uint8_t* pdata; +// tsk_size_t size; +// +// const tdav_codec_h261_t* h261 = ((tdav_runnable_video_t*)self)->userdata; +// +// TSK_DEBUG_INFO("H261 thread === START"); +// +// TSK_RUNNABLE_RUN_BEGIN(self); +// +// if((curr = TSK_RUNNABLE_POP_FIRST(self))){ +// /* 4 is sizeof(uint32_t) */ +// pdata = ((const tsk_buffer_t*)curr->data)->data; +// size = ((const tsk_buffer_t*)curr->data)->size; +// last_index = 0; +// +// if(size < RTP_PAYLOAD_SIZE){ +// goto last; +// } +// +// for(i = 4; i<(size - 4); i++){ +// if(pdata[i] == 0x00 && pdata[i+1] == 0x00 && pdata[i+2]>=0x80){ /* PSC or (GBSC) found */ +// if((i - last_index) >= RTP_PAYLOAD_SIZE){ +// tdav_codec_h261_rtp_callback((tdav_codec_h261_t*)h261, pdata+last_index, +// (i - last_index), (last_index == size)); +// } +// last_index = i; +// } +// } +//last: +// if(last_index < size - 3/*PSC/GBSC size*/){ +// tdav_codec_h261_rtp_callback((tdav_codec_h261_t*)h261, pdata + last_index, +// (size - last_index), tsk_true); +// } +// +// tsk_object_unref(curr); +// } +// +// TSK_RUNNABLE_RUN_END(self); +// +// TSK_DEBUG_INFO("H261 thread === STOP"); +// +// return tsk_null; +//} + +static void tdav_codec_h261_rtp_callback(tdav_codec_h261_t *self, const void *data, tsk_size_t size, tsk_bool_t marker) +{ + +} + +tsk_bool_t tdav_codec_ffmpeg_h261_is_supported() +{ + return /*(avcodec_find_encoder(CODEC_ID_H261) && avcodec_find_decoder(CODEC_ID_H261))*/tsk_false /* @deprecated */; +} + + +#endif /* HAVE_FFMPEG */
\ No newline at end of file diff --git a/tinyDAV/src/codecs/h263/tdav_codec_h263.c b/tinyDAV/src/codecs/h263/tdav_codec_h263.c new file mode 100644 index 0000000..ed5d77f --- /dev/null +++ b/tinyDAV/src/codecs/h263/tdav_codec_h263.c @@ -0,0 +1,1373 @@ +/* +* 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_h263.c + * @brief H.263-1996 and H.263-1998 codec plugins. + * RTP payloader follows RFC 4629 for H263+ and RFC 2190 for H263. + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/h263/tdav_codec_h263.h" + +#if HAVE_FFMPEG + +#include "tinydav/video/tdav_converter_video.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tnet_endianness.h" + +#include "tinymedia/tmedia_params.h" +#include "tinymedia/tmedia_defaults.h" + +#include "tsk_string.h" +#include "tsk_time.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#include <libavcodec/avcodec.h> + +#define TDAV_H263_GOP_SIZE_IN_SECONDS 25 +#define RTP_PAYLOAD_SIZE 750 + +#define H263P_HEADER_SIZE 2 +#define H263_HEADER_MODE_A_SIZE 4 +#define H263_HEADER_MODE_B_SIZE 8 +#define H263_HEADER_MODE_C_SIZE 12 + +#define tdav_codec_h263p_set tdav_codec_h263_set +#define tdav_codec_h263p_open tdav_codec_h263_open +#define tdav_codec_h263p_close tdav_codec_h263_close +#define tdav_codec_h263p_encode tdav_codec_h263_encode +#define tdav_codec_h263p_sdp_att_match tdav_codec_h263_sdp_att_match +#define tdav_codec_h263p_sdp_att_get tdav_codec_h263_sdp_att_get + +#define tdav_codec_h263pp_set tdav_codec_h263_set +#define tdav_codec_h263pp_open tdav_codec_h263_open +#define tdav_codec_h263pp_close tdav_codec_h263_close +#define tdav_codec_h263pp_encode tdav_codec_h263_encode +#define tdav_codec_h263pp_decode tdav_codec_h263_decode +#define tdav_codec_h263pp_sdp_att_match tdav_codec_h263_sdp_att_match +#define tdav_codec_h263pp_sdp_att_get tdav_codec_h263_sdp_att_get + +#define TDAV_CODEC_H263(self) ((tdav_codec_h263_t*)(self)) + +typedef enum tdav_codec_h263_type_e +{ + tdav_codec_h263_1996, + tdav_codec_h263_1998, + tdav_codec_h263_2000, +} +tdav_codec_h263_type_t; + +/** H.263-1996 codec */ +typedef struct tdav_codec_h263_s +{ + TMEDIA_DECLARE_CODEC_VIDEO; + + tdav_codec_h263_type_t type; + + struct{ + uint8_t* ptr; + tsk_size_t size; + } rtp; + + // Encoder + struct{ + AVCodec* codec; + AVCodecContext* context; + AVFrame* picture; + void* buffer; + tsk_bool_t force_idr; + int32_t quality; // [1-31] + int32_t max_bw_kpbs; + } encoder; + + // decoder + struct{ + AVCodec* codec; + AVCodecContext* context; + AVFrame* picture; + + void* accumulator; + uint8_t ebit; + tsk_size_t accumulator_pos; + uint16_t last_seq; + } decoder; +} +tdav_codec_h263_t; + +#define TDAV_DECLARE_CODEC_H263 tdav_codec_h263_t __codec_h263__ + +static int tdav_codec_h263_init(tdav_codec_h263_t* self, tdav_codec_h263_type_t type, enum CodecID encoder, enum CodecID decoder); +static int tdav_codec_h263_deinit(tdav_codec_h263_t* self); +static int tdav_codec_h263_open_encoder(tdav_codec_h263_t* self); +static int tdav_codec_h263_open_decoder(tdav_codec_h263_t* self); +static int tdav_codec_h263_close_encoder(tdav_codec_h263_t* self); +static int tdav_codec_h263_close_decoder(tdav_codec_h263_t* self); + +/** H.263-1998 codec */ +typedef struct tdav_codec_h263p_s +{ + TDAV_DECLARE_CODEC_H263; +} +tdav_codec_h263p_t; + +/** H.263-2000 codec */ +typedef struct tdav_codec_h263pp_s +{ + TDAV_DECLARE_CODEC_H263; +} +tdav_codec_h263pp_t; + + +static void tdav_codec_h263_rtp_callback(tdav_codec_h263_t *self, const void *data, tsk_size_t size, tsk_bool_t marker); +static void tdav_codec_h263p_rtp_callback(tdav_codec_h263_t *self, const void *data, tsk_size_t size, tsk_bool_t frag, tsk_bool_t marker); + +static void tdav_codec_h263_encap(const tdav_codec_h263_t* h263, const uint8_t* pdata, tsk_size_t size); + + +/* ============ Common To all H263 codecs ================= */ + +static int tdav_codec_h263_set(tmedia_codec_t* self, const tmedia_param_t* param) +{ + tdav_codec_h263_t* h263 = (tdav_codec_h263_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: + { + h263->encoder.force_idr = tsk_true; + break; + } + case tmedia_codec_action_bw_down: + { + h263->encoder.quality = TSK_CLAMP(1, (h263->encoder.quality + 1), 31); + h263->encoder.context->global_quality = FF_QP2LAMBDA * h263->encoder.quality; + break; + } + case tmedia_codec_action_bw_up: + { + h263->encoder.quality = TSK_CLAMP(1, (h263->encoder.quality - 1), 31); + h263->encoder.context->global_quality = FF_QP2LAMBDA * h263->encoder.quality; + break; + } + } + return 0; + } + } + return -1; +} + +int tdav_codec_h263_init(tdav_codec_h263_t* self, tdav_codec_h263_type_t type, enum CodecID encoder, enum CodecID decoder) +{ + int ret = 0; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + self->type = type; + self->encoder.quality = 1; + + if(!(self->encoder.codec = avcodec_find_encoder(encoder))){ + TSK_DEBUG_ERROR("Failed to find [%d]encoder", encoder); + ret = -2; + } + + if(!(self->decoder.codec = avcodec_find_decoder(decoder))){ + TSK_DEBUG_ERROR("Failed to find [%d] decoder", decoder); + ret = -3; + } + + self->encoder.max_bw_kpbs = tmedia_defaults_get_bandwidth_video_upload_max(); + + /* allocations MUST be done by open() */ + return ret; +} + +int tdav_codec_h263_deinit(tdav_codec_h263_t* self) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + self->encoder.codec = tsk_null; + self->decoder.codec = tsk_null; + + // FFMpeg resources are destroyed by close() + + + + TSK_FREE(self->rtp.ptr); + self->rtp.size = 0; + + return 0; +} + + + +/* ============ H.263-1996 Plugin interface ================= */ + +// +// H.263-1996 object definition +// +static int tdav_codec_h263_open(tmedia_codec_t* self) +{ + int ret; + + tdav_codec_h263_t* h263 = (tdav_codec_h263_t*)self; + + if(!h263){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + // Encoder + if((ret = tdav_codec_h263_open_encoder(h263))){ + return ret; + } + + // Decoder + if((ret = tdav_codec_h263_open_decoder(h263))){ + return ret; + } + + return ret; +} + +static int tdav_codec_h263_close(tmedia_codec_t* self) +{ + tdav_codec_h263_t* h263 = (tdav_codec_h263_t*)self; + int ret; + + if(!h263){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is opened */ + + // Encoder + ret = tdav_codec_h263_close_encoder(h263); + // Decoder + ret = tdav_codec_h263_close_decoder(h263); + + return ret; +} + +static tsk_size_t tdav_codec_h263_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_h263_t* h263 = (tdav_codec_h263_t*)self; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + // wrap yuv420 buffer + size = avpicture_fill((AVPicture *)h263->encoder.picture, (uint8_t*)in_data, PIX_FMT_YUV420P, h263->encoder.context->width, h263->encoder.context->height); + if(size != in_size){ + /* guard */ + TSK_DEBUG_ERROR("Invalid size"); + return 0; + } +#if LIBAVCODEC_VERSION_MAJOR <= 53 + h263->encoder.picture->pict_type = h263->encoder.force_idr ? FF_I_TYPE : 0; +#else + h263->encoder.picture->pict_type = h263->encoder.force_idr ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE; +#endif + h263->encoder.picture->pts = AV_NOPTS_VALUE; + h263->encoder.picture->quality = h263->encoder.context->global_quality; + ret = avcodec_encode_video(h263->encoder.context, h263->encoder.buffer, size, h263->encoder.picture); + if(ret > 0){ + tdav_codec_h263_encap(h263, h263->encoder.buffer, (tsk_size_t)ret); + } + h263->encoder.force_idr = tsk_false; + + return 0; +} + +static tsk_size_t tdav_codec_h263_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) +{ + uint8_t F, P, sbit, ebit; + const uint8_t* pdata = in_data; + 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_h263_t* h263 = (tdav_codec_h263_t*)self; + const trtp_rtp_header_t* rtp_hdr = proto_hdr; + tsk_bool_t is_idr = tsk_false; + + if(!self || !in_data || !in_size || !out_data || !h263->decoder.context){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* RFC 2190 + get F and P bits, used to determine the header Mode (A, B or C) + F: 1 bit + The flag bit indicates the mode of the payload header. F=0, mode A; + F=1, mode B or mode C depending on P bit defined below. + P: 1 bit + Optional PB-frames mode as defined by the H.263 [4]. "0" implies + normal I or P frame, "1" PB-frames. When F=1, P also indicates modes: + mode B if P=0, mode C if P=1. + + I: 1 bit. + Picture coding type, bit 9 in PTYPE defined by H.263[4], "0" is + intra-coded, "1" is inter-coded. + */ + F = *pdata >> 7; + P = (*pdata >> 6) & 0x01; + + /* SBIT and EBIT */ + sbit = (*pdata >> 3) & 0x0F; + ebit = (*pdata & 0x07); + + if(F == 0){ + /* MODE A + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F|P|SBIT |EBIT | SRC |I|U|S|A|R |DBQ| TRB | TR | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + hdr_size = H263_HEADER_MODE_A_SIZE; + is_idr = (in_size >= 2) && !(pdata[1] & 0x10) /* I==1 */; + } + else if(P == 0){ // F=1 and P=0 + /* MODE B + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F|P|SBIT |EBIT | SRC | QUANT | GOBN | MBA |R | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |I|U|S|A| HMV1 | VMV1 | HMV2 | VMV2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + hdr_size = H263_HEADER_MODE_B_SIZE; + is_idr = (in_size >= 5) && !(pdata[4] & 0x80) /* I==1 */; + } + else{ // F=1 and P=1 + /* MODE C + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F|P|SBIT |EBIT | SRC | QUANT | GOBN | MBA |R | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |I|U|S|A| HMV1 | VMV1 | HMV2 | VMV2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RR |DBQ| TRB | TR | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + hdr_size = H263_HEADER_MODE_C_SIZE; + is_idr = (in_size >= 5) && !(pdata[4] & 0x80) /* I==1 */; + } + + /* Check size */ + if(in_size < hdr_size){ + TSK_DEBUG_ERROR("Too short"); + return 0; + } + + pay_ptr = (pdata + hdr_size); + pay_size = (in_size - hdr_size); + xsize = avpicture_get_size(h263->decoder.context->pix_fmt, h263->decoder.context->width, h263->decoder.context->height); + + /* Packet lost? */ + if(h263->decoder.last_seq != (rtp_hdr->seq_num - 1) && h263->decoder.last_seq){ + if(h263->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("[H.263] Packet loss, seq_num=%d", rtp_hdr->seq_num); + } + h263->decoder.last_seq = rtp_hdr->seq_num; + + if((int)(h263->decoder.accumulator_pos + pay_size) <= xsize){ + if((h263->decoder.ebit + sbit) == 8){ /* Perfect one Byte to clean up */ + if(h263->decoder.accumulator_pos){ + ((uint8_t*)h263->decoder.accumulator)[h263->decoder.accumulator_pos-1] = (((uint8_t*)h263->decoder.accumulator)[h263->decoder.accumulator_pos-1] & (0xFF << h263->decoder.ebit)) | + (*pay_ptr & (0xFF >> sbit)); + } + pay_ptr++, pay_size--; + } + h263->decoder.ebit = ebit; + + memcpy(&((uint8_t*)h263->decoder.accumulator)[h263->decoder.accumulator_pos], pay_ptr, pay_size); + h263->decoder.accumulator_pos += pay_size; + } + else{ + TSK_DEBUG_WARN("Buffer overflow"); + h263->decoder.accumulator_pos = 0; + return 0; + } + + if(rtp_hdr->marker){ + AVPacket packet; + /* allocate destination buffer */ + if(*out_max_size <xsize){ + if(!(*out_data = tsk_realloc(*out_data, xsize))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + h263->decoder.accumulator_pos = 0; + *out_max_size = 0; + return 0; + } + *out_max_size = xsize; + } + + av_init_packet(&packet); + packet.size = (int)h263->decoder.accumulator_pos; + packet.data = h263->decoder.accumulator; + ret = avcodec_decode_video2(h263->decoder.context, h263->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; + // Is it IDR frame? + if(is_idr && TMEDIA_CODEC_VIDEO(self)->in.callback){ + TSK_DEBUG_INFO("Decoded H.263 IDR"); + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_idr; + TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } + TMEDIA_CODEC_VIDEO(h263)->in.width = h263->decoder.context->width; + TMEDIA_CODEC_VIDEO(h263)->in.height = h263->decoder.context->height; + /* copy picture into a linear buffer */ + avpicture_layout((AVPicture *)h263->decoder.picture, h263->decoder.context->pix_fmt, (int)h263->decoder.context->width, (int)h263->decoder.context->height, + *out_data, (int)retsize); + } + /* in all cases: reset accumulator */ + h263->decoder.accumulator_pos = 0; + } + + return retsize; +} + +static tsk_bool_t tdav_codec_h263_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + if(tsk_striequals(att_name, "fmtp")){ + unsigned width, height, fps; + if(tmedia_parse_video_fmtp(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &width, &height, &fps)){ + TSK_DEBUG_ERROR("Failed to match fmtp=%s", att_value); + return tsk_false; + } + TMEDIA_CODEC_VIDEO(codec)->in.width = TMEDIA_CODEC_VIDEO(codec)->out.width = width; + TMEDIA_CODEC_VIDEO(codec)->in.height = TMEDIA_CODEC_VIDEO(codec)->out.height = height; + TMEDIA_CODEC_VIDEO(codec)->in.fps = TMEDIA_CODEC_VIDEO(codec)->out.fps = fps; + } +#if 0 + 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; + } +#endif + + return tsk_true; +} + +static char* tdav_codec_h263_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) +{ + if(tsk_striequals(att_name, "fmtp")){ + tmedia_pref_video_size_t cif_vs; + if(tmedia_video_get_closest_cif_size(TMEDIA_CODEC_VIDEO(codec)->pref_size, &cif_vs)){ + TSK_DEBUG_ERROR("Failed to get closest CIF family size"); + return tsk_null; + } + return tmedia_get_video_fmtp(cif_vs); + } +#if 0 + 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); + } +#endif + return tsk_null; +} + +/* constructor */ +static tsk_object_t* tdav_codec_h263_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h263_t *h263 = self; + if(h263){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + tdav_codec_h263_init(TDAV_CODEC_H263(self), tdav_codec_h263_1996, CODEC_ID_H263, CODEC_ID_H263); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h263_dtor(tsk_object_t * self) +{ + tdav_codec_h263_t *h263 = self; + if(h263){ + /* deinit base */ + tmedia_codec_video_deinit(h263); + /* deinit self */ + tdav_codec_h263_deinit(TDAV_CODEC_H263(self)); + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h263_def_s = +{ + sizeof(tdav_codec_h263_t), + tdav_codec_h263_ctor, + tdav_codec_h263_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h263_plugin_def_s = +{ + &tdav_codec_h263_def_s, + + tmedia_video, + tmedia_codec_id_h263, + "H263", + "H263-1996 codec (FFmpeg)", + TMEDIA_CODEC_FORMAT_H263, + tsk_false, + 90000, // rate + + /* audio */ + { 0 }, + + /* video */ + {176, 144, 15}, + + tdav_codec_h263_set, + tdav_codec_h263_open, + tdav_codec_h263_close, + tdav_codec_h263_encode, + tdav_codec_h263_decode, + tdav_codec_h263_sdp_att_match, + tdav_codec_h263_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h263_plugin_def_t = &tdav_codec_h263_plugin_def_s; + + + + + + + + + + + + + + + + + + + + + + + +/* ============ H.263-1998 Plugin interface ================= */ + +// +// H.263-1998 object definition +// + +static tsk_size_t tdav_codec_h263p_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) +{ + uint8_t P, V, PLEN, PEBIT; + uint8_t* pdata = (uint8_t*)in_data; + const uint8_t* pay_ptr; + tsk_size_t pay_size; + int hdr_size = H263P_HEADER_SIZE; + tsk_size_t xsize, retsize = 0; + int got_picture_ptr; + int ret; + + tdav_codec_h263_t* h263 = (tdav_codec_h263_t*)self; + const trtp_rtp_header_t* rtp_hdr = proto_hdr; + + if(!self || !in_data || !in_size || ((int)in_size <= hdr_size) || !out_data || !h263->decoder.context){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + +/* + rfc4629 - 5.1. General H.263+ Payload Header + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RR |P|V| PLEN |PEBIT| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + P = (pdata[0] & 0x04)>>2; + V = (pdata[0] & 0x02)>>1; + PLEN = (((pdata[0] & 0x01)<<5) | pdata[1]>>3); + PEBIT = pdata[1] & 0x07; + + if(V){ + /* + Indicates the presence of an 8-bit field containing information + for Video Redundancy Coding (VRC), which follows immediately after + the initial 16 bits of the payload header, if present. For syntax + and semantics of that 8-bit VRC field, see Section 5.2. + */ + } + if(PLEN){ + /* + Length, in bytes, of the extra picture header. If no extra + picture header is attached, PLEN is 0. If PLEN>0, the extra + picture header is attached immediately following the rest of the + payload header. Note that the length reflects the omission of the + first two bytes of the picture start code (PSC). See Section 6.1. + */ + hdr_size += PLEN; + if(PEBIT){ + /* + Indicates the number of bits that shall be ignored in the last + byte of the picture header. If PLEN is not zero, the ignored bits + shall be the least significant bits of the byte. If PLEN is zero, + then PEBIT shall also be zero. + */ + TSK_DEBUG_WARN("PEBIT ignored"); + } + } + if(P){ /* MUST be done after PLEN and PEBIT */ + /* + Indicates the picture start or a picture segment (GOB/Slice) start + or a video sequence end (EOS or EOSBS). Two bytes of zero bits + then have to be prefixed to the payload of such a packet to + compose a complete picture/GOB/slice/EOS/EOSBS start code. This + bit allows the omission of the two first bytes of the start codes, + thus improving the compression ratio. + */ + hdr_size -= 2; + pdata[hdr_size] = 0x00, pdata[hdr_size + 1] = 0x00; + } + + pay_ptr = (pdata + hdr_size); + pay_size = (in_size - hdr_size); + xsize = avpicture_get_size(h263->decoder.context->pix_fmt, h263->decoder.context->width, h263->decoder.context->height); + + /* Packet lost? */ + if(h263->decoder.last_seq != (rtp_hdr->seq_num - 1) && h263->decoder.last_seq){ + if(h263->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("[H.263+] Packet loss, seq_num=%d", rtp_hdr->seq_num); + } + h263->decoder.last_seq = rtp_hdr->seq_num; + + if((int)(h263->decoder.accumulator_pos + pay_size) <= xsize){ + /* PEBIT is ignored */ + memcpy(&((uint8_t*)h263->decoder.accumulator)[h263->decoder.accumulator_pos], pay_ptr, pay_size); + h263->decoder.accumulator_pos += pay_size; + } + else{ + TSK_DEBUG_WARN("Buffer overflow"); + h263->decoder.accumulator_pos = 0; + return 0; + } + + if(rtp_hdr->marker){ + AVPacket packet; + /* allocate destination buffer */ + if(*out_max_size < xsize){ + if(!(*out_data = tsk_realloc(*out_data, xsize))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + h263->decoder.accumulator_pos = 0; + return 0; + } + *out_max_size = xsize; + } + + /* decode the picture */ + av_init_packet(&packet); + packet.size = (int)h263->decoder.accumulator_pos; + packet.data = h263->decoder.accumulator; + ret = avcodec_decode_video2(h263->decoder.context, h263->decoder.picture, &got_picture_ptr, &packet); + + if(ret <0 || !got_picture_ptr){ + TSK_DEBUG_WARN("Failed to decode the buffer"); + } + else{ + retsize = xsize; + TMEDIA_CODEC_VIDEO(h263)->in.width = h263->decoder.context->width; + TMEDIA_CODEC_VIDEO(h263)->in.height = h263->decoder.context->height; + /* copy picture into a linear buffer */ + avpicture_layout((AVPicture *)h263->decoder.picture, h263->decoder.context->pix_fmt, (int)h263->decoder.context->width, (int)h263->decoder.context->height, + *out_data, (int)retsize); + } + /* in all cases: reset accumulator */ + h263->decoder.accumulator_pos = 0; + } + + return retsize; +} + +/* constructor */ +static tsk_object_t* tdav_codec_h263p_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h263p_t *h263p = self; + if(h263p){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + tdav_codec_h263_init(TDAV_CODEC_H263(self), tdav_codec_h263_1998, CODEC_ID_H263P, CODEC_ID_H263); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h263p_dtor(tsk_object_t * self) +{ + tdav_codec_h263p_t *h263p = self; + if(h263p){ + /* deinit base */ + tmedia_codec_video_deinit(h263p); + /* deinit self */ + tdav_codec_h263_deinit(TDAV_CODEC_H263(self)); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h263p_def_s = +{ + sizeof(tdav_codec_h263p_t), + tdav_codec_h263p_ctor, + tdav_codec_h263p_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h263p_plugin_def_s = +{ + &tdav_codec_h263p_def_s, + + tmedia_video, + tmedia_codec_id_h263p, + "H263-1998", + "H263-1998 codec (FFmpeg)", + TMEDIA_CODEC_FORMAT_H263_1998, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (width, height, fps) */ + {176, 144, 0},// fps is @deprecated + + tdav_codec_h263p_set, + tdav_codec_h263p_open, + tdav_codec_h263p_close, + tdav_codec_h263p_encode, + tdav_codec_h263p_decode, + tdav_codec_h263p_sdp_att_match, + tdav_codec_h263p_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h263p_plugin_def_t = &tdav_codec_h263p_plugin_def_s; + + + + + + + + + + + + + + +/* ============ H.263-2000 Plugin interface ================= */ + +// +// H.263-2000 object definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_h263pp_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h263pp_t *h263pp = self; + if(h263pp){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + tdav_codec_h263_init(TDAV_CODEC_H263(self), tdav_codec_h263_2000, CODEC_ID_H263P, CODEC_ID_H263); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h263pp_dtor(tsk_object_t * self) +{ + tdav_codec_h263pp_t *h263pp = self; + if(h263pp){ + /* deinit base */ + tmedia_codec_video_deinit(h263pp); + /* deinit self */ + tdav_codec_h263_deinit(TDAV_CODEC_H263(self)); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h263pp_def_s = +{ + sizeof(tdav_codec_h263pp_t), + tdav_codec_h263pp_ctor, + tdav_codec_h263pp_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h263pp_plugin_def_s = +{ + &tdav_codec_h263pp_def_s, + + tmedia_video, + tmedia_codec_id_h263pp, + "H263-2000", + "H263-2000 codec (FFmpeg)", + TMEDIA_CODEC_FORMAT_H263_2000, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (width, height, fps)*/ + {176, 144, 0},// fps is @deprecated + + tdav_codec_h263pp_set, + tdav_codec_h263pp_open, + tdav_codec_h263pp_close, + tdav_codec_h263pp_encode, + tdav_codec_h263pp_decode, + tdav_codec_h263pp_sdp_att_match, + tdav_codec_h263pp_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h263pp_plugin_def_t = &tdav_codec_h263pp_plugin_def_s; + + + +int tdav_codec_h263_open_encoder(tdav_codec_h263_t* self) +{ + int ret; + int size; + int32_t max_bw_kpbs; + if(self->encoder.context){ + TSK_DEBUG_ERROR("Encoder already opened"); + 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 = TMEDIA_CODEC_VIDEO(self)->out.width; + self->encoder.context->height = TMEDIA_CODEC_VIDEO(self)->out.height; + + self->encoder.context->qmin = 10; + self->encoder.context->qmax = 51; +#if LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->mb_qmin = self->encoder.context->qmin; + self->encoder.context->mb_qmax = self->encoder.context->qmax; +#endif + self->encoder.context->mb_decision = FF_MB_DECISION_RD; + 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 + //self->encoder.context->rc_lookahead = 0; + self->encoder.context->rtp_payload_size = RTP_PAYLOAD_SIZE; + self->encoder.context->opaque = tsk_null; + self->encoder.context->gop_size = (TMEDIA_CODEC_VIDEO(self)->out.fps * TDAV_H263_GOP_SIZE_IN_SECONDS); + self->encoder.context->flags |= CODEC_FLAG_QSCALE; + self->encoder.context->global_quality = FF_QP2LAMBDA * self->encoder.quality; + self->encoder.context->max_b_frames = 0; + + // 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); + //if((ret = avpicture_alloc((AVPicture*)self->encoder.picture, PIX_FMT_YUV420P, self->encoder.context->width, self->encoder.context->height))){ + // TSK_DEBUG_ERROR("Failed to allocate encoder picture"); + // return ret; + //} + + 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; + } + + + // RTP Callback + switch(self->type){ + case tdav_codec_h263_1996: + { // H263 - 1996 + break; + } + case tdav_codec_h263_1998: + { // H263 - 1998 +#if defined(CODEC_FLAG_H263P_UMV) + self->encoder.context->flags |= CODEC_FLAG_H263P_UMV; // Annex D+ +#endif + self->encoder.context->flags |= CODEC_FLAG_AC_PRED; // Annex I and T + self->encoder.context->flags |= CODEC_FLAG_LOOP_FILTER; // Annex J +#if defined(CODEC_FLAG_H263P_SLICE_STRUCT) + self->encoder.context->flags |= CODEC_FLAG_H263P_SLICE_STRUCT; // Annex K +#endif +#if defined(CODEC_FLAG_H263P_AIV) + self->encoder.context->flags |= CODEC_FLAG_H263P_AIV; // Annex S +#endif + break; + } + case tdav_codec_h263_2000: + { // H263 - 2000 +#if defined(CODEC_FLAG_H263P_UMV) + self->encoder.context->flags |= CODEC_FLAG_H263P_UMV; // Annex D+ +#endif + self->encoder.context->flags |= CODEC_FLAG_AC_PRED; // Annex I and T + self->encoder.context->flags |= CODEC_FLAG_LOOP_FILTER; // Annex J +#if defined(CODEC_FLAG_H263P_SLICE_STRUCT) + self->encoder.context->flags |= CODEC_FLAG_H263P_SLICE_STRUCT; // Annex K +#endif +#if defined(CODEC_FLAG_H263P_AIV) + self->encoder.context->flags |= CODEC_FLAG_H263P_AIV; // Annex S +#endif + break; + } + } + // Open encoder + if((ret = avcodec_open(self->encoder.context, self->encoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(self)->plugin->desc); + return ret; + } + + TSK_DEBUG_INFO("[H.263] bitrate=%d bps", self->encoder.context->bit_rate); + + return ret; +} + +int tdav_codec_h263_open_decoder(tdav_codec_h263_t* self) +{ + int ret, size; + + 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; + } + + // Open decoder + if((ret = avcodec_open(self->decoder.context, self->decoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(self)->plugin->desc); + return ret; + } + + self->decoder.last_seq = 0; + + return ret; +} + +int tdav_codec_h263_close_encoder(tdav_codec_h263_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_h263_close_decoder(tdav_codec_h263_t* self) +{ + if(self->decoder.context){ + avcodec_close(self->decoder.context); + 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); + self->decoder.accumulator_pos = 0; + } + return 0; +} + +/* ============ Callbacks ================= */ + +static void tdav_codec_h263_encap(const tdav_codec_h263_t* h263, const uint8_t* pdata, tsk_size_t size) +{ + tsk_bool_t frag = tsk_false; + register uint32_t i, last_index = 0; + + if(size < RTP_PAYLOAD_SIZE){ + goto last; + } + + for(i = 4; i<(size - 4); i++){ + if(pdata[i] == 0x00 && pdata[i+1] == 0x00 && pdata[i+2]>=0x80){ /* PSC or (GBSC) found */ + if((i - last_index) >= RTP_PAYLOAD_SIZE || tsk_true/* FIXME */){ + switch(h263->type){ + case tdav_codec_h263_1996: + tdav_codec_h263_rtp_callback((tdav_codec_h263_t*) h263, pdata+last_index, + (i - last_index), (last_index == size)); + break; + default: + tdav_codec_h263p_rtp_callback((tdav_codec_h263_t*) h263, pdata + last_index, + (i - last_index), frag, (last_index == size)); + frag = tsk_true; + break; + } + last_index = i; + } + } + } +last: + if(last_index < size){ + switch(h263->type){ + case tdav_codec_h263_1996: + tdav_codec_h263_rtp_callback((tdav_codec_h263_t*) h263, pdata + last_index, + (size - last_index), tsk_true); + break; + default: + tdav_codec_h263p_rtp_callback((tdav_codec_h263_t*) h263, pdata + last_index, + (size - last_index), frag, tsk_true); + break; + } + } +} + + +static void tdav_codec_h263_rtp_callback(tdav_codec_h263_t *self, const void *data, tsk_size_t size, tsk_bool_t marker) +{ + uint8_t* pdata = (uint8_t*)data; + + if(self->rtp.size < (size + H263_HEADER_MODE_A_SIZE)){ + if(!(self->rtp.ptr = tsk_realloc(self->rtp.ptr, (size + H263_HEADER_MODE_A_SIZE)))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + return; + } + self->rtp.size = (size + H263_HEADER_MODE_A_SIZE); + } + memcpy((self->rtp.ptr + H263_HEADER_MODE_A_SIZE), data, size); + + /* http://eu.sabotage.org/www/ITU/H/H0263e.pdf section 5.1 + * 5.1.1 Picture Start Code (PSC) (22 bits) - PSC is a word of 22 bits. Its value is 0000 0000 0000 0000 1 00000. + + * + * 5.1.1 Picture Start Code (PSC) (22 bits) + * 5.1.2 Temporal Reference (TR) (8 bits) + * 5.1.3 Type Information (PTYPE) (Variable Length) + * – Bit 1: Always "1", in order to avoid start code emulation. + * – Bit 2: Always "0", for distinction with Recommendation H.261. + + * – Bit 3: Split screen indicator, "0" off, "1" on. + * – Bit 4: Document camera indicator, "0" off, "1" on. + * – Bit 5: Full Picture Freeze Release, "0" off, "1" on. + * – Bits 6-8: Source Format, "000" forbidden, "001" sub-QCIF, "010" QCIF, "011" CIF, + "100" 4CIF, "101" 16CIF, "110" reserved, "111" extended PTYPE. + If bits 6-8 are not equal to "111", which indicates an extended PTYPE (PLUSPTYPE), the following + five bits are also present in PTYPE: + – Bit 9: Picture Coding Type, "0" INTRA (I-picture), "1" INTER (P-picture). + – Bit 10: Optional Unrestricted Motion Vector mode (see Annex D), "0" off, "1" on. + – Bit 11: Optional Syntax-based Arithmetic Coding mode (see Annex E), "0" off, "1" on. + – Bit 12: Optional Advanced Prediction mode (see Annex F), "0" off, "1" on. + – Bit 13: Optional PB-frames mode (see Annex G), "0" normal I- or P-picture, "1" PB-frame. + */ + if(pdata[0] == 0x00 && pdata[1] == 0x00 && (pdata[2] & 0xfc)==0x80){ /* PSC */ + /* RFC 2190 -5.1 Mode A + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F|P|SBIT |EBIT | SRC |I|U|S|A|R |DBQ| TRB | TR | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + SRC : 3 bits + Source format, bit 6,7 and 8 in PTYPE defined by H.263 [4], specifies + the resolution of the current picture. + + I: 1 bit. + Picture coding type, bit 9 in PTYPE defined by H.263[4], "0" is + intra-coded, "1" is inter-coded. + */ + + // PDATA[4] ======> Bits 3-10 of PTYPE + uint32_t rtp_hdr = 0; + uint8_t format, pict_type; + + // Source Format = 4,5,6 + format = (pdata[4] & 0x3C)>>2; + // Picture Coding Type = 7 + pict_type = (pdata[4] & 0x02)>>1; + // RTP mode A header + ((uint8_t*)&rtp_hdr)[1] = (format <<5) | (pict_type << 4); + //rtp_hdr = tnet_htonl(rtp_hdr); + memcpy(self->rtp.ptr, &rtp_hdr, sizeof(rtp_hdr)); + } + + // 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 = (size + H263_HEADER_MODE_A_SIZE); + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (uint32_t)((1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate); + TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = marker; + TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); + } +} + +static void tdav_codec_h263p_rtp_callback(tdav_codec_h263_t *self, const void *data, tsk_size_t size, tsk_bool_t frag, tsk_bool_t marker) +{ + uint8_t* pdata = (uint8_t*)data; + //uint8_t rtp_hdr[2] = {0x00, 0x00}; + //tsk_bool_t eos = tsk_false; + + const void* _ptr = tsk_null; + tsk_size_t _size = 0; + //static tsk_bool_t frag = tsk_false; + //tsk_bool_t found_gob = tsk_false; + + /* RFC 4629 - 5.1. General H.263+ Payload Header + The H.263+ payload header is structured as follows: + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RR |P|V| PLEN |PEBIT| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + /* http://eu.sabotage.org/www/ITU/H/H0263e.pdf + * + * 5.1.1 Picture Start Code (PSC) (22 bits) + * ->PSC is a word of 22 bits. Its value is 0000 0000 0000 0000 1 00000. + * 5.1.27 End Of Sequence (EOS) (22 bits) + * ->A codeword of 22 bits. Its value is 0000 0000 0000 0000 1 11111 + * 5.2.2 Group of Block Start Code (GBSC) (17 bits) + * ->A word of 17 bits. Its value is 0000 0000 0000 0000 1 + * C.4.1 End Of Sub-Bitstream code (EOSBS) (23 bits) + * ->The EOSBS code is a codeword of 23 bits. Its value is 0000 0000 0000 0000 1 11110 0 + * + * + * 5.2.3 Group Number (GN) (5 bits) + * -> last 5 bits + */ + //if(pdata[0] == 0x00 && pdata[1] == 0x00 && pdata[2] >= 0x80){ /* PSC or EOS or GBSC */ + // uint8_t GN = ((pdata[2]>>2) & 0x1F); + // found_gob = tsk_true; + // //TSK_DEBUG_INFO("GN=%u", pdata[2]); + // + // /* RFC 4629 - 6.1.1. Packets that begin with a Picture Start Code + // A packet that begins at the location of a Picture, GOB, slice, EOS, + // or EOSBS start code shall omit the first two (all zero) bytes from + // the H.263+ bitstream and signify their presence by setting P=1 in the + // payload header. + // */ + + // if(GN == 0x00){ /* PSC 00000 */ + // /* Use the two first bytes as RTP header */ + // //pdata[0] |= 0x04; // P=1 + + // /* + // 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 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | RR |1|V|0|0|0|0|0|0|0|0|0| bitstream data without the : + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // : first two 0 bytes of the PSC + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // */ + + // //TSK_DEBUG_INFO("H263 - PSC"); + // } + // else if(GN == 0x1F){ /* EOS 11111 */ + // /* Use the two first bytes as RTP header */ + // //pdata[0] |= 0x04; // P=1 + // eos = tsk_true; + // /* RFC 4629 - 6.1.3. Packets that begin with an EOS or EOSBS Code + // 0 1 2 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | RR |1|V|0|0|0|0|0|0|0|0|0|1|1|1|1|1|1|0|0| + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // */ + // //TSK_DEBUG_INFO("H263 - EOS"); + // } + // else /*if((GN >> 4) == 0x01)*/{ /* GBSC 10000 */ + // /* Use the two first bytes as RTP header */ + // //pdata[0] |= 0x04; // P=1 + // + // /* RFC 4629 - 6.1.2. Packets that begin with GBSC or SSC + // 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 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | RR |1|V|0 0 1 0 0 1|PEBIT|1 0 0 0 0 0| picture header : + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // : starting with TR, PTYPE ... | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | ... | bitstream : + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // : data starting with GBSC/SSC without its first two 0 bytes + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // */ + // //TSK_DEBUG_INFO("H263 - GBSC"); + // found_gob = tsk_false; + // } + // //else if(EOSBS) -> Not Supported + //} + //else{ + // /* 6.2. Encapsulating Follow-on Packet (P=0) */ + // int i = 0; + // i++; + //} + + //if(/*eos*/!found_gob && frag){ + // if(self->rtp.size < (size + 2/* H263+ Header size */)){ + // if(!(self->rtp.ptr = tsk_realloc(self->rtp.ptr, (size + 2)))){ + // TSK_DEBUG_ERROR("Failed to allocate new buffer"); + // return; + // } + // self->rtp.size = (size + 2); + // } + // /* RFC 4629 - 6. Packetization Schemes */ + // //rtp_hdr[0] |= 0x00; + // //memcpy(self->rtp.ptr, rtp_hdr/* zeros-> is it corretc? */, 2); + // //memcpy((self->rtp.ptr + 2), pdata, size); + // //_ptr = self->rtp.ptr; + // //_size = (size + 2); + + // pdata[0] |= pdata[2] > 0x80 ? 0x04 : 0x04; + // _ptr = pdata; + // _size = size; + //} + //else{ + // pdata[0] |= pdata[2] > 0x80 ? 0x04 : 0x04; + // _ptr = pdata; + // _size = size; + //} + +// FIXME + pdata[0] |= pdata[2] > 0x80 ? 0x04 : 0x04; + _ptr = pdata; + _size = size; + + + // Send data over the network + if(TMEDIA_CODEC_VIDEO(self)->out.callback){ + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = _ptr; + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = _size; + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (uint32_t)((1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate); + TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = marker; + TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); + } +} + +tsk_bool_t tdav_codec_ffmpeg_h263_is_supported() +{ + return (avcodec_find_encoder(CODEC_ID_H263) && avcodec_find_decoder(CODEC_ID_H263)); +} + +tsk_bool_t tdav_codec_ffmpeg_h263p_is_supported() +{ + return (avcodec_find_encoder(CODEC_ID_H263P) && avcodec_find_decoder(CODEC_ID_H263)); +} + +tsk_bool_t tdav_codec_ffmpeg_h263pp_is_supported() +{ + return tdav_codec_ffmpeg_h263p_is_supported(); +} + + +#endif /* HAVE_FFMPEG */ diff --git a/tinyDAV/src/codecs/h264/tdav_codec_h264.c b/tinyDAV/src/codecs/h264/tdav_codec_h264.c new file mode 100644 index 0000000..0ec3760 --- /dev/null +++ b/tinyDAV/src/codecs/h264/tdav_codec_h264.c @@ -0,0 +1,993 @@ +/* +* 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_h264.c + * @brief H.264 codec plugin using FFmpeg for decoding and x264 for encoding + * RTP payloader/depayloader follows RFC 3984 + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/h264/tdav_codec_h264.h" + +#if HAVE_FFMPEG || HAVE_H264_PASSTHROUGH + +#include "tinydav/codecs/h264/tdav_codec_h264_rtp.h" +#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_params.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#if HAVE_FFMPEG +# include <libavcodec/avcodec.h> +# if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51, 35, 0)) +# include <libavutil/opt.h> +# endif +#endif + +typedef struct tdav_codec_h264_s +{ + TDAV_DECLARE_CODEC_H264_COMMON; + + // Encoder + struct{ +#if HAVE_FFMPEG + AVCodec* codec; + AVCodecContext* context; + AVFrame* picture; +#endif + void* buffer; + int64_t frame_count; + tsk_bool_t force_idr; + int32_t quality; // [1-31] + int rotation; + int32_t max_bw_kpbs; + tsk_bool_t passthrough; // whether to bypass encoding + } encoder; + + // decoder + struct{ +#if HAVE_FFMPEG + AVCodec* codec; + AVCodecContext* context; + AVFrame* picture; +#endif + void* accumulator; + tsk_size_t accumulator_pos; + tsk_size_t accumulator_size; + uint16_t last_seq; + tsk_bool_t passthrough; // whether to bypass decoding + } decoder; +} +tdav_codec_h264_t; + +#if !defined(TDAV_H264_GOP_SIZE_IN_SECONDS) +# define TDAV_H264_GOP_SIZE_IN_SECONDS 25 +#endif + +#define kResetRotationTrue tsk_true +#define kResetRotationFalse tsk_false + +static int tdav_codec_h264_init(tdav_codec_h264_t* self, profile_idc_t profile); +static int tdav_codec_h264_deinit(tdav_codec_h264_t* self); +static int tdav_codec_h264_open_encoder(tdav_codec_h264_t* self); +static int tdav_codec_h264_close_encoder(tdav_codec_h264_t* self, tsk_bool_t reset_rotation); +static int tdav_codec_h264_open_decoder(tdav_codec_h264_t* self); +static int tdav_codec_h264_close_decoder(tdav_codec_h264_t* self); + +/* ============ H.264 Base/Main Profile X.X Plugin interface functions ================= */ + +static int tdav_codec_h264_set(tmedia_codec_t* self, const tmedia_param_t* param) +{ + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + 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: + { + h264->encoder.force_idr = tsk_true; + break; + } + case tmedia_codec_action_bw_down: + { + h264->encoder.quality = TSK_CLAMP(1, (h264->encoder.quality + 1), 31); +#if HAVE_FFMPEG + if (h264->encoder.context) { + h264->encoder.context->global_quality = FF_QP2LAMBDA * h264->encoder.quality; + } +#endif + break; + } + case tmedia_codec_action_bw_up: + { + h264->encoder.quality = TSK_CLAMP(1, (h264->encoder.quality - 1), 31); +#if HAVE_FFMPEG + if (h264->encoder.context) { + h264->encoder.context->global_quality = FF_QP2LAMBDA * h264->encoder.quality; + } +#endif + break; + } + } + return 0; + } + else if(tsk_striequals(param->key, "bw_kbps")){ + int32_t max_bw_userdefine = self->bandwidth_max_upload; + int32_t max_bw_new = *((int32_t*)param->value); + if (max_bw_userdefine > 0) { + // do not use more than what the user defined in it's configuration + h264->encoder.max_bw_kpbs = TSK_MIN(max_bw_new, max_bw_userdefine); + } + else { + h264->encoder.max_bw_kpbs = max_bw_new; + } + return 0; + } + else if(tsk_striequals(param->key, "bypass-encoding")){ + h264->encoder.passthrough = *((int32_t*)param->value) ? tsk_true : tsk_false; + TSK_DEBUG_INFO("[H.264] bypass-encoding = %d", h264->encoder.passthrough); + return 0; + } + else if(tsk_striequals(param->key, "bypass-decoding")){ + h264->decoder.passthrough = *((int32_t*)param->value) ? tsk_true : tsk_false; + TSK_DEBUG_INFO("[H.264] bypass-decoding = %d", h264->decoder.passthrough); + return 0; + } + else if(tsk_striequals(param->key, "rotation")){ + int32_t rotation = *((int32_t*)param->value); + if(h264->encoder.rotation != rotation){ + h264->encoder.rotation = rotation; + if (self->opened) { + int ret; + if ((ret = tdav_codec_h264_close_encoder(h264, kResetRotationFalse))) { + return ret; + } + if ((ret = tdav_codec_h264_open_encoder(h264))) { + return ret; + } +#if 0 // Not working + if((ret = avcodec_close(h264->encoder.context))){ + TSK_DEBUG_ERROR("Failed to close [%s] codec", TMEDIA_CODEC(h264)->plugin->desc); + return ret; + } + h264->encoder.context->width = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(h264)->out.height : TMEDIA_CODEC_VIDEO(h264)->out.width; + h264->encoder.context->height = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(h264)->out.width : TMEDIA_CODEC_VIDEO(h264)->out.height; + if((ret = avcodec_open(h264->encoder.context, h264->encoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(h264)->plugin->desc); + return ret; + } + h264->encoder.force_idr = tsk_true; +#endif + } + } + return 0; + } + } + return -1; +} + + +static int tdav_codec_h264_open(tmedia_codec_t* self) +{ + int ret; + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + // Encoder + if((ret = tdav_codec_h264_open_encoder(h264))){ + return ret; + } + + // Decoder + if((ret = tdav_codec_h264_open_decoder(h264))){ + return ret; + } + + return 0; +} + +static int tdav_codec_h264_close(tmedia_codec_t* self) +{ + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) alreasy checked that the codec is opened */ + + // Encoder + tdav_codec_h264_close_encoder(h264, kResetRotationTrue); + + // Decoder + tdav_codec_h264_close_decoder(h264); + + return 0; +} + +static tsk_size_t tdav_codec_h264_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 = 0; + +#if HAVE_FFMPEG + int size; + tsk_bool_t send_idr, send_hdr; +#endif + + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + + if(!self || !in_data || !in_size){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(!self->opened){ + TSK_DEBUG_ERROR("Codec not opened"); + return 0; + } + + if(h264->encoder.passthrough) { + tdav_codec_h264_rtp_encap(TDAV_CODEC_H264_COMMON(h264), (const uint8_t*)in_data, in_size); + } + else { // !h264->encoder.passthrough +#if HAVE_FFMPEG // wrap yuv420 buffer + size = avpicture_fill((AVPicture *)h264->encoder.picture, (uint8_t*)in_data, PIX_FMT_YUV420P, h264->encoder.context->width, h264->encoder.context->height); + if (size != in_size){ + /* guard */ + TSK_DEBUG_ERROR("Invalid size: %u<>%u", size, in_size); + return 0; + } + + // send IDR for: + // - the first frame + // - remote peer requested an IDR + // - every second within the first 4seconds + send_idr = ( + h264->encoder.frame_count++ == 0 + || h264 ->encoder.force_idr + //|| ( (h264->encoder.frame_count < (int)TMEDIA_CODEC_VIDEO(h264)->out.fps * 4) && ((h264->encoder.frame_count % TMEDIA_CODEC_VIDEO(h264)->out.fps)==0) ) + ); + + // send SPS and PPS headers for: + // - IDR frames (not required but it's the easiest way to deal with pkt loss) + // - every 5 seconds after the first 4seconds + send_hdr = ( + send_idr + //|| ( (h264->encoder.frame_count % (TMEDIA_CODEC_VIDEO(h264)->out.fps * 5))==0 ) + ); + if(send_hdr){ + tdav_codec_h264_rtp_encap(TDAV_CODEC_H264_COMMON(h264), h264->encoder.context->extradata, (tsk_size_t)h264->encoder.context->extradata_size); + } + + // Encode data + #if LIBAVCODEC_VERSION_MAJOR <= 53 + h264->encoder.picture->pict_type = send_idr ? FF_I_TYPE : 0; + #else + h264->encoder.picture->pict_type = send_idr ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE; + #endif + h264->encoder.picture->key_frame = send_idr ? 1 : 0; + h264->encoder.picture->pts = AV_NOPTS_VALUE; + h264->encoder.picture->quality = h264->encoder.context->global_quality; + // h264->encoder.picture->pts = h264->encoder.frame_count; MUST NOT + ret = avcodec_encode_video(h264->encoder.context, h264->encoder.buffer, size, h264->encoder.picture); + if(ret > 0){ + tdav_codec_h264_rtp_encap(TDAV_CODEC_H264_COMMON(h264), h264->encoder.buffer, (tsk_size_t)ret); + } + h264 ->encoder.force_idr = tsk_false; +#endif + }// else(!h264->encoder.passthrough) + + return 0; +} + +static tsk_size_t tdav_codec_h264_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) +{ + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + const trtp_rtp_header_t* rtp_hdr = (const trtp_rtp_header_t*)proto_hdr; + + const uint8_t* pay_ptr = tsk_null; + tsk_size_t pay_size = 0; + int ret; + tsk_bool_t sps_or_pps, append_scp, end_of_unit; + tsk_size_t retsize = 0, size_to_copy = 0; + static const tsk_size_t xmax_size = (3840 * 2160 * 3) >> 3; // >>3 instead of >>1 (not an error) + static tsk_size_t start_code_prefix_size = sizeof(H264_START_CODE_PREFIX); +#if HAVE_FFMPEG + int got_picture_ptr = 0; +#endif + + if(!h264 || !in_data || !in_size || !out_data +#if HAVE_FFMPEG + || !h264->decoder.context +#endif + ) + { + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + //TSK_DEBUG_INFO("SeqNo=%hu", rtp_hdr->seq_num); + + /* Packet lost? */ + if((h264->decoder.last_seq + 1) != rtp_hdr->seq_num && h264->decoder.last_seq){ + TSK_DEBUG_INFO("[H.264] Packet loss, seq_num=%d", (h264->decoder.last_seq + 1)); + } + h264->decoder.last_seq = rtp_hdr->seq_num; + + + /* 5.3. NAL Unit Octet Usage + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + */ + if(*((uint8_t*)in_data) & 0x80){ + TSK_DEBUG_WARN("F=1"); + /* reset accumulator */ + h264->decoder.accumulator_pos = 0; + return 0; + } + + /* get payload */ + if((ret = tdav_codec_h264_get_pay(in_data, in_size, (const void**)&pay_ptr, &pay_size, &append_scp, &end_of_unit)) || !pay_ptr || !pay_size){ + TSK_DEBUG_ERROR("Depayloader failed to get H.264 content"); + return 0; + } + //append_scp = tsk_true; + size_to_copy = pay_size + (append_scp ? start_code_prefix_size : 0); + // whether it's SPS or PPS (append_scp is false for subsequent FUA chuncks) + sps_or_pps = append_scp && pay_ptr && ((pay_ptr[0] & 0x1F) == 7 || (pay_ptr[0] & 0x1F) == 8); + + // start-accumulator + if(!h264->decoder.accumulator){ + if(size_to_copy > xmax_size){ + TSK_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", size_to_copy, xmax_size); + return 0; + } + if(!(h264->decoder.accumulator = tsk_calloc(size_to_copy, sizeof(uint8_t)))){ + TSK_DEBUG_ERROR("Failed to allocated new buffer"); + return 0; + } + h264->decoder.accumulator_size = size_to_copy; + } + if((h264->decoder.accumulator_pos + size_to_copy) >= xmax_size){ + TSK_DEBUG_ERROR("BufferOverflow"); + h264->decoder.accumulator_pos = 0; + return 0; + } + if((h264->decoder.accumulator_pos + size_to_copy) > h264->decoder.accumulator_size){ + if(!(h264->decoder.accumulator = tsk_realloc(h264->decoder.accumulator, (h264->decoder.accumulator_pos + size_to_copy)))){ + TSK_DEBUG_ERROR("Failed to reallocated new buffer"); + h264->decoder.accumulator_pos = 0; + h264->decoder.accumulator_size = 0; + return 0; + } + h264->decoder.accumulator_size = (h264->decoder.accumulator_pos + size_to_copy); + } + + if(append_scp){ + memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], H264_START_CODE_PREFIX, start_code_prefix_size); + h264->decoder.accumulator_pos += start_code_prefix_size; + } + memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], pay_ptr, pay_size); + h264->decoder.accumulator_pos += pay_size; + // end-accumulator + + if(sps_or_pps){ + // http://libav-users.943685.n4.nabble.com/Decode-H264-streams-how-to-fill-AVCodecContext-from-SPS-PPS-td2484472.html + // SPS and PPS should be bundled with IDR + TSK_DEBUG_INFO("Receiving SPS or PPS ...to be tied to an IDR"); + } + else if(rtp_hdr->marker){ + if(h264->decoder.passthrough){ + if(*out_max_size < h264->decoder.accumulator_pos){ + if((*out_data = tsk_realloc(*out_data, h264->decoder.accumulator_pos))){ + *out_max_size = h264->decoder.accumulator_pos; + } + else{ + *out_max_size = 0; + return 0; + } + } + memcpy(*out_data, h264->decoder.accumulator, h264->decoder.accumulator_pos); + retsize = h264->decoder.accumulator_pos; + } + else { // !h264->decoder.passthrough +#if HAVE_FFMPEG + AVPacket packet; + + /* decode the picture */ + av_init_packet(&packet); + packet.dts = packet.pts = AV_NOPTS_VALUE; + packet.size = (int)h264->decoder.accumulator_pos; + packet.data = h264->decoder.accumulator; + ret = avcodec_decode_video2(h264->decoder.context, h264->decoder.picture, &got_picture_ptr, &packet); + + if(ret <0){ + TSK_DEBUG_INFO("Failed to decode the buffer with error code =%d, size=%u, append=%s", ret, h264->decoder.accumulator_pos, append_scp ? "yes" : "no"); + 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){ + tsk_size_t xsize; + + /* IDR ? */ + if(((pay_ptr[0] & 0x1F) == 0x05) && TMEDIA_CODEC_VIDEO(self)->in.callback){ + TSK_DEBUG_INFO("Decoded H.264 IDR"); + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_idr; + TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } + /* fill out */ + xsize = avpicture_get_size(h264->decoder.context->pix_fmt, h264->decoder.context->width, h264->decoder.context->height); + if(*out_max_size<xsize){ + if((*out_data = tsk_realloc(*out_data, (xsize + FF_INPUT_BUFFER_PADDING_SIZE)))){ + *out_max_size = xsize; + } + else{ + *out_max_size = 0; + return 0; + } + } + retsize = xsize; + TMEDIA_CODEC_VIDEO(h264)->in.width = h264->decoder.context->width; + TMEDIA_CODEC_VIDEO(h264)->in.height = h264->decoder.context->height; + avpicture_layout((AVPicture *)h264->decoder.picture, h264->decoder.context->pix_fmt, (int)h264->decoder.context->width, (int)h264->decoder.context->height, + *out_data, (int)retsize); + } +#endif /* HAVE_FFMPEG */ + } // else(h264->decoder.passthrough) + + h264->decoder.accumulator_pos = 0; + } // else if(rtp_hdr->marker) + + return retsize; +} + +static tsk_bool_t tdav_codec_h264_sdp_att_match(const tmedia_codec_t* self, const char* att_name, const char* att_value) +{ + return tdav_codec_h264_common_sdp_att_match((tdav_codec_h264_common_t*)self, att_name, att_value); +} + +static char* tdav_codec_h264_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + char* att = tdav_codec_h264_common_sdp_att_get((const tdav_codec_h264_common_t*)self, att_name); + if(att && tsk_striequals(att_name, "fmtp")) { + tsk_strcat_2(&att, "; impl=%s", +#if HAVE_FFMPEG + "FFMPEG" +#elif HAVE_H264_PASSTHROUGH + "PASSTHROUGH" +#endif + ); + } + return att; +} + + + + +/* ============ H.264 Base Profile Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_base_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; + if(h264){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + if(tdav_codec_h264_init(h264, profile_idc_baseline) != 0){ + return tsk_null; + } + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_base_dtor(tsk_object_t * self) +{ + tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; + if(h264){ + /* deinit base */ + tdav_codec_h264_common_deinit((tdav_codec_h264_common_t*)self); + /* deinit self */ + tdav_codec_h264_deinit(h264); + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_base_def_s = +{ + sizeof(tdav_codec_h264_t), + tdav_codec_h264_base_ctor, + tdav_codec_h264_base_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_base_plugin_def_s = +{ + &tdav_codec_h264_base_def_s, + + tmedia_video, + tmedia_codec_id_h264_bp, + "H264", + "H264 Base Profile (FFmpeg, x264)", + TMEDIA_CODEC_FORMAT_H264_BP, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (width, height, fps) */ + {176, 144, 0}, // fps is @deprecated + + tdav_codec_h264_set, + tdav_codec_h264_open, + tdav_codec_h264_close, + tdav_codec_h264_encode, + tdav_codec_h264_decode, + tdav_codec_h264_sdp_att_match, + tdav_codec_h264_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h264_base_plugin_def_t = &tdav_codec_h264_base_plugin_def_s; + +/* ============ H.264 Main Profile Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_main_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; + if(h264){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + if(tdav_codec_h264_init(h264, profile_idc_main) != 0){ + return tsk_null; + } + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_main_dtor(tsk_object_t * self) +{ + tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; + if(h264){ + /* deinit base */ + tdav_codec_h264_common_deinit((tdav_codec_h264_common_t*)self); + /* deinit self */ + tdav_codec_h264_deinit(h264); + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_main_def_s = +{ + sizeof(tdav_codec_h264_t), + tdav_codec_h264_main_ctor, + tdav_codec_h264_main_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_main_plugin_def_s = +{ + &tdav_codec_h264_main_def_s, + + tmedia_video, + tmedia_codec_id_h264_mp, + "H264", + "H264 Main Profile (FFmpeg, x264)", + TMEDIA_CODEC_FORMAT_H264_MP, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (width, height, fps)*/ + {176, 144, 0},// fps is @deprecated + + tdav_codec_h264_set, + tdav_codec_h264_open, + tdav_codec_h264_close, + tdav_codec_h264_encode, + tdav_codec_h264_decode, + tdav_codec_h264_sdp_att_match, + tdav_codec_h264_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h264_main_plugin_def_t = &tdav_codec_h264_main_plugin_def_s; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/* ============ Common To all H264 codecs ================= */ + +int tdav_codec_h264_open_encoder(tdav_codec_h264_t* self) +{ +#if HAVE_FFMPEG + int ret; + tsk_size_t size; + + if(self->encoder.context){ + TSK_DEBUG_ERROR("Encoder already opened"); + return -1; + } + +#if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51, 35, 0)) + if((self->encoder.context = avcodec_alloc_context3(self->encoder.codec))){ + avcodec_get_context_defaults3(self->encoder.context, self->encoder.codec); + } +#else + if((self->encoder.context = avcodec_alloc_context())){ + avcodec_get_context_defaults(self->encoder.context); + } +#endif + + if(!self->encoder.context){ + TSK_DEBUG_ERROR("Failed to allocate context"); + return -1; + } + +#if TDAV_UNDER_X86 && LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->dsp_mask = (FF_MM_MMX | FF_MM_MMXEXT | FF_MM_SSE); +#endif + + 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.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), + TMEDIA_CODEC(self)->bandwidth_max_upload + ); + self->encoder.context->bit_rate = (self->encoder.max_bw_kpbs * 1024);// bps + + self->encoder.context->rc_min_rate = (self->encoder.context->bit_rate >> 3); + self->encoder.context->rc_max_rate = self->encoder.context->bit_rate; + +#if LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->rc_lookahead = 0; +#endif + self->encoder.context->global_quality = FF_QP2LAMBDA * self->encoder.quality; + +#if LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->partitions = X264_PART_I4X4 | X264_PART_I8X8 | X264_PART_P8X8 | X264_PART_B8X8; +#endif + self->encoder.context->me_method = ME_UMH; + self->encoder.context->me_range = 16; + self->encoder.context->qmin = 10; + self->encoder.context->qmax = 51; +#if LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->mb_qmin = self->encoder.context->qmin; + self->encoder.context->mb_qmax = self->encoder.context->qmax; +#endif + /* METROPOLIS = G2J.COM TelePresence client. Check Issue 378: No video when calling "TANDBERG/4129 (X8.1.1)" */ +#if !METROPOLIS && 0 + self->encoder.context->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif + self->encoder.context->flags |= CODEC_FLAG_LOW_DELAY; + if (self->encoder.context->profile == FF_PROFILE_H264_BASELINE) { + self->encoder.context->max_b_frames = 0; + } + + switch(TDAV_CODEC_H264_COMMON(self)->profile){ + case profile_idc_baseline: + default: + self->encoder.context->profile = FF_PROFILE_H264_BASELINE; + self->encoder.context->level = TDAV_CODEC_H264_COMMON(self)->level; + break; + case profile_idc_main: + self->encoder.context->profile = FF_PROFILE_H264_MAIN; + self->encoder.context->level = TDAV_CODEC_H264_COMMON(self)->level; + break; + } + + /* Comment from libavcodec/libx264.c: + * Allow x264 to be instructed through AVCodecContext about the maximum + * size of the RTP payload. For example, this enables the production of + * payload suitable for the H.264 RTP packetization-mode 0 i.e. single + * NAL unit per RTP packet. + */ + self->encoder.context->rtp_payload_size = H264_RTP_PAYLOAD_SIZE; + self->encoder.context->opaque = tsk_null; + self->encoder.context->gop_size = (TMEDIA_CODEC_VIDEO(self)->out.fps * TDAV_H264_GOP_SIZE_IN_SECONDS); + +#if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51, 35, 0)) + if((ret = av_opt_set_int(self->encoder.context->priv_data, "slice-max-size", H264_RTP_PAYLOAD_SIZE, 0))){ + TSK_DEBUG_ERROR("Failed to set x264 slice-max-size to %d", H264_RTP_PAYLOAD_SIZE); + } + if((ret = av_opt_set(self->encoder.context->priv_data, "profile", (self->encoder.context->profile == FF_PROFILE_H264_BASELINE ? "baseline" : "main"), 0))){ + TSK_DEBUG_ERROR("Failed to set x264 profile"); + } + if((ret = av_opt_set(self->encoder.context->priv_data, "preset", "veryfast", 0))){ + TSK_DEBUG_ERROR("Failed to set x264 preset to veryfast"); + } + if((ret = av_opt_set_int(self->encoder.context->priv_data, "rc-lookahead", 0, 0)) && (ret = av_opt_set_int(self->encoder.context->priv_data, "rc_lookahead", 0, 0))){ + TSK_DEBUG_ERROR("Failed to set x264 rc_lookahead=0"); + } + if((ret = av_opt_set(self->encoder.context->priv_data, "tune", "animation+zerolatency", 0))){ + TSK_DEBUG_ERROR("Failed to set x264 tune to zerolatency"); + } +#endif + + // 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 [%s] codec", TMEDIA_CODEC(self)->plugin->desc); + return ret; + } + + self->encoder.frame_count = 0; + + TSK_DEBUG_INFO("[H.264] bitrate=%d bps", self->encoder.context->bit_rate); + + return ret; +#elif HAVE_H264_PASSTHROUGH + self->encoder.frame_count = 0; + return 0; +#endif + + TSK_DEBUG_ERROR("Not expected code called"); + return -1; +} + +int tdav_codec_h264_close_encoder(tdav_codec_h264_t* self, tsk_bool_t reset_rotation) +{ +#if HAVE_FFMPEG + 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; + } +#endif + if(self->encoder.buffer){ + TSK_FREE(self->encoder.buffer); + } + self->encoder.frame_count = 0; + if (reset_rotation) { + self->encoder.rotation = 0; // reset rotation + } + + return 0; +} + +int tdav_codec_h264_open_decoder(tdav_codec_h264_t* self) +{ +#if HAVE_FFMPEG + int ret; + + 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->flags2 |= CODEC_FLAG2_FAST; + 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); + + // Open decoder + if((ret = avcodec_open(self->decoder.context, self->decoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(self)->plugin->desc); + return ret; + } + self->decoder.last_seq = 0; + + return ret; + +#elif HAVE_H264_PASSTHROUGH + return 0; +#endif + + TSK_DEBUG_ERROR("Unexpected code called"); + return -1; + +} + +int tdav_codec_h264_close_decoder(tdav_codec_h264_t* self) +{ +#if HAVE_FFMPEG + if(self->decoder.context){ + avcodec_close(self->decoder.context); + av_free(self->decoder.context); + self->decoder.context = tsk_null; + } + if(self->decoder.picture){ + av_free(self->decoder.picture); + self->decoder.picture = tsk_null; + } +#endif + TSK_FREE(self->decoder.accumulator); + self->decoder.accumulator_pos = 0; + + return 0; +} + +int tdav_codec_h264_init(tdav_codec_h264_t* self, profile_idc_t profile) +{ + int ret = 0; + level_idc_t level; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if((ret = tdav_codec_h264_common_init(TDAV_CODEC_H264_COMMON(self)))){ + TSK_DEBUG_ERROR("tdav_codec_h264_common_init() faile with error code=%d", ret); + return ret; + } + + if((ret = tdav_codec_h264_common_level_from_size(TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height, &level))){ + TSK_DEBUG_ERROR("Failed to find level for size=[%u, %u]", TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height); + return ret; + } + + (self)->encoder.max_bw_kpbs = TMEDIA_CODEC(self)->bandwidth_max_upload; + TDAV_CODEC_H264_COMMON(self)->pack_mode_local = H264_PACKETIZATION_MODE; + TDAV_CODEC_H264_COMMON(self)->profile = profile; + TDAV_CODEC_H264_COMMON(self)->level = level; + TMEDIA_CODEC_VIDEO(self)->in.max_mbps = TMEDIA_CODEC_VIDEO(self)->out.max_mbps = H264_MAX_MBPS*1000; + TMEDIA_CODEC_VIDEO(self)->in.max_br = TMEDIA_CODEC_VIDEO(self)->out.max_br = H264_MAX_BR*1000; + +#if HAVE_FFMPEG + if(!(self->encoder.codec = avcodec_find_encoder(CODEC_ID_H264))){ + TSK_DEBUG_ERROR("Failed to find H.264 encoder"); + ret = -2; + } + + if(!(self->decoder.codec = avcodec_find_decoder(CODEC_ID_H264))){ + TSK_DEBUG_ERROR("Failed to find H.264 decoder"); + ret = -3; + } +#endif +#if HAVE_H264_PASSTHROUGH + TMEDIA_CODEC(self)->passthrough = tsk_true; + self->decoder.passthrough = tsk_true; + self->encoder.passthrough = tsk_true; +#endif + + self->encoder.quality = 1; + + /* allocations MUST be done by open() */ + return ret; +} + +int tdav_codec_h264_deinit(tdav_codec_h264_t* self) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + +#if HAVE_FFMPEG + self->encoder.codec = tsk_null; + self->decoder.codec = tsk_null; + + // FFMpeg resources are destroyed by close() +#endif + + return 0; +} + +tsk_bool_t tdav_codec_ffmpeg_h264_is_supported() +{ +#if HAVE_FFMPEG + return (avcodec_find_encoder(CODEC_ID_H264) && avcodec_find_decoder(CODEC_ID_H264)); +#else + return tsk_false; +#endif +} + +tsk_bool_t tdav_codec_passthrough_h264_is_supported() +{ +#if HAVE_H264_PASSTHROUGH + return tsk_true; +#else + return tsk_false; +#endif +} + +#endif /* HAVE_FFMPEG || HAVE_H264_PASSTHROUGH */ diff --git a/tinyDAV/src/codecs/h264/tdav_codec_h264_cisco.cxx b/tinyDAV/src/codecs/h264/tdav_codec_h264_cisco.cxx new file mode 100644 index 0000000..a501221 --- /dev/null +++ b/tinyDAV/src/codecs/h264/tdav_codec_h264_cisco.cxx @@ -0,0 +1,882 @@ +/* +* Copyright (C) 2014-2015 Mamadou DIOP. +* +* +* 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_h264_cisco.cxx + * @brief H.264 codec plugin using OpenH264 (https://github.com/cisco/openh264) v1.1 for encoding/decoding. + */ +#include "tinydav/codecs/h264/tdav_codec_h264_cisco.h" + +#if HAVE_OPENH264 + +#include "tinydav/codecs/h264/tdav_codec_h264_common.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tinymedia/tmedia_codec.h" +#include "tinymedia/tmedia_params.h" +#include "tinymedia/tmedia_defaults.h" + +#include "tsk_mutex.h" +#include "tsk_params.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +extern "C" { +#include <wels/codec_api.h> +#include <wels/codec_app_def.h> +} + +#include <limits.h> /* INT_MAX */ + +typedef struct tdav_codec_h264_cisco_s +{ + TDAV_DECLARE_CODEC_H264_COMMON; + + // Encoder + struct{ + ISVCEncoder *pInst; + SEncParamExt sEncParam; + SSourcePicture sEncPic; + void* buffer; + int64_t frame_count; + tsk_bool_t force_idr; + int rotation; + int neg_width; + int neg_height; + int neg_fps; + tsk_mutex_handle_t* mutex; + } encoder; + + // decoder + struct{ + ISVCDecoder* pInst; + void* accumulator; + tsk_size_t accumulator_pos; + tsk_size_t accumulator_size; + uint16_t last_seq; + } decoder; +} +tdav_codec_h264_cisco_t; + +#if !defined(CISCO_H264_GOP_SIZE_IN_SECONDS) +# define CISCO_H264_GOP_SIZE_IN_SECONDS 25 +#endif + +#define kResetRotationTrue tsk_true +#define kResetRotationFalse tsk_false + +static int tdav_codec_h264_cisco_init(tdav_codec_h264_cisco_t* self, profile_idc_t profile); +static int tdav_codec_h264_cisco_deinit(tdav_codec_h264_cisco_t* self); +static int tdav_codec_h264_cisco_open_encoder(tdav_codec_h264_cisco_t* self); +static int tdav_codec_h264_cisco_close_encoder(tdav_codec_h264_cisco_t* self, tsk_bool_t reset_rotation); +static int tdav_codec_h264_cisco_open_decoder(tdav_codec_h264_cisco_t* self); +static int tdav_codec_h264_cisco_close_decoder(tdav_codec_h264_cisco_t* self); +static ELevelIdc tdav_codec_h264_cisco_convert_level(enum level_idc_e level); +static void tdav_codec_h264_cisco_debug_cb(void* context, int level, const char* message); + +static void (*__tdav_codec_h264_cisco_debug_cb)(void* context, int level, const char* message) = tdav_codec_h264_cisco_debug_cb; + +/* ============ H.264 Base/Main Profile X.X Plugin interface functions ================= */ + +static int tdav_codec_h264_cisco_set(tmedia_codec_t* self, const tmedia_param_t* param) +{ + tdav_codec_h264_cisco_t* h264 = (tdav_codec_h264_cisco_t*)self; + tsk_bool_t reconf = tsk_false; + 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: + { + TSK_DEBUG_INFO("OpenH264 force_idr action"); + h264->encoder.force_idr = tsk_true; + return 0; + } + case tmedia_codec_action_bw_up: + case tmedia_codec_action_bw_down: + { + int32_t rc_target_bitrate; + int32_t bandwidth_max_upload_bps = TMEDIA_CODEC(h264)->bandwidth_max_upload == INT_MAX ? TMEDIA_CODEC(h264)->bandwidth_max_upload : (TMEDIA_CODEC(h264)->bandwidth_max_upload * 1024); // kbps -> bps + if (action == tmedia_codec_action_bw_up) { + rc_target_bitrate = TSK_CLAMP(0, (int32_t)((h264->encoder.sEncParam.iTargetBitrate * 3) >> 1), bandwidth_max_upload_bps); + } + else { + rc_target_bitrate = TSK_CLAMP(0, (int32_t)((h264->encoder.sEncParam.iTargetBitrate << 1) / 3), bandwidth_max_upload_bps); + } + h264->encoder.sEncParam.iTargetBitrate = rc_target_bitrate; + h264->encoder.sEncParam.iMaxBitrate = rc_target_bitrate; + SSpatialLayerConfig* layer = &h264->encoder.sEncParam.sSpatialLayers[0]; + layer->iMaxSpatialBitrate = h264->encoder.sEncParam.iMaxBitrate; + layer->iSpatialBitrate = h264->encoder.sEncParam.iTargetBitrate; + reconf = tsk_true; + TSK_DEBUG_INFO("OpenH264 new target bitrate = %d bps", rc_target_bitrate); + break; + } + } + } + else if (tsk_striequals(param->key, "bw_kbps")) { // both up and down (from the SDP) + int32_t max_bw_userdefine_kbps = tmedia_defaults_get_bandwidth_video_upload_max(); + int32_t max_bw_new_kbps = *((int32_t*)param->value); + if (max_bw_userdefine_kbps > 0) { + // do not use more than what the user defined in it's configuration + TMEDIA_CODEC(h264)->bandwidth_max_upload = TSK_MIN(max_bw_new_kbps, max_bw_userdefine_kbps); + } + else { + TMEDIA_CODEC(h264)->bandwidth_max_upload = max_bw_new_kbps; + } + TSK_DEBUG_INFO("OpenH264 codec: bandwidth-max-upload= %d kbps", TMEDIA_CODEC(h264)->bandwidth_max_upload); + reconf = tsk_true; + } + else if (tsk_striequals(param->key, "bandwidth-max-upload")) { + int32_t bw_max_upload_kbps = *((int32_t*)param->value); + TSK_DEBUG_INFO("OpenH264 codec: bandwidth-max-upload= %d kbps", bw_max_upload_kbps); + TMEDIA_CODEC(h264)->bandwidth_max_upload = bw_max_upload_kbps; + reconf = tsk_true; + } + else if (tsk_striequals(param->key, "rotation")) { + int rotation = *((int32_t*)param->value); + if (h264->encoder.rotation != rotation) { + h264->encoder.rotation = rotation; + if (self->opened) { + int ret; + if ((ret = tdav_codec_h264_cisco_close_encoder(h264, kResetRotationFalse))) { + return ret; + } + if ((ret = tdav_codec_h264_cisco_open_encoder(h264))) { + return ret; + } + } + } + return 0; + } + } + + if (reconf) { + if (h264->encoder.pInst) { + long err; + // lock required because of https://code.google.com/p/doubango/issues/detail?id=422 + tsk_mutex_lock(h264->encoder.mutex); + err = h264->encoder.pInst->InitializeExt(&h264->encoder.sEncParam); + tsk_mutex_unlock(h264->encoder.mutex); + if (err != cmResultSuccess) { + TSK_DEBUG_ERROR("InitializeExt failed: %ld", err); + return -1; + } + } + return 0; + } + + return -1; +} + + +static int tdav_codec_h264_cisco_open(tmedia_codec_t* self) +{ + int ret; + tdav_codec_h264_cisco_t* h264 = (tdav_codec_h264_cisco_t*)self; + + if (!h264) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + // Encoder + if ((ret = tdav_codec_h264_cisco_open_encoder(h264))) { + return ret; + } + + // Decoder + if ((ret = tdav_codec_h264_cisco_open_decoder(h264))) { + return ret; + } + + return 0; +} + +static int tdav_codec_h264_cisco_close(tmedia_codec_t* self) +{ + tdav_codec_h264_cisco_t* h264 = (tdav_codec_h264_cisco_t*)self; + + if (!h264) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is opened */ + + // Encoder + tdav_codec_h264_cisco_close_encoder(h264, kResetRotationTrue); + + // Decoder + tdav_codec_h264_cisco_close_decoder(h264); + + return 0; +} + +static tsk_size_t tdav_codec_h264_cisco_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + long err; + tsk_bool_t send_idr, send_hdr; + tsk_size_t in_xsize; + SFrameBSInfo bsInfo; + + tdav_codec_h264_cisco_t* h264 = (tdav_codec_h264_cisco_t*)self; + tdav_codec_h264_common_t* common = (tdav_codec_h264_common_t*)self; + + if (!self || !in_data || !in_size) { + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if (!self->opened || !h264->encoder.pInst) { + TSK_DEBUG_ERROR("Encoder not opened or not ready"); + return 0; + } + + in_xsize = (h264->encoder.sEncPic.iPicHeight * h264->encoder.sEncPic.iPicWidth * 3) >> 1; + if (in_xsize != in_size){ + /* guard */ + TSK_DEBUG_ERROR("Invalid size: %u<>%u", in_xsize, in_size); + return 0; + } + + // send IDR for: + // - the first frame + // - remote peer requested an IDR + // - every second within the first 4seconds + send_idr = ( + h264->encoder.frame_count++ == 0 + || h264 ->encoder.force_idr + //|| ( (h264->encoder.frame_count < (int)TMEDIA_CODEC_VIDEO(h264)->out.fps * 4) && ((h264->encoder.frame_count % TMEDIA_CODEC_VIDEO(h264)->out.fps)==0) ) + ); + + // send SPS and PPS headers for: + // - IDR frames (not required but it's the easiest way to deal with pkt loss) + // - every 5 seconds after the first 4seconds + send_hdr = ( + send_idr + //|| ( (h264->encoder.frame_count % (TMEDIA_CODEC_VIDEO(h264)->out.fps * 5))==0 ) + ); + + if (send_idr) { + TSK_DEBUG_INFO("OpenH264 call ForceIntraFrame"); + if ((err = h264->encoder.pInst->ForceIntraFrame(true)) != cmResultSuccess) { + TSK_DEBUG_WARN("OpenH264 ForceIntraFrame(%d) failed: %ld", send_idr, err); + } + } + if (send_hdr) { +#if 0 // Not needed + memset(&bsInfo, 0, sizeof(bsInfo)); + tsk_mutex_lock(h264->encoder.mutex); + if ((err = h264->encoder.pInst->EncodeParameterSets(&bsInfo)) != cmResultSuccess) { + TSK_DEBUG_WARN("OpenH264 EncodeParameterSets(%d) failed: %ld", send_idr, err); + } + else { + for (int iLayerNum = 0; iLayerNum < bsInfo.iLayerNum; ++iLayerNum) { + unsigned char* pBsBuf = bsInfo.sLayerInfo[iLayerNum].pBsBuf; + int iNalLengthInByte = 0, _iNalLengthInByte; + for (int iNalCount = 0; iNalCount < bsInfo.sLayerInfo[iLayerNum].iNalCount; ++iNalCount) { + if ((_iNalLengthInByte = bsInfo.sLayerInfo[iLayerNum].pNalLengthInByte[iNalCount]) > 0) { + iNalLengthInByte += _iNalLengthInByte; + } + } + if (iNalLengthInByte > 0) { + tdav_codec_h264_rtp_encap(TDAV_CODEC_H264_COMMON(h264), pBsBuf, (tsk_size_t)iNalLengthInByte); + } + } + } + tsk_mutex_unlock(h264->encoder.mutex); +#endif + } + + h264->encoder.sEncPic.pData[0] = ((unsigned char*)in_data); + h264->encoder.sEncPic.pData[1] = h264->encoder.sEncPic.pData[0] + (h264->encoder.sEncPic.iPicHeight * h264->encoder.sEncPic.iPicWidth); + h264->encoder.sEncPic.pData[2] = h264->encoder.sEncPic.pData[1] + ((h264->encoder.sEncPic.iPicHeight * h264->encoder.sEncPic.iPicWidth) >> 2); + // h264->encoder.sEncPic.uiTimeStamp = rand(); + + memset(&bsInfo, 0, sizeof(bsInfo)); + + tsk_mutex_lock(h264->encoder.mutex); + if ((err = h264->encoder.pInst->EncodeFrame(&h264->encoder.sEncPic, &bsInfo)) != cmResultSuccess) { + TSK_DEBUG_ERROR("OpenH264 setting EncodeFrame() failed: %ld", err); + tsk_mutex_unlock(h264->encoder.mutex); + return 0; + } + + // Memory held by bsInfo is freed when "InitializeExt()" is called this is why the unlock is after reading the output stream + if (bsInfo.eFrameType != videoFrameTypeInvalid) { + for (int iLayerNum = 0; iLayerNum < bsInfo.iLayerNum; ++iLayerNum) { + unsigned char* pBsBuf = bsInfo.sLayerInfo[iLayerNum].pBsBuf; + int iNalLengthInByte = 0, _iNalLengthInByte; + for (int iNalCount = 0; iNalCount < bsInfo.sLayerInfo[iLayerNum].iNalCount; ++iNalCount) { + if ((_iNalLengthInByte = bsInfo.sLayerInfo[iLayerNum].pNalLengthInByte[iNalCount]) > 0) { + iNalLengthInByte += _iNalLengthInByte; + } + } + if (iNalLengthInByte > 0) { + tdav_codec_h264_rtp_encap(TDAV_CODEC_H264_COMMON(h264), pBsBuf, (tsk_size_t)iNalLengthInByte); + } + } + } + tsk_mutex_unlock(h264->encoder.mutex); + + h264 ->encoder.force_idr = tsk_false; // reset + + return 0; +} + +static tsk_size_t tdav_codec_h264_cisco_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) +{ + tdav_codec_h264_cisco_t* h264 = (tdav_codec_h264_cisco_t*)self; + const trtp_rtp_header_t* rtp_hdr = (const trtp_rtp_header_t*)proto_hdr; + + const uint8_t* pay_ptr = tsk_null; + tsk_size_t pay_size = 0; + int ret; + long err = cmResultSuccess; + tsk_bool_t append_scp, end_of_unit, got_picture_ptr = tsk_false; + tsk_bool_t sps_or_pps; + tsk_size_t retsize = 0, size_to_copy = 0; + static const tsk_size_t xmax_size = (3840 * 2160 * 3) >> 3; // >>3 instead of >>1 (not an error) + static tsk_size_t start_code_prefix_size = sizeof(H264_START_CODE_PREFIX); + + if (!h264 || !in_data || !in_size || !out_data) { + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if (!self->opened || !h264->encoder.pInst) { + TSK_DEBUG_ERROR("Decoder not opened or not ready"); + return 0; + } + + /* Packet lost? */ + if ((h264->decoder.last_seq + 1) != rtp_hdr->seq_num && h264->decoder.last_seq) { + TSK_DEBUG_INFO("[H.264] Packet loss, seq_num=%d", (h264->decoder.last_seq + 1)); + } + h264->decoder.last_seq = rtp_hdr->seq_num; + + + /* 5.3. NAL Unit Octet Usage + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + */ + if (*((uint8_t*)in_data) & 0x80) { + TSK_DEBUG_WARN("F=1"); + /* reset accumulator */ + h264->decoder.accumulator_pos = 0; + return 0; + } + + /* get payload */ + if ((ret = tdav_codec_h264_get_pay(in_data, in_size, (const void**)&pay_ptr, &pay_size, &append_scp, &end_of_unit)) || !pay_ptr || !pay_size) { + TSK_DEBUG_ERROR("Depayloader failed to get H.264 content"); + return 0; + } + //append_scp = tsk_true; + size_to_copy = pay_size + (append_scp ? start_code_prefix_size : 0); + // whether it's SPS or PPS (append_scp is false for subsequent FUA chuncks) + sps_or_pps = append_scp && pay_ptr && ((pay_ptr[0] & 0x1F) == 7 || (pay_ptr[0] & 0x1F) == 8); + + // start-accumulator + if (!h264->decoder.accumulator) { + if (size_to_copy > xmax_size) { + TSK_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", size_to_copy, xmax_size); + return 0; + } + if (!(h264->decoder.accumulator = tsk_calloc(size_to_copy, sizeof(uint8_t)))) { + TSK_DEBUG_ERROR("Failed to allocated new buffer"); + return 0; + } + h264->decoder.accumulator_size = size_to_copy; + } + if ((h264->decoder.accumulator_pos + size_to_copy) >= xmax_size) { + TSK_DEBUG_ERROR("BufferOverflow"); + h264->decoder.accumulator_pos = 0; + return 0; + } + if ((h264->decoder.accumulator_pos + size_to_copy) > h264->decoder.accumulator_size) { + if (!(h264->decoder.accumulator = tsk_realloc(h264->decoder.accumulator, (h264->decoder.accumulator_pos + size_to_copy)))) { + TSK_DEBUG_ERROR("Failed to reallocated new buffer"); + h264->decoder.accumulator_pos = 0; + h264->decoder.accumulator_size = 0; + return 0; + } + h264->decoder.accumulator_size = (h264->decoder.accumulator_pos + size_to_copy); + } + + if (append_scp) { + memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], H264_START_CODE_PREFIX, start_code_prefix_size); + h264->decoder.accumulator_pos += start_code_prefix_size; + } + memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], pay_ptr, pay_size); + h264->decoder.accumulator_pos += pay_size; + // end-accumulator + + if (/*rtp_hdr->marker*/end_of_unit) { + /* decode the picture */ + unsigned char* out_ptr[3] = { NULL }; + int out_stride[2] = {0}, out_width = 0, out_height = 0; + tsk_size_t out_xsize; + + // Decode a Unit + err = h264->decoder.pInst->DecodeFrame( + (const unsigned char*)h264->decoder.accumulator, h264->decoder.accumulator_pos, + out_ptr, out_stride, out_width, out_height); + + if (err != cmResultSuccess) { + if (0 && err == dsDataErrorConcealed) { + TSK_DEBUG_INFO("OpenH264: Data error concealed"); + err = cmResultSuccess; + } + else { + TSK_DEBUG_WARN("OpenH264: DecodeFrame failed: %ld", err); + goto bail; + } + } + // Do we have a complete frame? + if (!(got_picture_ptr = ((out_ptr[0] && out_ptr[1] && out_ptr[2]) && (out_stride[0] && out_stride[1]) && out_width && out_height))) { + goto bail; + } + out_xsize = (out_width * out_height * 3) >> 1; // I420 + /* IDR ? */ + if (((pay_ptr[0] & 0x1F) == 0x05) && TMEDIA_CODEC_VIDEO(self)->in.callback) { + TSK_DEBUG_INFO("Decoded H.264 IDR"); + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_idr; + TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } + /* fill out */ + if (*out_max_size < out_xsize) { + if ((*out_data = tsk_realloc(*out_data, out_xsize))){ + *out_max_size = out_xsize; + } + else { + *out_max_size = 0; + return 0; + } + } + TMEDIA_CODEC_VIDEO(h264)->in.width = out_width; + TMEDIA_CODEC_VIDEO(h264)->in.height = out_height; + /* layout picture */ + { + int plane, y, stride; + retsize = 0; + for (plane=0; plane < 3; plane++) { + unsigned char *buf = out_ptr[plane]; + stride = out_stride[plane ? 1 : 0]; + for (y=0; y<out_height >> (plane ? 1 : 0); y++) { + unsigned int w_count = out_width >> (plane ? 1 : 0); + if ((ret + w_count) > *out_max_size) { + TSK_DEBUG_ERROR("BufferOverflow"); + ret = 0; + goto bail; + } + memcpy(((uint8_t*)*out_data) + retsize, buf, w_count); + retsize += w_count; + buf += stride; + } + } + } + } // else if(rtp_hdr->marker) + +bail: + /* end of frame */ + if (got_picture_ptr) { + int32_t endOfStream = 1; + err = h264->decoder.pInst->SetOption(DECODER_OPTION_END_OF_STREAM, (void*)&endOfStream); + if (err != cmResultSuccess) { + TSK_DEBUG_WARN("OpenH264 setting DECODER_OPTION_END_OF_STREAM failed: %ld", err); + goto bail; + } + } + if (/*rtp_hdr->marker*/end_of_unit) { + /* reset accumulator */ + h264->decoder.accumulator_pos = 0; + } + if (err != cmResultSuccess){ + TSK_DEBUG_INFO("Failed to decode the buffer with error code =%ld, size=%u, append=%s", err, h264->decoder.accumulator_pos, append_scp ? "yes" : "no"); + 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); + } + } + return retsize; +} + +static tsk_bool_t tdav_codec_h264_cisco_sdp_att_match(const tmedia_codec_t* self, const char* att_name, const char* att_value) +{ + return tdav_codec_h264_common_sdp_att_match((tdav_codec_h264_common_t*)self, att_name, att_value); +} + +static char* tdav_codec_h264_cisco_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + char* att = tdav_codec_h264_common_sdp_att_get((const tdav_codec_h264_common_t*)self, att_name); + if (att && tsk_striequals(att_name, "fmtp")) { + tsk_strcat(&att, "; impl=openh264"); + } + return att; +} + +/* ============ H.264 Base Profile Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_cisco_base_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_cisco_t *h264 = (tdav_codec_h264_cisco_t*)self; + if (h264) { + /* init base: called by tmedia_codec_create() */ + /* init self */ + if (tdav_codec_h264_cisco_init(h264, profile_idc_baseline) != 0) { + return tsk_null; + } + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_cisco_base_dtor(tsk_object_t * self) +{ + tdav_codec_h264_cisco_t *h264 = (tdav_codec_h264_cisco_t*)self; + if (h264) { + /* deinit base */ + tdav_codec_h264_common_deinit(TDAV_CODEC_H264_COMMON(self)); + /* deinit self */ + tdav_codec_h264_cisco_deinit(h264); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_cisco_base_def_s = +{ + sizeof(tdav_codec_h264_cisco_t), + tdav_codec_h264_cisco_base_ctor, + tdav_codec_h264_cisco_base_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_cisco_base_plugin_def_s = +{ + &tdav_codec_h264_cisco_base_def_s, + + tmedia_video, + tmedia_codec_id_h264_bp, + "H264", + "H264 Base Profile (OpenH264)", + TMEDIA_CODEC_FORMAT_H264_BP, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (width, height, fps) */ + {176, 144, 0}, // fps is @deprecated + + tdav_codec_h264_cisco_set, + tdav_codec_h264_cisco_open, + tdav_codec_h264_cisco_close, + tdav_codec_h264_cisco_encode, + tdav_codec_h264_cisco_decode, + tdav_codec_h264_cisco_sdp_att_match, + tdav_codec_h264_cisco_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h264_cisco_base_plugin_def_t = &tdav_codec_h264_cisco_base_plugin_def_s; + +/* ============ Common To all H264 profiles ================= */ + +static int tdav_codec_h264_cisco_open_encoder(tdav_codec_h264_cisco_t* self) +{ + int ret = -1, max_bitrate_bps; + long err; + SSpatialLayerConfig* layer; + + int32_t max_bw_kpbs; + tdav_codec_h264_common_t* common = (tdav_codec_h264_common_t*)self; + + if (self->encoder.pInst) { + TSK_DEBUG_ERROR("Encoder already initialized"); + goto bail; + } + + // create encoder + if ((err = WelsCreateSVCEncoder(&self->encoder.pInst)) != cmResultSuccess) { + TSK_DEBUG_ERROR("Failed to create ancoder: %ld", err); + goto bail; + } + + self->encoder.pInst->SetOption(ENCODER_OPTION_TRACE_CALLBACK_CONTEXT, self); + self->encoder.pInst->SetOption(ENCODER_OPTION_TRACE_CALLBACK, &__tdav_codec_h264_cisco_debug_cb); + + if ((err = self->encoder.pInst->GetDefaultParams(&self->encoder.sEncParam)) != cmResultSuccess) { + TSK_DEBUG_ERROR("GetDefaultParams failed: %ld", err); + goto bail; + } + + self->encoder.neg_width = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.height : TMEDIA_CODEC_VIDEO(self)->out.width; + self->encoder.neg_height = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.width : TMEDIA_CODEC_VIDEO(self)->out.height; + self->encoder.neg_fps = TMEDIA_CODEC_VIDEO(self)->out.fps; + max_bw_kpbs = TSK_CLAMP( + 0, + tmedia_get_video_bandwidth_kbps_2(self->encoder.neg_width, self->encoder.neg_height, self->encoder.neg_fps), + TMEDIA_CODEC(self)->bandwidth_max_upload + ); + max_bitrate_bps = (max_bw_kpbs * 1024); + + TSK_DEBUG_INFO("[H.264 OpenH264 Encoder] neg_width=%d, neg_height=%d, neg_fps=%d, max_bitrate_bps=%d", + self->encoder.neg_width, + self->encoder.neg_height, + self->encoder.neg_fps, + max_bitrate_bps + ); + + self->encoder.sEncParam.iInputCsp = videoFormatI420; + self->encoder.sEncParam.iSpatialLayerNum = 1; + self->encoder.sEncParam.iTemporalLayerNum = 1; + self->encoder.sEncParam.uiIntraPeriod = (self->encoder.neg_fps * CISCO_H264_GOP_SIZE_IN_SECONDS); + self->encoder.sEncParam.iUsageType = CAMERA_VIDEO_REAL_TIME; // TODO: use "SCREEN_CONTENT_REAL_TIME" screencast + self->encoder.sEncParam.iPicWidth = self->encoder.neg_width; + self->encoder.sEncParam.iPicHeight = self->encoder.neg_height; + self->encoder.sEncParam.iTargetBitrate = max_bitrate_bps; + self->encoder.sEncParam.iMaxBitrate = max_bitrate_bps; + self->encoder.sEncParam.fMaxFrameRate = (float)self->encoder.neg_fps; + self->encoder.sEncParam.uiMaxNalSize = H264_RTP_PAYLOAD_SIZE; + self->encoder.sEncParam.bEnableSpsPpsIdAddition = true; + self->encoder.sEncParam.bEnableFrameCroppingFlag = true; + + layer = &self->encoder.sEncParam.sSpatialLayers[0]; + layer->uiProfileIdc = PRO_BASELINE; +#if BUILD_TYPE_TCH + layer->uiLevelIdc = tdav_codec_h264_cisco_convert_level(common->level); +#else + layer->uiLevelIdc = LEVEL_UNKNOWN; // auto-detect +#endif + layer->fFrameRate = self->encoder.sEncParam.fMaxFrameRate; + layer->iMaxSpatialBitrate = self->encoder.sEncParam.iMaxBitrate; + layer->iSpatialBitrate = self->encoder.sEncParam.iTargetBitrate; + layer->iVideoWidth = self->encoder.sEncParam.iPicWidth; + layer->iVideoHeight = self->encoder.sEncParam.iPicHeight; + layer->sSliceCfg.uiSliceMode = SM_DYN_SLICE; + layer->sSliceCfg.sSliceArgument.uiSliceSizeConstraint = H264_RTP_PAYLOAD_SIZE; + layer->sSliceCfg.sSliceArgument.uiSliceNum = 1; + //layer->sSliceCfg.sSliceArgument.uiSliceMbNum[0] = 960; + + if ((err = self->encoder.pInst->InitializeExt(&self->encoder.sEncParam)) != cmResultSuccess) { + TSK_DEBUG_ERROR("InitializeExt failed: %ld", err); + goto bail; + } + + self->encoder.sEncPic.iColorFormat = videoFormatI420; + self->encoder.sEncPic.iPicWidth = self->encoder.sEncParam.iPicWidth; + self->encoder.sEncPic.iPicHeight = self->encoder.sEncParam.iPicHeight; + self->encoder.sEncPic.iStride[0] = self->encoder.sEncPic.iPicWidth; + self->encoder.sEncPic.iStride[1] = self->encoder.sEncPic.iStride[0] >> 1; + self->encoder.sEncPic.iStride[2] = self->encoder.sEncPic.iStride[1]; + + // Create encoder mutex + if (!self->encoder.mutex && !(self->encoder.mutex = tsk_mutex_create())) { + TSK_DEBUG_ERROR("Failed to create mutex for the encoder"); + goto bail; + } + + self->encoder.frame_count = 0; + + ret = 0; + +bail: + return ret; +} + +static int tdav_codec_h264_cisco_close_encoder(tdav_codec_h264_cisco_t* self, tsk_bool_t reset_rotation) +{ + if (self) { + if (self->encoder.pInst) { + self->encoder.pInst->Uninitialize(); + WelsDestroySVCEncoder(self->encoder.pInst); + self->encoder.pInst = NULL; + } + if (self->encoder.buffer) { + TSK_FREE(self->encoder.buffer); + } + if (self->encoder.mutex) { + tsk_mutex_destroy(&self->encoder.mutex); + } + self->encoder.frame_count = 0; + if (reset_rotation) { + self->encoder.rotation = 0; // reset rotation + } + } + return 0; +} + +int tdav_codec_h264_cisco_open_decoder(tdav_codec_h264_cisco_t* self) +{ + int ret = -1; + long err; + tdav_codec_h264_common_t* common = (tdav_codec_h264_common_t*)self; + SDecodingParam sDecParam = { 0 }; + + if (self->decoder.pInst) { + TSK_DEBUG_ERROR("Decoder already initialized"); + goto bail; + } + + // create decoder + if ((err = WelsCreateDecoder(&self->decoder.pInst)) != cmResultSuccess) { + TSK_DEBUG_ERROR("Failed to create decoder: %ld", err); + goto bail; + } + self->decoder.pInst->SetOption(DECODER_OPTION_TRACE_CALLBACK_CONTEXT, self); + self->decoder.pInst->SetOption(DECODER_OPTION_TRACE_CALLBACK, &__tdav_codec_h264_cisco_debug_cb); + + // initialize decoder + sDecParam.iOutputColorFormat = videoFormatI420; + sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC; + if ((err = self->decoder.pInst->Initialize(&sDecParam)) != cmResultSuccess) { + TSK_DEBUG_ERROR("Failed to initialize decoder: %ld", err); + goto bail; + } + self->decoder.last_seq = 0; + TSK_DEBUG_INFO("[OpenH264 Decoder] neg_width=%d, neg_height=%d, neg_fps=%d", + TMEDIA_CODEC_VIDEO(self)->in.width, + TMEDIA_CODEC_VIDEO(self)->in.height, + TMEDIA_CODEC_VIDEO(self)->in.fps + ); + ret = 0; + +bail: + return ret; +} + +static int tdav_codec_h264_cisco_close_decoder(tdav_codec_h264_cisco_t* self) +{ + if (self) { + if (self->decoder.pInst) { + self->decoder.pInst->Uninitialize(); + WelsDestroyDecoder(self->decoder.pInst); + self->decoder.pInst = NULL; + } + TSK_FREE(self->decoder.accumulator); + self->decoder.accumulator_pos = 0; + } + + return 0; +} + +static ELevelIdc tdav_codec_h264_cisco_convert_level(enum level_idc_e level) +{ + switch(level) { + case level_idc_1_0: return LEVEL_1_0; + case level_idc_1_b: return LEVEL_1_B; + case level_idc_1_1: return LEVEL_1_1; + case level_idc_1_2: return LEVEL_1_2; + case level_idc_1_3: return LEVEL_1_3; + case level_idc_2_0: return LEVEL_2_0; + case level_idc_2_1: return LEVEL_2_1; + case level_idc_2_2: return LEVEL_2_2; + case level_idc_3_0: return LEVEL_3_0; + case level_idc_3_1: return LEVEL_3_1; + case level_idc_3_2: return LEVEL_3_2; + case level_idc_4_0: return LEVEL_4_0; + case level_idc_4_1: return LEVEL_4_1; + case level_idc_4_2: return LEVEL_4_2; + case level_idc_5_0: return LEVEL_5_0; + case level_idc_5_1: return LEVEL_5_1; + case level_idc_5_2: return LEVEL_2_2; + default: return LEVEL_UNKNOWN; + } +} + +static void tdav_codec_h264_cisco_debug_cb(void* context, int level, const char* message) +{ + switch (level) { + case WELS_LOG_ERROR: + case WELS_LOG_QUIET: + TSK_DEBUG_ERROR("OpenH264: level=%d, message=%s", level, message); + break; + case WELS_LOG_WARNING: + TSK_DEBUG_WARN("OpenH264: level=%d, message=%s", level, message); + break; + default: + TSK_DEBUG_INFO("OpenH264: level=%d, message=%s", level, message); + break; + } +} + +static int tdav_codec_h264_cisco_init(tdav_codec_h264_cisco_t* self, profile_idc_t profile) +{ + int ret = -1; + level_idc_t level; + tdav_codec_h264_common_t* common = (tdav_codec_h264_common_t*)self; + + if (!self || profile != profile_idc_baseline) { + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + + if ((ret = tdav_codec_h264_common_init(common))) { + TSK_DEBUG_ERROR("tdav_codec_h264_cisco_common_init() faile with error code=%d", ret); + goto bail; + } + + if ((ret = tdav_codec_h264_common_level_from_size(TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height, &level))) { + TSK_DEBUG_ERROR("Failed to find level for size=[%u, %u]", TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height); + goto bail; + } + + common->pack_mode_local = H264_PACKETIZATION_MODE; + common->profile = profile; + common->level = level; + // A.2.1.1 Constrained Baseline profile + // Conformance of a bitstream to the Constrained Baseline profile is indicated by profile_idc being equal to 66 with + // constraint_set1_flag being equal to 1. + common->profile_iop = 0xe0; // "constraint_set0_flag=1 and constraint_set1_flag=1" -> Constrained Baseline profile + TMEDIA_CODEC_VIDEO(self)->in.max_mbps = TMEDIA_CODEC_VIDEO(self)->out.max_mbps = H264_MAX_MBPS*1000; + TMEDIA_CODEC_VIDEO(self)->in.max_br = TMEDIA_CODEC_VIDEO(self)->out.max_br = H264_MAX_BR*1000; + + TMEDIA_CODEC_VIDEO(self)->in.chroma = tmedia_chroma_yuv420p; + TMEDIA_CODEC_VIDEO(self)->out.chroma = tmedia_chroma_yuv420p; + + ret = 0; + +bail: + return ret; +} + +static int tdav_codec_h264_cisco_deinit(tdav_codec_h264_cisco_t* self) +{ + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tdav_codec_h264_cisco_close((tmedia_codec_t*)self); + + return 0; +} + +#endif /* HAVE_OPENH264 */ diff --git a/tinyDAV/src/codecs/h264/tdav_codec_h264_cuda.cxx b/tinyDAV/src/codecs/h264/tdav_codec_h264_cuda.cxx new file mode 100644 index 0000000..f9fbc11 --- /dev/null +++ b/tinyDAV/src/codecs/h264/tdav_codec_h264_cuda.cxx @@ -0,0 +1,1130 @@ +/* +* Copyright (C) 2011 Doubango Telecom <http://www.doubango.org>. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango(DOT)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_h264_cuda.c + * @brief H.264 codec plugin using NVIDIA CUDA for encoding/decoding + * Env: gpucomputingsdk_4.0.17_win_32, cudatoolkit_4.0.17_win_32 and 280.26-notebook-win7-winvista-32bit-international-whql. + * http://developer.download.nvidia.com/compute/DevZone/docs/html/C/doc/CUDA_VideoDecoder_Library.pdf + * http://developer.download.nvidia.com/compute/DevZone/docs/html/C/doc/CUDA_VideoEncoder_Library.pdf + * 2.0: https://developer.nvidia.com/sites/default/files/akamai/cuda/files/CUDADownloads/NVENC_VideoEncoder_API_ProgGuide.pdf + * + * RTP payloader/depayloader follows RFC 3984. + * + * @author Mamadou Diop <diopmamadou(at)doubango(DOT)org> + * + */ +#include "tinydav/codecs/h264/tdav_codec_h264_cuda.h" + +#if HAVE_CUDA + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#if defined(_MSC_VER) +# pragma comment(lib, "nvcuvenc.lib") +# pragma comment(lib, "nvcuvid.lib") +# pragma comment(lib, "cuda.lib") + +# pragma comment(lib, "d3d9.lib") +# pragma comment(lib, "d3dx9.lib") +#endif + +#include "tsk_memory.h" +#include "tsk_debug.h" + +#include <cutil_inline.h> +#include <cuda.h> + +#define tdav_codec_h264_cuda_fmtp_set tsk_null + +#if !defined(CUDA_MAX_FRM_CNT) +# define CUDA_MAX_FRM_CNT 10 +#endif + +#include "tsk_semaphore.h" +tsk_semaphore_handle_t *sem = tsk_null; + +#define InitH264DecoderInfo(_self) \ + memset(&_self->decoder.info, 0, sizeof(_self->decoder.info)); \ + _self->decoder.info.ulCreationFlags = cudaVideoCreate_PreferCUDA; \ + _self->decoder.info.CodecType = cudaVideoCodec_H264; \ + _self->decoder.info.ulWidth = TMEDIA_CODEC_VIDEO(_self)->in.width; \ + _self->decoder.info.ulTargetWidth = TMEDIA_CODEC_VIDEO(_self)->in.width; \ + _self->decoder.info.ulHeight = TMEDIA_CODEC_VIDEO(_self)->in.height; \ + _self->decoder.info.ulTargetHeight = TMEDIA_CODEC_VIDEO(_self)->in.height; \ + _self->decoder.info.ulNumDecodeSurfaces = CUDA_MAX_FRM_CNT; \ + _self->decoder.info.ulNumOutputSurfaces = 1; \ + _self->decoder.info.ChromaFormat = cudaVideoChromaFormat_420; \ + _self->decoder.info.OutputFormat = cudaVideoSurfaceFormat_NV12; \ + _self->decoder.info.DeinterlaceMode = cudaVideoDeinterlaceMode_Adaptive; + +static int CUDAAPI _NVCallback_HandleVideoSequence(void *pvUserData, CUVIDEOFORMAT *pFormat); +static int CUDAAPI _NVCallback_HandlePictureDecode(void *pvUserData, CUVIDPICPARAMS *pPicParams); +static int CUDAAPI _NVCallback_HandlePictureDisplay(void *pvUserData, CUVIDPARSERDISPINFO *pPicParams); +static unsigned char* CUDAAPI _NVCallback_HandleAcquireBitStream(int *pBufferSize, void *pUserdata); +static void CUDAAPI _NVCallback_HandleReleaseBitStream(int nBytesInBuffer, unsigned char *cb,void *pUserdata); +static void CUDAAPI _NVCallback_HandleOnBeginFrame(const NVVE_BeginFrameInfo *pbfi, void *pUserdata); +static void CUDAAPI _NVCallback_HandleOnEndFrame(const NVVE_EndFrameInfo *pefi, void *pUserdata); + +static inline void _tdav_codec_h264_cuda_encap(const tdav_codec_h264_cuda_t* h264, const uint8_t* pdata, tsk_size_t size); +static inline tsk_size_t _tdav_codec_h264_cuda_pict_layout(tdav_codec_h264_cuda_t* self, void**output, tsk_size_t *output_size); + +static int tdav_codec_h264_cuda_open(tmedia_codec_t* self) +{ + int ret = 0, i; + int bestGPU = 0, gpuPerf = 0, adapterCount; + static int low_latency = 1; + HRESULT hr; + CUresult cuResult; + D3DPRESENT_PARAMETERS d3dpp; + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)self; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + // + // encoder + // + memset(&h264->encoder.clb_params, 0, sizeof(h264->encoder.clb_params)); + memset(&h264->encoder.ctx_params, 0, sizeof(h264->encoder.ctx_params)); + + h264->encoder.ctx_params.iInputSize[0] = TMEDIA_CODEC_VIDEO(h264)->out.width; + h264->encoder.ctx_params.iInputSize[1] = TMEDIA_CODEC_VIDEO(h264)->out.height; + h264->encoder.ctx_params.iOutputSize[0] = TMEDIA_CODEC_VIDEO(h264)->out.width; + h264->encoder.ctx_params.iOutputSize[1] = TMEDIA_CODEC_VIDEO(h264)->out.height; + h264->encoder.ctx_params.GPUOffloadLevel= NVVE_GPU_OFFLOAD_DEFAULT; + h264->encoder.ctx_params.iSurfaceFormat = (int)IYUV; + h264->encoder.ctx_params.iPictureType = (int)FRAME_PICTURE; + h264->encoder.ctx_params.Fieldmode = MODE_FRAME; + h264->encoder.ctx_params.Presets = (NVVE_PRESETS_TARGET)-1;//Should be iPod, Zune ... + h264->encoder.ctx_params.iP_Interval = 1; + h264->encoder.ctx_params.iAspectRatio[0] = 4; + h264->encoder.ctx_params.iAspectRatio[1] = 3; + h264->encoder.ctx_params.iAspectRatio[2] = 0; + h264->encoder.ctx_params.iIDR_Period = TMEDIA_CODEC_VIDEO(h264)->out.fps * 3; + h264->encoder.ctx_params.iUseDeviceMem = 0; + h264->encoder.ctx_params.iDynamicGOP = 0; + h264->encoder.ctx_params.RCType = RC_VBR; + h264->encoder.ctx_params.iAvgBitrate = 400000; + h264->encoder.ctx_params.iPeakBitrate = 800000; + h264->encoder.ctx_params.iQP_Level_Intra = 25; + h264->encoder.ctx_params.iQP_Level_InterP = 28; + h264->encoder.ctx_params.iQP_Level_InterB = 31; + h264->encoder.ctx_params.iFrameRate[0] = TMEDIA_CODEC_VIDEO(h264)->out.fps * 1000; + h264->encoder.ctx_params.iFrameRate[1] = 1000; + h264->encoder.ctx_params.iDeblockMode = 1; + h264->encoder.ctx_params.iForceIntra = 0; + h264->encoder.ctx_params.iForceIDR = 0; + h264->encoder.ctx_params.iClearStat = 0; + h264->encoder.ctx_params.DIMode = DI_MEDIAN; + h264->encoder.ctx_params.iDisableSPSPPS = 1; // Do not include SPS/PPS frames + h264->encoder.ctx_params.iNaluFramingType = 0; // StartCodes + h264->encoder.ctx_params.iMultiGPU = 1; + switch(TDAV_CODEC_H264_COMMON(h264)->profile){ + case tdav_codec_h264_bp10: + h264->encoder.ctx_params.iDisableCabac = 1; + h264->encoder.ctx_params.iProfileLevel = 0xff42; + break; + case tdav_codec_h264_bp20: + h264->encoder.ctx_params.iDisableCabac = 1; + h264->encoder.ctx_params.iProfileLevel = 0xff42; + break; + case tdav_codec_h264_bp30: + h264->encoder.ctx_params.iDisableCabac = 1; + h264->encoder.ctx_params.iProfileLevel = 0xff42; + break; + default: + break; + } + + hr = NVCreateEncoder(&h264->encoder.context); + if(FAILED(hr)){ + TSK_DEBUG_ERROR("NVCreateEncoder failed with error code = %08x", hr); + return -2; + } + + hr = NVSetCodec(h264->encoder.context, NV_CODEC_TYPE_H264); + if(FAILED(hr)){ + TSK_DEBUG_ERROR("NVSetCodec failed with error code = %08x", hr); + return -2; + } + + hr = NVSetDefaultParam(h264->encoder.context); + if(FAILED(hr)){ + TSK_DEBUG_ERROR("NVSetDefaultParam() failed with error code = %08x", hr); + return -2; + } + + hr = NVGetParamValue(h264->encoder.context, NVVE_GET_GPU_COUNT, &h264->encoder.ctx_params.GPU_count); + if(SUCCEEDED(hr)){ + int temp = 0, deviceCount; + for (deviceCount=0; deviceCount < h264->encoder.ctx_params.GPU_count; deviceCount++) { + NVVE_GPUAttributes GPUAttributes = {0}; + + GPUAttributes.iGpuOrdinal = deviceCount; + hr = NVGetParamValue(h264->encoder.context, NVVE_GET_GPU_ATTRIBUTES, &GPUAttributes); + if(FAILED(hr)){ + TSK_DEBUG_ERROR("NVGetParamValue(NVVE_GET_GPU_ATTRIBUTES) failed with error code = %08x", hr); + continue; + } + + temp = GPUAttributes.iClockRate * GPUAttributes.iMultiProcessorCount; + temp = temp * _ConvertSMVer2Cores(GPUAttributes.iMajor, GPUAttributes.iMinor); + + if(temp > gpuPerf){ + gpuPerf = temp; + bestGPU = deviceCount; + } + } + } + else{ + TSK_DEBUG_ERROR("NVGetParamValue(NVVE_GET_GPU_COUNT) failed with error code = %08x", hr); + return -2; + } + + h264->encoder.ctx_params.iForcedGPU = bestGPU; + hr = NVSetParamValue(h264->encoder.context, NVVE_FORCE_GPU_SELECTION, &h264->encoder.ctx_params.iForcedGPU); + if(FAILED(hr)){ + TSK_DEBUG_WARN("NVSetParamValue(NVVE_FORCE_GPU_SELECTION) failed with error code = %08x", hr); + } + + hr = NVSetParamValue(h264->encoder.context, NVVE_DEVICE_MEMORY_INPUT, &(h264->encoder.ctx_params.iUseDeviceMem)); + if(FAILED(hr)){ + TSK_DEBUG_ERROR("NVSetParamValue(NVVE_OUT_SIZE) failed with error code = %08x", hr); + return -2; + } + + h264->encoder.buffer_size = (h264->encoder.ctx_params.iOutputSize[1] * h264->encoder.ctx_params.iOutputSize[0] * 3) >> 4; + if(!h264->encoder.buffer && !(h264->encoder.buffer = tsk_realloc(h264->encoder.buffer, h264->encoder.buffer_size))){ + TSK_DEBUG_ERROR("Failed to allocate buffer with size=%u", h264->encoder.buffer_size); + h264->encoder.buffer_size = 0; + return -2; + } + + hr = NVSetParamValue(h264->encoder.context,NVVE_OUT_SIZE, &(h264->encoder.ctx_params.iOutputSize)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_OUT_SIZE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_IN_SIZE, &(h264->encoder.ctx_params.iInputSize)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_IN_SIZE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_MULTI_GPU, &(h264->encoder.ctx_params.iMultiGPU)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_MULTI_GPU) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_ASPECT_RATIO, &(h264->encoder.ctx_params.iAspectRatio));if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_ASPECT_RATIO) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_FIELD_ENC_MODE, &(h264->encoder.ctx_params.Fieldmode)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_FIELD_ENC_MODE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_P_INTERVAL, &(h264->encoder.ctx_params.iP_Interval)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_P_INTERVAL) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_IDR_PERIOD, &(h264->encoder.ctx_params.iIDR_Period)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_IDR_PERIOD) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_DYNAMIC_GOP, &(h264->encoder.ctx_params.iDynamicGOP)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_DYNAMIC_GOP) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_RC_TYPE, &(h264->encoder.ctx_params.RCType)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_RC_TYPE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_AVG_BITRATE, &(h264->encoder.ctx_params.iAvgBitrate)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_AVG_BITRATE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_PEAK_BITRATE, &(h264->encoder.ctx_params.iPeakBitrate)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_PEAK_BITRATE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_QP_LEVEL_INTRA, &(h264->encoder.ctx_params.iQP_Level_Intra)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_OUT_SIZE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_QP_LEVEL_INTER_P,&(h264->encoder.ctx_params.iQP_Level_InterP)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_QP_LEVEL_INTER_P) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_QP_LEVEL_INTER_B,&(h264->encoder.ctx_params.iQP_Level_InterB)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_QP_LEVEL_INTER_B) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_FRAME_RATE, &(h264->encoder.ctx_params.iFrameRate)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_FRAME_RATE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_DEBLOCK_MODE, &(h264->encoder.ctx_params.iDeblockMode)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_DEBLOCK_MODE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_PROFILE_LEVEL, &(h264->encoder.ctx_params.iProfileLevel)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_PROFILE_LEVEL) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_FORCE_INTRA, &(h264->encoder.ctx_params.iForceIntra)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_FORCE_INTRA) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_FORCE_IDR, &(h264->encoder.ctx_params.iForceIDR)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_FORCE_IDR) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_CLEAR_STAT, &(h264->encoder.ctx_params.iClearStat)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_CLEAR_STAT) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_SET_DEINTERLACE,&(h264->encoder.ctx_params.DIMode)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_SET_DEINTERLACE) failed with error code = %08x", hr); } + if (h264->encoder.ctx_params.Presets != -1) { + hr = NVSetParamValue(h264->encoder.context,NVVE_PRESETS, &(h264->encoder.ctx_params.Presets)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_PRESETS) failed with error code = %08x", hr); } + } + hr = NVSetParamValue(h264->encoder.context,NVVE_DISABLE_CABAC, &(h264->encoder.ctx_params.iDisableCabac)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_DISABLE_CABAC) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_CONFIGURE_NALU_FRAMING_TYPE, &(h264->encoder.ctx_params.iNaluFramingType)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_CONFIGURE_NALU_FRAMING_TYPE) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_DISABLE_SPS_PPS,&(h264->encoder.ctx_params.iDisableSPSPPS)); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_DISABLE_SPS_PPS) failed with error code = %08x", hr); } + hr = NVSetParamValue(h264->encoder.context,NVVE_LOW_LATENCY,&low_latency); if (hr!=S_OK) { TSK_DEBUG_WARN("NVSetParamValue(NVVE_LOW_LATENCY) failed with error code = %08x", hr); } + + h264->encoder.clb_params.pfnacquirebitstream = _NVCallback_HandleAcquireBitStream; + h264->encoder.clb_params.pfnonbeginframe = _NVCallback_HandleOnBeginFrame; + h264->encoder.clb_params.pfnonendframe = _NVCallback_HandleOnEndFrame; + h264->encoder.clb_params.pfnreleasebitstream = _NVCallback_HandleReleaseBitStream; + NVRegisterCB(h264->encoder.context, h264->encoder.clb_params, h264); + + + hr = NVCreateHWEncoder(h264->encoder.context); + if(FAILED(hr)){ + TSK_DEBUG_ERROR("NVCreateHWEncoder failed with error code = %08x", hr); + return -2; + } + + + + // + // decoder + // + if((cuResult = cuInit(0)) != CUDA_SUCCESS){ + TSK_DEBUG_ERROR("cuInit(0) failed with error code = %d", (int)cuResult); + return -3; + } + + InitH264DecoderInfo(h264); + + h264->decoder.cu_device = cutilDrvGetMaxGflopsGraphicsDeviceId(); + +#if _DEBUG || DEBUG + { + int major, minor; + size_t totalGlobalMem; + char deviceName[256]; + cuDeviceComputeCapability(&major, &minor, h264->decoder.cu_device); + cuDeviceGetName(deviceName, sizeof(deviceName), h264->decoder.cu_device); + TSK_DEBUG_INFO("Using GPU Device %d: %s has SM %d.%d compute capability", h264->decoder.cu_device, deviceName, major, minor); + + cutilDrvSafeCallNoSync(cuDeviceTotalMem(&totalGlobalMem, h264->decoder.cu_device) ); + TSK_DEBUG_INFO("Total amount of global memory in GPU device: %4.4f MB", (float)totalGlobalMem/(1024*1024)); + } +#endif + + // create Direct3D instance + h264->decoder.dx_d3d = Direct3DCreate9(D3D_SDK_VERSION); + if(!h264->decoder.dx_d3d){ + TSK_DEBUG_ERROR("Direct3DCreate9 failed"); + return -3; + } + adapterCount = h264->decoder.dx_d3d->GetAdapterCount(); + for(i=0; i<adapterCount; ++i){ + ZeroMemory(&d3dpp, sizeof(d3dpp)); + d3dpp.Windowed = TRUE; + d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; + d3dpp.BackBufferWidth = h264->decoder.info.ulTargetWidth; + d3dpp.BackBufferHeight = h264->decoder.info.ulTargetHeight; + d3dpp.BackBufferCount = 1; + d3dpp.SwapEffect = D3DSWAPEFFECT_COPY; + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + d3dpp.Flags = D3DPRESENTFLAG_VIDEO;//D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; + hr = h264->decoder.dx_d3d->CreateDevice(i, + D3DDEVTYPE_HAL, + GetDesktopWindow(), + D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED | D3DCREATE_HARDWARE_VERTEXPROCESSING, + &d3dpp, + &h264->decoder.dx_d3ddevice); + if(hr == S_OK){ + cuResult = cuD3D9CtxCreate(&h264->decoder.cu_context, &h264->decoder.cu_device, 0, h264->decoder.dx_d3ddevice); + if(cuResult == CUDA_SUCCESS){ + break; + } + if(h264->decoder.dx_d3ddevice){ + h264->decoder.dx_d3ddevice->Release(); + h264->decoder.dx_d3ddevice = NULL; + } + } + } + + memset(&h264->decoder.cu_paser_params, 0, sizeof(h264->decoder.cu_paser_params)); + h264->decoder.cu_paser_params.CodecType = cudaVideoCodec_H264; + h264->decoder.cu_paser_params.ulMaxNumDecodeSurfaces = CUDA_MAX_FRM_CNT; + h264->decoder.cu_paser_params.pUserData = h264; + h264->decoder.cu_paser_params.pfnSequenceCallback = _NVCallback_HandleVideoSequence; + h264->decoder.cu_paser_params.pfnDecodePicture = _NVCallback_HandlePictureDecode; + h264->decoder.cu_paser_params.pfnDisplayPicture = _NVCallback_HandlePictureDisplay; + cuResult = cuvidCreateVideoParser(&h264->decoder.cu_parser, &h264->decoder.cu_paser_params); + if(cuResult != CUDA_SUCCESS){ + TSK_DEBUG_ERROR("cuvidCreateVideoParser(0) failed with error code = %d", (int)cuResult); + return -3; + } + + cuResult = cuvidCreateDecoder(&h264->decoder.context, &h264->decoder.info); + if(CUDA_SUCCESS != cuResult){ + TSK_DEBUG_ERROR("cuvidCreateDecoder failed with error code=%d", (int)cuResult); + return -3; + } + + return ret; +} + +static int tdav_codec_h264_cuda_close(tmedia_codec_t* self) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)self; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(h264->encoder.context){ + NVDestroyEncoder(h264->encoder.context); + h264->encoder.context = NULL; + } + return 0; +} + +static tsk_size_t tdav_codec_h264_cuda_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + NVVE_EncodeFrameParams efparams; + int ret = 0; + unsigned long flags = 0; + HRESULT hr; + + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)self; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(!self->opened){ + TSK_DEBUG_ERROR("Codec not opened"); + return 0; + } + + if((h264->encoder.ctx_params.iOutputSize[1] * h264->encoder.ctx_params.iOutputSize[0] * 3)>>1 != in_size){ + /* guard */ + TSK_DEBUG_ERROR("Invalid size"); + return 0; + } + + //return 0; + + efparams.Width = h264->encoder.ctx_params.iOutputSize[0]; + efparams.Height = h264->encoder.ctx_params.iOutputSize[1]; + efparams.Pitch = (h264->encoder.ctx_params.nDeviceMemPitch ? h264->encoder.ctx_params.nDeviceMemPitch : h264->encoder.ctx_params.iOutputSize[0]); + efparams.PictureStruc = (NVVE_PicStruct)h264->encoder.ctx_params.iPictureType; + efparams.SurfFmt = (NVVE_SurfaceFormat)h264->encoder.ctx_params.iSurfaceFormat; + efparams.progressiveFrame = (h264->encoder.ctx_params.iSurfaceFormat == 3) ? 1 : 0; + efparams.repeatFirstField = 0; + efparams.topfieldfirst = (h264->encoder.ctx_params.iSurfaceFormat == 1) ? 1 : 0; + efparams.picBuf = (unsigned char *)in_data; + efparams.bLast = 0; + + // send keyframe for: + // - the first frame + // - every second within the first 4seconds + // - every 7 seconds after the first 4seconds + if(h264->encoder.frame_count++ == 0 + || + ( (h264->encoder.frame_count < (int)TMEDIA_CODEC_VIDEO(h264)->out.fps * 4) && ((h264->encoder.frame_count % TMEDIA_CODEC_VIDEO(h264)->out.fps)==0) ) + || + ( (h264->encoder.frame_count % (TMEDIA_CODEC_VIDEO(h264)->out.fps * 5))==0 ) + ) + { + flags |= 0x04; // FORCE IDR + if(h264->encoder.ctx_params.iDisableSPSPPS){ + unsigned char SPSPPSBuff[1024]; + int SPSPPSBuffSize = sizeof(SPSPPSBuff); + hr = NVGetSPSPPS(h264->encoder.context, SPSPPSBuff, SPSPPSBuffSize, &SPSPPSBuffSize); + if(SUCCEEDED(hr)){ + int size = 0; + while(size < SPSPPSBuffSize - 2){ + int16_t next_size = ((int16_t)SPSPPSBuff[size])<<1 | ((int16_t)SPSPPSBuff[size + 1]); + _tdav_codec_h264_cuda_encap(h264, &SPSPPSBuff[size + 2], next_size); + size+=next_size + 2; + } + } + else{ + TSK_DEBUG_ERROR("NVGetSPSPPS failed with error code = %08x", hr) + } + } + } + + hr = NVEncodeFrame(h264->encoder.context, &efparams, flags, NULL); + + return 0; +} + +static tsk_size_t tdav_codec_h264_cuda_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) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)self; + const trtp_rtp_header_t* rtp_hdr = (const trtp_rtp_header_t*)proto_hdr; + const uint8_t* pay_ptr = tsk_null; + tsk_size_t pay_size = 0, retsize = 0, size_to_copy = 0; + int ret = 0; + tsk_bool_t append_scp = tsk_false; + static tsk_size_t xmax_size = (1920 * 1080 * 3) >> 3; + static tsk_size_t start_code_prefix_size = sizeof(H264_START_CODE_PREFIX); + + // Packet lost? + if(h264->decoder.last_seq != (rtp_hdr->seq_num - 1) && h264->decoder.last_seq){ + if(h264->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); + } + h264->decoder.last_seq = rtp_hdr->seq_num; + + /* 5.3. NAL Unit Octet Usage + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + */ + if(*((uint8_t*)in_data) >> 7){ + TSK_DEBUG_WARN("F=1"); + /* reset accumulator */ + h264->decoder.accumulator_pos = 0; + goto bail; + } + + // get payload + if((ret = tdav_codec_h264_get_pay(in_data, in_size, (const void**)&pay_ptr, &pay_size, &append_scp)) || !pay_ptr || !pay_size){ + TSK_DEBUG_ERROR("Depayloader failed to get H.264 content"); + goto bail; + } + //append_scp = tsk_true; + size_to_copy = pay_size + (append_scp ? start_code_prefix_size : 0); + + // start-accumulator + if(!h264->decoder.accumulator){ + if(size_to_copy > xmax_size){ + TSK_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", size_to_copy, xmax_size); + goto bail; + } + if(!(h264->decoder.accumulator = tsk_calloc(size_to_copy, sizeof(uint8_t)))){ + TSK_DEBUG_ERROR("Failed to allocated new buffer"); + goto bail; + } + h264->decoder.accumulator_size = size_to_copy; + } + if((h264->decoder.accumulator_pos + size_to_copy) >= xmax_size){ + TSK_DEBUG_ERROR("BufferOverflow"); + h264->decoder.accumulator_pos = 0; + goto bail; + } + if((h264->decoder.accumulator_pos + size_to_copy) > h264->decoder.accumulator_size){ + if(!(h264->decoder.accumulator = tsk_realloc(h264->decoder.accumulator, (h264->decoder.accumulator_pos + size_to_copy)))){ + TSK_DEBUG_ERROR("Failed to reallocated new buffer"); + h264->decoder.accumulator_pos = 0; + h264->decoder.accumulator_size = 0; + goto bail; + } + h264->decoder.accumulator_size = (h264->decoder.accumulator_pos + size_to_copy); + } + + if(append_scp){ + memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], H264_START_CODE_PREFIX, start_code_prefix_size); + h264->decoder.accumulator_pos += start_code_prefix_size; + } + memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], pay_ptr, pay_size); + h264->decoder.accumulator_pos += pay_size; + // end-accumulator + + if(rtp_hdr->marker){ + CUVIDSOURCEDATAPACKET pkt; + CUresult cuResult; + pkt.flags = 0; + pkt.payload_size = (unsigned long) h264->decoder.accumulator_pos; + pkt.payload = (unsigned char *)h264->decoder.accumulator; + pkt.timestamp = 0; + + // reset accumulator + h264->decoder.accumulator_pos = 0; + cuResult = cuvidParseVideoData(h264->decoder.cu_parser, &pkt); + if(cuResult != CUDA_SUCCESS){ + TSK_DEBUG_ERROR("cuvidParseVideoData() failed with error code = %d", (int)cuResult); + goto bail; + } + + if(h264->decoder.cu_buffer_avail){ + h264->decoder.cu_buffer_avail = tsk_false; + if((retsize = _tdav_codec_h264_cuda_pict_layout(h264, out_data, out_max_size)) == 0){ + TSK_DEBUG_ERROR("_tdav_codec_h264_cuda_pict_layout failed"); + goto bail; + } + } + } + +bail: + return retsize; +} + +static tsk_bool_t tdav_codec_h264_cuda_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)codec; + profile_idc_t profile; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + /* Check whether the profile match (If the profile is missing, then we consider that it's ok) */ + if(((profile = tdav_codec_h264_common_get_profile(fmtp)) != tdav_codec_h264_bp99) && (profile != TDAV_CODEC_H264_COMMON(h264)->profile)){ + TSK_DEBUG_INFO("Profile not matching"); + return tsk_false; + } + + TMEDIA_CODEC_VIDEO(h264)->in.width = 800, TMEDIA_CODEC_VIDEO(h264)->in.height = 640; + TMEDIA_CODEC_VIDEO(h264)->out.width = 800, TMEDIA_CODEC_VIDEO(h264)->out.height = 640; + //TMEDIA_CODEC_VIDEO(h264)->in.width = 352, TMEDIA_CODEC_VIDEO(h264)->in.height = 288; + //TMEDIA_CODEC_VIDEO(h264)->out.width = 352, TMEDIA_CODEC_VIDEO(h264)->out.height = 288; + + return tsk_true; +} + +static char* tdav_codec_h264_cuda_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + char* fmtp = tsk_null; + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)self; + + switch(TDAV_CODEC_H264_COMMON(h264)->profile){ + case tdav_codec_h264_bp10: + fmtp = tsk_strdup("profile-level-id=42e00a"); + break; + case tdav_codec_h264_bp20: + fmtp = tsk_strdup("profile-level-id=42e014"); + break; + case tdav_codec_h264_bp30: + fmtp = tsk_strdup("profile-level-id=42e01e"); + break; + } + + //1080p(1920 x 1080), 720p(1280 x 720), SVGA(800 x 600), VGA(640 x 480), 4CIF(704 x 576), CIF(352 x 288), QCIF(176 x 144), SQCIF(128 x 96) + return fmtp; +} + +tsk_bool_t tdav_codec_h264_cuda_is_supported() +{ + static tsk_bool_t __already_checked = tsk_false; + static tsk_bool_t __is_supported = tsk_false; + if(!__already_checked){ + HRESULT hr; + __already_checked = tsk_true; + hr = NVGetHWEncodeCaps(); + if(SUCCEEDED(hr)){ + NVEncoder encoder; + hr = NVCreateEncoder(&encoder); + if(SUCCEEDED(hr)){ + hr = NVIsSupportedCodec(encoder, NV_CODEC_TYPE_H264); + __is_supported = SUCCEEDED(hr); + } + else{ + TSK_DEBUG_ERROR("NVCreateEncoder() failed with error code = %08x", hr); + } + if(encoder){ + NVDestroyEncoder(encoder); + encoder = NULL; + } + } + } + return __is_supported; +} + +static int tdav_codec_h264_cuda_init(tdav_codec_h264_cuda_t* self, profile_idc_t profile) +{ + int ret = 0; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if((ret = tdav_codec_h264_common_init(TDAV_CODEC_H264_COMMON(self)))){ + TSK_DEBUG_ERROR("tdav_codec_h264_common_init() faile with error code=%d", ret); + return ret; + } + + if(!self->decoder.mutex && !(self->decoder.mutex = tsk_mutex_create_2(tsk_false))){ + TSK_DEBUG_ERROR("Failed to create mutex"); + return -2; + } + + sem = tsk_semaphore_create_2(1); + + TDAV_CODEC_H264_COMMON(self)->pack_mode_local = H264_PACKETIZATION_MODE; + TDAV_CODEC_H264_COMMON(self)->profile = profile; + TMEDIA_CODEC_VIDEO(self)->in.max_mbps = TMEDIA_CODEC_VIDEO(self)->out.max_mbps = H264_MAX_MBPS*1000; + TMEDIA_CODEC_VIDEO(self)->in.max_br = TMEDIA_CODEC_VIDEO(self)->out.max_br = H264_MAX_BR*1000; + + // At this time self->plugin is Null + TMEDIA_CODEC_VIDEO(self)->in.width = TMEDIA_CODEC_VIDEO(self)->out.width = 176; + TMEDIA_CODEC_VIDEO(self)->in.height = TMEDIA_CODEC_VIDEO(self)->out.height = 144; + TMEDIA_CODEC_VIDEO(self)->in.fps = TMEDIA_CODEC_VIDEO(self)->out.fps = 15; + TMEDIA_CODEC_VIDEO(self)->in.chroma = tmedia_chroma_yuv420p;// no choice + + return 0; +} + +static int tdav_codec_h264_cuda_deinit(tdav_codec_h264_cuda_t* self) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)self; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(h264->encoder.context){ + NVDestroyEncoder(h264->encoder.context); + h264->encoder.context = NULL; + } + TSK_FREE(h264->encoder.buffer); + h264->encoder.buffer_size = 0; + + if(h264->decoder.context){ + cuvidDestroyDecoder(h264->decoder.context); + h264->decoder.context = NULL; + } + if(h264->decoder.cu_context){ + cuCtxDestroy(h264->decoder.cu_context); + h264->decoder.cu_context = NULL; + } + if (h264->decoder.dx_d3ddevice){ + h264->decoder.dx_d3ddevice->Release(); + h264->decoder.dx_d3ddevice = NULL; + } + if (h264->decoder.dx_d3d){ + h264->decoder.dx_d3d->Release(); + h264->decoder.dx_d3d = NULL; + } + if(h264->decoder.cu_parser){ + cuvidDestroyVideoParser(h264->decoder.cu_parser); + h264->decoder.cu_parser = NULL; + } + if(h264->decoder.cu_buffer){ + cuMemFreeHost(h264->decoder.cu_buffer); + h264->decoder.cu_buffer = NULL; + } + h264->decoder.cu_buffer_size = 0; + if(self->decoder.mutex){ + tsk_mutex_destroy(&self->decoder.mutex); + } + + TSK_FREE(h264->decoder.accumulator); + h264->decoder.accumulator_pos = 0; + h264->decoder.accumulator_size = 0; + + return 0; +} + +static inline void _tdav_codec_h264_cuda_encap(const tdav_codec_h264_cuda_t* h264, const uint8_t* pdata, tsk_size_t size) +{ + register int32_t i; + int32_t last_scp, prev_scp; + static int32_t size_of_scp = sizeof(H264_START_CODE_PREFIX); /* we know it's equal to 4 ..but */ + + if(!pdata || !size){ + return; + } + + last_scp = 0, prev_scp = 0; + + for(i = size_of_scp; i<(int32_t)(size - size_of_scp); i++){ + if(pdata[i] == H264_START_CODE_PREFIX[0] && pdata[i+1] == H264_START_CODE_PREFIX[1] && pdata[i+2] == H264_START_CODE_PREFIX[2] && pdata[i+3] == H264_START_CODE_PREFIX[3]){ /* Found Start Code Prefix */ + prev_scp = last_scp; + if((i - last_scp) >= H264_RTP_PAYLOAD_SIZE || 1){ + tdav_codec_h264_rtp_callback(TDAV_CODEC_H264_COMMON(h264), pdata + prev_scp, + (i - prev_scp), (prev_scp == size)); + } + last_scp = i; + } + } + if(last_scp < (int32_t)size){ + tdav_codec_h264_rtp_callback(TDAV_CODEC_H264_COMMON(h264), pdata + last_scp, + (size - last_scp), tsk_true); + } +} + +static inline tsk_size_t _tdav_codec_h264_cuda_pict_layout(tdav_codec_h264_cuda_t* self, void**output, tsk_size_t *output_size) +{ + if(self && self->decoder.cu_buffer && self->decoder.cu_buffer_size){ + unsigned int w = TMEDIA_CODEC_VIDEO(self)->in.width; + unsigned int h = TMEDIA_CODEC_VIDEO(self)->in.height; + unsigned int pitch = self->decoder.cu_buffer_pitch; + tsk_size_t xsize = (w * h * 3) >> 1; + // resize if too short + if(*output_size<xsize){ + if((*output = tsk_realloc(*output, xsize))){ + *output_size = xsize; + } + else{ + *output_size = 0; + return 0; + } + } + + tsk_semaphore_decrement(sem); + + register unsigned int y; + register unsigned int x, x2; + const unsigned char *p = (const unsigned char *)self->decoder.cu_buffer; + register unsigned char *iyuv = (unsigned char *)*output, *i = iyuv, *j; + // copy luma + for (y=0; y<h; y++){ + memcpy(i+y*w, p+y*pitch, w); + } + // de-interleave chroma (NV12 stored as U,V,U,V,...) + p += h * pitch; + i += h * w; + j = i + (h/2)*(w/2); + for (y=0; y<h/2; y++){ + for (x=0, x2=0; x<w/2; x++, x2+=2) i[x] = p[x2], j[x] = p[x2+1]; + p += pitch, i += w/2, j += w/2; + } + + tsk_semaphore_increment(sem); + + return xsize; + } + return 0; +} + +static int CUDAAPI _NVCallback_HandleVideoSequence(void *pvUserData, CUVIDEOFORMAT *pFormat) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)pvUserData; + if(!h264 || !pFormat){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0;//error + } + + int ret = 1; + + if(pFormat->coded_width != TMEDIA_CODEC_VIDEO(h264)->in.width || pFormat->coded_height != TMEDIA_CODEC_VIDEO(h264)->in.height){ + tsk_mutex_lock(h264->decoder.mutex); + + TMEDIA_CODEC_VIDEO(h264)->in.width = pFormat->coded_width; + TMEDIA_CODEC_VIDEO(h264)->in.height = pFormat->coded_height; + + InitH264DecoderInfo(h264); + CUresult cuResult; + if(h264->decoder.context){ + cuResult = cuvidDestroyDecoder(h264->decoder.context); + if(CUDA_SUCCESS != cuResult){ + TSK_DEBUG_ERROR("cuvidDestroyDecoder failed with error code=%d", (int)cuResult); + ret = 0; + } + h264->decoder.context = NULL; + } + cuResult = cuvidCreateDecoder(&h264->decoder.context, &h264->decoder.info); + if(CUDA_SUCCESS != cuResult){ + TSK_DEBUG_ERROR("cuvidCreateDecoder failed with error code=%d", (int)cuResult); + ret = 0; + } + + tsk_mutex_unlock(h264->decoder.mutex); + } + + + + return ret;//success +} + +static int CUDAAPI _NVCallback_HandlePictureDecode(void *pvUserData, CUVIDPICPARAMS *pPicParams) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)pvUserData; + if(!h264 || !pPicParams){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0;//error + } + + tsk_mutex_lock(h264->decoder.mutex); + CUresult cuResult = cuvidDecodePicture(h264->decoder.context, pPicParams); + tsk_mutex_unlock(h264->decoder.mutex); + + if(cuResult != CUDA_SUCCESS){ + TSK_DEBUG_ERROR("cuvidDecodePicture failed with error code= %d", cuResult); + return 0;//error + } + + return 1;//success +} + +static int CUDAAPI _NVCallback_HandlePictureDisplay(void *pvUserData, CUVIDPARSERDISPINFO *pPicParams) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)pvUserData; + CUVIDPROCPARAMS vpp; + CUdeviceptr devPtr; + CUresult cuResult; + tsk_size_t nv12_size; + tsk_bool_t mapped = tsk_false; + int ret = 1;//success + + if(!h264 || !pPicParams){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0;//error + } +tsk_semaphore_decrement(sem); + cuResult = cuCtxPushCurrent(h264->decoder.cu_context); + if(cuResult != CUDA_SUCCESS){ + TSK_DEBUG_ERROR("cuCtxPushCurrent failed with error code = %d", (int)cuResult); + ret = 0;//error + goto bail; + } + + memset(&vpp, 0, sizeof(vpp)); + vpp.progressive_frame = pPicParams->progressive_frame; + vpp.top_field_first = pPicParams->top_field_first; + cuResult = cuvidMapVideoFrame(h264->decoder.context, pPicParams->picture_index, &devPtr, &h264->decoder.cu_buffer_pitch, &vpp); + + if(cuResult != CUDA_SUCCESS){ + TSK_DEBUG_ERROR("cuvidMapVideoFrame failed with error code = %d", (int)cuResult); + ret = 0;//error + goto bail; + } + mapped = tsk_true; + nv12_size = h264->decoder.cu_buffer_pitch * (h264->decoder.info.ulTargetHeight + h264->decoder.info.ulTargetHeight/2); // 12bpp + //nv12_size = (w * h * 3) >> 1; + if ((!h264->decoder.cu_buffer) || (nv12_size > h264->decoder.cu_buffer_size)){ + h264->decoder.cu_buffer_size = 0; + if (h264->decoder.cu_buffer){ + cuResult = cuMemFreeHost(h264->decoder.cu_buffer); + h264->decoder.cu_buffer = NULL; + } + cuResult = cuMemAllocHost((void**)&h264->decoder.cu_buffer, nv12_size); + if (cuResult != CUDA_SUCCESS){ + TSK_DEBUG_ERROR("cuMemAllocHost failed to allocate %d bytes (error code=%d)", nv12_size, (int)cuResult); + h264->decoder.cu_buffer = 0; + h264->decoder.cu_buffer_size = 0; + ret = 0;//error + } + else{ + h264->decoder.cu_buffer_size = nv12_size; + } + } + if(h264->decoder.cu_buffer){ + cuResult = cuMemcpyDtoH(h264->decoder.cu_buffer, devPtr, nv12_size); + } + +bail: + if(mapped){ + cuResult = cuvidUnmapVideoFrame(h264->decoder.context, devPtr); + } + cuResult = cuCtxPopCurrent(NULL); +tsk_semaphore_increment(sem); + h264->decoder.cu_buffer_avail = (ret == 1); + return ret; +} + +static unsigned char* CUDAAPI _NVCallback_HandleAcquireBitStream(int *pBufferSize, void *pUserdata) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)pUserdata; + if(!h264 || !pBufferSize){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + *pBufferSize = (int)h264->encoder.buffer_size; + return (unsigned char*)h264->encoder.buffer; +} + +static void CUDAAPI _NVCallback_HandleReleaseBitStream(int nBytesInBuffer, unsigned char *cb,void *pUserdata) +{ + tdav_codec_h264_cuda_t* h264 = (tdav_codec_h264_cuda_t*)pUserdata; + if(!h264 || !cb || !nBytesInBuffer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return; + } + _tdav_codec_h264_cuda_encap(h264, cb, (tsk_size_t)nBytesInBuffer); + + return; +} + +static void CUDAAPI _NVCallback_HandleOnBeginFrame(const NVVE_BeginFrameInfo *pbfi, void *pUserdata) +{ + return; +} + +static void CUDAAPI _NVCallback_HandleOnEndFrame(const NVVE_EndFrameInfo *pefi, void *pUserdata) +{ + return; +} + +/* ============ H.264 Base Profile 1.0 Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_cuda_bp10_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_cuda_t *h264 = (tdav_codec_h264_cuda_t *)self; + if(h264){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + tdav_codec_h264_cuda_init(h264, tdav_codec_h264_bp10); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_cuda_bp10_dtor(tsk_object_t * self) +{ + tdav_codec_h264_cuda_t *h264 = (tdav_codec_h264_cuda_t *)self; + if(h264){ + /* deinit base */ + tdav_codec_h264_common_deinit(TDAV_CODEC_H264_COMMON(self)); + /* deinit self */ + tdav_codec_h264_cuda_deinit(h264); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_cuda_bp10_def_s = +{ + sizeof(tdav_codec_h264_cuda_t), + tdav_codec_h264_cuda_bp10_ctor, + tdav_codec_h264_cuda_bp10_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_cuda_bp10_plugin_def_s = +{ + &tdav_codec_h264_cuda_bp10_def_s, + + tmedia_video, + "H264", + "H264 Base Profile 1.0 using CUDA", + TMEDIA_CODEC_FORMAT_H264_BP10, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video */ + {176, 144, 15}, + + tdav_codec_h264_cuda_open, + tdav_codec_h264_cuda_close, + tdav_codec_h264_cuda_encode, + tdav_codec_h264_cuda_decode, + tdav_codec_h264_cuda_sdp_att_match, + tdav_codec_h264_cuda_sdp_att_get, + tdav_codec_h264_cuda_fmtp_set +}; +extern const tmedia_codec_plugin_def_t *tdav_codec_h264_cuda_bp10_plugin_def_t = &tdav_codec_h264_cuda_bp10_plugin_def_s; + + +/* ============ H.264 Base Profile 2.0 Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_cuda_bp20_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_cuda_t *h264 = (tdav_codec_h264_cuda_t *)self; + if(h264){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + tdav_codec_h264_cuda_init(h264, tdav_codec_h264_bp20); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_cuda_bp20_dtor(tsk_object_t * self) +{ + tdav_codec_h264_cuda_t *h264 = (tdav_codec_h264_cuda_t *)self; + if(h264){ + /* deinit base */ + tdav_codec_h264_common_deinit(TDAV_CODEC_H264_COMMON(self)); + /* deinit self */ + tdav_codec_h264_cuda_deinit(h264); + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_cuda_bp20_def_s = +{ + sizeof(tdav_codec_h264_cuda_t), + tdav_codec_h264_cuda_bp20_ctor, + tdav_codec_h264_cuda_bp20_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_cuda_bp20_plugin_def_s = +{ + &tdav_codec_h264_cuda_bp20_def_s, + + tmedia_video, + "H264", + "H264 Base Profile 2.0 using CUDA", + TMEDIA_CODEC_FORMAT_H264_BP20, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video */ + {352, 288, 15}, + + tdav_codec_h264_cuda_open, + tdav_codec_h264_cuda_close, + tdav_codec_h264_cuda_encode, + tdav_codec_h264_cuda_decode, + tdav_codec_h264_cuda_sdp_att_match, + tdav_codec_h264_cuda_sdp_att_get, + tdav_codec_h264_cuda_fmtp_set +}; +extern const tmedia_codec_plugin_def_t *tdav_codec_h264_cuda_bp20_plugin_def_t = &tdav_codec_h264_cuda_bp20_plugin_def_s; + + +/* ============ H.264 Base Profile 3.0 Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_cuda_bp30_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_cuda_t *h264 = (tdav_codec_h264_cuda_t *)self; + if(h264){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + tdav_codec_h264_cuda_init(h264, tdav_codec_h264_bp30); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_cuda_bp30_dtor(tsk_object_t * self) +{ + tdav_codec_h264_cuda_t *h264 = (tdav_codec_h264_cuda_t *)self; + if(h264){ + /* deinit base */ + tdav_codec_h264_common_deinit(TDAV_CODEC_H264_COMMON(self)); + /* deinit self */ + tdav_codec_h264_cuda_deinit(h264); + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_cuda_bp30_def_s = +{ + sizeof(tdav_codec_h264_cuda_t), + tdav_codec_h264_cuda_bp30_ctor, + tdav_codec_h264_cuda_bp30_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_cuda_bp30_plugin_def_s = +{ + &tdav_codec_h264_cuda_bp30_def_s, + + tmedia_video, + "H264", + "H264 Base Profile 3.0 using CUDA", + TMEDIA_CODEC_FORMAT_H264_BP30, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video */ + {352, 288, 15}, + + tdav_codec_h264_cuda_open, + tdav_codec_h264_cuda_close, + tdav_codec_h264_cuda_encode, + tdav_codec_h264_cuda_decode, + tdav_codec_h264_cuda_sdp_att_match, + tdav_codec_h264_cuda_sdp_att_get, + tdav_codec_h264_cuda_fmtp_set +}; +extern const tmedia_codec_plugin_def_t *tdav_codec_h264_cuda_bp30_plugin_def_t = &tdav_codec_h264_cuda_bp30_plugin_def_s; + + +#endif /* HAVE_CUDA */ diff --git a/tinyDAV/src/codecs/h264/tdav_codec_h264_intel.cxx b/tinyDAV/src/codecs/h264/tdav_codec_h264_intel.cxx new file mode 100644 index 0000000..49f9e1c --- /dev/null +++ b/tinyDAV/src/codecs/h264/tdav_codec_h264_intel.cxx @@ -0,0 +1,2221 @@ +/* +* Copyright (C) 2014-2015 Mamadou DIOP. +* +* 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_h264_intel.cxx +* @brief H.264 codec plugin using Intel Media SDK 2014 R2 for Clients (https://software.intel.com/en-us/media-solutions-portal) v1.1 for encoding/decoding. +* Low latency encoding/decoding: https://software.intel.com/en-us/articles/video-conferencing-features-of-intel-media-software-development-kit +*/ +#include "tinydav/codecs/h264/tdav_codec_h264_intel.h" + +#if HAVE_INTEL_MEDIA_SDK + +#include "tinydav/codecs/h264/tdav_codec_h264_common.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tinymedia/tmedia_codec.h" +#include "tinymedia/tmedia_params.h" +#include "tinymedia/tmedia_defaults.h" + +#include "tsk_thread.h" +#include "tsk_params.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#include <mfxvideo++.h> + +#if defined(_MSC_VER) +# pragma comment(lib, "libmfx.lib") +#endif /* _MSC_VER */ + +#if !defined(INTEL_DX11_D3D) +# define INTEL_DX11_D3D 1 +#endif /* INTEL_DX11_D3D */ + +#if INTEL_DX11_D3D +#include <d3d11.h> +#include <dxgi1_2.h> +#include <atlbase.h> +# if defined(_MSC_VER) +# pragma comment(lib, "d3d11.lib") +# pragma comment(lib, "dxgi.lib") +# endif /* _MSC_VER */ + +#endif /* INTEL_DX11_D3D */ + +#define INTEL_DEBUG_INFO(FMT, ...) TSK_DEBUG_INFO("[MSDK H264 Codec] " FMT, ##__VA_ARGS__) +#define INTEL_DEBUG_WARN(FMT, ...) TSK_DEBUG_WARN("[MSDK H264 Codec] " FMT, ##__VA_ARGS__) +#define INTEL_DEBUG_ERROR(FMT, ...) TSK_DEBUG_ERROR("[MSDK H264 Codec] " FMT, ##__VA_ARGS__) +#define INTEL_DEBUG_FATAL(FMT, ...) TSK_DEBUG_FATAL("[MSDK H264 Codec] " FMT, ##__VA_ARGS__) + +#define INTEL_ALIGN16(i) ((((i) + 15) >> 4) << 4) +#define INTEL_ALIGN32(i) (((mfxU32)((i)+31)) & (~ (mfxU32)31)) +#define INTEL_SAFE_RELEASE(X) {if ((X)) { (X)->Release(); (X) = NULL; }} + +#define INTEL_CHECK_STATUS(x) { mfxStatus __status__ = (x); if (__status__ != MFX_ERR_NONE) { INTEL_DEBUG_ERROR("Operation Failed (%d)", __status__); goto bail; } } +#define INTEL_BREAK(msg) { INTEL_DEBUG_ERROR("%s", msg); goto bail; } + +#define INTEL_ENABLE_REALTIME 1 + +static mfxIMPL __IntelDefaultImpl = MFX_IMPL_AUTO_ANY +#if INTEL_DX11_D3D +| MFX_IMPL_VIA_D3D11 +#endif +; +static mfxVersion __IntelDefaultVer = { 0, 1 }; + +// TODO: Test against FFmpeg, CUDA, OpenH264 and Microsoft implementations +// TODO: When Bandwidth change (or any other event) Reset() fails + +class IntelCodecEncoder; +class IntelCodecDecoder; + +typedef struct tdav_codec_h264_intel_s +{ + TDAV_DECLARE_CODEC_H264_COMMON; + + MFXVideoSession* mfxSession; + + // DX11_D3D +#if INTEL_DX11_D3D + mfxFrameAllocResponse D3D11SavedAllocResponses[2/*Encode=0, Decode=1*/]; + mfxFrameAllocator D3D11Allocator; + + CComPtr<ID3D11Device> pD3D11Device; + CComPtr<ID3D11DeviceContext> pD3D11Ctx; + CComPtr<IDXGIAdapter>pAdapter; + CComPtr<IDXGIFactory1> pDXGIFactory; + CComPtr<IDXGIAdapter> hAdapter; +#endif + + // Encoder + struct{ + IntelCodecEncoder *pInst; + int64_t frame_count; + tsk_bool_t force_idr; + int rotation; + int neg_width; + int neg_height; + int neg_fps; + int max_bitrate_bps; + } encoder; + + // decoder + struct{ + IntelCodecDecoder *pInst; + } decoder; +} +tdav_codec_h264_intel_t; + +#if !defined(INTEL_H264_GOP_SIZE_IN_SECONDS) +# define INTEL_H264_GOP_SIZE_IN_SECONDS 25 +#endif + +static int tdav_codec_h264_intel_init(tdav_codec_h264_intel_t* self, profile_idc_t profile); +static int tdav_codec_h264_intel_deinit(tdav_codec_h264_intel_t* self); +static int tdav_codec_h264_intel_open_encoder(tdav_codec_h264_intel_t* self); +static int tdav_codec_h264_intel_close_encoder(tdav_codec_h264_intel_t* self); +static int tdav_codec_h264_intel_open_decoder(tdav_codec_h264_intel_t* self); +static int tdav_codec_h264_intel_close_decoder(tdav_codec_h264_intel_t* self); + +#if INTEL_DX11_D3D +#define D3D11_WILL_READ 0x1000 +#define D3D11_WILL_WRITE 0x2000 + +typedef struct { + mfxMemId memId; + mfxMemId memIdStage; + mfxU16 rw; +} CustomMemId; + +const struct { + mfxIMPL impl; // actual implementation + mfxU32 adapterID; // device adapter number +} implTypes[] = { + { MFX_IMPL_HARDWARE, 0 }, + { MFX_IMPL_HARDWARE2, 1 }, + { MFX_IMPL_HARDWARE3, 2 }, + { MFX_IMPL_HARDWARE4, 3 } +}; + +static mfxStatus D3D11_CreateHWDevice(mfxHDL pthis, mfxSession session, mfxHDL* deviceHandle, HWND hWnd); +static void D3D11_CleanupHWDevice(mfxHDL pthis); +static void D3D11_SetHWDeviceContext(mfxHDL pthis, CComPtr<ID3D11DeviceContext> devCtx); + +// Intel Media SDK memory allocator entrypoints.... +// - A slightly different allocation procedure is used for encode, decode and VPP +static mfxStatus D3D11_SimpleAlloc(mfxHDL pthis, mfxFrameAllocRequest *request, mfxFrameAllocResponse *response); +static mfxStatus D3D11_SimpleLock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr); +static mfxStatus D3D11_SimpleUnlock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr); +static mfxStatus D3D11_SimpleGethdl(mfxHDL pthis, mfxMemId mid, mfxHDL *handle); +static mfxStatus D3D11_SimpleFree(mfxHDL pthis, mfxFrameAllocResponse *response); +#endif /* INTEL_DX11_D3D */ + +// +// IntelCodec +// +class IntelCodec +{ +protected: + IntelCodec(MFXVideoSession* pSession) + : m_bOpened(false) + , m_pSession(pSession) + , n_nNumSurfaces(0) + , m_nSurfaceWidth(0) + , m_nSurfaceHeight(0) + , m_nSurfaceBitsPerPixel(0) + , m_nSurfaceSize(0) + , m_pSurfaceBuffers(NULL) + , m_ppSurfacePtrs(NULL) + { + memset(&m_sBitstream, 0, sizeof(m_sBitstream)); + memset(&m_sParamReq, 0, sizeof(m_sParamReq)); + memset(&m_sParamSel, 0, sizeof(m_sParamSel)); + memset(&m_sAllocRequest, 0, sizeof(m_sAllocRequest)); + } +public: + virtual ~IntelCodec() + { + Close(); + } + virtual mfxStatus Open(struct tdav_codec_h264_intel_s* pWrappedCodec) = 0; + + virtual mfxStatus Close() + { + DeAllocSurfaces(); + DeAllocateBitstream(); + + memset(&m_sAllocRequest, 0, sizeof(m_sAllocRequest)); + + m_bOpened = false; + + return MFX_ERR_NONE; + } + +protected: + int GetFreeSurfaceIndex() + { + if (m_ppSurfacePtrs) + { + for (mfxU16 i = 0; i < n_nNumSurfaces; i++) + { + if (0 == m_ppSurfacePtrs[i]->Data.Locked) + { + return i; + } + } + } + return MFX_ERR_NOT_FOUND; + } + + mfxStatus ReadPlaneData(mfxU16 w, mfxU16 h, mfxU8 *buf, mfxU8 *ptr, mfxU16 pitch, mfxU16 offset, const mfxU8* &src) + { + for (mfxU16 i = 0; i < h; i++) + { + memcpy(buf, src, w); + src += w; + + for (mfxU16 j = 0; j < w; j++) + ptr[i * pitch + j * 2 + offset] = buf[j]; + } + return MFX_ERR_NONE; + } + + mfxStatus LoadRawFrame(int nSurfaceIndex, const mfxU8* src) + { + mfxFrameSurface1* pSurface = (m_ppSurfacePtrs && nSurfaceIndex >= 0 && nSurfaceIndex < n_nNumSurfaces) ? m_ppSurfacePtrs[nSurfaceIndex] : NULL; + if (!pSurface) + { + INTEL_DEBUG_ERROR("Failed to find surface at index=%d", nSurfaceIndex); + return MFX_ERR_NOT_FOUND; + } + + mfxStatus sts = MFX_ERR_NONE; + mfxU16 w, h, i, pitch; + mfxU8 *ptr; + mfxFrameInfo* pInfo = &pSurface->Info; + mfxFrameData* pData = &pSurface->Data; + + if (pInfo->CropH > 0 && pInfo->CropW > 0) { + w = pInfo->CropW; + h = pInfo->CropH; + } + else { + w = pInfo->Width; + h = pInfo->Height; + } + + pitch = pData->Pitch; + ptr = pData->Y + pInfo->CropX + pInfo->CropY * pData->Pitch; + + // read luminance plane + for (i = 0; i < h; i++) + { + memcpy(ptr + i * pitch, src, w); + src += w; + } + + mfxU8 buf[2048]; // maximum supported chroma width for nv12 + w /= 2; + h /= 2; + ptr = pData->UV + pInfo->CropX + (pInfo->CropY / 2) * pitch; + if (w > 2048) + return MFX_ERR_UNSUPPORTED; + + // load U + sts = ReadPlaneData(w, h, buf, ptr, pitch, 0, src); + if (MFX_ERR_NONE != sts) return sts; + // load V + sts = ReadPlaneData(w, h, buf, ptr, pitch, 1, src); + if (MFX_ERR_NONE != sts) return sts; + + return MFX_ERR_NONE; + } + + virtual mfxStatus AllocSurfaces(mfxU16 nNumSurfaces, mfxU16 nSurfaceWidth, mfxU16 nSurfaceHeight, const mfxFrameInfo* pcFrameInfo) + { + mfxStatus status = MFX_ERR_UNKNOWN; + + INTEL_DEBUG_INFO("Alloc surfaces: num=%u, width=%u, height=%u", nNumSurfaces, nSurfaceWidth, nSurfaceHeight); + + DeAllocSurfaces(); + + n_nNumSurfaces = nNumSurfaces; + m_nSurfaceWidth = (mfxU16)INTEL_ALIGN32(nSurfaceWidth); + m_nSurfaceHeight = (mfxU16)INTEL_ALIGN32(nSurfaceHeight); + m_nSurfaceBitsPerPixel = 12; // NV12 format is a 12 bits per pixel format + m_nSurfaceSize = m_nSurfaceWidth * m_nSurfaceHeight * m_nSurfaceBitsPerPixel / 8; + +#if !INTEL_DX11_D3D + if (!(m_pSurfaceBuffers = (mfxU8 *)new mfxU8[m_nSurfaceSize * n_nNumSurfaces])) + { + INTEL_CHECK_STATUS(MFX_ERR_MEMORY_ALLOC); + } +#endif + + if (!(m_ppSurfacePtrs = new mfxFrameSurface1*[n_nNumSurfaces])) + { + INTEL_CHECK_STATUS(MFX_ERR_MEMORY_ALLOC); + } + for (mfxU16 i = 0; i < n_nNumSurfaces; i++) + { + if (!(m_ppSurfacePtrs[i] = new mfxFrameSurface1)) + { + INTEL_CHECK_STATUS(MFX_ERR_MEMORY_ALLOC); + } + memset(m_ppSurfacePtrs[i], 0, sizeof(mfxFrameSurface1)); + memcpy(&(m_ppSurfacePtrs[i]->Info), pcFrameInfo, sizeof(mfxFrameInfo)); +#if INTEL_DX11_D3D + m_ppSurfacePtrs[i]->Data.MemId = m_sD3D11Response.mids[i]; // MID (memory id) represent one D3D NV12 surface +#else + m_ppSurfacePtrs[i]->Data.Y = &m_pSurfaceBuffers[m_nSurfaceSize * i]; + m_ppSurfacePtrs[i]->Data.U = m_ppSurfacePtrs[i]->Data.Y + m_nSurfaceWidth * m_nSurfaceHeight; + m_ppSurfacePtrs[i]->Data.V = m_ppSurfacePtrs[i]->Data.U + 1; + m_ppSurfacePtrs[i]->Data.Pitch = m_nSurfaceWidth; +#endif + } + + return MFX_ERR_NONE; + + bail: + DeAllocSurfaces(); + return status; + } + + mfxStatus AllocateBitstream(mfxU32 nMaxLength) + { + DeAllocateBitstream(); + + m_sBitstream.MaxLength = nMaxLength; + if (!(m_sBitstream.Data = new mfxU8[nMaxLength])) + { + INTEL_CHECK_STATUS(MFX_ERR_MEMORY_ALLOC); + } + + return MFX_ERR_NONE; + + bail: + return MFX_ERR_MEMORY_ALLOC; + } +private: + mfxStatus DeAllocSurfaces() + { + if (m_ppSurfacePtrs) + { + for (mfxU16 i = 0; i < n_nNumSurfaces; i++) + { + if (m_ppSurfacePtrs[i]) + { + delete m_ppSurfacePtrs[i]; + } + } + delete[] m_ppSurfacePtrs; + m_ppSurfacePtrs = NULL; + } + n_nNumSurfaces = 0; + + if (m_pSurfaceBuffers) + { + delete[] m_pSurfaceBuffers; + m_pSurfaceBuffers = NULL; + } + + m_nSurfaceWidth = 0; + m_nSurfaceHeight = 0; + m_nSurfaceBitsPerPixel = 0; + m_nSurfaceSize = 0; + + return MFX_ERR_NONE; + } + + mfxStatus DeAllocateBitstream() + { + if (m_sBitstream.Data) + { + delete[]m_sBitstream.Data; + } + memset(&m_sBitstream, 0, sizeof(m_sBitstream)); + + return MFX_ERR_NONE; + } + + +protected: + bool m_bOpened; + MFXVideoSession* m_pSession; + mfxU16 n_nNumSurfaces; + mfxU16 m_nSurfaceWidth; + mfxU16 m_nSurfaceHeight; + mfxU8 m_nSurfaceBitsPerPixel; + mfxU32 m_nSurfaceSize; + mfxU8* m_pSurfaceBuffers; // mfxU8[]; + mfxFrameSurface1** m_ppSurfacePtrs; // mfxFrameSurface1[] + mfxBitstream m_sBitstream; + mfxVideoParam m_sParamReq; // requested params + mfxVideoParam m_sParamSel; // selected params + mfxFrameAllocRequest m_sAllocRequest; +#if INTEL_DX11_D3D + mfxFrameAllocResponse m_sD3D11Response; +#endif +}; + + +// +// IntelCodecEncoder +// +class IntelCodecEncoder : public IntelCodec +{ +public: + IntelCodecEncoder(MFXVideoSession* pSession) + : IntelCodec(pSession) + , m_Inst(*pSession) + { + memset(&m_sFrameCtrl, 0, sizeof(m_sFrameCtrl)); + } + virtual ~IntelCodecEncoder() + { + Close(); + } + + virtual mfxStatus Close() + { + m_Inst.Close(); + memset(&m_sFrameCtrl, 0, sizeof(m_sFrameCtrl)); + return IntelCodec::Close(); + } + + mfxStatus Reset() + { + if (m_bOpened) + { + return m_Inst.Reset(&m_sParamSel); + } + return MFX_ERR_NONE; + } + + mfxStatus Open(struct tdav_codec_h264_intel_s* pWrappedCodec) + { + int32_t max_bw_kpbs; + tdav_codec_h264_common_t* pWrappedCodecCommon = (tdav_codec_h264_common_t*)pWrappedCodec; + mfxStatus status = MFX_ERR_UNKNOWN; + mfxU16 uSelWidth, uSelHeight; + + pWrappedCodec->encoder.neg_width = (pWrappedCodec->encoder.rotation == 90 || pWrappedCodec->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(pWrappedCodec)->out.height : TMEDIA_CODEC_VIDEO(pWrappedCodec)->out.width; + pWrappedCodec->encoder.neg_height = (pWrappedCodec->encoder.rotation == 90 || pWrappedCodec->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(pWrappedCodec)->out.width : TMEDIA_CODEC_VIDEO(pWrappedCodec)->out.height; + pWrappedCodec->encoder.neg_fps = TMEDIA_CODEC_VIDEO(pWrappedCodec)->out.fps; + max_bw_kpbs = TSK_CLAMP( + 0, + tmedia_get_video_bandwidth_kbps_2(pWrappedCodec->encoder.neg_width, pWrappedCodec->encoder.neg_height, pWrappedCodec->encoder.neg_fps), + TMEDIA_CODEC(pWrappedCodec)->bandwidth_max_upload + ); + pWrappedCodec->encoder.max_bitrate_bps = (max_bw_kpbs * 1024); + + INTEL_DEBUG_INFO("neg_width=%d, neg_height=%d, neg_fps=%d, max_bitrate_bps=%d", + pWrappedCodec->encoder.neg_width, + pWrappedCodec->encoder.neg_height, + pWrappedCodec->encoder.neg_fps, + pWrappedCodec->encoder.max_bitrate_bps + ); + + // Initialize encoder parameters + memset(&m_sParamReq, 0, sizeof(m_sParamReq)); + m_sParamReq.mfx.CodecId = MFX_CODEC_AVC; + m_sParamReq.mfx.CodecProfile = pWrappedCodecCommon->profile == profile_idc_main ? MFX_PROFILE_AVC_MAIN : MFX_PROFILE_AVC_BASELINE; + m_sParamReq.mfx.CodecLevel = (mfxU16)pWrappedCodecCommon->level; + // TODO: Update "CodecProfile" based on "common->profile_iop" + m_sParamReq.mfx.TargetUsage = MFX_TARGETUSAGE_BALANCED; + m_sParamReq.mfx.TargetKbps = max_bw_kpbs; + m_sParamReq.mfx.RateControlMethod = MFX_RATECONTROL_CBR; + m_sParamReq.mfx.IdrInterval = (pWrappedCodec->encoder.neg_fps * INTEL_H264_GOP_SIZE_IN_SECONDS); + m_sParamReq.mfx.FrameInfo.FrameRateExtN = pWrappedCodec->encoder.neg_fps; + m_sParamReq.mfx.FrameInfo.FrameRateExtD = 1; + m_sParamReq.mfx.FrameInfo.FourCC = MFX_FOURCC_NV12; + m_sParamReq.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV420; + m_sParamReq.mfx.FrameInfo.PicStruct = MFX_PICSTRUCT_PROGRESSIVE; + m_sParamReq.mfx.FrameInfo.CropX = 0; + m_sParamReq.mfx.FrameInfo.CropY = 0; + m_sParamReq.mfx.FrameInfo.CropW = pWrappedCodec->encoder.neg_width; + m_sParamReq.mfx.FrameInfo.CropH = pWrappedCodec->encoder.neg_height; + m_sParamReq.mfx.FrameInfo.Width = INTEL_ALIGN16(pWrappedCodec->encoder.neg_width); // must be a multiple of 16 + m_sParamReq.mfx.FrameInfo.Height = INTEL_ALIGN16(pWrappedCodec->encoder.neg_height); // must be a multiple of 16 +#if INTEL_DX11_D3D + m_sParamReq.IOPattern = MFX_IOPATTERN_IN_VIDEO_MEMORY; +#else + m_sParamReq.IOPattern = MFX_IOPATTERN_IN_SYSTEM_MEMORY; +#endif + + memset(&m_sOpt2MaxFrameSize, 0, sizeof(m_sOpt2MaxFrameSize)); + m_sOpt2MaxFrameSize.Header.BufferId = MFX_EXTBUFF_CODING_OPTION2; + m_sOpt2MaxFrameSize.Header.BufferSz = sizeof(m_sOpt2MaxFrameSize); + m_sOpt2MaxFrameSize.MaxSliceSize = (H264_RTP_PAYLOAD_SIZE - 100); + m_sOpt2MaxFrameSize.RepeatPPS = MFX_CODINGOPTION_OFF; + m_pExtendedBuffers[0] = (mfxExtBuffer*)&m_sOpt2MaxFrameSize; +#if INTEL_ENABLE_REALTIME + m_sParamReq.AsyncDepth = 1; // limits internal frame buffering + m_sParamReq.mfx.GopRefDist = 1; // No B-Frames + m_sParamReq.mfx.NumRefFrame = 1; + memset(&m_sOptLowLatency, 0, sizeof(m_sOptLowLatency)); + m_sOptLowLatency.Header.BufferId = MFX_EXTBUFF_CODING_OPTION; + m_sOptLowLatency.Header.BufferSz = sizeof(m_sOptLowLatency); + m_sOptLowLatency.MaxDecFrameBuffering = 1; + m_pExtendedBuffers[1] = (mfxExtBuffer*)&m_sOptLowLatency; + m_sParamReq.NumExtParam = 2; +#else + m_sParamReq.NumExtParam = 1; +#endif + m_sParamReq.ExtParam = m_pExtendedBuffers; + + // Check parameters + status = m_Inst.Query(&m_sParamReq, &m_sParamReq); + if (status != MFX_ERR_NONE && status != MFX_WRN_INCOMPATIBLE_VIDEO_PARAM /* Best one will be selected by the encoder */) { + INTEL_CHECK_STATUS(status); + } + if (m_sOpt2MaxFrameSize.MaxSliceSize == 0) + { + INTEL_DEBUG_INFO("The encoder doesn't support setting 'MaxSliceSize' :("); + } + + // Query number required surfaces for encoder + memset(&m_sAllocRequest, 0, sizeof(m_sAllocRequest)); + INTEL_CHECK_STATUS(status = m_Inst.QueryIOSurf(&m_sParamReq, &m_sAllocRequest)); + INTEL_DEBUG_INFO("nEncSurfNum = %hu", m_sAllocRequest.NumFrameSuggested); +#if INTEL_DX11_D3D + m_sAllocRequest.Type |= D3D11_WILL_WRITE; // Hint to DX11 memory handler that application will write data to input surfaces +#endif + + // Allocate surfaces for encoder +#if INTEL_DX11_D3D + INTEL_CHECK_STATUS(status = pWrappedCodec->D3D11Allocator.Alloc(pWrappedCodec->D3D11Allocator.pthis, &m_sAllocRequest, &m_sD3D11Response)); + if (m_sD3D11Response.NumFrameActual == 0) + { + INTEL_CHECK_STATUS(status = MFX_ERR_UNKNOWN); + } + INTEL_CHECK_STATUS(status = AllocSurfaces(m_sD3D11Response.NumFrameActual, m_sAllocRequest.Info.Width, m_sAllocRequest.Info.Height, &m_sParamReq.mfx.FrameInfo)); +#else + INTEL_CHECK_STATUS(status = AllocSurfaces(m_sAllocRequest.NumFrameSuggested, m_sAllocRequest.Info.Width, m_sAllocRequest.Info.Height, &m_sParamReq.mfx.FrameInfo)); +#endif + + // Initialize the Media SDK encoder + status = m_Inst.Init(&m_sParamReq); + if (status != MFX_ERR_NONE && status != MFX_WRN_PARTIAL_ACCELERATION) { + INTEL_CHECK_STATUS(status); + } + INTEL_DEBUG_INFO("Encoder->Init() returned: %d", status); + + // Retrieve video parameters selected by encoder. + memset(&m_sParamSel, 0, sizeof(m_sParamSel)); + INTEL_CHECK_STATUS(status = m_Inst.GetVideoParam(&m_sParamSel)); + INTEL_DEBUG_INFO("sel_width=%u.crop=%u, sel_height=%u.crop=%u, sel_fps=%u/%u", + m_sParamSel.mfx.FrameInfo.Width, m_sParamSel.mfx.FrameInfo.CropW, + m_sParamSel.mfx.FrameInfo.Height, m_sParamSel.mfx.FrameInfo.CropH, + m_sParamReq.mfx.FrameInfo.FrameRateExtN, + m_sParamReq.mfx.FrameInfo.FrameRateExtD + ); + if (m_sParamSel.mfx.FrameInfo.CropW > 0 && m_sParamSel.mfx.FrameInfo.CropH > 0) + { + uSelWidth = m_sParamSel.mfx.FrameInfo.CropW; + uSelHeight = m_sParamSel.mfx.FrameInfo.CropH; + } + else + { + uSelWidth = m_sParamSel.mfx.FrameInfo.Width; + uSelHeight = m_sParamSel.mfx.FrameInfo.Height; + } + if (pWrappedCodec->encoder.neg_width != uSelWidth || pWrappedCodec->encoder.neg_height != uSelHeight) { + INTEL_DEBUG_INFO("Encoder neg size <> sel size: %dx%d<>%dx%d", pWrappedCodec->encoder.neg_width, pWrappedCodec->encoder.neg_height, uSelWidth, uSelHeight); + pWrappedCodec->encoder.neg_width = uSelWidth; + pWrappedCodec->encoder.neg_height = uSelHeight; + TMEDIA_CODEC_VIDEO(pWrappedCodec)->out.width = pWrappedCodec->encoder.neg_width; + TMEDIA_CODEC_VIDEO(pWrappedCodec)->out.height = pWrappedCodec->encoder.neg_height; + } + + // Allocate BitStream + INTEL_CHECK_STATUS(status = AllocateBitstream(m_sParamSel.mfx.BufferSizeInKB * 1000)); + + m_bOpened = true; + return MFX_ERR_NONE; + + bail: + Close(); + return status; + } + + mfxStatus UpdateBandwidth(bool bUp, mfxU16 max) + { + if (bUp) + { + m_sParamSel.mfx.TargetKbps = TSK_CLAMP(0, (mfxU16)((m_sParamSel.mfx.TargetKbps * 3) >> 1), max); + } + else + { + m_sParamSel.mfx.TargetKbps = TSK_CLAMP(0, (mfxU16)((m_sParamSel.mfx.TargetKbps << 1) / 3), max); + } + m_sParamReq.mfx.TargetKbps = m_sParamSel.mfx.TargetKbps; + INTEL_DEBUG_INFO("Setting new target bandwidth to %ukbps", m_sParamSel.mfx.TargetKbps); + return m_Inst.Reset(&m_sParamSel); + } + + mfxStatus SetMaxBandwidth(mfxU16 max) + { + m_sParamSel.mfx.TargetKbps = TSK_CLAMP(0, m_sParamSel.mfx.TargetKbps, max); + m_sParamReq.mfx.TargetKbps = m_sParamSel.mfx.TargetKbps; + INTEL_DEBUG_INFO("Setting new target bandwidth to %ukbps", m_sParamSel.mfx.TargetKbps); + return m_Inst.Reset(&m_sParamSel); + } + + mfxStatus Encode(struct tmedia_codec_s* pWrappedCodec, const mfxU8* pcInDataPtr, mfxU32 nInDataSize) + { + tdav_codec_h264_intel_t* pWrappedCodecH264 = (tdav_codec_h264_intel_t*)pWrappedCodec; + tdav_codec_h264_common_t* pWrappedCodecCommon = (tdav_codec_h264_common_t*)pWrappedCodec; + mfxU32 nInDataXSize; + tsk_bool_t bSendIDR; + int nEncSurfIdx = 0; + mfxSyncPoint syncp; + mfxStatus status = MFX_ERR_UNKNOWN; + + if (!pWrappedCodec || !pcInDataPtr || !nInDataSize) { + INTEL_CHECK_STATUS(MFX_ERR_NULL_PTR); + } + + if (!m_bOpened) { + INTEL_CHECK_STATUS(MFX_ERR_NOT_INITIALIZED); + } + + nInDataXSize = (pWrappedCodecH264->encoder.neg_width * pWrappedCodecH264->encoder.neg_height * 3) >> 1; + if (nInDataXSize != nInDataSize) + { + /* guard */ + INTEL_DEBUG_ERROR("Invalid size: %u<>%u", nInDataXSize, nInDataSize); + goto bail; + } + + bSendIDR = (pWrappedCodecH264->encoder.frame_count++ == 0 || pWrappedCodecH264->encoder.force_idr); + + nEncSurfIdx = GetFreeSurfaceIndex(); + if (MFX_ERR_NOT_FOUND == nEncSurfIdx) + { + INTEL_CHECK_STATUS(MFX_ERR_MEMORY_ALLOC); + } + + // Surface locking required when read/write D3D surfaces + +#if INTEL_DX11_D3D + INTEL_CHECK_STATUS(status = pWrappedCodecH264->D3D11Allocator.Lock(pWrappedCodecH264->D3D11Allocator.pthis, m_ppSurfacePtrs[nEncSurfIdx]->Data.MemId, &(m_ppSurfacePtrs[nEncSurfIdx]->Data))); +#endif + + INTEL_CHECK_STATUS(status = LoadRawFrame(nEncSurfIdx, pcInDataPtr)); + +#if INTEL_DX11_D3D + INTEL_CHECK_STATUS(status = pWrappedCodecH264->D3D11Allocator.Unlock(pWrappedCodecH264->D3D11Allocator.pthis, m_ppSurfacePtrs[nEncSurfIdx]->Data.MemId, &(m_ppSurfacePtrs[nEncSurfIdx]->Data))); +#endif + + m_sFrameCtrl.FrameType = bSendIDR ? (MFX_FRAMETYPE_I | MFX_FRAMETYPE_REF | MFX_FRAMETYPE_IDR) : MFX_FRAMETYPE_UNKNOWN; + + // + // Stage 1: Main encoding loop + // + do + { + for (;;) + { + // Encode a frame asychronously (returns immediately) + status = m_Inst.EncodeFrameAsync(&m_sFrameCtrl, m_ppSurfacePtrs[nEncSurfIdx], &m_sBitstream, &syncp); + + if (MFX_ERR_NONE < status && !syncp) // Repeat the call if warning and no output + { + if (MFX_WRN_DEVICE_BUSY == status) + { + tsk_thread_sleep(1); // Wait if device is busy, then repeat the same call + } + } + else if (MFX_ERR_NONE < status && syncp) + { + status = MFX_ERR_NONE; // Ignore warnings if output is available + break; + } + else if (MFX_ERR_NOT_ENOUGH_BUFFER == status) + { + // Allocate more bitstream buffer memory here if needed... + break; + } + else + { + if (status != MFX_ERR_MORE_DATA) + { + INTEL_CHECK_STATUS(status); + } + break; + } + } + if (MFX_ERR_NONE == status) + { + INTEL_CHECK_STATUS(m_pSession->SyncOperation(syncp, 60000)); // Synchronize. Wait until encoded frame is ready + if (m_sBitstream.DataLength > 0) + { + tdav_codec_h264_rtp_encap(pWrappedCodecCommon, (const uint8_t*)(m_sBitstream.Data + m_sBitstream.DataOffset), (tsk_size_t)m_sBitstream.DataLength); + m_sBitstream.DataLength = 0; + pWrappedCodecH264->encoder.force_idr = tsk_false; // reset + } + } + } while (0); + + // + // Stage 2: Retrieve the buffered encoded frames + // + while (MFX_ERR_NONE <= status) + { + for (;;) + { + // Encode a frame asychronously (returns immediately) + status = m_Inst.EncodeFrameAsync(&m_sFrameCtrl, NULL, &m_sBitstream, &syncp); + + if (MFX_ERR_NONE < status && !syncp) // Repeat the call if warning and no output + { + if (MFX_WRN_DEVICE_BUSY == status) + { + tsk_thread_sleep(1); // Wait if device is busy, then repeat the same call + } + } + else if (MFX_ERR_NONE < status && syncp) + { + status = MFX_ERR_NONE; // Ignore warnings if output is available + break; + } + else + { + break; + } + } + + if (MFX_ERR_NONE == status) + { + INTEL_CHECK_STATUS(m_pSession->SyncOperation(syncp, 60000)); // Synchronize. Wait until encoded frame is + if (m_sBitstream.DataLength > 0) + { + tdav_codec_h264_rtp_encap(pWrappedCodecCommon, (const uint8_t*)(m_sBitstream.Data + m_sBitstream.DataOffset), (tsk_size_t)m_sBitstream.DataLength); + m_sBitstream.DataLength = 0; + pWrappedCodecH264->encoder.force_idr = tsk_false; // reset + } + } + } + + bail: + return MFX_ERR_NONE; + } + +private: + MFXVideoENCODE m_Inst; + mfxEncodeCtrl m_sFrameCtrl; + mfxExtCodingOption m_sOptLowLatency; + mfxExtCodingOption2 m_sOpt2MaxFrameSize; + mfxExtBuffer* m_pExtendedBuffers[2]; // Not allocated +}; + + +// +// IntelCodecDecoder +// +class IntelCodecDecoder : public IntelCodec +{ +public: + IntelCodecDecoder(MFXVideoSession* pSession) + : IntelCodec(pSession) + , m_Inst(*pSession) + , m_pAccumulatorPtr(NULL) + , m_nAccumulatorSize(0) + , m_nAccumulatorPos(0) + , m_bInit(false) + { + + } + virtual ~IntelCodecDecoder() + { + Close(); + } + + virtual mfxStatus Close() + { + m_Inst.Close(); + TSK_FREE(m_pAccumulatorPtr); + m_nAccumulatorSize = 0; + m_nAccumulatorPos = 0; + m_bInit = false; + return IntelCodec::Close(); + } + + mfxStatus Open(struct tdav_codec_h264_intel_s* pWrappedCodec) + { + tdav_codec_h264_common_t* pWrappedCodecCommon = (tdav_codec_h264_common_t*)pWrappedCodec; + + INTEL_DEBUG_INFO("Decoder.Open width=%d, height=%d, fps=%d", + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.width, + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.height, + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.fps + ); + + // Allocation will be done each time we decode the SPS:PPS header + + m_bOpened = true; + return MFX_ERR_NONE; + } + + mfxU32 Decode(struct tmedia_codec_s* pWrappedCodec, const mfxU8* pcInDataPtr, mfxU32 nInDataSize, void **ppOutDataPtr, tsk_size_t *pOutDataMaxSize, const trtp_rtp_header_t* pcRtpHdr) + { + mfxU32 nRetSize = 0, nOutXSize; + mfxStatus status = MFX_ERR_NONE; + tsk_bool_t append_scp, end_of_unit; + tsk_bool_t sps_or_pps; + const uint8_t* pay_ptr = tsk_null; + tsk_size_t pay_size = 0, size_to_copy = 0; + bool bGotFrame = false; + mfxFrameSurface1* pmfxOutSurface = NULL; + static const tsk_size_t xmax_size = (3840 * 2160 * 3) >> 3; // >>3 instead of >>1 (not an error) + static tsk_size_t start_code_prefix_size = sizeof(H264_START_CODE_PREFIX); + + tdav_codec_h264_intel_t* pWrappedCodecH264 = (tdav_codec_h264_intel_t*)pWrappedCodec; + tdav_codec_h264_common_t* pWrappedCodecCommon = (tdav_codec_h264_common_t*)pWrappedCodec; + + if (!pWrappedCodec || !pcInDataPtr || !nInDataSize || !ppOutDataPtr) + { + INTEL_CHECK_STATUS(MFX_ERR_NULL_PTR); + } + //INTEL_DEBUG_INFO("Size=%u", nInDataSize); + if (!m_bOpened) + { + INTEL_CHECK_STATUS(MFX_ERR_NOT_INITIALIZED); + } + + + /* 5.3. NAL Unit Octet Usage + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + */ + if (pcInDataPtr[0] & 0x80) // F ?== 1 + { + /* reset accumulator */ + m_nAccumulatorPos = 0; + INTEL_CHECK_STATUS(status = MFX_ERR_UNDEFINED_BEHAVIOR); + } + + // New frame? + if (m_nLastRtpTimestamp != pcRtpHdr->timestamp) + { + m_nAccumulatorPos = 0; + m_nLastRtpTimestamp = pcRtpHdr->timestamp; + } + + /* get payload */ + if ((tdav_codec_h264_get_pay(pcInDataPtr, nInDataSize, (const void**)&pay_ptr, &pay_size, &append_scp, &end_of_unit) != 0) || !pay_ptr || !pay_size) + { + INTEL_BREAK("Depayloader failed to get H.264 content"); + } +#if 1 // TODO: MSDK cannot decode slices + end_of_unit = pcRtpHdr->marker; +#endif + //append_scp = tsk_true; + size_to_copy = pay_size + (append_scp ? start_code_prefix_size : 0); + // whether it's SPS or PPS (append_scp is false for subsequent FUA chuncks) + sps_or_pps = append_scp && pay_ptr && ((pay_ptr[0] & 0x1F) == 7 || (pay_ptr[0] & 0x1F) == 8); + + // start-accumulator + if (!m_pAccumulatorPtr) + { + if (size_to_copy > xmax_size) + { + INTEL_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", size_to_copy, xmax_size); + m_nAccumulatorPos = 0; + return 0; + } + if (!(m_pAccumulatorPtr = (mfxU8*)tsk_calloc(size_to_copy, sizeof(mfxU8)))) + { + INTEL_DEBUG_ERROR("Failed to allocated new buffer"); + m_nAccumulatorPos = 0; + return 0; + } + m_nAccumulatorSize = (mfxU32)size_to_copy; + } + if ((m_nAccumulatorPos + size_to_copy) >= xmax_size) + { + INTEL_DEBUG_ERROR("BufferOverflow"); + m_nAccumulatorPos = 0; + return 0; + } + if ((m_nAccumulatorPos + size_to_copy) > m_nAccumulatorSize) + { + if (!(m_pAccumulatorPtr = (mfxU8*)tsk_realloc(m_pAccumulatorPtr, (m_nAccumulatorPos + size_to_copy)))) + { + INTEL_DEBUG_ERROR("Failed to reallocated new buffer"); + m_nAccumulatorPos = 0; + m_nAccumulatorSize = 0; + return 0; + } + m_nAccumulatorSize = (mfxU32)(m_nAccumulatorPos + size_to_copy); + } + + if (append_scp) + { + memcpy(&m_pAccumulatorPtr[m_nAccumulatorPos], H264_START_CODE_PREFIX, start_code_prefix_size); + m_nAccumulatorPos += (mfxU32)start_code_prefix_size; + } + memcpy(&m_pAccumulatorPtr[m_nAccumulatorPos], pay_ptr, pay_size); + m_nAccumulatorPos += (mfxU32)pay_size; + // end-accumulator + + if (/*rtp_hdr->marker*/end_of_unit) + { + /* decode the picture */ + mfxU32 nOutWidth, nOutHeight; + + // Decode a Unit + status = DecodeFrame(pWrappedCodecH264, m_pAccumulatorPtr, m_nAccumulatorPos, !!sps_or_pps, &pmfxOutSurface, bGotFrame); + if (status != MFX_ERR_NONE) + { + INTEL_DEBUG_WARN("DecodeFrame failed: %d", status); + goto bail; + } + + // Do we have a complete frame? + if (!bGotFrame || !pmfxOutSurface) + { + goto bail; + } + +#if INTEL_DX11_D3D + INTEL_CHECK_STATUS(status = pWrappedCodecH264->D3D11Allocator.Lock(pWrappedCodecH264->D3D11Allocator.pthis, pmfxOutSurface->Data.MemId, &(pmfxOutSurface->Data))); +#endif + if (!pmfxOutSurface->Data.Y || !pmfxOutSurface->Data.U || !pmfxOutSurface->Data.V) + { +#if INTEL_DX11_D3D + INTEL_CHECK_STATUS(status = pWrappedCodecH264->D3D11Allocator.Unlock(pWrappedCodecH264->D3D11Allocator.pthis, pmfxOutSurface->Data.MemId, &(pmfxOutSurface->Data))); +#endif + goto bail; + } + + if (pmfxOutSurface->Info.CropW > 0 && pmfxOutSurface->Info.CropH > 0) + { + nOutWidth = pmfxOutSurface->Info.CropW; + nOutHeight = pmfxOutSurface->Info.CropH; + } + else + { + nOutWidth = pmfxOutSurface->Info.Width; + nOutHeight = pmfxOutSurface->Info.Height; + } + + nOutXSize = (nOutWidth * nOutHeight * 3) >> 1; // I420 + /* IDR ? */ + if (((pay_ptr[0] & 0x1F) == 0x05) && TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.callback) + { + INTEL_DEBUG_INFO("Decoded H.264 IDR"); + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.result.type = tmedia_video_decode_result_type_idr; + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.result.proto_hdr = pcRtpHdr; + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.callback(&TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.result); + } + /* fill out */ + if (*pOutDataMaxSize < nOutXSize) + { + if ((*ppOutDataPtr = tsk_realloc(*ppOutDataPtr, nOutXSize))) + { + *pOutDataMaxSize = nOutXSize; + } + else + { + *pOutDataMaxSize = 0; + return 0; + } + } + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.width = nOutWidth; + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.height = nOutHeight; + + /* layout picture */ + INTEL_CHECK_STATUS(status = IntelCodecDecoder::LayoutPicture(pmfxOutSurface, (mfxU8 *)*ppOutDataPtr)); + nRetSize = nOutXSize; +#if INTEL_DX11_D3D + INTEL_CHECK_STATUS(status = pWrappedCodecH264->D3D11Allocator.Unlock(pWrappedCodecH264->D3D11Allocator.pthis, pmfxOutSurface->Data.MemId, &(pmfxOutSurface->Data))); +#endif + } // else if(rtp_hdr->marker) + + bail: + if (end_of_unit) + { + /* reset accumulator */ + m_nAccumulatorPos = 0; + } + + if (status != MFX_ERR_NONE) + { + INTEL_DEBUG_INFO("Failed to decode the buffer with error code =%d, size=%u, append=%s", status, m_nAccumulatorPos, append_scp ? "yes" : "no"); + if (TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.callback) + { + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.result.type = tmedia_video_decode_result_type_error; + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.result.proto_hdr = pcRtpHdr; + TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.callback(&TMEDIA_CODEC_VIDEO(pWrappedCodec)->in.result); + } + } + return nRetSize; + } + +private: +#if 0 + static mfxStatus WriteSection(mfxU8* plane, mfxU16 factor, mfxU16 chunksize, mfxFrameInfo *pInfo, mfxFrameData *pData, mfxU32 i, mfxU32 j, mfxU8 *pDstPtr) + { + memcpy(pDstPtr, plane + (pInfo->CropY * pData->Pitch / factor + pInfo->CropX) + i * pData->Pitch + j, chunksize); + return MFX_ERR_NONE; + } +#else +#define WriteSection(_plane, _factor, _chunksize, _pInfo, _pData, _i, _j, _pDstPtr) \ + memcpy((_pDstPtr), (_plane) + ((_pInfo)->CropY * (_pData)->Pitch / (_factor) + (_pInfo)->CropX) + (_i) * (_pData)->Pitch + (_j), (_chunksize)) +#define WriteSection1(_plane, _factor, _pInfo, _pData, _i, _j, _pDstPtr) \ + *(_pDstPtr) = *((_plane) + ((_pInfo)->CropY * (_pData)->Pitch / (_factor) + (_pInfo)->CropX) + (_i) * (_pData)->Pitch + (_j)); +#endif + + static mfxStatus LayoutPicture(mfxFrameSurface1 *pSurface, mfxU8 *pDstPtr) + { +#if 1 // ->YUV420 + mfxFrameInfo *pInfo = &pSurface->Info; + mfxFrameData *pData = &pSurface->Data; + mfxU32 i, j, h, w; + + if (pSurface->Info.CropW > 0 && pSurface->Info.CropH > 0) + { + w = pSurface->Info.CropW; + h = pSurface->Info.CropH; + } + else + { + w = pSurface->Info.Width; + h = pSurface->Info.Height; + } + + for (i = 0; i < h; i++) + { + WriteSection(pData->Y, 1, w, pInfo, pData, i, 0, pDstPtr); + pDstPtr += w; + } + + h >>= 1; + for (i = 0; i < h; i++) + { + for (j = 0; j < w; j += 2) + { + WriteSection1(pData->UV, 2, pInfo, pData, i, j, pDstPtr); + pDstPtr += 1; + } + } + + for (i = 0; i < h; i++) + { + for (j = 1; j < w; j += 2) + { + WriteSection1(pData->UV, 2, pInfo, pData, i, j, pDstPtr); + pDstPtr += 1; + } + } +#elif 1 // ->NV12 + mfxFrameInfo *pInfo = &pSurface->Info; + mfxFrameData *pData = &pSurface->Data; + mfxU32 i, j, h, w; + + if (pSurface->Info.CropW > 0 && pSurface->Info.CropH > 0) + { + w = pSurface->Info.CropW; + h = pSurface->Info.CropH; + } + else + { + w = pSurface->Info.Width; + h = pSurface->Info.Height; + } + + for (i = 0; i < h; i++) + { + WriteSection(pData->Y, 1, w, pInfo, pData, i, 0, pDstPtr); + pDstPtr += w; + } + + h >>= 1; + for (i = 0; i < h; i++) + { + for (j = 0; j < w; j += 2) + { + WriteSection1(pData->UV, 2, pInfo, pData, i, j, &pDstPtr[0]); + WriteSection1(pData->UV, 2, pInfo, pData, i, j + 1, &pDstPtr[1]); + pDstPtr += 2; + } + } +#endif + + return MFX_ERR_NONE; + } + + mfxStatus DecodeFrame(struct tdav_codec_h264_intel_s* pWrappedCodec, const mfxU8* pcInDataPtr, mfxU32 nInDataSize, bool bSpsOrPps, mfxFrameSurface1** ppmfxOutSurface, bool &bGotFrame) + { + mfxStatus status = MFX_ERR_NONE; + int nSurfaceIndex; + mfxSyncPoint syncp; + bGotFrame = false; + *ppmfxOutSurface = NULL; + mfxFrameSurface1* pmfxOutSurface = NULL; + +#if 0 + if (!bSpsOrPps && !m_bInit) + { + INTEL_CHECK_STATUS(status = MFX_ERR_NOT_INITIALIZED); + } +#endif + + if (m_sBitstream.DataLength < nInDataSize) + { + INTEL_CHECK_STATUS(status = AllocateBitstream(nInDataSize)); + } + memcpy(m_sBitstream.Data, pcInDataPtr, nInDataSize); + m_sBitstream.DataOffset = 0; + m_sBitstream.DataLength = nInDataSize; + m_sBitstream.DataFlag = MFX_BITSTREAM_COMPLETE_FRAME; + + if (bSpsOrPps || !m_bInit) + { + memset(&m_sParamReq, 0, sizeof(m_sParamReq)); + m_sParamReq.mfx.CodecId = MFX_CODEC_AVC; + m_sParamReq.mfx.FrameInfo.FourCC = MFX_FOURCC_NV12; + m_sParamReq.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV420; +#if INTEL_DX11_D3D + m_sParamReq.IOPattern = MFX_IOPATTERN_OUT_VIDEO_MEMORY; +#else + m_sParamReq.IOPattern = MFX_IOPATTERN_OUT_SYSTEM_MEMORY; +#endif + m_sParamReq.AsyncDepth = 1; // Low latency: limits internal frame buffering + + status = m_Inst.DecodeHeader(&m_sBitstream, &m_sParamReq); + if (status == MFX_WRN_PARTIAL_ACCELERATION) + { + status = MFX_ERR_NONE; + } + INTEL_CHECK_STATUS(status); + + memcpy(&m_sParamSel, &m_sParamReq, sizeof(m_sParamSel)); + + // Check parameters + status = m_Inst.Query(&m_sParamReq, &m_sParamReq); + if (status != MFX_ERR_NONE && status != MFX_WRN_INCOMPATIBLE_VIDEO_PARAM /* Best one will be selected by the encoder */) + { + INTEL_CHECK_STATUS(status); + } + + // Query number required surfaces for encoder + memset(&m_sAllocRequest, 0, sizeof(m_sAllocRequest)); + INTEL_CHECK_STATUS(status = m_Inst.QueryIOSurf(&m_sParamReq, &m_sAllocRequest)); +#if INTEL_DX11_D3D + m_sAllocRequest.Type |= D3D11_WILL_READ; // Hint to DX11 memory handler that application will read data from output surfaces +#endif + + // Allocate surfaces for decoder +#if INTEL_DX11_D3D + INTEL_CHECK_STATUS(status = pWrappedCodec->D3D11Allocator.Alloc(pWrappedCodec->D3D11Allocator.pthis, &m_sAllocRequest, &m_sD3D11Response)); + if (m_sD3D11Response.NumFrameActual == 0) + { + INTEL_CHECK_STATUS(status = MFX_ERR_UNKNOWN); + } + INTEL_DEBUG_INFO("nEncSurfNum = %hu", m_sD3D11Response.NumFrameActual); + INTEL_CHECK_STATUS(status = AllocSurfaces(m_sD3D11Response.NumFrameActual, m_sAllocRequest.Info.Width, m_sAllocRequest.Info.Height, &m_sParamReq.mfx.FrameInfo)); +#else + INTEL_DEBUG_INFO("nEncSurfNum = %hu", m_sAllocRequest.NumFrameSuggested); + INTEL_CHECK_STATUS(status = AllocSurfaces(m_sAllocRequest.NumFrameSuggested, m_sAllocRequest.Info.Width, m_sAllocRequest.Info.Height, &m_sParamReq.mfx.FrameInfo)); +#endif + // Initialize the Media SDK decoder + status = m_Inst.Init(&m_sParamReq); + if (status != MFX_ERR_NONE && status != MFX_WRN_PARTIAL_ACCELERATION) + { + INTEL_CHECK_STATUS(status); + } + INTEL_DEBUG_INFO("Decoder->Init() returned: %d", status); + + m_bInit = true; + } + + // + // Stage 1: Main decoding loop + // + while (MFX_ERR_NONE <= status || MFX_ERR_MORE_DATA == status || MFX_ERR_MORE_SURFACE == status) + { + if (MFX_WRN_DEVICE_BUSY == status) + { + tsk_thread_sleep(1); // Wait if device is busy, then repeat the same call to DecodeFrameAsync + } + + if (MFX_ERR_MORE_DATA == status) + { + return MFX_ERR_NONE; + } + + if (MFX_ERR_MORE_SURFACE == status || MFX_ERR_NONE == status) + { + nSurfaceIndex = GetFreeSurfaceIndex(); // Find free frame surface + if (MFX_ERR_NOT_FOUND == nSurfaceIndex) + { + INTEL_CHECK_STATUS((status = MFX_ERR_MEMORY_ALLOC)); + } + } + + // Decode a frame asychronously (returns immediately) + // - If input bitstream contains multiple frames DecodeFrameAsync will start decoding multiple frames, and remove them from bitstream + status = m_Inst.DecodeFrameAsync(&m_sBitstream, m_ppSurfacePtrs[nSurfaceIndex], &pmfxOutSurface, &syncp); + + // Ignore warnings if output is available, + // if no output and no action required just repeat the DecodeFrameAsync call + if (MFX_ERR_NONE < status && syncp) + { + status = MFX_ERR_NONE; + } + + if (MFX_ERR_NONE == status) + { + status = m_pSession->SyncOperation(syncp, 60000); // Synchronize. Wait until decoded frame is ready + } + + if (MFX_ERR_NONE == status) + { + bGotFrame = true; + if (pmfxOutSurface) + { + *ppmfxOutSurface = pmfxOutSurface; + } + } + } + + // + // Stage 2: Retrieve the buffered decoded frames + // + while (MFX_ERR_NONE <= status || MFX_ERR_MORE_SURFACE == status) + { + if (MFX_WRN_DEVICE_BUSY == status) + { + tsk_thread_sleep(1); // Wait if device is busy, then repeat the same call to DecodeFrameAsync + } + + nSurfaceIndex = GetFreeSurfaceIndex(); // Find free frame surface + if (MFX_ERR_NOT_FOUND == nSurfaceIndex) + { + INTEL_CHECK_STATUS((status = MFX_ERR_MEMORY_ALLOC)); + } + + // Decode a frame asychronously (returns immediately) + status = m_Inst.DecodeFrameAsync(NULL, m_ppSurfacePtrs[nSurfaceIndex], ppmfxOutSurface, &syncp); + + // Ignore warnings if output is available, + // if no output and no action required just repeat the DecodeFrameAsync call + if (MFX_ERR_NONE < status && syncp) + { + status = MFX_ERR_NONE; + } + + if (MFX_ERR_NONE == status) + { + status = m_pSession->SyncOperation(syncp, 60000); // Synchronize. Waits until decoded frame is ready + } + + if (MFX_ERR_NONE == status) + { + bGotFrame = true; + if (pmfxOutSurface) + { + *ppmfxOutSurface = pmfxOutSurface; + } + } + } + + status = MFX_ERR_NONE; + + bail: + return status; + } +private: + mfxU8 *m_pAccumulatorPtr; + mfxU32 m_nAccumulatorSize; + mfxU32 m_nAccumulatorPos; + mfxU32 m_nLastRtpTimestamp; + MFXVideoDECODE m_Inst; + bool m_bInit; +}; + +/* ============ H.264 Base/Main Profile X.X Plugin interface functions ================= */ + +static int tdav_codec_h264_intel_set(tmedia_codec_t* self, const tmedia_param_t* param) +{ + tdav_codec_h264_intel_t* h264 = (tdav_codec_h264_intel_t*)self; + 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: + { + h264->encoder.force_idr = tsk_true; + return 0; + } + case tmedia_codec_action_bw_up: + case tmedia_codec_action_bw_down: + { + if (self->opened) + { + INTEL_CHECK_STATUS(h264->encoder.pInst->UpdateBandwidth(action == tmedia_codec_action_bw_up, TMEDIA_CODEC(h264)->bandwidth_max_upload)); + } + break; + } + } + } + else if (tsk_striequals(param->key, "bw_kbps")) // both up and down (from the SDP) + { + int32_t max_bw_userdefine = tmedia_defaults_get_bandwidth_video_upload_max(); + int32_t max_bw_new = *((int32_t*)param->value); + if (max_bw_userdefine > 0) + { + // do not use more than what the user defined in it's configuration + TMEDIA_CODEC(h264)->bandwidth_max_upload = TSK_MIN(max_bw_new, max_bw_userdefine); + } + else + { + TMEDIA_CODEC(h264)->bandwidth_max_upload = max_bw_new; + } + INTEL_DEBUG_INFO("bandwidth-max-upload=%d", TMEDIA_CODEC(h264)->bandwidth_max_upload); + if (self->opened) + { + INTEL_CHECK_STATUS(h264->encoder.pInst->SetMaxBandwidth(TMEDIA_CODEC(h264)->bandwidth_max_upload)); + } + return 0; + } + else if (tsk_striequals(param->key, "bandwidth-max-upload")) + { + int32_t bw_max_upload = *((int32_t*)param->value); + TSK_DEBUG_INFO("OpenH264 codec: bandwidth-max-upload=%d", bw_max_upload); + TMEDIA_CODEC(h264)->bandwidth_max_upload = bw_max_upload; + if (self->opened) + { + INTEL_CHECK_STATUS(h264->encoder.pInst->SetMaxBandwidth(TMEDIA_CODEC(h264)->bandwidth_max_upload)); + } + return 0; + } + else if (tsk_striequals(param->key, "rotation")) + { + int rotation = *((int32_t*)param->value); + if (h264->encoder.rotation != rotation) + { + if (self->opened) + { + int ret; + h264->encoder.rotation = rotation; + if ((ret = tdav_codec_h264_intel_close_encoder(h264))) + { + return ret; + } + if ((ret = tdav_codec_h264_intel_open_encoder(h264))) + { + return ret; + } + } + } + return 0; + } + } +bail: + + return -1; +} + + +static int tdav_codec_h264_intel_open(tmedia_codec_t* self) +{ + int ret; + tdav_codec_h264_intel_t* h264 = (tdav_codec_h264_intel_t*)self; + + if (!h264) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + // Encoder + if ((ret = tdav_codec_h264_intel_open_encoder(h264))) { + return ret; + } + + // Decoder + if ((ret = tdav_codec_h264_intel_open_decoder(h264))) { + return ret; + } + + return 0; +} + +static int tdav_codec_h264_intel_close(tmedia_codec_t* self) +{ + tdav_codec_h264_intel_t* h264 = (tdav_codec_h264_intel_t*)self; + + if (!h264) + { + INTEL_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) alreasy checked that the codec is opened */ + + // Encoder + tdav_codec_h264_intel_close_encoder(h264); + + // Decoder + tdav_codec_h264_intel_close_decoder(h264); + + return 0; +} + +static tsk_size_t tdav_codec_h264_intel_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tdav_codec_h264_intel_t* h264 = (tdav_codec_h264_intel_t*)self; + (void)(out_data); + (void)(out_max_size); + + if (!h264 || !h264->encoder.pInst) + { + INTEL_CHECK_STATUS(MFX_ERR_NULL_PTR); + } + INTEL_CHECK_STATUS(h264->encoder.pInst->Encode(self, (const mfxU8*)in_data, (mfxU32)in_size)); +bail: + return 0; +} + +static tsk_size_t tdav_codec_h264_intel_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) +{ + tdav_codec_h264_intel_t* h264 = (tdav_codec_h264_intel_t*)self; + + if (!h264 || !h264->decoder.pInst) + { + INTEL_CHECK_STATUS(MFX_ERR_NULL_PTR); + } + return (tsk_size_t)h264->decoder.pInst->Decode(self, (const mfxU8*)in_data, (mfxU32)in_size, out_data, out_max_size, (const trtp_rtp_header_t*)proto_hdr); +bail: + return 0; +} + +static tsk_bool_t tdav_codec_h264_intel_sdp_att_match(const tmedia_codec_t* self, const char* att_name, const char* att_value) +{ + return tdav_codec_h264_common_sdp_att_match((tdav_codec_h264_common_t*)self, att_name, att_value); +} + +static char* tdav_codec_h264_intel_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + char* att = tdav_codec_h264_common_sdp_att_get((const tdav_codec_h264_common_t*)self, att_name); + if (att && tsk_striequals(att_name, "fmtp")) { + tsk_strcat(&att, "; impl=intel"); + } + return att; +} + +/* ============ H.264 Base Profile Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_intel_base_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_intel_t *h264 = (tdav_codec_h264_intel_t*)self; + if (h264) { + /* init base: called by tmedia_codec_create() */ + /* init self */ + if (tdav_codec_h264_intel_init(h264, profile_idc_baseline) != 0) { + return tsk_null; + } + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_intel_base_dtor(tsk_object_t * self) +{ + tdav_codec_h264_intel_t *h264 = (tdav_codec_h264_intel_t*)self; + if (h264) { + /* deinit base */ + tdav_codec_h264_common_deinit(TDAV_CODEC_H264_COMMON(self)); + /* deinit self */ + tdav_codec_h264_intel_deinit(h264); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_intel_base_def_s = +{ + sizeof(tdav_codec_h264_intel_t), + tdav_codec_h264_intel_base_ctor, + tdav_codec_h264_intel_base_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_intel_base_plugin_def_s = +{ + &tdav_codec_h264_intel_base_def_s, + + tmedia_video, + tmedia_codec_id_h264_bp, + "H264", + "H264 Base Profile (Intel Media SDK)", + TMEDIA_CODEC_FORMAT_H264_BP, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (width, height, fps) */ + { 176, 144, 0 }, // fps is @deprecated + + tdav_codec_h264_intel_set, + tdav_codec_h264_intel_open, + tdav_codec_h264_intel_close, + tdav_codec_h264_intel_encode, + tdav_codec_h264_intel_decode, + tdav_codec_h264_intel_sdp_att_match, + tdav_codec_h264_intel_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h264_intel_base_plugin_def_t = &tdav_codec_h264_intel_base_plugin_def_s; + + +/* ============ H.264 Main Profile Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_intel_main_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_intel_t *h264 = (tdav_codec_h264_intel_t*)self; + if (h264) { + /* init main: called by tmedia_codec_create() */ + /* init self */ + if (tdav_codec_h264_intel_init(h264, profile_idc_main) != 0) { + return tsk_null; + } + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_intel_main_dtor(tsk_object_t * self) +{ + tdav_codec_h264_intel_t *h264 = (tdav_codec_h264_intel_t*)self; + if (h264) + { + /* deinit main */ + tdav_codec_h264_common_deinit(TDAV_CODEC_H264_COMMON(self)); + /* deinit self */ + tdav_codec_h264_intel_deinit(h264); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_intel_main_def_s = +{ + sizeof(tdav_codec_h264_intel_t), + tdav_codec_h264_intel_main_ctor, + tdav_codec_h264_intel_main_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_intel_main_plugin_def_s = +{ + &tdav_codec_h264_intel_main_def_s, + + tmedia_video, + tmedia_codec_id_h264_mp, + "H264", + "H264 main Profile (Intel Media SDK)", + TMEDIA_CODEC_FORMAT_H264_MP, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (width, height, fps) */ + { 176, 144, 0 }, // fps is @deprecated + + tdav_codec_h264_intel_set, + tdav_codec_h264_intel_open, + tdav_codec_h264_intel_close, + tdav_codec_h264_intel_encode, + tdav_codec_h264_intel_decode, + tdav_codec_h264_intel_sdp_att_match, + tdav_codec_h264_intel_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h264_intel_main_plugin_def_t = &tdav_codec_h264_intel_main_plugin_def_s; + +/* ============ Common To all H264 profiles ================= */ + +static int tdav_codec_h264_intel_open_encoder(tdav_codec_h264_intel_t* self) +{ + if (self->encoder.pInst) + { + INTEL_BREAK("Already initialized"); + } + + if (!(self->encoder.pInst = new IntelCodecEncoder(self->mfxSession))) + { + INTEL_CHECK_STATUS(MFX_ERR_MEMORY_ALLOC); + } + INTEL_CHECK_STATUS(self->encoder.pInst->Open(self)); + + return 0; + +bail: + if (self->encoder.pInst) + { + delete self->encoder.pInst, self->encoder.pInst = NULL; + } + return -1; +} + +static int tdav_codec_h264_intel_close_encoder(tdav_codec_h264_intel_t* self) +{ + if (self) + { + if (self->encoder.pInst) + { + delete self->encoder.pInst; + self->encoder.pInst = NULL; + } + self->encoder.frame_count = 0; + } + return 0; +} + +int tdav_codec_h264_intel_open_decoder(tdav_codec_h264_intel_t* self) +{ + if (self->decoder.pInst) + { + INTEL_BREAK("Already initialized"); + } + + if (!(self->decoder.pInst = new IntelCodecDecoder(self->mfxSession))) + { + INTEL_CHECK_STATUS(MFX_ERR_MEMORY_ALLOC); + } + INTEL_CHECK_STATUS(self->decoder.pInst->Open(self)); + + return 0; + +bail: + if (self->decoder.pInst) + { + delete self->decoder.pInst, self->decoder.pInst = NULL; + } + return -1; +} + +static int tdav_codec_h264_intel_close_decoder(tdav_codec_h264_intel_t* self) +{ + if (self) + { + if (self->decoder.pInst) + { + delete self->decoder.pInst; + self->decoder.pInst = NULL; + } + } + return 0; +} + +static int tdav_codec_h264_intel_init(tdav_codec_h264_intel_t* self, profile_idc_t profile) +{ + int ret = -1; + level_idc_t level; + tdav_codec_h264_common_t* common = (tdav_codec_h264_common_t*)self; +#if INTEL_DX11_D3D + mfxHDL deviceHandle = NULL; +#endif + + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + + if ((ret = tdav_codec_h264_common_init(common))) { + TSK_DEBUG_ERROR("tdav_codec_h264_intel_common_init() faile with error code=%d", ret); + goto bail; + } + + if ((ret = tdav_codec_h264_common_level_from_size(TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height, &level))) { + TSK_DEBUG_ERROR("Failed to find level for size=[%u, %u]", TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height); + goto bail; + } + + if (/*MFUtils::IsLowLatencyH264SupportsMaxSliceSize()*/0) { // TODO: MSDK doesn't support PM=0. Neg. PM=1 but try to do the best to produce SingleNalUnits + common->pack_mode_local = H264_PACKETIZATION_MODE; + } + else { + common->pack_mode_local = Non_Interleaved_Mode; + } + common->profile = profile; + common->level = level; +#if 0 + // A.2.1.1 Constrained Baseline profile + // Conformance of a bitstream to the Constrained Baseline profile is indicated by profile_idc being equal to 66 with + // constraint_set1_flag being equal to 1. + common->profile_iop = 0xe0; // "constraint_set0_flag=1 and constraint_set1_flag=1" -> Constrained Baseline profile +#endif + TMEDIA_CODEC_VIDEO(self)->in.max_mbps = TMEDIA_CODEC_VIDEO(self)->out.max_mbps = H264_MAX_MBPS * 1000; + TMEDIA_CODEC_VIDEO(self)->in.max_br = TMEDIA_CODEC_VIDEO(self)->out.max_br = H264_MAX_BR * 1000; + + TMEDIA_CODEC_VIDEO(self)->in.chroma = tmedia_chroma_yuv420p; // decoded + TMEDIA_CODEC_VIDEO(self)->out.chroma = tmedia_chroma_yuv420p; // encoded + + if (!self->mfxSession && !(self->mfxSession = new MFXVideoSession())) + { + INTEL_CHECK_STATUS(MFX_ERR_MEMORY_ALLOC); + } + INTEL_CHECK_STATUS(self->mfxSession->Init(__IntelDefaultImpl, &__IntelDefaultVer)); + +#if INTEL_DX11_D3D + // Create DirectX device context + INTEL_CHECK_STATUS(D3D11_CreateHWDevice(self, *self->mfxSession, &deviceHandle, NULL)); + + // Provide device manager to Media SDK + INTEL_CHECK_STATUS(self->mfxSession->SetHandle(MFX_HANDLE_D3D11_DEVICE, deviceHandle)); + + self->D3D11Allocator.Alloc = D3D11_SimpleAlloc; + self->D3D11Allocator.Free = D3D11_SimpleFree; + self->D3D11Allocator.Lock = D3D11_SimpleLock; + self->D3D11Allocator.Unlock = D3D11_SimpleUnlock; + self->D3D11Allocator.GetHDL = D3D11_SimpleGethdl; + self->D3D11Allocator.pthis = self; + + // Since we are using video memory we must provide Media SDK with an external allocator + INTEL_CHECK_STATUS(self->mfxSession->SetFrameAllocator(&self->D3D11Allocator)); +#endif /* INTEL_DX11_D3D */ + + ret = 0; + +bail: + return ret; +} + +static int tdav_codec_h264_intel_deinit(tdav_codec_h264_intel_t* self) +{ + if (!self) + { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tdav_codec_h264_intel_close((tmedia_codec_t*)self); + + if (self->mfxSession) + { + delete self->mfxSession, self->mfxSession = NULL; + } + +#if INTEL_DX11_D3D + D3D11_CleanupHWDevice(self); +#endif /* INTEL_DX11_D3D */ + + return 0; +} + + +#if INTEL_DX11_D3D + +static IDXGIAdapter* D3D11_GetIntelDeviceAdapterHandle(mfxHDL _pthis, mfxSession session) +{ + mfxU32 adapterNum = 0; + mfxIMPL impl; + tdav_codec_h264_intel_t* pthis = (tdav_codec_h264_intel_t*)_pthis; + + MFXQueryIMPL(session, &impl); + + mfxIMPL baseImpl = MFX_IMPL_BASETYPE(impl); // Extract Media SDK base implementation type + + // get corresponding adapter number + for (mfxU8 i = 0; i < sizeof(implTypes) / sizeof(implTypes[0]); i++) + { + if (implTypes[i].impl == baseImpl) + { + adapterNum = implTypes[i].adapterID; + break; + } + } + + HRESULT hres = CreateDXGIFactory(__uuidof(IDXGIFactory2), (void**)(&pthis->pDXGIFactory)); + if (FAILED(hres)) + { + INTEL_DEBUG_ERROR("CreateDXGIFactory failed: %ld", hres); + return NULL; + } + + IDXGIAdapter* adapter = NULL; + hres = pthis->pDXGIFactory->EnumAdapters(adapterNum, &adapter); + if (FAILED(hres)) + { + INTEL_DEBUG_ERROR("EnumAdapters failed: %ld", hres); + return NULL; + } + + return adapter; +} + +// Create DirectX 11 device context +// - Required when using D3D surfaces. +// - D3D Device created and handed to Intel Media SDK +// - Intel graphics device adapter will be determined automatically (does not have to be primary), +// but with the following caveats: +// - Device must be active (but monitor does NOT have to be attached) +// - Device must be enabled in BIOS. Required for the case when used together with a discrete graphics card +// - For switchable graphics solutions (mobile) make sure that Intel device is the active device +static mfxStatus D3D11_CreateHWDevice(mfxHDL _pthis, mfxSession session, mfxHDL* deviceHandle, HWND hWnd) +{ + hWnd; // Window handle not required by DX11 since we do not showcase rendering. + + HRESULT hres = S_OK; + tdav_codec_h264_intel_t* pthis = (tdav_codec_h264_intel_t*)_pthis; + + static D3D_FEATURE_LEVEL FeatureLevels[] = { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0 + }; + D3D_FEATURE_LEVEL pFeatureLevelsOut; + + pthis->hAdapter = D3D11_GetIntelDeviceAdapterHandle(_pthis, session); + if (NULL == pthis->hAdapter) + { + INTEL_DEBUG_ERROR("D3D11_GetIntelDeviceAdapterHandle failed"); + return MFX_ERR_DEVICE_FAILED; + } + + hres = D3D11CreateDevice(pthis->hAdapter, + D3D_DRIVER_TYPE_UNKNOWN, + NULL, + 0, + FeatureLevels, + (sizeof(FeatureLevels) / sizeof(FeatureLevels[0])), + D3D11_SDK_VERSION, + &pthis->pD3D11Device, + &pFeatureLevelsOut, + &pthis->pD3D11Ctx); + if (FAILED(hres)) + { + INTEL_DEBUG_ERROR("D3D11CreateDevice failed: %ld", hres); + return MFX_ERR_DEVICE_FAILED; + } + + // turn on multithreading for the DX11 cntext + CComQIPtr<ID3D10Multithread> p_mt(pthis->pD3D11Ctx); + if (p_mt) + { + p_mt->SetMultithreadProtected(true); + } + else + { + INTEL_DEBUG_ERROR("Failed to create ID3D10Multithread object"); + return MFX_ERR_DEVICE_FAILED; + } + + *deviceHandle = (mfxHDL)pthis->pD3D11Device; + return MFX_ERR_NONE; +} + +static void D3D11_CleanupHWDevice(mfxHDL _pthis) +{ + tdav_codec_h264_intel_t* pthis = (tdav_codec_h264_intel_t*)_pthis; + pthis->pD3D11Device = NULL; + pthis->pDXGIFactory = NULL; + pthis->pD3D11Ctx = NULL; + pthis->hAdapter = NULL; + pthis->pAdapter = NULL; +} + +static void D3D11_SetHWDeviceContext(mfxHDL _pthis, CComPtr<ID3D11DeviceContext> devCtx) +{ + tdav_codec_h264_intel_t* pthis = (tdav_codec_h264_intel_t*)_pthis; + pthis->pD3D11Ctx = devCtx; + devCtx->GetDevice(&pthis->pD3D11Device); +} + +// Intel Media SDK memory allocator entrypoints.... +// - A slightly different allocation procedure is used for encode, decode and VPP +static mfxStatus _D3D11_SimpleAlloc(mfxHDL _pthis, mfxFrameAllocRequest *request, mfxFrameAllocResponse *response) +{ + HRESULT hRes; + tdav_codec_h264_intel_t* pthis = (tdav_codec_h264_intel_t*)_pthis; + + // Determine surface format (current simple implementation only supports NV12 and RGB4(32)) + DXGI_FORMAT format; + if (MFX_FOURCC_NV12 == request->Info.FourCC) + { + format = DXGI_FORMAT_NV12; + } + else if (MFX_FOURCC_RGB4 == request->Info.FourCC) + { + format = DXGI_FORMAT_B8G8R8A8_UNORM; + } + else + { + format = DXGI_FORMAT_P8; + } + + // Allocate custom container to keep texture and stage buffers for each surface + // Container also stores the intended read and/or write operation. + CustomMemId** mids = new CustomMemId *[request->NumFrameSuggested]; + if (!mids) return MFX_ERR_MEMORY_ALLOC; + for (int i = 0; i < request->NumFrameSuggested; i++) + { + mids[i] = new CustomMemId; + memset(mids[i], 0, sizeof(CustomMemId)); + mids[i]->rw = request->Type & 0xF000; // Set intended read/write operation + } + + request->Type = request->Type & 0x0FFF; + + // because P8 data (bitstream) for h264 encoder should be allocated by CreateBuffer() + // but P8 data (MBData) for MPEG2 encoder should be allocated by CreateTexture2D() + if (request->Info.FourCC == MFX_FOURCC_P8) + { + D3D11_BUFFER_DESC desc = { 0 }; + + desc.ByteWidth = request->Info.Width * request->Info.Height; + desc.Usage = D3D11_USAGE_STAGING; + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + desc.StructureByteStride = 0; + + ID3D11Buffer * buffer = 0; + hRes = pthis->pD3D11Device->CreateBuffer(&desc, 0, &buffer); + if (FAILED(hRes)) + { + INTEL_DEBUG_ERROR("CreateBuffer failed:%ld", hRes); + return MFX_ERR_MEMORY_ALLOC; + } + + mids[0]->memId = reinterpret_cast<ID3D11Texture2D *>(buffer); + } + else + { + D3D11_TEXTURE2D_DESC desc = { 0 }; + + desc.Width = request->Info.Width; + desc.Height = request->Info.Height; + desc.MipLevels = 1; + desc.ArraySize = 1; // number of subresources is 1 in this case + desc.Format = format; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_DECODER; + desc.MiscFlags = 0; + + if ((MFX_MEMTYPE_FROM_VPPIN & request->Type) && + (DXGI_FORMAT_B8G8R8A8_UNORM == desc.Format)) + { + desc.BindFlags = D3D11_BIND_RENDER_TARGET; + if (desc.ArraySize > 2) + return MFX_ERR_MEMORY_ALLOC; + } + + if ((MFX_MEMTYPE_FROM_VPPOUT & request->Type) || + (MFX_MEMTYPE_VIDEO_MEMORY_PROCESSOR_TARGET & request->Type)) + { + desc.BindFlags = D3D11_BIND_RENDER_TARGET; + if (desc.ArraySize > 2) + { + return MFX_ERR_MEMORY_ALLOC; + } + } + + if (DXGI_FORMAT_P8 == desc.Format) + desc.BindFlags = 0; + + ID3D11Texture2D* pTexture2D; + + // Create surface textures + for (size_t i = 0; i < request->NumFrameSuggested / desc.ArraySize; i++) + { + hRes = pthis->pD3D11Device->CreateTexture2D(&desc, NULL, &pTexture2D); + + if (FAILED(hRes)) + { + INTEL_DEBUG_ERROR("CreateTexture2D failed:%ld", hRes); + return MFX_ERR_MEMORY_ALLOC; + } + + mids[i]->memId = pTexture2D; + } + + desc.ArraySize = 1; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ /*| D3D11_CPU_ACCESS_WRITE*/; + desc.BindFlags = 0; + desc.MiscFlags = 0; + + // Create surface staging textures + for (size_t i = 0; i < request->NumFrameSuggested; i++) + { + hRes = pthis->pD3D11Device->CreateTexture2D(&desc, NULL, &pTexture2D); + + if (FAILED(hRes)) + { + INTEL_DEBUG_ERROR("CreateTexture2D failed:%ld", hRes); + return MFX_ERR_MEMORY_ALLOC; + } + + mids[i]->memIdStage = pTexture2D; + } + } + + response->mids = (mfxMemId*)mids; + response->NumFrameActual = request->NumFrameSuggested; + + return MFX_ERR_NONE; +} + +static mfxStatus D3D11_SimpleAlloc(mfxHDL _pthis, mfxFrameAllocRequest *request, mfxFrameAllocResponse *response) +{ + tdav_codec_h264_intel_t* pthis = (tdav_codec_h264_intel_t*)_pthis; + mfxStatus sts = MFX_ERR_NONE; + int idx = (MFX_MEMTYPE_FROM_DECODE & request->Type) ? 1 : 0; + + if (request->NumFrameSuggested <= pthis->D3D11SavedAllocResponses[idx].NumFrameActual && + MFX_MEMTYPE_EXTERNAL_FRAME & request->Type && + MFX_MEMTYPE_FROM_DECODE & request->Type && + pthis->D3D11SavedAllocResponses[idx].NumFrameActual != 0) + { + // Memory for this request was already allocated during manual allocation stage. Return saved response + // When decode acceleration device (DXVA) is created it requires a list of d3d surfaces to be passed. + // Therefore Media SDK will ask for the surface info/mids again at Init() stage, thus requiring us to return the saved response + // (No such restriction applies to Encode or VPP) + + *response = pthis->D3D11SavedAllocResponses[idx]; + } + else + { + sts = _D3D11_SimpleAlloc(_pthis, request, response); + pthis->D3D11SavedAllocResponses[idx] = *response; + } + + return sts; +} + +static mfxStatus D3D11_SimpleLock(mfxHDL _pthis, mfxMemId mid, mfxFrameData *ptr) +{ + HRESULT hRes = S_OK; + tdav_codec_h264_intel_t* pthis = (tdav_codec_h264_intel_t*)_pthis; + + D3D11_TEXTURE2D_DESC desc = { 0 }; + D3D11_MAPPED_SUBRESOURCE lockedRect = { 0 }; + + CustomMemId* memId = (CustomMemId*)mid; + ID3D11Texture2D* pSurface = (ID3D11Texture2D *)memId->memId; + ID3D11Texture2D* pStage = (ID3D11Texture2D *)memId->memIdStage; + + D3D11_MAP mapType = D3D11_MAP_READ; + UINT mapFlags = D3D11_MAP_FLAG_DO_NOT_WAIT; + + if (NULL == pStage) + { + hRes = pthis->pD3D11Ctx->Map(pSurface, 0, mapType, mapFlags, &lockedRect); + desc.Format = DXGI_FORMAT_P8; + } + else + { + pSurface->GetDesc(&desc); + + // copy data only in case of user wants o read from stored surface + if (memId->rw & D3D11_WILL_READ) + { + pthis->pD3D11Ctx->CopySubresourceRegion(pStage, 0, 0, 0, 0, pSurface, 0, NULL); + } + + do + { + hRes = pthis->pD3D11Ctx->Map(pStage, 0, mapType, mapFlags, &lockedRect); + if (S_OK != hRes && DXGI_ERROR_WAS_STILL_DRAWING != hRes) + { + return MFX_ERR_LOCK_MEMORY; + } + } while (DXGI_ERROR_WAS_STILL_DRAWING == hRes); + } + + if (FAILED(hRes)) + { + return MFX_ERR_LOCK_MEMORY; + } + + switch (desc.Format) + { + case DXGI_FORMAT_NV12: + ptr->Pitch = (mfxU16)lockedRect.RowPitch; + ptr->Y = (mfxU8 *)lockedRect.pData; + ptr->U = (mfxU8 *)lockedRect.pData + desc.Height * lockedRect.RowPitch; + ptr->V = ptr->U + 1; + break; + case DXGI_FORMAT_B8G8R8A8_UNORM: + ptr->Pitch = (mfxU16)lockedRect.RowPitch; + ptr->B = (mfxU8 *)lockedRect.pData; + ptr->G = ptr->B + 1; + ptr->R = ptr->B + 2; + ptr->A = ptr->B + 3; + break; + case DXGI_FORMAT_P8: + ptr->Pitch = (mfxU16)lockedRect.RowPitch; + ptr->Y = (mfxU8 *)lockedRect.pData; + ptr->U = 0; + ptr->V = 0; + break; + default: + return MFX_ERR_LOCK_MEMORY; + } + + return MFX_ERR_NONE; +} + +static mfxStatus D3D11_SimpleUnlock(mfxHDL _pthis, mfxMemId mid, mfxFrameData *ptr) +{ + tdav_codec_h264_intel_t* pthis = (tdav_codec_h264_intel_t*)_pthis; + + CustomMemId* memId = (CustomMemId*)mid; + ID3D11Texture2D* pSurface = (ID3D11Texture2D *)memId->memId; + ID3D11Texture2D* pStage = (ID3D11Texture2D *)memId->memIdStage; + + if (NULL == pStage) + { + pthis->pD3D11Ctx->Unmap(pSurface, 0); + } + else + { + pthis->pD3D11Ctx->Unmap(pStage, 0); + // copy data only in case of user wants to write to stored surface + if (memId->rw & D3D11_WILL_WRITE) + { + pthis->pD3D11Ctx->CopySubresourceRegion(pSurface, 0, 0, 0, 0, pStage, 0, NULL); + } + } + + if (ptr) + { + ptr->Pitch = 0; + ptr->U = ptr->V = ptr->Y = 0; + ptr->A = ptr->R = ptr->G = ptr->B = 0; + } + + return MFX_ERR_NONE; +} + +static mfxStatus D3D11_SimpleGethdl(mfxHDL _pthis, mfxMemId mid, mfxHDL *handle) +{ + _pthis; + if (NULL == handle) + { + return MFX_ERR_INVALID_HANDLE; + } + + mfxHDLPair* pPair = (mfxHDLPair*)handle; + CustomMemId* memId = (CustomMemId*)mid; + + pPair->first = memId->memId; // surface texture + pPair->second = 0; + + return MFX_ERR_NONE; +} + +static mfxStatus D3D11_SimpleFree(mfxHDL _pthis, mfxFrameAllocResponse *response) +{ + _pthis; + if (NULL == response) + { + return MFX_ERR_NULL_PTR; + } + + if (response->mids) + { + for (mfxU32 i = 0; i < response->NumFrameActual; i++) + { + if (response->mids[i]) + { + CustomMemId* mid = (CustomMemId*)response->mids[i]; + ID3D11Texture2D* pSurface = (ID3D11Texture2D *)mid->memId; + ID3D11Texture2D* pStage = (ID3D11Texture2D *)mid->memIdStage; + if (pSurface) + { + pSurface->Release(); + } + if (pStage) + { + pStage->Release(); + } + delete mid; + } + } + } + + delete[] response->mids; + response->mids = 0; + + return MFX_ERR_NONE; +} + +#endif /* INTEL_DX11_D3D */ + +#endif /* HAVE_INTEL_MEDIA_SDK */ diff --git a/tinyDAV/src/codecs/h264/tdav_codec_h264_rtp.c b/tinyDAV/src/codecs/h264/tdav_codec_h264_rtp.c new file mode 100644 index 0000000..5742f43 --- /dev/null +++ b/tinyDAV/src/codecs/h264/tdav_codec_h264_rtp.c @@ -0,0 +1,411 @@ +/* +* 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_h264_rtp.c + * @brief H.264 payloader/depayloder as per RFC 3984 + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + */ +#include "tinydav/codecs/h264/tdav_codec_h264_rtp.h" + +#include "tinydav/codecs/h264/tdav_codec_h264_common.h" + +#include "tinymedia/tmedia_codec.h" + +#include "tsk_string.h" +#include "tsk_debug.h" + +#include "tsk_memory.h" +#include <string.h> /* strlen() */ +#include <stdlib.h> /* strtol() */ + +/* +* ITU H.264 - http://www.itu.int/rec/T-REC-H.264-200903-S/en +*/ + +uint8_t H264_START_CODE_PREFIX[4] = { 0x00, 0x00, 0x00, 0x01 }; + +#define H264_NAL_UNIT_TYPE_HEADER_SIZE 1 +#define H264_F_UNIT_TYPE_HEADER_SIZE 1 +#define H264_FUA_HEADER_SIZE 2 +#define H264_FUB_HEADER_SIZE 4 +#define H264_NAL_AGG_MAX_SIZE 65535 + +static int tdav_codec_h264_get_fua_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp, tsk_bool_t* end_of_unit); +static int tdav_codec_h264_get_nalunit_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size); + +// profile_level_id MUST be a "null-terminated" string +int tdav_codec_h264_parse_profile(const char* profile_level_id, profile_idc_t *p_idc, profile_iop_t *p_iop, level_idc_t *l_idc) +{ + uint32_t value; + + if(tsk_strlen(profile_level_id) != 6){ + TSK_DEBUG_ERROR("I say [%s] is an invalid profile-level-id", profile_level_id); + return -1; + } + + value = strtol(profile_level_id, tsk_null, 16); + + /* profile-idc */ + if(p_idc){ + switch((value >> 16)){ + case profile_idc_baseline: + *p_idc = profile_idc_baseline; + break; + case profile_idc_extended: + *p_idc = profile_idc_extended; + break; + case profile_idc_main: + *p_idc = profile_idc_main; + break; + case profile_idc_high: + *p_idc = profile_idc_high; + break; + default: + *p_idc = profile_idc_none; + break; + } + } + + /* profile-iop */ + if(p_iop){ + p_iop->constraint_set0_flag = ((value >> 8) & 0x80)>>7; + p_iop->constraint_set1_flag = ((value >> 8) & 0x40)>>6; + p_iop->constraint_set2_flag = ((value >> 8) & 0x20)>>5; + p_iop->reserved_zero_5bits = ((value >> 8) & 0x1F); + } + + /* level-idc */ + if(l_idc){ + switch((value & 0xFF)){ + case level_idc_1_0: + *l_idc = level_idc_1_0; + break; + case level_idc_1_b: + *l_idc = level_idc_1_b; + break; + case level_idc_1_1: + *l_idc = level_idc_1_1; + break; + case level_idc_1_2: + *l_idc = level_idc_1_2; + break; + case level_idc_1_3: + *l_idc = level_idc_1_3; + break; + case level_idc_2_0: + *l_idc = level_idc_2_0; + break; + case level_idc_2_1: + *l_idc = level_idc_2_1; + break; + case level_idc_2_2: + *l_idc = level_idc_2_2; + break; + case level_idc_3_0: + *l_idc = level_idc_3_0; + break; + case level_idc_3_1: + *l_idc = level_idc_3_1; + break; + case level_idc_3_2: + *l_idc = level_idc_3_2; + break; + case level_idc_4_0: + *l_idc = level_idc_4_0; + break; + case level_idc_4_1: + *l_idc = level_idc_4_1; + break; + case level_idc_4_2: + *l_idc = level_idc_4_2; + break; + case level_idc_5_0: + *l_idc = level_idc_5_0; + break; + case level_idc_5_1: + *l_idc = level_idc_5_1; + break; + case level_idc_5_2: + *l_idc = level_idc_5_2; + break; + default: + *l_idc = level_idc_none; + break; + } + } + + return 0; +} + +int tdav_codec_h264_get_pay(const void* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp, tsk_bool_t* end_of_unit) +{ + const uint8_t* pdata = (const uint8_t*)in_data; + uint8_t nal_type; + if (!in_data || !in_size || !out_data || !out_size || !append_scp || !end_of_unit) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + *out_data = tsk_null; + *out_size = 0; + + /* 5.3. NAL Unit Octet Usage + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + */ + switch ((nal_type = (pdata[0] & 0x1F))) { + case undefined_0: + case undefined_30: + case undefined_31: + case stap_a: + case stap_b: + case mtap16: + case mtap24: + case fu_b: + break; + case fu_a: + return tdav_codec_h264_get_fua_pay(pdata, in_size, out_data, out_size, append_scp, end_of_unit); + default: /* NAL unit (1-23) */ + *append_scp = tsk_true; //(nal_type != 7 && nal_type != 8); // SPS or PPS + *end_of_unit = tsk_true; + return tdav_codec_h264_get_nalunit_pay(pdata, in_size, out_data, out_size); + } + + TSK_DEBUG_WARN("%d not supported as valid NAL Unit type", (*pdata & 0x1F)); + return -1; +} + + +static int tdav_codec_h264_get_fua_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp, tsk_bool_t* end_of_unit) +{ + if (in_size <=H264_FUA_HEADER_SIZE) { + TSK_DEBUG_ERROR("Too short"); + return -1; + } + /* RFC 3984 - 5.8. Fragmentation Units (FUs) + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | FU indicator | FU header | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + | | + | FU payload | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | :...OPTIONAL RTP padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The FU indicator octet has the following format: + + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + + The FU header has the following format: + + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |S|E|R| Type | + +---------------+ + */ + + if (((in_data[1] & 0x80) /*S*/)) { + /* discard "FU indicator" */ + *out_data = (in_data + H264_NAL_UNIT_TYPE_HEADER_SIZE); + *out_size = (in_size - H264_NAL_UNIT_TYPE_HEADER_SIZE); + + // Do need to append Start Code Prefix ? + /* S: 1 bit + When set to one, the Start bit indicates the start of a fragmented + NAL unit. When the following FU payload is not the start of a + fragmented NAL unit payload, the Start bit is set to zero.*/ + *append_scp = tsk_true; + + // F, NRI and Type + *((uint8_t*)*out_data) = (in_data[0] & 0xe0) /* F,NRI from "FU indicator"*/ | (in_data[1] & 0x1f) /* type from "FU header" */; + } + else { + *append_scp = tsk_false; + *out_data = (in_data + H264_FUA_HEADER_SIZE); + *out_size = (in_size - H264_FUA_HEADER_SIZE); + } + /* + E: 1 bit + When set to one, the End bit indicates the end of a fragmented + NAL unit, i.e., the last byte of the payload is also the last + byte of the fragmented NAL unit. When the following FU + payload is not the last fragment of a fragmented NAL unit, the + End bit is set to zero. + */ + *end_of_unit = (((in_data[1] & 0x40) /*E*/)) ? tsk_true : tsk_false; + + return 0; +} + +static int tdav_codec_h264_get_nalunit_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size) +{ + +/* 5.6. Single NAL Unit Packet + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F|NRI| type | | + +-+-+-+-+-+-+-+-+ | + | | + | Bytes 2..n of a Single NAL unit | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | :...OPTIONAL RTP padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + + *out_data = in_data; + *out_size = in_size; + + return 0; +} + +void tdav_codec_h264_rtp_encap(struct tdav_codec_h264_common_s* self, const uint8_t* pdata, tsk_size_t size) +{ + static const tsk_size_t size_of_scp = sizeof(H264_START_CODE_PREFIX); /* we know it's equal to 4 .. */ + register tsk_size_t i; + tsk_size_t last_scp, prev_scp; + tsk_size_t _size; + + if (!pdata || size < size_of_scp) { + return; + } + + if (pdata[0] == 0 && pdata[1] == 0) { + if (pdata[2] == 1) { + pdata += 3, size -= 3; + } + else if (pdata[2] == 0 && pdata[3] == 1) { + pdata += 4, size -= 4; + } + } + + _size = (size - size_of_scp); + last_scp = 0, prev_scp = 0; + for (i = size_of_scp; i<_size; i++) { + if (pdata[i] == 0 && pdata[i+1] == 0 && (pdata[i+2] == 1 || (pdata[i+2] == 0 && pdata[i+3] == 1))) { /* Find Start Code Prefix */ + prev_scp = last_scp; + if ((i - last_scp) >= H264_RTP_PAYLOAD_SIZE || 1) { + tdav_codec_h264_rtp_callback(self, pdata + prev_scp, + (i - prev_scp), (prev_scp == size)); + } + last_scp = i; + i += (pdata[i+2] == 1) ? 3 : 4; + } + } + + if (last_scp < (int32_t)size) { + tdav_codec_h264_rtp_callback(self, pdata + last_scp, + (size - last_scp), tsk_true); + } +} + +void tdav_codec_h264_rtp_callback(struct tdav_codec_h264_common_s *self, const void *data, tsk_size_t size, tsk_bool_t marker) +{ + uint8_t* pdata = (uint8_t*)data; + + //TSK_DEBUG_INFO("%x %x %x %x -- %u", pdata[0], pdata[1], pdata[2], pdata[3], size); + + if (size>4 && pdata[0] == H264_START_CODE_PREFIX[0] && pdata[1] == H264_START_CODE_PREFIX[1]) { + if(pdata[2] == H264_START_CODE_PREFIX[3]){ + pdata += 3, size -= 3; + } + else if (pdata[2] == H264_START_CODE_PREFIX[2] && pdata[3] == H264_START_CODE_PREFIX[3]) { + pdata += 4, size -= 4; + } + } + + //TSK_DEBUG_INFO("==> SCP %2x %2x %2x %2x", pdata[0], pdata[1], pdata[2], pdata[3]); + + if (self->pack_mode_local == Single_NAL_Unit_Mode || size < H264_RTP_PAYLOAD_SIZE) { + if (self->pack_mode_local == Single_NAL_Unit_Mode && size > H264_RTP_PAYLOAD_SIZE) { + TSK_DEBUG_WARN("pack_mode=Single_NAL_Unit_Mode but size(%d) > H264_RTP_PAYLOAD_SIZE(%d). Did you forget to set \"avctx->rtp_payload_size\"?", size, H264_RTP_PAYLOAD_SIZE); + } + // Can be packet in a Single Nal Unit + // Send data over the network + if (TMEDIA_CODEC_VIDEO(self)->out.callback) { + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = pdata; + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = size; + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (uint32_t)((1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate); + TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = marker; + TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); + } + } + else if (size > H264_NAL_UNIT_TYPE_HEADER_SIZE) { + /* Should be Fragmented as FUA */ + uint8_t fua_hdr[H264_FUA_HEADER_SIZE]; /* "FU indicator" and "FU header" - 2bytes */ + fua_hdr[0] = pdata[0] & 0x60/* NRI */, fua_hdr[0] |= fu_a; + fua_hdr[1] = 0x80/* S=1,E=0,R=0 */, fua_hdr[1] |= pdata[0] & 0x1f; /* type */ + // discard header + pdata += H264_NAL_UNIT_TYPE_HEADER_SIZE; + size -= H264_NAL_UNIT_TYPE_HEADER_SIZE; + + while(size) { + tsk_size_t packet_size = TSK_MIN(H264_RTP_PAYLOAD_SIZE, size); + + if (self->rtp.size < (packet_size + H264_FUA_HEADER_SIZE)){ + if(!(self->rtp.ptr = (uint8_t*)tsk_realloc(self->rtp.ptr, (packet_size + H264_FUA_HEADER_SIZE)))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + return; + } + self->rtp.size = (packet_size + H264_FUA_HEADER_SIZE); + } + // set E bit + if((size - packet_size) == 0){ + // Last packet + fua_hdr[1] |= 0x40; + } + // copy FUA header + memcpy(self->rtp.ptr, fua_hdr, H264_FUA_HEADER_SIZE); + // reset "S" bit + fua_hdr[1] &= 0x7F; + // copy data + memcpy((self->rtp.ptr + H264_FUA_HEADER_SIZE), pdata, packet_size); + pdata += packet_size; + size -= packet_size; + + // send data + 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 = (packet_size + H264_FUA_HEADER_SIZE); + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (uint32_t)((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); + } + } + } +} diff --git a/tinyDAV/src/codecs/ilbc/tdav_codec_ilbc.c b/tinyDAV/src/codecs/ilbc/tdav_codec_ilbc.c new file mode 100644 index 0000000..65df6ad --- /dev/null +++ b/tinyDAV/src/codecs/ilbc/tdav_codec_ilbc.c @@ -0,0 +1,265 @@ +/* +* 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_ilbc.c + * @brief iLBC codec + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/ilbc/tdav_codec_ilbc.h" + +#if HAVE_ILBC + +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#define TDAV_ILBC_MODE 20 + +/* ============ iLBC Plugin interface ================= */ + +#define tdav_codec_ilbc_set tsk_null + +static int tdav_codec_ilbc_open(tmedia_codec_t* self) +{ + tdav_codec_ilbc_t* ilbc = (tdav_codec_ilbc_t*)self; + + initEncode(&ilbc->encoder, TDAV_ILBC_MODE); + initDecode(&ilbc->decoder, TDAV_ILBC_MODE, tsk_true/* Enhancer */); + + return 0; +} + +static int tdav_codec_ilbc_close(tmedia_codec_t* self) +{ + tdav_codec_ilbc_t* ilbc = (tdav_codec_ilbc_t*)self; + + //ilbc->encoder = {0}; + //ilbc->decoder = {0}; + + return 0; +} + +static tsk_size_t tdav_codec_ilbc_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tdav_codec_ilbc_t* ilbc = (tdav_codec_ilbc_t*)self; + int k; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* convert signal to float */ + for(k=0; k<ilbc->encoder.blockl; k++){ + ilbc->encblock[k] = (float)((short*)in_data)[k]; + } + + /* allocate new buffer if needed */ + if((int)*out_max_size <ilbc->encoder.no_of_bytes){ + if(!(*out_data = tsk_realloc(*out_data, ilbc->encoder.no_of_bytes))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = ilbc->encoder.no_of_bytes; + } + + /* do the actual encoding */ + iLBC_encode(*out_data, ilbc->encblock, &ilbc->encoder); + + return ilbc->encoder.no_of_bytes; +} + +static tsk_size_t tdav_codec_ilbc_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) +{ + int blocks, i, k, block_size; + float dtmp; + tsk_size_t out_size; + tdav_codec_ilbc_t* ilbc = (tdav_codec_ilbc_t*)self; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if((in_size % NO_OF_BYTES_20MS) == 0){ + /* Using 20ms mode */ + blocks = (in_size/NO_OF_BYTES_20MS); + out_size = (BLOCKL_20MS * blocks) * sizeof(short); + block_size = out_size/blocks; + if(ilbc->decoder.mode != 20){ + initDecode(&ilbc->decoder, 20, tsk_true/* Enhancer */); + } + } + else if((in_size % NO_OF_BYTES_30MS) == 0){ + /* Using 30ms mode */ + blocks = (in_size/NO_OF_BYTES_30MS); + out_size = (BLOCKL_30MS * blocks) * sizeof(short); + block_size = out_size/blocks; + if(ilbc->decoder.mode != 30){ + initDecode(&ilbc->decoder, 30, tsk_true/* Enhancer */); + } + } + else{ + TSK_DEBUG_ERROR("Invalid iLBC mode"); + return 0; + } + + /* allocate new buffer if needed */ + if(*out_max_size<out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + + for(i = 0; i<blocks; i++){ + iLBC_decode(ilbc->decblock, &((uint8_t*)in_data)[i*block_size], &ilbc->decoder, 1/* Normal */); + + /* convert to short */ + for(k=0; k<ilbc->decoder.blockl; k++){ + dtmp=ilbc->decblock[k]; + + if(dtmp<MIN_SAMPLE){ + dtmp = MIN_SAMPLE; + } + else if(dtmp>MAX_SAMPLE){ + dtmp = MAX_SAMPLE; + } + + ((short*)*out_data)[(i*block_size) + k] = ((short) dtmp); + } + } + + return out_size; +} + +static char* tdav_codec_ilbc_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, "mode=%d", TDAV_ILBC_MODE); + return fmtp; + } + return tsk_null; +} + +static tsk_bool_t tdav_codec_ilbc_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + if(tsk_striequals(att_name, "fmtp")){ + /* RFC 3952 - 5. Mapping To SDP Parameters + + The offer contains the preferred mode of the offerer. The answerer + may agree to that mode by including the same mode in the answer, or + may include a different mode. The resulting mode used by both + parties SHALL be the lower of the bandwidth modes in the offer and + answer. + + That is, an offer of "mode=20" receiving an answer of "mode=30" will + result in "mode=30" being used by both participants. Similarly, an + offer of "mode=30" and an answer of "mode=20" will result in + "mode=30" being used by both participants. + + This is important when one end point utilizes a bandwidth constrained + link (e.g., 28.8k modem link or slower), where only the lower frame + size will work. + */ + return tsk_true; // FIXME + } + return tsk_true; +} + + +// +// iLBC Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_ilbc_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_ilbc_t *ilbc = self; + if(ilbc){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_ilbc_dtor(tsk_object_t * self) +{ + tdav_codec_ilbc_t *ilbc = self; + if(ilbc){ + /* deinit base */ + tmedia_codec_audio_deinit(ilbc); + /* deinit self */ + //ilbc->encoder = {0}; + //ilbc->decoder = {0}; + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_ilbc_def_s = +{ + sizeof(tdav_codec_ilbc_t), + tdav_codec_ilbc_ctor, + tdav_codec_ilbc_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_ilbc_plugin_def_s = +{ + &tdav_codec_ilbc_def_s, + + tmedia_audio, + tmedia_codec_id_ilbc, + "iLBC", + "iLBC codec (libILBc)", + TMEDIA_CODEC_FORMAT_ILBC, + tsk_true, + 8000, // rate + + { /* audio */ + 1, // channels + 20 // ptime + }, + + /* video */ + {0}, + + tdav_codec_ilbc_set, + tdav_codec_ilbc_open, + tdav_codec_ilbc_close, + tdav_codec_ilbc_encode, + tdav_codec_ilbc_decode, + tdav_codec_ilbc_sdp_att_match, + tdav_codec_ilbc_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_ilbc_plugin_def_t = &tdav_codec_ilbc_plugin_def_s; + + +#endif /* HAVE_ILBC */ diff --git a/tinyDAV/src/codecs/mp4ves/tdav_codec_mp4ves.c b/tinyDAV/src/codecs/mp4ves/tdav_codec_mp4ves.c new file mode 100644 index 0000000..e48cb00 --- /dev/null +++ b/tinyDAV/src/codecs/mp4ves/tdav_codec_mp4ves.c @@ -0,0 +1,818 @@ +/* +* 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_mp4ves.c + * @brief MP4V-ES codec plugin + * RTP payloader/depayloader follows RFC 3016. + * ISO-IEC-14496-2: http://www.csus.edu/indiv/p/pangj/aresearch/video_compression/presentation/ISO-IEC-14496-2_2001_MPEG4_Visual.pdf + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + * @date Created: Th Dec 2 16:54:58 2010 mdiop + */ +#include "tinydav/codecs/mp4ves/tdav_codec_mp4ves.h" + +#if HAVE_FFMPEG + +#include "tinydav/video/tdav_converter_video.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tnet_endianness.h" + +#include "tinymedia/tmedia_params.h" +#include "tinymedia/tmedia_defaults.h" + +#include "tsk_string.h" +#include "tsk_params.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#include <libavcodec/avcodec.h> + +#define DEFAULT_PROFILE_LEVEL_ID Simple_Profile_Level_1 + +#define MP4V_GOP_SIZE_IN_SECONDS 25 +#define MP4V_RTP_PAYLOAD_SIZE 900 + +typedef struct tdav_codec_mp4ves_s +{ + TMEDIA_DECLARE_CODEC_VIDEO; + + int profile; + + struct{ + uint8_t* ptr; + tsk_size_t size; + } rtp; + + // Encoder + struct{ + AVCodec* codec; + AVCodecContext* context; + AVFrame* picture; + void* buffer; + tsk_bool_t force_idr; + int quality; // [1-31] + int rotation; + int32_t max_bw_kpbs; + } encoder; + + // decoder + struct{ + AVCodec* codec; + AVCodecContext* context; + AVFrame* picture; + + void* accumulator; + uint8_t ebit; + tsk_size_t accumulator_pos; + uint16_t last_seq; + } decoder; +} +tdav_codec_mp4ves_t; + +// From ISO-IEC-14496-2 +typedef enum mp4v_codes_e +{ + // To initiate a visual session (6.3.2) + visual_object_sequence_start_code = 0x000001B0, + // To terminate a visual session (6.3.2) + visual_object_sequence_end_code = 0x000001B1, + // To initiate a visual object (6.3.2) + visual_object_start_code = 0x000001B5, + // To identify the beginning of user data. The user data continues until receipt of another start code. (6.3.2.1) + user_data_start_code = 0x000001B2, + // The video_object_layer_start_code is a string of 32 bits. The first 28 bits are + // ‘0000 0000 0000 0000 0000 0001 0010‘ in binary and the last 4-bits represent one of the values in the range of + // ‘0000’ to ‘1111’ in binary. The video_object_layer_start_code marks a new video object layer. (6.3.3) + video_object_layer_start_code = 0x0000012, + // To identify the beginning of a GOV header (6.3.4) + group_of_vop_start_code = 0x000001B3, + // To mark the start of a video object plane (6.3.5 ) + vop_start_code = 0x000001B6, +} +mp4v_start_code_t; + +// From ISO-IEC-14496-2 Annex G +typedef enum mp4v_profiles_e +{ + /* Reserved = 0x00000000 */ + Simple_Profile_Level_1 = 1, + Simple_Profile_Level_2 = 2, + Simple_Profile_Level_3 = 3, + /* Reserved 00000100 ? 00010000 */ + Simple_Scalable_Profile_Level_1 = 17, + Simple_Scalable_Profile_Level_2 = 18, + /* Reserved 00010011 ? = 0x00100000 */ + Core_Profile_Level_1 = 33, + Core_Profile_Level_2 = 34, + /* Reserved 00100011 ? = 0x00110001 */ + Main_Profile_Level_2 = 50, + Main_Profile_Level_3 = 51, + Main_Profile_Level_4 = 52, + /* Reserved 00110101 ? = 0x01000001 */ + N_bit_Profile_Level_2 = 66, + /* Reserved 01000011 ? = 0x01010000 */ + Scalable_Texture_Profile_Level_1 = 81, + /* Reserved 01010010 ? 01100000 */ + Simple_Face_Animation_Profile_Level_1 = 97, + Simple_Face_Animation_Profile_Level_2 = 98, + Simple_FBA_Profile_Level_1 = 99, + Simple_FBA_Profile_Level_2 = 100, + /* Reserved 01100101 ? 01110000 */ + Basic_Animated_Texture_Profile_Level_1 = 113, + Basic_Animated_Texture_Profile_Level_2 = 114, + /* Reserved 01110011 ? 10000000 */ + Hybrid_Profile_Level_1 = 129, + Hybrid_Profile_Level_2 = 130, + /* Reserved 10000011 ? 10010000 */ + Advanced_Real_Time_Simple_Profile_Level_1 = 145, + Advanced_Real_Time_Simple_Profile_Level_2 = 146, + Advanced_Real_Time_Simple_Profile_Level_3 = 147, + Advanced_Real_Time_Simple_Profile_Level_4 = 148, + /* Reserved 10010101 ? 10100000 */ +} +mp4v_profiles_t; + +static int tdav_codec_mp4ves_open_encoder(tdav_codec_mp4ves_t* self); +static int tdav_codec_mp4ves_open_decoder(tdav_codec_mp4ves_t* self); +static int tdav_codec_mp4ves_close_encoder(tdav_codec_mp4ves_t* self); +static int tdav_codec_mp4ves_close_decoder(tdav_codec_mp4ves_t* self); + +static void tdav_codec_mp4ves_encap(tdav_codec_mp4ves_t* mp4v, const uint8_t* pdata, tsk_size_t size); +static void tdav_codec_mp4ves_rtp_callback(tdav_codec_mp4ves_t *mp4v, const void *data, tsk_size_t size, tsk_bool_t marker); + +/* ============ MP4V-ES Plugin interface functions ================= */ + +static int tdav_codec_mp4ves_set(tmedia_codec_t* self, const tmedia_param_t* param) +{ + tdav_codec_mp4ves_t* mp4ves = (tdav_codec_mp4ves_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: + { + mp4ves->encoder.force_idr = tsk_true; + break; + } + case tmedia_codec_action_bw_down: + { + mp4ves->encoder.quality = TSK_CLAMP(1, (mp4ves->encoder.quality + 1), 31); + mp4ves->encoder.context->global_quality = FF_QP2LAMBDA * mp4ves->encoder.quality; + break; + } + case tmedia_codec_action_bw_up: + { + mp4ves->encoder.quality = TSK_CLAMP(1, (mp4ves->encoder.quality - 1), 31); + mp4ves->encoder.context->global_quality = FF_QP2LAMBDA * mp4ves->encoder.quality; + break; + } + } + } + else if(tsk_striequals(param->key, "rotation")){ + int rotation = *((int32_t*)param->value); + if(mp4ves->encoder.rotation != rotation){ + if(self->opened){ + int ret; + mp4ves->encoder.rotation = rotation; + if((ret = tdav_codec_mp4ves_close_encoder(mp4ves))){ + return ret; + } + if((ret = tdav_codec_mp4ves_open_encoder(mp4ves))){ + return ret; + } + } + } + return 0; + } + } + return -1; +} + +int tdav_codec_mp4ves_open(tmedia_codec_t* self) +{ + int ret; + + tdav_codec_mp4ves_t* mp4v = (tdav_codec_mp4ves_t*)self; + + if(!mp4v){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + + // Encoder + if((ret = tdav_codec_mp4ves_open_encoder(mp4v))){ + return ret; + } + + // Decoder + if((ret = tdav_codec_mp4ves_open_decoder(mp4v))){ + return ret; + } + + return 0; +} + +int tdav_codec_mp4ves_close(tmedia_codec_t* self) +{ + tdav_codec_mp4ves_t* mp4v = (tdav_codec_mp4ves_t*)self; + + if(!mp4v){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is opened */ + + // Encoder + tdav_codec_mp4ves_close_encoder(mp4v); + + // Decoder + tdav_codec_mp4ves_close_decoder(mp4v); + + return 0; +} + +tsk_size_t tdav_codec_mp4ves_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_mp4ves_t* mp4v = (tdav_codec_mp4ves_t*)self; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + // wrap yuv420 buffer + size = avpicture_fill((AVPicture *)mp4v->encoder.picture, (uint8_t*)in_data, PIX_FMT_YUV420P, mp4v->encoder.context->width, mp4v->encoder.context->height); + if(size != in_size){ + /* guard */ + TSK_DEBUG_ERROR("Invalid size"); + return 0; + } + + if(mp4v->encoder.force_idr){ +#if LIBAVCODEC_VERSION_MAJOR <= 53 + mp4v->encoder.picture->pict_type = FF_I_TYPE; +#else + mp4v->encoder.picture->pict_type = AV_PICTURE_TYPE_I; +#endif + mp4v->encoder.force_idr = tsk_false; + } + else{ + mp4v->encoder.picture->pict_type = 0;// reset + } + mp4v->encoder.picture->pts = AV_NOPTS_VALUE; + mp4v->encoder.picture->quality = mp4v->encoder.context->global_quality; + ret = avcodec_encode_video(mp4v->encoder.context, mp4v->encoder.buffer, size, mp4v->encoder.picture); + if(ret > 0){ + tdav_codec_mp4ves_encap(mp4v, mp4v->encoder.buffer, (tsk_size_t)ret); + } + + return 0; +} + +tsk_size_t tdav_codec_mp4ves_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) +{ + tdav_codec_mp4ves_t* self = (tdav_codec_mp4ves_t*)_self; + const trtp_rtp_header_t* rtp_hdr = proto_hdr; + + tsk_size_t xsize, retsize = 0; + int got_picture_ptr; + int ret; + + if(!self || !in_data || !in_size || !out_data || !self->decoder.context){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + // get expected size + xsize = avpicture_get_size(self->decoder.context->pix_fmt, self->decoder.context->width, self->decoder.context->height); + + /* Packet lost? */ + if(self->decoder.last_seq != (rtp_hdr->seq_num - 1) && self->decoder.last_seq){ + if(self->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); + } + self->decoder.last_seq = rtp_hdr->seq_num; + + if((self->decoder.accumulator_pos + in_size) <= xsize){ + memcpy(&((uint8_t*)self->decoder.accumulator)[self->decoder.accumulator_pos], in_data, in_size); + self->decoder.accumulator_pos += in_size; + } + else{ + TSK_DEBUG_WARN("Buffer overflow"); + self->decoder.accumulator_pos = 0; + return 0; + } + + if(rtp_hdr->marker){ + AVPacket packet; + /* allocate destination buffer */ + if(*out_max_size <xsize){ + if(!(*out_data = tsk_realloc(*out_data, xsize))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + self->decoder.accumulator_pos = 0; + *out_max_size = 0; + return 0; + } + *out_max_size = xsize; + } + + av_init_packet(&packet); + packet.size = (int)self->decoder.accumulator_pos; + packet.data = self->decoder.accumulator; + ret = avcodec_decode_video2(self->decoder.context, self->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(self)->in.width = self->decoder.context->width; + TMEDIA_CODEC_VIDEO(self)->in.height = self->decoder.context->height; + + /* copy picture into a linear buffer */ + avpicture_layout((AVPicture *)self->decoder.picture, self->decoder.context->pix_fmt, (int)self->decoder.context->width, (int)self->decoder.context->height, + *out_data, (int)retsize); + } + /* in all cases: reset accumulator */ + self->decoder.accumulator_pos = 0; + } + + return retsize; +} + +tsk_bool_t tdav_codec_mp4ves_sdp_att_match(const tmedia_codec_t* _self, const char* att_name, const char* att_value) +{ + tdav_codec_mp4ves_t *self = (tdav_codec_mp4ves_t *)_self; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + if(tsk_striequals(att_name, "fmtp")){ + tsk_params_L_t* params ; + /* e.g. profile-level-id=1; xx=yy */ + if((params = tsk_params_fromstring(att_value, ";", tsk_true))){ + int val_int; + if((val_int = tsk_params_get_param_value_as_int(params, "profile-level-id")) != -1){ + TSK_DEBUG_INFO("Proposed profile-level-id=%d", val_int); + self->profile = val_int; // FIXME: Take the remote profile-level-id even if the bandwidth level doesn't match + } + TSK_OBJECT_SAFE_FREE(params); + } + + switch (self->profile ) { + case Simple_Profile_Level_1: + TMEDIA_CODEC_VIDEO(self)->out.width = TMEDIA_CODEC_VIDEO(self)->in.width = 176; TMEDIA_CODEC_VIDEO(self)->in.height = TMEDIA_CODEC_VIDEO(self)->out.height = 144; + break; + case Simple_Profile_Level_2: + case Simple_Profile_Level_3: + default: + TMEDIA_CODEC_VIDEO(self)->out.width = TMEDIA_CODEC_VIDEO(self)->in.width = 352; TMEDIA_CODEC_VIDEO(self)->in.height = TMEDIA_CODEC_VIDEO(self)->out.height = 288; + break; + } + } + 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(self)->pref_size, &in_width, &in_height, &out_width, &out_height) != 0){ + return tsk_false; + } + TMEDIA_CODEC_VIDEO(self)->in.width = in_width; + TMEDIA_CODEC_VIDEO(self)->in.height = in_height; + TMEDIA_CODEC_VIDEO(self)->out.width = out_width; + TMEDIA_CODEC_VIDEO(self)->out.height = out_height; + } + + return tsk_true; +} + +char* tdav_codec_mp4ves_sdp_att_get(const tmedia_codec_t* _self, const char* att_name) +{ + tdav_codec_mp4ves_t *self = (tdav_codec_mp4ves_t *)_self; + + if(tsk_striequals(att_name, "fmtp")){ + char* fmtp = tsk_null; + switch(_self->bl){//FIXME: deprecated + case tmedia_bl_low: + default: + self->profile = Simple_Profile_Level_1; + break; + case tmedia_bl_medium: + self->profile = Simple_Profile_Level_2; + break; + case tmedia_bl_hight: + case tmedia_bl_unrestricted: + self->profile = Simple_Profile_Level_3; + break; + } + tsk_sprintf(&fmtp, "profile-level-id=%d", self->profile); + return fmtp; + } + else if(tsk_striequals(att_name, "imageattr")){ + return tmedia_get_video_imageattr(TMEDIA_CODEC_VIDEO(self)->pref_size, + TMEDIA_CODEC_VIDEO(self)->in.width, TMEDIA_CODEC_VIDEO(self)->in.height, TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height); + } + return tsk_null; +} + + +/* ============ Internal functions ================= */ +int tdav_codec_mp4ves_open_encoder(tdav_codec_mp4ves_t* self) +{ + int ret, size; + int32_t max_bw_kpbs; + if(!self->encoder.codec && !(self->encoder.codec = avcodec_find_encoder(CODEC_ID_MPEG4))){ + TSK_DEBUG_ERROR("Failed to find mp4v encoder"); + return -1; + } + + if(self->encoder.context){ + TSK_DEBUG_ERROR("Encoder already opened"); + 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)->in.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; + self->encoder.context->noise_reduction = 250; + self->encoder.context->flags |= CODEC_FLAG_QSCALE; + self->encoder.context->global_quality = FF_QP2LAMBDA * self->encoder.quality; + + 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 + self->encoder.context->rtp_payload_size = MP4V_RTP_PAYLOAD_SIZE; + self->encoder.context->opaque = tsk_null; + self->encoder.context->profile = self->profile>>4; + self->encoder.context->level = self->profile & 0x0F; + self->encoder.context->gop_size = (TMEDIA_CODEC_VIDEO(self)->in.fps * MP4V_GOP_SIZE_IN_SECONDS); + self->encoder.context->max_b_frames = 0; + self->encoder.context->b_frame_strategy = 1; + self->encoder.context->flags |= CODEC_FLAG_AC_PRED; + + // Picture (YUV 420) + if(!(self->encoder.picture = avcodec_alloc_frame())){ + TSK_DEBUG_ERROR("Failed to create MP4V-ES 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 MP4V-ES encoder buffer"); + return -2; + } + + // Open encoder + if((ret = avcodec_open(self->encoder.context, self->encoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open MP4V-ES encoder"); + return ret; + } + + TSK_DEBUG_INFO("[MP4V-ES] bitrate=%d bps", self->encoder.context->bit_rate); + + return ret; +} + +int tdav_codec_mp4ves_open_decoder(tdav_codec_mp4ves_t* self) +{ + int ret, size; + + if(!self->decoder.codec && !(self->decoder.codec = avcodec_find_decoder(CODEC_ID_MPEG4))){ + TSK_DEBUG_ERROR("Failed to find MP4V-ES 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)->out.width; + self->decoder.context->height = TMEDIA_CODEC_VIDEO(self)->out.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 MP4V-ES decoder"); + return ret; + } + + self->decoder.last_seq = 0; + + return ret; +} + +int tdav_codec_mp4ves_close_encoder(tdav_codec_mp4ves_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); + } + if(self->encoder.buffer){ + TSK_FREE(self->encoder.buffer); + } + return 0; +} + +int tdav_codec_mp4ves_close_decoder(tdav_codec_mp4ves_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_mp4ves_encap(tdav_codec_mp4ves_t* mp4v, const uint8_t* pdata, tsk_size_t size) +{ + uint32_t scode; // start code + + if(size <= 4/*32bits: start code size*/){ + TSK_DEBUG_ERROR("Too short"); + return; + } + // first 32bits + scode = tnet_htonl_2(pdata); + +/* RFC 3016 - 3.3 Examples of packetized MPEG-4 Visual bitstream + + VS= Visual Object Sequence + VO= Visual Object + VOL= Visual Object Layer + VOP= Visual Object Plane + GOV= Group of Visual Object Plane + VP= Video Plane + + +------+------+------+------+ +(a) | RTP | VS | VO | VOL | + |header|header|header|header| + +------+------+------+------+ + + +------+------+------+------+------------+ +(b) | RTP | VS | VO | VOL |Video Packet| + |header|header|header|header| | + +------+------+------+------+------------+ + + +------+-----+------------------+ +(c) | RTP | GOV |Video Object Plane| + |header| | | + +------+-----+------------------+ + + +------+------+------------+ +------+------+------------+ +(d) | RTP | VOP |Video Packet| | RTP | VP |Video Packet| + |header|header| (1) | |header|header| (2) | + +------+------+------------+ +------+------+------------+ + + +------+------+------------+------+------------+------+------------+ +(e) | RTP | VP |Video Packet| VP |Video Packet| VP |Video Packet| + |header|header| (1) |header| (2) |header| (3) | + +------+------+------------+------+------------+------+------------+ + + +------+------+------------+ +------+------------+ +(f) | RTP | VOP |VOP fragment| | RTP |VOP fragment| + |header|header| (1) | |header| (2) | ___ + +------+------+------------+ +------+------------+ + + Figure 2 - Examples of RTP packetized MPEG-4 Visual bitstream +*/ + +/* RFC 3016 - 3.2 Fragmentation of MPEG-4 Visual bitstream + + A fragmented MPEG-4 Visual bitstream is mapped directly onto the RTP + payload without any addition of extra header fields or any removal of + Visual syntax elements. The Combined Configuration/Elementary + streams mode is used. + + In the following, header means one of the following: + + - Configuration information (Visual Object Sequence Header, Visual + Object Header and Video Object Layer Header) + - visual_object_sequence_end_code + - The header of the entry point function for an elementary stream + (Group_of_VideoObjectPlane() or the header of VideoObjectPlane(), + video_plane_with_short_header(), MeshObject() or FaceObject()) + - The video packet header (video_packet_header() excluding + next_resync_marker()) + - The header of gob_layer() + See 6.2.1 "Start codes" of ISO/IEC 14496-2 [2][9][4] for the + definition of the configuration information and the entry point + functions. +*/ + + switch(scode){ + case visual_object_sequence_start_code: + case visual_object_start_code: + case user_data_start_code: + case video_object_layer_start_code: + case group_of_vop_start_code: + case vop_start_code: + { + register uint32_t i, last_index = 0; + int startcode = 0xffffffff; + + if(scode == visual_object_sequence_start_code && size >=5){ + //uint8_t profile_and_level_indication = pdata[4]; /* IEC 14496-2: 6.3.2 Visual Object Sequence and Visual Object */ + // TSK_DEBUG_INFO("profile_and_level_indication=%d", profile_and_level_indication); + } + + if(size < MP4V_RTP_PAYLOAD_SIZE){ + goto last; + } + + for(i = 4; i<(size - 4); i++){ + startcode = (startcode <<8) | pdata[i]; + switch(startcode){ + case visual_object_sequence_start_code: + case group_of_vop_start_code: + case vop_start_code: + tdav_codec_mp4ves_rtp_callback(mp4v, pdata + last_index, (i - last_index), (last_index == size)); + last_index = i; + } + } +last: + if(last_index < size){ + tdav_codec_mp4ves_rtp_callback(mp4v, pdata + last_index, (size - last_index), tsk_true); + } + break; + } + default: + TSK_DEBUG_ERROR("%x is an invalide start code", scode); + break; + } +} + +static void tdav_codec_mp4ves_rtp_callback(tdav_codec_mp4ves_t *mp4v, const void *data, tsk_size_t size, tsk_bool_t marker) +{ + // Send data over the network + if(TMEDIA_CODEC_VIDEO(mp4v)->out.callback){ + TMEDIA_CODEC_VIDEO(mp4v)->out.result.buffer.ptr = data; + TMEDIA_CODEC_VIDEO(mp4v)->out.result.buffer.size = size; + TMEDIA_CODEC_VIDEO(mp4v)->out.result.duration = (uint32_t)((1./(double)TMEDIA_CODEC_VIDEO(mp4v)->out.fps) * TMEDIA_CODEC(mp4v)->plugin->rate); + TMEDIA_CODEC_VIDEO(mp4v)->out.result.last_chunck = marker; + TMEDIA_CODEC_VIDEO(mp4v)->out.callback(&TMEDIA_CODEC_VIDEO(mp4v)->out.result); + } +} + + +/* ============ MP4V-ES Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_mp4ves_ctor(tsk_object_t * _self, va_list * app) +{ + tdav_codec_mp4ves_t *self = _self; + if(self){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + self->profile = DEFAULT_PROFILE_LEVEL_ID; + self->encoder.quality = 1; + self->encoder.max_bw_kpbs = tmedia_defaults_get_bandwidth_video_upload_max(); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_mp4ves_dtor(tsk_object_t * _self) +{ + tdav_codec_mp4ves_t *self = _self; + if(self){ + /* deinit base */ + tmedia_codec_video_deinit(self); // will close the codec if opened + /* deinit self */ + TSK_FREE(self->rtp.ptr); + self->rtp.size = 0; + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_mp4ves_def_s = +{ + sizeof(tdav_codec_mp4ves_t), + tdav_codec_mp4ves_ctor, + tdav_codec_mp4ves_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_mp4ves_plugin_def_s = +{ + &tdav_codec_mp4ves_def_s, + + tmedia_video, + tmedia_codec_id_mp4ves_es, + "MP4V-ES", + "MP4V-ES Codec", + TMEDIA_CODEC_FORMAT_MP4V_ES, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (width, height, fps) */ + {176, 144, 0},// fps is @deprecated + + tdav_codec_mp4ves_set, + tdav_codec_mp4ves_open, + tdav_codec_mp4ves_close, + tdav_codec_mp4ves_encode, + tdav_codec_mp4ves_decode, + tdav_codec_mp4ves_sdp_att_match, + tdav_codec_mp4ves_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_mp4ves_plugin_def_t = &tdav_codec_mp4ves_plugin_def_s; + +tsk_bool_t tdav_codec_ffmpeg_mp4ves_is_supported() +{ + return (avcodec_find_encoder(CODEC_ID_MPEG4) && avcodec_find_decoder(CODEC_ID_MPEG4)); +} + +#endif /* HAVE_FFMPEG */ + diff --git a/tinyDAV/src/codecs/msrp/tdav_codec_msrp.c b/tinyDAV/src/codecs/msrp/tdav_codec_msrp.c new file mode 100644 index 0000000..5b72ded --- /dev/null +++ b/tinyDAV/src/codecs/msrp/tdav_codec_msrp.c @@ -0,0 +1,106 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* +* 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_msrp.c + * @brief The Message Session Relay Protocol (MSRP) fake codec. + * Used for both Message (RFC 4975) and file transfer (RFC 5547). + * + */ +#include "tinydav/codecs/msrp/tdav_codec_msrp.h" + +#include "tsk_memory.h" +#include "tsk_debug.h" + +/* ============ MSRP Plugin interface ================= */ +#define tdav_codec_msrp_open tsk_null +#define tdav_codec_msrp_close tsk_null +#define tdav_codec_msrp_sdp_att_get tsk_null +#define tdav_codec_msrp_sdp_att_get tsk_null +#define tdav_codec_msrp_encode tsk_null +#define tdav_codec_msrp_decode tsk_null + +static tsk_bool_t tdav_codec_msrp_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ /* always match */ + return tsk_true; +} + +// +// MSRP Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_msrp_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_msrp_t *msrp = self; + if(msrp){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_msrp_dtor(tsk_object_t * self) +{ + tdav_codec_msrp_t *msrp = self; + if(msrp){ + /* deinit base */ + tmedia_codec_msrp_deinit(msrp); + /* deinit self */ + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_msrp_def_s = +{ + sizeof(tdav_codec_msrp_t), + tdav_codec_msrp_ctor, + tdav_codec_msrp_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_msrp_plugin_def_s = +{ + &tdav_codec_msrp_def_s, + + tmedia_msrp, + tmedia_codec_id_none, // fake codec without real id + "message", + "MSRP fake codec", + TMEDIA_CODEC_FORMAT_MSRP, + tsk_false, + 0, // rate + + /* audio */ + {0}, + + /* video */ + {0}, + + tsk_null, // set() + tdav_codec_msrp_open, + tdav_codec_msrp_close, + tdav_codec_msrp_encode, + tdav_codec_msrp_decode, + tdav_codec_msrp_sdp_att_match, + tdav_codec_msrp_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_msrp_plugin_def_t = &tdav_codec_msrp_plugin_def_s; diff --git a/tinyDAV/src/codecs/opus/tdav_codec_opus.c b/tinyDAV/src/codecs/opus/tdav_codec_opus.c new file mode 100644 index 0000000..355fc73 --- /dev/null +++ b/tinyDAV/src/codecs/opus/tdav_codec_opus.c @@ -0,0 +1,363 @@ +/* +* Copyright (C) 2010-2013 Doubango Telecom <http://www.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_opus.c + * @brief OPUS audio codec. + * SDP: http://tools.ietf.org/html/draft-spittka-payload-rtp-opus-03 + */ +#include "tinydav/codecs/opus/tdav_codec_opus.h" + +#if HAVE_LIBOPUS + +#include <opus/opus.h> + +#include "tinymedia/tmedia_defaults.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tsk_params.h" +#include "tsk_memory.h" +#include "tsk_string.h" +#include "tsk_debug.h" + +#if !defined(TDAV_OPUS_MAX_FRAME_SIZE_IN_SAMPLES) +# define TDAV_OPUS_MAX_FRAME_SIZE_IN_SAMPLES (5760) /* 120ms@48kHz */ +#endif +#if !defined(TDAV_OPUS_MAX_FRAME_SIZE_IN_BYTES) +# define TDAV_OPUS_MAX_FRAME_SIZE_IN_BYTES (TDAV_OPUS_MAX_FRAME_SIZE_IN_SAMPLES << 1) /* 120ms@48kHz */ +#endif +#if !defined(TDAV_OPUS_FEC_ENABLED) +# define TDAV_OPUS_FEC_ENABLED 0 +#endif +#if !defined(TDAV_OPUS_DTX_ENABLED) +# define TDAV_OPUS_DTX_ENABLED 0 +#endif + +typedef struct tdav_codec_opus_s +{ + TMEDIA_DECLARE_CODEC_AUDIO; + + struct { + OpusEncoder *inst; + } encoder; + + struct { + OpusDecoder *inst; + opus_int16 buff[TDAV_OPUS_MAX_FRAME_SIZE_IN_SAMPLES]; + tsk_bool_t fec_enabled; + tsk_bool_t dtx_enabled; + uint16_t last_seq; + } decoder; +} +tdav_codec_opus_t; + + +static tsk_bool_t _tdav_codec_opus_rate_is_valid(const int32_t rate) +{ + switch(rate){ + case 8000: case 12000: case 16000: case 24000: case 48000: return tsk_true; + default: return tsk_false; + } +} + +static int tdav_codec_opus_open(tmedia_codec_t* self) +{ + tdav_codec_opus_t* opus = (tdav_codec_opus_t*)self; + int opus_err; + + if(!opus){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + // Initialize the decoder + if(!opus->decoder.inst){ + TSK_DEBUG_INFO("[OPUS] Open decoder: rate=%d, channels=%d", (int)self->in.rate, (int)TMEDIA_CODEC_AUDIO(self)->in.channels); + if(!(opus->decoder.inst = opus_decoder_create((opus_int32)self->in.rate, (int)TMEDIA_CODEC_AUDIO(self)->in.channels, &opus_err)) || opus_err != OPUS_OK){ + TSK_DEBUG_ERROR("Failed to create Opus decoder(rate=%d, channels=%d) instance with error code=%d.", (int)self->in.rate, (int)TMEDIA_CODEC_AUDIO(self)->in.channels, opus_err); + return -2; + } + } + opus->decoder.last_seq = 0; + + // Initialize the encoder + if(!opus->encoder.inst){ + TSK_DEBUG_INFO("[OPUS] Open encoder: rate=%d, channels=%d", (int)self->out.rate, (int)TMEDIA_CODEC_AUDIO(self)->out.channels); + if(!(opus->encoder.inst = opus_encoder_create((opus_int32)self->out.rate, (int)TMEDIA_CODEC_AUDIO(self)->out.channels, OPUS_APPLICATION_VOIP, &opus_err)) || opus_err != OPUS_OK){ + TSK_DEBUG_ERROR("Failed to create Opus decoder(rate=%d, channels=%d) instance with error code=%d.", (int)self->out.rate, (int)TMEDIA_CODEC_AUDIO(self)->out.channels, opus_err); + return -2; + } + } +#if TDAV_UNDER_MOBILE /* iOS, Android and WP8 */ + opus_encoder_ctl(opus->encoder.inst, OPUS_SET_COMPLEXITY(3)); +#endif + opus_encoder_ctl(opus->encoder.inst, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); + + return 0; +} + +static int tdav_codec_opus_close(tmedia_codec_t* self) +{ + tdav_codec_opus_t* opus = (tdav_codec_opus_t*)self; + + (void)(opus); + + /* resources will be freed by the dctor() */ + + return 0; +} + +static tsk_size_t tdav_codec_opus_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tdav_codec_opus_t* opus = (tdav_codec_opus_t*)self; + opus_int32 ret; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(!opus->encoder.inst){ + TSK_DEBUG_ERROR("Encoder not ready"); + return 0; + } + + // we're sure that the output (encoded) size cannot be higher than the input (raw) + if(*out_max_size < in_size){ + if(!(*out_data = tsk_realloc(*out_data, in_size))){ + TSK_DEBUG_ERROR("Failed to allocate buffer with size = %u", in_size); + *out_max_size = 0; + return 0; + } + *out_max_size = in_size; + } + + ret = opus_encode(opus->encoder.inst, + (const opus_int16 *)in_data, (int)(in_size >> 1), + (unsigned char *)*out_data, (opus_int32)*out_max_size); + + if(ret < 0){ + TSK_DEBUG_ERROR("opus_encode() failed with error code = %d", ret); + return 0; + } + + return (tsk_size_t)ret; +} + +static tsk_size_t tdav_codec_opus_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) +{ + tdav_codec_opus_t* opus = (tdav_codec_opus_t*)self; + int frame_size; + const trtp_rtp_header_t* rtp_hdr = proto_hdr; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(!opus->decoder.inst){ + TSK_DEBUG_ERROR("Decoder not ready"); + return 0; + } + + /* Packet loss? */ + if(opus->decoder.last_seq != (rtp_hdr->seq_num - 1) && opus->decoder.last_seq){ + if(opus->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("[Opus] Packet loss, seq_num=%d", rtp_hdr->seq_num); + opus_decode(opus->decoder.inst, tsk_null/*packet loss*/, (opus_int32)0, opus->decoder.buff, TDAV_OPUS_MAX_FRAME_SIZE_IN_SAMPLES, opus->decoder.fec_enabled); + } + opus->decoder.last_seq = rtp_hdr->seq_num; + + frame_size = opus_decode(opus->decoder.inst, (const unsigned char *)in_data, (opus_int32)in_size, opus->decoder.buff, TDAV_OPUS_MAX_FRAME_SIZE_IN_SAMPLES, opus->decoder.fec_enabled ? 1 : 0); + if(frame_size > 0){ + tsk_size_t frame_size_inbytes = (frame_size << 1); + if(*out_max_size < frame_size_inbytes){ + if(!(*out_data = tsk_realloc(*out_data, frame_size_inbytes))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = frame_size_inbytes; + } + memcpy(*out_data, opus->decoder.buff, frame_size_inbytes); + return frame_size_inbytes; + } + else{ + return 0; + } +} + +static tsk_bool_t tdav_codec_opus_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + tdav_codec_opus_t* opus = (tdav_codec_opus_t*)codec; + + if(!opus){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + TSK_DEBUG_INFO("[OPUS] Trying to match [%s:%s]", att_name, att_value); + + if(tsk_striequals(att_name, "fmtp")){ + int val_int; + tsk_params_L_t* params; + /* e.g. FIXME */ + if((params = tsk_params_fromstring(att_value, ";", tsk_true))){ + tsk_bool_t ret = tsk_false; + /* === maxplaybackrate ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "maxplaybackrate")) != -1){ + if(!_tdav_codec_opus_rate_is_valid(val_int)){ + TSK_DEBUG_ERROR("[OPUS] %d not valid as maxplaybackrate value", val_int); + goto done; + } + TMEDIA_CODEC(opus)->out.rate = TSK_MIN((int32_t)TMEDIA_CODEC(opus)->out.rate, val_int); + TMEDIA_CODEC_AUDIO(opus)->out.timestamp_multiplier = tmedia_codec_audio_get_timestamp_multiplier(codec->id, codec->out.rate); + } + /* === sprop-maxcapturerate ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "sprop-maxcapturerate")) != -1){ + if(!_tdav_codec_opus_rate_is_valid(val_int)){ + TSK_DEBUG_ERROR("[OPUS] %d not valid as sprop-maxcapturerate value", val_int); + goto done; + } + TMEDIA_CODEC(opus)->in.rate = TSK_MIN((int32_t)TMEDIA_CODEC(opus)->in.rate, val_int); + TMEDIA_CODEC_AUDIO(opus)->in.timestamp_multiplier = tmedia_codec_audio_get_timestamp_multiplier(codec->id, codec->in.rate); + } + ret = tsk_true; +done: + TSK_OBJECT_SAFE_FREE(params); + return ret; + } + } + + return tsk_true; +} + +static char* tdav_codec_opus_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) +{ + tdav_codec_opus_t* opus = (tdav_codec_opus_t*)codec; + + if(!opus){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + if(tsk_striequals(att_name, "fmtp")){ + char* fmtp = tsk_null; + tsk_sprintf(&fmtp, "maxplaybackrate=%d; sprop-maxcapturerate=%d; stereo=%d; sprop-stereo=%d; useinbandfec=%d; usedtx=%d", + TMEDIA_CODEC(opus)->in.rate, + TMEDIA_CODEC(opus)->out.rate, + (TMEDIA_CODEC_AUDIO(opus)->in.channels == 2) ? 1 : 0, + (TMEDIA_CODEC_AUDIO(opus)->out.channels == 2) ? 1 : 0, + opus->decoder.fec_enabled ? 1 : 0, + opus->decoder.dtx_enabled ? 1 : 0 + ); + return fmtp; + } + + return tsk_null; +} + +// +// OPUS Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_opus_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_opus_t *opus = self; + if(opus){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + TMEDIA_CODEC(opus)->in.rate = tmedia_defaults_get_opus_maxplaybackrate(); + TMEDIA_CODEC(opus)->out.rate = tmedia_defaults_get_opus_maxcapturerate(); + TMEDIA_CODEC_AUDIO(opus)->in.channels = 1; + TMEDIA_CODEC_AUDIO(opus)->out.channels = 1; +#if TDAV_OPUS_FEC_ENABLED + opus->decoder.fec_enabled = tsk_true; +#endif +#if TDAV_OPUS_DTX_ENABLED + opus->decoder.dtx_enabled = tsk_true; +#endif + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_opus_dtor(tsk_object_t * self) +{ + tdav_codec_opus_t *opus = self; + if(opus){ + /* deinit base */ + tmedia_codec_audio_deinit(opus); + /* deinit self */ + if(opus->decoder.inst){ + opus_decoder_destroy(opus->decoder.inst), opus->decoder.inst = tsk_null; + } + if(opus->encoder.inst){ + opus_encoder_destroy(opus->encoder.inst), opus->encoder.inst = tsk_null; + } + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_opus_def_s = +{ + sizeof(tdav_codec_opus_t), + tdav_codec_opus_ctor, + tdav_codec_opus_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_opus_plugin_def_s = +{ + &tdav_codec_opus_def_s, + + tmedia_audio, + tmedia_codec_id_opus, + "opus", + "opus Codec", + TMEDIA_CODEC_FORMAT_OPUS, + tsk_true, + 48000, // this is the default sample rate + + { /* audio */ + 2, // channels + 0 // ptime @deprecated + }, + + /* video */ + {0}, + + tsk_null, // set() + tdav_codec_opus_open, + tdav_codec_opus_close, + tdav_codec_opus_encode, + tdav_codec_opus_decode, + tdav_codec_opus_sdp_att_match, + tdav_codec_opus_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_opus_plugin_def_t = &tdav_codec_opus_plugin_def_s; + + +#endif /* HAVE_LIBOPUS */ diff --git a/tinyDAV/src/codecs/speex/tdav_codec_speex.c b/tinyDAV/src/codecs/speex/tdav_codec_speex.c new file mode 100644 index 0000000..18c4440 --- /dev/null +++ b/tinyDAV/src/codecs/speex/tdav_codec_speex.c @@ -0,0 +1,286 @@ +/* +* 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_speex.c + * @brief Speex codecs + * + * @author Mamadou Diop <diopmamadou(at)doubango.org> + * + + */ +#include "tinydav/codecs/speex/tdav_codec_speex.h" + +#if HAVE_LIB_SPEEX + +#include "tsk_memory.h" +#include "tsk_debug.h" + +#define SPEEX_BUFFER_MAX_SIZE 1024 +#define SPEEX_DEFAULT_QUALITY 6 + +/* ============ Common ================= */ +int tdav_codec_speex_init(tdav_codec_speex_t* self, tdav_codec_speex_type_t type); +int tdav_codec_speex_deinit(tdav_codec_speex_t* self); + +/* ============ Speex Plugin interface ================= */ + +int tdav_codec_speex_open(tmedia_codec_t* self) +{ + static int quality = SPEEX_DEFAULT_QUALITY; + tdav_codec_speex_t* speex = (tdav_codec_speex_t*)self; + + switch(speex->type){ + case tdav_codec_speex_type_nb: + speex->encoder.state = speex_encoder_init(&speex_nb_mode); + speex->decoder.state = speex_decoder_init(&speex_nb_mode); + break; + case tdav_codec_speex_type_wb: + speex->encoder.state = speex_encoder_init(&speex_wb_mode); + speex->decoder.state = speex_decoder_init(&speex_wb_mode); + break; + case tdav_codec_speex_type_uwb: + speex->encoder.state = speex_encoder_init(&speex_uwb_mode); + speex->decoder.state = speex_decoder_init(&speex_uwb_mode); + break; + default: + TSK_DEBUG_ERROR("Not implemented"); + return -2; + } + + speex_decoder_ctl(speex->decoder.state, SPEEX_GET_FRAME_SIZE, &speex->decoder.size); + speex->decoder.size *= sizeof(spx_int16_t); + if(!(speex->decoder.buffer = tsk_calloc(speex->decoder.size, 1))){ + speex->decoder.size = speex->decoder.size = 0; + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + return -3; + } + + speex_encoder_ctl(speex->encoder.state, SPEEX_SET_QUALITY, &quality); + speex_encoder_ctl(speex->encoder.state, SPEEX_GET_FRAME_SIZE, &speex->encoder.size); + + speex_bits_init(&speex->encoder.bits); + speex_bits_init(&speex->decoder.bits); + speex_bits_reset(&speex->encoder.bits); + speex_bits_reset(&speex->decoder.bits); + + return 0; +} + +int tdav_codec_speex_close(tmedia_codec_t* self) +{ + tdav_codec_speex_t* speex = (tdav_codec_speex_t*)self; + + (void)(speex); + + return 0; +} + +tsk_size_t tdav_codec_speex_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tdav_codec_speex_t* speex = (tdav_codec_speex_t*)self; + tsk_size_t outsize = 0; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + speex_bits_reset(&speex->encoder.bits); + speex_encode_int(speex->encoder.state, (spx_int16_t*)in_data, &speex->encoder.bits); + + if(*out_max_size <speex->encoder.size){ + if((*out_data = tsk_realloc(*out_data, speex->encoder.size))){ + *out_max_size = speex->encoder.size; + } + else{ + *out_max_size = 0; + return 0; + } + } + + outsize = speex_bits_write(&speex->encoder.bits, *out_data, (speex->encoder.size >> 1)); + + return outsize; +} + +tsk_size_t tdav_codec_speex_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) +{ + int ret; + tsk_size_t out_size = 0; + tdav_codec_speex_t* speex = (tdav_codec_speex_t*)self; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + // initializes the bit-stream + speex_bits_read_from(&speex->decoder.bits, (char*)in_data, in_size); + + do{ + // performs decode() + if((ret = speex_decode_int(speex->decoder.state, &speex->decoder.bits, speex->decoder.buffer))){ + TSK_DEBUG_ERROR("Failed to decode the buffer. retcode=%d", ret); + break; + } + + if(*out_max_size <(out_size + speex->decoder.size)){ + if((*out_data = tsk_realloc(*out_data, (out_size + speex->decoder.size)))){ + *out_max_size = (out_size + speex->decoder.size); + } + else{ + *out_max_size = 0; + return 0; + } + } + + // copy output buffer + memcpy(&((uint8_t*)*out_data)[out_size], speex->decoder.buffer, speex->decoder.size); + out_size += speex->decoder.size; + } + while(speex_bits_remaining(&speex->decoder.bits) >= 5); + + + return out_size; +} + +char* tdav_codec_speex_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) +{ + return tsk_null; +} + +tsk_bool_t tdav_codec_speex_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + return tsk_true; +} + + +// +// Speex Codec Object definition +// +#define SPEEX_OBJECT_DEFINITION(mode,name,description,format,rate) \ + static tsk_object_t* tdav_codec_speex_##mode##_ctor(tsk_object_t * self, va_list * app) \ + { \ + tdav_codec_speex_t *speex = self; \ + if(speex){ \ + tdav_codec_speex_init(speex, tdav_codec_speex_type_##mode); \ + } \ + return self; \ + } \ + static tsk_object_t* tdav_codec_speex_##mode##_dtor(tsk_object_t * self) \ + { \ + tdav_codec_speex_t *speex = self; \ + if(speex){ \ + /* deinit base */ \ + tmedia_codec_audio_deinit(speex); \ + /* deinit self */ \ + tdav_codec_speex_deinit(speex); \ + } \ + \ + return self; \ + } \ + static const tsk_object_def_t tdav_codec_speex_##mode##_def_s = \ + { \ + sizeof(tdav_codec_speex_t), \ + tdav_codec_speex_##mode##_ctor, \ + tdav_codec_speex_##mode##_dtor, \ + tmedia_codec_cmp, \ + }; \ + static const tmedia_codec_plugin_def_t tdav_codec_speex_##mode##_plugin_def_s = \ + { \ + &tdav_codec_speex_##mode##_def_s, \ + \ + tmedia_audio, \ + tmedia_codec_id_speex_##mode, \ + name, \ + description, \ + format, \ + tsk_true, \ + rate, /* rate*/ \ + \ + { /* audio */ \ + 1, /* channels*/ \ + 0 /* ptime @deprecated*/ \ + }, \ + \ + /* video */ \ + {0}, \ + \ + tsk_null, /* set()*/ \ + tdav_codec_speex_open, \ + tdav_codec_speex_close, \ + tdav_codec_speex_encode, \ + tdav_codec_speex_decode, \ + tdav_codec_speex_sdp_att_match, \ + tdav_codec_speex_sdp_att_get \ + }; \ + const tmedia_codec_plugin_def_t *tdav_codec_speex_##mode##_plugin_def_t = &tdav_codec_speex_##mode##_plugin_def_s; + + +SPEEX_OBJECT_DEFINITION(nb,"SPEEX","Speex-NB Codec",TMEDIA_CODEC_FORMAT_SPEEX_NB,8000); +SPEEX_OBJECT_DEFINITION(wb,"SPEEX","Speex-WB Codec",TMEDIA_CODEC_FORMAT_SPEEX_WB,16000); +SPEEX_OBJECT_DEFINITION(uwb,"SPEEX","Speex-UWB Codec",TMEDIA_CODEC_FORMAT_SPEEX_UWB,32000); + +// +// Common functions +// +int tdav_codec_speex_init(tdav_codec_speex_t* self, tdav_codec_speex_type_t type) +{ + if(self){ + self->type = type; + return 0; + } + else{ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } +} + +int tdav_codec_speex_deinit(tdav_codec_speex_t* self) +{ + if(self){ + if(self->decoder.state){ + speex_decoder_destroy(self->decoder.state); + self->decoder.state = tsk_null; + } + speex_bits_destroy(&self->decoder.bits); + if(self->decoder.buffer){ + TSK_FREE(self->decoder.buffer); + self->decoder.size = 0; + } + + if(self->encoder.state){ + speex_encoder_destroy(self->encoder.state); + self->encoder.state = tsk_null; + } + speex_bits_destroy(&self->encoder.bits); + self->encoder.size = 0; + + return 0; + } + else{ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } +} + +#endif /* HAVE_LIB_SPEEX */ diff --git a/tinyDAV/src/codecs/t140/tdav_codec_t140.c b/tinyDAV/src/codecs/t140/tdav_codec_t140.c new file mode 100644 index 0000000..b401321 --- /dev/null +++ b/tinyDAV/src/codecs/t140/tdav_codec_t140.c @@ -0,0 +1,175 @@ +/* +* Copyright (C) 2012 Mamadou Diop. +* +* 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_t140.c + * @brief T140 codec implementation (RFC 4103) + */ +#include "tinydav/codecs/t140/tdav_codec_t140.h" + +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +// RFC 4103 - 6. Parameter for Character Transmission Rate +#define TDAV_CODEC_T140_CPS 30 + +static int tdav_codec_t140_set(tmedia_codec_t* self, const struct tmedia_param_s* param) +{ + return 0; +} + +static int tdav_codec_t140_open(tmedia_codec_t* self) +{ + return 0; +} + +static int tdav_codec_t140_close(tmedia_codec_t* self) +{ + return 0; +} + + +static tsk_size_t tdav_codec_t140_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tsk_size_t out_size = in_size; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(*out_max_size <out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + memcpy(*out_data, in_data, out_size); + + return out_size; +} + +static tsk_size_t tdav_codec_t140_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) +{ + tsk_size_t out_size = in_size; + + if(!self || !in_data || !in_size || !out_data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + /* allocate new buffer */ + if(*out_max_size < out_size){ + if(!(*out_data = tsk_realloc(*out_data, out_size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + return 0; + } + *out_max_size = out_size; + } + memcpy(*out_data, in_data, out_size); + + return out_size; +} + +static tsk_bool_t tdav_codec_t140_sdp_att_match(const tmedia_codec_t* self, const char* att_name, const char* att_value) +{ + return tsk_true; +} + +static char* tdav_codec_t140_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + if(!self || !att_name){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + if(tsk_striequals("fmtp", att_name)){ + char* fmtp = tsk_null; + tsk_sprintf(&fmtp, "cps=%d", TDAV_CODEC_T140_CPS); + return fmtp; + } + return tsk_null; +} + + +// +// G.711u Plugin definition +// + +/* constructor */ +static tsk_object_t* tdav_codec_t140_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_t140_t *t140 = self; + if(t140){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_t140_dtor(tsk_object_t * self) +{ + tdav_codec_t140_t *t140 = self; + if(t140){ + /* deinit base */ + /* deinit self */ + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_t140_def_s = +{ + sizeof(tdav_codec_t140_t), + tdav_codec_t140_ctor, + tdav_codec_t140_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_t140_plugin_def_s = +{ + &tdav_codec_t140_def_s, + + tmedia_t140, + tmedia_codec_id_t140, + "t140", + "T140 codec (From tinyDAV)", + TMEDIA_CODEC_FORMAT_T140, + tsk_true, + 1000, // rate + + /* audio */ + {0}, + + /* video */ + {0}, + + tdav_codec_t140_set, + tdav_codec_t140_open, + tdav_codec_t140_close, + tdav_codec_t140_encode, + tdav_codec_t140_decode, + tdav_codec_t140_sdp_att_match, + tdav_codec_t140_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_t140_plugin_def_t = &tdav_codec_t140_plugin_def_s; 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 diff --git a/tinyDAV/src/codecs/vpx/tdav_codec_vp8.c b/tinyDAV/src/codecs/vpx/tdav_codec_vp8.c new file mode 100644 index 0000000..2c097e4 --- /dev/null +++ b/tinyDAV/src/codecs/vpx/tdav_codec_vp8.c @@ -0,0 +1,1059 @@ +/* +* Copyright (C) 2011-2015 Doubango Telecom <http://www.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_vp8.c +* @brief VP8 codec +* The RTP packetizer/depacketizer follows draft-ietf-payload-vp8 and draft-bankoski-vp8-bitstream-05 +* Google's VP8 (http://www.webmproject.org/) encoder/decoder +* +* We require v1.3.0 (2013-12-02 10:37:51) or later. For iOS, because of issue 423 (https://code.google.com/p/doubango/issues/detail?id=423) we require a version after "Mon, 28 Apr 2014 22:42:23 +0100 (14:42 -0700)" integrating fix in http://git.chromium.org/gitweb/?p=webm/libvpx.git;a=commit;h=33df6d1fc1d268b4901b74b4141f83594266f041 +* +*/ +#include "tinydav/codecs/vpx/tdav_codec_vp8.h" + +#if HAVE_LIBVPX + +#if TDAV_UNDER_WINDOWS +# include <windows.h> +#endif + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tinymedia/tmedia_params.h" +#include "tinymedia/tmedia_defaults.h" + +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_time.h" +#include "tsk_debug.h" + +#define VPX_CODEC_DISABLE_COMPAT 1 /* strict compliance with the latest SDK by disabling some backwards compatibility */ +#include <vpx/vpx_encoder.h> +#include <vpx/vpx_decoder.h> +#include <vpx/vp8cx.h> +#include <vpx/vp8dx.h> + +#if !defined(TDAV_VP8_DISABLE_EXTENSION) +# define TDAV_VP8_DISABLE_EXTENSION 0 /* Set X fied value to zero */ +#endif + +#if TDAV_VP8_DISABLE_EXTENSION +# define TDAV_VP8_PAY_DESC_SIZE 1 +#else +# define TDAV_VP8_PAY_DESC_SIZE 4 +#endif +#define TDAV_SYSTEM_CORES_COUNT 0 +#define TDAV_VP8_GOP_SIZE_IN_SECONDS 60 +#define TDAV_VP8_RTP_PAYLOAD_MAX_SIZE 1050 +#if !defined(TDAV_VP8_MAX_BANDWIDTH_KB) +# define TDAV_VP8_MAX_BANDWIDTH_KB 6000 +#endif +#if !defined(TDAV_VP8_MIN_BANDWIDTH_KB) +# define TDAV_VP8_MIN_BANDWIDTH_KB 100 +#endif + +/* VP8 codec */ +typedef struct tdav_codec_vp8_s +{ + TMEDIA_DECLARE_CODEC_VIDEO; + + // Encoder + struct { + vpx_codec_enc_cfg_t cfg; + tsk_bool_t initialized; + vpx_codec_pts_t pts; + vpx_codec_ctx_t context; + unsigned pic_id : 15; + uint64_t frame_count; + tsk_bool_t force_idr; + int rotation; + + struct { + uint8_t* ptr; + tsk_size_t size; + } rtp; + + tsk_mutex_handle_t* mutex; + } encoder; + + // decoder + struct { + vpx_codec_dec_cfg_t cfg; + unsigned initialized : 1; + vpx_codec_ctx_t context; + void* accumulator; + tsk_size_t accumulator_pos; + tsk_size_t accumulator_size; + tsk_size_t first_part_size; + uint16_t last_seq; + uint32_t last_timestamp; + tsk_bool_t idr; + tsk_bool_t corrupted; + } decoder; +} +tdav_codec_vp8_t; + +#define vp8_interface_enc (vpx_codec_vp8_cx()) +#define vp8_interface_dec (vpx_codec_vp8_dx()) + +static int tdav_codec_vp8_open_encoder(tdav_codec_vp8_t* self); +static int tdav_codec_vp8_open_decoder(tdav_codec_vp8_t* self); +static int tdav_codec_vp8_close_encoder(tdav_codec_vp8_t* self); +static int tdav_codec_vp8_close_decoder(tdav_codec_vp8_t* self); + +static void tdav_codec_vp8_encap(tdav_codec_vp8_t* self, const vpx_codec_cx_pkt_t *pkt); +static void tdav_codec_vp8_rtp_callback(tdav_codec_vp8_t *self, const void *data, tsk_size_t size, uint32_t partID, tsk_bool_t part_start, tsk_bool_t non_ref, tsk_bool_t last); + +/* ============ VP8 Plugin interface ================= */ + +static int tdav_codec_vp8_set(tmedia_codec_t* self, const tmedia_param_t* param) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + vpx_codec_err_t vpx_ret = VPX_CODEC_OK; + tsk_bool_t reconf = tsk_false; + + 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: + { + vp8->encoder.force_idr = tsk_true; + return 0; + } + case tmedia_codec_action_bw_down: + { + vp8->encoder.cfg.rc_target_bitrate = TSK_CLAMP(0, (int32_t)((vp8->encoder.cfg.rc_target_bitrate << 1) / 3), TMEDIA_CODEC(vp8)->bandwidth_max_upload); + TSK_DEBUG_INFO("New target bitrate = %d kbps", vp8->encoder.cfg.rc_target_bitrate); + reconf = tsk_true; + break; + } + case tmedia_codec_action_bw_up: + { + vp8->encoder.cfg.rc_target_bitrate = TSK_CLAMP(0, (int32_t)((vp8->encoder.cfg.rc_target_bitrate * 3) >> 1), TMEDIA_CODEC(vp8)->bandwidth_max_upload); + TSK_DEBUG_INFO("New target bitrate = %d kbps", vp8->encoder.cfg.rc_target_bitrate); + reconf = tsk_true; + break; + } + } + } + else if (tsk_striequals(param->key, "bw_kbps")) { // both up and down (from the SDP) + int32_t max_bw_userdefine = tmedia_defaults_get_bandwidth_video_upload_max(); + int32_t max_bw_new = *((int32_t*)param->value); + if (max_bw_userdefine > 0) { + // do not use more than what the user defined in it's configuration + TMEDIA_CODEC(vp8)->bandwidth_max_upload = TSK_MIN(max_bw_new, max_bw_userdefine); + } + else { + TMEDIA_CODEC(vp8)->bandwidth_max_upload = max_bw_new; + } + vp8->encoder.cfg.rc_target_bitrate = TSK_CLAMP(0, vp8->encoder.cfg.rc_target_bitrate, TMEDIA_CODEC(vp8)->bandwidth_max_upload); + TSK_DEBUG_INFO("New target bitrate = %d kbps", vp8->encoder.cfg.rc_target_bitrate); + reconf = tsk_true; + } + else if (tsk_striequals(param->key, "bandwidth-max-upload")) { + int32_t bw_max_upload = *((int32_t*)param->value); + TSK_DEBUG_INFO("VP8 codec: bandwidth-max-upload=%d", bw_max_upload); + TMEDIA_CODEC(vp8)->bandwidth_max_upload = bw_max_upload; + reconf = tsk_true; + } + else if (tsk_striequals(param->key, "rotation")) { + // IMPORTANT: changing resolution requires at least libvpx v1.1.0 "Eider" + int32_t rotation = *((int32_t*)param->value); + if (vp8->encoder.rotation != rotation) { + vp8->encoder.rotation = rotation; + vp8->encoder.cfg.g_w = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(vp8)->out.height : TMEDIA_CODEC_VIDEO(vp8)->out.width; + vp8->encoder.cfg.g_h = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(vp8)->out.width : TMEDIA_CODEC_VIDEO(vp8)->out.height; + reconf = tsk_true; + } + } + } + + if (reconf) { + if (vp8->encoder.initialized) { + // The encoder isn't thread safe. Without this lock (and the one in the encode() function) we may have corruptions in the video (issue report from GE). + // Google says the encoder is thread-safe but this is not the case. But it is *multi-instance* thread-safe. + tsk_mutex_lock(vp8->encoder.mutex); + if ((vpx_ret = vpx_codec_enc_config_set(&vp8->encoder.context, &vp8->encoder.cfg)) != VPX_CODEC_OK) { + TSK_DEBUG_ERROR("vpx_codec_enc_config_set failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + } + tsk_mutex_unlock(vp8->encoder.mutex); + } + return (vpx_ret == VPX_CODEC_OK) ? 0 : -2; + } + + return -1; +} + +static int tdav_codec_vp8_open(tmedia_codec_t* self) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + int ret; + + if (!vp8) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + + // Encoder + if ((ret = tdav_codec_vp8_open_encoder(vp8))) { + return ret; + } + + // Decoder + if ((ret = tdav_codec_vp8_open_decoder(vp8))) { + return ret; + } + + return ret; +} + +static int tdav_codec_vp8_close(tmedia_codec_t* self) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + + if (!vp8) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tdav_codec_vp8_close_encoder(vp8); + tdav_codec_vp8_close_decoder(vp8); + + return 0; +} + +static tsk_size_t tdav_codec_vp8_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + vpx_enc_frame_flags_t flags = 0; + vpx_codec_err_t vpx_ret = VPX_CODEC_OK; + const vpx_codec_cx_pkt_t *pkt; + vpx_codec_iter_t iter = tsk_null; + vpx_image_t image = {0}; + + if (!vp8 || !in_data || !in_size) { + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if (in_size != (vp8->encoder.context.config.enc->g_w * vp8->encoder.context.config.enc->g_h * 3) >> 1) { + TSK_DEBUG_ERROR("Invalid size"); + return 0; + } + + // wrap yuv420 buffer + if (!vpx_img_wrap(&image, VPX_IMG_FMT_I420, vp8->encoder.context.config.enc->g_w, vp8->encoder.context.config.enc->g_h, 1, (unsigned char*)in_data)) { + TSK_DEBUG_ERROR("vpx_img_wrap failed"); + return 0; + } + + // encode data + ++vp8->encoder.pts; + if (vp8->encoder.force_idr) { + flags |= VPX_EFLAG_FORCE_KF; + vp8->encoder.force_idr = tsk_false; + } + tsk_mutex_lock(vp8->encoder.mutex); // must + vpx_ret = vpx_codec_encode(&vp8->encoder.context, &image, vp8->encoder.pts, 1, flags, VPX_DL_REALTIME); + tsk_mutex_unlock(vp8->encoder.mutex); + + if (vpx_ret != VPX_CODEC_OK) { + TSK_DEBUG_ERROR("vpx_codec_encode failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + goto bail; + } + + ++vp8->encoder.frame_count; + ++vp8->encoder.pic_id; + + while ((pkt = vpx_codec_get_cx_data(&vp8->encoder.context, &iter))) { + switch (pkt->kind) { + case VPX_CODEC_CX_FRAME_PKT: + { + tdav_codec_vp8_encap(vp8, pkt); + break; + } + default: + case VPX_CODEC_STATS_PKT: /**< Two-pass statistics for this frame */ + case VPX_CODEC_PSNR_PKT: /**< PSNR statistics for this frame */ + case VPX_CODEC_CUSTOM_PKT: /**< Algorithm extensions */ + { + TSK_DEBUG_INFO("pkt->kind=%d not supported", (int)pkt->kind); + break; + } + } + } + +bail: + vpx_img_free(&image); + return 0; +} + +static tsk_size_t tdav_codec_vp8_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) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + const trtp_rtp_header_t* rtp_hdr = proto_hdr; + const uint8_t* pdata = in_data; + const uint8_t* pdata_end = (pdata + in_size); + tsk_size_t ret = 0; + tsk_bool_t fatal_error = tsk_false; + static const tsk_size_t xmax_size = (3840 * 2160 * 3) >> 3; // >>3 instead of >>1 (not an error) + uint8_t S, PartID; + + if (!self || !in_data || in_size < 1 || !out_data || !vp8->decoder.initialized) { + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + { /* 4.2. VP8 Payload Descriptor */ + uint8_t X, R, N, I, L, T, K;//TODO: store + + X = ((*pdata & 0x80) >> 7); + R = ((*pdata & 0x40) >> 6); + if (R) { + TSK_DEBUG_ERROR("R<>0"); + fatal_error = tsk_true; + goto bail; + } + N = ((*pdata & 0x20) >> 5); + S = ((*pdata & 0x10) >> 4); + PartID = (*pdata & 0x0F); + // skip "REQUIRED" header + if (++pdata >= pdata_end) { + TSK_DEBUG_ERROR("Too short"); goto bail; + } + // check "OPTIONAL" headers + if (X) { + I = (*pdata & 0x80); + L = (*pdata & 0x40); + T = (*pdata & 0x20); + K = (*pdata & 0x10); + if (++pdata >= pdata_end) { + TSK_DEBUG_ERROR("Too short"); goto bail; + } + + if (I) { + if (*pdata & 0x80) { // M + // PictureID on 16bits + if ((pdata += 2) >= pdata_end) { + TSK_DEBUG_ERROR("Too short"); goto bail; + } + } + else { + // PictureID on 8bits + if (++pdata >= pdata_end) { + TSK_DEBUG_ERROR("Too short"); goto bail; + } + } + } + if (L) { + if (++pdata >= pdata_end) { + TSK_DEBUG_ERROR("Too short"); goto bail; + } + } + if (T || K) { + if (++pdata >= pdata_end) { + TSK_DEBUG_ERROR("Too short"); goto bail; + } + } + } + } + + in_size = (pdata_end - pdata); + + // Packet lost? + if (vp8->decoder.last_seq && (vp8->decoder.last_seq + 1) != rtp_hdr->seq_num) { + TSK_DEBUG_INFO("[VP8] Packet loss, seq_num=%d", (vp8->decoder.last_seq + 1)); + vp8->decoder.corrupted = tsk_true; + } + vp8->decoder.last_seq = rtp_hdr->seq_num; + + // New frame ? + if (vp8->decoder.last_timestamp != rtp_hdr->timestamp) { + /* 4.3. VP8 Payload Header + Note that the header is present only in packets + which have the S bit equal to one and the PartID equal to zero in the + payload descriptor. Subsequent packets for the same frame do not + carry the payload header. + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |Size0|H| VER |P| + +-+-+-+-+-+-+-+-+ + | Size1 | + +-+-+-+-+-+-+-+-+ + | Size2 | + +-+-+-+-+-+-+-+-+ + | Bytes 4..N of | + | VP8 payload | + : : + +-+-+-+-+-+-+-+-+ + | OPTIONAL RTP | + | padding | + : : + +-+-+-+-+-+-+-+-+ + P: Inverse key frame flag. When set to 0 the current frame is a key + frame. When set to 1 the current frame is an interframe. Defined + in [RFC6386] + */ + + // Reset accumulator position + vp8->decoder.accumulator_pos = 0; + + // Make sure the header is present + if (S != 1 || PartID != 0 || in_size < 3) { + TSK_DEBUG_WARN("VP8 payload header is missing"); +#if 0 + if (in_size < 3) +#endif + { + fatal_error = tsk_true; + goto bail; + } + } + { + /* SizeN: The size of the first partition in bytes is calculated from + the 19 bits in Size0, Size1, and Size2 as 1stPartitionSize = Size0 + + 8 * Size1 + 2048 * Size2. [RFC6386]. */ + vp8->decoder.first_part_size = ((pdata[0] >> 5) & 0xFF) + 8 * pdata[1] + 2048 * pdata[2]; + } + + // Starting new frame...reset "corrupted" value + vp8->decoder.corrupted = tsk_false; + + // Key frame? + vp8->decoder.idr = !(pdata[0] & 0x01); + + // Update timestamp + vp8->decoder.last_timestamp = rtp_hdr->timestamp; + } + + if (in_size > xmax_size) { + vp8->decoder.accumulator_pos = 0; + TSK_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", (unsigned)in_size, (unsigned)xmax_size); + fatal_error = tsk_true; + goto bail; + } + // start-accumulator + if (!vp8->decoder.accumulator) { + if (!(vp8->decoder.accumulator = tsk_calloc(in_size, sizeof(uint8_t)))) { + TSK_DEBUG_ERROR("Failed to allocated new buffer"); + fatal_error = tsk_true; + goto bail; + } + vp8->decoder.accumulator_size = in_size; + } + if ((vp8->decoder.accumulator_pos + in_size) >= xmax_size) { + TSK_DEBUG_ERROR("BufferOverflow"); + vp8->decoder.accumulator_pos = 0; + fatal_error = tsk_true; + goto bail; + } + if ((vp8->decoder.accumulator_pos + in_size) > vp8->decoder.accumulator_size) { + if (!(vp8->decoder.accumulator = tsk_realloc(vp8->decoder.accumulator, (vp8->decoder.accumulator_pos + in_size)))) { + TSK_DEBUG_ERROR("Failed to reallocated new buffer"); + vp8->decoder.accumulator_pos = 0; + vp8->decoder.accumulator_size = 0; + fatal_error = tsk_true; + goto bail; + } + vp8->decoder.accumulator_size = (vp8->decoder.accumulator_pos + in_size); + } + + memcpy(&((uint8_t*)vp8->decoder.accumulator)[vp8->decoder.accumulator_pos], pdata, in_size); + vp8->decoder.accumulator_pos += in_size; + // end-accumulator + + // Decode the frame if we have a marker or the first partition is complete and not corrupted + if (rtp_hdr->marker /*|| (!vp8->decoder.corrupted && vp8->decoder.first_part_size == vp8->decoder.accumulator_pos)*/) { + vpx_image_t *img; + vpx_codec_iter_t iter = tsk_null; + vpx_codec_err_t vpx_ret; + const uint8_t* pay_ptr = (const uint8_t*)vp8->decoder.accumulator; + const tsk_size_t pay_size = vp8->decoder.accumulator_pos; + + // in all cases: reset accumulator position + vp8->decoder.accumulator_pos = 0; + +#if 0 /* http://groups.google.com/a/webmproject.org/group/apps-devel/browse_thread/thread/c84438e70fe122fa/2dfc322018aa22a8 */ + // libvpx will crash very ofen when the frame is corrupted => for now we decided not to decode such frame + // according to the latest release there is a function to check if the frame + // is corrupted or not => To be checked + if(vp8->decoder.corrupted) { + vp8->decoder.corrupted = tsk_false; + goto bail; + } +#endif + + if (pay_size < vp8->decoder.first_part_size) { + TSK_DEBUG_WARN("[VP8] No enough bytes for the first part: %u < %u", (unsigned)pay_size, (unsigned)vp8->decoder.first_part_size); + // Not a fatal error + goto bail; + } + + vpx_ret = vpx_codec_decode(&vp8->decoder.context, pay_ptr, (int)pay_size, tsk_null, 0); + + if (vpx_ret != VPX_CODEC_OK) { + TSK_DEBUG_INFO("vpx_codec_decode failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + fatal_error = tsk_true; + goto bail; + } + else if (vp8->decoder.idr) { + TSK_DEBUG_INFO("Decoded VP8 IDR"); + if (TMEDIA_CODEC_VIDEO(self)->in.callback) { + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_idr; + TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } + } + + // copy decoded data + ret = 0; + while ((img = vpx_codec_get_frame(&vp8->decoder.context, &iter))) { + unsigned int plane, y; + tsk_size_t xsize; + + // update sizes + TMEDIA_CODEC_VIDEO(vp8)->in.width = img->d_w; + TMEDIA_CODEC_VIDEO(vp8)->in.height = img->d_h; + xsize = (TMEDIA_CODEC_VIDEO(vp8)->in.width * TMEDIA_CODEC_VIDEO(vp8)->in.height * 3) >> 1; + // allocate destination buffer + if (*out_max_size < xsize) { + if (!(*out_data = tsk_realloc(*out_data, xsize))) { + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + *out_max_size = 0; + goto bail; + } + *out_max_size = xsize; + } + + // layout picture + for (plane = 0; plane < 3; plane++) { + unsigned char *buf = img->planes[plane]; + for (y = 0; y < img->d_h >> (plane ? 1 : 0); y++) { + unsigned int w_count = img->d_w >> (plane ? 1 : 0); + if ((ret + w_count) > *out_max_size) { + TSK_DEBUG_ERROR("BufferOverflow"); + ret = 0; + goto bail; + } + memcpy(((uint8_t*)*out_data) + ret, buf, w_count); + ret += w_count; + buf += img->stride[plane]; + } + } + } + } + +bail: + if (fatal_error && 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); + } + + // vp8->decoder.last_PartID = PartID; + // vp8->decoder.last_S = S; + // vp8->decoder.last_N = N; + return ret; +} + +static tsk_bool_t tdav_codec_vp8_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ +#if 0 + if(tsk_striequals(att_name, "fmtp")) { + unsigned width, height, fps; + if(tmedia_parse_video_fmtp(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &width, &height, &fps)) { + TSK_DEBUG_ERROR("Failed to match fmtp=%s", att_value); + return tsk_false; + } + TMEDIA_CODEC_VIDEO(codec)->in.width = TMEDIA_CODEC_VIDEO(codec)->out.width = width; + TMEDIA_CODEC_VIDEO(codec)->in.height = TMEDIA_CODEC_VIDEO(codec)->out.height = height; + TMEDIA_CODEC_VIDEO(codec)->in.fps = TMEDIA_CODEC_VIDEO(codec)->out.fps = fps; + } + else +#endif + 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 tsk_true; +} + +static char* tdav_codec_vp8_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) +{ +#if 0 + if(tsk_striequals(att_name, "fmtp")) { + return tmedia_get_video_fmtp(TMEDIA_CODEC_VIDEO(codec)->pref_size); + } + else +#endif + 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; +} + +/* ============ VP8 object definition ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_vp8_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_vp8_t *vp8 = self; + if (vp8) { + /* init base: called by tmedia_codec_create() */ + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_vp8_dtor(tsk_object_t * self) +{ + tdav_codec_vp8_t *vp8 = self; + TSK_DEBUG_INFO("*** tdav_codec_vp8_dtor destroyed ***"); + if (vp8) { + /* deinit base */ + tmedia_codec_video_deinit(vp8); + /* deinit self */ + tdav_codec_vp8_close_encoder(vp8); + tdav_codec_vp8_close_decoder(vp8); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_vp8_def_s = +{ + sizeof(tdav_codec_vp8_t), + tdav_codec_vp8_ctor, + tdav_codec_vp8_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_vp8_plugin_def_s = +{ + &tdav_codec_vp8_def_s, + + tmedia_video, + tmedia_codec_id_vp8, + "VP8", + "VP8 codec (libvpx)", + TMEDIA_CODEC_FORMAT_VP8, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (defaul width,height,fps) */ + { 176, 144, 0 }, // fps is @deprecated + + tdav_codec_vp8_set, + tdav_codec_vp8_open, + tdav_codec_vp8_close, + tdav_codec_vp8_encode, + tdav_codec_vp8_decode, + tdav_codec_vp8_sdp_att_match, + tdav_codec_vp8_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_vp8_plugin_def_t = &tdav_codec_vp8_plugin_def_s; + +/* ============ Internal functions ================= */ + +int tdav_codec_vp8_open_encoder(tdav_codec_vp8_t* self) +{ + vpx_codec_err_t vpx_ret; + vpx_enc_frame_flags_t enc_flags = 0; // VPX_EFLAG_XXX + + if (self->encoder.initialized) { + TSK_DEBUG_ERROR("VP8 encoder already inialized"); + return -1; + } + + if ((vpx_ret = vpx_codec_enc_config_default(vp8_interface_enc, &self->encoder.cfg, 0)) != VPX_CODEC_OK) { + TSK_DEBUG_ERROR("vpx_codec_enc_config_default failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + return -2; + } + self->encoder.cfg.g_timebase.num = 1; + self->encoder.cfg.g_timebase.den = TMEDIA_CODEC_VIDEO(self)->out.fps; + self->encoder.cfg.rc_target_bitrate = 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), + TMEDIA_CODEC(self)->bandwidth_max_upload + ); + self->encoder.cfg.g_w = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.height : TMEDIA_CODEC_VIDEO(self)->out.width; + self->encoder.cfg.g_h = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.width : TMEDIA_CODEC_VIDEO(self)->out.height; + self->encoder.cfg.kf_mode = VPX_KF_AUTO; + /*self->encoder.cfg.kf_min_dist =*/ self->encoder.cfg.kf_max_dist = (TDAV_VP8_GOP_SIZE_IN_SECONDS * TMEDIA_CODEC_VIDEO(self)->out.fps); +#if defined(VPX_ERROR_RESILIENT_DEFAULT) + self->encoder.cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; +#else + self->encoder.cfg.g_error_resilient = 1; +#endif +#if defined(VPX_ERROR_RESILIENT_PARTITIONS) + self->encoder.cfg.g_error_resilient |= VPX_ERROR_RESILIENT_PARTITIONS; +#endif +#if defined(VPX_CODEC_USE_OUTPUT_PARTITION) + enc_flags |= VPX_CODEC_USE_OUTPUT_PARTITION; +#endif + self->encoder.cfg.g_lag_in_frames = 0; +#if TDAV_UNDER_WINDOWS + { + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + self->encoder.cfg.g_threads = SystemInfo.dwNumberOfProcessors; + } +#endif + self->encoder.cfg.rc_end_usage = VPX_CBR; + self->encoder.cfg.g_pass = VPX_RC_ONE_PASS; +#if 0 + self->encoder.cfg.rc_dropframe_thresh = 30; + self->encoder.cfg.rc_resize_allowed = 0; + self->encoder.cfg.rc_min_quantizer = 2; + self->encoder.cfg.rc_max_quantizer = 56; + self->encoder.cfg.rc_undershoot_pct = 100; + self->encoder.cfg.rc_overshoot_pct = 15; + self->encoder.cfg.rc_buf_initial_sz = 500; + self->encoder.cfg.rc_buf_optimal_sz = 600; + self->encoder.cfg.rc_buf_sz = 1000; +#endif + + if ((vpx_ret = vpx_codec_enc_init(&self->encoder.context, vp8_interface_enc, &self->encoder.cfg, enc_flags)) != VPX_CODEC_OK) { + TSK_DEBUG_ERROR("vpx_codec_enc_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + return -3; + } + self->encoder.pic_id = /*(rand() ^ rand()) % 0x7FFF*/0/*Use zero: why do you want to make your life harder?*/; + + /* vpx_codec_control(&self->encoder.context, VP8E_SET_STATIC_THRESHOLD, 800); */ +#if !TDAV_UNDER_MOBILE /* must not remove: crash on Android for sure and probably on iOS also (all ARM devices ?) */ + vpx_codec_control(&self->encoder.context, VP8E_SET_NOISE_SENSITIVITY, 2); +#elif TDAV_UNDER_WINDOWS_CE + vpx_codec_control(&self->encoder.context, VP8E_SET_NOISE_SENSITIVITY, 16); + vpx_codec_control(&self->encoder.context, VP8E_SET_CPUUSED, 16); + vpx_codec_control(&self->encoder.context, VP8E_SET_STATIC_THRESHOLD, 16); + vpx_codec_control(&self->encoder.context, VP8E_SET_SHARPNESS, 16); +#endif + + // Set number of partitions +#if defined(VPX_CODEC_USE_OUTPUT_PARTITION) + { + unsigned _s = TMEDIA_CODEC_VIDEO(self)->out.height * TMEDIA_CODEC_VIDEO(self)->out.width; + if (_s < (352 * 288)) { + vpx_codec_control(&self->encoder.context, VP8E_SET_TOKEN_PARTITIONS, VP8_ONE_TOKENPARTITION); + } + else if (_s < (352 * 288) * 2 * 2) { + vpx_codec_control(&self->encoder.context, VP8E_SET_TOKEN_PARTITIONS, VP8_TWO_TOKENPARTITION); + } + else if (_s < (352 * 288) * 4 * 4) { + vpx_codec_control(&self->encoder.context, VP8E_SET_TOKEN_PARTITIONS, VP8_FOUR_TOKENPARTITION); + } + else if (_s < (352 * 288) * 8 * 8) { + vpx_codec_control(&self->encoder.context, VP8E_SET_TOKEN_PARTITIONS, VP8_EIGHT_TOKENPARTITION); + } + } +#endif + + // Create the mutex if not already done + if (!self->encoder.mutex && !(self->encoder.mutex = tsk_mutex_create())) { + vpx_codec_destroy(&self->encoder.context); + TSK_DEBUG_ERROR("Failed to create mutex"); + return -4; + } + + self->encoder.frame_count = 0; + + self->encoder.initialized = tsk_true; + + TSK_DEBUG_INFO("[VP8] target_bitrate=%d kbps", self->encoder.cfg.rc_target_bitrate); + + return 0; +} + +int tdav_codec_vp8_open_decoder(tdav_codec_vp8_t* self) +{ + vpx_codec_err_t vpx_ret; + vpx_codec_caps_t dec_caps; + vpx_codec_flags_t dec_flags = 0; +#if !TDAV_UNDER_MOBILE + static vp8_postproc_cfg_t __pp = { VP8_DEBLOCK | VP8_DEMACROBLOCK, 4, 0 }; +#endif + + if (self->decoder.initialized) { + TSK_DEBUG_ERROR("VP8 decoder already initialized"); + return -1; + } + + self->decoder.cfg.w = TMEDIA_CODEC_VIDEO(self)->out.width; + self->decoder.cfg.h = TMEDIA_CODEC_VIDEO(self)->out.height; +#if TDAV_UNDER_WINDOWS + { + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + self->decoder.cfg.threads = SystemInfo.dwNumberOfProcessors; + } +#endif + + dec_caps = vpx_codec_get_caps(&vpx_codec_vp8_dx_algo); +#if !TDAV_UNDER_MOBILE + if (dec_caps & VPX_CODEC_CAP_POSTPROC) { + dec_flags |= VPX_CODEC_USE_POSTPROC; + } +#endif +#if defined(VPX_CODEC_CAP_ERROR_CONCEALMENT) + if (dec_caps & VPX_CODEC_CAP_ERROR_CONCEALMENT) { + dec_flags |= VPX_CODEC_USE_ERROR_CONCEALMENT; + } +#endif + + if ((vpx_ret = vpx_codec_dec_init(&self->decoder.context, vp8_interface_dec, &self->decoder.cfg, dec_flags)) != VPX_CODEC_OK) { + TSK_DEBUG_ERROR("vpx_codec_dec_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + return -4; + } +#if !TDAV_UNDER_MOBILE + if ((vpx_ret = vpx_codec_control(&self->decoder.context, VP8_SET_POSTPROC, &__pp))) { + TSK_DEBUG_WARN("vpx_codec_dec_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + } +#endif + self->decoder.initialized = tsk_true; + + return 0; +} + +int tdav_codec_vp8_close_encoder(tdav_codec_vp8_t* self) +{ + TSK_DEBUG_INFO("tdav_codec_vp8_close_encoder(begin)"); + if (self->encoder.initialized) { + vpx_codec_destroy(&self->encoder.context); + self->encoder.initialized = tsk_false; + } + if (self->encoder.mutex) { + tsk_mutex_destroy(&self->encoder.mutex); + } + TSK_FREE(self->encoder.rtp.ptr); + self->encoder.rtp.size = 0; + self->encoder.rotation = 0; // reset rotation + TSK_DEBUG_INFO("tdav_codec_vp8_close_encoder(end)"); + return 0; +} + +int tdav_codec_vp8_close_decoder(tdav_codec_vp8_t* self) +{ + TSK_DEBUG_INFO("tdav_codec_vp8_close_decoder(begin)"); + if (self->decoder.initialized) { + vpx_codec_destroy(&self->decoder.context); + self->decoder.initialized = tsk_false; + } + TSK_FREE(self->decoder.accumulator); + self->decoder.accumulator_size = 0; + self->decoder.accumulator_pos = 0; + TSK_DEBUG_INFO("tdav_codec_vp8_close_decoder(end)"); + + return 0; +} + +/* ============ VP8 RTP packetizer/depacketizer ================= */ + + +static void tdav_codec_vp8_encap(tdav_codec_vp8_t* self, const vpx_codec_cx_pkt_t *pkt) +{ + tsk_bool_t non_ref, is_keyframe, part_start; + uint8_t *frame_ptr; + uint32_t part_size, part_ID, pkt_size, index; + + if (!self || !pkt || !pkt->data.frame.buf || !pkt->data.frame.sz) { + TSK_DEBUG_ERROR("Invalid parameter"); + return; + } + + index = 0; + frame_ptr = pkt->data.frame.buf; + pkt_size = (uint32_t)pkt->data.frame.sz; + non_ref = (pkt->data.frame.flags & VPX_FRAME_IS_DROPPABLE); + is_keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY); + + +#if defined(VPX_CODEC_USE_OUTPUT_PARTITION) + part_ID = pkt->data.frame.partition_id; + part_start = tsk_true; + part_size = pkt_size; + while (index < part_size) { + uint32_t frag_size = TSK_MIN(TDAV_VP8_RTP_PAYLOAD_MAX_SIZE, (part_size - index)); + tdav_codec_vp8_rtp_callback( + self, + &frame_ptr[index], + frag_size, + part_ID, + part_start, + non_ref, + ((pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) == 0 && (index + frag_size) == part_size) // RTP marker? + ); + part_start = tsk_false; + index += frag_size; + } +#else + // first partition (contains modes and motion vectors) + part_ID = 0; // The first VP8 partition(containing modes and motion vectors) MUST be labeled with PartID = 0 + part_start = tsk_true; + part_size = (frame_ptr[2] << 16) | (frame_ptr[1] << 8) | frame_ptr[0]; + part_size = (part_size >> 5) & 0x7FFFF; + if (part_size > pkt_size) { + TSK_DEBUG_ERROR("part_size is > pkt_size(%u,%u)", part_size, pkt_size); + return; + } + + // first,first,....partitions (or fragment if part_size > TDAV_VP8_RTP_PAYLOAD_MAX_SIZE) + while (index<part_size) { + uint32_t frag_size = TSK_MIN(TDAV_VP8_RTP_PAYLOAD_MAX_SIZE, (part_size - index)); + tdav_codec_vp8_rtp_callback(self, &frame_ptr[index], frag_size, part_ID, part_start, non_ref, tsk_false); + part_start = tsk_false; + index += frag_size; + } + + // second,third,... partitions (or fragment if part_size > TDAV_VP8_RTP_PAYLOAD_MAX_SIZE) + // FIXME: low FEC + part_start = tsk_true; + while (index<pkt_size) { + if (part_start) { + /* PartID SHOULD be incremented for each subsequent partition, + but MAY be kept at 0 for all packets. PartID MUST NOT be larger + than 8. + */ + part_ID++; + } + part_size = TSK_MIN(TDAV_VP8_RTP_PAYLOAD_MAX_SIZE, (pkt_size - index)); + + tdav_codec_vp8_rtp_callback(self, &frame_ptr[index], part_size, part_ID, part_start, non_ref, (index + part_size)==pkt_size); + index += part_size; + /* + If more than one packet in an encoded frame contains the + same PartID, the S bit MUST NOT be set for any other packet than + the first packet with that PartID. + */ + part_start = tsk_false; + } +#endif /* VPX_CODEC_USE_OUTPUT_PARTITION */ +} + +static void tdav_codec_vp8_rtp_callback(tdav_codec_vp8_t *self, const void *data, tsk_size_t size, uint32_t partID, tsk_bool_t part_start, tsk_bool_t non_ref, tsk_bool_t last) +{ + tsk_size_t paydesc_and_hdr_size = TDAV_VP8_PAY_DESC_SIZE; + tsk_bool_t has_hdr; + /* draft-ietf-payload-vp8-04 - 4.2. VP8 Payload Descriptor + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |X|R|N|S|PartID | (REQUIRED) + +-+-+-+-+-+-+-+-+ + X: |I|L|T|K| RSV | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + I: |M| PictureID | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + L: | TL0PICIDX | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + T/K: |TID|Y| KEYIDX | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + + draft-ietf-payload-vp8-04 - 4.3. VP8 Payload Header + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |Size0|H| VER |P| + +-+-+-+-+-+-+-+-+ + | Size1 | + +-+-+-+-+-+-+-+-+ + | Size2 | + +-+-+-+-+-+-+-+-+ + | Bytes 4..N of | + | VP8 payload | + : : + +-+-+-+-+-+-+-+-+ + | OPTIONAL RTP | + | padding | + : : + +-+-+-+-+-+-+-+-+ + */ + + /* + Note that the header is present only in packets which have the S bit equal to one and the + PartID equal to zero in the payload descriptor. + */ + if ((has_hdr = (part_start && partID == 0))) { + has_hdr = tsk_true; + paydesc_and_hdr_size += 0; // encoded data already contains payload header? + } + + if (!data || !size) { + TSK_DEBUG_ERROR("Invalid parameter"); + return; + } + if (self->encoder.rtp.size < (size + paydesc_and_hdr_size)) { + if (!(self->encoder.rtp.ptr = tsk_realloc(self->encoder.rtp.ptr, (size + paydesc_and_hdr_size)))) { + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + return; + } + self->encoder.rtp.size = (size + paydesc_and_hdr_size); + } + memcpy((self->encoder.rtp.ptr + paydesc_and_hdr_size), data, size); + + /* VP8 Payload Descriptor */ + // |X|R|N|S|PartID| + self->encoder.rtp.ptr[0] = (partID & 0x0F) // PartID + | ((part_start << 4) & 0x10)// S + | ((non_ref << 5) & 0x20) // N + // R = 0 +#if TDAV_VP8_DISABLE_EXTENSION + | (0x00) // X=0 +#else + | (0x80) // X=1 +#endif + ; + +#if !TDAV_VP8_DISABLE_EXTENSION + // X: |I|L|T|K| RSV | + self->encoder.rtp.ptr[1] = 0x80; // I = 1, L = 0, T = 0, K = 0, RSV = 0 + // I: |M| PictureID | + self->encoder.rtp.ptr[2] = (0x80 | ((self->encoder.pic_id >> 8) & 0x7F)); // M = 1 (PictureID on 15 bits) + self->encoder.rtp.ptr[3] = (self->encoder.pic_id & 0xFF); +#endif + + /* 4.2. VP8 Payload Header */ + //if(has_hdr) { + // already part of the encoded stream + //} + + // Send data over the network + if (TMEDIA_CODEC_VIDEO(self)->out.callback) { + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = self->encoder.rtp.ptr; + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = (size + TDAV_VP8_PAY_DESC_SIZE); + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (uint32_t)((1. / (double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate); + TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = last; + TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); + } +} + +#endif /* HAVE_LIBVPX */ |