/*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team Modifications Copyright (C) 2025 mikesolar This program 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 3 of the License, or (at your option) any later version. This program 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 this program. If not, see . ***/ #include "decoder.h" #include #include #include #include "codec/ffmpeg/ffmpegdecoder.h" #include "codec/planarfiledevice.h" #include "codec/oiio/oiiodecoder.h" #include "conformmanager.h" namespace olive { const rational Decoder::kAnyTimecode = RATIONAL_MIN; Decoder::Decoder() : cached_texture_(nullptr) { UpdateLastAccessed(); } void Decoder::IncrementAccessTime(qint64 t) { last_accessed_ += t; } bool Decoder::Open(const CodecStream &stream) { QMutexLocker locker(&mutex_); UpdateLastAccessed(); if (stream_.IsValid()) { // Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not. if (stream_ == stream) { return true; } else { qWarning() << "Tried to open a decoder that was already open with another stream"; return false; } } else { // Stream was not open, try opening it now if (!stream.IsValid()) { // Cannot open null stream qCritical() << "Decoder attempted to open null stream"; return false; } if (!stream.Exists()) { // Cannot open file that doesn't exist qCritical() << "Decoder attempted to open file that doesn't exist"; return false; } // Set stream stream_ = stream; // Try open internal if (OpenInternal()) { return true; } else { // Unset stream qCritical() << "Failed to open" << stream_.filename() << "stream" << stream_.stream(); CloseInternal(); stream_.Reset(); return false; } } } TexturePtr Decoder::RetrieveVideo(const RetrieveVideoParams &p) { QMutexLocker locker(&mutex_); UpdateLastAccessed(); if (!stream_.IsValid()) { qCritical() << "Can't retrieve video on a closed decoder"; return nullptr; } if (!SupportsVideo()) { qCritical() << "Decoder doesn't support video"; return nullptr; } if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } if (cached_texture_ && cached_time_ == p.time && cached_divider_ == p.divider) { return cached_texture_; } cached_texture_ = RetrieveVideoInternal(p); cached_time_ = p.time; cached_divider_ = p.divider; return cached_texture_; } Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString &cache_path, LoopMode loop_mode, RenderMode::Mode mode) { QMutexLocker locker(&mutex_); UpdateLastAccessed(); if (!stream_.IsValid()) { qCritical() << "Can't retrieve audio on a closed decoder"; return kInvalid; } if (!SupportsAudio()) { qCritical() << "Decoder doesn't support audio"; return kInvalid; } if (params.sample_rate() <= 0 || params.channel_count() <= 0) { qWarning() << "Invalid audio parameters, skipping audio retrieve"; return kInvalid; } // Get conform state from ConformManager ConformManager::Conform conform = ConformManager::instance()->GetConformState( id(), cache_path, stream_, params, (mode == RenderMode::kOnline)); if (conform.state == ConformManager::kConformGenerating) { // If we need the task, it's available in `conform.task` return kWaitingForConform; } // See if we got the conform if (RetrieveAudioFromConform(dest, conform.filenames, range, loop_mode, params)) { return kOK; } else { return kUnknownError; } } qint64 Decoder::GetLastAccessedTime() { return last_accessed_; } void Decoder::Close() { QMutexLocker locker(&mutex_); UpdateLastAccessed(); cached_texture_ = nullptr; if (stream_.IsValid()) { CloseInternal(); stream_.Reset(); } else { qWarning() << "Tried to close a decoder that wasn't open"; } } bool Decoder::ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, CancelAtom *cancelled) { return ConformAudioInternal(output_filenames, params, cancelled); } /* * DECODER STATIC PUBLIC MEMBERS */ QVector Decoder::ReceiveListOfAllDecoders() { QVector decoders; // The order in which these decoders are added is their priority when probing. Hence FFmpeg should usually be last, // since it supports so many formats and we presumably want to override those formats with a more specific decoder. decoders.append(std::make_shared()); decoders.append(std::make_shared()); return decoders; } DecoderPtr Decoder::CreateFromID(const QString &id) { if (id.isEmpty()) { return nullptr; } // Create list to iterate through QVector decoder_list = ReceiveListOfAllDecoders(); foreach (DecoderPtr d, decoder_list) { if (d->id() == id) { return d; } } return nullptr; } void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration) { if (duration != AV_NOPTS_VALUE && duration != 0) { emit IndexProgress(static_cast(ts) / static_cast(duration)); } } QString Decoder::TransformImageSequenceFileName(const QString &filename, const int64_t &number) { int digit_count = GetImageSequenceDigitCount(filename); QFileInfo file_info(filename); QString original_basename = file_info.completeBaseName(); QString new_basename = original_basename.left(original_basename.size() - digit_count) .append( QStringLiteral("%1").arg(number, digit_count, 10, QChar('0'))); return file_info.dir().filePath( file_info.fileName().replace(original_basename, new_basename)); } int Decoder::GetImageSequenceDigitCount(const QString &filename) { QString basename = QFileInfo(filename).completeBaseName(); // See if basename contains a number at the end int digit_count = 0; for (int i = basename.size() - 1; i >= 0; i--) { if (basename.at(i).isDigit()) { digit_count++; } else { break; } } return digit_count; } int64_t Decoder::GetImageSequenceIndex(const QString &filename) { int digit_count = GetImageSequenceDigitCount(filename); QFileInfo file_info(filename); QString original_basename = file_info.completeBaseName(); QString number_only = original_basename.mid(original_basename.size() - digit_count); return number_only.toLongLong(); } TexturePtr Decoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { Q_UNUSED(p) return nullptr; } bool Decoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, CancelAtom *cancelled) { Q_UNUSED(filenames) Q_UNUSED(cancelled) Q_UNUSED(params) return false; } bool Decoder::RetrieveAudioFromConform( SampleBuffer &sample_buffer, const QVector &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams &input_params) { PlanarFileDevice input; if (input.open(conform_filenames, QFile::ReadOnly)) { // Offset range by audio start offset range -= GetAudioStartOffset(); qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count(); qint64 write_index = 0; const qint64 buffer_length_in_bytes = sample_buffer.sample_count() * input_params.bytes_per_sample_per_channel(); while (write_index < buffer_length_in_bytes) { if (loop_mode == LoopMode::kLoopModeLoop) { while (read_index >= input.size()) { read_index -= input.size(); } while (read_index < 0) { read_index += input.size(); } } qint64 write_count = 0; if (read_index < 0) { // Reading before 0, write silence here until audio data would actually start write_count = qMin(-read_index, buffer_length_in_bytes); sample_buffer.silence_bytes(write_index, write_index + write_count); } else if (read_index >= input.size()) { // Reading after data length, write silence until the end of the buffer write_count = buffer_length_in_bytes - write_index; sample_buffer.silence_bytes(write_index, write_index + write_count); } else { write_count = qMin(input.size() - read_index, buffer_length_in_bytes - write_index); input.seek(read_index); input.read(reinterpret_cast( sample_buffer.to_raw_ptrs().data()), write_count, write_index); } read_index += write_count; write_index += write_count; } input.close(); return true; } return false; } void Decoder::UpdateLastAccessed() { last_accessed_ = QDateTime::currentMSecsSinceEpoch(); } uint qHash(Decoder::CodecStream stream, uint seed) { return qHash(stream.filename(), seed) ^ ::qHash(stream.stream(), seed) ^ qHash(stream.block(), seed); } }