Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqnxaudiorecorder.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include <QtCore/qcoreapplication.h>
7
8#include <private/qmediastoragelocation_p.h>
9
10#include <mm/renderer.h>
11
12#include <sys/stat.h>
13#include <sys/strm.h>
14
15static QByteArray buildDevicePath(const QByteArray &deviceId, const QMediaEncoderSettings &settings)
16{
17 QByteArray devicePath = QByteArrayLiteral("snd:/dev/snd/") + deviceId + QByteArrayLiteral("?");
18
19 if (settings.audioSampleRate() > 0)
20 devicePath += QByteArrayLiteral("frate=") + QByteArray::number(settings.audioSampleRate());
21
22 if (settings.audioChannelCount() > 0)
23 devicePath += QByteArrayLiteral("nchan=") + QByteArray::number(settings.audioChannelCount());
24
25 return devicePath;
26}
27
28QT_BEGIN_NAMESPACE
29
30QQnxAudioRecorder::QQnxAudioRecorder(QObject *parent)
31 : QObject(parent)
32{
33 openConnection();
34}
35
37{
38 stop();
39 closeConnection();
40}
41
42void QQnxAudioRecorder::openConnection()
43{
44 static int idCounter = 0;
45
46 m_connection = ConnectionUniquePtr { mmr_connect(nullptr) };
47
48 if (!m_connection) {
49 qWarning("QQnxAudioRecorder: Unable to connect to the multimedia renderer");
50 return;
51 }
52
53 m_id = idCounter++;
54
55 char contextName[256];
56
57 std::snprintf(contextName, sizeof contextName, "QQnxAudioRecorder_%d_%llu",
58 m_id, QCoreApplication::applicationPid());
59
60 m_context = ContextUniquePtr { mmr_context_create(m_connection.get(),
61 contextName, 0, S_IRWXU|S_IRWXG|S_IRWXO) };
62
63 if (m_context) {
64 startMonitoring();
65 } else {
66 qWarning("QQnxAudioRecorder: Unable to create context");
67 closeConnection();
68 }
69}
70
71void QQnxAudioRecorder::closeConnection()
72{
73 m_context.reset();
74 m_context.reset();
75
76 stopMonitoring();
77}
78
79void QQnxAudioRecorder::attach()
80{
81 if (isAttached())
82 return;
83
84 const QString container = m_encoderSettings.preferredSuffix();
85 const QString location = QMediaStorageLocation::generateFileName(m_outputUrl.toLocalFile(),
86 QStandardPaths::MusicLocation, container);
87
88 m_audioId = mmr_output_attach(m_context.get(), qPrintable(location), "file");
89
90 if (m_audioId == -1) {
91 qWarning("QQnxAudioRecorder: mmr_output_attach() for file failed");
92 return;
93 }
94
95 configureOutputBitRate();
96
97 const QByteArray devicePath = buildDevicePath(m_inputDeviceId, m_encoderSettings);
98
99 if (mmr_input_attach(m_context.get(), devicePath.constData(), "track") != 0) {
100 qWarning("QQnxAudioRecorder: mmr_input_attach() failed");
101 detach();
102 } else {
103 Q_EMIT actualLocationChanged(location);
104 }
105}
106
107void QQnxAudioRecorder::detach()
108{
109 if (!isAttached())
110 return;
111
112 mmr_input_detach(m_context.get());
113 mmr_output_detach(m_context.get(), m_audioId);
114
115 m_audioId = -1;
116}
117
118void QQnxAudioRecorder::configureOutputBitRate()
119{
120 const int bitRate = m_encoderSettings.audioBitRate();
121
122 if (!isAttached() || bitRate <= 0)
123 return;
124
125 char buf[12];
126 std::snprintf(buf, sizeof buf, "%d", bitRate);
127
128 strm_dict_t *dict = strm_dict_new();
129 dict = strm_dict_set(dict, "audio_bitrate", buf);
130
131 if (mmr_output_parameters(m_context.get(), m_audioId, dict) != 0)
132 qWarning("mmr_output_parameters: setting bitrate failed");
133}
134
135bool QQnxAudioRecorder::isAttached() const
136{
137 return m_context && m_audioId != -1;
138}
139
140void QQnxAudioRecorder::setInputDeviceId(const QByteArray &id)
141{
142 m_inputDeviceId = id;
143}
144
145void QQnxAudioRecorder::setOutputUrl(const QUrl &url)
146{
147 m_outputUrl = url;
148}
149
150void QQnxAudioRecorder::setMediaEncoderSettings(const QMediaEncoderSettings &settings)
151{
152 m_encoderSettings = settings;
153}
154
156{
157 if (!isAttached()) {
158 attach();
159
160 if (!isAttached())
161 return;
162 }
163
164 if (mmr_play(m_context.get()) != 0)
165 qWarning("QQnxAudioRecorder: mmr_play() failed");
166}
167
169{
170 if (!isAttached())
171 return;
172
173 mmr_stop(m_context.get());
174
175 detach();
176}
177
178void QQnxAudioRecorder::startMonitoring()
179{
180 m_eventThread = std::make_unique<QQnxMediaEventThread>(m_context.get());
181
182 connect(m_eventThread.get(), &QQnxMediaEventThread::eventPending,
183 this, &QQnxAudioRecorder::readEvents);
184
185 m_eventThread->setObjectName(QStringLiteral("MmrAudioEventThread-") + QString::number(m_id));
186 m_eventThread->start();
187}
188
189void QQnxAudioRecorder::stopMonitoring()
190{
191 if (m_eventThread)
192 m_eventThread.reset();
193}
194
195void QQnxAudioRecorder::readEvents()
196{
197 while (const mmr_event_t *event = mmr_event_get(m_context.get())) {
198 if (event->type == MMR_EVENT_NONE)
199 break;
200
201 switch (event->type) {
202 case MMR_EVENT_STATUS:
203 handleMmEventStatus(event);
204 break;
205 case MMR_EVENT_STATE:
206 handleMmEventState(event);
207 break;
208 case MMR_EVENT_ERROR:
209 handleMmEventError(event);
210 break;
211 case MMR_EVENT_METADATA:
212 case MMR_EVENT_NONE:
213 case MMR_EVENT_OVERFLOW:
214 case MMR_EVENT_WARNING:
215 case MMR_EVENT_PLAYLIST:
216 case MMR_EVENT_INPUT:
217 case MMR_EVENT_OUTPUT:
218 case MMR_EVENT_CTXTPAR:
219 case MMR_EVENT_TRKPAR:
220 case MMR_EVENT_OTHER:
221 break;
222 }
223 }
224
225 if (m_eventThread)
226 m_eventThread->signalRead();
227}
228
229void QQnxAudioRecorder::handleMmEventStatus(const mmr_event_t *event)
230{
231 if (!event || event->type != MMR_EVENT_STATUS)
232 return;
233
234 if (!event->pos_str)
235 return;
236
237 const QByteArray valueBa(event->pos_str);
238
239 bool ok;
240 const qint64 duration = valueBa.toLongLong(&ok);
241
242 if (!ok)
243 qCritical("Could not parse duration from '%s'", valueBa.constData());
244 else
245 durationChanged(duration);
246}
247
248void QQnxAudioRecorder::handleMmEventState(const mmr_event_t *event)
249{
250 if (!event || event->type != MMR_EVENT_STATE)
251 return;
252
253 switch (event->state) {
254 case MMR_STATE_DESTROYED:
255 case MMR_STATE_IDLE:
256 case MMR_STATE_STOPPED:
257 Q_EMIT stateChanged(QMediaRecorder::StoppedState);
258 break;
259 case MMR_STATE_PLAYING:
260 Q_EMIT stateChanged(QMediaRecorder::RecordingState);
261 break;
262 }
263}
264
265void QQnxAudioRecorder::handleMmEventError(const mmr_event_t *event)
266{
267 if (!event)
268 return;
269
270 // When playback is explicitly stopped using mmr_stop(), mm-renderer
271 // generates a STATE event. When the end of media is reached, an ERROR
272 // event is generated and the error code contained in the event information
273 // is set to MMR_ERROR_NONE. When an error causes playback to stop,
274 // the error code is set to something else.
275 if (event->details.error.info.error_code == MMR_ERROR_NONE) {
276 //TODO add error
277 Q_EMIT stateChanged(QMediaRecorder::StoppedState);
278 detach();
279 }
280}
281
282QT_END_NAMESPACE
283
284#include "moc_qqnxaudiorecorder_p.cpp"
void setInputDeviceId(const QByteArray &id)
void setOutputUrl(const QUrl &url)
void setMediaEncoderSettings(const QMediaEncoderSettings &settings)
static QByteArray buildDevicePath(const QByteArray &deviceId, const QMediaEncoderSettings &settings)