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