[FFmpeg-cvslog] movenc: write hvcC tag for HEVC.
Tim Walker
git at videolan.org
Tue Mar 11 11:57:14 CET 2014
ffmpeg | branch: release/2.2 | Tim Walker <tdskywalker at gmail.com> | Mon Mar 3 14:53:41 2014 +0000| [c761379825ff0bf9dd191e244c4b2f7697fb2b3c] | committer: Reinhard Tartler
movenc: write hvcC tag for HEVC.
(cherry picked from commit 20b40a597cdd4969cf1147d7c7efee2b6232524b)
> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=c761379825ff0bf9dd191e244c4b2f7697fb2b3c
---
libavformat/Makefile | 2 +-
libavformat/hevc.c | 1076 ++++++++++++++++++++++++++++++++++++++++++++++++++
libavformat/hevc.h | 50 +++
libavformat/movenc.c | 13 +
4 files changed, 1140 insertions(+), 1 deletion(-)
diff --git a/libavformat/Makefile b/libavformat/Makefile
index d491d43..a3cd504 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -179,7 +179,7 @@ OBJS-$(CONFIG_MM_DEMUXER) += mm.o
OBJS-$(CONFIG_MMF_DEMUXER) += mmf.o pcm.o
OBJS-$(CONFIG_MMF_MUXER) += mmf.o
OBJS-$(CONFIG_MOV_DEMUXER) += mov.o isom.o mov_chan.o
-OBJS-$(CONFIG_MOV_MUXER) += movenc.o isom.o avc.o \
+OBJS-$(CONFIG_MOV_MUXER) += movenc.o isom.o avc.o hevc.o \
movenchint.o mov_chan.o
OBJS-$(CONFIG_MP2_MUXER) += mp3enc.o rawenc.o id3v2enc.o
OBJS-$(CONFIG_MP3_DEMUXER) += mp3dec.o
diff --git a/libavformat/hevc.c b/libavformat/hevc.c
new file mode 100644
index 0000000..ab4eb10
--- /dev/null
+++ b/libavformat/hevc.c
@@ -0,0 +1,1076 @@
+/*
+ * Copyright (c) 2014 Tim Walker <tdskywalker at gmail.com>
+ *
+ * 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 "libavcodec/get_bits.h"
+#include "libavcodec/golomb.h"
+#include "libavcodec/hevc.h"
+#include "libavutil/intreadwrite.h"
+#include "avc.h"
+#include "avio.h"
+#include "hevc.h"
+
+#define MAX_SPATIAL_SEGMENTATION 4096 // max. value of u(12) field
+
+typedef struct HVCCNALUnitArray {
+ uint8_t array_completeness;
+ uint8_t NAL_unit_type;
+ uint16_t numNalus;
+ uint16_t *nalUnitLength;
+ uint8_t **nalUnit;
+} HVCCNALUnitArray;
+
+typedef struct HEVCDecoderConfigurationRecord {
+ uint8_t configurationVersion;
+ 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 *array;
+} 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;
+
+static void hvcc_update_ptl(HEVCDecoderConfigurationRecord *hvcc,
+ HVCCProfileTierLevel *ptl)
+{
+ /*
+ * The value of general_profile_space in all the parameter sets must be
+ * identical.
+ */
+ hvcc->general_profile_space = ptl->profile_space;
+
+ /*
+ * The level indication general_level_idc must indicate a level of
+ * capability equal to or greater than the highest level indicated for the
+ * highest tier in all the parameter sets.
+ */
+ if (hvcc->general_tier_flag < ptl->tier_flag)
+ hvcc->general_level_idc = ptl->level_idc;
+ else
+ hvcc->general_level_idc = FFMAX(hvcc->general_level_idc, ptl->level_idc);
+
+ /*
+ * The tier indication general_tier_flag must indicate a tier equal to or
+ * greater than the highest tier indicated in all the parameter sets.
+ */
+ hvcc->general_tier_flag = FFMAX(hvcc->general_tier_flag, ptl->tier_flag);
+
+ /*
+ * The profile indication general_profile_idc must indicate a profile to
+ * which the stream associated with this configuration record conforms.
+ *
+ * If the sequence parameter sets are marked with different profiles, then
+ * the stream may need examination to determine which profile, if any, the
+ * entire stream conforms to. If the entire stream is not examined, or the
+ * examination reveals that there is no profile to which the entire stream
+ * conforms, then the entire stream must be split into two or more
+ * sub-streams with separate configuration records in which these rules can
+ * be met.
+ *
+ * Note: set the profile to the highest value for the sake of simplicity.
+ */
+ hvcc->general_profile_idc = FFMAX(hvcc->general_profile_idc, ptl->profile_idc);
+
+ /*
+ * Each bit in general_profile_compatibility_flags may only be set if all
+ * the parameter sets set that bit.
+ */
+ hvcc->general_profile_compatibility_flags &= ptl->profile_compatibility_flags;
+
+ /*
+ * Each bit in general_constraint_indicator_flags may only be set if all
+ * the parameter sets set that bit.
+ */
+ hvcc->general_constraint_indicator_flags &= ptl->constraint_indicator_flags;
+}
+
+static void hvcc_parse_ptl(GetBitContext *gb,
+ HEVCDecoderConfigurationRecord *hvcc,
+ unsigned int max_sub_layers_minus1)
+{
+ unsigned int i;
+ HVCCProfileTierLevel general_ptl;
+ uint8_t sub_layer_profile_present_flag[MAX_SUB_LAYERS];
+ uint8_t sub_layer_level_present_flag[MAX_SUB_LAYERS];
+
+ general_ptl.profile_space = get_bits(gb, 2);
+ general_ptl.tier_flag = get_bits1(gb);
+ 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_bits1(gb);
+ sub_layer_level_present_flag[i] = get_bits1(gb);
+ }
+
+ if (max_sub_layers_minus1 > 0)
+ for (i = max_sub_layers_minus1; i < 8; i++)
+ skip_bits(gb, 2); // reserved_zero_2bits[i]
+
+ for (i = 0; i < max_sub_layers_minus1; i++) {
+ if (sub_layer_profile_present_flag[i]) {
+ /*
+ * sub_layer_profile_space[i] u(2)
+ * sub_layer_tier_flag[i] u(1)
+ * sub_layer_profile_idc[i] u(5)
+ * sub_layer_profile_compatibility_flag[i][0..31] u(32)
+ * sub_layer_progressive_source_flag[i] u(1)
+ * sub_layer_interlaced_source_flag[i] u(1)
+ * sub_layer_non_packed_constraint_flag[i] u(1)
+ * sub_layer_frame_only_constraint_flag[i] u(1)
+ * sub_layer_reserved_zero_44bits[i] u(44)
+ */
+ 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 void skip_sub_layer_hrd_parameters(GetBitContext *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
+ }
+
+ skip_bits1(gb); // cbr_flag
+ }
+}
+
+static void skip_hrd_parameters(GetBitContext *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_bits1(gb);
+ vcl_hrd_parameters_present_flag = get_bits1(gb);
+
+ if (nal_hrd_parameters_present_flag ||
+ vcl_hrd_parameters_present_flag) {
+ sub_pic_hrd_params_present_flag = get_bits1(gb);
+
+ if (sub_pic_hrd_params_present_flag)
+ /*
+ * tick_divisor_minus2 u(8)
+ * du_cpb_removal_delay_increment_length_minus1 u(5)
+ * sub_pic_cpb_params_in_pic_timing_sei_flag u(1)
+ * dpb_output_delay_du_length_minus1 u(5)
+ */
+ skip_bits(gb, 19);
+
+ /*
+ * bit_rate_scale u(4)
+ * cpb_size_scale u(4)
+ */
+ skip_bits(gb, 8);
+
+ if (sub_pic_hrd_params_present_flag)
+ skip_bits(gb, 4); // cpb_size_du_scale
+
+ /*
+ * initial_cpb_removal_delay_length_minus1 u(5)
+ * au_cpb_removal_delay_length_minus1 u(5)
+ * dpb_output_delay_length_minus1 u(5)
+ */
+ skip_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_bits1(gb);
+
+ if (!fixed_pic_rate_general_flag)
+ fixed_pic_rate_within_cvs_flag = get_bits1(gb);
+
+ if (fixed_pic_rate_within_cvs_flag)
+ get_ue_golomb_long(gb); // elemental_duration_in_tc_minus1
+ else
+ low_delay_hrd_flag = get_bits1(gb);
+
+ if (!low_delay_hrd_flag)
+ cpb_cnt_minus1 = get_ue_golomb_long(gb);
+
+ 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);
+ }
+}
+
+static void skip_timing_info(GetBitContext *gb)
+{
+ skip_bits_long(gb, 32); // num_units_in_tick
+ skip_bits_long(gb, 32); // time_scale
+
+ if (get_bits1(gb)) // poc_proportional_to_timing_flag
+ get_ue_golomb_long(gb); // num_ticks_poc_diff_one_minus1
+}
+
+static void hvcc_parse_vui(GetBitContext *gb,
+ HEVCDecoderConfigurationRecord *hvcc,
+ unsigned int max_sub_layers_minus1)
+{
+ unsigned int min_spatial_segmentation_idc;
+
+ if (get_bits1(gb)) // aspect_ratio_info_present_flag
+ if (get_bits(gb, 8) == 255) // aspect_ratio_idc
+ skip_bits_long(gb, 32); // sar_width u(16), sar_height u(16)
+
+ if (get_bits1(gb)) // overscan_info_present_flag
+ skip_bits1(gb); // overscan_appropriate_flag
+
+ if (get_bits1(gb)) { // video_signal_type_present_flag
+ skip_bits(gb, 4); // video_format u(3), video_full_range_flag u(1)
+
+ if (get_bits1(gb)) // colour_description_present_flag
+ /*
+ * colour_primaries u(8)
+ * transfer_characteristics u(8)
+ * matrix_coeffs u(8)
+ */
+ skip_bits(gb, 24);
+ }
+
+ if (get_bits1(gb)) { // chroma_loc_info_present_flag
+ get_ue_golomb_long(gb); // chroma_sample_loc_type_top_field
+ get_ue_golomb_long(gb); // chroma_sample_loc_type_bottom_field
+ }
+
+ /*
+ * neutral_chroma_indication_flag u(1)
+ * field_seq_flag u(1)
+ * frame_field_info_present_flag u(1)
+ */
+ skip_bits(gb, 3);
+
+ if (get_bits1(gb)) { // 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_bits1(gb)) { // vui_timing_info_present_flag
+ skip_timing_info(gb);
+
+ if (get_bits1(gb)) // vui_hrd_parameters_present_flag
+ skip_hrd_parameters(gb, 1, max_sub_layers_minus1);
+ }
+
+ if (get_bits1(gb)) { // bitstream_restriction_flag
+ /*
+ * tiles_fixed_structure_flag u(1)
+ * motion_vectors_over_pic_boundaries_flag u(1)
+ * restricted_ref_pic_lists_flag u(1)
+ */
+ skip_bits(gb, 3);
+
+ min_spatial_segmentation_idc = get_ue_golomb_long(gb);
+
+ /*
+ * unsigned int(12) min_spatial_segmentation_idc;
+ *
+ * The min_spatial_segmentation_idc indication must indicate a level of
+ * spatial segmentation equal to or less than the lowest level of
+ * spatial segmentation indicated in all the parameter sets.
+ */
+ hvcc->min_spatial_segmentation_idc = FFMIN(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 void skip_sub_layer_ordering_info(GetBitContext *gb)
+{
+ 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
+}
+
+static int hvcc_parse_vps(GetBitContext *gb,
+ HEVCDecoderConfigurationRecord *hvcc)
+{
+ unsigned int vps_max_sub_layers_minus1;
+
+ /*
+ * vps_video_parameter_set_id u(4)
+ * vps_reserved_three_2bits u(2)
+ * vps_max_layers_minus1 u(6)
+ */
+ skip_bits(gb, 12);
+
+ vps_max_sub_layers_minus1 = get_bits(gb, 3);
+
+ /*
+ * numTemporalLayers greater than 1 indicates that the stream to which this
+ * configuration record applies is temporally scalable and the contained
+ * number of temporal layers (also referred to as temporal sub-layer or
+ * sub-layer in ISO/IEC 23008-2) is equal to numTemporalLayers. Value 1
+ * indicates that the stream is not temporally scalable. Value 0 indicates
+ * that it is unknown whether the stream is temporally scalable.
+ */
+ hvcc->numTemporalLayers = FFMAX(hvcc->numTemporalLayers,
+ vps_max_sub_layers_minus1 + 1);
+
+ /*
+ * vps_temporal_id_nesting_flag u(1)
+ * vps_reserved_0xffff_16bits u(16)
+ */
+ skip_bits(gb, 17);
+
+ hvcc_parse_ptl(gb, hvcc, vps_max_sub_layers_minus1);
+
+ /* nothing useful for hvcC past this point */
+ return 0;
+}
+
+static void skip_scaling_list_data(GetBitContext *gb)
+{
+ int i, j, k, num_coeffs;
+
+ for (i = 0; i < 4; i++)
+ for (j = 0; j < (i == 3 ? 2 : 6); j++)
+ if (!get_bits1(gb)) // scaling_list_pred_mode_flag[i][j]
+ get_ue_golomb_long(gb); // scaling_list_pred_matrix_id_delta[i][j]
+ else {
+ num_coeffs = FFMIN(64, 1 << (4 + (i << 1)));
+
+ if (i > 1)
+ get_se_golomb(gb); // scaling_list_dc_coef_minus8[i-2][j]
+
+ for (k = 0; k < num_coeffs; k++)
+ get_se_golomb(gb); // scaling_list_delta_coef
+ }
+}
+
+static int parse_rps(GetBitContext *gb, unsigned int rps_idx,
+ unsigned int num_rps,
+ unsigned int num_delta_pocs[MAX_SHORT_TERM_RPS_COUNT])
+{
+ unsigned int i;
+
+ if (rps_idx && get_bits1(gb)) { // inter_ref_pic_set_prediction_flag
+ /* this should only happen for slice headers, and this isn't one */
+ if (rps_idx >= num_rps)
+ return AVERROR_INVALIDDATA;
+
+ skip_bits1 (gb); // delta_rps_sign
+ get_ue_golomb_long(gb); // abs_delta_rps_minus1
+
+ num_delta_pocs[rps_idx] = 0;
+
+ /*
+ * From libavcodec/hevc_ps.c:
+ *
+ * if (is_slice_header) {
+ * //foo
+ * } else
+ * rps_ridx = &sps->st_rps[rps - sps->st_rps - 1];
+ *
+ * where:
+ * rps: &sps->st_rps[rps_idx]
+ * sps->st_rps: &sps->st_rps[0]
+ * is_slice_header: rps_idx == num_rps
+ *
+ * thus:
+ * if (num_rps != rps_idx)
+ * rps_ridx = &sps->st_rps[rps_idx - 1];
+ *
+ * NumDeltaPocs[RefRpsIdx]: num_delta_pocs[rps_idx - 1]
+ */
+ 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_bits1(gb);
+ if (!used_by_curr_pic_flag)
+ use_delta_flag = get_bits1(gb);
+
+ 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);
+
+ 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]
+ skip_bits1 (gb); // 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]
+ skip_bits1 (gb); // used_by_curr_pic_s1_flag[rps_idx]
+ }
+ }
+
+ return 0;
+}
+
+static int hvcc_parse_sps(GetBitContext *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[MAX_SHORT_TERM_RPS_COUNT];
+
+ skip_bits(gb, 4); // sps_video_parameter_set_id
+
+ sps_max_sub_layers_minus1 = get_bits (gb, 3);
+
+ /*
+ * numTemporalLayers greater than 1 indicates that the stream to which this
+ * configuration record applies is temporally scalable and the contained
+ * number of temporal layers (also referred to as temporal sub-layer or
+ * sub-layer in ISO/IEC 23008-2) is equal to numTemporalLayers. Value 1
+ * indicates that the stream is not temporally scalable. Value 0 indicates
+ * that it is unknown whether the stream is temporally scalable.
+ */
+ hvcc->numTemporalLayers = FFMAX(hvcc->numTemporalLayers,
+ sps_max_sub_layers_minus1 + 1);
+
+ hvcc->temporalIdNested = get_bits1(gb);
+
+ 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)
+ skip_bits1(gb); // 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_bits1(gb)) { // 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_bits1(gb) ? 0 : sps_max_sub_layers_minus1;
+ for (; i <= sps_max_sub_layers_minus1; i++)
+ skip_sub_layer_ordering_info(gb);
+
+ 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_bits1(gb) && // scaling_list_enabled_flag
+ get_bits1(gb)) // sps_scaling_list_data_present_flag
+ skip_scaling_list_data(gb);
+
+ skip_bits1(gb); // amp_enabled_flag
+ skip_bits1(gb); // sample_adaptive_offset_enabled_flag
+
+ if (get_bits1(gb)) { // pcm_enabled_flag
+ skip_bits (gb, 4); // pcm_sample_bit_depth_luma_minus1
+ skip_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
+ skip_bits1 (gb); // pcm_loop_filter_disabled_flag
+ }
+
+ num_short_term_ref_pic_sets = get_ue_golomb_long(gb);
+ if (num_short_term_ref_pic_sets > MAX_SHORT_TERM_RPS_COUNT)
+ return AVERROR_INVALIDDATA;
+
+ 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_bits1(gb)) { // long_term_ref_pics_present_flag
+ for (i = 0; i < get_ue_golomb_long(gb); i++) { // num_long_term_ref_pics_sps
+ int len = FFMIN(log2_max_pic_order_cnt_lsb_minus4 + 4, 16);
+ skip_bits (gb, len); // lt_ref_pic_poc_lsb_sps[i]
+ skip_bits1(gb); // used_by_curr_pic_lt_sps_flag[i]
+ }
+ }
+
+ skip_bits1(gb); // sps_temporal_mvp_enabled_flag
+ skip_bits1(gb); // strong_intra_smoothing_enabled_flag
+
+ if (get_bits1(gb)) // 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(GetBitContext *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
+
+ /*
+ * dependent_slice_segments_enabled_flag u(1)
+ * output_flag_present_flag u(1)
+ * num_extra_slice_header_bits u(3)
+ * sign_data_hiding_enabled_flag u(1)
+ * cabac_init_present_flag u(1)
+ */
+ skip_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 (gb); // init_qp_minus26
+
+ /*
+ * constrained_intra_pred_flag u(1)
+ * transform_skip_enabled_flag u(1)
+ */
+ skip_bits(gb, 2);
+
+ if (get_bits1(gb)) // cu_qp_delta_enabled_flag
+ get_ue_golomb_long(gb); // diff_cu_qp_delta_depth
+
+ get_se_golomb(gb); // pps_cb_qp_offset
+ get_se_golomb(gb); // pps_cr_qp_offset
+
+ /*
+ * weighted_pred_flag u(1)
+ * weighted_bipred_flag u(1)
+ * transquant_bypass_enabled_flag u(1)
+ */
+ skip_bits(gb, 3);
+
+ tiles_enabled_flag = get_bits1(gb);
+ entropy_coding_sync_enabled_flag = get_bits1(gb);
+
+ 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 uint8_t *nal_unit_extract_rbsp(const uint8_t *src, uint32_t src_len,
+ uint32_t *dst_len)
+{
+ uint8_t *dst;
+ uint32_t i, len;
+
+ dst = av_malloc(src_len);
+ if (!dst)
+ return NULL;
+
+ /* NAL unit header (2 bytes) */
+ i = len = 0;
+ while (i < 2 && 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++];
+
+ *dst_len = len;
+ return dst;
+}
+
+
+
+static void nal_unit_parse_header(GetBitContext *gb, uint8_t *nal_type)
+{
+ skip_bits1(gb); // forbidden_zero_bit
+
+ *nal_type = get_bits(gb, 6);
+
+ /*
+ * nuh_layer_id u(6)
+ * nuh_temporal_id_plus1 u(3)
+ */
+ skip_bits(gb, 9);
+}
+
+static int hvcc_array_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size,
+ uint8_t nal_type, int ps_array_completeness,
+ HEVCDecoderConfigurationRecord *hvcc)
+{
+ int ret;
+ uint8_t index;
+ uint16_t numNalus;
+ HVCCNALUnitArray *array;
+
+ for (index = 0; index < hvcc->numOfArrays; index++)
+ if (hvcc->array[index].NAL_unit_type == nal_type)
+ break;
+
+ if (index >= hvcc->numOfArrays) {
+ uint8_t i;
+
+ ret = av_reallocp_array(&hvcc->array, index + 1, sizeof(HVCCNALUnitArray));
+ if (ret < 0)
+ return ret;
+
+ for (i = hvcc->numOfArrays; i <= index; i++)
+ memset(&hvcc->array[i], 0, sizeof(HVCCNALUnitArray));
+ hvcc->numOfArrays = index + 1;
+ }
+
+ array = &hvcc->array[index];
+ numNalus = array->numNalus;
+
+ ret = av_reallocp_array(&array->nalUnit, numNalus + 1, sizeof(uint8_t*));
+ if (ret < 0)
+ return ret;
+
+ ret = av_reallocp_array(&array->nalUnitLength, numNalus + 1, sizeof(uint16_t));
+ if (ret < 0)
+ return ret;
+
+ array->nalUnit [numNalus] = nal_buf;
+ array->nalUnitLength[numNalus] = nal_size;
+ array->NAL_unit_type = nal_type;
+ array->numNalus++;
+
+ /*
+ * When the sample entry name is ‘hvc1’, the default and mandatory value of
+ * array_completeness is 1 for arrays of all types of parameter sets, and 0
+ * for all other arrays. When the sample entry name is ‘hev1’, the default
+ * value of array_completeness is 0 for all arrays.
+ */
+ if (nal_type == NAL_VPS || nal_type == NAL_SPS || nal_type == NAL_PPS)
+ array->array_completeness = ps_array_completeness;
+
+ return 0;
+}
+
+static int hvcc_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size,
+ int ps_array_completeness,
+ HEVCDecoderConfigurationRecord *hvcc)
+{
+ int ret = 0;
+ GetBitContext gbc;
+ uint8_t nal_type;
+ uint8_t *rbsp_buf;
+ uint32_t rbsp_size;
+
+ rbsp_buf = nal_unit_extract_rbsp(nal_buf, nal_size, &rbsp_size);
+ if (!rbsp_buf) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ ret = init_get_bits8(&gbc, rbsp_buf, rbsp_size);
+ if (ret < 0)
+ goto end;
+
+ nal_unit_parse_header(&gbc, &nal_type);
+
+ /*
+ * Note: only 'declarative' SEI messages are allowed in
+ * hvcC. Perhaps the SEI playload type should be checked
+ * and non-declarative SEI messages discarded?
+ */
+ switch (nal_type) {
+ case NAL_VPS:
+ case NAL_SPS:
+ case NAL_PPS:
+ case NAL_SEI_PREFIX:
+ case NAL_SEI_SUFFIX:
+ ret = hvcc_array_add_nal_unit(nal_buf, nal_size, nal_type,
+ ps_array_completeness, hvcc);
+ if (ret < 0)
+ goto end;
+ else if (nal_type == NAL_VPS)
+ ret = hvcc_parse_vps(&gbc, hvcc);
+ else if (nal_type == NAL_SPS)
+ ret = hvcc_parse_sps(&gbc, hvcc);
+ else if (nal_type == NAL_PPS)
+ ret = hvcc_parse_pps(&gbc, hvcc);
+ if (ret < 0)
+ goto end;
+ break;
+ default:
+ ret = AVERROR_INVALIDDATA;
+ goto end;
+ }
+
+end:
+ av_free(rbsp_buf);
+ return ret;
+}
+
+static void hvcc_init(HEVCDecoderConfigurationRecord *hvcc)
+{
+ memset(hvcc, 0, sizeof(HEVCDecoderConfigurationRecord));
+ hvcc->configurationVersion = 1;
+ hvcc->lengthSizeMinusOne = 3; // 4 bytes
+
+ /*
+ * The following fields have all their valid bits set by default,
+ * the ProfileTierLevel parsing code will unset them when needed.
+ */
+ hvcc->general_profile_compatibility_flags = 0xffffffff;
+ hvcc->general_constraint_indicator_flags = 0xffffffffffff;
+
+ /*
+ * Initialize this field with an invalid value which can be used to detect
+ * whether we didn't see any VUI (in wich case it should be reset to zero).
+ */
+ hvcc->min_spatial_segmentation_idc = MAX_SPATIAL_SEGMENTATION + 1;
+}
+
+static void hvcc_close(HEVCDecoderConfigurationRecord *hvcc)
+{
+ uint8_t i;
+
+ for (i = 0; i < hvcc->numOfArrays; i++) {
+ hvcc->array[i].numNalus = 0;
+ av_freep(&hvcc->array[i].nalUnit);
+ av_freep(&hvcc->array[i].nalUnitLength);
+ }
+
+ hvcc->numOfArrays = 0;
+ av_freep(&hvcc->array);
+}
+
+static int hvcc_write(AVIOContext *pb, HEVCDecoderConfigurationRecord *hvcc)
+{
+ uint8_t i;
+ uint16_t j, vps_count = 0, sps_count = 0, pps_count = 0;
+
+ /*
+ * We only support writing HEVCDecoderConfigurationRecord version 1.
+ */
+ hvcc->configurationVersion = 1;
+
+ /*
+ * If min_spatial_segmentation_idc is invalid, reset to 0 (unspecified).
+ */
+ if (hvcc->min_spatial_segmentation_idc > MAX_SPATIAL_SEGMENTATION)
+ hvcc->min_spatial_segmentation_idc = 0;
+
+ /*
+ * parallelismType indicates the type of parallelism that is used to meet
+ * the restrictions imposed by min_spatial_segmentation_idc when the value
+ * of min_spatial_segmentation_idc is greater than 0.
+ */
+ if (!hvcc->min_spatial_segmentation_idc)
+ hvcc->parallelismType = 0;
+
+ /*
+ * It's unclear how to properly compute these fields, so
+ * let's always set them to values meaning 'unspecified'.
+ */
+ hvcc->avgFrameRate = 0;
+ hvcc->constantFrameRate = 0;
+
+ av_dlog(NULL, "configurationVersion: %"PRIu8"\n",
+ hvcc->configurationVersion);
+ av_dlog(NULL, "general_profile_space: %"PRIu8"\n",
+ hvcc->general_profile_space);
+ av_dlog(NULL, "general_tier_flag: %"PRIu8"\n",
+ hvcc->general_tier_flag);
+ av_dlog(NULL, "general_profile_idc: %"PRIu8"\n",
+ hvcc->general_profile_idc);
+ av_dlog(NULL, "general_profile_compatibility_flags: 0x%08"PRIx32"\n",
+ hvcc->general_profile_compatibility_flags);
+ av_dlog(NULL, "general_constraint_indicator_flags: 0x%012"PRIx64"\n",
+ hvcc->general_constraint_indicator_flags);
+ av_dlog(NULL, "general_level_idc: %"PRIu8"\n",
+ hvcc->general_level_idc);
+ av_dlog(NULL, "min_spatial_segmentation_idc: %"PRIu16"\n",
+ hvcc->min_spatial_segmentation_idc);
+ av_dlog(NULL, "parallelismType: %"PRIu8"\n",
+ hvcc->parallelismType);
+ av_dlog(NULL, "chromaFormat: %"PRIu8"\n",
+ hvcc->chromaFormat);
+ av_dlog(NULL, "bitDepthLumaMinus8: %"PRIu8"\n",
+ hvcc->bitDepthLumaMinus8);
+ av_dlog(NULL, "bitDepthChromaMinus8: %"PRIu8"\n",
+ hvcc->bitDepthChromaMinus8);
+ av_dlog(NULL, "avgFrameRate: %"PRIu16"\n",
+ hvcc->avgFrameRate);
+ av_dlog(NULL, "constantFrameRate: %"PRIu8"\n",
+ hvcc->constantFrameRate);
+ av_dlog(NULL, "numTemporalLayers: %"PRIu8"\n",
+ hvcc->numTemporalLayers);
+ av_dlog(NULL, "temporalIdNested: %"PRIu8"\n",
+ hvcc->temporalIdNested);
+ av_dlog(NULL, "lengthSizeMinusOne: %"PRIu8"\n",
+ hvcc->lengthSizeMinusOne);
+ av_dlog(NULL, "numOfArrays: %"PRIu8"\n",
+ hvcc->numOfArrays);
+ for (i = 0; i < hvcc->numOfArrays; i++) {
+ av_dlog(NULL, "array_completeness[%"PRIu8"]: %"PRIu8"\n",
+ i, hvcc->array[i].array_completeness);
+ av_dlog(NULL, "NAL_unit_type[%"PRIu8"]: %"PRIu8"\n",
+ i, hvcc->array[i].NAL_unit_type);
+ av_dlog(NULL, "numNalus[%"PRIu8"]: %"PRIu16"\n",
+ i, hvcc->array[i].numNalus);
+ for (j = 0; j < hvcc->array[i].numNalus; j++)
+ av_dlog(NULL,
+ "nalUnitLength[%"PRIu8"][%"PRIu16"]: %"PRIu16"\n",
+ i, j, hvcc->array[i].nalUnitLength[j]);
+ }
+
+ /*
+ * We need at least one of each: VPS, SPS and PPS.
+ */
+ for (i = 0; i < hvcc->numOfArrays; i++)
+ switch (hvcc->array[i].NAL_unit_type) {
+ case NAL_VPS:
+ vps_count += hvcc->array[i].numNalus;
+ break;
+ case NAL_SPS:
+ sps_count += hvcc->array[i].numNalus;
+ break;
+ case NAL_PPS:
+ pps_count += hvcc->array[i].numNalus;
+ break;
+ default:
+ break;
+ }
+ if (!vps_count || vps_count > MAX_VPS_COUNT ||
+ !sps_count || sps_count > MAX_SPS_COUNT ||
+ !pps_count || pps_count > MAX_PPS_COUNT)
+ return AVERROR_INVALIDDATA;
+
+ /* unsigned int(8) configurationVersion = 1; */
+ avio_w8(pb, hvcc->configurationVersion);
+
+ /*
+ * unsigned int(2) general_profile_space;
+ * unsigned int(1) general_tier_flag;
+ * unsigned int(5) general_profile_idc;
+ */
+ avio_w8(pb, hvcc->general_profile_space << 6 |
+ hvcc->general_tier_flag << 5 |
+ hvcc->general_profile_idc);
+
+ /* unsigned int(32) general_profile_compatibility_flags; */
+ avio_wb32(pb, hvcc->general_profile_compatibility_flags);
+
+ /* unsigned int(48) general_constraint_indicator_flags; */
+ avio_wb32(pb, hvcc->general_constraint_indicator_flags >> 16);
+ avio_wb16(pb, hvcc->general_constraint_indicator_flags);
+
+ /* unsigned int(8) general_level_idc; */
+ avio_w8(pb, hvcc->general_level_idc);
+
+ /*
+ * bit(4) reserved = ‘1111’b;
+ * unsigned int(12) min_spatial_segmentation_idc;
+ */
+ avio_wb16(pb, hvcc->min_spatial_segmentation_idc | 0xf000);
+
+ /*
+ * bit(6) reserved = ‘111111’b;
+ * unsigned int(2) parallelismType;
+ */
+ avio_w8(pb, hvcc->parallelismType | 0xfc);
+
+ /*
+ * bit(6) reserved = ‘111111’b;
+ * unsigned int(2) chromaFormat;
+ */
+ avio_w8(pb, hvcc->chromaFormat | 0xfc);
+
+ /*
+ * bit(5) reserved = ‘11111’b;
+ * unsigned int(3) bitDepthLumaMinus8;
+ */
+ avio_w8(pb, hvcc->bitDepthLumaMinus8 | 0xf8);
+
+ /*
+ * bit(5) reserved = ‘11111’b;
+ * unsigned int(3) bitDepthChromaMinus8;
+ */
+ avio_w8(pb, hvcc->bitDepthChromaMinus8 | 0xf8);
+
+ /* bit(16) avgFrameRate; */
+ avio_wb16(pb, hvcc->avgFrameRate);
+
+ /*
+ * bit(2) constantFrameRate;
+ * bit(3) numTemporalLayers;
+ * bit(1) temporalIdNested;
+ * unsigned int(2) lengthSizeMinusOne;
+ */
+ avio_w8(pb, hvcc->constantFrameRate << 6 |
+ hvcc->numTemporalLayers << 3 |
+ hvcc->temporalIdNested << 2 |
+ hvcc->lengthSizeMinusOne);
+
+ /* unsigned int(8) numOfArrays; */
+ avio_w8(pb, hvcc->numOfArrays);
+
+ for (i = 0; i < hvcc->numOfArrays; i++) {
+ /*
+ * bit(1) array_completeness;
+ * unsigned int(1) reserved = 0;
+ * unsigned int(6) NAL_unit_type;
+ */
+ avio_w8(pb, hvcc->array[i].array_completeness << 7 |
+ hvcc->array[i].NAL_unit_type & 0x3f);
+
+ /* unsigned int(16) numNalus; */
+ avio_wb16(pb, hvcc->array[i].numNalus);
+
+ for (j = 0; j < hvcc->array[i].numNalus; j++) {
+ /* unsigned int(16) nalUnitLength; */
+ avio_wb16(pb, hvcc->array[i].nalUnitLength[j]);
+
+ /* bit(8*nalUnitLength) nalUnit; */
+ avio_write(pb, hvcc->array[i].nalUnit[j],
+ hvcc->array[i].nalUnitLength[j]);
+ }
+ }
+
+ return 0;
+}
+
+int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data,
+ int size, int ps_array_completeness)
+{
+ int ret = 0;
+ uint8_t *buf, *end, *start = NULL;
+ HEVCDecoderConfigurationRecord hvcc;
+
+ hvcc_init(&hvcc);
+
+ if (size < 6) {
+ /* We can't write a valid hvcC from the provided data */
+ ret = AVERROR_INVALIDDATA;
+ goto end;
+ } else if (*data == 1) {
+ /* Data is already hvcC-formatted */
+ avio_write(pb, data, size);
+ goto end;
+ } else if (!(AV_RB24(data) == 1 || AV_RB32(data) == 1)) {
+ /* Not a valid Annex B start code prefix */
+ ret = AVERROR_INVALIDDATA;
+ goto end;
+ }
+
+ ret = ff_avc_parse_nal_units_buf(data, &start, &size);
+ if (ret < 0)
+ goto end;
+
+ buf = start;
+ end = start + size;
+
+ while (end - buf > 4) {
+ uint32_t len = FFMIN(AV_RB32(buf), end - buf - 4);
+ uint8_t type = (buf[4] >> 1) & 0x3f;
+
+ buf += 4;
+
+ switch (type) {
+ case NAL_VPS:
+ case NAL_SPS:
+ case NAL_PPS:
+ case NAL_SEI_PREFIX:
+ case NAL_SEI_SUFFIX:
+ ret = hvcc_add_nal_unit(buf, len, ps_array_completeness, &hvcc);
+ if (ret < 0)
+ goto end;
+ break;
+ default:
+ break;
+ }
+
+ buf += len;
+ }
+
+ ret = hvcc_write(pb, &hvcc);
+
+end:
+ hvcc_close(&hvcc);
+ av_free(start);
+ return ret;
+}
diff --git a/libavformat/hevc.h b/libavformat/hevc.h
new file mode 100644
index 0000000..82525ac
--- /dev/null
+++ b/libavformat/hevc.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2014 Tim Walker <tdskywalker at gmail.com>
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * internal header for HEVC (de)muxer utilities
+ */
+
+#ifndef AVFORMAT_HEVC_H
+#define AVFORMAT_HEVC_H
+
+#include <stdint.h>
+#include "avio.h"
+
+/**
+ * Writes HEVC extradata (parameter sets, declarative SEI NAL units) to the
+ * provided AVIOContext.
+ *
+ * If the extradata is Annex B format, it gets converted to hvcC format before
+ * writing.
+ *
+ * @param pb address of the AVIOContext where the hvcC shall be written
+ * @param data address of the buffer holding the data needed to write the hvcC
+ * @param size size (in bytes) of the data buffer
+ * @param ps_array_completeness whether all parameter sets are in the hvcC (1)
+ * or there may be additional parameter sets in the bitstream (0)
+ * @return 0 in case of success, a negative value corresponding to an AVERROR
+ * code in case of failure
+ */
+int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data,
+ int size, int ps_array_completeness);
+
+#endif /* AVFORMAT_HEVC_H */
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 6344e38..641e1c7 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -39,6 +39,7 @@
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/dict.h"
+#include "hevc.h"
#include "rtpenc.h"
#include "mov_chan.h"
@@ -685,6 +686,16 @@ static int mov_write_avcc_tag(AVIOContext *pb, MOVTrack *track)
return update_size(pb, pos);
}
+static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track)
+{
+ int64_t pos = avio_tell(pb);
+
+ avio_wb32(pb, 0);
+ ffio_wfourcc(pb, "hvcC");
+ ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
+ return update_size(pb, pos);
+}
+
/* also used by all avid codecs (dv, imx, meridien) and their variants */
static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
{
@@ -1035,6 +1046,8 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track)
mov_write_svq3_tag(pb);
else if (track->enc->codec_id == AV_CODEC_ID_DNXHD)
mov_write_avid_tag(pb, track);
+ else if (track->enc->codec_id == AV_CODEC_ID_HEVC)
+ mov_write_hvcc_tag(pb, track);
else if (track->enc->codec_id == AV_CODEC_ID_H264) {
mov_write_avcc_tag(pb, track);
if (track->mode == MODE_IPOD)
More information about the ffmpeg-cvslog
mailing list