|
@@ -0,0 +1,938 @@
|
|
|
+/******************************************************************************
|
|
|
+ Copyright (C) 2023 by Hugh Bailey <[email protected]>
|
|
|
+
|
|
|
+ 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, see <http://www.gnu.org/licenses/>.
|
|
|
+******************************************************************************/
|
|
|
+
|
|
|
+#include "rtmp-hevc.h"
|
|
|
+#include "utils.h"
|
|
|
+
|
|
|
+#include <obs.h>
|
|
|
+#include <obs-nal.h>
|
|
|
+#include <obs-hevc.h>
|
|
|
+#include <util/array-serializer.h>
|
|
|
+
|
|
|
+/* Adapted from FFmpeg's libavformat/hevc.c for our FLV muxer. */
|
|
|
+
|
|
|
+enum {
|
|
|
+ OBS_VPS_INDEX,
|
|
|
+ OBS_SPS_INDEX,
|
|
|
+ OBS_PPS_INDEX,
|
|
|
+ OBS_SEI_PREFIX_INDEX,
|
|
|
+ OBS_SEI_SUFFIX_INDEX,
|
|
|
+ OBS_NB_ARRAYS
|
|
|
+};
|
|
|
+
|
|
|
+enum {
|
|
|
+ OBS_HEVC_MAX_VPS_COUNT = 16,
|
|
|
+ OBS_HEVC_MAX_SPS_COUNT = 16,
|
|
|
+ OBS_HEVC_MAX_PPS_COUNT = 64,
|
|
|
+};
|
|
|
+
|
|
|
+typedef struct HVCCNALUnitArray {
|
|
|
+ uint8_t array_completeness;
|
|
|
+ uint8_t NAL_unit_type;
|
|
|
+ uint16_t numNalus;
|
|
|
+ struct array_output_data nalUnitData;
|
|
|
+ struct serializer nalUnit;
|
|
|
+} HVCCNALUnitArray;
|
|
|
+
|
|
|
+typedef struct HEVCDecoderConfigurationRecord {
|
|
|
+ uint8_t general_profile_space;
|
|
|
+ uint8_t general_tier_flag;
|
|
|
+ uint8_t general_profile_idc;
|
|
|
+ uint32_t general_profile_compatibility_flags;
|
|
|
+ uint64_t general_constraint_indicator_flags;
|
|
|
+ uint8_t general_level_idc;
|
|
|
+ uint16_t min_spatial_segmentation_idc;
|
|
|
+ uint8_t parallelismType;
|
|
|
+ uint8_t chromaFormat;
|
|
|
+ uint8_t bitDepthLumaMinus8;
|
|
|
+ uint8_t bitDepthChromaMinus8;
|
|
|
+ uint16_t avgFrameRate;
|
|
|
+ uint8_t constantFrameRate;
|
|
|
+ uint8_t numTemporalLayers;
|
|
|
+ uint8_t temporalIdNested;
|
|
|
+ uint8_t lengthSizeMinusOne;
|
|
|
+ uint8_t numOfArrays;
|
|
|
+ HVCCNALUnitArray arrays[OBS_NB_ARRAYS];
|
|
|
+} HEVCDecoderConfigurationRecord;
|
|
|
+
|
|
|
+typedef struct HVCCProfileTierLevel {
|
|
|
+ uint8_t profile_space;
|
|
|
+ uint8_t tier_flag;
|
|
|
+ uint8_t profile_idc;
|
|
|
+ uint32_t profile_compatibility_flags;
|
|
|
+ uint64_t constraint_indicator_flags;
|
|
|
+ uint8_t level_idc;
|
|
|
+} HVCCProfileTierLevel;
|
|
|
+
|
|
|
+typedef struct HevcGetBitContext {
|
|
|
+ const uint8_t *buffer, *buffer_end;
|
|
|
+ uint64_t cache;
|
|
|
+ unsigned bits_left;
|
|
|
+ int index;
|
|
|
+ int size_in_bits;
|
|
|
+ int size_in_bits_plus8;
|
|
|
+} HevcGetBitContext;
|
|
|
+
|
|
|
+static inline uint32_t rb32(const uint8_t *ptr)
|
|
|
+{
|
|
|
+ return (ptr[0] << 24) + (ptr[1] << 16) + (ptr[2] << 8) + ptr[3];
|
|
|
+}
|
|
|
+
|
|
|
+static inline void refill_32(HevcGetBitContext *s)
|
|
|
+{
|
|
|
+ s->cache = s->cache | (uint64_t)rb32(s->buffer + (s->index >> 3))
|
|
|
+ << (32 - s->bits_left);
|
|
|
+ s->index += 32;
|
|
|
+ s->bits_left += 32;
|
|
|
+}
|
|
|
+
|
|
|
+static inline uint64_t get_val(HevcGetBitContext *s, unsigned n)
|
|
|
+{
|
|
|
+ uint64_t ret;
|
|
|
+ ret = s->cache >> (64 - n);
|
|
|
+ s->cache <<= n;
|
|
|
+ s->bits_left -= n;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static inline int init_get_bits_xe(HevcGetBitContext *s, const uint8_t *buffer,
|
|
|
+ int bit_size)
|
|
|
+{
|
|
|
+ int buffer_size;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (bit_size >= INT_MAX - 64 * 8 || bit_size < 0 || !buffer) {
|
|
|
+ bit_size = 0;
|
|
|
+ buffer = NULL;
|
|
|
+ ret = -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ buffer_size = (bit_size + 7) >> 3;
|
|
|
+
|
|
|
+ s->buffer = buffer;
|
|
|
+ s->size_in_bits = bit_size;
|
|
|
+ s->size_in_bits_plus8 = bit_size + 8;
|
|
|
+ s->buffer_end = buffer + buffer_size;
|
|
|
+ s->index = 0;
|
|
|
+
|
|
|
+ s->cache = 0;
|
|
|
+ s->bits_left = 0;
|
|
|
+ refill_32(s);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static inline int init_get_bits(HevcGetBitContext *s, const uint8_t *buffer,
|
|
|
+ int bit_size)
|
|
|
+{
|
|
|
+ return init_get_bits_xe(s, buffer, bit_size);
|
|
|
+}
|
|
|
+
|
|
|
+static inline int init_get_bits8(HevcGetBitContext *s, const uint8_t *buffer,
|
|
|
+ int byte_size)
|
|
|
+{
|
|
|
+ if (byte_size > INT_MAX / 8 || byte_size < 0)
|
|
|
+ byte_size = -1;
|
|
|
+ return init_get_bits(s, buffer, byte_size * 8);
|
|
|
+}
|
|
|
+
|
|
|
+static inline unsigned int get_bits(HevcGetBitContext *s, unsigned int n)
|
|
|
+{
|
|
|
+ register unsigned int tmp;
|
|
|
+
|
|
|
+ if (n > s->bits_left) {
|
|
|
+ refill_32(s);
|
|
|
+ }
|
|
|
+ tmp = (unsigned int)get_val(s, n);
|
|
|
+ return tmp;
|
|
|
+}
|
|
|
+
|
|
|
+#define skip_bits get_bits
|
|
|
+
|
|
|
+static inline int get_bits_count(HevcGetBitContext *s)
|
|
|
+{
|
|
|
+ return s->index - s->bits_left;
|
|
|
+}
|
|
|
+
|
|
|
+static inline int get_bits_left(HevcGetBitContext *gb)
|
|
|
+{
|
|
|
+ return gb->size_in_bits - get_bits_count(gb);
|
|
|
+}
|
|
|
+
|
|
|
+static inline unsigned int get_bits_long(HevcGetBitContext *s, int n)
|
|
|
+{
|
|
|
+ if (!n)
|
|
|
+ return 0;
|
|
|
+ return get_bits(s, n);
|
|
|
+}
|
|
|
+
|
|
|
+#define skip_bits_long get_bits_long
|
|
|
+
|
|
|
+static inline uint64_t get_bits64(HevcGetBitContext *s, int n)
|
|
|
+{
|
|
|
+ if (n <= 32) {
|
|
|
+ return get_bits_long(s, n);
|
|
|
+ } else {
|
|
|
+ uint64_t ret = (uint64_t)get_bits_long(s, n - 32) << 32;
|
|
|
+ return ret | get_bits_long(s, 32);
|
|
|
+ }
|
|
|
+}
|
|
|
+static inline int ilog2(unsigned x)
|
|
|
+{
|
|
|
+ return (31 - clz32(x | 1));
|
|
|
+}
|
|
|
+static inline unsigned show_val(const HevcGetBitContext *s, unsigned n)
|
|
|
+{
|
|
|
+ return s->cache & ((UINT64_C(1) << n) - 1);
|
|
|
+}
|
|
|
+static inline unsigned int show_bits(HevcGetBitContext *s, unsigned int n)
|
|
|
+{
|
|
|
+ register unsigned int tmp;
|
|
|
+ if (n > s->bits_left)
|
|
|
+ refill_32(s);
|
|
|
+ tmp = show_val(s, n);
|
|
|
+ return tmp;
|
|
|
+}
|
|
|
+static inline unsigned int show_bits_long(HevcGetBitContext *s, int n)
|
|
|
+{
|
|
|
+ if (n <= 25) {
|
|
|
+ return show_bits(s, n);
|
|
|
+ } else {
|
|
|
+ HevcGetBitContext gb = *s;
|
|
|
+ return get_bits_long(&gb, n);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static inline unsigned get_ue_golomb_long(HevcGetBitContext *gb)
|
|
|
+{
|
|
|
+ unsigned buf, log;
|
|
|
+
|
|
|
+ buf = show_bits_long(gb, 32);
|
|
|
+ log = 31 - ilog2(buf);
|
|
|
+ skip_bits_long(gb, log);
|
|
|
+
|
|
|
+ return get_bits_long(gb, log + 1) - 1;
|
|
|
+}
|
|
|
+static inline int get_se_golomb_long(HevcGetBitContext *gb)
|
|
|
+{
|
|
|
+ unsigned int buf = get_ue_golomb_long(gb);
|
|
|
+ int sign = (buf & 1) - 1;
|
|
|
+ return ((buf >> 1) ^ sign) + 1;
|
|
|
+}
|
|
|
+
|
|
|
+static inline bool has_start_code(const uint8_t *data, size_t size)
|
|
|
+{
|
|
|
+ if (size > 3 && data[0] == 0 && data[1] == 0 && data[2] == 1)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ if (size > 4 && data[0] == 0 && data[1] == 0 && data[2] == 0 &&
|
|
|
+ data[3] == 1)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+uint8_t *ff_nal_unit_extract_rbsp(uint8_t *dst, const uint8_t *src, int src_len,
|
|
|
+ uint32_t *dst_len, int header_len)
|
|
|
+{
|
|
|
+ int i, len;
|
|
|
+
|
|
|
+ /* NAL unit header */
|
|
|
+ i = len = 0;
|
|
|
+ while (i < header_len && i < src_len)
|
|
|
+ dst[len++] = src[i++];
|
|
|
+
|
|
|
+ while (i + 2 < src_len)
|
|
|
+ if (!src[i] && !src[i + 1] && src[i + 2] == 3) {
|
|
|
+ dst[len++] = src[i++];
|
|
|
+ dst[len++] = src[i++];
|
|
|
+ i++; // remove emulation_prevention_three_byte
|
|
|
+ } else
|
|
|
+ dst[len++] = src[i++];
|
|
|
+
|
|
|
+ while (i < src_len)
|
|
|
+ dst[len++] = src[i++];
|
|
|
+
|
|
|
+ memset(dst + len, 0, 64);
|
|
|
+
|
|
|
+ *dst_len = (uint32_t)len;
|
|
|
+ return dst;
|
|
|
+}
|
|
|
+
|
|
|
+static int hvcc_array_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size,
|
|
|
+ uint8_t nal_type, int ps_array_completeness,
|
|
|
+ HVCCNALUnitArray *array)
|
|
|
+{
|
|
|
+ s_wb16(&array->nalUnit, nal_size);
|
|
|
+ s_write(&array->nalUnit, nal_buf, nal_size);
|
|
|
+ array->NAL_unit_type = nal_type;
|
|
|
+ array->numNalus++;
|
|
|
+
|
|
|
+ if (nal_type == OBS_HEVC_NAL_VPS || nal_type == OBS_HEVC_NAL_SPS ||
|
|
|
+ nal_type == OBS_HEVC_NAL_PPS)
|
|
|
+ array->array_completeness = ps_array_completeness;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void nal_unit_parse_header(HevcGetBitContext *gb, uint8_t *nal_type)
|
|
|
+{
|
|
|
+ skip_bits(gb, 1); // forbidden_zero_bit
|
|
|
+ *nal_type = get_bits(gb, 6);
|
|
|
+ get_bits(gb, 9);
|
|
|
+}
|
|
|
+
|
|
|
+static void hvcc_update_ptl(HEVCDecoderConfigurationRecord *hvcc,
|
|
|
+ HVCCProfileTierLevel *ptl)
|
|
|
+{
|
|
|
+ hvcc->general_profile_space = ptl->profile_space;
|
|
|
+ if (hvcc->general_tier_flag < ptl->tier_flag)
|
|
|
+ hvcc->general_level_idc = ptl->level_idc;
|
|
|
+ else
|
|
|
+ hvcc->general_level_idc =
|
|
|
+ max_u8(hvcc->general_level_idc, ptl->level_idc);
|
|
|
+ hvcc->general_tier_flag =
|
|
|
+ max_u8(hvcc->general_tier_flag, ptl->tier_flag);
|
|
|
+ hvcc->general_profile_idc =
|
|
|
+ max_u8(hvcc->general_profile_idc, ptl->profile_idc);
|
|
|
+ hvcc->general_profile_compatibility_flags &=
|
|
|
+ ptl->profile_compatibility_flags;
|
|
|
+ hvcc->general_constraint_indicator_flags &=
|
|
|
+ ptl->constraint_indicator_flags;
|
|
|
+}
|
|
|
+
|
|
|
+static void hvcc_parse_ptl(HevcGetBitContext *gb,
|
|
|
+ HEVCDecoderConfigurationRecord *hvcc,
|
|
|
+ unsigned int max_sub_layers_minus1)
|
|
|
+{
|
|
|
+ unsigned int i;
|
|
|
+ HVCCProfileTierLevel general_ptl;
|
|
|
+ uint8_t sub_layer_profile_present_flag[7]; // max sublayers
|
|
|
+ uint8_t sub_layer_level_present_flag[7]; // max sublayers
|
|
|
+
|
|
|
+ general_ptl.profile_space = get_bits(gb, 2);
|
|
|
+ general_ptl.tier_flag = get_bits(gb, 1);
|
|
|
+ general_ptl.profile_idc = get_bits(gb, 5);
|
|
|
+ general_ptl.profile_compatibility_flags = get_bits_long(gb, 32);
|
|
|
+ general_ptl.constraint_indicator_flags = get_bits64(gb, 48);
|
|
|
+ general_ptl.level_idc = get_bits(gb, 8);
|
|
|
+ hvcc_update_ptl(hvcc, &general_ptl);
|
|
|
+
|
|
|
+ for (i = 0; i < max_sub_layers_minus1; i++) {
|
|
|
+ sub_layer_profile_present_flag[i] = get_bits(gb, 1);
|
|
|
+ sub_layer_level_present_flag[i] = get_bits(gb, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // skip the rest
|
|
|
+ if (max_sub_layers_minus1 > 0)
|
|
|
+ for (i = max_sub_layers_minus1; i < 8; i++)
|
|
|
+ skip_bits(gb, 2);
|
|
|
+
|
|
|
+ for (i = 0; i < max_sub_layers_minus1; i++) {
|
|
|
+ if (sub_layer_profile_present_flag[i]) {
|
|
|
+ skip_bits_long(gb, 32);
|
|
|
+ skip_bits_long(gb, 32);
|
|
|
+ skip_bits(gb, 24);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sub_layer_level_present_flag[i])
|
|
|
+ skip_bits(gb, 8);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int hvcc_parse_vps(HevcGetBitContext *gb,
|
|
|
+ HEVCDecoderConfigurationRecord *hvcc)
|
|
|
+{
|
|
|
+ unsigned int vps_max_sub_layers_minus1;
|
|
|
+ skip_bits(gb, 12);
|
|
|
+ vps_max_sub_layers_minus1 = get_bits(gb, 3);
|
|
|
+ hvcc->numTemporalLayers =
|
|
|
+ max_u8(hvcc->numTemporalLayers, vps_max_sub_layers_minus1 + 1);
|
|
|
+ skip_bits(gb, 17);
|
|
|
+ hvcc_parse_ptl(gb, hvcc, vps_max_sub_layers_minus1);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+#define HEVC_MAX_SHORT_TERM_REF_PIC_SETS 64
|
|
|
+
|
|
|
+static void skip_scaling_list_data(HevcGetBitContext *gb)
|
|
|
+{
|
|
|
+ int i, j, k, num_coeffs;
|
|
|
+
|
|
|
+ for (i = 0; i < 4; i++) {
|
|
|
+ for (j = 0; j < (i == 3 ? 2 : 6); j++) {
|
|
|
+ if (!get_bits(gb, 1))
|
|
|
+ get_ue_golomb_long(gb);
|
|
|
+ else {
|
|
|
+ num_coeffs = min_i32(64, 1 << (4 + (i << 1)));
|
|
|
+
|
|
|
+ if (i > 1)
|
|
|
+ get_se_golomb_long(gb);
|
|
|
+ for (k = 0; k < num_coeffs; k++)
|
|
|
+ get_se_golomb_long(gb);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+parse_rps(HevcGetBitContext *gb, unsigned int rps_idx, unsigned int num_rps,
|
|
|
+ unsigned int num_delta_pocs[HEVC_MAX_SHORT_TERM_REF_PIC_SETS])
|
|
|
+{
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ if (rps_idx && get_bits(gb, 1)) { // inter_ref_pic_set_prediction_flag
|
|
|
+ /* this should only happen for slice headers, and this isn't one */
|
|
|
+ if (rps_idx >= num_rps)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ get_bits(gb, 1); // delta_rps_sign
|
|
|
+ get_ue_golomb_long(gb); // abs_delta_rps_minus1
|
|
|
+
|
|
|
+ num_delta_pocs[rps_idx] = 0;
|
|
|
+
|
|
|
+ for (i = 0; i <= num_delta_pocs[rps_idx - 1]; i++) {
|
|
|
+ uint8_t use_delta_flag = 0;
|
|
|
+ uint8_t used_by_curr_pic_flag = get_bits(gb, 1);
|
|
|
+ if (!used_by_curr_pic_flag)
|
|
|
+ use_delta_flag = get_bits(gb, 1);
|
|
|
+
|
|
|
+ if (used_by_curr_pic_flag || use_delta_flag)
|
|
|
+ num_delta_pocs[rps_idx]++;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ unsigned int num_negative_pics = get_ue_golomb_long(gb);
|
|
|
+ unsigned int num_positive_pics = get_ue_golomb_long(gb);
|
|
|
+
|
|
|
+ if ((num_positive_pics + (uint64_t)num_negative_pics) * 2 >
|
|
|
+ (uint64_t)get_bits_left(gb))
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ num_delta_pocs[rps_idx] = num_negative_pics + num_positive_pics;
|
|
|
+
|
|
|
+ for (i = 0; i < num_negative_pics; i++) {
|
|
|
+ get_ue_golomb_long(gb); // delta_poc_s0_minus1[rps_idx]
|
|
|
+ get_bits(gb, 1); // used_by_curr_pic_s0_flag[rps_idx]
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < num_positive_pics; i++) {
|
|
|
+ get_ue_golomb_long(gb); // delta_poc_s1_minus1[rps_idx]
|
|
|
+ get_bits(gb, 1); // used_by_curr_pic_s1_flag[rps_idx]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+skip_sub_layer_hrd_parameters(HevcGetBitContext *gb,
|
|
|
+ unsigned int cpb_cnt_minus1,
|
|
|
+ uint8_t sub_pic_hrd_params_present_flag)
|
|
|
+{
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ for (i = 0; i <= cpb_cnt_minus1; i++) {
|
|
|
+ get_ue_golomb_long(gb); // bit_rate_value_minus1
|
|
|
+ get_ue_golomb_long(gb); // cpb_size_value_minus1
|
|
|
+
|
|
|
+ if (sub_pic_hrd_params_present_flag) {
|
|
|
+ get_ue_golomb_long(gb); // cpb_size_du_value_minus1
|
|
|
+ get_ue_golomb_long(gb); // bit_rate_du_value_minus1
|
|
|
+ }
|
|
|
+
|
|
|
+ get_bits(gb, 1); // cbr_flag
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int skip_hrd_parameters(HevcGetBitContext *gb,
|
|
|
+ uint8_t cprms_present_flag,
|
|
|
+ unsigned int max_sub_layers_minus1)
|
|
|
+{
|
|
|
+ unsigned int i;
|
|
|
+ uint8_t sub_pic_hrd_params_present_flag = 0;
|
|
|
+ uint8_t nal_hrd_parameters_present_flag = 0;
|
|
|
+ uint8_t vcl_hrd_parameters_present_flag = 0;
|
|
|
+
|
|
|
+ if (cprms_present_flag) {
|
|
|
+ nal_hrd_parameters_present_flag = get_bits(gb, 1);
|
|
|
+ vcl_hrd_parameters_present_flag = get_bits(gb, 1);
|
|
|
+
|
|
|
+ if (nal_hrd_parameters_present_flag ||
|
|
|
+ vcl_hrd_parameters_present_flag) {
|
|
|
+ sub_pic_hrd_params_present_flag = get_bits(gb, 1);
|
|
|
+
|
|
|
+ if (sub_pic_hrd_params_present_flag)
|
|
|
+ get_bits(gb, 19);
|
|
|
+
|
|
|
+ get_bits(gb, 8);
|
|
|
+
|
|
|
+ if (sub_pic_hrd_params_present_flag)
|
|
|
+ get_bits(gb, 4);
|
|
|
+ get_bits(gb, 15);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i <= max_sub_layers_minus1; i++) {
|
|
|
+ unsigned int cpb_cnt_minus1 = 0;
|
|
|
+ uint8_t low_delay_hrd_flag = 0;
|
|
|
+ uint8_t fixed_pic_rate_within_cvs_flag = 0;
|
|
|
+ uint8_t fixed_pic_rate_general_flag = get_bits(gb, 1);
|
|
|
+
|
|
|
+ if (!fixed_pic_rate_general_flag)
|
|
|
+ fixed_pic_rate_within_cvs_flag = get_bits(gb, 1);
|
|
|
+
|
|
|
+ if (fixed_pic_rate_within_cvs_flag)
|
|
|
+ get_ue_golomb_long(gb);
|
|
|
+ else
|
|
|
+ low_delay_hrd_flag = get_bits(gb, 1);
|
|
|
+
|
|
|
+ if (!low_delay_hrd_flag) {
|
|
|
+ cpb_cnt_minus1 = get_ue_golomb_long(gb);
|
|
|
+ if (cpb_cnt_minus1 > 31)
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nal_hrd_parameters_present_flag)
|
|
|
+ skip_sub_layer_hrd_parameters(
|
|
|
+ gb, cpb_cnt_minus1,
|
|
|
+ sub_pic_hrd_params_present_flag);
|
|
|
+
|
|
|
+ if (vcl_hrd_parameters_present_flag)
|
|
|
+ skip_sub_layer_hrd_parameters(
|
|
|
+ gb, cpb_cnt_minus1,
|
|
|
+ sub_pic_hrd_params_present_flag);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void hvcc_parse_vui(HevcGetBitContext *gb,
|
|
|
+ HEVCDecoderConfigurationRecord *hvcc,
|
|
|
+ unsigned int max_sub_layers_minus1)
|
|
|
+{
|
|
|
+ unsigned int min_spatial_segmentation_idc;
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) // aspect_ratio_info_present_flag
|
|
|
+ if (get_bits(gb, 8) == 255) // aspect_ratio_idc
|
|
|
+ get_bits_long(gb,
|
|
|
+ 32); // sar_width u(16), sar_height u(16)
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) // overscan_info_present_flag
|
|
|
+ get_bits(gb, 1); // overscan_appropriate_flag
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) { // video_signal_type_present_flag
|
|
|
+ get_bits(gb,
|
|
|
+ 4); // video_format u(3), video_full_range_flag u(1)
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) // colour_description_present_flag
|
|
|
+ get_bits(gb, 24);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) {
|
|
|
+ get_ue_golomb_long(gb);
|
|
|
+ get_ue_golomb_long(gb);
|
|
|
+ }
|
|
|
+
|
|
|
+ get_bits(gb, 3);
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) { // default_display_window_flag
|
|
|
+ get_ue_golomb_long(gb); // def_disp_win_left_offset
|
|
|
+ get_ue_golomb_long(gb); // def_disp_win_right_offset
|
|
|
+ get_ue_golomb_long(gb); // def_disp_win_top_offset
|
|
|
+ get_ue_golomb_long(gb); // def_disp_win_bottom_offset
|
|
|
+ }
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) { // vui_timing_info_present_flag
|
|
|
+ // skip timing info
|
|
|
+ get_bits_long(gb, 32); // num_units_in_tick
|
|
|
+ get_bits_long(gb, 32); // time_scale
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) // poc_proportional_to_timing_flag
|
|
|
+ get_ue_golomb_long(gb); // num_ticks_poc_diff_one_minus1
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) // vui_hrd_parameters_present_flag
|
|
|
+ skip_hrd_parameters(gb, 1, max_sub_layers_minus1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) { // bitstream_restriction_flag
|
|
|
+ get_bits(gb, 3);
|
|
|
+
|
|
|
+ min_spatial_segmentation_idc = get_ue_golomb_long(gb);
|
|
|
+ hvcc->min_spatial_segmentation_idc =
|
|
|
+ min_u16(hvcc->min_spatial_segmentation_idc,
|
|
|
+ min_spatial_segmentation_idc);
|
|
|
+
|
|
|
+ get_ue_golomb_long(gb); // max_bytes_per_pic_denom
|
|
|
+ get_ue_golomb_long(gb); // max_bits_per_min_cu_denom
|
|
|
+ get_ue_golomb_long(gb); // log2_max_mv_length_horizontal
|
|
|
+ get_ue_golomb_long(gb); // log2_max_mv_length_vertical
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int hvcc_parse_sps(HevcGetBitContext *gb,
|
|
|
+ HEVCDecoderConfigurationRecord *hvcc)
|
|
|
+{
|
|
|
+ unsigned int i, sps_max_sub_layers_minus1,
|
|
|
+ log2_max_pic_order_cnt_lsb_minus4;
|
|
|
+ unsigned int num_short_term_ref_pic_sets,
|
|
|
+ num_delta_pocs[HEVC_MAX_SHORT_TERM_REF_PIC_SETS];
|
|
|
+
|
|
|
+ get_bits(gb, 4); // sps_video_parameter_set_id
|
|
|
+ sps_max_sub_layers_minus1 = get_bits(gb, 3);
|
|
|
+
|
|
|
+ hvcc->numTemporalLayers =
|
|
|
+ max_u8(hvcc->numTemporalLayers, sps_max_sub_layers_minus1 + 1);
|
|
|
+
|
|
|
+ hvcc->temporalIdNested = get_bits(gb, 1);
|
|
|
+
|
|
|
+ hvcc_parse_ptl(gb, hvcc, sps_max_sub_layers_minus1);
|
|
|
+
|
|
|
+ get_ue_golomb_long(gb); // sps_seq_parameter_set_id
|
|
|
+
|
|
|
+ hvcc->chromaFormat = get_ue_golomb_long(gb);
|
|
|
+
|
|
|
+ if (hvcc->chromaFormat == 3)
|
|
|
+ get_bits(gb, 1); // separate_colour_plane_flag
|
|
|
+
|
|
|
+ get_ue_golomb_long(gb); // pic_width_in_luma_samples
|
|
|
+ get_ue_golomb_long(gb); // pic_height_in_luma_samples
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) { // conformance_window_flag
|
|
|
+ get_ue_golomb_long(gb); // conf_win_left_offset
|
|
|
+ get_ue_golomb_long(gb); // conf_win_right_offset
|
|
|
+ get_ue_golomb_long(gb); // conf_win_top_offset
|
|
|
+ get_ue_golomb_long(gb); // conf_win_bottom_offset
|
|
|
+ }
|
|
|
+
|
|
|
+ hvcc->bitDepthLumaMinus8 = get_ue_golomb_long(gb);
|
|
|
+ hvcc->bitDepthChromaMinus8 = get_ue_golomb_long(gb);
|
|
|
+ log2_max_pic_order_cnt_lsb_minus4 = get_ue_golomb_long(gb);
|
|
|
+
|
|
|
+ /* sps_sub_layer_ordering_info_present_flag */
|
|
|
+ i = get_bits(gb, 1) ? 0 : sps_max_sub_layers_minus1;
|
|
|
+ for (; i <= sps_max_sub_layers_minus1; i++) {
|
|
|
+ get_ue_golomb_long(gb); // max_dec_pic_buffering_minus1
|
|
|
+ get_ue_golomb_long(gb); // max_num_reorder_pics
|
|
|
+ get_ue_golomb_long(gb); // max_latency_increase_plus1
|
|
|
+ }
|
|
|
+
|
|
|
+ get_ue_golomb_long(gb); // log2_min_luma_coding_block_size_minus3
|
|
|
+ get_ue_golomb_long(gb); // log2_diff_max_min_luma_coding_block_size
|
|
|
+ get_ue_golomb_long(gb); // log2_min_transform_block_size_minus2
|
|
|
+ get_ue_golomb_long(gb); // log2_diff_max_min_transform_block_size
|
|
|
+ get_ue_golomb_long(gb); // max_transform_hierarchy_depth_inter
|
|
|
+ get_ue_golomb_long(gb); // max_transform_hierarchy_depth_intra
|
|
|
+
|
|
|
+ if (get_bits(gb, 1) && // scaling_list_enabled_flag
|
|
|
+ get_bits(gb, 1)) // sps_scaling_list_data_present_flag
|
|
|
+ skip_scaling_list_data(gb);
|
|
|
+
|
|
|
+ get_bits(gb, 1); // amp_enabled_flag
|
|
|
+ get_bits(gb, 1); // sample_adaptive_offset_enabled_flag
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) { // pcm_enabled_flag
|
|
|
+ get_bits(gb, 4); // pcm_sample_bit_depth_luma_minus1
|
|
|
+ get_bits(gb, 4); // pcm_sample_bit_depth_chroma_minus1
|
|
|
+ get_ue_golomb_long(
|
|
|
+ gb); // log2_min_pcm_luma_coding_block_size_minus3
|
|
|
+ get_ue_golomb_long(
|
|
|
+ gb); // log2_diff_max_min_pcm_luma_coding_block_size
|
|
|
+ get_bits(gb, 1); // pcm_loop_filter_disabled_flag
|
|
|
+ }
|
|
|
+
|
|
|
+ num_short_term_ref_pic_sets = get_ue_golomb_long(gb);
|
|
|
+ if (num_short_term_ref_pic_sets > HEVC_MAX_SHORT_TERM_REF_PIC_SETS)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ for (i = 0; i < num_short_term_ref_pic_sets; i++) {
|
|
|
+ int ret = parse_rps(gb, i, num_short_term_ref_pic_sets,
|
|
|
+ num_delta_pocs);
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) { // long_term_ref_pics_present_flag
|
|
|
+ unsigned num_long_term_ref_pics_sps = get_ue_golomb_long(gb);
|
|
|
+ if (num_long_term_ref_pics_sps > 31U)
|
|
|
+ return -1;
|
|
|
+ for (i = 0; i < num_long_term_ref_pics_sps;
|
|
|
+ i++) { // num_long_term_ref_pics_sps
|
|
|
+ int len = min_i32(log2_max_pic_order_cnt_lsb_minus4 + 4,
|
|
|
+ 16);
|
|
|
+ get_bits(gb, len); // lt_ref_pic_poc_lsb_sps[i]
|
|
|
+ get_bits(gb, 1); // used_by_curr_pic_lt_sps_flag[i]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ get_bits(gb, 1); // sps_temporal_mvp_enabled_flag
|
|
|
+ get_bits(gb, 1); // strong_intra_smoothing_enabled_flag
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) // vui_parameters_present_flag
|
|
|
+ hvcc_parse_vui(gb, hvcc, sps_max_sub_layers_minus1);
|
|
|
+
|
|
|
+ /* nothing useful for hvcC past this point */
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int hvcc_parse_pps(HevcGetBitContext *gb,
|
|
|
+ HEVCDecoderConfigurationRecord *hvcc)
|
|
|
+{
|
|
|
+ uint8_t tiles_enabled_flag, entropy_coding_sync_enabled_flag;
|
|
|
+
|
|
|
+ get_ue_golomb_long(gb); // pps_pic_parameter_set_id
|
|
|
+ get_ue_golomb_long(gb); // pps_seq_parameter_set_id
|
|
|
+
|
|
|
+ get_bits(gb, 7);
|
|
|
+
|
|
|
+ get_ue_golomb_long(gb); // num_ref_idx_l0_default_active_minus1
|
|
|
+ get_ue_golomb_long(gb); // num_ref_idx_l1_default_active_minus1
|
|
|
+ get_se_golomb_long(gb); // init_qp_minus26
|
|
|
+
|
|
|
+ get_bits(gb, 2);
|
|
|
+
|
|
|
+ if (get_bits(gb, 1)) // cu_qp_delta_enabled_flag
|
|
|
+ get_ue_golomb_long(gb); // diff_cu_qp_delta_depth
|
|
|
+
|
|
|
+ get_se_golomb_long(gb); // pps_cb_qp_offset
|
|
|
+ get_se_golomb_long(gb); // pps_cr_qp_offset
|
|
|
+
|
|
|
+ get_bits(gb, 4);
|
|
|
+
|
|
|
+ tiles_enabled_flag = get_bits(gb, 1);
|
|
|
+ entropy_coding_sync_enabled_flag = get_bits(gb, 1);
|
|
|
+
|
|
|
+ if (entropy_coding_sync_enabled_flag && tiles_enabled_flag)
|
|
|
+ hvcc->parallelismType = 0; // mixed-type parallel decoding
|
|
|
+ else if (entropy_coding_sync_enabled_flag)
|
|
|
+ hvcc->parallelismType = 3; // wavefront-based parallel decoding
|
|
|
+ else if (tiles_enabled_flag)
|
|
|
+ hvcc->parallelismType = 2; // tile-based parallel decoding
|
|
|
+ else
|
|
|
+ hvcc->parallelismType = 1; // slice-based parallel decoding
|
|
|
+
|
|
|
+ /* nothing useful for hvcC past this point */
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int hvcc_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size,
|
|
|
+ int ps_array_completeness,
|
|
|
+ HEVCDecoderConfigurationRecord *hvcc,
|
|
|
+ unsigned array_idx)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ HevcGetBitContext gbc;
|
|
|
+ uint8_t nal_type;
|
|
|
+ uint8_t *rbsp_buf;
|
|
|
+ uint32_t rbsp_size;
|
|
|
+
|
|
|
+ uint8_t *dst;
|
|
|
+ dst = bmalloc(nal_size + 64);
|
|
|
+
|
|
|
+ rbsp_buf =
|
|
|
+ ff_nal_unit_extract_rbsp(dst, nal_buf, nal_size, &rbsp_size, 2);
|
|
|
+ if (!rbsp_buf) {
|
|
|
+ ret = -1;
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = init_get_bits8(&gbc, rbsp_buf, rbsp_size);
|
|
|
+ if (ret < 0)
|
|
|
+ goto end;
|
|
|
+
|
|
|
+ nal_unit_parse_header(&gbc, &nal_type);
|
|
|
+
|
|
|
+ ret = hvcc_array_add_nal_unit(nal_buf, nal_size, nal_type,
|
|
|
+ ps_array_completeness,
|
|
|
+ &hvcc->arrays[array_idx]);
|
|
|
+ if (ret < 0)
|
|
|
+ goto end;
|
|
|
+ if (hvcc->arrays[array_idx].numNalus == 1)
|
|
|
+ hvcc->numOfArrays++;
|
|
|
+ if (nal_type == OBS_HEVC_NAL_VPS)
|
|
|
+ ret = hvcc_parse_vps(&gbc, hvcc);
|
|
|
+ else if (nal_type == OBS_HEVC_NAL_SPS)
|
|
|
+ ret = hvcc_parse_sps(&gbc, hvcc);
|
|
|
+ else if (nal_type == OBS_HEVC_NAL_PPS)
|
|
|
+ ret = hvcc_parse_pps(&gbc, hvcc);
|
|
|
+ if (ret < 0)
|
|
|
+ goto end;
|
|
|
+
|
|
|
+end:
|
|
|
+ bfree(dst);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+size_t obs_parse_hevc_header(uint8_t **header, const uint8_t *data, size_t size)
|
|
|
+{
|
|
|
+ const uint8_t *start;
|
|
|
+ const uint8_t *end;
|
|
|
+
|
|
|
+ if (!has_start_code(data, size)) {
|
|
|
+ *header = bmemdup(data, size);
|
|
|
+ return size;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (size < 6)
|
|
|
+ return 0; // invalid
|
|
|
+ if (*data == 1) { // already hvcC-formatted
|
|
|
+ *header = bmemdup(data, size);
|
|
|
+ return size;
|
|
|
+ }
|
|
|
+
|
|
|
+ struct array_output_data nals;
|
|
|
+ struct serializer sn;
|
|
|
+ array_output_serializer_init(&sn, &nals);
|
|
|
+
|
|
|
+ const uint8_t *nal_start, *nal_end;
|
|
|
+ start = data;
|
|
|
+ end = data + size;
|
|
|
+
|
|
|
+ size = 0; // reset size
|
|
|
+ nal_start = obs_nal_find_startcode(start, end);
|
|
|
+ for (;;) {
|
|
|
+ while (nal_start < end && !*(nal_start++))
|
|
|
+ ;
|
|
|
+ if (nal_start == end)
|
|
|
+ break;
|
|
|
+ nal_end = obs_nal_find_startcode(nal_start, end);
|
|
|
+
|
|
|
+ assert(nal_end - nal_start <= INT_MAX);
|
|
|
+ s_wb32(&sn, (uint32_t)(nal_end - nal_start));
|
|
|
+ s_write(&sn, nal_start, nal_end - nal_start);
|
|
|
+ size += 4 + nal_end - nal_start;
|
|
|
+ nal_start = nal_end;
|
|
|
+ }
|
|
|
+ if (size == 0)
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ start = nals.bytes.array;
|
|
|
+ end = nals.bytes.array + nals.bytes.num;
|
|
|
+
|
|
|
+ // HVCC init
|
|
|
+ HEVCDecoderConfigurationRecord hvcc;
|
|
|
+ memset(&hvcc, 0, sizeof(HEVCDecoderConfigurationRecord));
|
|
|
+ hvcc.lengthSizeMinusOne = 3; // 4 bytes
|
|
|
+ hvcc.general_profile_compatibility_flags = 0xffffffff; // all bits set
|
|
|
+ hvcc.general_constraint_indicator_flags =
|
|
|
+ 0xffffffffffff; // all bits set
|
|
|
+ hvcc.min_spatial_segmentation_idc = 4096 + 1; // assume invalid value
|
|
|
+ for (unsigned i = 0; i < OBS_NB_ARRAYS; i++) {
|
|
|
+ HVCCNALUnitArray *const array = &hvcc.arrays[i];
|
|
|
+ array_output_serializer_init(&array->nalUnit,
|
|
|
+ &array->nalUnitData);
|
|
|
+ }
|
|
|
+
|
|
|
+ uint8_t *buf = (uint8_t *)start;
|
|
|
+ while (end - buf > 4) {
|
|
|
+ uint32_t len = rb32(buf);
|
|
|
+ assert((end - buf - 4) <= INT_MAX);
|
|
|
+ len = min_u32(len, (uint32_t)(end - buf - 4));
|
|
|
+ uint8_t type = (buf[4] >> 1) & 0x3f;
|
|
|
+
|
|
|
+ buf += 4;
|
|
|
+
|
|
|
+ for (unsigned i = 0; i < OBS_NB_ARRAYS; i++) {
|
|
|
+ static const uint8_t array_idx_to_type[] = {
|
|
|
+ OBS_HEVC_NAL_VPS, OBS_HEVC_NAL_SPS,
|
|
|
+ OBS_HEVC_NAL_PPS, OBS_HEVC_NAL_SEI_PREFIX,
|
|
|
+ OBS_HEVC_NAL_SEI_SUFFIX};
|
|
|
+
|
|
|
+ if (type == array_idx_to_type[i]) {
|
|
|
+ int ps_array_completeness = 0;
|
|
|
+ int ret = hvcc_add_nal_unit(
|
|
|
+ buf, len, ps_array_completeness, &hvcc,
|
|
|
+ i);
|
|
|
+ if (ret < 0)
|
|
|
+ goto free;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ buf += len;
|
|
|
+ }
|
|
|
+
|
|
|
+ // write hvcc data
|
|
|
+ uint16_t vps_count, sps_count, pps_count;
|
|
|
+ if (hvcc.min_spatial_segmentation_idc > 4096) // invalid?
|
|
|
+ hvcc.min_spatial_segmentation_idc = 0;
|
|
|
+ if (!hvcc.min_spatial_segmentation_idc)
|
|
|
+ hvcc.parallelismType = 0;
|
|
|
+ hvcc.avgFrameRate = 0;
|
|
|
+ hvcc.constantFrameRate = 0;
|
|
|
+
|
|
|
+ vps_count = hvcc.arrays[OBS_VPS_INDEX].numNalus;
|
|
|
+ sps_count = hvcc.arrays[OBS_SPS_INDEX].numNalus;
|
|
|
+ pps_count = hvcc.arrays[OBS_PPS_INDEX].numNalus;
|
|
|
+ if (!vps_count || vps_count > OBS_HEVC_MAX_VPS_COUNT || !sps_count ||
|
|
|
+ sps_count > OBS_HEVC_MAX_SPS_COUNT || !pps_count ||
|
|
|
+ pps_count > OBS_HEVC_MAX_PPS_COUNT)
|
|
|
+ goto free;
|
|
|
+
|
|
|
+ struct array_output_data output;
|
|
|
+ struct serializer s;
|
|
|
+ array_output_serializer_init(&s, &output);
|
|
|
+ s_w8(&s, 1); // configurationVersion, always 1
|
|
|
+
|
|
|
+ s_w8(&s, hvcc.general_profile_space << 6 | hvcc.general_tier_flag << 5 |
|
|
|
+ hvcc.general_profile_idc);
|
|
|
+
|
|
|
+ s_wb32(&s, hvcc.general_profile_compatibility_flags);
|
|
|
+
|
|
|
+ s_wb32(&s, (uint32_t)(hvcc.general_constraint_indicator_flags >> 16));
|
|
|
+ s_wb16(&s, (uint16_t)(hvcc.general_constraint_indicator_flags));
|
|
|
+
|
|
|
+ s_w8(&s, hvcc.general_level_idc);
|
|
|
+
|
|
|
+ s_wb16(&s, hvcc.min_spatial_segmentation_idc | 0xf000);
|
|
|
+ s_w8(&s, hvcc.parallelismType | 0xfc);
|
|
|
+ s_w8(&s, hvcc.chromaFormat | 0xfc);
|
|
|
+ s_w8(&s, hvcc.bitDepthLumaMinus8 | 0xf8);
|
|
|
+ s_w8(&s, hvcc.bitDepthChromaMinus8 | 0xf8);
|
|
|
+ s_wb16(&s, hvcc.avgFrameRate);
|
|
|
+
|
|
|
+ s_w8(&s, hvcc.constantFrameRate << 6 | hvcc.numTemporalLayers << 3 |
|
|
|
+ hvcc.temporalIdNested << 2 | hvcc.lengthSizeMinusOne);
|
|
|
+
|
|
|
+ s_w8(&s, hvcc.numOfArrays);
|
|
|
+ for (unsigned i = 0; i < OBS_NB_ARRAYS; i++) {
|
|
|
+ const HVCCNALUnitArray *const array = &hvcc.arrays[i];
|
|
|
+
|
|
|
+ if (!array->numNalus)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ s_w8(&s, (array->array_completeness << 7) |
|
|
|
+ (array->NAL_unit_type & 0x3f));
|
|
|
+ s_wb16(&s, array->numNalus);
|
|
|
+
|
|
|
+ s_write(&s, array->nalUnitData.bytes.array,
|
|
|
+ array->nalUnitData.bytes.num);
|
|
|
+ }
|
|
|
+
|
|
|
+ *header = output.bytes.array;
|
|
|
+ size = output.bytes.num;
|
|
|
+
|
|
|
+free:
|
|
|
+ for (unsigned i = 0; i < OBS_NB_ARRAYS; i++) {
|
|
|
+ HVCCNALUnitArray *const array = &hvcc.arrays[i];
|
|
|
+ array->numNalus = 0;
|
|
|
+ array_output_serializer_free(&array->nalUnitData);
|
|
|
+ }
|
|
|
+
|
|
|
+done:
|
|
|
+ array_output_serializer_free(&nals);
|
|
|
+ return size;
|
|
|
+}
|