summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2026-04-18 13:39:33 +0200
committerCarl Hetherington <cth@carlh.net>2026-04-23 16:49:48 +0200
commit9f828b327e680c322a70883910233337a37e481e (patch)
treee7fb8e4c3ef3ed6324cc1a9e25fd97af3c8bd5e1
parent2666e3232a9520c3b0fa94c48a689880cdde13fc (diff)
Fix late subtitles when they are muxed late with respect to the video.
In one example we have the sequence video 3088,377 sub 3087,334 sub 3088,710 video 3088,419 so the 3087,334 sub is very late. Here we insert a queue to bring subtitle packets a little forward for processing. There is already a similar thing in the player (_delay) but adding a longer delay there seems wasteful because a) the video is by that point already decompressed and b) this problem only applies to FFmpeg-decoded files (and then, I think only if we are previewing or burning in subtitles).
-rw-r--r--src/lib/ffmpeg_decoder.cc69
-rw-r--r--src/lib/ffmpeg_decoder.h7
-rw-r--r--src/lib/packet_queue.cc35
-rw-r--r--src/lib/packet_queue.h83
-rw-r--r--src/lib/passthrough_packet_queue.cc56
-rw-r--r--src/lib/passthrough_packet_queue.h45
-rw-r--r--src/lib/subtitle_sync_packet_queue.cc99
-rw-r--r--src/lib/subtitle_sync_packet_queue.h62
-rw-r--r--src/lib/wscript3
-rw-r--r--test/packet_queue_test.cc116
-rw-r--r--test/wscript1
11 files changed, 568 insertions, 8 deletions
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index bb2edc31c..d37b59fa9 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -38,7 +38,9 @@
#include "frame_interval_checker.h"
#include "image.h"
#include "log.h"
+#include "passthrough_packet_queue.h"
#include "raw_image_proxy.h"
+#include "subtitle_sync_packet_queue.h"
#include "text_content.h"
#include "text_decoder.h"
#include "util.h"
@@ -104,6 +106,12 @@ FFmpegDecoder::FFmpegDecoder(shared_ptr<const Film> film, shared_ptr<const FFmpe
}
_dropped_time.resize(_format_context->nb_streams);
+
+ if (video && !text.empty()) {
+ _packet_queue.reset(new SubtitleSyncPacketQueue());
+ } else {
+ _packet_queue.reset(new PassthroughPacketQueue());
+ }
}
@@ -113,6 +121,12 @@ FFmpegDecoder::flush()
LOG_DEBUG_PLAYER("DEC: Flush FFmpeg decoder: current state {}", static_cast<int>(_flush_state));
switch (_flush_state) {
+ case FlushState::PACKET_QUEUE:
+ if (!process_from_packet_queue(true)) {
+ LOG_DEBUG_PLAYER("DEC: Finished flushing packets");
+ _flush_state = FlushState::CODECS;
+ }
+ break;
case FlushState::CODECS:
if (flush_codecs() == FlushResult::DONE) {
LOG_DEBUG_PLAYER("DEC: Finished flushing codecs");
@@ -209,6 +223,41 @@ FFmpegDecoder::flush_fill()
bool
+FFmpegDecoder::process_from_packet_queue(bool flushing)
+{
+ auto process = _packet_queue->get(flushing);
+ if (!process) {
+ return false;
+ }
+
+ auto packet = boost::get<AVPacket*>(&process->first);
+
+ switch (process->second) {
+ case PacketQueue::Type::VIDEO:
+ decode_and_process_video_packet(*packet);
+ break;
+ case PacketQueue::Type::SUBTITLE:
+ decode_and_process_subtitle_packet(*packet);
+ break;
+ case PacketQueue::Type::AUDIO:
+ decode_and_process_audio_packet(*packet);
+ break;
+ case PacketQueue::Type::DROP:
+ {
+ auto info = boost::get<PacketQueue::PacketInfo>(process->first);
+ DCPOMATIC_ASSERT(static_cast<int>(_dropped_time.size()) > info.stream_index);
+ _dropped_time[info.stream_index] = dcpomatic::ContentTime::from_seconds(info.dts * av_q2d(_format_context->streams[info.stream_index]->time_base) + _pts_offset.seconds());
+ break;
+ }
+ }
+
+ av_packet_free(packet);
+
+ return true;
+}
+
+
+bool
FFmpegDecoder::pass()
{
auto packet = av_packet_alloc();
@@ -238,15 +287,21 @@ FFmpegDecoder::pass()
int const si = packet->stream_index;
auto fc = _ffmpeg_content;
+ optional<PacketQueue::Type> type;
+
if (_video_stream && si == _video_stream.get() && video && !video->ignore()) {
- decode_and_process_video_packet(packet);
+ type = PacketQueue::Type::VIDEO;
} else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index(_format_context, si) && !only_text()->ignore()) {
- decode_and_process_subtitle_packet(packet);
+ type = PacketQueue::Type::SUBTITLE;
} else if (audio) {
- decode_and_process_audio_packet(packet);
+ type = PacketQueue::Type::AUDIO;
} else {
- DCPOMATIC_ASSERT(static_cast<int>(_dropped_time.size()) > si);
- _dropped_time[si] = dcpomatic::ContentTime::from_seconds(packet->dts * av_q2d(_format_context->streams[si]->time_base) + _pts_offset.seconds());
+ type = PacketQueue::Type::DROP;
+ }
+
+ if (type) {
+ _packet_queue->add(packet, *type);
+ process_from_packet_queue(false);
}
if (_have_current_subtitle && _current_subtitle_to && position() > *_current_subtitle_to) {
@@ -254,7 +309,6 @@ FFmpegDecoder::pass()
_have_current_subtitle = false;
}
- av_packet_free(&packet);
return false;
}
@@ -407,7 +461,8 @@ FFmpegDecoder::seek(ContentTime time, bool accurate)
{
Decoder::seek(time, accurate);
- _flush_state = FlushState::CODECS;
+ _flush_state = FlushState::PACKET_QUEUE;
+ _packet_queue->clear();
/* If we are doing an `accurate' seek, we need to use pre-roll, as
we don't really know what the seek will give us.
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 498bb11a8..795320280 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -27,6 +27,7 @@
#include "bitmap_text.h"
#include "decoder.h"
#include "ffmpeg.h"
+#include "packet_queue.h"
#include "video_filter_graph_set.h"
extern "C" {
#include <libavcodec/avcodec.h>
@@ -39,6 +40,7 @@ class AudioBuffers;
class FFmpegAudioStream;
class Image;
class Log;
+class PacketQueue;
class VideoFilterGraph;
struct ffmpeg_pts_offset_test;
@@ -64,6 +66,7 @@ private:
};
FlushResult flush();
+ bool process_from_packet_queue(bool flushing);
AVSampleFormat audio_sample_format(std::shared_ptr<FFmpegAudioStream> stream) const;
int bytes_per_audio_sample(std::shared_ptr<FFmpegAudioStream> stream) const;
@@ -97,12 +100,14 @@ private:
std::map<std::shared_ptr<FFmpegAudioStream>, boost::optional<dcpomatic::ContentTime>> _next_time;
enum class FlushState {
+ PACKET_QUEUE,
CODECS,
AUDIO_DECODER,
FILL,
};
- FlushState _flush_state = FlushState::CODECS;
+ FlushState _flush_state = FlushState::PACKET_QUEUE;
std::vector<boost::optional<dcpomatic::ContentTime>> _dropped_time;
+ std::unique_ptr<PacketQueue> _packet_queue;
};
diff --git a/src/lib/packet_queue.cc b/src/lib/packet_queue.cc
new file mode 100644
index 000000000..9cdc7bc53
--- /dev/null
+++ b/src/lib/packet_queue.cc
@@ -0,0 +1,35 @@
+/*
+ Copyright (C) 2026 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "packet_queue.h"
+extern "C" {
+#include <libavcodec/packet.h>
+}
+
+
+PacketQueue::PacketInfo::PacketInfo(AVPacket* packet)
+ : stream_index(packet->stream_index)
+ , dts(packet->dts)
+{
+
+}
+
+
diff --git a/src/lib/packet_queue.h b/src/lib/packet_queue.h
new file mode 100644
index 000000000..60bd4e8af
--- /dev/null
+++ b/src/lib/packet_queue.h
@@ -0,0 +1,83 @@
+/*
+ Copyright (C) 2026 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_PACKET_QUEUE_H
+#define DCPOMATIC_PACKET_QUEUE_H
+
+
+/** @file src/lib/packet_queue.h
+ * @brief PacketQueue parent class.
+ */
+
+
+#include <boost/optional.hpp>
+#include <boost/variant.hpp>
+#include <cstdint>
+#include <deque>
+
+struct AVPacket;
+
+
+/** @class PacketQueue
+ * @brief Parent class for things which take and then return AVPackets, possibly
+ * re-ordering them.
+ */
+class PacketQueue
+{
+public:
+ virtual ~PacketQueue() = default;
+
+ enum class Type {
+ VIDEO,
+ AUDIO,
+ SUBTITLE,
+ DROP,
+ };
+
+ /** Container for the only information we need to keep from a DROP packet */
+ struct PacketInfo {
+ PacketInfo(AVPacket* packet);
+
+ int stream_index;
+ int64_t dts;
+ };
+
+ typedef boost::variant<AVPacket*, PacketInfo> Packet;
+
+ /** Add a packet to the queue. Does not ref the packet; we expect
+ * the packet to be freed when it comes out of get() (or by clear()).
+ */
+ virtual void add(AVPacket* packet, Type type) = 0;
+
+ /** Get the next packet to process.
+ * @param flushing should be true if we are flushing at the end of a decode.
+ * When this is true the queue will be emptied without trying to re-order it.
+ * Returns boost::none when there are no more packets to get.
+ */
+ virtual boost::optional<std::pair<Packet, Type>> get(bool flushing) = 0;
+
+ /** Clear the queue. Packets will be freed. */
+ virtual void clear() = 0;
+};
+
+
+#endif
+
diff --git a/src/lib/passthrough_packet_queue.cc b/src/lib/passthrough_packet_queue.cc
new file mode 100644
index 000000000..03038ff5f
--- /dev/null
+++ b/src/lib/passthrough_packet_queue.cc
@@ -0,0 +1,56 @@
+/*
+ Copyright (C) 2026 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_assert.h"
+#include "passthrough_packet_queue.h"
+#include <boost/core/exchange.hpp>
+#include <boost/optional.hpp>
+#include <utility>
+
+
+using boost::optional;
+
+
+void
+PassthroughPacketQueue::add(AVPacket* packet, Type type)
+{
+ DCPOMATIC_ASSERT(!_store);
+ if (type == PacketQueue::Type::DROP) {
+ _store = std::make_pair(PacketInfo(packet), type);
+ } else {
+ _store = std::make_pair(Packet(packet), type);
+ }
+}
+
+
+optional<std::pair<PacketQueue::Packet, PacketQueue::Type>>
+PassthroughPacketQueue::get(bool)
+{
+ return boost::exchange(_store, boost::none);
+}
+
+
+void
+PassthroughPacketQueue::clear()
+{
+ _store = boost::none;
+}
+
diff --git a/src/lib/passthrough_packet_queue.h b/src/lib/passthrough_packet_queue.h
new file mode 100644
index 000000000..56ebf3867
--- /dev/null
+++ b/src/lib/passthrough_packet_queue.h
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2026 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "packet_queue.h"
+#include <boost/optional.hpp>
+
+
+class PassthroughPacketQueue : public PacketQueue
+{
+public:
+ void add(AVPacket* packet, Type type) override;
+
+ /** Get the next packet to process.
+ * @param flushing should be true if we are flushing at the end of a decode.
+ * When this is true the queue will be emptied without trying to re-order it.
+ * Returns { nullptr, VIDEO } when there are no more packets to get.
+ */
+ boost::optional<std::pair<Packet, Type>> get(bool flushing) override;
+
+ /** Clear the queue. Packets will be freed. */
+ void clear() override;
+
+private:
+ boost::optional<std::pair<Packet, Type>> _store;
+};
+
+
diff --git a/src/lib/subtitle_sync_packet_queue.cc b/src/lib/subtitle_sync_packet_queue.cc
new file mode 100644
index 000000000..6a96924df
--- /dev/null
+++ b/src/lib/subtitle_sync_packet_queue.cc
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2026 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_log.h"
+#include "subtitle_sync_packet_queue.h"
+extern "C" {
+#include <libavcodec/packet.h>
+#include <libavutil/avutil.h>
+}
+#include <iostream>
+
+
+using boost::optional;
+
+
+void
+SubtitleSyncPacketQueue::add(AVPacket* packet, Type type)
+{
+ switch (type) {
+ case Type::VIDEO:
+ ++_num_video;
+ _other.push_back({packet, type});
+ break;
+ case Type::AUDIO:
+ _other.push_back({packet, type});
+ break;
+ case Type::DROP:
+ _other.push_back({PacketInfo(packet), type});
+ av_packet_free(&packet);
+ break;
+ case Type::SUBTITLE:
+ _subtitle.push_back(packet);
+ break;
+ }
+}
+
+
+optional<std::pair<PacketQueue::Packet, PacketQueue::Type>>
+SubtitleSyncPacketQueue::get(bool flushing)
+{
+ if (!_subtitle.empty()) {
+ /* Any subtitle packets we have get returned first */
+ auto packet = _subtitle.front();
+ _subtitle.pop_front();
+ return std::make_pair(Packet(packet), Type::SUBTITLE);
+ }
+
+ if (_other.size() > 4096) {
+ LOG_WARNING("SubtitleSyncPacketQueue is getting large: {} (_num_video={})", _other.size(), _num_video);
+ }
+
+ if ((!flushing && _num_video < 48 && _other.size() < 8192) || _other.empty()) {
+ /* We haven't queued up enough video yet, or we don't have anything */
+ return boost::none;
+ }
+
+ auto packet = _other.front();
+ if (packet.second == Type::VIDEO) {
+ --_num_video;
+ }
+ _other.pop_front();
+ return packet;
+}
+
+
+void
+SubtitleSyncPacketQueue::clear()
+{
+ for (auto i: _other) {
+ if (auto packet = boost::get<AVPacket*>(&i.first)) {
+ av_packet_free(packet);
+ }
+ }
+ for (auto i: _subtitle) {
+ av_packet_free(&i);
+ }
+ _other.clear();
+ _subtitle.clear();
+ _num_video = 0;
+}
+
diff --git a/src/lib/subtitle_sync_packet_queue.h b/src/lib/subtitle_sync_packet_queue.h
new file mode 100644
index 000000000..203b99bf2
--- /dev/null
+++ b/src/lib/subtitle_sync_packet_queue.h
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2026 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+/** @file src/lib/subtitle_sync_packet_queue.h
+ * @brief SubtitleSyncPacketQueue class.
+ */
+
+
+#include "packet_queue.h"
+#include <boost/variant.hpp>
+#include <cstdint>
+#include <deque>
+
+struct AVPacket;
+
+
+/** @class PacketQueue
+ * @brief A queue of FFmpeg packets, used to re-order them so that
+ * subtitles do not arrive too late.
+ */
+class SubtitleSyncPacketQueue : public PacketQueue
+{
+public:
+ /** Add a packet to the queue. Does not ref the packet; we expect
+ * the packet to be freed when it comes out of get() (or by clear()).
+ */
+ void add(AVPacket* packet, Type type) override;
+
+ /** Get the next packet to process.
+ * @param flushing should be true if we are flushing at the end of a decode.
+ * When this is true the queue will be emptied without trying to re-order it.
+ * Returns boost::none when there are no more packets to get.
+ */
+ boost::optional<std::pair<Packet, Type>> get(bool flushing) override;
+
+ /** Clear the queue. Packets will be freed. */
+ void clear() override;
+
+private:
+ int _num_video = 0;
+ std::deque<AVPacket*> _subtitle;
+ std::deque<std::pair<Packet, Type>> _other;
+};
+
diff --git a/src/lib/wscript b/src/lib/wscript
index 8e4b4d783..44b77729e 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -170,6 +170,8 @@ sources = """
mpeg2_encoder.cc
named_channel.cc
overlaps.cc
+ packet_queue.cc
+ passthrough_packet_queue.cc
pixel_quanta.cc
player.cc
player_video.cc
@@ -208,6 +210,7 @@ sources = """
string_text_file_decoder.cc
subtitle_analysis.cc
subtitle_film_encoder.cc
+ subtitle_sync_packet_queue.cc
territory_type.cc
text_ring_buffers.cc
text_type.cc
diff --git a/test/packet_queue_test.cc b/test/packet_queue_test.cc
new file mode 100644
index 000000000..ae1cc5b22
--- /dev/null
+++ b/test/packet_queue_test.cc
@@ -0,0 +1,116 @@
+/*
+ Copyright (C) 2026 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/subtitle_sync_packet_queue.h"
+extern "C" {
+#include <libavcodec/packet.h>
+}
+#include <boost/test/unit_test.hpp>
+
+
+BOOST_AUTO_TEST_CASE(subtitle_sync_packet_queue_test)
+{
+ SubtitleSyncPacketQueue queue;
+ std::vector<std::pair<AVPacket*, PacketQueue::Type>> expected;
+
+ auto add_video = [&queue, &expected]() {
+ auto packet = av_packet_alloc();
+ queue.add(packet, PacketQueue::Type::VIDEO);
+ expected.push_back(std::make_pair(packet, PacketQueue::Type::VIDEO));
+ return packet;
+ };
+
+ auto add_audio = [&queue, &expected]() {
+ auto packet = av_packet_alloc();
+ queue.add(packet, PacketQueue::Type::AUDIO);
+ expected.push_back(std::make_pair(packet, PacketQueue::Type::AUDIO));
+ return packet;
+ };
+
+ auto add_subtitle = [&queue]() {
+ auto packet = av_packet_alloc();
+ queue.add(packet, PacketQueue::Type::SUBTITLE);
+ return packet;
+ };
+
+ auto check = [&queue](bool flush, std::pair<AVPacket*, PacketQueue::Type> ref) {
+ auto check = queue.get(flush);
+ BOOST_CHECK(check.has_value());
+ BOOST_CHECK(boost::get<AVPacket*>(check->first) == ref.first);
+ BOOST_CHECK(check->second == ref.second);
+ };
+
+ for (int i = 0; i < 24; ++i) {
+ add_video();
+ add_audio();
+ add_audio();
+ }
+
+ BOOST_CHECK(queue.get(false) == boost::none);
+
+ for (int i = 0; i < 23; ++i) {
+ add_video();
+ add_audio();
+ add_audio();
+ }
+
+ BOOST_CHECK(queue.get(false) == boost::none);
+
+ auto iter = expected.begin();
+
+ add_video();
+ check(false, *iter);
+ ++iter;
+
+ auto sub1 = add_subtitle();
+ auto sub2 = add_subtitle();
+
+ for (int i = 0; i < 24; ++i) {
+ add_video();
+ add_audio();
+ add_audio();
+ }
+
+ check(false, { sub1, PacketQueue::Type::SUBTITLE });
+ check(false, { sub2, PacketQueue::Type::SUBTITLE });
+
+ for (int i = 0; i < 24; ++i) {
+ check(false, *iter);
+ ++iter;
+ check(false, *iter);
+ ++iter;
+ check(false, *iter);
+ ++iter;
+ }
+
+ for (int i = 0; i < 47; ++i) {
+ check(true, *iter);
+ BOOST_REQUIRE(iter != expected.end());
+ ++iter;
+ check(true, *iter);
+ BOOST_REQUIRE(iter != expected.end());
+ ++iter;
+ check(true, *iter);
+ BOOST_REQUIRE(iter != expected.end());
+ ++iter;
+ }
+}
+
diff --git a/test/wscript b/test/wscript
index 1390beebb..341c407e7 100644
--- a/test/wscript
+++ b/test/wscript
@@ -141,6 +141,7 @@ def build(bld):
open_caption_test.cc
optimise_stills_test.cc
overlap_video_test.cc
+ packet_queue_test.cc
pixel_formats_test.cc
player_test.cc
playlist_test.cc