From 2fbcc4089425c2f9c85210f79658c7ebaab2ff3e Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Tue, 3 May 2022 14:30:03 +0200 Subject: [PATCH] lavd: mitigate flood of errors at H.264 dec start Do not pass decoder frames until one beginning with SPS is received - this mitigates flood of errors at the beginning of H.264 decode, which may be potentially harmful because it pushes off other messages (including warnings). --- src/rtp/rtpdec_h264.c | 10 +++---- src/rtp/rtpdec_h264.h | 3 ++ src/rtp/rtpenc_h264.c | 49 ++++++++++++++++++++++++++++++- src/rtp/rtpenc_h264.h | 1 + src/video_decompress/libavcodec.c | 45 ++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 6 deletions(-) diff --git a/src/rtp/rtpdec_h264.c b/src/rtp/rtpdec_h264.c index 7e2d5b6c3..8244c24fc 100644 --- a/src/rtp/rtpdec_h264.c +++ b/src/rtp/rtpdec_h264.c @@ -81,8 +81,8 @@ int fill_coded_frame_from_sps(struct video_frame *rx_data, unsigned char *data, * @retval H.264 or RTP NAL type */ static uint8_t process_nal(uint8_t nal, struct video_frame *frame, uint8_t *data, int data_len) { - uint8_t type = nal & 0x1f; - uint8_t nri = (nal & 0x60) >> 5; + uint8_t type = NALU_HDR_GET_TYPE(nal); + uint8_t nri = NALU_HDR_GET_NRI(nal); log_msg(LOG_LEVEL_DEBUG2, "NAL type %d (nri: %d)\n", (int) type, (int) nri); if (type == NAL_SPS) { @@ -102,7 +102,7 @@ static uint8_t process_nal(uint8_t nal, struct video_frame *frame, uint8_t *data static _Bool decode_nal_unit(struct video_frame *frame, int *total_length, int pass, unsigned char **dst, uint8_t *data, int data_len) { int fu_length = 0; uint8_t nal = data[0]; - uint8_t type = pass == 0 ? process_nal(nal, frame, data, data_len) : nal & 0x1f; + uint8_t type = pass == 0 ? process_nal(nal, frame, data, data_len) : NALU_HDR_GET_TYPE(nal); if (type >= NAL_MIN && type <= NAL_MAX) { type = H264_NAL; } @@ -132,7 +132,7 @@ static _Bool decode_nal_unit(struct video_frame *frame, int *total_length, int p data += 2; data_len -= 2; - log_msg(LOG_LEVEL_DEBUG2, "STAP-A subpacket NAL type %d (nri: %d)\n", (int) (data[0] & 0x1f), (int) ((nal & 0x60) >> 5)); + log_msg(LOG_LEVEL_DEBUG2, "STAP-A subpacket NAL type %d (nri: %d)\n", (int) NALU_HDR_GET_TYPE(data[0]), (int) NALU_HDR_GET_NRI(nal)); if (nal_size <= data_len) { if (pass == 0) { @@ -181,7 +181,7 @@ static _Bool decode_nal_unit(struct video_frame *frame, int *total_length, int p uint8_t fu_header = *data; uint8_t start_bit = fu_header >> 7; uint8_t end_bit = (fu_header & 0x40) >> 6; - uint8_t nal_type = fu_header & 0x1f; + uint8_t nal_type = NALU_HDR_GET_TYPE(fu_header); uint8_t reconstructed_nal; // Reconstruct this packet's true nal; only the data follows. diff --git a/src/rtp/rtpdec_h264.h b/src/rtp/rtpdec_h264.h index beae02b36..8aad4727d 100644 --- a/src/rtp/rtpdec_h264.h +++ b/src/rtp/rtpdec_h264.h @@ -64,6 +64,9 @@ struct decode_data_h264 { struct coded_data; +#define NALU_HDR_GET_TYPE(nal) ((nal) & 0x1FU) +#define NALU_HDR_GET_NRI(nal) (((nal) & 0x60U) >> 5U) + int decode_frame_h264(struct coded_data *cdata, void *decode_data); int width_height_from_SDP(int *widthOut, int *heightOut , unsigned char *data, int data_len); diff --git a/src/rtp/rtpenc_h264.c b/src/rtp/rtpenc_h264.c index 6ae928079..2720bdc40 100644 --- a/src/rtp/rtpenc_h264.c +++ b/src/rtp/rtpenc_h264.c @@ -158,6 +158,53 @@ long rtpenc_h264_frame_parse(struct rtpenc_h264_state *rtpench264state, unsigned return curNALSize(rtpench264state); } +static uint32_t get4Bytes(const unsigned char *ptr) { + return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; +} + +static const unsigned char *get_next_nal(const unsigned char *start, long len, _Bool with_start_code) { + const unsigned char * const stop = start + len; + while (stop - start >= 4) { + uint32_t next4Bytes = get4Bytes(start); + if (next4Bytes == 0x00000001) { + return start + (with_start_code ? 0 : 4); + } + if ((next4Bytes & 0xFFFFFF00) == 0x00000100) { + return start + (with_start_code ? 0 : 3); + } + // We save at least some of "next4Bytes". + if ((unsigned) (next4Bytes & 0xFF) > 1) { + // Common case: 0x00000001 or 0x000001 definitely doesn't begin anywhere in "next4Bytes", so we save all of it: + start += 4; + } else { + // Save the first byte, and continue testing the rest: + start += 1; + } + } + return NULL; +} + +/** + * Returns pointer to next NAL unit in stream (excluding start code). + * + * @param start start of the buffer + * @param len length of the buffer + * @param endptr pointer to store the end of the NAL unit; may be NULL + * @returns NAL unit beginning or NULL if no further NAL unit was found + */ +const unsigned char *rtpenc_h264_get_next_nal(const unsigned char *start, long len, const unsigned char **endptr) { + const unsigned char *nal = get_next_nal(start, len, 0); + if (endptr == NULL) { + return nal; + } + if (nal == NULL) { + return NULL; + } + const unsigned char *end = get_next_nal(nal, len - (nal - start), 1); + *endptr = end ? end : start + len; + return nal; +} + static bool rtpenc_h264_have_seen_eof(struct rtpenc_h264_state *rtpench264state) { return rtpench264state->haveSeenEOF; } @@ -167,7 +214,7 @@ static uint32_t test4Bytes(struct rtpenc_h264_state *rtpench264state) { checkEndOfFrame(rtpench264state, 4); unsigned char const* ptr = nextToParse(rtpench264state); - return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; + return get4Bytes(ptr); } static unsigned char* startOfFrame(struct rtpenc_h264_state *rtpench264state) { return rtpench264state->startOfFrame; diff --git a/src/rtp/rtpenc_h264.h b/src/rtp/rtpenc_h264.h index 3ca6b62d2..fb234c934 100644 --- a/src/rtp/rtpenc_h264.h +++ b/src/rtp/rtpenc_h264.h @@ -61,6 +61,7 @@ struct rtpenc_h264_state; // functions documented at definition struct rtpenc_h264_state *rtpenc_h264_init_state(void *buf, unsigned char *buf_in, long size); long rtpenc_h264_frame_parse(struct rtpenc_h264_state *rtpench264state, unsigned char **start, bool *last); +const unsigned char *rtpenc_h264_get_next_nal(const unsigned char *start, long len, const unsigned char **endptr); #ifdef __cplusplus } diff --git a/src/video_decompress/libavcodec.c b/src/video_decompress/libavcodec.c index 60fc2122e..a7baa92d7 100644 --- a/src/video_decompress/libavcodec.c +++ b/src/video_decompress/libavcodec.c @@ -48,6 +48,8 @@ #include "libavcodec/from_lavc_vid_conv.h" #include "lib_common.h" #include "tv.h" +#include "rtp/rtpdec_h264.h" +#include "rtp/rtpenc_h264.h" #include "utils/misc.h" // get_cpu_core_count() #include "utils/worker.h" #include "video.h" @@ -99,6 +101,8 @@ struct state_libavcodec_decompress { } sws; struct hw_accel_state hwaccel; + + _Bool h264_sps_found; ///< to avoid initial error flood, start decoding after SPS was received }; static enum AVPixelFormat get_format_callback(struct AVCodecContext *s, const enum AVPixelFormat *fmt); @@ -805,6 +809,43 @@ static int change_pixfmt(AVFrame *frame, unsigned char *dst, int av_codec, codec #endif // HAVE_SWSCALE } +/** + * This function handles beginning of H.264 stream that usually floods terminal + * output with errors because it usually doesn't start with IDR frame (even if + * it does, codec probing swallows this). As a workaround, we wait until first + * SPS NAL unit to avoid initial decoding errors. + * + * A drawback may be that it can in theory happen that the SPS NAL unit is not + * at the beginning of the buffer, but it is not the case of libx264 and + * hopefully neither other decoders (if so, it needs to be reworked/removed). + */ +static _Bool check_first_h264_sps(struct state_libavcodec_decompress *s, unsigned char *src, unsigned int src_len) { + if (s->h264_sps_found) { + return 1; + } + _Thread_local static time_ns_t t0; + if (t0 == 0) { + t0 = get_time_in_ns(); + } + if (get_time_in_ns() - t0 > 10 * NS_IN_SEC) { // after 10 seconds surrender and let decoder do the job + log_msg(LOG_LEVEL_WARNING, MOD_NAME "No SPS found, starting decode, anyway. Please report a bug to " PACKAGE_BUGREPORT " if decoding succeeds from now.\n"); + s->h264_sps_found = 1; + return 1; + } + const unsigned char *first_nal = rtpenc_h264_get_next_nal(src, src_len, NULL); + if (!first_nal) { + return 0; + } + int type = NALU_HDR_GET_TYPE(first_nal[0]); + if (type == NAL_SPS || type == NAL_SEI) { + log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Received H.264 SPS NALU, decoding begins...\n"); + s->h264_sps_found = 1; + return 1; + } + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Waiting for first H.264 SPS NALU.\n"); + return 0; +} + static decompress_status libavcodec_decompress(void *state, unsigned char *dst, unsigned char *src, unsigned int src_len, int frame_seq, struct video_frame_callbacks *callbacks, codec_t *internal_codec) { @@ -812,6 +853,10 @@ static decompress_status libavcodec_decompress(void *state, unsigned char *dst, int got_frame = 0; decompress_status res = DECODER_NO_FRAME; + if (s->desc.color_spec == H264 && !check_first_h264_sps(s, src, src_len)) { + return DECODER_NO_FRAME; + } + if (libav_codec_has_extradata(s->desc.color_spec)) { int extradata_size = *(uint32_t *)(void *) src; if (s->codec_ctx == NULL) {