Fix MP4 recorder timestamp monotonicity
This commit is contained in:
@@ -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};
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user