1 | // Copyright (C) 2022 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 | |
4 | #include "qffmpegvideoframeencoder_p.h" |
5 | #include "qffmpegmediaformatinfo_p.h" |
6 | #include "qffmpegencoderoptions_p.h" |
7 | #include "qffmpegvideoencoderutils_p.h" |
8 | #include "qffmpegcodecstorage_p.h" |
9 | #include <qloggingcategory.h> |
10 | #include <QtMultimedia/private/qmaybe_p.h> |
11 | |
12 | extern "C" { |
13 | #include "libavutil/display.h" |
14 | #include "libavutil/pixdesc.h" |
15 | } |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | static Q_LOGGING_CATEGORY(qLcVideoFrameEncoder, "qt.multimedia.ffmpeg.videoencoder" ); |
20 | |
21 | namespace QFFmpeg { |
22 | |
23 | namespace { |
24 | |
25 | AVCodecID avCodecID(const QMediaEncoderSettings &settings) |
26 | { |
27 | const QMediaFormat::VideoCodec qVideoCodec = settings.videoCodec(); |
28 | return QFFmpegMediaFormatInfo::codecIdForVideoCodec(codec: qVideoCodec); |
29 | } |
30 | |
31 | } // namespace |
32 | |
33 | VideoFrameEncoderUPtr VideoFrameEncoder::create(const QMediaEncoderSettings &encoderSettings, |
34 | const SourceParams &sourceParams, |
35 | AVFormatContext *formatContext) |
36 | { |
37 | Q_ASSERT(isSwPixelFormat(sourceParams.swFormat)); |
38 | Q_ASSERT(isHwPixelFormat(sourceParams.format) || sourceParams.swFormat == sourceParams.format); |
39 | |
40 | AVStream *stream = createStream(sourceParams, formatContext); |
41 | |
42 | if (!stream) |
43 | return nullptr; |
44 | |
45 | CreationResult result; |
46 | |
47 | auto findAndOpenAVEncoder = [&](const auto &scoresGetter, const auto &creator) { |
48 | auto createWithTargetFormatFallback = [&](const Codec &codec) { |
49 | result = creator(codec, AVPixelFormatSet{}); |
50 | #ifdef Q_OS_ANDROID |
51 | // On Android some encoders fail to open encoders with 4:2:0 formats unless it's NV12. |
52 | // Let's fallback to another format. |
53 | if (!result.encoder) { |
54 | const auto targetFormatDesc = av_pix_fmt_desc_get(result.targetFormat); |
55 | const bool is420TargetFormat = targetFormatDesc |
56 | && targetFormatDesc->log2_chroma_h == 1 |
57 | && targetFormatDesc->log2_chroma_w == 1; |
58 | if (is420TargetFormat && result.targetFormat != AV_PIX_FMT_NV12) |
59 | result = creator(codec, AVPixelFormatSet{ result.targetFormat }); |
60 | } |
61 | #endif |
62 | return result.encoder != nullptr; |
63 | }; |
64 | return QFFmpeg::findAndOpenAVEncoder(codecId: avCodecID(settings: encoderSettings), scoresGetter, |
65 | codecOpener: createWithTargetFormatFallback); |
66 | }; |
67 | |
68 | { |
69 | const auto &deviceTypes = HWAccel::encodingDeviceTypes(); |
70 | |
71 | auto findDeviceType = [&](const Codec &codec) { |
72 | std::optional<AVPixelFormat> pixelFormat = findAVPixelFormat(codec, predicate: &isHwPixelFormat); |
73 | if (!pixelFormat) |
74 | return deviceTypes.end(); |
75 | |
76 | return std::find_if(first: deviceTypes.begin(), last: deviceTypes.end(), |
77 | pred: [pixelFormat](AVHWDeviceType deviceType) { |
78 | return pixelFormatForHwDevice(deviceType) == pixelFormat; |
79 | }); |
80 | }; |
81 | |
82 | findAndOpenAVEncoder( |
83 | [&](const Codec &codec) { |
84 | const auto found = findDeviceType(codec); |
85 | if (found == deviceTypes.end()) |
86 | return NotSuitableAVScore; |
87 | |
88 | return DefaultAVScore - static_cast<AVScore>(found - deviceTypes.begin()); |
89 | }, |
90 | [&](const Codec &codec, const AVPixelFormatSet &prohibitedTargetFormats) { |
91 | HWAccelUPtr hwAccel = HWAccel::create(deviceType: *findDeviceType(codec)); |
92 | if (!hwAccel) |
93 | return CreationResult{}; |
94 | if (!hwAccel->matchesSizeContraints(size: encoderSettings.videoResolution())) |
95 | return CreationResult{}; |
96 | return create(stream, codec, hwAccel: std::move(hwAccel), sourceParams, encoderSettings, |
97 | prohibitedTargetFormats); |
98 | }); |
99 | } |
100 | |
101 | if (!result.encoder) { |
102 | findAndOpenAVEncoder( |
103 | [&](const Codec &codec) { |
104 | return findSWFormatScores(codec, sourceSWFormat: sourceParams.swFormat); |
105 | }, |
106 | [&](const Codec &codec, const AVPixelFormatSet &prohibitedTargetFormats) { |
107 | return create(stream, codec, hwAccel: nullptr, sourceParams, encoderSettings, |
108 | prohibitedTargetFormats); |
109 | }); |
110 | } |
111 | |
112 | if (auto &encoder = result.encoder) |
113 | qCDebug(qLcVideoFrameEncoder) |
114 | << "found" << (encoder->m_accel ? "hw" : "sw" ) << "encoder" |
115 | << encoder->m_codec.name() << "for id" << encoder->m_codec.id(); |
116 | else |
117 | qCWarning(qLcVideoFrameEncoder) << "No valid video codecs found" ; |
118 | |
119 | return std::move(result.encoder); |
120 | } |
121 | |
122 | VideoFrameEncoder::VideoFrameEncoder(AVStream *stream, const Codec &codec, HWAccelUPtr hwAccel, |
123 | const SourceParams &sourceParams, |
124 | const QMediaEncoderSettings &encoderSettings) |
125 | : m_settings(encoderSettings), |
126 | m_stream(stream), |
127 | m_codec(codec), |
128 | m_accel(std::move(hwAccel)), |
129 | m_sourceSize(sourceParams.size), |
130 | m_sourceFormat(sourceParams.format), |
131 | m_sourceSWFormat(sourceParams.swFormat) |
132 | { |
133 | } |
134 | |
135 | AVStream *VideoFrameEncoder::createStream(const SourceParams &sourceParams, |
136 | AVFormatContext *formatContext) |
137 | { |
138 | AVStream *stream = avformat_new_stream(s: formatContext, c: nullptr); |
139 | |
140 | if (!stream) |
141 | return stream; |
142 | |
143 | stream->id = formatContext->nb_streams - 1; |
144 | stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; |
145 | |
146 | stream->codecpar->color_trc = sourceParams.colorTransfer; |
147 | stream->codecpar->color_space = sourceParams.colorSpace; |
148 | stream->codecpar->color_range = sourceParams.colorRange; |
149 | |
150 | if (sourceParams.transform.rotation != QtVideo::Rotation::None || sourceParams.transform.mirrorredHorizontallyAfterRotation) { |
151 | constexpr auto displayMatrixSize = sizeof(int32_t) * 9; |
152 | AVPacketSideData sideData = { .data: reinterpret_cast<uint8_t *>(av_malloc(size: displayMatrixSize)), |
153 | .size: displayMatrixSize, .type: AV_PKT_DATA_DISPLAYMATRIX }; |
154 | int32_t *matrix = reinterpret_cast<int32_t *>(sideData.data); |
155 | av_display_rotation_set(matrix, angle: static_cast<double>(sourceParams.transform.rotation)); |
156 | if (sourceParams.transform.mirrorredHorizontallyAfterRotation) |
157 | av_display_matrix_flip(matrix, hflip: sourceParams.transform.mirrorredHorizontallyAfterRotation, vflip: false); |
158 | |
159 | addStreamSideData(stream, sideData); |
160 | } |
161 | |
162 | return stream; |
163 | } |
164 | |
165 | VideoFrameEncoder::CreationResult |
166 | VideoFrameEncoder::create(AVStream *stream, const Codec &codec, HWAccelUPtr hwAccel, |
167 | const SourceParams &sourceParams, |
168 | const QMediaEncoderSettings &encoderSettings, |
169 | const AVPixelFormatSet &prohibitedTargetFormats) |
170 | { |
171 | VideoFrameEncoderUPtr frameEncoder(new VideoFrameEncoder(stream, codec, std::move(hwAccel), |
172 | sourceParams, encoderSettings)); |
173 | frameEncoder->initTargetSize(); |
174 | |
175 | frameEncoder->initCodecFrameRate(); |
176 | |
177 | if (!frameEncoder->initTargetFormats(prohibitedTargetFormats)) |
178 | return {}; |
179 | |
180 | frameEncoder->initStream(); |
181 | |
182 | const AVPixelFormat targetFormat = frameEncoder->m_targetFormat; |
183 | |
184 | if (!frameEncoder->initCodecContext()) |
185 | return { .encoder: nullptr, .targetFormat: targetFormat }; |
186 | |
187 | if (!frameEncoder->open()) |
188 | return { .encoder: nullptr, .targetFormat: targetFormat }; |
189 | |
190 | frameEncoder->updateConversions(); |
191 | |
192 | return { .encoder: std::move(frameEncoder), .targetFormat: targetFormat }; |
193 | } |
194 | |
195 | void VideoFrameEncoder::initTargetSize() |
196 | { |
197 | m_targetSize = adjustVideoResolution(codec: m_codec, requestedResolution: m_settings.videoResolution()); |
198 | |
199 | #ifdef Q_OS_WINDOWS |
200 | // TODO: investigate, there might be more encoders not supporting odd resolution |
201 | if (m_codec.name() == u"h264_mf" ) { |
202 | auto makeEven = [](int size) { return size & ~1; }; |
203 | const QSize fixedSize(makeEven(m_targetSize.width()), makeEven(m_targetSize.height())); |
204 | if (fixedSize != m_targetSize) { |
205 | qCDebug(qLcVideoFrameEncoder) << "Fix odd video resolution for codec" << m_codec.name() |
206 | << ":" << m_targetSize << "->" << fixedSize; |
207 | m_targetSize = fixedSize; |
208 | } |
209 | } |
210 | #endif |
211 | } |
212 | |
213 | void VideoFrameEncoder::initCodecFrameRate() |
214 | { |
215 | const auto frameRates = m_codec.frameRates(); |
216 | if (qLcVideoFrameEncoder().isEnabled(type: QtDebugMsg)) |
217 | for (AVRational rate : frameRates) |
218 | qCDebug(qLcVideoFrameEncoder) << "supported frame rate:" << rate; |
219 | |
220 | m_codecFrameRate = adjustFrameRate(supportedRates: frameRates, requestedRate: m_settings.videoFrameRate()); |
221 | qCDebug(qLcVideoFrameEncoder) << "Adjusted frame rate:" << m_codecFrameRate; |
222 | } |
223 | |
224 | bool VideoFrameEncoder::initTargetFormats(const AVPixelFormatSet &prohibitedTargetFormats) |
225 | { |
226 | const auto format = findTargetFormat(sourceFormat: m_sourceFormat, sourceSWFormat: m_sourceSWFormat, codec: m_codec, accel: m_accel.get(), |
227 | prohibitedFormats: prohibitedTargetFormats); |
228 | |
229 | if (!format) { |
230 | qWarning() << "Could not find target format for codecId" << m_codec.id(); |
231 | return false; |
232 | } |
233 | |
234 | m_targetFormat = *format; |
235 | |
236 | if (isHwPixelFormat(format: m_targetFormat)) { |
237 | Q_ASSERT(m_accel); |
238 | |
239 | // don't pass prohibitedTargetFormats here as m_targetSWFormat is the format, |
240 | // from which we load a hardware texture, and the format doesn't impact on encoding. |
241 | const auto swFormat = findTargetSWFormat(sourceSWFormat: m_sourceSWFormat, codec: m_codec, accel: *m_accel); |
242 | if (!swFormat) { |
243 | qWarning() << "Cannot find software target format. sourceSWFormat:" << m_sourceSWFormat |
244 | << "targetFormat:" << m_targetFormat; |
245 | return false; |
246 | } |
247 | |
248 | m_targetSWFormat = *swFormat; |
249 | |
250 | m_accel->createFramesContext(swFormat: m_targetSWFormat, size: m_targetSize); |
251 | if (!m_accel->hwFramesContextAsBuffer()) |
252 | return false; |
253 | } else { |
254 | m_targetSWFormat = m_targetFormat; |
255 | } |
256 | |
257 | return true; |
258 | } |
259 | |
260 | VideoFrameEncoder::~VideoFrameEncoder() = default; |
261 | |
262 | void VideoFrameEncoder::initStream() |
263 | { |
264 | m_stream->codecpar->codec_id = m_codec.id(); |
265 | |
266 | // Apples HEVC decoders don't like the hev1 tag ffmpeg uses by default, use hvc1 as the more |
267 | // commonly accepted tag |
268 | if (m_codec.id() == AV_CODEC_ID_HEVC) |
269 | m_stream->codecpar->codec_tag = MKTAG('h', 'v', 'c', '1'); |
270 | else |
271 | m_stream->codecpar->codec_tag = 0; |
272 | |
273 | // ### Fix hardcoded values |
274 | m_stream->codecpar->format = m_targetFormat; |
275 | m_stream->codecpar->width = m_targetSize.width(); |
276 | m_stream->codecpar->height = m_targetSize.height(); |
277 | m_stream->codecpar->sample_aspect_ratio = AVRational{ .num: 1, .den: 1 }; |
278 | #if QT_CODEC_PARAMETERS_HAVE_FRAMERATE |
279 | m_stream->codecpar->framerate = m_codecFrameRate; |
280 | #endif |
281 | |
282 | const auto frameRates = m_codec.frameRates(); |
283 | m_stream->time_base = adjustFrameTimeBase(supportedRates: frameRates, frameRate: m_codecFrameRate); |
284 | } |
285 | |
286 | bool VideoFrameEncoder::initCodecContext() |
287 | { |
288 | Q_ASSERT(m_stream->codecpar->codec_id); |
289 | |
290 | m_codecContext.reset(p: avcodec_alloc_context3(codec: m_codec.get())); |
291 | if (!m_codecContext) { |
292 | qWarning() << "Could not allocate codec context" ; |
293 | return false; |
294 | } |
295 | |
296 | // copies format, size, color params, framerate |
297 | avcodec_parameters_to_context(codec: m_codecContext.get(), par: m_stream->codecpar); |
298 | #if !QT_CODEC_PARAMETERS_HAVE_FRAMERATE |
299 | m_codecContext->framerate = m_codecFrameRate; |
300 | #endif |
301 | m_codecContext->time_base = m_stream->time_base; |
302 | qCDebug(qLcVideoFrameEncoder) << "codecContext time base" << m_codecContext->time_base.num |
303 | << m_codecContext->time_base.den; |
304 | |
305 | if (m_accel) { |
306 | auto deviceContext = m_accel->hwDeviceContextAsBuffer(); |
307 | Q_ASSERT(deviceContext); |
308 | m_codecContext->hw_device_ctx = av_buffer_ref(buf: deviceContext); |
309 | |
310 | if (auto framesContext = m_accel->hwFramesContextAsBuffer()) |
311 | m_codecContext->hw_frames_ctx = av_buffer_ref(buf: framesContext); |
312 | } |
313 | |
314 | avcodec_parameters_from_context(par: m_stream->codecpar, codec: m_codecContext.get()); |
315 | |
316 | return true; |
317 | } |
318 | |
319 | bool VideoFrameEncoder::open() |
320 | { |
321 | Q_ASSERT(m_codecContext); |
322 | |
323 | AVDictionaryHolder opts; |
324 | applyVideoEncoderOptions(settings: m_settings, codecName: QByteArray{ m_codec.name() }, codec: m_codecContext.get(), opts); |
325 | applyExperimentalCodecOptions(codec: m_codec, opts); |
326 | |
327 | const int res = avcodec_open2(avctx: m_codecContext.get(), codec: m_codec.get(), options: opts); |
328 | if (res < 0) { |
329 | qCWarning(qLcVideoFrameEncoder) |
330 | << "Couldn't open video encoder" << m_codec.name() << "; result:" << err2str(errnum: res); |
331 | return false; |
332 | } |
333 | qCDebug(qLcVideoFrameEncoder) << "video codec opened" << res << "time base" |
334 | << m_codecContext->time_base; |
335 | return true; |
336 | } |
337 | |
338 | qreal VideoFrameEncoder::codecFrameRate() const |
339 | { |
340 | return m_codecFrameRate.den ? qreal(m_codecFrameRate.num) / m_codecFrameRate.den : 0.; |
341 | } |
342 | |
343 | qint64 VideoFrameEncoder::getPts(qint64 us) const |
344 | { |
345 | qint64 div = 1'000'000 * m_stream->time_base.num; |
346 | return div != 0 ? (us * m_stream->time_base.den + div / 2) / div : 0; |
347 | } |
348 | |
349 | const AVRational &VideoFrameEncoder::getTimeBase() const |
350 | { |
351 | return m_stream->time_base; |
352 | } |
353 | |
354 | namespace { |
355 | struct FrameConverter |
356 | { |
357 | FrameConverter(AVFrameUPtr inputFrame) : m_inputFrame{ std::move(inputFrame) } { } |
358 | |
359 | int downloadFromHw() |
360 | { |
361 | AVFrameUPtr cpuFrame = makeAVFrame(); |
362 | |
363 | int err = av_hwframe_transfer_data(dst: cpuFrame.get(), src: currentFrame(), flags: 0); |
364 | if (err < 0) { |
365 | qCDebug(qLcVideoFrameEncoder) |
366 | << "Error transferring frame data to surface." << err2str(errnum: err); |
367 | return err; |
368 | } |
369 | |
370 | setFrame(std::move(cpuFrame)); |
371 | return 0; |
372 | } |
373 | |
374 | void convert(SwsContext *scaleContext, AVPixelFormat format, const QSize &size) |
375 | { |
376 | AVFrameUPtr scaledFrame = makeAVFrame(); |
377 | |
378 | scaledFrame->format = format; |
379 | scaledFrame->width = size.width(); |
380 | scaledFrame->height = size.height(); |
381 | |
382 | av_frame_get_buffer(frame: scaledFrame.get(), align: 0); |
383 | |
384 | const AVFrame *srcFrame = currentFrame(); |
385 | |
386 | const auto scaledHeight = |
387 | sws_scale(c: scaleContext, srcSlice: srcFrame->data, srcStride: srcFrame->linesize, srcSliceY: 0, srcSliceH: srcFrame->height, |
388 | dst: scaledFrame->data, dstStride: scaledFrame->linesize); |
389 | |
390 | if (scaledHeight != scaledFrame->height) |
391 | qCWarning(qLcVideoFrameEncoder) |
392 | << "Scaled height" << scaledHeight << "!=" << scaledFrame->height; |
393 | |
394 | setFrame(std::move(scaledFrame)); |
395 | } |
396 | |
397 | int uploadToHw(HWAccel *accel) |
398 | { |
399 | auto *hwFramesContext = accel->hwFramesContextAsBuffer(); |
400 | Q_ASSERT(hwFramesContext); |
401 | AVFrameUPtr hwFrame = makeAVFrame(); |
402 | if (!hwFrame) |
403 | return AVERROR(ENOMEM); |
404 | |
405 | int err = av_hwframe_get_buffer(hwframe_ctx: hwFramesContext, frame: hwFrame.get(), flags: 0); |
406 | if (err < 0) { |
407 | qCDebug(qLcVideoFrameEncoder) << "Error getting HW buffer" << err2str(errnum: err); |
408 | return err; |
409 | } else { |
410 | qCDebug(qLcVideoFrameEncoder) << "got HW buffer" ; |
411 | } |
412 | if (!hwFrame->hw_frames_ctx) { |
413 | qCDebug(qLcVideoFrameEncoder) << "no hw frames context" ; |
414 | return AVERROR(ENOMEM); |
415 | } |
416 | err = av_hwframe_transfer_data(dst: hwFrame.get(), src: currentFrame(), flags: 0); |
417 | if (err < 0) { |
418 | qCDebug(qLcVideoFrameEncoder) |
419 | << "Error transferring frame data to surface." << err2str(errnum: err); |
420 | return err; |
421 | } |
422 | |
423 | setFrame(std::move(hwFrame)); |
424 | |
425 | return 0; |
426 | } |
427 | |
428 | QMaybe<AVFrameUPtr, int> takeResultFrame() |
429 | { |
430 | // Ensure that object is reset to empty state |
431 | AVFrameUPtr converted = std::move(m_convertedFrame); |
432 | AVFrameUPtr input = std::move(m_inputFrame); |
433 | |
434 | if (!converted) |
435 | return input; |
436 | |
437 | // Copy metadata except size and format from input frame |
438 | const int status = av_frame_copy_props(dst: converted.get(), src: input.get()); |
439 | if (status != 0) |
440 | return status; |
441 | |
442 | return converted; |
443 | } |
444 | |
445 | private: |
446 | void setFrame(AVFrameUPtr frame) { m_convertedFrame = std::move(frame); } |
447 | |
448 | AVFrame *currentFrame() const |
449 | { |
450 | if (m_convertedFrame) |
451 | return m_convertedFrame.get(); |
452 | return m_inputFrame.get(); |
453 | } |
454 | |
455 | AVFrameUPtr m_inputFrame; |
456 | AVFrameUPtr m_convertedFrame; |
457 | }; |
458 | } // namespace |
459 | |
460 | int VideoFrameEncoder::sendFrame(AVFrameUPtr inputFrame) |
461 | { |
462 | if (!m_codecContext) { |
463 | qWarning() << "codec context is not initialized!" ; |
464 | return AVERROR(EINVAL); |
465 | } |
466 | |
467 | if (!inputFrame) |
468 | return avcodec_send_frame(avctx: m_codecContext.get(), frame: nullptr); // Flush |
469 | |
470 | if (!updateSourceFormatAndSize(frame: inputFrame.get())) |
471 | return AVERROR(EINVAL); |
472 | |
473 | FrameConverter converter{ std::move(inputFrame) }; |
474 | |
475 | if (m_downloadFromHW) { |
476 | const int status = converter.downloadFromHw(); |
477 | if (status != 0) |
478 | return status; |
479 | } |
480 | |
481 | if (m_scaleContext) |
482 | converter.convert(scaleContext: m_scaleContext.get(), format: m_targetSWFormat, size: m_targetSize); |
483 | |
484 | if (m_uploadToHW) { |
485 | const int status = converter.uploadToHw(accel: m_accel.get()); |
486 | if (status != 0) |
487 | return status; |
488 | } |
489 | |
490 | const QMaybe<AVFrameUPtr, int> resultFrame = converter.takeResultFrame(); |
491 | if (!resultFrame) |
492 | return resultFrame.error(); |
493 | |
494 | AVRational timeBase{}; |
495 | int64_t pts{}; |
496 | getAVFrameTime(frame: *resultFrame.value(), pts, timeBase); |
497 | qCDebug(qLcVideoFrameEncoder) << "sending frame" << pts << "*" << timeBase; |
498 | |
499 | return avcodec_send_frame(avctx: m_codecContext.get(), frame: resultFrame.value().get()); |
500 | } |
501 | |
502 | qint64 VideoFrameEncoder::estimateDuration(const AVPacket &packet, bool isFirstPacket) |
503 | { |
504 | qint64 duration = 0; // In stream units, multiply by time_base to get seconds |
505 | |
506 | if (isFirstPacket) { |
507 | // First packet - Estimate duration from frame rate. Duration must |
508 | // be set for single-frame videos, otherwise they won't open in |
509 | // media player. |
510 | const AVRational frameDuration = av_inv_q(q: m_codecContext->framerate); |
511 | duration = av_rescale_q(a: 1, bq: frameDuration, cq: m_stream->time_base); |
512 | } else { |
513 | // Duration is calculated from actual packet times. TODO: Handle discontinuities |
514 | duration = packet.pts - m_lastPacketTime; |
515 | } |
516 | |
517 | return duration; |
518 | } |
519 | |
520 | AVPacketUPtr VideoFrameEncoder::retrievePacket() |
521 | { |
522 | if (!m_codecContext) |
523 | return nullptr; |
524 | |
525 | auto getPacket = [&]() { |
526 | AVPacketUPtr packet(av_packet_alloc()); |
527 | const int ret = avcodec_receive_packet(avctx: m_codecContext.get(), avpkt: packet.get()); |
528 | if (ret < 0) { |
529 | if (ret != AVERROR(EOF) && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) |
530 | qCDebug(qLcVideoFrameEncoder) << "Error receiving packet" << ret << err2str(errnum: ret); |
531 | return AVPacketUPtr{}; |
532 | } |
533 | auto ts = timeStampMs(ts: packet->pts, base: m_stream->time_base); |
534 | |
535 | qCDebug(qLcVideoFrameEncoder) |
536 | << "got a packet" << packet->pts << packet->dts << (ts ? *ts : 0); |
537 | |
538 | packet->stream_index = m_stream->id; |
539 | |
540 | if (packet->duration == 0) { |
541 | const bool firstFrame = m_lastPacketTime == AV_NOPTS_VALUE; |
542 | packet->duration = estimateDuration(packet: *packet, isFirstPacket: firstFrame); |
543 | } |
544 | |
545 | m_lastPacketTime = packet->pts; |
546 | |
547 | return packet; |
548 | }; |
549 | |
550 | auto fixPacketDts = [&](AVPacket &packet) { |
551 | // Workaround for some ffmpeg codecs bugs (e.g. nvenc) |
552 | // Ideally, packet->pts < packet->dts is not expected |
553 | |
554 | if (packet.dts == AV_NOPTS_VALUE) |
555 | return true; |
556 | |
557 | packet.dts -= m_packetDtsOffset; |
558 | |
559 | if (packet.pts != AV_NOPTS_VALUE && packet.pts < packet.dts) { |
560 | m_packetDtsOffset += packet.dts - packet.pts; |
561 | packet.dts = packet.pts; |
562 | |
563 | if (m_prevPacketDts != AV_NOPTS_VALUE && packet.dts < m_prevPacketDts) { |
564 | qCWarning(qLcVideoFrameEncoder) |
565 | << "Skip packet; failed to fix dts:" << packet.dts << m_prevPacketDts; |
566 | return false; |
567 | } |
568 | } |
569 | |
570 | m_prevPacketDts = packet.dts; |
571 | |
572 | return true; |
573 | }; |
574 | |
575 | while (auto packet = getPacket()) { |
576 | if (fixPacketDts(*packet)) |
577 | return packet; |
578 | } |
579 | |
580 | return nullptr; |
581 | } |
582 | |
583 | bool VideoFrameEncoder::updateSourceFormatAndSize(const AVFrame *frame) |
584 | { |
585 | Q_ASSERT(frame); |
586 | |
587 | const QSize frameSize(frame->width, frame->height); |
588 | const AVPixelFormat frameFormat = static_cast<AVPixelFormat>(frame->format); |
589 | |
590 | if (frameSize == m_sourceSize && frameFormat == m_sourceFormat) |
591 | return true; |
592 | |
593 | auto applySourceFormatAndSize = [&](AVPixelFormat swFormat) { |
594 | m_sourceSize = frameSize; |
595 | m_sourceFormat = frameFormat; |
596 | m_sourceSWFormat = swFormat; |
597 | updateConversions(); |
598 | return true; |
599 | }; |
600 | |
601 | if (frameFormat == m_sourceFormat) |
602 | return applySourceFormatAndSize(m_sourceSWFormat); |
603 | |
604 | if (frameFormat == AV_PIX_FMT_NONE) { |
605 | qWarning() << "Got a frame with invalid pixel format" ; |
606 | return false; |
607 | } |
608 | |
609 | if (isSwPixelFormat(format: frameFormat)) |
610 | return applySourceFormatAndSize(frameFormat); |
611 | |
612 | auto framesCtx = reinterpret_cast<const AVHWFramesContext *>(frame->hw_frames_ctx->data); |
613 | if (!framesCtx || framesCtx->sw_format == AV_PIX_FMT_NONE) { |
614 | qWarning() << "Cannot update conversions as hw frame has invalid framesCtx" << framesCtx; |
615 | return false; |
616 | } |
617 | |
618 | return applySourceFormatAndSize(framesCtx->sw_format); |
619 | } |
620 | |
621 | void VideoFrameEncoder::updateConversions() |
622 | { |
623 | const bool needToScale = m_sourceSize != m_targetSize; |
624 | const bool zeroCopy = m_sourceFormat == m_targetFormat && !needToScale; |
625 | |
626 | m_scaleContext.reset(); |
627 | |
628 | if (zeroCopy) { |
629 | m_downloadFromHW = false; |
630 | m_uploadToHW = false; |
631 | |
632 | qCDebug(qLcVideoFrameEncoder) << "zero copy encoding, format" << m_targetFormat; |
633 | // no need to initialize any converters |
634 | return; |
635 | } |
636 | |
637 | m_downloadFromHW = m_sourceFormat != m_sourceSWFormat; |
638 | m_uploadToHW = m_targetFormat != m_targetSWFormat; |
639 | |
640 | if (m_sourceSWFormat != m_targetSWFormat || needToScale) { |
641 | qCDebug(qLcVideoFrameEncoder) |
642 | << "video source and encoder use different formats:" << m_sourceSWFormat |
643 | << m_targetSWFormat << "or sizes:" << m_sourceSize << m_targetSize; |
644 | |
645 | const int conversionType = getScaleConversionType(sourceSize: m_sourceSize, targetSize: m_targetSize); |
646 | |
647 | m_scaleContext = createSwsContext(srcSize: m_sourceSize, srcPixFmt: m_sourceSWFormat, dstSize: m_targetSize, |
648 | dstPixFmt: m_targetSWFormat, conversionType); |
649 | } |
650 | |
651 | qCDebug(qLcVideoFrameEncoder) << "VideoFrameEncoder conversions initialized:" |
652 | << "sourceFormat:" << m_sourceFormat |
653 | << (isHwPixelFormat(format: m_sourceFormat) ? "(hw)" : "(sw)" ) |
654 | << "targetFormat:" << m_targetFormat |
655 | << (isHwPixelFormat(format: m_targetFormat) ? "(hw)" : "(sw)" ) |
656 | << "sourceSWFormat:" << m_sourceSWFormat |
657 | << "targetSWFormat:" << m_targetSWFormat |
658 | << "scaleContext:" << m_scaleContext.get(); |
659 | } |
660 | |
661 | } // namespace QFFmpeg |
662 | |
663 | QT_END_NAMESPACE |
664 | |