/* $Id$ */ /* * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) #define THIS_FILE "h264_packetizer.c" #define DBG_PACKETIZE 0 #define DBG_UNPACKETIZE 0 /* H.264 packetizer definition */ struct pjmedia_h264_packetizer { /* Current settings */ pjmedia_h264_packetizer_cfg cfg; /* Unpacketizer state */ unsigned unpack_last_sync_pos; pj_bool_t unpack_prev_lost; }; /* Enumeration of H.264 NAL unit types */ enum { NAL_TYPE_SINGLE_NAL_MIN = 1, NAL_TYPE_SINGLE_NAL_MAX = 23, NAL_TYPE_STAP_A = 24, NAL_TYPE_FU_A = 28, }; /* * Find next NAL unit from the specified H.264 bitstream data. */ static pj_uint8_t* find_next_nal_unit(pj_uint8_t *start, pj_uint8_t *end) { pj_uint8_t *p = start; /* Simply lookup "0x000001" pattern */ while (p <= end-3 && (p[0] || p[1] || p[2]!=1)) ++p; if (p > end-3) /* No more NAL unit in this bitstream */ return NULL; /* Include 8 bits leading zero */ if (p>start && *(p-1)==0) return (p-1); return p; } /* * Create H264 packetizer. */ PJ_DEF(pj_status_t) pjmedia_h264_packetizer_create( pj_pool_t *pool, const pjmedia_h264_packetizer_cfg *cfg, pjmedia_h264_packetizer **p) { pjmedia_h264_packetizer *p_; PJ_ASSERT_RETURN(pool && p, PJ_EINVAL); if (cfg && cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED && cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL && cfg->unpack_nal_start != 0 && cfg->unpack_nal_start != 3 && cfg->unpack_nal_start != 4) { return PJ_ENOTSUP; } p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h264_packetizer); if (cfg) { pj_memcpy(&p_->cfg, cfg, sizeof(*cfg)); if (p_->cfg.unpack_nal_start == 0) p_->cfg.unpack_nal_start = 3; } else { p_->cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED; p_->cfg.mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE; p_->cfg.unpack_nal_start = 3; } *p = p_; return PJ_SUCCESS; } /* * Generate an RTP payload from H.264 frame bitstream, in-place processing. */ PJ_DEF(pj_status_t) pjmedia_h264_packetize(pjmedia_h264_packetizer *pktz, pj_uint8_t *buf, pj_size_t buf_len, unsigned *pos, const pj_uint8_t **payload, pj_size_t *payload_len) { pj_uint8_t *nal_start = NULL, *nal_end = NULL, *nal_octet = NULL; pj_uint8_t *p, *end; enum { HEADER_SIZE_FU_A = 2, HEADER_SIZE_STAP_A = 3, }; enum { MAX_NALS_IN_AGGR = 32 }; #if DBG_PACKETIZE if (*pos == 0 && buf_len) { PJ_LOG(3, ("h264pack", "<< Start packing new frame >>")); } #endif p = buf + *pos; end = buf + buf_len; /* Find NAL unit startcode */ if (end-p >= 4) nal_start = find_next_nal_unit(p, p+4); if (nal_start) { /* Get NAL unit octet pointer */ while (*nal_start++ == 0); nal_octet = nal_start; } else { /* This NAL unit is being fragmented */ nal_start = p; } /* Get end of NAL unit */ p = nal_start+pktz->cfg.mtu+1; if (p > end || pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) p = end; nal_end = find_next_nal_unit(nal_start, p); if (!nal_end) nal_end = p; /* Validate MTU vs NAL length on single NAL unit packetization */ if ((pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && nal_end - nal_start > pktz->cfg.mtu) { //pj_assert(!"MTU too small for H.264 single NAL packetization mode"); PJ_LOG(2,(THIS_FILE, "MTU too small for H.264 (required=%u, MTU=%u)", nal_end - nal_start, pktz->cfg.mtu)); return PJ_ETOOSMALL; } /* Evaluate the proper payload format structure */ /* Fragmentation (FU-A) packet */ if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && (!nal_octet || nal_end-nal_start > pktz->cfg.mtu)) { pj_uint8_t NRI, TYPE; if (nal_octet) { /* We have NAL unit octet, so this is the first fragment */ NRI = (*nal_octet & 0x60) >> 5; TYPE = *nal_octet & 0x1F; /* Skip nal_octet in nal_start to be overriden by FU header */ ++nal_start; } else { /* Not the first fragment, get NRI and NAL unit type * from the previous fragment. */ p = nal_start - pktz->cfg.mtu; NRI = (*p & 0x60) >> 5; TYPE = *(p+1) & 0x1F; } /* Init FU indicator (one octet: F+NRI+TYPE) */ p = nal_start - HEADER_SIZE_FU_A; *p = (NRI << 5) | NAL_TYPE_FU_A; ++p; /* Init FU header (one octed: S+E+R+TYPE) */ *p = TYPE; if (nal_octet) *p |= (1 << 7); /* S bit flag = start of fragmentation */ if (nal_end-nal_start+HEADER_SIZE_FU_A <= pktz->cfg.mtu) *p |= (1 << 6); /* E bit flag = end of fragmentation */ /* Set payload, payload length */ *payload = nal_start - HEADER_SIZE_FU_A; if (nal_end-nal_start+HEADER_SIZE_FU_A > pktz->cfg.mtu) *payload_len = pktz->cfg.mtu; else *payload_len = nal_end - nal_start + HEADER_SIZE_FU_A; *pos = (unsigned)(*payload + *payload_len - buf); #if DBG_PACKETIZE PJ_LOG(3, ("h264pack", "Packetized fragmented H264 NAL unit " "(pos=%d, type=%d, NRI=%d, S=%d, E=%d, len=%d/%d)", *payload-buf, TYPE, NRI, *p>>7, (*p>>6)&1, *payload_len, buf_len)); #endif return PJ_SUCCESS; } /* Aggregation (STAP-A) packet */ if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && (nal_end != end) && (nal_end - nal_start + HEADER_SIZE_STAP_A) < pktz->cfg.mtu) { int total_size; unsigned nal_cnt = 1; pj_uint8_t *nal[MAX_NALS_IN_AGGR]; pj_size_t nal_size[MAX_NALS_IN_AGGR]; pj_uint8_t NRI; pj_assert(nal_octet); /* Init the first NAL unit in the packet */ nal[0] = nal_start; nal_size[0] = nal_end - nal_start; total_size = (int)nal_size[0] + HEADER_SIZE_STAP_A; NRI = (*nal_octet & 0x60) >> 5; /* Populate next NAL units */ while (nal_cnt < MAX_NALS_IN_AGGR) { pj_uint8_t *tmp_end; /* Find start address of the next NAL unit */ p = nal[nal_cnt-1] + nal_size[nal_cnt-1]; while (*p++ == 0); nal[nal_cnt] = p; /* Find end address of the next NAL unit */ tmp_end = p + (pktz->cfg.mtu - total_size); if (tmp_end > end) tmp_end = end; p = find_next_nal_unit(p+1, tmp_end); if (p) { nal_size[nal_cnt] = p - nal[nal_cnt]; } else { break; } /* Update total payload size (2 octet NAL size + NAL) */ total_size += (2 + (int)nal_size[nal_cnt]); if (total_size <= pktz->cfg.mtu) { pj_uint8_t tmp_nri; /* Get maximum NRI of the aggregated NAL units */ tmp_nri = (*(nal[nal_cnt]-1) & 0x60) >> 5; if (tmp_nri > NRI) NRI = tmp_nri; } else { break; } ++nal_cnt; } /* Only use STAP-A when we found more than one NAL units */ if (nal_cnt > 1) { unsigned i; /* Init STAP-A NAL header (F+NRI+TYPE) */ p = nal[0] - HEADER_SIZE_STAP_A; *p++ = (NRI << 5) | NAL_TYPE_STAP_A; /* Append all populated NAL units into payload (SIZE+NAL) */ for (i = 0; i < nal_cnt; ++i) { /* Put size (2 octets in network order) */ pj_assert(nal_size[i] <= 0xFFFF); *p++ = (pj_uint8_t)(nal_size[i] >> 8); *p++ = (pj_uint8_t)(nal_size[i] & 0xFF); /* Append NAL unit, watchout memmove()-ing bitstream! */ if (p != nal[i]) pj_memmove(p, nal[i], nal_size[i]); p += nal_size[i]; } /* Set payload, payload length, and pos */ *payload = nal[0] - HEADER_SIZE_STAP_A; pj_assert(*payload >= buf+*pos); *payload_len = p - *payload; *pos = (unsigned)(nal[nal_cnt-1] + nal_size[nal_cnt-1] - buf); #if DBG_PACKETIZE PJ_LOG(3, ("h264pack", "Packetized aggregation of " "%d H264 NAL units (pos=%d, NRI=%d len=%d/%d)", nal_cnt, *payload-buf, NRI, *payload_len, buf_len)); #endif return PJ_SUCCESS; } } /* Single NAL unit packet */ *payload = nal_start; *payload_len = nal_end - nal_start; *pos = (unsigned)(nal_end - buf); #if DBG_PACKETIZE PJ_LOG(3, ("h264pack", "Packetized single H264 NAL unit " "(pos=%d, type=%d, NRI=%d, len=%d/%d)", nal_start-buf, *nal_octet&0x1F, (*nal_octet&0x60)>>5, *payload_len, buf_len)); #endif return PJ_SUCCESS; } /* * Append RTP payload to a H.264 picture bitstream. Note that the only * payload format that cares about packet lost is the NAL unit * fragmentation format (FU-A/B), so we will only manage the "prev_lost" * state for the FU-A/B packets. */ PJ_DEF(pj_status_t) pjmedia_h264_unpacketize(pjmedia_h264_packetizer *pktz, const pj_uint8_t *payload, pj_size_t payload_len, pj_uint8_t *bits, pj_size_t bits_len, unsigned *bits_pos) { const pj_uint8_t nal_start[4] = {0, 0, 0, 1}; const pj_uint8_t *nal_start_code; enum { MIN_PAYLOAD_SIZE = 2 }; pj_uint8_t nal_type; nal_start_code = nal_start + PJ_ARRAY_SIZE(nal_start) - pktz->cfg.unpack_nal_start; #if DBG_UNPACKETIZE if (*bits_pos == 0 && payload_len) { PJ_LOG(3, ("h264unpack", ">> Start unpacking new frame <<")); } #endif /* Check if this is a missing/lost packet */ if (payload == NULL) { pktz->unpack_prev_lost = PJ_TRUE; return PJ_SUCCESS; } /* H264 payload size */ if (payload_len < MIN_PAYLOAD_SIZE) { /* Invalid bitstream, discard this payload */ pktz->unpack_prev_lost = PJ_TRUE; return PJ_EINVAL; } /* Reset last sync point for every new picture bitstream */ if (*bits_pos == 0) pktz->unpack_last_sync_pos = 0; nal_type = *payload & 0x1F; if (nal_type >= NAL_TYPE_SINGLE_NAL_MIN && nal_type <= NAL_TYPE_SINGLE_NAL_MAX) { /* Single NAL unit packet */ pj_uint8_t *p = bits + *bits_pos; /* Validate bitstream length */ if (bits_len-*bits_pos < payload_len+pktz->cfg.unpack_nal_start) { /* Insufficient bistream buffer, discard this payload */ pj_assert(!"Insufficient H.264 bitstream buffer"); return PJ_ETOOSMALL; } /* Write NAL unit start code */ pj_memcpy(p, nal_start_code, pktz->cfg.unpack_nal_start); p += pktz->cfg.unpack_nal_start; /* Write NAL unit */ pj_memcpy(p, payload, payload_len); p += payload_len; /* Update the bitstream writing offset */ *bits_pos = (unsigned)(p - bits); pktz->unpack_last_sync_pos = *bits_pos; #if DBG_UNPACKETIZE PJ_LOG(3, ("h264unpack", "Unpacked single H264 NAL unit " "(type=%d, NRI=%d, len=%d)", nal_type, (*payload&0x60)>>5, payload_len)); #endif } else if (nal_type == NAL_TYPE_STAP_A) { /* Aggregation packet */ pj_uint8_t *p, *p_end; const pj_uint8_t *q, *q_end; unsigned cnt = 0; /* Validate bitstream length */ if (bits_len - *bits_pos < payload_len + 32) { /* Insufficient bistream buffer, discard this payload */ pj_assert(!"Insufficient H.264 bitstream buffer"); return PJ_ETOOSMALL; } /* Fill bitstream */ p = bits + *bits_pos; p_end = bits + bits_len; q = payload + 1; q_end = payload + payload_len; while (q < q_end && p < p_end) { pj_uint16_t tmp_nal_size; /* Write NAL unit start code */ pj_memcpy(p, nal_start_code, pktz->cfg.unpack_nal_start); p += pktz->cfg.unpack_nal_start; /* Get NAL unit size */ tmp_nal_size = (*q << 8) | *(q+1); q += 2; if (q + tmp_nal_size > q_end) { /* Invalid bitstream, discard the rest of the payload */ return PJ_EINVAL; } /* Write NAL unit */ pj_memcpy(p, q, tmp_nal_size); p += tmp_nal_size; q += tmp_nal_size; ++cnt; /* Update the bitstream writing offset */ *bits_pos = (unsigned)(p - bits); pktz->unpack_last_sync_pos = *bits_pos; } #if DBG_UNPACKETIZE PJ_LOG(3, ("h264unpack", "Unpacked %d H264 NAL units (len=%d)", cnt, payload_len)); #endif } else if (nal_type == NAL_TYPE_FU_A) { /* Fragmentation packet */ pj_uint8_t *p; const pj_uint8_t *q = payload; pj_uint8_t NRI, TYPE, S, E; p = bits + *bits_pos; /* Validate bitstream length */ if (bits_len-*bits_pos < payload_len+pktz->cfg.unpack_nal_start) { /* Insufficient bistream buffer, drop this packet */ pj_assert(!"Insufficient H.264 bitstream buffer"); pktz->unpack_prev_lost = PJ_TRUE; return PJ_ETOOSMALL; } /* Get info */ S = *(q+1) & 0x80; /* Start bit flag */ E = *(q+1) & 0x40; /* End bit flag */ TYPE = *(q+1) & 0x1f; NRI = (*q & 0x60) >> 5; /* Fill bitstream */ if (S) { /* This is the first part, write NAL unit start code */ pj_memcpy(p, nal_start_code, pktz->cfg.unpack_nal_start); p += pktz->cfg.unpack_nal_start; /* Write NAL unit octet */ *p++ = (NRI << 5) | TYPE; } else if (pktz->unpack_prev_lost) { /* If prev packet was lost, revert the bitstream pointer to * the last sync point. */ pj_assert(pktz->unpack_last_sync_pos <= *bits_pos); *bits_pos = pktz->unpack_last_sync_pos; /* And discard this payload (and the following fragmentation * payloads carrying this same NAL unit. */ return PJ_EIGNORED; } q += 2; /* Write NAL unit */ pj_memcpy(p, q, payload_len - 2); p += (payload_len - 2); /* Update the bitstream writing offset */ *bits_pos = (unsigned)(p - bits); if (E) { /* Update the sync pos only if the end bit flag is set */ pktz->unpack_last_sync_pos = *bits_pos; } #if DBG_UNPACKETIZE PJ_LOG(3, ("h264unpack", "Unpacked fragmented H264 NAL unit " "(type=%d, NRI=%d, len=%d)", TYPE, NRI, payload_len)); #endif } else { *bits_pos = 0; return PJ_ENOTSUP; } pktz->unpack_prev_lost = PJ_FALSE; return PJ_SUCCESS; } #endif /* PJMEDIA_HAS_VIDEO */