Fix MP4 recorder timestamp monotonicity

This commit is contained in:
2026-04-12 22:04:06 +08:00
parent 3e5b720e0e
commit b277ed363f
2 changed files with 60 additions and 9 deletions
+30 -1
View File
@@ -81,6 +81,23 @@ AVRational frame_rate_rational(const float fps) {
return AVRational{scaled, 1000}; 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<std::uint64_t>(frame_rate.den) * kNanosPerSecond;
const auto denominator = static_cast<std::uint64_t>(frame_rate.num);
if (denominator == 0) {
return 33'333'333ull;
}
const auto interval = numerator / denominator;
return interval == 0 ? 1ull : interval;
}
[[nodiscard]] [[nodiscard]]
std::vector<EncoderCandidate> encoder_candidates(const CodecType codec, const EncoderDeviceType device) { std::vector<EncoderCandidate> encoder_candidates(const CodecType codec, const EncoderDeviceType device) {
const std::string hardware_name = codec == CodecType::H265 ? "hevc_nvenc" : "h264_nvenc"; const std::string hardware_name = codec == CodecType::H265 ? "hevc_nvenc" : "h264_nvenc";
@@ -303,6 +320,8 @@ struct Mp4RecordWriter::Impl {
codec = codec_arg; codec = codec_arg;
frame_rate = frame_rate_rational(fps); 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; input_pixel_format = input_pixel_format_arg;
source_pixel_format = av_pixel_format(input_pixel_format); source_pixel_format = av_pixel_format(input_pixel_format);
if (source_pixel_format == AV_PIX_FMT_NONE) { if (source_pixel_format == AV_PIX_FMT_NONE) {
@@ -439,7 +458,13 @@ struct Mp4RecordWriter::Impl {
frame->data, frame->data,
frame->linesize); frame->linesize);
frame->pts = static_cast<std::int64_t>(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<std::int64_t>(normalized_timestamp_ns);
last_frame_pts_ns = normalized_timestamp_ns;
const auto send_result = avcodec_send_frame(encoder_context, frame); const auto send_result = avcodec_send_frame(encoder_context, frame);
if (send_result < 0) { if (send_result < 0) {
@@ -534,6 +559,8 @@ struct Mp4RecordWriter::Impl {
encoder_name.clear(); encoder_name.clear();
using_hardware = false; using_hardware = false;
trailer_written = false; trailer_written = false;
frame_period = 33'333'333ull;
last_frame_pts_ns.reset();
resolved_settings = ResolvedEncoderSettings{}; resolved_settings = ResolvedEncoderSettings{};
} }
@@ -551,6 +578,8 @@ struct Mp4RecordWriter::Impl {
AVPixelFormat encoder_pixel_format{AV_PIX_FMT_NONE}; AVPixelFormat encoder_pixel_format{AV_PIX_FMT_NONE};
AVPixelFormat source_pixel_format{AV_PIX_FMT_NONE}; AVPixelFormat source_pixel_format{AV_PIX_FMT_NONE};
AVRational frame_rate{30, 1}; AVRational frame_rate{30, 1};
std::uint64_t frame_period{33'333'333ull};
std::optional<std::uint64_t> last_frame_pts_ns{};
std::string encoder_name{}; std::string encoder_name{};
ResolvedEncoderSettings resolved_settings{}; ResolvedEncoderSettings resolved_settings{};
bool using_hardware{false}; bool using_hardware{false};
+30 -8
View File
@@ -64,6 +64,23 @@ AVRational frame_rate_rational(const float fps) {
return AVRational{scaled, 1000}; 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<std::uint64_t>(frame_rate.den) * 1'000'000'000ull;
const auto denominator = static_cast<std::uint64_t>(frame_rate.num);
if (denominator == 0) {
return 33'333'333ull;
}
const auto interval = numerator / denominator;
return interval == 0 ? 1ull : interval;
}
[[nodiscard]] [[nodiscard]]
std::vector<EncoderCandidate> encoder_candidates(const CodecType codec, const EncoderDeviceType device) { std::vector<EncoderCandidate> encoder_candidates(const CodecType codec, const EncoderDeviceType device) {
const std::string hardware_name = codec == CodecType::H265 ? "hevc_nvenc" : "h264_nvenc"; const std::string hardware_name = codec == CodecType::H265 ? "hevc_nvenc" : "h264_nvenc";
@@ -314,6 +331,8 @@ struct Mp4Writer::Impl {
codec = codec_arg; codec = codec_arg;
frame_rate = frame_rate_rational(fps); 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); auto encoder = open_encoder(codec, encoder_device, width, height, frame_rate, tuning);
if (!encoder) { if (!encoder) {
return std::unexpected(encoder.error()); return std::unexpected(encoder.error());
@@ -447,7 +466,13 @@ struct Mp4Writer::Impl {
frame->data, frame->data,
frame->linesize); frame->linesize);
frame->pts = static_cast<std::int64_t>(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<std::int64_t>(normalized_timestamp_ns);
last_frame_pts_ns = normalized_timestamp_ns;
const auto send_result = avcodec_send_frame(encoder_context, frame); const auto send_result = avcodec_send_frame(encoder_context, frame);
if (send_result < 0) { if (send_result < 0) {
@@ -542,6 +567,8 @@ struct Mp4Writer::Impl {
encoder_name.clear(); encoder_name.clear();
using_hardware = false; using_hardware = false;
trailer_written = false; trailer_written = false;
frame_period = 33'333'333ull;
last_frame_pts_ns.reset();
resolved_settings = ResolvedEncoderSettings{}; resolved_settings = ResolvedEncoderSettings{};
} }
@@ -558,6 +585,8 @@ struct Mp4Writer::Impl {
SwsContext *scaler{nullptr}; SwsContext *scaler{nullptr};
AVPixelFormat encoder_pixel_format{AV_PIX_FMT_NONE}; AVPixelFormat encoder_pixel_format{AV_PIX_FMT_NONE};
AVRational frame_rate{30, 1}; AVRational frame_rate{30, 1};
std::uint64_t frame_period{33'333'333ull};
std::optional<std::uint64_t> last_frame_pts_ns{};
std::string encoder_name{}; std::string encoder_name{};
ResolvedEncoderSettings resolved_settings{}; ResolvedEncoderSettings resolved_settings{};
bool using_hardware{false}; bool using_hardware{false};
@@ -636,13 +665,6 @@ std::string_view tune_name(const TuneKind tune) {
return "low-latency"; return "low-latency";
} }
std::uint64_t frame_period_ns(const float fps) {
if (!(fps > 0.0f)) {
return 33'333'333ull;
}
return static_cast<std::uint64_t>(std::llround(1'000'000'000.0 / static_cast<double>(fps)));
}
std::filesystem::path derive_output_path(const std::filesystem::path &input_path) { std::filesystem::path derive_output_path(const std::filesystem::path &input_path) {
auto output_path = input_path; auto output_path = input_path;
output_path.replace_extension(".mp4"); output_path.replace_extension(".mp4");