#if HAVE_CRT #define _CRTDBG_MAP_ALLOC #include #include #endif //HAVE_CRT /* * Copyright (C) 2020, University of the Basque Country (UPV/EHU) * Contact for licensing options: * * The original file was part of Open Source Doubango Framework * Copyright (C) 2010-2011 Mamadou Diop. * Copyright (C) 2012 Doubango Telecom * * 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 * * @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 #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 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 HAVE_CRT //Debug memory if(!(self->encoder.buffer = calloc(size, sizeof(uint8_t)))){ #else if(!(self->encoder.buffer = tsk_calloc(size, sizeof(uint8_t)))){ #endif //HAVE_CRT 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 HAVE_CRT //Debug memory if(!(self->decoder.accumulator = calloc((size + FF_INPUT_BUFFER_PADDING_SIZE), sizeof(uint8_t)))){ #else if(!(self->decoder.accumulator = tsk_calloc((size + FF_INPUT_BUFFER_PADDING_SIZE), sizeof(uint8_t)))){ #endif //HAVE_CRT TSK_DEBUG_ERROR("Failed to allocate decoder buffer"); return -2; } #if HAVE_CRT //Debug memory if(!(self->decoder.accumulator = calloc((size + FF_INPUT_BUFFER_PADDING_SIZE), sizeof(uint8_t)))){ #else if(!(self->decoder.accumulator = tsk_calloc((size + FF_INPUT_BUFFER_PADDING_SIZE), sizeof(uint8_t)))){ #endif //HAVE_CRT 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; } 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 */