| /* $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> |
| |
| |
| #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) |
| { |
| 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_VID_PAYLOAD_SIZE; |
| } |
| |
| *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 = (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_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 = (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.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 = (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+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 = (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 */ |