diff --git a/scripts/fault_suite.sh b/scripts/fault_suite.sh index 431cf32..6512582 100755 --- a/scripts/fault_suite.sh +++ b/scripts/fault_suite.sh @@ -238,6 +238,18 @@ EOF --frame-interval-ms 1 \ --linger-ms 0 + run_expected_failure 8 "libavformat_connection_refused" "libavformat connection refusal surfaces without crashing" \ + "failed to open RTMP output 'rtmp://127.0.0.1:1/live/test': Connection refused" \ + "${RTMP_OUTPUT_TESTER}" \ + --rtmp-url rtmp://127.0.0.1:1/live/test \ + --transport libavformat \ + --codec h264 \ + --frames 1 \ + --width 320 \ + --height 240 \ + --frame-interval-ms 1 \ + --linger-ms 0 + local finished_at_utc finished_at_utc="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" @@ -294,7 +306,7 @@ PY echo "counts_pass=${pass_count}" echo "counts_fail=${fail_count}" echo "all_pass=${all_pass}" - echo "scenarios=removed_encoder_backend,removed_rtmp_transport,removed_rtmp_mode_cli,removed_rtmp_mode_toml,missing_rtmp_url,invalid_rtp_endpoint,ffmpeg_process_bad_binary" + echo "scenarios=removed_encoder_backend,removed_rtmp_transport,removed_rtmp_mode_cli,removed_rtmp_mode_toml,missing_rtmp_url,invalid_rtp_endpoint,ffmpeg_process_bad_binary,libavformat_connection_refused" } > "${EVIDENCE_TEXT}" if [[ "${all_pass}" == "true" ]]; then diff --git a/scripts/fault_summary_helper.py b/scripts/fault_summary_helper.py index ed34a20..84c72b2 100755 --- a/scripts/fault_summary_helper.py +++ b/scripts/fault_summary_helper.py @@ -86,7 +86,7 @@ def build_summary(args: CliArgs) -> dict[str, object]: pass_count = sum(1 for row in rows if row["status"] == "PASS") fail_count = sum(1 for row in rows if row["status"] == "FAIL") skip_count = sum(1 for row in rows if row["status"] == "SKIP") - all_pass = len(rows) == 7 and pass_count == 7 and fail_count == 0 and skip_count == 0 + all_pass = len(rows) > 0 and pass_count == len(rows) and fail_count == 0 and skip_count == 0 return { "run_id": args.run_id, diff --git a/src/protocol/rtmp_output.cpp b/src/protocol/rtmp_output.cpp index a0fff6c..704d273 100644 --- a/src/protocol/rtmp_output.cpp +++ b/src/protocol/rtmp_output.cpp @@ -230,6 +230,8 @@ public: std::string url{}; AVFormatContext *format_context{nullptr}; AVStream *video_stream{nullptr}; + bool io_opened{false}; + bool header_written{false}; }; LibavformatRtmpOutput() = default; @@ -368,6 +370,7 @@ private: ERR_NETWORK, "failed to open RTMP output '" + url + "': " + av_error_string(open_result)); } + session.io_opened = true; } // RTMP sockets are non-seekable, so the FLV muxer must not try to backfill @@ -388,6 +391,7 @@ private: ERR_PROTOCOL, "failed to write RTMP header for '" + url + "': " + av_error_string(header_result)); } + session.header_written = true; spdlog::info( "RTMP_OUTPUT_READY backend=libavformat codec={} url={}", @@ -401,9 +405,15 @@ private: return; } - av_write_trailer(session.format_context); - if (!(session.format_context->oformat->flags & AVFMT_NOFILE) && session.format_context->pb != nullptr) { + if (session.header_written) { + av_write_trailer(session.format_context); + session.header_written = false; + } + if (session.io_opened && + !(session.format_context->oformat->flags & AVFMT_NOFILE) && + session.format_context->pb != nullptr) { avio_closep(&session.format_context->pb); + session.io_opened = false; } avformat_free_context(session.format_context); session.format_context = nullptr;