/**************************************************************************** ** ** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qqnxaudiorecorder_p.h" #include "qqnxmediaeventthread_p.h" #include #include #include #include #include static int idCounter = 0; 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() { 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(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