From 51421ffc2b0b4e20945f5e8de43344e3d4ec898c Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Thu, 25 Jun 2026 09:23:01 +0200 Subject: [PATCH] this feature will not last very long, its too hard to maintain, sometimes segfaults when encoder is finised or stops ? moved into seperate folder, and cleaned up some components --- CMakeLists.txt | 15 +- src/engine/encoder/AlbumReencoder.cpp | 84 +++++++ src/engine/encoder/AlbumReencoder.h | 47 ++++ src/engine/encoder/FfmpegGuards.h | 47 ++++ src/engine/{ => encoder}/ReencodeJob.h | 0 src/engine/encoder/SampleRateUtils.h | 35 +++ src/engine/encoder/StageBuffer.cpp | 128 +++++++++++ src/engine/encoder/StageBuffer.h | 78 +++++++ src/engine/encoder/TrackEncoder.cpp | 306 +++++++++++++++++++++++++ src/engine/encoder/TrackEncoder.h | 19 ++ src/gui/ReencodeDialog.h | 2 +- 11 files changed, 757 insertions(+), 4 deletions(-) create mode 100644 src/engine/encoder/AlbumReencoder.cpp create mode 100644 src/engine/encoder/AlbumReencoder.h create mode 100644 src/engine/encoder/FfmpegGuards.h rename src/engine/{ => encoder}/ReencodeJob.h (100%) create mode 100644 src/engine/encoder/SampleRateUtils.h create mode 100644 src/engine/encoder/StageBuffer.cpp create mode 100644 src/engine/encoder/StageBuffer.h create mode 100644 src/engine/encoder/TrackEncoder.cpp create mode 100644 src/engine/encoder/TrackEncoder.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ab97ca..c353bdc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,10 @@ set(SOURCES src/engine/SpectrumAnalyzer.cpp src/engine/MusicLibrary.cpp src/engine/MetadataReader.cpp - src/engine/AlbumReencoder.cpp + # engine/encoder + src/engine/encoder/AlbumReencoder.cpp + src/engine/encoder/TrackEncoder.cpp + src/engine/encoder/StageBuffer.cpp # ffmpeg src/engine/ffmpeg/FormatContext.cpp src/engine/ffmpeg/CodecContext.cpp @@ -74,8 +77,13 @@ set(HEADERS src/engine/Track.h src/engine/MetadataReader.h src/engine/MusicLibrary.h - src/engine/ReencodeJob.h - src/engine/AlbumReencoder.h + # engine/encoder + src/engine/encoder/ReencodeJob.h + src/engine/encoder/AlbumReencoder.h + src/engine/encoder/TrackEncoder.h + src/engine/encoder/StageBuffer.h + src/engine/encoder/FfmpegGuards.h + src/engine/encoder/SampleRateUtils.h # ffmpeg wrapper src/engine/ffmpeg/FormatContext.h src/engine/ffmpeg/CodecContext.h @@ -111,6 +119,7 @@ target_include_directories(SubWave PRIVATE src/config src/controllers src/engine + src/engine/encoder src/engine/ffmpeg src/playlists src/gui diff --git a/src/engine/encoder/AlbumReencoder.cpp b/src/engine/encoder/AlbumReencoder.cpp new file mode 100644 index 0000000..e28e2b0 --- /dev/null +++ b/src/engine/encoder/AlbumReencoder.cpp @@ -0,0 +1,84 @@ +#include "AlbumReencoder.h" +#include "TrackEncoder.h" + +namespace engine { + +AlbumReencoder::AlbumReencoder(QObject *parent) : QObject(parent) {} + +AlbumReencoder::~AlbumReencoder() +{ + requestStop(); + if (m_thread) { + m_thread->wait(8000); + } +} + +void AlbumReencoder::setJobs(const QList &jobs) +{ + QMutexLocker lk(&m_mtx); + m_jobs = jobs; +} + +QList AlbumReencoder::jobs() const +{ + QMutexLocker lk(&m_mtx); + return m_jobs; +} + +bool AlbumReencoder::isRunning() const +{ + return m_thread && m_thread->isRunning(); +} + +void AlbumReencoder::requestStop() +{ + m_stopReq = true; +} + +void AlbumReencoder::start() +{ + if (isRunning()) return; + m_stopReq = false; + + m_thread = QThread::create([this]{ workerRun(); }); + m_thread->setObjectName("subwave-reencode"); + + connect(m_thread, &QThread::finished, m_thread, &QObject::deleteLater); + + m_thread->start(QThread::LowPriority); +} + +void AlbumReencoder::workerRun() +{ + TrackEncoder encoder(m_stopReq); + + int succeeded = 0, failed = 0; + + QMutexLocker lk(&m_mtx); + int total = m_jobs.size(); + lk.unlock(); + + for (int i = 0; i < total; ++i) { + if (m_stopReq) break; + + lk.relock(); + ReencodeJob &job = m_jobs[i]; + job.status = ReencodeJob::Status::Running; + lk.unlock(); + + emit trackStarted(i, job.track.displayTitle()); + + bool ok = encoder.encode(job); + + lk.relock(); + job.status = ok ? ReencodeJob::Status::Done : ReencodeJob::Status::Failed; + lk.unlock(); + + if (ok) ++succeeded; else ++failed; + emit trackFinished(i, ok, job.outputPath, job.errorString); + } + + emit allFinished(succeeded, failed); +} + +} // namespace engine diff --git a/src/engine/encoder/AlbumReencoder.h b/src/engine/encoder/AlbumReencoder.h new file mode 100644 index 0000000..8c0d897 --- /dev/null +++ b/src/engine/encoder/AlbumReencoder.h @@ -0,0 +1,47 @@ +#pragma once + +#include "ReencodeJob.h" + +#include +#include +#include +#include + +#include + +namespace engine { + +class AlbumReencoder : public QObject +{ + Q_OBJECT + +public: + explicit AlbumReencoder(QObject *parent = nullptr); + ~AlbumReencoder() override; + + void setJobs(const QList &jobs); + QList jobs() const; + + bool isRunning() const; + +public slots: + void start(); + void requestStop(); + +signals: + void trackStarted (int index, const QString &title); + void trackFinished(int index, bool ok, + const QString &outputPath, + const QString &errorString); + void allFinished (int succeeded, int failed); + +private: + void workerRun(); + + mutable QMutex m_mtx; + QList m_jobs; + QThread *m_thread = nullptr; + std::atomic m_stopReq{false}; +}; + +} // namespace engine diff --git a/src/engine/encoder/FfmpegGuards.h b/src/engine/encoder/FfmpegGuards.h new file mode 100644 index 0000000..21fe998 --- /dev/null +++ b/src/engine/encoder/FfmpegGuards.h @@ -0,0 +1,47 @@ +#pragma once + +extern "C" { +#include +#include +#include +} + +namespace engine { + +struct FmtCtxIn { + AVFormatContext *p = nullptr; + ~FmtCtxIn() { if (p) avformat_close_input(&p); } +}; + +struct FmtCtxOut { + AVFormatContext *p = nullptr; + ~FmtCtxOut() { + if (p) { + if (!(p->oformat->flags & AVFMT_NOFILE)) + avio_closep(&p->pb); + avformat_free_context(p); + } + } +}; + +struct CodecCtxGuard { + AVCodecContext *p = nullptr; + ~CodecCtxGuard() { if (p) avcodec_free_context(&p); } +}; + +struct SwrGuard { + SwrContext *p = nullptr; + ~SwrGuard() { if (p) swr_free(&p); } +}; + +struct FrameGuard { + AVFrame *p = av_frame_alloc(); + ~FrameGuard() { av_frame_free(&p); } +}; + +struct PacketGuard { + AVPacket *p = av_packet_alloc(); + ~PacketGuard() { av_packet_free(&p); } +}; + +} // namespace engine diff --git a/src/engine/ReencodeJob.h b/src/engine/encoder/ReencodeJob.h similarity index 100% rename from src/engine/ReencodeJob.h rename to src/engine/encoder/ReencodeJob.h diff --git a/src/engine/encoder/SampleRateUtils.h b/src/engine/encoder/SampleRateUtils.h new file mode 100644 index 0000000..0036568 --- /dev/null +++ b/src/engine/encoder/SampleRateUtils.h @@ -0,0 +1,35 @@ +#pragma once + +extern "C" { +#include +} + +namespace engine { + +inline int pickSampleRate(const AVCodec *encoder, int sourceRate) +{ + if (!encoder->supported_samplerates) + return sourceRate; + + for (const int *r = encoder->supported_samplerates; *r != 0; ++r) + if (*r == sourceRate) + return sourceRate; + + int best = 0; + for (const int *r = encoder->supported_samplerates; *r != 0; ++r) { + if (*r >= sourceRate) { + if (best == 0 || *r < best) + best = *r; + } + } + if (best != 0) + return best; + + for (const int *r = encoder->supported_samplerates; *r != 0; ++r) + if (*r > best) + best = *r; + + return best; +} + +} // namespace engine \ No newline at end of file diff --git a/src/engine/encoder/StageBuffer.cpp b/src/engine/encoder/StageBuffer.cpp new file mode 100644 index 0000000..695182b --- /dev/null +++ b/src/engine/encoder/StageBuffer.cpp @@ -0,0 +1,128 @@ +// StageBuffer.cpp +// "America doesn't have health insurance" - Joe Biden +// ^^ may god help you +#include "StageBuffer.h" + +#include +#include + +extern "C" { +#include +} + +namespace engine { + +StageBuffer::StageBuffer(SwrContext *swr, + AVFrame *encFrame, + AVSampleFormat fmt, + int channels, + int frameSize, + SendFrameFn sendFrame) + : m_swr(swr) + , m_encFrame(encFrame) + , m_fmt(fmt) + , m_channels(channels) + , m_frameSize(frameSize) + , m_bytesPerSample(av_get_bytes_per_sample(fmt)) + , m_isPlanar(av_sample_fmt_is_planar(fmt)) + , m_numPlanes(m_isPlanar ? channels : 1) + , m_sendFrame(std::move(sendFrame)) + , m_bufs(m_numPlanes) +{} + +int StageBuffer::bytesForSamples(int samples, int /*plane*/) const +{ + return samples * m_bytesPerSample * (m_isPlanar ? 1 : m_channels); +} + + +/** + * @brief Resample and append audio to the staging buffer + * + * Input samples are converted using the swrcontext and append + * to the accumulation buffers. No encoder frames are emitted by + * this function; callers should invoke flush() to emit output frames + * + * @param srcData Input audio buffers in the format expected by + * the swrcontext + * @param nbSamples Number of input samples per channel + * + * @return true if conversion succeeded ; false if swr_convert() failed + * @note The number of samples may differ from the number of input samples due + * to resampling + * + * @author rattatwinko + * @date 25/06/2026 + */ +bool StageBuffer::push(const uint8_t * const *srcData, int nbSamples) +{ + int maxOut = swr_get_out_samples(m_swr, nbSamples); + if (maxOut <= 0) return true; // nothing to do + + std::vector dstPtrs(m_numPlanes); + for (int pl = 0; pl < m_numPlanes; ++pl) { + int prevSz = static_cast(m_bufs[pl].size()); + m_bufs[pl].resize(prevSz + bytesForSamples(maxOut, pl)); + dstPtrs[pl] = m_bufs[pl].data() + prevSz; + } + + int converted = swr_convert(m_swr, + dstPtrs.data(), maxOut, + srcData, nbSamples); + + for (int pl = 0; pl < m_numPlanes; ++pl) { + int keepSz = static_cast(m_bufs[pl].size()) + - bytesForSamples(maxOut - std::max(converted, 0), pl); + m_bufs[pl].resize(std::max(keepSz, 0)); + } + + return converted >= 0; +} + +/** + * @brief Emit buffered samples as a encoder frame + * + * In normal mode (drain=false), frames are emitted only when least + * framesize samples are available. + * + * In drain mode (drain=true), all remaining samples are emitted, + * including a final partial frame. this is typically used at eos + * + * foreach emitted frame: + * - the encoder frame is made writeable. + * - samples are copied from the buffers + * - the callback supplied at construction is invoked + * + * @param drain Wether to flush partial frames. + * @return true if the frames callback reports failure, true otherwise + * + * @author rattatwinko + * @date 25/06/2026 + */ +bool StageBuffer::flush(bool drain) +{ + while (true) { + int avail = static_cast(m_bufs[0].size()) + / (m_bytesPerSample * (m_isPlanar ? 1 : m_channels)); + + if (avail < (drain ? 1 : m_frameSize)) + break; + + int use = drain ? avail : m_frameSize; + + av_frame_make_writable(m_encFrame); + m_encFrame->nb_samples = use; + + for (int pl = 0; pl < m_numPlanes; ++pl) { + int bytes = bytesForSamples(use, pl); + std::memcpy(m_encFrame->data[pl], m_bufs[pl].data(), bytes); + m_bufs[pl].erase(m_bufs[pl].begin(), m_bufs[pl].begin() + bytes); + } + + if (!m_sendFrame(m_encFrame)) + return false; + } + return true; +} + +} // namespace engine diff --git a/src/engine/encoder/StageBuffer.h b/src/engine/encoder/StageBuffer.h new file mode 100644 index 0000000..a8047b5 --- /dev/null +++ b/src/engine/encoder/StageBuffer.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include + +extern "C" { +#include +#include +} + +namespace engine { + +/** + * @brief Buffers resampled audio and emits encoder-sized frames + * + * The Buffer sits between a SWRContext resampler and a audio encoder. + * Incoming audio blocks may contain an arbitrary number of samples and + * resampling may change the sample count. This class accumulates the converted + * output until it has enough samples to fill an encoder frame, + * then it invokes a callback to send the frame to the encoder. + * + * Ownership: + * - The SWRContext and AVFrame are not owned by StageBuffer and must + * remain valid for the lifetime of the object. + * - The callback is invoked syncronously from the flush() + * + * Typical Usage: + * @code + * StageBuffer buffer(swr, + * encFrame, + * encoderFmt, + * channels, + * encoderFrameSize, + * sendFrame + * ); + * buffer.push(srcDatam nbSamples); + * buffer.flush(false); // emit any full frames + * + * buffer.flush(true); // emit remaining samples + * @endcode + * + * @author rattatwinko + * @date 25/06/2026 + */ +class StageBuffer +{ +public: + using SendFrameFn = std::function; + + StageBuffer(SwrContext *swr, + AVFrame *encFrame, + AVSampleFormat fmt, + int channels, + int frameSize, + SendFrameFn sendFrame); + + bool push(const uint8_t * const *srcData, int nbSamples); + + bool flush(bool drain); + +private: + int bytesForSamples(int samples, int plane) const; + + SwrContext *m_swr; + AVFrame *m_encFrame; + AVSampleFormat m_fmt; + int m_channels; + int m_frameSize; + int m_bytesPerSample; + bool m_isPlanar; + int m_numPlanes; + SendFrameFn m_sendFrame; + + std::vector> m_bufs; +}; + +} // namespace engine diff --git a/src/engine/encoder/TrackEncoder.cpp b/src/engine/encoder/TrackEncoder.cpp new file mode 100644 index 0000000..020cbae --- /dev/null +++ b/src/engine/encoder/TrackEncoder.cpp @@ -0,0 +1,306 @@ +#include "TrackEncoder.h" +#include "FfmpegGuards.h" +#include "StageBuffer.h" +#include "SampleRateUtils.h" + +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +namespace engine { + +namespace { + +QString buildOutputPath(const ReencodeJob &job) +{ + QString outDir = job.outputDir; + if (outDir.isEmpty()) + outDir = QFileInfo(job.track.filePath).absolutePath(); + + QDir().mkpath(outDir); + + QString base = QFileInfo(job.track.filePath).completeBaseName(); + return outDir + QDir::separator() + base + "." + job.preset.extension; +} + +AVSampleFormat pickSampleFormat(const AVCodec *encoder, const QString &codecName) +{ + if (!encoder->sample_fmts) + return AV_SAMPLE_FMT_FLTP; + + auto supports = [&](AVSampleFormat fmt) -> bool { + for (int i = 0; encoder->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) + if (encoder->sample_fmts[i] == fmt) return true; + return false; + }; + + if (codecName == "pcm_s16le") + return supports(AV_SAMPLE_FMT_S16) ? AV_SAMPLE_FMT_S16 + : encoder->sample_fmts[0]; + + if (codecName == "pcm_s24le" || codecName == "pcm_s32le") + return supports(AV_SAMPLE_FMT_S32) ? AV_SAMPLE_FMT_S32 + : encoder->sample_fmts[0]; + + if (supports(AV_SAMPLE_FMT_FLTP)) return AV_SAMPLE_FMT_FLTP; + + return encoder->sample_fmts[0]; +} + +int pickChannelCount(const QString &codecName, int inChannels) +{ + if (codecName == "libopus") + return std::min(inChannels, 2); + if (codecName == "libvorbis") + return std::min(inChannels, 8); + return inChannels; +} + +int64_t clampBitrate(const QString &codecName, int64_t requestedKbps, int channels) +{ + if (requestedKbps <= 0) return -1; + + if (codecName == "libopus") { + // opus: 6–510 kbps per channel + int64_t minTotal = 6LL * channels; + int64_t maxTotal = 510LL * channels; + return std::clamp(requestedKbps, minTotal, maxTotal); + } + if (codecName == "libmp3lame") { + return std::clamp(requestedKbps, INT64_C(8), INT64_C(320)); + } + return requestedKbps; +} + +} // anonymous namespace + +TrackEncoder::TrackEncoder(const std::atomic &stopFlag) + : m_stop(stopFlag) +{} + +bool TrackEncoder::encode(ReencodeJob &job) const +{ + job.outputPath = buildOutputPath(job); + job.errorString = {}; + + FmtCtxIn in; + { + AVDictionary *opts = nullptr; + av_dict_set(&opts, "probesize", "5000000", 0); + av_dict_set(&opts, "analyzeduration", "5000000", 0); + int err = avformat_open_input(&in.p, + job.track.filePath.toUtf8().constData(), + nullptr, &opts); + av_dict_free(&opts); + if (err < 0) { job.errorString = "Cannot open input file"; return false; } + } + if (avformat_find_stream_info(in.p, nullptr) < 0) { + job.errorString = "Cannot read stream info"; return false; + } + + int audioIdx = av_find_best_stream(in.p, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); + if (audioIdx < 0) { job.errorString = "No audio stream found"; return false; } + + AVStream *inStream = in.p->streams[audioIdx]; + + const AVCodec *decoder = avcodec_find_decoder(inStream->codecpar->codec_id); + if (!decoder) { job.errorString = "No decoder for input codec"; return false; } + + CodecCtxGuard decCtx; + decCtx.p = avcodec_alloc_context3(decoder); + if (!decCtx.p) { job.errorString = "OOM: decoder context"; return false; } + + avcodec_parameters_to_context(decCtx.p, inStream->codecpar); + + decCtx.p->pkt_timebase = inStream->time_base; + if (avcodec_open2(decCtx.p, decoder, nullptr) < 0) { + job.errorString = "Cannot open decoder"; return false; + } + + const int inChannels = decCtx.p->ch_layout.nb_channels; + const int inSampleRate = decCtx.p->sample_rate; + + const AVCodec *encoder = avcodec_find_encoder_by_name( + job.preset.codecName.toUtf8().constData()); + if (!encoder) { + job.errorString = QString("Encoder not found: %1").arg(job.preset.codecName); + return false; + } + + // opus only accepts 8/12/16/24/48 kHz; 44100 must become 48000. + const int outSampleRate = pickSampleRate(encoder, inSampleRate); + + const int outChannels = pickChannelCount(job.preset.codecName, inChannels); + + const AVSampleFormat encFmt = pickSampleFormat(encoder, job.preset.codecName); + + CodecCtxGuard encCtx; + encCtx.p = avcodec_alloc_context3(encoder); + if (!encCtx.p) { job.errorString = "OOM: encoder context"; return false; } + + AVChannelLayout outLayout{}; + av_channel_layout_default(&outLayout, outChannels); + av_channel_layout_copy(&encCtx.p->ch_layout, &outLayout); + + encCtx.p->sample_rate = outSampleRate; + encCtx.p->sample_fmt = encFmt; + encCtx.p->time_base = { 1, outSampleRate }; + + { + int64_t clamped = clampBitrate(job.preset.codecName, + job.preset.bitrate, outChannels); + if (clamped > 0) + encCtx.p->bit_rate = clamped * 1000; + } + + if (job.preset.vbrQuality >= 0) { + if (job.preset.codecName == "libmp3lame") { + av_opt_set_int(encCtx.p->priv_data, "q", job.preset.vbrQuality, 0); + encCtx.p->flags |= AV_CODEC_FLAG_QSCALE; + } else if (job.preset.codecName == "libvorbis") { + av_opt_set_double(encCtx.p->priv_data, "q", job.preset.vbrQuality, 0); + } + } + + if (job.preset.compression > 0 && job.preset.codecName == "flac") + av_opt_set_int(encCtx.p->priv_data, "compression_level", + job.preset.compression, 0); + + if (avcodec_open2(encCtx.p, encoder, nullptr) < 0) { + job.errorString = "Cannot open encoder"; return false; + } + + FmtCtxOut out; + avformat_alloc_output_context2(&out.p, nullptr, nullptr, + job.outputPath.toUtf8().constData()); + if (!out.p) { job.errorString = "Cannot allocate output context"; return false; } + + AVStream *outStream = avformat_new_stream(out.p, nullptr); + if (!outStream) { job.errorString = "Cannot create output stream"; return false; } + + avcodec_parameters_from_context(outStream->codecpar, encCtx.p); + outStream->time_base = encCtx.p->time_base; + + av_dict_copy(&out.p->metadata, in.p->metadata, 0); + + if (!(out.p->oformat->flags & AVFMT_NOFILE)) { + if (avio_open(&out.p->pb, job.outputPath.toUtf8().constData(), + AVIO_FLAG_WRITE) < 0) { + job.errorString = "Cannot open output file for writing"; return false; + } + } + + if (avformat_write_header(out.p, nullptr) < 0) { + job.errorString = "Cannot write output header"; return false; + } + + SwrGuard swr; + { + AVChannelLayout inLayout{}; + av_channel_layout_copy(&inLayout, &decCtx.p->ch_layout); + int err = swr_alloc_set_opts2(&swr.p, + &outLayout, encFmt, outSampleRate, + &inLayout, decCtx.p->sample_fmt, inSampleRate, + 0, nullptr); + av_channel_layout_uninit(&inLayout); + + if (err < 0 || swr_init(swr.p) < 0) { + job.errorString = "Cannot initialise resampler"; return false; + } + } + av_channel_layout_uninit(&outLayout); + + const int frameSize = (encCtx.p->frame_size > 0) ? encCtx.p->frame_size : 1152; + + FrameGuard encFrame; + encFrame.p->format = encFmt; + encFrame.p->nb_samples = frameSize; + encFrame.p->sample_rate = outSampleRate; + av_channel_layout_copy(&encFrame.p->ch_layout, &encCtx.p->ch_layout); + if (av_frame_get_buffer(encFrame.p, 0) < 0) { + job.errorString = "Cannot allocate encode frame buffer"; return false; + } + + qint64 ptsOut = 0; + + auto sendFrameToEncoder = [&](AVFrame *f) -> bool { + if (f) { + f->pts = ptsOut; + ptsOut += f->nb_samples; + } + if (avcodec_send_frame(encCtx.p, f) < 0) return false; + + PacketGuard outPkt; + int ret; + while ((ret = avcodec_receive_packet(encCtx.p, outPkt.p)) == 0) { + av_packet_rescale_ts(outPkt.p, encCtx.p->time_base, outStream->time_base); + outPkt.p->stream_index = 0; + av_interleaved_write_frame(out.p, outPkt.p); + av_packet_unref(outPkt.p); + } + return ret == AVERROR(EAGAIN) || ret == AVERROR_EOF; + }; + + StageBuffer stage(swr.p, encFrame.p, encFmt, outChannels, frameSize, + sendFrameToEncoder); + + PacketGuard inPkt; + FrameGuard decFrame; + + while (!m_stop) { + int ret = av_read_frame(in.p, inPkt.p); + if (ret < 0) break; + + if (inPkt.p->stream_index != audioIdx) { + av_packet_unref(inPkt.p); + continue; + } + + av_packet_rescale_ts(inPkt.p, inStream->time_base, decCtx.p->pkt_timebase); + avcodec_send_packet(decCtx.p, inPkt.p); + av_packet_unref(inPkt.p); + + while (avcodec_receive_frame(decCtx.p, decFrame.p) == 0) { + if (!stage.push(const_cast(decFrame.p->data), + decFrame.p->nb_samples)) { + job.errorString = "SWR convert error"; + return false; + } + av_frame_unref(decFrame.p); + + if (!stage.flush(false)) { + job.errorString = "Encode error during conversion"; + return false; + } + } + } + + if (m_stop) { job.errorString = "Cancelled"; return false; } + + avcodec_send_packet(decCtx.p, nullptr); + while (avcodec_receive_frame(decCtx.p, decFrame.p) == 0) { + stage.push(const_cast(decFrame.p->data), + decFrame.p->nb_samples); + av_frame_unref(decFrame.p); + stage.flush(false); + } + + stage.push(nullptr, 0); + + stage.flush(true); + + sendFrameToEncoder(nullptr); + + av_write_trailer(out.p); + return true; +} + +} // namespace engine diff --git a/src/engine/encoder/TrackEncoder.h b/src/engine/encoder/TrackEncoder.h new file mode 100644 index 0000000..498a1f9 --- /dev/null +++ b/src/engine/encoder/TrackEncoder.h @@ -0,0 +1,19 @@ +#pragma once + +#include "ReencodeJob.h" +#include + +namespace engine { + +class TrackEncoder +{ +public: + explicit TrackEncoder(const std::atomic &stopFlag); + + bool encode(ReencodeJob &job) const; + +private: + const std::atomic &m_stop; +}; + +} // namespace engine diff --git a/src/gui/ReencodeDialog.h b/src/gui/ReencodeDialog.h index 71cb4fe..8701c18 100644 --- a/src/gui/ReencodeDialog.h +++ b/src/gui/ReencodeDialog.h @@ -2,7 +2,7 @@ #include #include #include "engine/Track.h" -#include "engine/ReencodeJob.h" +#include "engine/encoder/ReencodeJob.h" class QComboBox; class QLineEdit;