diff --git a/AVQt/filter/EncoderQSV.cpp b/AVQt/filter/EncoderQSV.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7950b75f8871a94628b1db329cab7b29281f39a4 --- /dev/null +++ b/AVQt/filter/EncoderQSV.cpp @@ -0,0 +1,427 @@ +// +// Created by silas on 4/18/21. +// + +#include "private/EncoderQSV_p.h" +#include "EncoderQSV.h" + +#include "output/IPacketSink.h" + +namespace AVQt { + EncoderQSV::EncoderQSV(CODEC codec, int bitrate, QObject *parent) : QThread(parent), d_ptr(new EncoderQSVPrivate(this)) { + Q_D(AVQt::EncoderQSV); + + d->m_codec = codec; + d->m_bitrate = bitrate; + } + + [[maybe_unused]] EncoderQSV::EncoderQSV(EncoderQSVPrivate &p) : d_ptr(&p) { + + } + + EncoderQSV::EncoderQSV(EncoderQSV &&other) noexcept: d_ptr(other.d_ptr) { + other.d_ptr = nullptr; + d_ptr->q_ptr = this; + } + + EncoderQSV::~EncoderQSV() { + delete d_ptr; + } + + int EncoderQSV::init() { + Q_D(AVQt::EncoderQSV); + +// int ret = 0; +// constexpr auto strBufSize = 64; +// char strBuf[strBufSize]; + + std::string codec_name; + switch (d->m_codec) { + case CODEC::H264: + codec_name = "h264_qsv"; + break; + case CODEC::HEVC: + codec_name = "hevc_qsv"; + break; + case CODEC::VP9: + codec_name = "vp9_qsv"; + break; + case CODEC::VP8: +// codec_name = "vp8_qsv"; + qFatal("[AVQt::EncoderQSV] Unsupported codec: VP8"); + case CODEC::MPEG2: + codec_name = "mpeg2_qsv"; + break; + case CODEC::AV1: + qFatal("[AVQt::EncoderQSV] Unsupported codec: AV1"); + } + + d->m_pCodec = avcodec_find_encoder_by_name(codec_name.c_str()); + if (!d->m_pCodec) { + qFatal("Could not find encoder: %s", codec_name.c_str()); + } + + return 0; + } + + int EncoderQSV::deinit() { + Q_D(AVQt::EncoderQSV); + + stop(); + + { + QMutexLocker lock(&d->m_cbListMutex); + for (const auto &cb: d->m_cbList) { + cb->deinit(this); + } + } + + if (d->m_pDeviceCtx) { + av_buffer_unref(&d->m_pDeviceCtx); + } + + d->m_pCodec = nullptr; + + if (d->m_pCodecCtx) { + if (avcodec_is_open(d->m_pCodecCtx)) { + avcodec_close(d->m_pCodecCtx); + } + avcodec_free_context(&d->m_pCodecCtx); + } + + if (d->m_pFramesCtx) { + av_buffer_unref(&d->m_pFramesCtx); + } + + return 0; + } + + int EncoderQSV::start() { + Q_D(AVQt::EncoderQSV); + + bool shouldBe = false; + if (d->m_running.compare_exchange_strong(shouldBe, true)) { + d->m_paused.store(false); + + QThread::start(); + + started(); + } + + return 0; + } + + int EncoderQSV::stop() { + Q_D(AVQt::EncoderQSV); + + bool shouldBe = true; + if (d->m_running.compare_exchange_strong(shouldBe, false)) { + d->m_paused.store(false); + + { + QMutexLocker lock{&d->m_cbListMutex}; + for (const auto &cb: d->m_cbList) { + cb->stop(this); + } + } + + wait(); + + { + QMutexLocker lock{&d->m_inputQueueMutex}; + while (!d->m_inputQueue.isEmpty()) { + auto frame = d->m_inputQueue.dequeue(); + av_frame_free(&frame.first); + } + } + + stopped(); + } + + return 0; + } + + void EncoderQSV::pause(bool pause) { + Q_D(AVQt::EncoderQSV); + + bool shouldBe = !pause; + if (d->m_paused.compare_exchange_strong(shouldBe, pause)) { + paused(pause); + } + } + + bool EncoderQSV::isPaused() { + Q_D(AVQt::EncoderQSV); + return d->m_paused.load(); + } + + int EncoderQSV::init(IFrameSource *source, AVRational framerate, int64_t duration) { + Q_UNUSED(source) + Q_UNUSED(duration) + Q_D(AVQt::EncoderQSV); + d->m_framerate = framerate; + init(); + return 0; + } + + int EncoderQSV::deinit(IFrameSource *source) { + Q_UNUSED(source) + deinit(); + return 0; + } + + int EncoderQSV::start(IFrameSource *source) { + Q_UNUSED(source) + start(); + return 0; + } + + int EncoderQSV::stop(IFrameSource *source) { + Q_UNUSED(source) + stop(); + return 0; + } + + void EncoderQSV::pause(IFrameSource *source, bool paused) { + Q_UNUSED(source) + pause(paused); + } + + qint64 EncoderQSV::registerCallback(IPacketSink *packetSink, int8_t type) { + Q_D(AVQt::EncoderQSV); + + if (type != IPacketSource::CB_VIDEO) { + return -1; + } + + { + QMutexLocker lock{&d->m_cbListMutex}; + if (!d->m_cbList.contains(packetSink)) { + d->m_cbList.append(packetSink); + return d->m_cbList.indexOf(packetSink); + } else { + return -1; + } + } + } + + qint64 EncoderQSV::unregisterCallback(IPacketSink *packetSink) { + Q_D(AVQt::EncoderQSV); + + { + QMutexLocker lock{&d->m_cbListMutex}; + auto count = d->m_cbList.removeAll(packetSink); + return count > 0 ? count : -1; + } + } + + void EncoderQSV::onFrame(IFrameSource *source, AVFrame *frame, int64_t frameDuration, AVBufferRef *pDeviceCtx) { + Q_UNUSED(source) + Q_D(AVQt::EncoderQSV); + + QMutexLocker locker{&d->m_onFrameMutex}; + int ret = 0; + constexpr auto strBufSize = 64; + char strBuf[strBufSize]; + + bool shouldBe = true; + // Encoder init is only possible with frame parameters + if (d->m_firstFrame.compare_exchange_strong(shouldBe, false)) { + d->m_pCodecCtx = avcodec_alloc_context3(d->m_pCodec); + if (!d->m_pCodecCtx) { + qFatal("Could not create QSV encoder context"); + } + d->m_pCodecCtx->width = frame->width; + d->m_pCodecCtx->height = frame->height; + qDebug("Creating new HW context..."); + + if (pDeviceCtx) { + ret = av_hwdevice_ctx_create_derived(&d->m_pDeviceCtx, AV_HWDEVICE_TYPE_QSV, pDeviceCtx, 0); + } + ret = av_hwdevice_ctx_create(&d->m_pDeviceCtx, AV_HWDEVICE_TYPE_QSV, "", nullptr, 0); + if (ret < 0) { + qFatal("%i: Unable to create AVHWDeviceContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); + } else if (!d->m_pDeviceCtx) { + qFatal("Unable to create AVHWDeviceContext"); + } + + if (frame->hw_frames_ctx) { + d->m_pCodecCtx->sw_pix_fmt = static_cast<AVPixelFormat>(reinterpret_cast<AVHWFramesContext *>(frame->hw_frames_ctx->data)->sw_format); + } else { + d->m_pCodecCtx->sw_pix_fmt = static_cast<AVPixelFormat>(frame->format); + } + + d->m_pFramesCtx = av_hwframe_ctx_alloc(d->m_pDeviceCtx); + auto *framesContext = reinterpret_cast<AVHWFramesContext *>(d->m_pFramesCtx->data); + framesContext->width = d->m_pCodecCtx->width; + framesContext->height = d->m_pCodecCtx->height; + framesContext->sw_format = d->m_pCodecCtx->sw_pix_fmt; + framesContext->format = AV_PIX_FMT_QSV; + framesContext->initial_pool_size = 20; + + ret = av_hwframe_ctx_init(d->m_pFramesCtx); + if (ret != 0) { + qFatal("%i: Could not init HW frames context: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); + } + + d->m_pCodecCtx->pix_fmt = framesContext->format; + + d->m_pCodecCtx->hw_device_ctx = av_buffer_ref(d->m_pDeviceCtx); +// d->m_pCodecCtx->hw_frames_ctx = av_buffer_ref(d->m_pFramesCtx); + d->m_pCodecCtx->bit_rate = d->m_bitrate; + d->m_pCodecCtx->rc_min_rate = static_cast<int>(std::round(d->m_bitrate * 0.8)); + d->m_pCodecCtx->rc_max_rate = static_cast<int>(std::round(d->m_bitrate * 1.1)); + d->m_pCodecCtx->rc_buffer_size = d->m_bitrate * 2; + d->m_pCodecCtx->gop_size = 20; + d->m_pCodecCtx->max_b_frames = 0; + d->m_pCodecCtx->color_primaries = AVCOL_PRI_BT2020; + d->m_pCodecCtx->color_trc = AVCOL_TRC_SMPTE2084; + d->m_pCodecCtx->colorspace = AVCOL_SPC_BT2020_NCL; + d->m_pCodecCtx->framerate = d->m_framerate; + + // Timestamps from frame sources are always microseconds, trying to use this timebase for the encoder too + d->m_pCodecCtx->time_base = av_make_q(1, 1000000); + ret = avcodec_open2(d->m_pCodecCtx, d->m_pCodec, nullptr); + if (ret < 0) { + qFatal("%i: Unable to open QSV encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); + } + qDebug("[AVQt::EncoderQSV] Encoder timebase: %d/%d", d->m_pCodecCtx->time_base.num, d->m_pCodecCtx->time_base.den); + +// // Preallocating frame on gpu + d->m_pHWFrame = av_frame_alloc(); +// ret = av_hwframe_get_buffer(d->m_pFramesCtx, d->m_pHWFrame, 0); + + QMutexLocker lock(&d->m_cbListMutex); + for (const auto &cb: d->m_cbList) { + AVCodecParameters *parameters = avcodec_parameters_alloc(); + avcodec_parameters_from_context(parameters, d->m_pCodecCtx); + cb->init(this, d->m_framerate, d->m_pCodecCtx->time_base, 0, parameters, nullptr, nullptr); + cb->start(this); + } + d->m_initialized.store(true); + } + + if (frame->hw_frames_ctx) { + ret = av_hwframe_map(d->m_pHWFrame, frame, AV_HWFRAME_MAP_READ); + if (ret != 0) { + qDebug("[AVQt::EncoderQSV] %i: Could not map frame to GPU memory, trying copy: %s", ret, + av_make_error_string(strBuf, strBufSize, ret)); + ret = av_hwframe_transfer_data(d->m_pHWFrame, frame, 0); + if (ret != 0) { + qFatal("[AVQt::EncoderQSV] %i: Could not copy frame to GPU memory: %s", ret, + av_make_error_string(strBuf, strBufSize, ret)); + } + } + } else { + d->m_pHWFrame = av_frame_clone(frame); + } + + d->m_pHWFrame->pts = av_rescale_q(frame->pts, av_make_q(1, 1000000), d->m_pCodecCtx->time_base); + + while (d->m_codecBufferFull) { + msleep(1); + } + + { + QMutexLocker lock1{&d->m_codecMutex}; + ret = avcodec_send_frame(d->m_pCodecCtx, d->m_pHWFrame); + } + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { + d->m_codecBufferFull.store(true); + } + av_frame_unref(d->m_pHWFrame); +// d->m_pHWFrame->hw_frames_ctx = av_buffer_ref(d->m_pFramesCtx); + av_hwframe_get_buffer(d->m_pFramesCtx, d->m_pHWFrame, 0); + +// while (d->m_inputQueue.size() > 3) { +// QThread::msleep(1); +// } +// { +// QMutexLocker lock{&d->m_inputQueueMutex}; +// d->m_inputQueue.enqueue(queueFrame); +//// std::sort(d->m_inputQueue.begin(), d->m_inputQueue.end(), +//// [&](const QPair<AVFrame *, int64_t> &f1, const QPair<AVFrame *, int64_t> &f2) { +//// return f1.first->pts < f2.first->pts; +//// }); +// } + } + + void EncoderQSV::run() { + Q_D(AVQt::EncoderQSV); + + int ret; + constexpr auto strBufSize{64}; + char strBuf[strBufSize]; + + while (d->m_running.load()) { + if (!d->m_paused.load() && d->m_initialized.load()) { +// if (!d->m_inputQueue.isEmpty()) { +// QPair<AVFrame *, int64_t> frame; +// { +// QMutexLocker lock(&d->m_inputQueueMutex); +// frame = d->m_inputQueue.dequeue(); +// } +// +// ret = avcodec_send_frame(d->m_pCodecCtx, frame.first); +// qDebug("[AVQt::EncoderQSV] Sent frame with PTS %lld to encoder", static_cast<long long>(frame.first->pts)); +// /*if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { +// break; +// } else */if (ret != 0) { +// qFatal("%i: Could not send frame to QSV encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); +// } +// +//// avcodec_send_frame(d->m_pCodecCtx, nullptr); +//// +//// if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { +//// break; +//// } else if (ret != 0) { +//// qFatal("%i: Could not flush QSV encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); +//// } +// } + AVPacket *packet = av_packet_alloc(); + while (true) { + { + QMutexLocker lock{&d->m_codecMutex}; + ret = avcodec_receive_packet(d->m_pCodecCtx, packet); + } + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { + break; + } else if (ret != 0) { + qFatal("%i: Could not receive packet from encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); + } +// packet->pts = d->m_pHWFrame->pts; +// packet->pts = d->m_pCodecCtx->coded_frame->pts; +// packet->pts = packet->dts; + packet->pos = -1; + av_packet_rescale_ts(packet, d->m_pCodecCtx->time_base, av_make_q(1, 1000000)); + qDebug("[AVQt::EncoderQSV] Got packet from encoder with PTS: %lld, DTS: %lld, duration: %lld, timebase: %d/%d", + static_cast<long long>(packet->pts), + static_cast<long long>(packet->dts), + static_cast<long long>(packet->duration), + d->m_pCodecCtx->time_base.num, d->m_pCodecCtx->time_base.den); + if (packet->buf) { + QMutexLocker lock(&d->m_cbListMutex); + for (const auto &cb: d->m_cbList) { + AVPacket *cbPacket = av_packet_clone(packet); + cb->onPacket(this, cbPacket, IPacketSource::CB_VIDEO); + av_packet_free(&cbPacket); + } + } + av_packet_unref(packet); + } + d->m_codecBufferFull.store(false); + } else { + msleep(1); + } + } + } + + EncoderQSV &EncoderQSV::operator=(EncoderQSV &&other) noexcept { + delete d_ptr; + d_ptr = other.d_ptr; + other.d_ptr = nullptr; + d_ptr->q_ptr = this; + + return *this; + } +} \ No newline at end of file diff --git a/AVQt/filter/EncoderQSV.h b/AVQt/filter/EncoderQSV.h new file mode 100644 index 0000000000000000000000000000000000000000..5221b7e46661ece263b9c10be40e341a94d4c425 --- /dev/null +++ b/AVQt/filter/EncoderQSV.h @@ -0,0 +1,95 @@ +// +// Created by silas on 4/18/21. +// + +#include "IEncoder.h" + +#include <QThread> + +extern "C" { +#include <libavutil/avutil.h> +#include <libavutil/hwcontext.h> +#include <libavfilter/avfilter.h> +#include <libavformat/avformat.h> +#include <libavdevice/avdevice.h> +#include <libavcodec/avcodec.h> +#include <libswscale/swscale.h> +#include <libswresample/swresample.h> +#include <libavutil/imgutils.h> +} + +#ifndef LIBAVQT_ENCODERQSV_H +#define LIBAVQT_ENCODERQSV_H + +namespace AVQt { + class EncoderQSVPrivate; + + class EncoderQSV : public QThread, public IEncoder { + Q_OBJECT + + Q_DECLARE_PRIVATE(AVQt::EncoderQSV) + + Q_INTERFACES(AVQt::IEncoder) + + public: + explicit EncoderQSV(CODEC codec, int bitrate, QObject *parent = nullptr); + + EncoderQSV(EncoderQSV &&other) noexcept; + + explicit EncoderQSV(EncoderQSV &other) = delete; + + ~EncoderQSV() Q_DECL_OVERRIDE; + + bool isPaused() Q_DECL_OVERRIDE; + + qint64 registerCallback(IPacketSink *packetSink, int8_t type) Q_DECL_OVERRIDE; + + qint64 unregisterCallback(IPacketSink *packetSink) Q_DECL_OVERRIDE; + + void run() Q_DECL_OVERRIDE; + + EncoderQSV &operator=(const EncoderQSV &other) = delete; + + EncoderQSV &operator=(EncoderQSV &&other) noexcept; + + public slots: + + Q_INVOKABLE int init() Q_DECL_OVERRIDE; + + Q_INVOKABLE int deinit() Q_DECL_OVERRIDE; + + Q_INVOKABLE int start() Q_DECL_OVERRIDE; + + Q_INVOKABLE int stop() Q_DECL_OVERRIDE; + + Q_INVOKABLE void pause(bool pause) Q_DECL_OVERRIDE; + + Q_INVOKABLE int init(IFrameSource *source, AVRational framerate, int64_t duration) Q_DECL_OVERRIDE; + + Q_INVOKABLE int deinit(IFrameSource *source) Q_DECL_OVERRIDE; + + Q_INVOKABLE int start(IFrameSource *source) Q_DECL_OVERRIDE; + + Q_INVOKABLE int stop(IFrameSource *source) Q_DECL_OVERRIDE; + + Q_INVOKABLE void pause(IFrameSource *source, bool paused) Q_DECL_OVERRIDE; + + Q_INVOKABLE void onFrame(IFrameSource *source, AVFrame *frame, int64_t frameDuration, AVBufferRef *pDeviceCtx) Q_DECL_OVERRIDE; + + signals: + + void started() Q_DECL_OVERRIDE; + + void stopped() Q_DECL_OVERRIDE; + + void paused(bool pause) Q_DECL_OVERRIDE; + + protected: + [[maybe_unused]] explicit EncoderQSV(EncoderQSVPrivate &p); + + EncoderQSVPrivate *d_ptr; + + }; +} + +#endif //LIBAVQT_ENCODERQSV_H \ No newline at end of file diff --git a/AVQt/filter/private/EncoderQSV_p.h b/AVQt/filter/private/EncoderQSV_p.h new file mode 100644 index 0000000000000000000000000000000000000000..856b11e84720dd15691ac04d13152be2cf69846a --- /dev/null +++ b/AVQt/filter/private/EncoderQSV_p.h @@ -0,0 +1,49 @@ +// +// Created by silas on 4/18/21. +// + +#include "../EncoderQSV.h" + +#include <QtCore> + +#ifndef LIBAVQT_ENCODERQSV_P_H +#define LIBAVQT_ENCODERQSV_P_H + +namespace AVQt { + class EncoderQSVPrivate { + public: + EncoderQSVPrivate(const EncoderQSVPrivate &) = delete; + + void operator=(const EncoderQSVPrivate &) = delete; + + private: + explicit EncoderQSVPrivate(EncoderQSV *q) : q_ptr(q) {}; + + EncoderQSV *q_ptr; + + IEncoder::CODEC m_codec{IEncoder::CODEC::H264}; + int m_bitrate{5 * 1024 * 1024}; + + AVRational m_framerate{0, 1}; // TODO: Remove d->m_framerate + AVCodec *m_pCodec{nullptr}; + AVCodecContext *m_pCodecCtx{nullptr}; + AVBufferRef *m_pDeviceCtx{nullptr}, *m_pFramesCtx{nullptr}; + static constexpr auto HW_FRAME_POOL_SIZE = 4; + AVFrame *m_pHWFrame{nullptr}; + QMutex m_codecMutex{}; + + QMutex m_inputQueueMutex{}; + QQueue<QPair<AVFrame *, int64_t>> m_inputQueue{}; + + QMutex m_cbListMutex{}; + QList<IPacketSink *> m_cbList{}; + + QMutex m_onFrameMutex{}; + + std::atomic_bool m_running{false}, m_paused{false}, m_firstFrame{true}, m_initialized{false}, m_codecBufferFull{false}; + + friend class EncoderQSV; + }; +} + +#endif //LIBAVQT_ENCODERQSV_P_H \ No newline at end of file