diff options
Diffstat (limited to 'src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp')
-rw-r--r-- | src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp b/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp new file mode 100644 index 000000000..00a20bbd7 --- /dev/null +++ b/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp @@ -0,0 +1,284 @@ +// 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 +#include "qqnxaudiorecorder_p.h" +#include "qqnxmediaeventthread_p.h" + +#include <QtCore/qcoreapplication.h> + +#include <private/qmediastoragelocation_p.h> + +#include <mm/renderer.h> + +#include <sys/stat.h> +#include <sys/strm.h> + +static QByteArray buildDevicePath(const QByteArray &deviceId, const QMediaEncoderSettings &settings) +{ + QByteArray devicePath = QByteArrayLiteral("snd:/dev/snd/") + deviceId + QByteArrayLiteral("?"); + + if (settings.audioSampleRate() > 0) + devicePath += QByteArrayLiteral("frate=") + QByteArray::number(settings.audioSampleRate()); + + if (settings.audioChannelCount() > 0) + devicePath += QByteArrayLiteral("nchan=") + QByteArray::number(settings.audioChannelCount()); + + return devicePath; +} + +QT_BEGIN_NAMESPACE + +QQnxAudioRecorder::QQnxAudioRecorder(QObject *parent) + : QObject(parent) +{ + openConnection(); +} + +QQnxAudioRecorder::~QQnxAudioRecorder() +{ + stop(); + closeConnection(); +} + +void QQnxAudioRecorder::openConnection() +{ + static int idCounter = 0; + + m_connection = ConnectionUniquePtr { mmr_connect(nullptr) }; + + if (!m_connection) { + qWarning("QQnxAudioRecorder: Unable to connect to the multimedia renderer"); + return; + } + + m_id = idCounter++; + + char contextName[256]; + + std::snprintf(contextName, sizeof contextName, "QQnxAudioRecorder_%d_%llu", + m_id, QCoreApplication::applicationPid()); + + m_context = ContextUniquePtr { mmr_context_create(m_connection.get(), + contextName, 0, S_IRWXU|S_IRWXG|S_IRWXO) }; + + if (m_context) { + startMonitoring(); + } else { + qWarning("QQnxAudioRecorder: Unable to create context"); + closeConnection(); + } +} + +void QQnxAudioRecorder::closeConnection() +{ + m_context.reset(); + m_context.reset(); + + stopMonitoring(); +} + +void QQnxAudioRecorder::attach() +{ + if (isAttached()) + return; + + const QString container = m_encoderSettings.mimeType().preferredSuffix(); + const QString location = QMediaStorageLocation::generateFileName(m_outputUrl.toLocalFile(), + QStandardPaths::MusicLocation, container); + + m_audioId = mmr_output_attach(m_context.get(), qPrintable(location), "file"); + + if (m_audioId == -1) { + qWarning("QQnxAudioRecorder: mmr_output_attach() for file failed"); + return; + } + + configureOutputBitRate(); + + const QByteArray devicePath = buildDevicePath(m_inputDeviceId, m_encoderSettings); + + if (mmr_input_attach(m_context.get(), devicePath.constData(), "track") != 0) { + qWarning("QQnxAudioRecorder: mmr_input_attach() failed"); + detach(); + } else { + Q_EMIT actualLocationChanged(location); + } +} + +void QQnxAudioRecorder::detach() +{ + if (!isAttached()) + return; + + mmr_input_detach(m_context.get()); + mmr_output_detach(m_context.get(), m_audioId); + + m_audioId = -1; +} + +void QQnxAudioRecorder::configureOutputBitRate() +{ + const int bitRate = m_encoderSettings.audioBitRate(); + + if (!isAttached() || bitRate <= 0) + return; + + char buf[12]; + std::snprintf(buf, sizeof buf, "%d", bitRate); + + strm_dict_t *dict = strm_dict_new(); + dict = strm_dict_set(dict, "audio_bitrate", buf); + + if (mmr_output_parameters(m_context.get(), m_audioId, dict) != 0) + qWarning("mmr_output_parameters: setting bitrate failed"); +} + +bool QQnxAudioRecorder::isAttached() const +{ + return m_context && m_audioId != -1; +} + +void QQnxAudioRecorder::setInputDeviceId(const QByteArray &id) +{ + m_inputDeviceId = id; +} + +void QQnxAudioRecorder::setOutputUrl(const QUrl &url) +{ + m_outputUrl = url; +} + +void QQnxAudioRecorder::setMediaEncoderSettings(const QMediaEncoderSettings &settings) +{ + m_encoderSettings = settings; +} + +void QQnxAudioRecorder::record() +{ + if (!isAttached()) { + attach(); + + if (!isAttached()) + return; + } + + if (mmr_play(m_context.get()) != 0) + qWarning("QQnxAudioRecorder: mmr_play() failed"); +} + +void QQnxAudioRecorder::stop() +{ + if (!isAttached()) + return; + + mmr_stop(m_context.get()); + + detach(); +} + +void QQnxAudioRecorder::startMonitoring() +{ + m_eventThread = std::make_unique<QQnxMediaEventThread>(m_context.get()); + + connect(m_eventThread.get(), &QQnxMediaEventThread::eventPending, + this, &QQnxAudioRecorder::readEvents); + + m_eventThread->setObjectName(QStringLiteral("MmrAudioEventThread-") + QString::number(m_id)); + m_eventThread->start(); +} + +void QQnxAudioRecorder::stopMonitoring() +{ + if (m_eventThread) + m_eventThread.reset(); +} + +void QQnxAudioRecorder::readEvents() +{ + while (const mmr_event_t *event = mmr_event_get(m_context.get())) { + if (event->type == MMR_EVENT_NONE) + break; + + switch (event->type) { + case MMR_EVENT_STATUS: + handleMmEventStatus(event); + break; + case MMR_EVENT_STATE: + handleMmEventState(event); + break; + case MMR_EVENT_ERROR: + handleMmEventError(event); + break; + case MMR_EVENT_METADATA: + case MMR_EVENT_NONE: + case MMR_EVENT_OVERFLOW: + case MMR_EVENT_WARNING: + case MMR_EVENT_PLAYLIST: + case MMR_EVENT_INPUT: + case MMR_EVENT_OUTPUT: + case MMR_EVENT_CTXTPAR: + case MMR_EVENT_TRKPAR: + case MMR_EVENT_OTHER: + break; + } + } + + if (m_eventThread) + m_eventThread->signalRead(); +} + +void QQnxAudioRecorder::handleMmEventStatus(const mmr_event_t *event) +{ + if (!event || event->type != MMR_EVENT_STATUS) + return; + + if (!event->pos_str) + return; + + const QByteArray valueBa(event->pos_str); + + bool ok; + const qint64 duration = valueBa.toLongLong(&ok); + + if (!ok) + qCritical("Could not parse duration from '%s'", valueBa.constData()); + else + durationChanged(duration); +} + +void QQnxAudioRecorder::handleMmEventState(const mmr_event_t *event) +{ + if (!event || event->type != MMR_EVENT_STATE) + return; + + switch (event->state) { + case MMR_STATE_DESTROYED: + case MMR_STATE_IDLE: + case MMR_STATE_STOPPED: + Q_EMIT stateChanged(QMediaRecorder::StoppedState); + break; + case MMR_STATE_PLAYING: + Q_EMIT stateChanged(QMediaRecorder::RecordingState); + break; + } +} + +void QQnxAudioRecorder::handleMmEventError(const mmr_event_t *event) +{ + if (!event) + return; + + // When playback is explicitly stopped using mmr_stop(), mm-renderer + // generates a STATE event. When the end of media is reached, an ERROR + // event is generated and the error code contained in the event information + // is set to MMR_ERROR_NONE. When an error causes playback to stop, + // the error code is set to something else. + if (event->details.error.info.error_code == MMR_ERROR_NONE) { + //TODO add error + Q_EMIT stateChanged(QMediaRecorder::StoppedState); + detach(); + } +} + +QT_END_NAMESPACE + +#include "moc_qqnxaudiorecorder_p.cpp" |