9#include <QtCore/qfile.h>
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qloggingcategory.h>
12#include <QtMultimedia/qaudiodevice.h>
13#include <QtMultimedia/qmediadevices.h>
15#include <private/qplatformaudioinput_p.h>
31 case QMediaFormat::AudioCodec::MP3:
32 return AVRECORDER_AUDIO_MP3;
33 case QMediaFormat::AudioCodec::AAC:
34 case QMediaFormat::AudioCodec::Unspecified:
36 return AVRECORDER_AUDIO_AAC;
43 case QMediaFormat::AAC:
44 return AVRECORDER_CFT_AAC;
45 case QMediaFormat::MP3:
46 return AVRECORDER_CFT_MP3;
47 case QMediaFormat::Wave:
48 return AVRECORDER_CFT_WAV;
49 case QMediaFormat::Mpeg4Audio:
50 return AVRECORDER_CFT_MPEG_4A;
51 case QMediaFormat::MPEG4:
52 case QMediaFormat::UnspecifiedFormat:
54 return AVRECORDER_CFT_MPEG_4;
61 case QMediaRecorder::VeryLowQuality:
return 32000;
62 case QMediaRecorder::LowQuality:
return 64000;
63 case QMediaRecorder::HighQuality:
return 192000;
64 case QMediaRecorder::VeryHighQuality:
return 256000;
65 case QMediaRecorder::NormalQuality:
66 default:
return 128000;
73 : QObject(parent), QPlatformMediaRecorder(parent)
75 m_audioOnlyDurationTimer.setInterval(100);
76 connect(&m_audioOnlyDurationTimer, &QTimer::timeout,
this, [
this] {
77 if (state() == QMediaRecorder::RecordingState)
78 durationChanged(duration());
84 destroyWaveRecorder();
85 destroyAudioOnlyRecorder();
90 return location.isValid() && (location.isLocalFile() || location.isRelative());
97 if (m_audioOnlyRecorder)
98 return m_audioOnlyState;
99 return m_session ? m_session->recorderState() : QMediaRecorder::StoppedState;
105 if (m_waveState == QMediaRecorder::StoppedState)
107 if (m_waveState == QMediaRecorder::PausedState)
108 return m_wavePausedMs;
109 if (!m_waveTimer.isValid())
110 return m_wavePausedMs;
111 return m_wavePausedMs + (m_waveTimer.elapsed() - m_waveResumeStartMs);
113 if (m_audioOnlyRecorder) {
114 if (m_audioOnlyState == QMediaRecorder::StoppedState)
116 if (m_audioOnlyState == QMediaRecorder::PausedState)
117 return m_audioOnlyPausedMs;
118 if (!m_audioOnlyTimer.isValid())
119 return m_audioOnlyPausedMs;
120 return m_audioOnlyPausedMs
121 + (m_audioOnlyTimer.elapsed() - m_audioOnlyResumeStartMs);
123 return m_session ? m_session->recorderDuration() : 0;
129 updateError(QMediaRecorder::ResourceError,
130 tr(
"Recorder has no capture session attached"));
133 settings.resolveFormat();
134 const QString location = findActualLocation(settings);
135 if (location.isEmpty()) {
136 updateError(QMediaRecorder::ResourceError, tr(
"No writable output location"));
140 if (m_session && m_session->isActive()) {
141 m_session->startRecording(settings, location);
142 m_audioOnlyDurationTimer.start();
147 if (!m_service->audioInput()) {
148 updateError(QMediaRecorder::ResourceError,
149 tr(
"No audio or video input is attached to the capture session"));
155 if (settings.audioCodec() == QMediaFormat::AudioCodec::Wave
156 || settings.fileFormat() == QMediaFormat::Wave) {
157 if (recordWave(settings, location))
158 m_audioOnlyDurationTimer.start();
160 destroyWaveRecorder();
164 if (recordAudioOnly(settings, location))
165 m_audioOnlyDurationTimer.start();
167 destroyAudioOnlyRecorder();
176 if (m_audioOnlyRecorder) {
181 m_session->pauseRecording();
190 if (m_audioOnlyRecorder) {
195 m_session->resumeRecording();
200 m_audioOnlyDurationTimer.stop();
205 if (m_audioOnlyRecorder) {
210 m_session->stopRecording();
215 if (m_metaData == metaData)
217 m_metaData = metaData;
224 QOhosCameraSession *newCameraSession = ohosSession ? ohosSession->cameraSession() :
nullptr;
225 if (m_service == ohosSession && m_session == newCameraSession)
231 if (m_audioOnlyRecorder)
234 && m_session->recorderState() != QMediaRecorder::StoppedState
235 && m_session != newCameraSession)
236 m_session->stopRecording();
238 disconnectFromSession();
239 m_service = ohosSession;
240 m_session = newCameraSession;
247 stateChanged(
static_cast<QMediaRecorder::RecorderState>(state));
252 updateError(
static_cast<QMediaRecorder::Error>(code), message);
262 actualLocationChanged(url);
271 connect(m_session, &QOhosCameraSession::recorderErrorOccurred,
this,
272 &QOhosMediaRecorder::onRecorderError);
273 connect(m_session, &QOhosCameraSession::recorderDurationChanged,
this,
274 &QOhosMediaRecorder::onRecorderDuration);
275 connect(m_session, &QOhosCameraSession::recorderActualLocationChanged,
this,
276 &QOhosMediaRecorder::onRecorderActualLocation);
282 disconnect(m_session,
nullptr,
this,
nullptr);
286 const QString &location)
288 m_audioOnlyRecorder = OH_AVRecorder_Create();
289 if (!m_audioOnlyRecorder) {
290 updateError(QMediaRecorder::ResourceError, tr(
"OH_AVRecorder_Create failed"));
294 OH_AVRecorder_SetStateCallback(m_audioOnlyRecorder, audioOnlyStateCallback,
this);
295 OH_AVRecorder_SetErrorCallback(m_audioOnlyRecorder, audioOnlyErrorCallback,
this);
297 QString resolved = location;
298 if (QFileInfo(resolved).suffix().isEmpty()) {
299 const QString suffix = settings.preferredSuffix();
300 if (!suffix.isEmpty())
301 resolved.append(QLatin1Char(
'.')).append(suffix);
303 resolved.append(QStringLiteral(
".m4a"));
306 m_audioOnlyFd = ::open(QFile::encodeName(resolved).constData(),
307 O_RDWR | O_CREAT | O_TRUNC, 0644);
308 if (m_audioOnlyFd < 0) {
309 updateError(QMediaRecorder::ResourceError,
310 tr(
"Could not open output file: %1").arg(resolved));
313 QByteArray urlBytes = QStringLiteral(
"fd://").toUtf8();
314 urlBytes.append(QByteArray::number(m_audioOnlyFd));
316 OH_AVRecorder_Config config{};
317 config.audioSourceType = AVRECORDER_MIC;
318 config.profile.audioBitrate = settings.audioBitRate() > 0
319 ? settings.audioBitRate() : qualityToAudioBitrate(settings.quality());
320 config.profile.audioChannels = settings.audioChannelCount() > 0
321 ? settings.audioChannelCount() : 2;
322 config.profile.audioCodec = audioCodecToOhos(settings.audioCodec());
323 config.profile.audioSampleRate = settings.audioSampleRate() > 0
324 ? settings.audioSampleRate() : 48000;
325 config.profile.fileFormat = containerToOhos(settings.fileFormat());
326 config.profile.isHdr =
false;
327 config.profile.enableTemporalScale =
false;
328 config.url =
const_cast<
char *>(urlBytes.constData());
329 config.fileGenerationMode = AVRECORDER_APP_CREATE;
330 config.maxDuration = 0;
332 if (
auto prepResult = OH_AVRecorder_Prepare(m_audioOnlyRecorder, &config);
333 prepResult != AV_ERR_OK) {
334 qCWarning(qLcOhosMediaRecorder) <<
"OH_AVRecorder_Prepare failed code:" << prepResult;
335 updateError(QMediaRecorder::FormatError, tr(
"OH_AVRecorder_Prepare failed"));
339 if (
auto startResult = OH_AVRecorder_Start(m_audioOnlyRecorder); startResult != AV_ERR_OK) {
340 qCWarning(qLcOhosMediaRecorder) <<
"OH_AVRecorder_Start failed code:" << startResult;
341 updateError(QMediaRecorder::ResourceError, tr(
"OH_AVRecorder_Start failed"));
345 m_audioOnlyActualLocation = QUrl::fromLocalFile(resolved);
346 actualLocationChanged(m_audioOnlyActualLocation);
347 m_audioOnlyPausedMs = 0;
348 m_audioOnlyResumeStartMs = 0;
349 m_audioOnlyTimer.restart();
355 if (!m_audioOnlyRecorder)
357 OH_AVRecorder_Stop(m_audioOnlyRecorder);
358 destroyAudioOnlyRecorder();
363 if (!m_audioOnlyRecorder || m_audioOnlyState != QMediaRecorder::RecordingState)
365 if (OH_AVRecorder_Pause(m_audioOnlyRecorder) == AV_ERR_OK)
366 m_audioOnlyPausedMs += (m_audioOnlyTimer.elapsed() - m_audioOnlyResumeStartMs);
371 if (!m_audioOnlyRecorder || m_audioOnlyState != QMediaRecorder::PausedState)
373 if (OH_AVRecorder_Resume(m_audioOnlyRecorder) == AV_ERR_OK)
374 m_audioOnlyResumeStartMs = m_audioOnlyTimer.elapsed();
379 m_audioOnlyDurationTimer.stop();
380 if (m_audioOnlyRecorder) {
381 OH_AVRecorder_Release(m_audioOnlyRecorder);
382 m_audioOnlyRecorder =
nullptr;
384 if (m_audioOnlyFd >= 0) {
385 ::close(m_audioOnlyFd);
388 if (m_audioOnlyState != QMediaRecorder::StoppedState) {
389 m_audioOnlyState = QMediaRecorder::StoppedState;
390 stateChanged(QMediaRecorder::StoppedState);
392 m_audioOnlyTimer.invalidate();
393 m_audioOnlyPausedMs = 0;
394 m_audioOnlyResumeStartMs = 0;
398 OH_AVRecorder_State state,
399 OH_AVRecorder_StateChangeReason ,
405 QMetaObject::invokeMethod(
406 self, [self, state] { self->onAudioOnlyStateNotification(
int(state)); },
407 Qt::QueuedConnection);
411 const char *errorMsg,
void *userData)
416 const QString message = QString::fromUtf8(errorMsg ? errorMsg :
"");
417 QMetaObject::invokeMethod(
418 self, [self, errorCode, message] {
419 self->onAudioOnlyErrorNotification(errorCode, message);
421 Qt::QueuedConnection);
426 QMediaRecorder::RecorderState mapped = m_audioOnlyState;
428 case AVRECORDER_STARTED:
429 mapped = QMediaRecorder::RecordingState;
431 case AVRECORDER_PAUSED:
432 mapped = QMediaRecorder::PausedState;
434 case AVRECORDER_STOPPED:
435 case AVRECORDER_IDLE:
436 case AVRECORDER_RELEASED:
437 case AVRECORDER_ERROR:
438 mapped = QMediaRecorder::StoppedState;
443 if (mapped == m_audioOnlyState)
445 m_audioOnlyState = mapped;
446 stateChanged(mapped);
447 durationChanged(duration());
450void QOhosMediaRecorder::onAudioOnlyErrorNotification(
int code,
const QString &message)
452 updateError(QMediaRecorder::ResourceError,
453 message.isEmpty() ? tr(
"Recorder error %1").arg(code) : message);
460void writeWavHeader(QFile &out,
int channels,
int sampleRate,
int bitsPerSample)
462 const auto write = [&](
const char *s) { out.write(s, 4); };
463 const auto writeU32 = [&](quint32 v) {
465 char(v & 0xff),
char((v >> 8) & 0xff),
466 char((v >> 16) & 0xff),
char((v >> 24) & 0xff),
470 const auto writeU16 = [&](quint16 v) {
471 char b[2]{
char(v & 0xff),
char((v >> 8) & 0xff) };
480 writeU16(quint16(channels));
481 writeU32(quint32(sampleRate));
482 writeU32(quint32(sampleRate * channels * (bitsPerSample / 8)));
483 writeU16(quint16(channels * (bitsPerSample / 8)));
484 writeU16(quint16(bitsPerSample));
489void patchWavSizes(QFile &file, qint64 dataBytes)
492 quint32 chunkSize = quint32(36 + dataBytes);
494 char(chunkSize & 0xff),
char((chunkSize >> 8) & 0xff),
495 char((chunkSize >> 16) & 0xff),
char((chunkSize >> 24) & 0xff),
499 quint32 dataSize = quint32(dataBytes);
501 char(dataSize & 0xff),
char((dataSize >> 8) & 0xff),
502 char((dataSize >> 16) & 0xff),
char((dataSize >> 24) & 0xff),
507int wavBitsForSampleFormat(QAudioFormat::SampleFormat f)
510 case QAudioFormat::UInt8:
return 8;
511 case QAudioFormat::Int16:
return 16;
512 case QAudioFormat::Int32:
return 32;
513 case QAudioFormat::Float:
return 32;
514 case QAudioFormat::Unknown:
515 case QAudioFormat::NSampleFormats:
break;
523 const QString &location)
525 QString resolved = location;
526 if (QFileInfo(resolved).suffix().isEmpty())
527 resolved.append(QStringLiteral(
".wav"));
529 m_waveFormat = QAudioFormat{};
530 m_waveFormat.setSampleRate(settings.audioSampleRate() > 0 ? settings.audioSampleRate()
532 m_waveFormat.setChannelCount(settings.audioChannelCount() > 0 ? settings.audioChannelCount()
534 m_waveFormat.setSampleFormat(QAudioFormat::Int16);
535 m_waveFormat.setChannelConfig(
536 QAudioFormat::defaultChannelConfigForChannelCount(m_waveFormat.channelCount()));
538 const QAudioDevice inputDevice = m_service && m_service->audioInput()
539 ? m_service->audioInput()->device
540 : QMediaDevices::defaultAudioInput();
541 if (inputDevice.isNull()) {
542 updateError(QMediaRecorder::ResourceError, tr(
"No audio input device available"));
546 auto file =
std::make_unique<QFile>(resolved);
547 if (!file->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
548 updateError(QMediaRecorder::ResourceError,
549 tr(
"Could not open output file: %1").arg(resolved));
552 writeWavHeader(*file, m_waveFormat.channelCount(), m_waveFormat.sampleRate(),
553 wavBitsForSampleFormat(m_waveFormat.sampleFormat()));
555 auto source = std::make_unique<QAudioSource>(inputDevice, m_waveFormat);
556 source->start(file.get());
557 if (source->error() != QAudio::NoError) {
558 updateError(QMediaRecorder::ResourceError, tr(
"Audio capture start failed"));
562 m_waveFile = std::move(file);
563 m_waveSource = std::move(source);
565 m_waveResumeStartMs = 0;
566 m_waveTimer.restart();
567 m_waveActualLocation = QUrl::fromLocalFile(resolved);
568 actualLocationChanged(m_waveActualLocation);
569 m_waveState = QMediaRecorder::RecordingState;
570 stateChanged(QMediaRecorder::RecordingState);
578 destroyWaveRecorder();
583 if (!m_waveSource || m_waveState != QMediaRecorder::RecordingState)
585 m_waveSource->suspend();
586 m_wavePausedMs += (m_waveTimer.elapsed() - m_waveResumeStartMs);
587 m_waveState = QMediaRecorder::PausedState;
588 stateChanged(QMediaRecorder::PausedState);
593 if (!m_waveSource || m_waveState != QMediaRecorder::PausedState)
595 m_waveSource->resume();
596 m_waveResumeStartMs = m_waveTimer.elapsed();
597 m_waveState = QMediaRecorder::RecordingState;
598 stateChanged(QMediaRecorder::RecordingState);
604 m_waveSource->stop();
605 m_waveSource.reset();
609 const qint64 dataBytes = qMax<qint64>(0, m_waveFile->size() - 44);
610 patchWavSizes(*m_waveFile, dataBytes);
614 if (m_waveState != QMediaRecorder::StoppedState) {
615 m_waveState = QMediaRecorder::StoppedState;
616 stateChanged(QMediaRecorder::StoppedState);
618 m_waveTimer.invalidate();
620 m_waveResumeStartMs = 0;
625#include "moc_qohosmediarecorder_p.cpp"
void recorderStateChanged(int state)
int qualityToAudioBitrate(QMediaRecorder::Quality q)
OH_AVRecorder_ContainerFormatType containerToOhos(QMediaFormat::FileFormat fmt)
OH_AVRecorder_CodecMimeType audioCodecToOhos(QMediaFormat::AudioCodec codec)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)