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
qdarwinformatsinfo.mm
Go to the documentation of this file.
1// Copyright (C) 2021 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
5
6#include <QtMultimedia/private/qapple_utils_p.h>
7#include <QtMultimedia/private/qmultimedia_ranges_p.h>
8
9#include <QtCore/qdebug.h>
10#include <QtCore/private/qcore_mac_p.h>
11
12#include <AVFoundation/AVFoundation.h>
13#include <AudioToolbox/AudioToolbox.h>
14#include <VideoToolbox/VideoToolbox.h>
15
17
18namespace {
19
20constexpr struct
21{
22 const char *__nonnull name;
24 // For single-codec containers the codec is implicit in the MIME type itself — no extended
25 // MIME type probe needed. Unspecified means probe via audioCodecMap instead.
27} mediaContainerMap[] = {
28 { "video/x-ms-asf", QMediaFormat::WMV, QMediaFormat::AudioCodec::Unspecified },
29 { "video/avi", QMediaFormat::AVI, QMediaFormat::AudioCodec::Unspecified },
30 { "video/x-matroska", QMediaFormat::Matroska, QMediaFormat::AudioCodec::Unspecified },
31 { "video/mp4", QMediaFormat::MPEG4, QMediaFormat::AudioCodec::Unspecified },
32 { "video/quicktime", QMediaFormat::QuickTime, QMediaFormat::AudioCodec::Unspecified },
33 { "audio/ogg", QMediaFormat::Ogg, QMediaFormat::AudioCodec::Unspecified },
34 { "audio/mp4", QMediaFormat::Mpeg4Audio, QMediaFormat::AudioCodec::Unspecified },
35 { "audio/aac", QMediaFormat::AAC, QMediaFormat::AudioCodec::AAC },
36 { "audio/mp3", QMediaFormat::MP3, QMediaFormat::AudioCodec::MP3 },
37 { "audio/flac", QMediaFormat::FLAC, QMediaFormat::AudioCodec::FLAC },
38 { "audio/x-wav", QMediaFormat::Wave, QMediaFormat::AudioCodec::Wave },
39};
40
41constexpr struct {
42 const char *__nonnull name;
44} videoCodecMap[] = {
45 // See CMVideoCodecType for the four character code names of codecs
46 { "; codecs=\"mp1v\"", QMediaFormat::VideoCodec::MPEG1 },
47 { "; codecs=\"mp2v\"", QMediaFormat::VideoCodec::MPEG2 },
48 { "; codecs=\"mp4v\"", QMediaFormat::VideoCodec::MPEG4 },
49 { "; codecs=\"avc1\"", QMediaFormat::VideoCodec::H264 },
50 { "; codecs=\"hvc1\"", QMediaFormat::VideoCodec::H265 },
51 { "; codecs=\"vp09\"", QMediaFormat::VideoCodec::VP9 },
52 { "; codecs=\"av01\"", QMediaFormat::VideoCodec::AV1 },
53 { "; codecs=\"jpeg\"", QMediaFormat::VideoCodec::MotionJPEG },
54};
55
56constexpr struct {
57 const char *__nonnull name;
58 QMediaFormat::AudioCodec codec;
59} audioCodecMap[] = {
60 // RFC 6381 / ISO 14496-3 codec strings understood by isPlayableExtendedMIMEType
61 { "; codecs=\"mp4a.40.2\"", QMediaFormat::AudioCodec::AAC }, // AAC-LC
62 { "; codecs=\"ac-3\"", QMediaFormat::AudioCodec::AC3 },
63 { "; codecs=\"ec-3\"", QMediaFormat::AudioCodec::EAC3 },
64 { "; codecs=\"flac\"", QMediaFormat::AudioCodec::FLAC },
65 { "; codecs=\"alac\"", QMediaFormat::AudioCodec::ALAC },
66 { "; codecs=\"opus\"", QMediaFormat::AudioCodec::Opus },
67 { "; codecs=\"vorbis\"", QMediaFormat::AudioCodec::Vorbis },
68};
69
70bool canEncodeAudioFormat(AudioFormatID formatID)
71{
72 if (formatID == kAudioFormatLinearPCM)
73 return true;
74 UInt32 size = 0;
75 return AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
76 sizeof(formatID), &formatID, &size) == noErr
77 && size > 0;
78}
79
80bool canEncodeDecodeFormat(AudioFormatID formatID)
81{
82 if (formatID == kAudioFormatLinearPCM)
83 return true;
84 UInt32 size = 0;
85 return AudioFormatGetPropertyInfo(kAudioFormatProperty_Decoders, sizeof(formatID), &formatID,
86 &size)
87 == noErr
88 && size > 0;
89}
90
92{
93 using namespace QtMultimediaPrivate;
94
95 QCFType<CFArrayRef> list;
96 if (VTCopyVideoEncoderList(nullptr, &list) != noErr || !list)
97 return {};
98
99 auto results =
100 CFArrayRange<CFDictionaryRef>(list) | views::transform([](CFDictionaryRef entry) {
101 auto *typeRef = CFNumberRef(CFDictionaryGetValue(entry, kVTVideoEncoderList_CodecType));
102 CMVideoCodecType type = 0;
103 CFNumberGetValue(typeRef, kCFNumberSInt32Type, &type);
104 return type;
105 });
106
107 return results | ranges::to<std::set<CMVideoCodecType>>();
108}
109
110constexpr bool isVideoContainer(QMediaFormat::FileFormat fileFormat)
111{
112 return fileFormat < QMediaFormat::Mpeg4Audio;
113}
114
115bool isPlayableExtendedMIMEType(const char *extendedMimetype)
116{
117 return [AVURLAsset isPlayableExtendedMIMEType:[NSString stringWithUTF8String:extendedMimetype]];
118}
119
120bool isPlayableExtendedMIMEType(const char *containerName, const char *codecName)
121{
122 const QByteArray extendedMimetype = QByteArrayView(containerName) + codecName;
123 return isPlayableExtendedMIMEType(extendedMimetype.constData());
124}
125
126constexpr auto recordGetCodec = QtMultimediaPrivate::views::transform([](const auto &record) {
127 return record.codec;
128});
129
131{
132 namespace views = QtMultimediaPrivate::views;
133 namespace ranges = QtMultimediaPrivate::ranges;
134 using AudioCodec = QMediaFormat::AudioCodec;
135 using VideoCodec = QMediaFormat::VideoCodec;
136 using CodecMap = QPlatformMediaFormatInfo::CodecMap;
137
138 const bool canDecodeMp3 = canEncodeDecodeFormat(kAudioFormatMPEGLayer3);
139
140 QList<CodecMap> decoders;
141 auto avtypes = [AVURLAsset audiovisualMIMETypes];
142
143 for (AVFileType filetype : avtypes) {
144 for (auto [containerName, fileFormat, implicitCodec] : mediaContainerMap) {
145 if (strcmp(filetype.UTF8String, containerName))
146 continue;
147
148 QList<VideoCodec> video;
149 QList<AudioCodec> audio;
150
151 if (implicitCodec != QMediaFormat::AudioCodec::Unspecified) {
152 // Single-codec container: the MIME type appearing in audiovisualMIMETypes is
153 // sufficient proof of support — no extended MIME type probe needed.
154 audio << implicitCodec;
155 } else {
156 // Multi-codec container: probe each codec via extended MIME type.
157
158 if (isVideoContainer(fileFormat)) {
159 switch (fileFormat) {
160 case QMediaFormat::AVI: {
161 // isPlayableExtendedMIMEType returns false for AVI+video combinations even
162 // when AVFoundation can actually decode them.
163 if (!isPlayableExtendedMIMEType(containerName))
164 break;
165
166 // AVI is a legacy container format that can carry a wide variety of codecs.
167 // Hardcode the known supported codecs instead of probing via extended MIME type.
168 video << VideoCodec::MPEG4 << VideoCodec::H264 << VideoCodec::MotionJPEG;
169 break;
170 }
171
172 default: {
173 video = videoCodecMap | views::filter([&](const auto &record) {
174 return isPlayableExtendedMIMEType(containerName, record.name);
175 }) | recordGetCodec
176 | ranges::to<QList<VideoCodec>>();
177 }
178 };
179 }
180
181 audio = audioCodecMap | views::filter([&](const auto &record) {
182 return isPlayableExtendedMIMEType(containerName, record.name);
183 }) | recordGetCodec
184 | ranges::to<QList<AudioCodec>>();
185
186 // MP3 has no RFC 6381 codec string that isPlayableExtendedMIMEType recognises
187 // (neither ".mp3" nor "mp4a.6B" work). Add it manually for containers that
188 // are known to carry it: MPEG-4 family and Mpeg4Audio.
189 if (canDecodeMp3
190 && (isVideoContainer(fileFormat) || fileFormat == QMediaFormat::Mpeg4Audio))
191 audio << AudioCodec::MP3;
192 }
193
194 decoders << CodecMap{
195 fileFormat,
196 std::move(audio),
197 std::move(video),
198 };
199 }
200 }
201 return decoders;
202}
203
205{
206 namespace views = QtMultimediaPrivate::views;
207 namespace ranges = QtMultimediaPrivate::ranges;
208 using AudioCodec = QMediaFormat::AudioCodec;
209 using VideoCodec = QMediaFormat::VideoCodec;
210 using CodecMap = QPlatformMediaFormatInfo::CodecMap;
211
212 static constexpr struct AudioProbeRecord
213 {
214 AudioFormatID fmtId;
215 AudioCodec codec;
216 } audioProbes[] = {
217 { kAudioFormatMPEG4AAC, AudioCodec::AAC },
218 { kAudioFormatMPEGLayer3, AudioCodec::MP3 },
219 { kAudioFormatAC3, AudioCodec::AC3 },
220 { kAudioFormatEnhancedAC3, AudioCodec::EAC3 },
221 { kAudioFormatFLAC, AudioCodec::FLAC },
222 { kAudioFormatAppleLossless, AudioCodec::ALAC },
223 { kAudioFormatOpus, AudioCodec::Opus },
224 { kAudioFormatLinearPCM, AudioCodec::Wave },
225
226 // no API constants, but the fourcc is supported
227 { 'vorb', QMediaFormat::AudioCodec::Vorbis },
228 };
229
230 static constexpr struct VideoProbeRecord
231 {
232 CMVideoCodecType type;
233 VideoCodec codec;
234 } videoProbes[] = {
235 { kCMVideoCodecType_H264, VideoCodec::H264 },
236 { kCMVideoCodecType_HEVC, VideoCodec::H265 },
237 { kCMVideoCodecType_JPEG, VideoCodec::MotionJPEG },
238 { kCMVideoCodecType_MPEG4Video, VideoCodec::MPEG4 },
239 { kCMVideoCodecType_AV1, VideoCodec::AV1 },
240
241 // no API constants, but the fourcc is supported
242 { 'mp1v', VideoCodec::MPEG1 },
243 { 'mp2v', VideoCodec::MPEG2 },
244 };
245
246 auto filterCodecs = [](const auto &codecsSet) {
247 return views::filter([&](auto c) {
248 return codecsSet.count(c);
249 });
250 };
251
252 const std::set<AudioCodec> supportedAudioCodecs = audioProbes
253 | views::filter([](const auto &record) {
254 return canEncodeAudioFormat(record.fmtId);
255 }) | recordGetCodec
256 | ranges::to<std::set<AudioCodec>>();
257
258 const std::set<CMVideoCodecType> vtCodecTypes = availableVTVideoEncoderCodecTypes();
259
260 const std::set<VideoCodec> supportedVideoCodecs = videoProbes
261 | views::filter([&](const auto &record) {
262 return ranges::contains(vtCodecTypes, record.type);
263 }) | recordGetCodec
264 | ranges::to<std::set<VideoCodec>>();
265
266 auto audioFormats = [&](auto candidates) -> QList<AudioCodec> {
267 return candidates | filterCodecs(supportedAudioCodecs) | ranges::to<QList<AudioCodec>>();
268 };
269
270 auto videoFormats = [&](auto candidates) -> QList<VideoCodec> {
271 return candidates | filterCodecs(supportedVideoCodecs) | ranges::to<QList<VideoCodec>>();
272 };
273
274 // Video containers share the same codec candidates; audio candidates differ by container
275 // spec (e.g. Mpeg4Audio is audio-only, Wave only accepts LPCM).
276 const QList allSupportedVideoCodecs = videoFormats(videoProbes | recordGetCodec);
277 const QList allSupportedAudioCodecs = audioFormats(audioProbes | recordGetCodec);
278
279 return QList{
280 CodecMap{ QMediaFormat::MPEG4, allSupportedAudioCodecs, allSupportedVideoCodecs },
281 CodecMap{ QMediaFormat::QuickTime, allSupportedAudioCodecs, allSupportedVideoCodecs },
282 CodecMap{ QMediaFormat::Mpeg4Audio,
283 audioFormats(std::array{ AudioCodec::AAC, AudioCodec::ALAC, AudioCodec::FLAC }),
284 {} },
285 CodecMap{ QMediaFormat::Wave, audioFormats(std::array{ AudioCodec::Wave }), {} },
286 };
287}
288
289} // namespace
290
292{
293 decoders = enumerateDecoders();
294 encoders = enumerateEncoders();
295
296 // ###
297 imageFormats << QImageCapture::JPEG;
298
299 fixupCodecMaps();
300}
301
303
304int QDarwinFormatInfo::audioFormatForCodec(QMediaFormat::AudioCodec codec)
305{
306 int codecId = kAudioFormatMPEG4AAC;
307 switch (codec) {
308 case QMediaFormat::AudioCodec::Unspecified:
309 case QMediaFormat::AudioCodec::DolbyTrueHD:
310 case QMediaFormat::AudioCodec::Vorbis:
311 case QMediaFormat::AudioCodec::WMA:
312 // Unsupported, shouldn't happen. Fall back to AAC
313 case QMediaFormat::AudioCodec::AAC:
314 codecId = kAudioFormatMPEG4AAC;
315 break;
316 case QMediaFormat::AudioCodec::MP3:
317 codecId = kAudioFormatMPEGLayer3;
318 break;
319 case QMediaFormat::AudioCodec::AC3:
320 codecId = kAudioFormatAC3;
321 break;
322 case QMediaFormat::AudioCodec::EAC3:
323 codecId = kAudioFormatEnhancedAC3;
324 break;
325 case QMediaFormat::AudioCodec::FLAC:
326 codecId = kAudioFormatFLAC;
327 break;
328 case QMediaFormat::AudioCodec::ALAC:
329 codecId = kAudioFormatAppleLossless;
330 break;
331 case QMediaFormat::AudioCodec::Opus:
332 codecId = kAudioFormatOpus;
333 break;
334 case QMediaFormat::AudioCodec::Wave:
335 codecId = kAudioFormatLinearPCM;
336 }
337 return codecId;
338}
339
340NSString *QDarwinFormatInfo::videoFormatForCodec(QMediaFormat::VideoCodec codec)
341{
342 switch (codec) {
343 case QMediaFormat::VideoCodec::MPEG1:
344 return @"mp1v";
345 break;
346 case QMediaFormat::VideoCodec::MPEG2:
347 return @"mp2v";
348 break;
349 case QMediaFormat::VideoCodec::MPEG4:
350 return @"mp4v";
351 break;
352 case QMediaFormat::VideoCodec::H264:
353 return @"avc1";
354 break;
355 case QMediaFormat::VideoCodec::VP9:
356 return @"vp09";
357 break;
358 case QMediaFormat::VideoCodec::MotionJPEG:
359 return @"jpeg";
360 case QMediaFormat::VideoCodec::H265:
361 return @"hvc1";
362 case QMediaFormat::VideoCodec::AV1:
363 return @"av01";
364 case QMediaFormat::VideoCodec::Unspecified:
365 case QMediaFormat::VideoCodec::VP8:
366 case QMediaFormat::VideoCodec::Theora:
367 case QMediaFormat::VideoCodec::WMV:
368 default:
369 qDebug() << "Unsupported video codec" << codec << ", using H.265 as fallback";
370 return @"hvc1";
371 }
372}
373
374NSString *QDarwinFormatInfo::avFileTypeForContainerFormat(QMediaFormat::FileFormat container)
375{
376 switch (container) {
377 case QMediaFormat::MPEG4:
378 return AVFileTypeMPEG4;
379 case QMediaFormat::QuickTime:
380 return AVFileTypeQuickTimeMovie;
381 case QMediaFormat::MP3:
382 return AVFileTypeMPEGLayer3;
383 case QMediaFormat::Mpeg4Audio:
384 return AVFileTypeAppleM4A;
385 case QMediaFormat::Wave:
386 return AVFileTypeWAVE;
387 default:
388 return AVFileTypeQuickTimeMovie;
389 }
390}
391
392QT_END_NAMESPACE
~QDarwinFormatInfo() override
Combined button and popup list for selecting options.
bool isPlayableExtendedMIMEType(const char *extendedMimetype)
constexpr bool isVideoContainer(QMediaFormat::FileFormat fileFormat)
bool canEncodeDecodeFormat(AudioFormatID formatID)
std::set< CMVideoCodecType > availableVTVideoEncoderCodecTypes()
QList< QPlatformMediaFormatInfo::CodecMap > enumerateDecoders()
QMediaFormat::VideoCodec codec
constexpr auto recordGetCodec
bool canEncodeAudioFormat(AudioFormatID formatID)
QMediaFormat::AudioCodec implicitCodec
QMediaFormat::FileFormat fileFormat
const char *__nonnull name
QList< QPlatformMediaFormatInfo::CodecMap > enumerateEncoders()
bool isPlayableExtendedMIMEType(const char *containerName, const char *codecName)