diff options
Diffstat (limited to 'pjmedia/src/pjmedia-codec/h264_packetizer.c')
-rw-r--r-- | pjmedia/src/pjmedia-codec/h264_packetizer.c | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-codec/h264_packetizer.c b/pjmedia/src/pjmedia-codec/h264_packetizer.c new file mode 100644 index 00000000..8eb22d6f --- /dev/null +++ b/pjmedia/src/pjmedia-codec/h264_packetizer.c @@ -0,0 +1,530 @@ +/* $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 <pjmedia-codec/h264_packetizer.h> +#include <pjmedia/types.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +#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) + { + return PJ_ENOTSUP; + } + + p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h264_packetizer); + if (cfg) { + pj_memcpy(&p_->cfg, cfg, sizeof(*cfg)); + } else { + p_->cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED; + p_->cfg.mtu = PJMEDIA_MAX_MTU; + } + + *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,("h264_packetizer.c", + "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 = *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 = 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 + 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 = 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 */ + pj_assert(nal_octet); + + *payload = nal_start; + *payload_len = nal_end - nal_start; + *pos = 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_code[3] = {0, 0, 1}; + enum { MIN_PAYLOAD_SIZE = 2 }; + pj_uint8_t nal_type; + + PJ_UNUSED_ARG(pktz); + +#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+PJ_ARRAY_SIZE(nal_start_code)) { + /* Insufficient bistream buffer, discard this payload */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + return PJ_ETOOSMALL; + } + + /* Write NAL unit start code */ + pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* Write NAL unit */ + pj_memcpy(p, payload, payload_len); + p += payload_len; + + /* Update the bitstream writing offset */ + *bits_pos = 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.263 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, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* 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 = 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+PJ_ARRAY_SIZE(nal_start_code)) { + /* Insufficient bistream buffer, drop this packet */ + pj_assert(!"Insufficient H.263 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, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* 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 = 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; +} |