diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/ffmpeg_decoder.cc | 69 | ||||
| -rw-r--r-- | src/lib/ffmpeg_decoder.h | 7 | ||||
| -rw-r--r-- | src/lib/packet_queue.cc | 35 | ||||
| -rw-r--r-- | src/lib/packet_queue.h | 83 | ||||
| -rw-r--r-- | src/lib/passthrough_packet_queue.cc | 56 | ||||
| -rw-r--r-- | src/lib/passthrough_packet_queue.h | 45 | ||||
| -rw-r--r-- | src/lib/subtitle_sync_packet_queue.cc | 99 | ||||
| -rw-r--r-- | src/lib/subtitle_sync_packet_queue.h | 62 | ||||
| -rw-r--r-- | src/lib/wscript | 3 |
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 |
