From 95e177eeb21f3e968aa9353bc69d1946966cc835 Mon Sep 17 00:00:00 2001 From: Thomas Volkert Date: Tue, 26 Aug 2014 21:00:03 +0200 Subject: [PATCH] rtpdec: HEVC/H.265 support As specified in draft-ietf-payload-rtp-h265-06. Signed-off-by: Luca Barbato --- Changelog | 1 + libavformat/Makefile | 1 + libavformat/rtpdec.c | 2 + libavformat/rtpdec_formats.h | 2 + libavformat/rtpdec_hevc.c | 372 +++++++++++++++++++++++++++++++++++ libavformat/version.h | 2 +- 6 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 libavformat/rtpdec_hevc.c diff --git a/Changelog b/Changelog index 064945761a..5da25887a6 100644 --- a/Changelog +++ b/Changelog @@ -33,6 +33,7 @@ version : - support for using metadata in stream specifiers in avtools - Aliases and defaults for Ogg subtypes (opus, spx) - matroska 3d support +- HEVC/H.265 RTP payload format (draft v6) depacketizer version 10: diff --git a/libavformat/Makefile b/libavformat/Makefile index a048157375..dbb24277d3 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -35,6 +35,7 @@ OBJS-$(CONFIG_RTPDEC) += rdt.o \ rtpdec_h263.o \ rtpdec_h263_rfc2190.o \ rtpdec_h264.o \ + rtpdec_hevc.o \ rtpdec_ilbc.o \ rtpdec_jpeg.o \ rtpdec_latm.o \ diff --git a/libavformat/rtpdec.c b/libavformat/rtpdec.c index 308a48a7e0..d6421d0a21 100644 --- a/libavformat/rtpdec.c +++ b/libavformat/rtpdec.c @@ -70,6 +70,8 @@ void ff_register_rtp_dynamic_payload_handlers(void) ff_register_dynamic_payload_handler(&ff_h263_2000_dynamic_handler); ff_register_dynamic_payload_handler(&ff_h263_rfc2190_dynamic_handler); ff_register_dynamic_payload_handler(&ff_h264_dynamic_handler); + ff_register_dynamic_payload_handler(&ff_h265_dynamic_handler); + ff_register_dynamic_payload_handler(&ff_hevc_dynamic_handler); ff_register_dynamic_payload_handler(&ff_ilbc_dynamic_handler); ff_register_dynamic_payload_handler(&ff_jpeg_dynamic_handler); ff_register_dynamic_payload_handler(&ff_mp4a_latm_dynamic_handler); diff --git a/libavformat/rtpdec_formats.h b/libavformat/rtpdec_formats.h index e5f4ccb777..956c6cbae0 100644 --- a/libavformat/rtpdec_formats.h +++ b/libavformat/rtpdec_formats.h @@ -45,6 +45,8 @@ extern RTPDynamicProtocolHandler ff_h263_1998_dynamic_handler; extern RTPDynamicProtocolHandler ff_h263_2000_dynamic_handler; extern RTPDynamicProtocolHandler ff_h263_rfc2190_dynamic_handler; extern RTPDynamicProtocolHandler ff_h264_dynamic_handler; +extern RTPDynamicProtocolHandler ff_h265_dynamic_handler; +extern RTPDynamicProtocolHandler ff_hevc_dynamic_handler; extern RTPDynamicProtocolHandler ff_ilbc_dynamic_handler; extern RTPDynamicProtocolHandler ff_jpeg_dynamic_handler; extern RTPDynamicProtocolHandler ff_mp4a_latm_dynamic_handler; diff --git a/libavformat/rtpdec_hevc.c b/libavformat/rtpdec_hevc.c new file mode 100644 index 0000000000..aea3f381d3 --- /dev/null +++ b/libavformat/rtpdec_hevc.c @@ -0,0 +1,372 @@ +/* + * RTP parser for HEVC/H.265 payload format (draft version 6) + * Copyright (c) 2014 Thomas Volkert + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "libavutil/avstring.h" + +#include "avformat.h" +#include "rtpdec.h" + +#define RTP_HEVC_PAYLOAD_HEADER_SIZE 2 +#define RTP_HEVC_FU_HEADER_SIZE 1 +#define RTP_HEVC_DONL_FIELD_SIZE 2 +#define HEVC_SPECIFIED_NAL_UNIT_TYPES 48 + +/* SDP out-of-band signaling data */ +struct PayloadContext { + int using_donl_field; + int profile_id; +}; + +static const uint8_t start_sequence[] = { 0x00, 0x00, 0x00, 0x01 }; + +static av_cold PayloadContext *hevc_new_context(void) +{ + return av_mallocz(sizeof(PayloadContext)); +} + +static av_cold void hevc_free_context(PayloadContext *data) +{ + av_free(data); +} + +static av_cold int hevc_init(AVFormatContext *ctx, int st_index, + PayloadContext *data) +{ + av_dlog(ctx, "hevc_init() for stream %d\n", st_index); + + if (st_index < 0) + return 0; + + ctx->streams[st_index]->need_parsing = AVSTREAM_PARSE_FULL; + + return 0; +} + +static av_cold int hevc_sdp_parse_fmtp_config(AVFormatContext *s, + AVStream *stream, + PayloadContext *hevc_data, + char *attr, char *value) +{ + /* profile-space: 0-3 */ + /* profile-id: 0-31 */ + if (!strcmp(attr, "profile-id")) { + hevc_data->profile_id = atoi(value); + av_dlog(s, "SDP: found profile-id: %d\n", hevc_data->profile_id); + } + + /* tier-flag: 0-1 */ + /* level-id: 0-255 */ + /* interop-constraints: [base16] */ + /* profile-compatibility-indicator: [base16] */ + /* sprop-sub-layer-id: 0-6, defines highest possible value for TID, default: 6 */ + /* recv-sub-layer-id: 0-6 */ + /* max-recv-level-id: 0-255 */ + /* tx-mode: MSM,SSM */ + /* sprop-vps: [base64] */ + /* sprop-sps: [base64] */ + /* sprop-pps: [base64] */ + /* sprop-sei: [base64] */ + /* max-lsr, max-lps, max-cpb, max-dpb, max-br, max-tr, max-tc */ + /* max-fps */ + + /* sprop-max-don-diff: 0-32767 + + When the RTP stream depends on one or more other RTP + streams (in this case tx-mode MUST be equal to "MSM" and + MSM is in use), this parameter MUST be present and the + value MUST be greater than 0. + */ + if (!strcmp(attr, "sprop-max-don-diff")) { + if (atoi(value) > 0) + hevc_data->using_donl_field = 1; + av_dlog(s, "Found sprop-max-don-diff in SDP, DON field usage is: %d\n", + hevc_data->using_donl_field); + } + + /* sprop-depack-buf-nalus: 0-32767 */ + if (!strcmp(attr, "sprop-depack-buf-nalus")) { + if (atoi(value) > 0) + hevc_data->using_donl_field = 1; + av_dlog(s, "Found sprop-depack-buf-nalus in SDP, DON field usage is: %d\n", + hevc_data->using_donl_field); + } + + /* sprop-depack-buf-bytes: 0-4294967295 */ + /* depack-buf-cap */ + /* sprop-segmentation-id: 0-3 */ + /* sprop-spatial-segmentation-idc: [base16] */ + /* dec-parallel-ca: */ + /* include-dph */ + + return 0; +} + +static av_cold int hevc_parse_sdp_line(AVFormatContext *ctx, int st_index, + PayloadContext *hevc_data, const char *line) +{ + AVStream *current_stream; + AVCodecContext *codec; + const char *sdp_line_ptr = line; + + if (st_index < 0) + return 0; + + current_stream = ctx->streams[st_index]; + codec = current_stream->codec; + + if (av_strstart(sdp_line_ptr, "framesize:", &sdp_line_ptr)) { + char str_video_width[50]; + char *str_video_width_ptr = str_video_width; + + /* + * parse "a=framesize:96 320-240" + */ + + /* ignore spaces */ + while (*sdp_line_ptr && *sdp_line_ptr == ' ') + sdp_line_ptr++; + /* ignore RTP payload ID */ + while (*sdp_line_ptr && *sdp_line_ptr != ' ') + sdp_line_ptr++; + /* ignore spaces */ + while (*sdp_line_ptr && *sdp_line_ptr == ' ') + sdp_line_ptr++; + /* extract the actual video resolution description */ + while (*sdp_line_ptr && *sdp_line_ptr != '-' && + (str_video_width_ptr - str_video_width) < sizeof(str_video_width) - 1) + *str_video_width_ptr++ = *sdp_line_ptr++; + /* add trailing zero byte */ + *str_video_width_ptr = '\0'; + + /* determine the width value */ + codec->width = atoi(str_video_width); + /* jump beyond the "-" and determine the height value */ + codec->height = atoi(sdp_line_ptr + 1); + } else if (av_strstart(sdp_line_ptr, "fmtp:", &sdp_line_ptr)) { + return ff_parse_fmtp(ctx, current_stream, hevc_data, sdp_line_ptr, + hevc_sdp_parse_fmtp_config); + } + + return 0; +} + +static int hevc_handle_packet(AVFormatContext *ctx, PayloadContext *rtp_hevc_ctx, + AVStream *st, AVPacket *pkt, uint32_t *timestamp, + const uint8_t *buf, int len, uint16_t seq, + int flags) +{ + const uint8_t *rtp_pl = buf; + int tid, lid, nal_type; + int first_fragment, last_fragment, fu_type; + uint8_t new_nal_header[2]; + int res = 0; + + /* sanity check for size of input packet: 1 byte payload at least */ + if (len < RTP_HEVC_PAYLOAD_HEADER_SIZE + 1) { + av_log(ctx, AV_LOG_ERROR, "Too short RTP/HEVC packet, got %d bytes\n", len); + return AVERROR_INVALIDDATA; + } + + /* + decode the HEVC payload header according to section 4 of draft version 6: + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F| Type | LayerId | TID | + +-------------+-----------------+ + + Forbidden zero (F): 1 bit + NAL unit type (Type): 6 bits + NUH layer ID (LayerId): 6 bits + NUH temporal ID plus 1 (TID): 3 bits + */ + nal_type = (buf[0] >> 1) & 0x3f; + lid = ((buf[0] << 5) & 0x20) | ((buf[1] >> 3) & 0x1f); + tid = buf[1] & 0x07; + + /* sanity check for correct layer ID */ + if (lid) { + /* future scalable or 3D video coding extensions */ + avpriv_report_missing_feature(ctx, "Multi-layer HEVC coding\n"); + return AVERROR_PATCHWELCOME; + } + + /* sanity check for correct temporal ID */ + if (!tid) { + av_log(ctx, AV_LOG_ERROR, "Illegal temporal ID in RTP/HEVC packet\n"); + return AVERROR_INVALIDDATA; + } + + /* sanity check for correct NAL unit type */ + if (nal_type > 50) { + av_log(ctx, AV_LOG_ERROR, "Unsupported (HEVC) NAL type (%d)\n", nal_type); + return AVERROR_INVALIDDATA; + } + + switch (nal_type) { + /* aggregated packets (AP) */ + case 48: + /* pass the HEVC payload header */ + buf += RTP_HEVC_PAYLOAD_HEADER_SIZE; + len -= RTP_HEVC_PAYLOAD_HEADER_SIZE; + + /* pass the HEVC DONL field */ + if (rtp_hevc_ctx->using_donl_field) { + buf += RTP_HEVC_DONL_FIELD_SIZE; + len -= RTP_HEVC_DONL_FIELD_SIZE; + } + + /* fall-through */ + /* video parameter set (VPS) */ + case 32: + /* sequence parameter set (SPS) */ + case 33: + /* picture parameter set (PPS) */ + case 34: + /* supplemental enhancement information (SEI) */ + case 39: + /* single NAL unit packet */ + default: + /* sanity check for size of input packet: 1 byte payload at least */ + if (len < 1) { + av_log(ctx, AV_LOG_ERROR, + "Too short RTP/HEVC packet, got %d bytes of NAL unit type %d\n", + len, nal_type); + return AVERROR_INVALIDDATA; + } + + /* create A/V packet */ + if ((res = av_new_packet(pkt, sizeof(start_sequence) + len)) < 0) + return res; + /* A/V packet: copy start sequence */ + memcpy(pkt->data, start_sequence, sizeof(start_sequence)); + /* A/V packet: copy NAL unit data */ + memcpy(pkt->data + sizeof(start_sequence), buf, len); + + break; + /* fragmentation unit (FU) */ + case 49: + /* pass the HEVC payload header */ + buf += RTP_HEVC_PAYLOAD_HEADER_SIZE; + len -= RTP_HEVC_PAYLOAD_HEADER_SIZE; + + if (len < 1) + return AVERROR_INVALIDDATA; + /* + decode the FU header + + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |S|E| FuType | + +---------------+ + + Start fragment (S): 1 bit + End fragment (E): 1 bit + FuType: 6 bits + */ + first_fragment = buf[0] & 0x80; + last_fragment = buf[0] & 0x40; + fu_type = buf[0] & 0x3f; + + /* pass the HEVC FU header */ + buf += RTP_HEVC_FU_HEADER_SIZE; + len -= RTP_HEVC_FU_HEADER_SIZE; + + /* pass the HEVC DONL field */ + if (rtp_hevc_ctx->using_donl_field) { + buf += RTP_HEVC_DONL_FIELD_SIZE; + len -= RTP_HEVC_DONL_FIELD_SIZE; + } + + av_dlog(ctx, " FU type %d with %d bytes\n", fu_type, len); + + if (len > 0) { + new_nal_header[0] = (rtp_pl[0] & 0x81) | (fu_type << 1); + new_nal_header[1] = rtp_pl[1]; + + /* start fragment vs. subsequent fragments */ + if (first_fragment) { + if (!last_fragment) { + /* create A/V packet which is big enough */ + if ((res = av_new_packet(pkt, sizeof(start_sequence) + sizeof(new_nal_header) + len)) < 0) + return res; + /* A/V packet: copy start sequence */ + memcpy(pkt->data, start_sequence, sizeof(start_sequence)); + /* A/V packet: copy new NAL header */ + memcpy(pkt->data + sizeof(start_sequence), new_nal_header, sizeof(new_nal_header)); + /* A/V packet: copy NAL unit data */ + memcpy(pkt->data + sizeof(start_sequence) + sizeof(new_nal_header), buf, len); + } else { + av_log(ctx, AV_LOG_ERROR, "Illegal combination of S and E bit in RTP/HEVC packet\n"); + res = AVERROR_INVALIDDATA; + } + } else { + /* create A/V packet */ + if ((res = av_new_packet(pkt, len)) < 0) + return res; + /* A/V packet: copy NAL unit data */ + memcpy(pkt->data, buf, len); + } + } else { + /* sanity check for size of input packet: 1 byte payload at least */ + av_log(ctx, AV_LOG_ERROR, + "Too short RTP/HEVC packet, got %d bytes of NAL unit type %d\n", + len, nal_type); + res = AVERROR_INVALIDDATA; + } + + break; + /* PACI packet */ + case 50: + /* Temporal scalability control information (TSCI) */ + avpriv_report_missing_feature(ctx, "PACI packets for RTP/HEVC\n"); + res = AVERROR_PATCHWELCOME; + break; + } + + pkt->stream_index = st->index; + + return res; +} + +RTPDynamicProtocolHandler ff_hevc_dynamic_handler = { + .enc_name = "HEVC", + .codec_type = AVMEDIA_TYPE_VIDEO, + .codec_id = AV_CODEC_ID_HEVC, + .init = hevc_init, + .parse_sdp_a_line = hevc_parse_sdp_line, + .alloc = hevc_new_context, + .free = hevc_free_context, + .parse_packet = hevc_handle_packet +}; + +RTPDynamicProtocolHandler ff_h265_dynamic_handler = { + .enc_name = "H265", + .codec_type = AVMEDIA_TYPE_VIDEO, + .codec_id = AV_CODEC_ID_HEVC, + .init = hevc_init, + .parse_sdp_a_line = hevc_parse_sdp_line, + .alloc = hevc_new_context, + .free = hevc_free_context, + .parse_packet = hevc_handle_packet +}; diff --git a/libavformat/version.h b/libavformat/version.h index d4dee72222..4fef2b4ff0 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFORMAT_VERSION_MAJOR 56 -#define LIBAVFORMAT_VERSION_MINOR 3 +#define LIBAVFORMAT_VERSION_MINOR 4 #define LIBAVFORMAT_VERSION_MICRO 0 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \