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
#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.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(QUrl{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"