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