Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsresampler.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
5
6#include <QtCore/qloggingcategory.h>
7#include <QtCore/private/qfunctions_win_p.h>
8#include <QtCore/private/qsystemerror_p.h>
9#include <QtMultimedia/private/qaudio_alignment_support_p.h>
10#include <QtMultimedia/private/qwindowsaudioutils_p.h>
11#include <QtMultimedia/private/qwmf_support_p.h>
12
13#include <wmcodecdsp.h>
14#include <mftransform.h>
15#include <mferror.h>
16
18
19Q_STATIC_LOGGING_CATEGORY(qLcAudioResampler, "qt.multimedia.audioresampler");
20
21namespace {
22
23HRESULT replaceBuffer(const ComPtr<IMFSample> &sample, const ComPtr<IMFMediaBuffer> &buffer)
24{
25 HRESULT hr = sample->RemoveAllBuffers();
26 if (FAILED(hr))
27 return hr;
28
29 return sample->AddBuffer(buffer.Get());
30}
31
32} // namespace
33
34bool QWindowsResampler::isAvailable()
35{
36 return QWindowsMediaFoundation::instance();
37}
38
39QWindowsResampler::QWindowsResampler()
40{
41 qt_win_ensureComInitializedOnThisThread();
42
43 CoCreateInstance(__uuidof(CResamplerMediaObject), nullptr, CLSCTX_INPROC_SERVER,
44 IID_PPV_ARGS(&m_resampler));
45 if (m_resampler)
46 m_resampler->AddInputStreams(1, &m_inputStreamID);
47
48 for (ComPtr<IMFSample> &sample : { std::ref(m_inputSample), std::ref(m_outputSample) }) {
49 HRESULT hr = m_wmf->mfCreateSample(sample.GetAddressOf());
50 if (FAILED(hr)) {
51 qCWarning(qLcAudioResampler) << "Failed to create sample for resampling:"
52 << QSystemError::windowsComString(hr);
53 m_resampler = nullptr;
54 return;
55 }
56 }
57}
58
59QWindowsResampler::~QWindowsResampler() = default;
60
61quint64 QWindowsResampler::outputBufferSize(quint64 inputBufferSize) const
62{
63 if (m_inputFormat.isValid() && m_outputFormat.isValid())
64 return m_outputFormat.bytesForDuration(m_inputFormat.durationForBytes(inputBufferSize));
65 else
66 return 0;
67}
68
69quint64 QWindowsResampler::inputBufferSize(quint64 outputBufferSize) const
70{
71 if (m_inputFormat.isValid() && m_outputFormat.isValid())
72 return m_inputFormat.bytesForDuration(m_outputFormat.durationForBytes(outputBufferSize));
73 else
74 return 0;
75}
76
77qsizetype QWindowsResampler::overAllocatedOutputBufferSize()
78{
79 auto expectedOutputSize = outputBufferSize(m_totalInputBytes) - m_totalOutputBytes;
80 // we may have some rounding errors, so we over-allocate by 10ms
81 expectedOutputSize += m_outputFormat.bytesForDuration(10000);
82 expectedOutputSize = QtMultimediaPrivate::alignUp(expectedOutputSize, 1024);
83 return expectedOutputSize;
84}
85
86template <typename Functor>
87auto QWindowsResampler::processOutput(ComPtr<IMFMediaBuffer> buffer, Functor &&f)
88 -> std::invoke_result_t<Functor, const ComPtr<IMFMediaBuffer> &>
89{
90 HRESULT hr = replaceBuffer(m_outputSample, buffer);
91 if (FAILED(hr))
92 return q23::unexpected{ hr };
93
94 MFT_OUTPUT_DATA_BUFFER outputDataBuffer;
95 outputDataBuffer.dwStreamID = 0;
96 outputDataBuffer.pEvents = nullptr;
97 outputDataBuffer.dwStatus = 0;
98 outputDataBuffer.pSample = m_outputSample.Get();
99 DWORD status = 0;
100 hr = m_resampler->ProcessOutput(0, 1, &outputDataBuffer, &status);
101 if (FAILED(hr))
102 return q23::unexpected{ hr };
103
104 return f(buffer);
105}
106
107q23::expected<QByteArray, HRESULT> QWindowsResampler::processOutput()
108{
109 using namespace QWMF;
110
111 ComPtr<IMFMediaBuffer> buffer;
112 HRESULT hr = QByteArrayMFMediaBuffer::CreateInstance(overAllocatedOutputBufferSize(),
113 buffer.GetAddressOf());
114 if (FAILED(hr))
115 return q23::unexpected{ hr };
116
117 return processOutput(std::move(buffer), [&](const ComPtr<IMFMediaBuffer> &buffer) {
118 return withLockedBuffer(buffer, [&](QSpan<BYTE> data, QSpan<BYTE> /*max*/) {
119 auto *byteArrayBuffer = static_cast<QByteArrayMFMediaBuffer *>(buffer.Get());
120 QByteArray out = byteArrayBuffer->takeByteArray();
121 out.truncate(data.size());
122 return out;
123 });
124 });
125}
126
127QByteArray QWindowsResampler::resample(QByteArray in)
128{
129 m_totalInputBytes += in.size();
130
131 if (m_inputFormat == m_outputFormat) {
132 m_totalOutputBytes += in.size();
133 return in;
134 }
135
136 Q_ASSERT(m_resampler && m_wmf);
137
138 // process input
139 ComPtr<IMFMediaBuffer> buffer;
140 // we always do out-of-place transformations, so read-only will prevent the buffer to detach()
141 QWMF::QByteArrayMFMediaBuffer::CreateInstance(std::move(in), buffer.GetAddressOf(),
142 /*isReadOnly=*/true);
143
144 HRESULT hr = replaceBuffer(m_inputSample, buffer);
145 if (FAILED(hr))
146 return {};
147
148 hr = m_resampler->ProcessInput(m_inputStreamID, m_inputSample.Get(), 0);
149 if (FAILED(hr)) {
150 qCWarning(qLcAudioResampler)
151 << "Failed to process input" << QSystemError::windowsComString(hr);
152 return {};
153 }
154
155 // process output
156 auto result = processOutput();
157 if (result) {
158 m_totalOutputBytes += result.value().size();
159 return result.value();
160 }
161
162 qCWarning(qLcAudioResampler) << "Resampling failed"
163 << QSystemError::windowsComString(result.error());
164 return {};
165}
166
167QByteArray QWindowsResampler::resample(const QByteArrayView &in)
168{
169 return QWindowsResampler::resample(QByteArray(in));
170}
171
172QByteArray QWindowsResampler::resample(const ComPtr<IMFSample> &sample)
173{
174 using namespace QWMF;
175
176 Q_ASSERT(sample);
177
178 DWORD totalLength = 0;
179 HRESULT hr = sample->GetTotalLength(&totalLength);
180 if (FAILED(hr))
181 return {};
182
183 m_totalInputBytes += totalLength;
184
185 if (m_inputFormat == m_outputFormat) {
186 ComPtr<IMFMediaBuffer> outputBuffer;
187 sample->ConvertToContiguousBuffer(outputBuffer.GetAddressOf());
188
189 auto result = withLockedBuffer(outputBuffer, [&](QSpan<BYTE> data, QSpan<BYTE> /*max*/) {
190 return QByteArray(data);
191 });
192 if (result) {
193 m_totalOutputBytes += result.value().size();
194 return result.value();
195 }
196 qCWarning(qLcAudioResampler) << "Failed to convert sample to contiguous buffer"
197 << QSystemError::windowsComString(result.error());
198 return {};
199 }
200
201 Q_ASSERT(m_resampler && m_wmf);
202
203 // process input
204 hr = m_resampler->ProcessInput(m_inputStreamID, sample.Get(), 0);
205 if (FAILED(hr)) {
206 qCWarning(qLcAudioResampler)
207 << "Failed to process input sample" << QSystemError::windowsComString(hr);
208 return {};
209 }
210
211 // process output
212 auto result = processOutput();
213 if (result) {
214 m_totalOutputBytes += result.value().size();
215 return result.value();
216 }
217 qCWarning(qLcAudioResampler) << "Resampling failed" << QSystemError::windowsComString(hr);
218 return {};
219}
220
221QAudioBuffer QWindowsResampler::resample(const char *data, size_t size)
222{
223 quint64 elapsedBytesAtStart = m_totalOutputBytes;
224
225 QByteArray resampled = resample(QSpan{
226 reinterpret_cast<const std::byte *>(data),
227 qsizetype(size),
228 });
229
230 if (resampled.isEmpty())
231 return {};
232
233 return QAudioBuffer{
234 std::move(resampled),
235 m_outputFormat,
236 m_outputFormat.durationForBytes(elapsedBytesAtStart) + m_startTimeOffset.count(),
237 };
238}
239
240std::pmr::vector<std::byte> QWindowsResampler::resample(QSpan<const std::byte> in,
241 std::pmr::memory_resource *mr)
242{
243 using namespace QWMF;
244
245 m_totalInputBytes += in.size_bytes();
246 if (m_inputFormat == m_outputFormat) {
247 m_totalOutputBytes += in.size();
248 return std::pmr::vector<std::byte>{ in.begin(), in.end(), mr };
249 }
250
251 // process input
252 ComPtr<IMFMediaBuffer> inputBuffer;
253 HRESULT hr = QPmrMediaBuffer::CreateInstance(in, mr, inputBuffer.GetAddressOf());
254 if (FAILED(hr))
255 return {};
256
257 hr = replaceBuffer(m_inputSample, inputBuffer);
258 if (FAILED(hr))
259 return {};
260
261 hr = m_resampler->ProcessInput(m_inputStreamID, m_inputSample.Get(), 0);
262 if (FAILED(hr))
263 return {};
264
265 // process output
266 ComPtr<IMFMediaBuffer> outputBuffer;
267 hr = QPmrMediaBuffer::CreateInstance(overAllocatedOutputBufferSize(), mr,
268 outputBuffer.GetAddressOf());
269 if (FAILED(hr)) {
270 qCWarning(qLcAudioResampler)
271 << "Failed to create output buffer" << QSystemError::windowsComString(hr);
272 return {};
273 }
274
275 auto result = processOutput(std::move(outputBuffer), [&](const ComPtr<IMFMediaBuffer> &buffer) {
276 return withLockedBuffer(buffer, [&](QSpan<BYTE> data, QSpan<BYTE> /*max*/) {
277 return std::pmr::vector<std::byte>{
278 reinterpret_cast<std::byte *>(data.begin()),
279 reinterpret_cast<std::byte *>(data.end()),
280 mr,
281 };
282 });
283 });
284
285 if (!result) {
286 qCWarning(qLcAudioResampler)
287 << "Resampling failed" << QSystemError::windowsComString(result.error());
288 return {};
289 }
290 m_totalOutputBytes += result->size();
291 return result.value();
292}
293
294bool QWindowsResampler::setup(const QAudioFormat &fin, const QAudioFormat &fout)
295{
296 qCDebug(qLcAudioResampler) << "Setup audio resampler" << fin << "->" << fout;
297
298 m_totalInputBytes = 0;
299 m_totalOutputBytes = 0;
300
301 if (fin == fout) {
302 qCDebug(qLcAudioResampler) << "Pass through mode";
303 m_inputFormat = fin;
304 m_outputFormat = fout;
305 return true;
306 }
307
308 if (!m_resampler || !m_wmf)
309 return false;
310
311 ComPtr<IMFMediaType> min = QWindowsAudioUtils::formatToMediaType(*m_wmf, fin);
312 ComPtr<IMFMediaType> mout = QWindowsAudioUtils::formatToMediaType(*m_wmf, fout);
313
314 HRESULT hr = m_resampler->SetInputType(m_inputStreamID, min.Get(), 0);
315 if (FAILED(hr)) {
316 qCWarning(qLcAudioResampler)
317 << "Failed to set input type" << QSystemError::windowsComString(hr);
318 return false;
319 }
320
321 hr = m_resampler->SetOutputType(0, mout.Get(), 0);
322 if (FAILED(hr)) {
323 qCWarning(qLcAudioResampler)
324 << "Failed to set output type" << QSystemError::windowsComString(hr);
325 return false;
326 }
327
328 MFT_OUTPUT_STREAM_INFO streamInfo;
329 hr = m_resampler->GetOutputStreamInfo(0, &streamInfo);
330 if (FAILED(hr)) {
331 qCWarning(qLcAudioResampler)
332 << "Could not obtain stream info" << QSystemError::windowsComString(hr);
333 return false;
334 }
335
336 m_inputFormat = fin;
337 m_outputFormat = fout;
338
339 return true;
340}
341
342void QWindowsResampler::setStartTimeOffset(std::chrono::microseconds startTime)
343{
344 m_startTimeOffset = startTime;
345}
346
347QT_END_NAMESPACE
Combined button and popup list for selecting options.