| 1 | // Copyright (C) 2016 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 |
| 3 | |
| 4 | #include <common/qgstutils_p.h> |
| 5 | #include <common/qgst_p.h> |
| 6 | |
| 7 | #include <QtMultimedia/qaudioformat.h> |
| 8 | |
| 9 | #include <chrono> |
| 10 | |
| 11 | QT_BEGIN_NAMESPACE |
| 12 | |
| 13 | namespace { |
| 14 | |
| 15 | const char *audioSampleFormatNames[QAudioFormat::NSampleFormats] = { |
| 16 | nullptr, |
| 17 | #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
| 18 | "U8" , |
| 19 | "S16LE" , |
| 20 | "S32LE" , |
| 21 | "F32LE" |
| 22 | #else |
| 23 | "U8" , |
| 24 | "S16BE" , |
| 25 | "S32BE" , |
| 26 | "F32BE" |
| 27 | #endif |
| 28 | }; |
| 29 | |
| 30 | QAudioFormat::SampleFormat gstSampleFormatToSampleFormat(const char *fmt) |
| 31 | { |
| 32 | if (fmt) { |
| 33 | for (int i = 1; i < QAudioFormat::NSampleFormats; ++i) { |
| 34 | if (strcmp(s1: fmt, s2: audioSampleFormatNames[i])) |
| 35 | continue; |
| 36 | return QAudioFormat::SampleFormat(i); |
| 37 | } |
| 38 | } |
| 39 | return QAudioFormat::Unknown; |
| 40 | } |
| 41 | |
| 42 | } // namespace |
| 43 | |
| 44 | /* |
| 45 | Returns audio format for a sample \a sample. |
| 46 | If the buffer doesn't have a valid audio format, an empty QAudioFormat is returned. |
| 47 | */ |
| 48 | QAudioFormat QGstUtils::audioFormatForSample(GstSample *sample) |
| 49 | { |
| 50 | auto caps = QGstCaps(gst_sample_get_caps(sample), QGstCaps::NeedsRef); |
| 51 | if (!caps) |
| 52 | return {}; |
| 53 | return audioFormatForCaps(caps); |
| 54 | } |
| 55 | |
| 56 | QAudioFormat QGstUtils::audioFormatForCaps(const QGstCaps &caps) |
| 57 | { |
| 58 | QAudioFormat format; |
| 59 | QGstStructureView s = caps.at(index: 0); |
| 60 | if (s.name() != "audio/x-raw" ) |
| 61 | return format; |
| 62 | |
| 63 | auto rate = s["rate" ].toInt(); |
| 64 | auto channels = s["channels" ].toInt(); |
| 65 | QAudioFormat::SampleFormat fmt = gstSampleFormatToSampleFormat(fmt: s["format" ].toString()); |
| 66 | if (!rate || !channels || fmt == QAudioFormat::Unknown) |
| 67 | return format; |
| 68 | |
| 69 | format.setSampleRate(*rate); |
| 70 | format.setChannelCount(*channels); |
| 71 | format.setSampleFormat(fmt); |
| 72 | |
| 73 | return format; |
| 74 | } |
| 75 | |
| 76 | /* |
| 77 | Builds GstCaps for an audio format \a format. |
| 78 | Returns 0 if the audio format is not valid. |
| 79 | |
| 80 | \note Caller must unreference GstCaps. |
| 81 | */ |
| 82 | |
| 83 | QGstCaps QGstUtils::capsForAudioFormat(const QAudioFormat &format) |
| 84 | { |
| 85 | if (!format.isValid()) |
| 86 | return {}; |
| 87 | |
| 88 | auto sampleFormat = format.sampleFormat(); |
| 89 | auto caps = gst_caps_new_simple( |
| 90 | media_type: "audio/x-raw" , |
| 91 | fieldname: "format" , G_TYPE_STRING, audioSampleFormatNames[sampleFormat], |
| 92 | "rate" , G_TYPE_INT , format.sampleRate(), |
| 93 | "channels" , G_TYPE_INT , format.channelCount(), |
| 94 | "layout" , G_TYPE_STRING, "interleaved" , |
| 95 | nullptr); |
| 96 | |
| 97 | return QGstCaps(caps, QGstCaps::HasRef); |
| 98 | } |
| 99 | |
| 100 | QList<QAudioFormat::SampleFormat> QGValue::getSampleFormats() const |
| 101 | { |
| 102 | if (!GST_VALUE_HOLDS_LIST(value)) |
| 103 | return {}; |
| 104 | |
| 105 | QList<QAudioFormat::SampleFormat> formats; |
| 106 | guint nFormats = gst_value_list_get_size(value); |
| 107 | for (guint f = 0; f < nFormats; ++f) { |
| 108 | QGValue v = QGValue{ gst_value_list_get_value(value, index: f) }; |
| 109 | auto *name = v.toString(); |
| 110 | QAudioFormat::SampleFormat fmt = gstSampleFormatToSampleFormat(fmt: name); |
| 111 | if (fmt == QAudioFormat::Unknown) |
| 112 | continue; |
| 113 | formats.append(t: fmt); |
| 114 | } |
| 115 | return formats; |
| 116 | } |
| 117 | |
| 118 | void QGstUtils::setFrameTimeStampsFromBuffer(QVideoFrame *frame, GstBuffer *buffer) |
| 119 | { |
| 120 | using namespace std::chrono; |
| 121 | using namespace std::chrono_literals; |
| 122 | |
| 123 | // GStreamer uses nanoseconds, Qt uses microseconds |
| 124 | nanoseconds startTime{ GST_BUFFER_TIMESTAMP(buffer) }; |
| 125 | if (startTime >= 0ns) { |
| 126 | frame->setStartTime(floor<microseconds>(d: startTime).count()); |
| 127 | |
| 128 | nanoseconds duration{ GST_BUFFER_DURATION(buffer) }; |
| 129 | if (duration >= 0ns) |
| 130 | frame->setEndTime(floor<microseconds>(d: startTime + duration).count()); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | GList *qt_gst_video_sinks() |
| 135 | { |
| 136 | return gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_SINK |
| 137 | | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO, |
| 138 | minrank: GST_RANK_MARGINAL); |
| 139 | } |
| 140 | |
| 141 | QLocale::Language QGstUtils::codeToLanguage(const gchar *lang) |
| 142 | { |
| 143 | return QLocale::codeToLanguage(languageCode: QString::fromUtf8(utf8: lang), codeTypes: QLocale::AnyLanguageCode); |
| 144 | } |
| 145 | |
| 146 | QT_END_NAMESPACE |
| 147 | |