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