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(const CVTimeStamp &ts)
134{
135 Q_UNUSED(ts);
136
137 if (!m_sink)
138 return;
139
140 if (!m_layer)
141 return;
142
143 auto *layer = playerLayer();
144 if (!layer.readyForDisplay)
145 return;
147
148 QVideoFrame frame;
149 size_t width, height;
150 qint64 startTime = -1;
151 QCFType<CVPixelBufferRef> pixelBuffer = copyPixelBufferFromLayer(width, height, startTime);
152 if (!pixelBuffer)
153 return;
154 // qDebug() << "Got pixelbuffer with format" << fmt << Qt::hex <<
155 // CVPixelBufferGetPixelFormatType(pixelBuffer);
156
157 auto buffer = std::make_unique<AVFVideoBuffer>(this, std::move(pixelBuffer));
158
159 auto format = buffer->videoFormat();
160 format.setRotation(m_rotation);
161 format.setMirrored(m_mirrored);
162 frame = QVideoFramePrivate::createFrame(std::move(buffer), format);
163
164 if (startTime >= 0) {
165 frame.setStartTime(startTime);
166
167 // Estimate end time from video track's nominal frame rate
168 AVPlayerItem *playerItem = [layer.player currentItem];
169 if (playerItem) {
170 float fps = 0;
171 for (AVPlayerItemTrack *track in playerItem.tracks) {
172 if ([track.assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
173 fps = track.assetTrack.nominalFrameRate;
174 break;
175 }
176 }
177 if (fps > 0)
178 frame.setEndTime(startTime + qint64(1000000.0 / fps));
179 }
180 }
181
182 m_sink->setVideoFrame(frame);
183}
184
185QCFType<CVPixelBufferRef>
186AVFVideoRendererControl::copyPixelBufferFromLayer(size_t &width, size_t &height, qint64 &startTime)
187{
188 AVPlayerLayer *layer = playerLayer();
189 //Is layer valid
190 if (!layer) {
191#ifdef QT_DEBUG_AVF
192 qWarning("copyPixelBufferFromLayer: invalid layer");
193#endif
194 return nullptr;
195 }
196
197 AVPlayerItem * item = [[layer player] currentItem];
198
199 if (!m_videoOutput) {
200 if (!m_outputSettings)
202 m_videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:m_outputSettings];
203 [m_videoOutput setDelegate:nil queue:nil];
204 }
205 if (!m_subtitleOutput) {
206 m_subtitleOutput = [[AVPlayerItemLegibleOutput alloc] init];
207 m_subtitleDelegate = [[SubtitleDelegate alloc] initWithRenderer:this];
208 [m_subtitleOutput setDelegate:m_subtitleDelegate queue:dispatch_get_main_queue()];
209 }
210 if (![item.outputs containsObject:m_videoOutput])
211 [item addOutput:m_videoOutput];
212 if (![item.outputs containsObject:m_subtitleOutput])
213 [item addOutput:m_subtitleOutput];
214
215 CFTimeInterval currentCAFrameTime = CACurrentMediaTime();
216 CMTime currentCMFrameTime = [m_videoOutput itemTimeForHostTime:currentCAFrameTime];
217 // happens when buffering / loading
218 if (CMTimeCompare(currentCMFrameTime, kCMTimeZero) < 0) {
219 return nullptr;
220 }
221
222 if (![m_videoOutput hasNewPixelBufferForItemTime:currentCMFrameTime])
223 return nullptr;
224
225 CMTime outItemTimeForDisplay = kCMTimeInvalid;
226 CVPixelBufferRef pixelBuffer =
227 [m_videoOutput copyPixelBufferForItemTime:currentCMFrameTime
228 itemTimeForDisplay:&outItemTimeForDisplay];
229 if (!pixelBuffer) {
230#ifdef QT_DEBUG_AVF
231 qWarning("copyPixelBufferForItemTime returned nil");
232 CMTimeShow(currentCMFrameTime);
233#endif
234 return nullptr;
235 }
236
237 CMTime pts =
238 CMTIME_IS_VALID(outItemTimeForDisplay) ? outItemTimeForDisplay : currentCMFrameTime;
239 startTime = qint64(qreal(pts.value) / pts.timescale * 1000000.0);
240
241 width = CVPixelBufferGetWidth(pixelBuffer);
242 height = CVPixelBufferGetHeight(pixelBuffer);
243// auto f = CVPixelBufferGetPixelFormatType(pixelBuffer);
244// char fmt[5];
245// memcpy(fmt, &f, 4);
246// fmt[4] = 0;
247// qDebug() << "copyPixelBuffer" << f << fmt << width << height;
248 return QCFType<CVPixelBufferRef>{ pixelBuffer };
249}
250
251#include "moc_avfvideorenderercontrol_p.cpp"
void setVideoRotation(QtVideo::Rotation)
void setLayer(CALayer *layer) override
virtual void setOutputSettings()