Skip to content

Commit c91306f

Browse files
authored
Update the way timestamp is handled (#1153)
1. When comparing the timestamp and PTS, convert them into AVRational instead of double, so that the comparison is more accurate. 2. Use the same timestamp filtering mechanism in Packets and decoding. 3. Do not use the `trim` filter for timestamp mechanism. Instead filter manually, using the above mechanism. The new implementation gives a result closer to Python's slice notation. For example in one of the test, we used `timestamp=(2.88, 2.89)`. The `trim` filter will discard the frame at 2.88, while the new implementation keeps it.
1 parent e8594b8 commit c91306f

3 files changed

Lines changed: 101 additions & 2 deletions

File tree

src/libspdl/core/detail/ffmpeg/decoder.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "libspdl/core/detail/ffmpeg/ctx_utils.h"
1212
#include "libspdl/core/detail/ffmpeg/logging.h"
13+
#include "libspdl/core/detail/ffmpeg/rational_utils.h"
1314
#include "libspdl/core/detail/tracing.h"
1415

1516
#include <glog/logging.h>
@@ -142,6 +143,10 @@ Rational DecoderImpl<media>::get_output_time_base() const {
142143
return {codec_ctx->time_base.num, codec_ctx->time_base.den};
143144
}
144145

146+
// For audio and image.
147+
// Note: when decoding audio with timestamp, we rely on `atrim` filter
148+
// for handling timestamp.
149+
// This is handled through high-level Python interface.
145150
template <MediaType media>
146151
FramesPtr<media> DecoderImpl<media>::decode_and_flush(
147152
PacketsPtr<media> packets,
@@ -161,6 +166,55 @@ FramesPtr<media> DecoderImpl<media>::decode_and_flush(
161166
return ret;
162167
}
163168

169+
// Specialization for video.
170+
// For video we want to ensure the half-open range.
171+
// Originally we used `trim` filter like how audio is processed above,
172+
// but this was not properly handling the half-open range, so we have
173+
// specialization for video.
174+
template <>
175+
VideoFramesPtr DecoderImpl<MediaType::Video>::decode_and_flush(
176+
VideoPacketsPtr packets,
177+
int num_frames) {
178+
auto time_base = get_output_time_base();
179+
auto [has_timestamp, start, end] =
180+
[&]() -> std::tuple<bool, AVRational, AVRational> {
181+
if (packets->timestamp) {
182+
auto [start_time, end_time] = *packets->timestamp;
183+
return std::make_tuple(
184+
true,
185+
av_d2q(start_time, AV_TIME_BASE),
186+
av_d2q(end_time, AV_TIME_BASE));
187+
}
188+
return std::make_tuple(false, AVRational{-1, 0}, AVRational{1, 0});
189+
}();
190+
191+
auto ret = std::make_unique<VideoFrames>(packets->id, get_output_time_base());
192+
auto gen = decode_packets(
193+
codec_ctx, packets->pkts.get_packets(), filter_graph, true);
194+
int num_yielded = 0;
195+
while (gen) {
196+
// For video, we manualy apply timestamps.
197+
auto frame = gen().release();
198+
if (has_timestamp && frame) {
199+
auto frame_pts = detail::to_rational(frame->pts, time_base);
200+
if (!is_within_window(frame_pts, start, end)) {
201+
av_frame_free(&frame);
202+
continue;
203+
}
204+
}
205+
ret->push_back(frame);
206+
num_yielded += 1;
207+
if (num_frames > 0 && num_yielded >= num_frames) {
208+
break;
209+
}
210+
}
211+
return ret;
212+
}
213+
214+
// For audio and image.
215+
// Note: when decoding audio with timestamp, we rely on `atrim` filter
216+
// for handling timestamp.
217+
// This is handled through high-level Python interface.
164218
template <MediaType media>
165219
FramesPtr<media> DecoderImpl<media>::decode(PacketsPtr<media> packets) {
166220
auto ret =
@@ -173,6 +227,47 @@ FramesPtr<media> DecoderImpl<media>::decode(PacketsPtr<media> packets) {
173227
return ret;
174228
}
175229

230+
// Specialization for video.
231+
// For video we want to ensure the half-open range.
232+
// Originally we used `trim` filter like how audio is processed above,
233+
// but this was not properly handling the half-open range, so we have
234+
// specialization for video.
235+
template <>
236+
VideoFramesPtr DecoderImpl<MediaType::Video>::decode(VideoPacketsPtr packets) {
237+
auto ret = std::make_unique<VideoFrames>(packets->id, get_output_time_base());
238+
auto gen = decode_packets(
239+
codec_ctx, packets->pkts.get_packets(), filter_graph, false);
240+
241+
auto time_base = get_output_time_base();
242+
AVRational start = {0, 1};
243+
AVRational end = {0, 1};
244+
bool has_timestamp = false;
245+
246+
if (packets->timestamp) {
247+
auto [start_time, end_time] = *packets->timestamp;
248+
start = av_d2q(start_time, AV_TIME_BASE);
249+
end = av_d2q(end_time, AV_TIME_BASE);
250+
has_timestamp = true;
251+
}
252+
253+
while (gen) {
254+
auto frame = gen().release();
255+
256+
if (has_timestamp && frame) {
257+
auto frame_pts = AVRational{
258+
static_cast<int>(frame->pts * time_base.num), time_base.den};
259+
260+
if (!is_within_window(frame_pts, start, end)) {
261+
av_frame_free(&frame);
262+
continue;
263+
}
264+
}
265+
266+
ret->push_back(frame);
267+
}
268+
return ret;
269+
}
270+
176271
template <MediaType media>
177272
FramesPtr<media> DecoderImpl<media>::flush() {
178273
auto ret = std::make_unique<Frames<media>>(

src/libspdl/core/packets.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ extract_packets(const VideoPacketsPtr& src, size_t start, size_t end) {
247247
auto ret = std::make_unique<VideoPackets>();
248248
ret->src = src->src;
249249
ret->codec = src->codec;
250-
ret->timestamp = src->timestamp;
250+
// Do not preserve timestamp as indices are already adjusted
251+
ret->timestamp = std::nullopt;
251252
for (size_t t = start; t < end; ++t) {
252253
ret->pkts.push(CHECK_AVALLOCATE(av_packet_clone(src_packets[t])));
253254
}

src/spdl/io/_preprocessing.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,12 @@ def get_filter_desc(
254254
"""
255255
match type(packets):
256256
case _libspdl.AudioPackets:
257+
# When audio packets have `timestamp` attribute, we delegate to `atrim` filter.
257258
return get_audio_filter_desc(timestamp=packets.timestamp, **filter_args)
258259
case _libspdl.VideoPackets:
259-
return get_video_filter_desc(timestamp=packets.timestamp, **filter_args)
260+
# When video packets have `timestamp` attribute, we manually filter the frame
261+
# in the decoding logic, so we don't use `trim` filter.
262+
return get_video_filter_desc(timestamp=None, **filter_args)
260263
case _libspdl.ImagePackets:
261264
return get_video_filter_desc(timestamp=None, **filter_args)
262265
case _:

0 commit comments

Comments
 (0)