6#include <QtCore/qiodevice.h>
7#include <QtCore/qloggingcategory.h>
8#include <QtCore/qmimedatabase.h>
9#include <QtCore/qmutex.h>
10#include <QtCore/qthread.h>
11#include <QtCore/private/qcore_mac_p.h>
13#include "private/qcoreaudioutils_p.h"
15#import <AVFoundation/AVFoundation.h>
19Q_STATIC_LOGGING_CATEGORY(qLcAVFAudioDecoder,
"qt.multimedia.darwin.AVFAudioDecoder");
21using namespace Qt::Literals;
29 auto validateFormat = [&] {
30 CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
31 if (!formatDescription)
33 const AudioStreamBasicDescription *
const asbd =
34 CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription);
37 return qtFormat == QCoreAudioUtils::toPreferredQAudioFormat(*asbd);
40 Q_ASSERT(validateFormat());
43 size_t audioBufferListSize = 0;
44 OSStatus err = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer,
50 kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
55 CMBlockBufferRef blockBuffer =
nullptr;
56 AudioBufferList* audioBufferList = (AudioBufferList*) malloc(audioBufferListSize);
58 err = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer,
64 kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
67 free(audioBufferList);
72 for (UInt32 i = 0; i < audioBufferList->mNumberBuffers; i++)
74 AudioBuffer audioBuffer = audioBufferList->mBuffers[i];
75 abuf.push_back(QByteArray((
const char*)audioBuffer.mData, audioBuffer.mDataByteSize));
78 free(audioBufferList);
79 CFRelease(blockBuffer);
81 CMTime sampleStartTime = (CMSampleBufferGetPresentationTimeStamp(sampleBuffer));
82 float sampleStartTimeSecs = CMTimeGetSeconds(sampleStartTime);
84 return QAudioBuffer(abuf, qtFormat, qint64(sampleStartTimeSecs * 1000000));
87@interface AVFResourceReaderDelegate : NSObject <AVAssetResourceLoaderDelegate> {
88 AVFAudioDecoder *m_decoder;
92- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader
93 shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
97@implementation AVFResourceReaderDelegate
99- (id)initWithDecoder:(AVFAudioDecoder *)decoder
101 if (!(self = [super init]))
109-(BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader
110 shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
112 Q_UNUSED(resourceLoader);
114 if (![loadingRequest.request.URL.scheme isEqualToString:@
"iodevice"])
117 QMutexLocker locker(&m_mutex);
119 QIODevice *device = m_decoder->sourceDevice();
123 device->seek(loadingRequest.dataRequest.requestedOffset);
124 if (loadingRequest.contentInformationRequest) {
125 loadingRequest.contentInformationRequest.contentLength = device->size();
126 loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
129 if (loadingRequest.dataRequest) {
130 NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
131 int maxBytes = qMin(32 * 1024,
int(requestedLength));
133 buffer.resize(maxBytes);
134 NSInteger submitted = 0;
135 while (submitted < requestedLength) {
136 qint64 len = device->read(buffer.data(), maxBytes);
140 [loadingRequest.dataRequest respondWithData:[NSData dataWithBytes:buffer length:len]];
145 [loadingRequest finishLoading];
155NSDictionary *av_audio_settings_for_format(
const QAudioFormat &format)
157 float sampleRate = format.sampleRate();
158 int nChannels = format.channelCount();
159 int sampleSize = format.bytesPerSample() * 8;
160 BOOL isFloat = format.sampleFormat() == QAudioFormat::Float;
162 NSDictionary *audioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
163 [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
164 [NSNumber numberWithFloat:sampleRate], AVSampleRateKey,
165 [NSNumber numberWithInt:nChannels], AVNumberOfChannelsKey,
166 [NSNumber numberWithInt:sampleSize], AVLinearPCMBitDepthKey,
167 [NSNumber numberWithBool:isFloat], AVLinearPCMIsFloatKey,
168 [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,
169 [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey,
172 return audioSettings;
175QAudioFormat qt_format_for_audio_track(AVAssetTrack *track)
177 CMFormatDescriptionRef desc = (__bridge CMFormatDescriptionRef)track.formatDescriptions[0];
178 const AudioStreamBasicDescription*
const asbd =
179 CMAudioFormatDescriptionGetStreamBasicDescription(desc);
180 return QCoreAudioUtils::toPreferredQAudioFormat(*asbd);
193 [m_reader cancelReading];
197 [m_readerOutput release];
201AVFAudioDecoder::AVFAudioDecoder(QAudioDecoder *parent)
202 : QPlatformAudioDecoder(parent)
204 m_readingQueue = dispatch_queue_create(
"reader_queue", DISPATCH_QUEUE_SERIAL);
205 m_decodingQueue = dispatch_queue_create(
"decoder_queue", DISPATCH_QUEUE_SERIAL);
207 m_readerDelegate = [[AVFResourceReaderDelegate alloc] initWithDecoder:
this];
210AVFAudioDecoder::~AVFAudioDecoder()
214 [m_readerDelegate release];
217 dispatch_release(m_readingQueue);
218 dispatch_release(m_decodingQueue);
221QUrl AVFAudioDecoder::source()
const
226void AVFAudioDecoder::setSource(
const QUrl &fileName)
228 if (!m_device && m_source == fileName)
238 if (!m_source.isEmpty()) {
239 NSURL *nsURL = m_source.toNSURL();
240 m_asset = [[AVURLAsset alloc] initWithURL:nsURL options:nil];
246QIODevice *AVFAudioDecoder::sourceDevice()
const
251void AVFAudioDecoder::setSourceDevice(QIODevice *device)
253 if (m_device == device && m_source.isEmpty())
264 const QString ext = QMimeDatabase().mimeTypeForData(m_device).preferredSuffix();
265 const QString url = u"iodevice:///iodevice."_s + ext;
266 NSString *urlString = url.toNSString();
267 NSURL *nsURL = [NSURL URLWithString:urlString];
269 m_asset = [[AVURLAsset alloc] initWithURL:nsURL options:nil];
273 [m_asset.resourceLoader setDelegate:m_readerDelegate queue:m_decodingQueue];
279void AVFAudioDecoder::start()
281 if (m_decodingContext) {
282 qCDebug(qLcAVFAudioDecoder()) <<
"AVFAudioDecoder has been already started";
288 if (m_device && (!m_device->isOpen() || !m_device->isReadable())) {
289 processInvalidMedia(QAudioDecoder::ResourceError, tr(
"Unable to read from specified device"));
293 m_decodingContext = std::make_shared<DecodingContext>();
294 std::weak_ptr<DecodingContext> weakContext(m_decodingContext);
296 auto handleLoadingResult = [=,
this]() {
297 NSError *error = nil;
298 AVKeyValueStatus status = [m_asset statusOfValueForKey:@
"tracks" error:&error];
300 if (status == AVKeyValueStatusFailed) {
301 if (error.domain == NSURLErrorDomain)
302 processInvalidMedia(QAudioDecoder::ResourceError,
303 QString::fromNSString(error.localizedDescription));
305 processInvalidMedia(QAudioDecoder::FormatError,
306 tr(
"Could not load media source's tracks"));
307 }
else if (status != AVKeyValueStatusLoaded) {
308 qWarning() <<
"Unexpected AVKeyValueStatus:" << status;
316 [m_asset loadValuesAsynchronouslyForKeys:@[ @
"tracks" ]
317 completionHandler:[=,
this]() {
318 invokeWithDecodingContext(weakContext, handleLoadingResult);
322void AVFAudioDecoder::decBuffersCounter(uint val)
325 QMutexLocker locker(&m_buffersCounterMutex);
326 m_buffersCounter -= val;
329 Q_ASSERT(m_buffersCounter >= 0);
331 m_buffersCounterCondition.wakeAll();
334void AVFAudioDecoder::stop()
336 qCDebug(qLcAVFAudioDecoder()) <<
"stop decoding";
338 m_decodingContext.reset();
339 decBuffersCounter(m_cachedBuffers.size());
340 m_cachedBuffers.clear();
342 bufferAvailableChanged(
false);
349QAudioFormat AVFAudioDecoder::audioFormat()
const
354void AVFAudioDecoder::setAudioFormat(
const QAudioFormat &format)
356 if (m_format != format) {
358 formatChanged(m_format);
362QAudioBuffer AVFAudioDecoder::read()
364 if (m_cachedBuffers.empty())
365 return QAudioBuffer();
367 Q_ASSERT(m_cachedBuffers.size() > 0);
368 QAudioBuffer buffer = m_cachedBuffers.dequeue();
369 decBuffersCounter(1);
371 positionChanged(buffer.startTime() / 1000);
372 bufferAvailableChanged(!m_cachedBuffers.empty());
376void AVFAudioDecoder::processInvalidMedia(QAudioDecoder::Error errorCode,
377 const QString &errorString)
379 qCDebug(qLcAVFAudioDecoder()) <<
"Invalid media. Error code:" << errorCode
380 <<
"Description:" << errorString;
382 Q_ASSERT(QThread::currentThread() == thread());
384 error(
int(errorCode), errorString);
393void AVFAudioDecoder::onFinished()
395 m_decodingContext.reset();
401void AVFAudioDecoder::initAssetReaderImpl(AVAssetTrack *track, NSError *error)
403 Q_ASSERT(track !=
nullptr);
406 processInvalidMedia(QAudioDecoder::ResourceError, QString::fromNSString(error.localizedDescription));
410 QAudioFormat format = m_format.isValid() ? m_format : qt_format_for_audio_track(track);
411 if (!format.isValid()) {
412 processInvalidMedia(QAudioDecoder::FormatError, tr(
"Unsupported source format"));
416 durationChanged(CMTimeGetSeconds(track.timeRange.duration) * 1000);
418 NSDictionary *audioSettings = av_audio_settings_for_format(format);
420 AVAssetReaderTrackOutput *readerOutput =
421 [[AVAssetReaderTrackOutput alloc] initWithTrack:track outputSettings:audioSettings];
422 AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:m_asset error:&error];
423 auto cleanup = qScopeGuard([&] {
424 [readerOutput release];
429 processInvalidMedia(QAudioDecoder::ResourceError, QString::fromNSString(error.localizedDescription));
432 if (![reader canAddOutput:readerOutput]) {
433 processInvalidMedia(QAudioDecoder::ResourceError, tr(
"Failed to add asset reader output"));
437 [reader addOutput:readerOutput];
439 Q_ASSERT(m_decodingContext);
442 m_decodingContext->m_reader = reader;
443 m_decodingContext->m_readerOutput = readerOutput;
448void AVFAudioDecoder::initAssetReader()
450 qCDebug(qLcAVFAudioDecoder()) <<
"Init asset reader";
453 Q_ASSERT(QThread::currentThread() == thread());
455#if defined(Q_OS_VISIONOS)
456 [m_asset loadTracksWithMediaType:AVMediaTypeAudio completionHandler:[=](NSArray<AVAssetTrack *> *tracks, NSError *error) {
457 if (tracks && tracks.count > 0) {
458 if (AVAssetTrack *track = [tracks objectAtIndex:0])
459 QMetaObject::invokeMethod(
this, &AVFAudioDecoder::initAssetReaderImpl, Qt::QueuedConnection, track, error);
463 NSArray<AVAssetTrack *> *tracks = [m_asset tracksWithMediaType:AVMediaTypeAudio];
464 if (tracks && tracks.count > 0) {
465 if (AVAssetTrack *track = [tracks objectAtIndex:0])
466 initAssetReaderImpl(track,
nullptr );
472void AVFAudioDecoder::startReading(QAudioFormat format)
474 Q_ASSERT(m_decodingContext);
475 Q_ASSERT(m_decodingContext->m_reader);
476 Q_ASSERT(QThread::currentThread() == thread());
479 if (![m_decodingContext->m_reader startReading]) {
480 processInvalidMedia(QAudioDecoder::ResourceError, tr(
"Could not start reading"));
486 std::weak_ptr<DecodingContext> weakContext = m_decodingContext;
491 auto copyNextSampleBuffer = [=,
this]() {
492 auto decodingContext = weakContext.lock();
493 if (!decodingContext)
496 QCFType<CMSampleBufferRef> sampleBuffer{
497 [decodingContext->m_readerOutput copyNextSampleBuffer],
502 dispatch_async(m_decodingQueue, [=,
this]() {
503 if (!weakContext.expired() && CMSampleBufferDataIsReady(sampleBuffer)) {
504 auto audioBuffer = handleNextSampleBuffer(format, sampleBuffer);
506 if (audioBuffer.isValid())
507 invokeWithDecodingContext(weakContext, [=,
this]() {
508 handleNewAudioBuffer(audioBuffer);
516 dispatch_async(m_readingQueue, [=,
this]() {
517 qCDebug(qLcAVFAudioDecoder()) <<
"start reading thread";
523 waitUntilBuffersCounterLessMax();
524 }
while (copyNextSampleBuffer());
527 invokeWithDecodingContext(weakContext, [
this]() { onFinished(); });
531void AVFAudioDecoder::waitUntilBuffersCounterLessMax()
533 if (m_buffersCounter >= MAX_BUFFERS_IN_QUEUE) {
536 QMutexLocker locker(&m_buffersCounterMutex);
538 while (m_buffersCounter >= MAX_BUFFERS_IN_QUEUE)
539 m_buffersCounterCondition.wait(&m_buffersCounterMutex);
543void AVFAudioDecoder::handleNewAudioBuffer(QAudioBuffer buffer)
545 m_cachedBuffers.enqueue(std::move(buffer));
548 Q_ASSERT(m_cachedBuffers.size() == m_buffersCounter);
550 bufferAvailableChanged(
true);
555
556
557
558
560void AVFAudioDecoder::invokeWithDecodingContext(std::weak_ptr<DecodingContext> weakContext, F &&f)
562 if (!weakContext.expired())
563 QMetaObject::invokeMethod(
564 this, [
this, f = std::forward<F>(f), weakContext = std::move(weakContext)]() {
567 if (
auto context = weakContext.lock(); context && context == m_decodingContext)
572#include "moc_avfaudiodecoder_p.cpp"
static constexpr int MAX_BUFFERS_IN_QUEUE
static QAudioBuffer handleNextSampleBuffer(QAudioFormat qtFormat, CMSampleBufferRef sampleBuffer)
AVAssetReaderTrackOutput * m_readerOutput