Revision control

Copy as Markdown

// Copyright (C) 2026 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
#include "qohaudiostream_p.h"
#include <QtMultimedia/private/qmultimedia_enum_to_string_converter_p.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qmutex.h>
QT_BEGIN_NAMESPACE
// clang-format off
QT_MM_MAKE_STRING_RESOLVER(OH_AudioStream_Result, QtMultimediaPrivate::EnumName,
(AUDIOSTREAM_SUCCESS, "AUDIOSTREAM_SUCCESS")
(AUDIOSTREAM_ERROR_INVALID_PARAM, "AUDIOSTREAM_ERROR_INVALID_PARAM")
(AUDIOSTREAM_ERROR_ILLEGAL_STATE, "AUDIOSTREAM_ERROR_ILLEGAL_STATE")
(AUDIOSTREAM_ERROR_SYSTEM, "AUDIOSTREAM_ERROR_SYSTEM")
(AUDIOSTREAM_ERROR_UNSUPPORTED_FORMAT, "AUDIOSTREAM_ERROR_UNSUPPORTED_FORMAT")
);
// clang-format on
QT_MM_DEFINE_QDEBUG_ENUM(OH_AudioStream_Result);
namespace QtOHAudio {
Q_LOGGING_CATEGORY(qLcOHAudioStream, "qt.multimedia.ohos.audiostream")
namespace {
// OH_AudioStreamBuilder is not thread-safe: concurrent Create/Destroy and
// GenerateRenderer/GenerateCapturer calls from multiple threads crash inside
// the audio service. Serialize all builder lifecycle operations.
Q_CONSTINIT QBasicMutex g_streamBuilderMutex;
}
OH_AudioStream_SampleFormat toOHSampleFormat(QAudioFormat::SampleFormat fmt)
{
switch (fmt) {
case QAudioFormat::UInt8:
return AUDIOSTREAM_SAMPLE_U8;
case QAudioFormat::Int16:
return AUDIOSTREAM_SAMPLE_S16LE;
case QAudioFormat::Int32:
return AUDIOSTREAM_SAMPLE_S32LE;
case QAudioFormat::Float:
return AUDIOSTREAM_SAMPLE_F32LE;
case QAudioFormat::Unknown:
case QAudioFormat::NSampleFormats:
break;
}
return AUDIOSTREAM_SAMPLE_S16LE;
}
QAudioFormat::SampleFormat fromOHSampleFormat(OH_AudioStream_SampleFormat fmt)
{
switch (fmt) {
case AUDIOSTREAM_SAMPLE_U8:
return QAudioFormat::UInt8;
case AUDIOSTREAM_SAMPLE_S16LE:
return QAudioFormat::Int16;
case AUDIOSTREAM_SAMPLE_S24LE:
return QAudioFormat::Unknown;
case AUDIOSTREAM_SAMPLE_S32LE:
return QAudioFormat::Int32;
case AUDIOSTREAM_SAMPLE_F32LE:
return QAudioFormat::Float;
}
return QAudioFormat::Unknown;
}
QAudioFormat::SampleFormat preferredCompatibleSampleFormat(QAudioFormat::SampleFormat requested)
{
switch (requested) {
case QAudioFormat::UInt8:
case QAudioFormat::Int16:
case QAudioFormat::Int32:
case QAudioFormat::Float:
return requested;
case QAudioFormat::Unknown:
case QAudioFormat::NSampleFormats:
break;
}
return QAudioFormat::Int16;
}
StreamBuilder::StreamBuilder(QAudioFormat fmt, OH_AudioStream_Type direction)
: format{ fmt }
{
params.direction = direction;
QMutexLocker lock{ &g_streamBuilderMutex };
OH_AudioStream_Result result = OH_AudioStreamBuilder_Create(&m_builder, direction);
if (result != AUDIOSTREAM_SUCCESS)
qCWarning(qLcOHAudioStream)
<< "Failed to create stream builder:" << result;
}
StreamBuilder::~StreamBuilder()
{
QMutexLocker lock{ &g_streamBuilderMutex };
if (m_builder)
OH_AudioStreamBuilder_Destroy(m_builder);
}
void StreamBuilder::setupBuilder()
{
if (!m_builder)
return;
OH_AudioStreamBuilder_SetSamplingRate(m_builder, format.sampleRate());
OH_AudioStreamBuilder_SetChannelCount(m_builder, format.channelCount());
OH_AudioStreamBuilder_SetSampleFormat(m_builder, toOHSampleFormat(format.sampleFormat()));
OH_AudioStreamBuilder_SetEncodingType(m_builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
OH_AudioStreamBuilder_SetLatencyMode(m_builder, params.latencyMode);
// TODO: register the OH_AudioRenderer_Callbacks / OH_AudioCapturer_Callbacks
// error and interrupt-event handlers so the backend can react to focus loss,
// device changes and stream errors. See:
if (params.direction == AUDIOSTREAM_TYPE_RENDERER) {
OH_AudioStreamBuilder_SetRendererInfo(m_builder, params.outputUsage);
if (writeCallback) {
OH_AudioStreamBuilder_SetRendererWriteDataCallback(m_builder, writeCallback,
userData);
}
} else {
OH_AudioStreamBuilder_SetCapturerInfo(m_builder, params.inputSourceType);
if (readCallback) {
OH_AudioCapturer_Callbacks callbacks{};
callbacks.OH_AudioCapturer_OnReadData = readCallback;
OH_AudioStreamBuilder_SetCapturerCallback(m_builder, callbacks, userData);
}
}
}
Stream::Stream(StreamBuilder &builder)
: m_streamType{ builder.params.direction }
{
if (!builder.m_builder) {
qCWarning(qLcOHAudioStream) << "Builder is invalid";
return;
}
OH_AudioStream_Result result = AUDIOSTREAM_SUCCESS;
{
QMutexLocker lock{ &g_streamBuilderMutex };
if (m_streamType == AUDIOSTREAM_TYPE_RENDERER) {
result = OH_AudioStreamBuilder_GenerateRenderer(builder.m_builder, &m_renderer);
} else {
result = OH_AudioStreamBuilder_GenerateCapturer(builder.m_builder, &m_capturer);
}
}
if (result != AUDIOSTREAM_SUCCESS) {
qCWarning(qLcOHAudioStream)
<< "Failed to generate stream:" << result;
return;
}
m_areStreamParametersRespected = true;
}
Stream::~Stream()
{
if (isOpen())
close();
}
bool Stream::start()
{
OH_AudioStream_Result result = AUDIOSTREAM_SUCCESS;
if (m_streamType == AUDIOSTREAM_TYPE_RENDERER)
result = OH_AudioRenderer_Start(m_renderer);
else
result = OH_AudioCapturer_Start(m_capturer);
if (result != AUDIOSTREAM_SUCCESS)
qCWarning(qLcOHAudioStream) << "Failed to start stream:" << result;
return result == AUDIOSTREAM_SUCCESS;
}
void Stream::stop()
{
if (m_streamType == AUDIOSTREAM_TYPE_RENDERER)
OH_AudioRenderer_Stop(m_renderer);
else
OH_AudioCapturer_Stop(m_capturer);
}
void Stream::pause()
{
OH_AudioStream_Result result = AUDIOSTREAM_SUCCESS;
if (m_streamType == AUDIOSTREAM_TYPE_RENDERER)
result = OH_AudioRenderer_Pause(m_renderer);
else
result = OH_AudioCapturer_Pause(m_capturer);
if (result != AUDIOSTREAM_SUCCESS)
qCWarning(qLcOHAudioStream)
<< "Failed to pause stream:" << result;
}
void Stream::flush()
{
if (m_streamType == AUDIOSTREAM_TYPE_RENDERER)
OH_AudioRenderer_Flush(m_renderer);
}
bool Stream::isOpen() const
{
return m_renderer != nullptr || m_capturer != nullptr;
}
bool Stream::areStreamParametersRespected() const
{
return m_areStreamParametersRespected;
}
void Stream::close()
{
if (m_streamType == AUDIOSTREAM_TYPE_RENDERER && m_renderer) {
OH_AudioRenderer_Release(m_renderer);
m_renderer = nullptr;
} else if (m_capturer) {
OH_AudioCapturer_Release(m_capturer);
m_capturer = nullptr;
}
}
} // namespace QtOHAudio
QT_END_NAMESPACE