summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
9 files changed, 451 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