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