1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | #include "qffmpegresampler_p.h" |
4 | #include "playbackengine/qffmpegcodec_p.h" |
5 | #include "qffmpegmediaformatinfo_p.h" |
6 | #include <qloggingcategory.h> |
7 | |
8 | static Q_LOGGING_CATEGORY(qLcResampler, "qt.multimedia.ffmpeg.resampler" ) |
9 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | using namespace QFFmpeg; |
13 | |
14 | QFFmpegResampler::QFFmpegResampler(const QAudioFormat &inputFormat, const QAudioFormat &outputFormat) : |
15 | m_inputFormat(inputFormat), m_outputFormat(outputFormat) |
16 | { |
17 | Q_ASSERT(inputFormat.isValid()); |
18 | Q_ASSERT(outputFormat.isValid()); |
19 | |
20 | m_resampler = |
21 | createResampleContext(inputFormat: AVAudioFormat(m_inputFormat), outputFormat: AVAudioFormat(m_outputFormat)); |
22 | } |
23 | |
24 | QFFmpegResampler::QFFmpegResampler(const Codec *codec, const QAudioFormat &outputFormat, |
25 | qint64 startTime) |
26 | : m_outputFormat(outputFormat), m_startTime(startTime) |
27 | { |
28 | Q_ASSERT(codec); |
29 | |
30 | qCDebug(qLcResampler) << "createResampler" ; |
31 | const AVStream *audioStream = codec->stream(); |
32 | |
33 | if (!m_outputFormat.isValid()) |
34 | // want the native format |
35 | m_outputFormat = QFFmpegMediaFormatInfo::audioFormatFromCodecParameters(codecPar: audioStream->codecpar); |
36 | |
37 | m_resampler = createResampleContext(inputFormat: AVAudioFormat(audioStream->codecpar), |
38 | outputFormat: AVAudioFormat(m_outputFormat)); |
39 | } |
40 | |
41 | QFFmpegResampler::~QFFmpegResampler() = default; |
42 | |
43 | QAudioBuffer QFFmpegResampler::resample(const char* data, size_t size) |
44 | { |
45 | if (!m_inputFormat.isValid()) |
46 | return {}; |
47 | |
48 | return resample(inputData: reinterpret_cast<const uint8_t **>(&data), |
49 | inputSamplesCount: m_inputFormat.framesForBytes(byteCount: static_cast<qint32>(size))); |
50 | } |
51 | |
52 | QAudioBuffer QFFmpegResampler::resample(const AVFrame *frame) |
53 | { |
54 | return resample(inputData: const_cast<const uint8_t **>(frame->extended_data), inputSamplesCount: frame->nb_samples); |
55 | } |
56 | |
57 | QAudioBuffer QFFmpegResampler::resample(const uint8_t **inputData, int inputSamplesCount) |
58 | { |
59 | const int maxOutSamples = adjustMaxOutSamples(inputSamplesCount); |
60 | |
61 | QByteArray samples(m_outputFormat.bytesForFrames(frameCount: maxOutSamples), Qt::Uninitialized); |
62 | auto *out = reinterpret_cast<uint8_t *>(samples.data()); |
63 | const int outSamples = |
64 | swr_convert(s: m_resampler.get(), out: &out, out_count: maxOutSamples, in: inputData, in_count: inputSamplesCount); |
65 | |
66 | samples.resize(size: m_outputFormat.bytesForFrames(frameCount: outSamples)); |
67 | |
68 | const qint64 startTime = m_outputFormat.durationForFrames(frameCount: m_samplesProcessed) + m_startTime; |
69 | m_samplesProcessed += outSamples; |
70 | |
71 | qCDebug(qLcResampler) << " new frame" << startTime << "in_samples" << inputSamplesCount |
72 | << outSamples << maxOutSamples; |
73 | return QAudioBuffer(samples, m_outputFormat, startTime); |
74 | } |
75 | |
76 | int QFFmpegResampler::adjustMaxOutSamples(int inputSamplesCount) |
77 | { |
78 | int maxOutSamples = swr_get_out_samples(s: m_resampler.get(), in_samples: inputSamplesCount); |
79 | |
80 | const auto remainingCompensationDistance = m_endCompensationSample - m_samplesProcessed; |
81 | |
82 | if (remainingCompensationDistance > 0 && maxOutSamples > remainingCompensationDistance) { |
83 | // If the remaining compensation distance less than output frame, |
84 | // the ffmpeg resampler bufferises the rest of frames that makes |
85 | // unexpected delays on large frames. |
86 | // The hack might cause some compensation bias on large frames, |
87 | // however it's not significant for our logic, in fact. |
88 | // TODO: probably, it will need some improvements |
89 | setSampleCompensation(delta: 0, distance: 0); |
90 | maxOutSamples = swr_get_out_samples(s: m_resampler.get(), in_samples: inputSamplesCount); |
91 | } |
92 | |
93 | return maxOutSamples; |
94 | } |
95 | |
96 | void QFFmpegResampler::setSampleCompensation(qint32 delta, quint32 distance) |
97 | { |
98 | const int res = swr_set_compensation(s: m_resampler.get(), sample_delta: delta, compensation_distance: static_cast<int>(distance)); |
99 | if (res < 0) |
100 | qCWarning(qLcResampler) << "swr_set_compensation fail:" << res; |
101 | else { |
102 | m_sampleCompensationDelta = delta; |
103 | m_endCompensationSample = m_samplesProcessed + distance; |
104 | } |
105 | } |
106 | |
107 | qint32 QFFmpegResampler::activeSampleCompensationDelta() const |
108 | { |
109 | return m_samplesProcessed < m_endCompensationSample ? m_sampleCompensationDelta : 0; |
110 | } |
111 | |
112 | QT_END_NAMESPACE |
113 | |