Source code

Revision control

Copy as Markdown

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#ifndef QTEXTTOSPEECHENGINE_WINRT_AUDIOSOURCE_H
#define QTEXTTOSPEECHENGINE_WINRT_AUDIOSOURCE_H
#include <QtCore/QIODevice>
#include <QtCore/qspan.h>
#include <QtMultimedia/QAudioFormat>
#include <robuffer.h>
#include <winrt/base.h>
#include <QtCore/private/qfactorycacheregistration_p.h>
#include <QtCore/private/qcomobject_p.h>
#include <windows.foundation.h>
#include <windows.media.speechsynthesis.h>
#include <windows.storage.streams.h>
#include <wrl.h>
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Media::SpeechSynthesis;
using namespace ABI::Windows::Storage::Streams;
using namespace Microsoft::WRL;
QT_BEGIN_NAMESPACE
using StreamReadyHandler = IAsyncOperationCompletedHandler<SpeechSynthesisStream*>;
using BytesReadyHandler = IAsyncOperationWithProgressCompletedHandler<IBuffer*, UINT32>;
class AudioSource final : public QIODevice, public QComObject<StreamReadyHandler, BytesReadyHandler>
{
Q_OBJECT
public:
AudioSource(ComPtr<IAsyncOperation<SpeechSynthesisStream*>> synthOperation);
bool isSequential() const override { return true; }
void close() override;
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override { return 0; }
bool atEnd() const override;
qint64 bytesAvailable() const override;
QAudioFormat format() const { return audioFormat; }
enum PauseState {
NoPause,
PauseRequested,
Paused
} m_pause = NoPause;
void pause(quint64 atByte)
{
m_pause = PauseRequested;
m_pauseRequestedAt = atByte;
}
void resume()
{
m_pause = NoPause;
m_pauseRequestedAt = 0;
if (bytesAvailable())
emit readyRead();
}
struct Boundary {
enum Type { Word, Sentence, Unknown } type;
QString text;
int beginIndex;
int endIndex;
qint64 startTime;
friend inline bool operator<(const Boundary &lhs, const Boundary &rhs)
{
return lhs.startTime < rhs.startTime;
}
};
QList<Boundary> boundaryData() const
{
return boundaries;
}
// completion handler for synthesising the stream
HRESULT STDMETHODCALLTYPE Invoke(IAsyncOperation<SpeechSynthesisStream*> *operation,
AsyncStatus status) override;
// completion handler for reading from the stream
HRESULT STDMETHODCALLTYPE Invoke(IAsyncOperationWithProgress<IBuffer*, unsigned int> *read,
AsyncStatus status) override;
Q_SIGNALS:
void streamReady(const QAudioFormat &format);
void errorInStream();
private:
// lifetime is controlled via IUnknown reference counting, make sure
// we don't destroy by accident, polymorphically, or via a QObject parent
~AudioSource() override;
bool fetchMore();
QAudioFormat audioFormat;
// The input stream that gives access to the synthesis stream. We keep the
// async operation so that we can cancel it if we get destroyed prematurely.
ComPtr<IAsyncOperation<SpeechSynthesisStream*>> synthOperation;
ComPtr<IInputStream> inputStream;
ComPtr<IRandomAccessStream> randomAccessStream;
// the current ReadAsync operation that yields an IBuffer
ComPtr<IAsyncOperationWithProgress<IBuffer*, UINT32>> readOperation;
ComPtr<IBuffer> m_buffer;
// access to the raw pcm bytes in the IBuffer; this took much reading of Windows header files...
ComPtr<::Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
// Span into the current IBuffer, shrunk as data is consumed
QSpan<const char> m_bufferSpan;
quint64 m_bytesRead = 0;
quint64 m_pauseRequestedAt = 0;
bool m_headerConsumed{};
std::optional<qint64> m_pauseDetectionSilenceCount;
void populateBoundaries();
QList<Boundary> boundaries;
};
QT_END_NAMESPACE
#endif