diff --git a/src/record/mp4_record_writer.cpp b/src/record/mp4_record_writer.cpp index ddb75f6..d663265 100644 --- a/src/record/mp4_record_writer.cpp +++ b/src/record/mp4_record_writer.cpp @@ -81,6 +81,23 @@ AVRational frame_rate_rational(const float fps) { return AVRational{scaled, 1000}; } +[[nodiscard]] +std::uint64_t frame_period_ns(const AVRational frame_rate) { + if (frame_rate.num <= 0 || frame_rate.den <= 0) { + return 33'333'333ull; + } + + const auto numerator = + static_cast(frame_rate.den) * kNanosPerSecond; + const auto denominator = static_cast(frame_rate.num); + if (denominator == 0) { + return 33'333'333ull; + } + + const auto interval = numerator / denominator; + return interval == 0 ? 1ull : interval; +} + [[nodiscard]] std::vector encoder_candidates(const CodecType codec, const EncoderDeviceType device) { const std::string hardware_name = codec == CodecType::H265 ? "hevc_nvenc" : "h264_nvenc"; @@ -303,6 +320,8 @@ struct Mp4RecordWriter::Impl { codec = codec_arg; frame_rate = frame_rate_rational(fps); + frame_period = frame_period_ns(frame_rate); + last_frame_pts_ns.reset(); input_pixel_format = input_pixel_format_arg; source_pixel_format = av_pixel_format(input_pixel_format); if (source_pixel_format == AV_PIX_FMT_NONE) { @@ -439,7 +458,13 @@ struct Mp4RecordWriter::Impl { frame->data, frame->linesize); - frame->pts = static_cast(relative_timestamp_ns); + auto normalized_timestamp_ns = relative_timestamp_ns; + if (last_frame_pts_ns && normalized_timestamp_ns <= *last_frame_pts_ns) { + normalized_timestamp_ns = *last_frame_pts_ns + frame_period; + } + + frame->pts = static_cast(normalized_timestamp_ns); + last_frame_pts_ns = normalized_timestamp_ns; const auto send_result = avcodec_send_frame(encoder_context, frame); if (send_result < 0) { @@ -534,6 +559,8 @@ struct Mp4RecordWriter::Impl { encoder_name.clear(); using_hardware = false; trailer_written = false; + frame_period = 33'333'333ull; + last_frame_pts_ns.reset(); resolved_settings = ResolvedEncoderSettings{}; } @@ -551,6 +578,8 @@ struct Mp4RecordWriter::Impl { AVPixelFormat encoder_pixel_format{AV_PIX_FMT_NONE}; AVPixelFormat source_pixel_format{AV_PIX_FMT_NONE}; AVRational frame_rate{30, 1}; + std::uint64_t frame_period{33'333'333ull}; + std::optional last_frame_pts_ns{}; std::string encoder_name{}; ResolvedEncoderSettings resolved_settings{}; bool using_hardware{false}; diff --git a/src/tools/zed_svo_mp4_support.cpp b/src/tools/zed_svo_mp4_support.cpp index c040583..1e43f5d 100644 --- a/src/tools/zed_svo_mp4_support.cpp +++ b/src/tools/zed_svo_mp4_support.cpp @@ -64,6 +64,23 @@ AVRational frame_rate_rational(const float fps) { return AVRational{scaled, 1000}; } +[[nodiscard]] +std::uint64_t frame_period_ns(const AVRational frame_rate) { + if (frame_rate.num <= 0 || frame_rate.den <= 0) { + return 33'333'333ull; + } + + const auto numerator = + static_cast(frame_rate.den) * 1'000'000'000ull; + const auto denominator = static_cast(frame_rate.num); + if (denominator == 0) { + return 33'333'333ull; + } + + const auto interval = numerator / denominator; + return interval == 0 ? 1ull : interval; +} + [[nodiscard]] std::vector encoder_candidates(const CodecType codec, const EncoderDeviceType device) { const std::string hardware_name = codec == CodecType::H265 ? "hevc_nvenc" : "h264_nvenc"; @@ -314,6 +331,8 @@ struct Mp4Writer::Impl { codec = codec_arg; frame_rate = frame_rate_rational(fps); + frame_period = frame_period_ns(frame_rate); + last_frame_pts_ns.reset(); auto encoder = open_encoder(codec, encoder_device, width, height, frame_rate, tuning); if (!encoder) { return std::unexpected(encoder.error()); @@ -447,7 +466,13 @@ struct Mp4Writer::Impl { frame->data, frame->linesize); - frame->pts = static_cast(relative_timestamp_ns); + auto normalized_timestamp_ns = relative_timestamp_ns; + if (last_frame_pts_ns && normalized_timestamp_ns <= *last_frame_pts_ns) { + normalized_timestamp_ns = *last_frame_pts_ns + frame_period; + } + + frame->pts = static_cast(normalized_timestamp_ns); + last_frame_pts_ns = normalized_timestamp_ns; const auto send_result = avcodec_send_frame(encoder_context, frame); if (send_result < 0) { @@ -542,6 +567,8 @@ struct Mp4Writer::Impl { encoder_name.clear(); using_hardware = false; trailer_written = false; + frame_period = 33'333'333ull; + last_frame_pts_ns.reset(); resolved_settings = ResolvedEncoderSettings{}; } @@ -558,6 +585,8 @@ struct Mp4Writer::Impl { SwsContext *scaler{nullptr}; AVPixelFormat encoder_pixel_format{AV_PIX_FMT_NONE}; AVRational frame_rate{30, 1}; + std::uint64_t frame_period{33'333'333ull}; + std::optional last_frame_pts_ns{}; std::string encoder_name{}; ResolvedEncoderSettings resolved_settings{}; bool using_hardware{false}; @@ -636,13 +665,6 @@ std::string_view tune_name(const TuneKind tune) { return "low-latency"; } -std::uint64_t frame_period_ns(const float fps) { - if (!(fps > 0.0f)) { - return 33'333'333ull; - } - return static_cast(std::llround(1'000'000'000.0 / static_cast(fps))); -} - std::filesystem::path derive_output_path(const std::filesystem::path &input_path) { auto output_path = input_path; output_path.replace_extension(".mp4");