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
avfvideorenderercontrol.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
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 <avfvideobuffer_p.h>
7#include <avfvideosink_p.h>
8#include <mediaplayer/avfdisplaylink_p.h>
9
10#include <QtMultimedia/qvideoframeformat.h>
11#include <QtMultimedia/private/qavfhelpers_p.h>
12#include <QtMultimedia/private/qvideoframe_p.h>
13#include <QtGui/rhi/qrhi.h>
14#include <QtCore/qdebug.h>
15
16#import <AVFoundation/AVFoundation.h>
17#include <CoreVideo/CVPixelBuffer.h>
18#include <CoreVideo/CVImageBuffer.h>
19
20QT_USE_NAMESPACE
21
22@interface SubtitleDelegate : NSObject <AVPlayerItemLegibleOutputPushDelegate>
23{
24 AVFVideoRendererControl *m_renderer;
26
27- (void)legibleOutput:(AVPlayerItemLegibleOutput *)output
28 didOutputAttributedStrings:(NSArray<NSAttributedString *> *)strings
29 nativeSampleBuffers:(NSArray *)nativeSamples
30 forItemTime:(CMTime)itemTime;
31
32@end
33
34@implementation SubtitleDelegate
35
36-(id)initWithRenderer: (AVFVideoRendererControl *)renderer
37{
38 if (!(self = [super init]))
39 return nil;
40
41 m_renderer = renderer;
42
43 return self;
44}
45
46- (void)legibleOutput:(AVPlayerItemLegibleOutput *)output
47 didOutputAttributedStrings:(NSArray<NSAttributedString *> *)strings
48 nativeSampleBuffers:(NSArray *)nativeSamples
49 forItemTime:(CMTime)itemTime
50{
51 QString text;
52 for (NSAttributedString *s : strings) {
53 if (!text.isEmpty())
54 text += QChar::LineSeparator;
55 text += QString::fromNSString(s.string);
56 }
57 m_renderer->setSubtitleText(text);
58}
59
60@end
61
62
63AVFVideoRendererControl::AVFVideoRendererControl(QObject *parent)
64 : QObject(parent)
65{
66 m_displayLink = new AVFDisplayLink(this);
67
68 connect(m_displayLink, &AVFDisplayLink::tick, this, &AVFVideoRendererControl::updateVideoFrame);
69}
70
73#ifdef QT_DEBUG_AVF
74 qDebug() << Q_FUNC_INFO;
75#endif
76 m_displayLink->stop();
77 m_displayLink->disconnect(this);
78 if (m_videoOutput)
79 [m_videoOutput release];
80 if (m_subtitleOutput)
81 [m_subtitleOutput release];
82 if (m_subtitleDelegate)
83 [m_subtitleDelegate release];
84}
85
88#ifdef QT_DEBUG_AVF
89 qDebug() << "reconfigure";
90#endif
91 if (!m_layer) {
92 m_displayLink->stop();
93 return;
94 }
95
96 QMutexLocker locker(&m_mutex);
97
98 m_displayLink->start();
99
101}
102
103void AVFVideoRendererControl::setLayer(CALayer *layer)
105 if (m_layer == layer)
106 return;
107
108 AVPlayerLayer *plLayer = playerLayer();
109 if (plLayer) {
110 if (m_videoOutput)
111 [[[plLayer player] currentItem] removeOutput:m_videoOutput];
112
113 if (m_subtitleOutput)
114 [[[plLayer player] currentItem] removeOutput:m_subtitleOutput];
115 }
116
117 if (!layer && m_sink)
118 m_sink->setVideoFrame(QVideoFrame());
119
120 AVFVideoSinkInterface::setLayer(layer);
121}
122
123void AVFVideoRendererControl::setVideoRotation(QtVideo::Rotation rotation)
125 m_rotation = rotation;
126}
127
128void AVFVideoRendererControl::setVideoMirrored(bool mirrored)
130 m_mirrored = mirrored;
131}
132
133void AVFVideoRendererControl::updateVideoFrame()
134{
135 if (!m_sink)
136 return;
137
138 if (!m_layer)
139 return;
140
141 auto *layer = playerLayer();
142 if (!layer.readyForDisplay)
143 return;
145
146 QVideoFrame frame;
147 size_t width, height;
148 qint64 startTime = -1;
149 QCFType<CVPixelBufferRef> pixelBuffer = copyPixelBufferFromLayer(width, height, startTime);
150 if (!pixelBuffer)
151 return;
152 // qDebug() << "Got pixelbuffer with format" << fmt << Qt::hex <<
153 // CVPixelBufferGetPixelFormatType(pixelBuffer);
154
155 auto buffer = std::make_unique<AVFVideoBuffer>(this, std::move(pixelBuffer));
156
157 auto format = buffer->videoFormat();
158 format.setRotation(m_rotation);
159 format.setMirrored(m_mirrored);
160 frame = QVideoFramePrivate::createFrame(std::move(buffer), format);
161
162 if (startTime >= 0) {
163 frame.setStartTime(startTime);
164
165 // Estimate end time from video track's nominal frame rate
166 AVPlayerItem *playerItem = [layer.player currentItem];
167 if (playerItem) {
168 float fps = 0;
169 for (AVPlayerItemTrack *track in playerItem.tracks) {
170 if ([track.assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
171 fps = track.assetTrack.nominalFrameRate;
172 break;
173 }
174 }
175 if (fps > 0)
176 frame.setEndTime(startTime + qint64(1000000.0 / fps));
177 }
178 }
179
180 m_sink->setVideoFrame(frame);
181}
182
183QCFType<CVPixelBufferRef>
184AVFVideoRendererControl::copyPixelBufferFromLayer(size_t &width, size_t &height, qint64 &startTime)
185{
186 AVPlayerLayer *layer = playerLayer();
187 //Is layer valid
188 if (!layer) {
189#ifdef QT_DEBUG_AVF
190 qWarning("copyPixelBufferFromLayer: invalid layer");
191#endif
192 return nullptr;
193 }
194
195 AVPlayerItem * item = [[layer player] currentItem];
196
197 if (!m_videoOutput) {
198 if (!m_outputSettings)
200 m_videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:m_outputSettings];
201 [m_videoOutput setDelegate:nil queue:nil];
202 }
203 if (!m_subtitleOutput) {
204 m_subtitleOutput = [[AVPlayerItemLegibleOutput alloc] init];
205 m_subtitleDelegate = [[SubtitleDelegate alloc] initWithRenderer:this];
206 [m_subtitleOutput setDelegate:m_subtitleDelegate queue:dispatch_get_main_queue()];
207 }
208 if (![item.outputs containsObject:m_videoOutput])
209 [item addOutput:m_videoOutput];
210 if (![item.outputs containsObject:m_subtitleOutput])
211 [item addOutput:m_subtitleOutput];
212
213 CFTimeInterval currentCAFrameTime = CACurrentMediaTime();
214 CMTime currentCMFrameTime = [m_videoOutput itemTimeForHostTime:currentCAFrameTime];
215 // happens when buffering / loading
216 if (CMTimeCompare(currentCMFrameTime, kCMTimeZero) < 0) {
217 return nullptr;
218 }
219
220 if (![m_videoOutput hasNewPixelBufferForItemTime:currentCMFrameTime])
221 return nullptr;
222
223 CMTime outItemTimeForDisplay = kCMTimeInvalid;
224 CVPixelBufferRef pixelBuffer =
225 [m_videoOutput copyPixelBufferForItemTime:currentCMFrameTime
226 itemTimeForDisplay:&outItemTimeForDisplay];
227 if (!pixelBuffer) {
228#ifdef QT_DEBUG_AVF
229 qWarning("copyPixelBufferForItemTime returned nil");
230 CMTimeShow(currentCMFrameTime);
231#endif
232 return nullptr;
233 }
234
235 CMTime pts =
236 CMTIME_IS_VALID(outItemTimeForDisplay) ? outItemTimeForDisplay : currentCMFrameTime;
237 startTime = qint64(qreal(pts.value) / pts.timescale * 1000000.0);
238
239 width = CVPixelBufferGetWidth(pixelBuffer);
240 height = CVPixelBufferGetHeight(pixelBuffer);
241// auto f = CVPixelBufferGetPixelFormatType(pixelBuffer);
242// char fmt[5];
243// memcpy(fmt, &f, 4);
244// fmt[4] = 0;
245// qDebug() << "copyPixelBuffer" << f << fmt << width << height;
246 return QCFType<CVPixelBufferRef>{ pixelBuffer };
247}
248
249#include "moc_avfvideorenderercontrol_p.cpp"
void setVideoRotation(QtVideo::Rotation)
void setLayer(CALayer *layer) override
virtual void setOutputSettings()