/* * Copyright (C) 2010-2011 Mamadou Diop. * * Contact: 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_rtp.c * @brief H.264 payloader/depayloder as per RFC 3984 * * @author Mamadou Diop * */ #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 /* strlen() */ #include /* 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 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); 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) { const uint8_t* pdata = in_data; uint8_t nal_type; if(!in_data || !in_size || !out_data || !out_size){ 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); default: /* NAL unit (1-23) */ *append_scp = tsk_true;//(nal_type != 7 && nal_type != 8); // SPS or PPS 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; } 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) { if(in_size <=2){ 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" 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. */ uint8_t hdr; *out_data = (in_data + H264_NAL_UNIT_TYPE_HEADER_SIZE); *out_size = (in_size - H264_NAL_UNIT_TYPE_HEADER_SIZE); // F, NRI and Type hdr = (in_data[0] & 0xe0) /* F,NRI from "FU indicator"*/ | (in_data[1] & 0x1f) /* type from "FU header" */; *((uint8_t*)*out_data) = hdr; // Need to append Start Code Prefix *append_scp = tsk_true; } else{ /* "FU indicator" and "FU header" */ *out_data = (in_data + H264_FUA_HEADER_SIZE); *out_size = (in_size - H264_FUA_HEADER_SIZE); *append_scp = tsk_false; } return 0; } 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_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(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 = (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 = 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 = (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); } } } }