diff --git a/.idea/.gitignore b/.idea/.gitignore index e94e0e3f5cd58cd2fa7ce47e1c1213048899ff9d..6700efe6022a04a6dc6c560a437730065837fd95 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -8,3 +8,4 @@ /dataSources.local.xml # Editor-based HTTP Client requests /httpRequests/ +/discord.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000000000000000000000000000000000..ba0f7e5ea16a2606db664148aefdeaa4437feca7 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ +<component name="InspectionProjectProfileManager"> + <profile version="1.0"> + <option name="myName" value="Project Default" /> + <inspection_tool class="Clazy" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="clazyChecks" value="level0,no-qt-macros" /> + </inspection_tool> + </profile> +</component> \ No newline at end of file diff --git a/AVQt/CMakeLists.txt b/AVQt/CMakeLists.txt index 700bb739d2fbebf4f791fb1c913d9a9fd1626a4d..436bf3967c1ccb18c9ba68c3293332a5cb754bd2 100644 --- a/AVQt/CMakeLists.txt +++ b/AVQt/CMakeLists.txt @@ -12,10 +12,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR on) #find_package(Qt6 COMPONENTS Core Gui Concurrent Widgets OpenGL OpenGLWidgets) find_package(Qt5 COMPONENTS Core Gui Concurrent Widgets OpenGL) -if (WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS on) -endif () - #if (${CMAKE_BUILD_TYPE} EQUAL "Release") # -O3) #endif () @@ -84,95 +80,105 @@ add_library(AVQt SHARED ${SOURCES}) add_library(AVQtStatic STATIC ${SOURCES}) set_target_properties(AVQtStatic PROPERTIES OUTPUT_NAME AVQt_static) -target_compile_options(AVQt PRIVATE - -std=c++20 - -Werror=all - -Werror=extra - -Werror=pedantic - -Wno-float-equal - -ansi - -Werror=init-self - -Werror=old-style-cast - -Werror=overloaded-virtual - -Werror=uninitialized - -Werror=missing-declarations - -Werror=init-self - # -Wall -Wextra - -Wno-error=non-virtual-dtor - # -Wno-unused-result - #-Wpedantic - -Wshadow - -Wfatal-errors - -Wundef - -Wuninitialized - -Winit-self - -Wconversion - -Wfloat-equal - -Wstrict-aliasing - -Wtrigraphs - -Wodr # one definition rule - -Wzero-as-null-pointer-constant - -Wsizeof-array-argument - -Winline - -Wcast-qual - -Weffc++ - - - # -fno-common # each global variable is only declared once. - # -fmerge-all-constants - # -ffunction-sections - # -fdata-sections - # -fno-use-cxa-atexit - # -fverbose-asm - # -fstrict-enums - -Wstack-protector - -Wdouble-promotion - ) -target_compile_options(AVQtStatic PRIVATE - -std=c++20 - -Werror=all - -Werror=extra - -Werror=pedantic - -ansi - -Werror=init-self - -Werror=old-style-cast - -Werror=overloaded-virtual - -Werror=uninitialized - -Werror=missing-declarations - -Werror=init-self - # -Wall -Wextra - -Wno-error=non-virtual-dtor - # -Wno-unused-result - #-Wpedantic - -Wshadow - -Wfatal-errors - -Wundef - -Wuninitialized - -Winit-self - -Wconversion - -Wfloat-equal - -Wstrict-aliasing - -Wtrigraphs - -Wodr # one definition rule - -Wzero-as-null-pointer-constant - -Wsizeof-array-argument - -Winline - -Wcast-qual - -Weffc++ - - - # -fno-common # each global variable is only declared once. - # -fmerge-all-constants - # -ffunction-sections - # -fdata-sections - # -fno-use-cxa-atexit - # -fverbose-asm - # -fstrict-enums - -Wstack-protector - -Wdouble-promotion - ) +if (!WIN32) + target_compile_options(AVQt PRIVATE + -std=c++20 + -Werror=all + -Werror=extra + -Werror=pedantic + -Wno-float-equal + -ansi + -Werror=init-self + -Werror=old-style-cast + -Werror=overloaded-virtual + -Werror=uninitialized + -Werror=missing-declarations + -Werror=init-self + # -Wall -Wextra + -Wno-error=non-virtual-dtor + # -Wno-unused-result + #-Wpedantic + -Wshadow + -Wfatal-errors + -Wundef + -Wuninitialized + -Winit-self + -Wconversion + -Wfloat-equal + -Wstrict-aliasing + -Wtrigraphs + -Wodr # one definition rule + -Wzero-as-null-pointer-constant + -Wsizeof-array-argument + -Winline + -Wcast-qual + -Weffc++ + + + # -fno-common # each global variable is only declared once. + # -fmerge-all-constants + # -ffunction-sections + # -fdata-sections + # -fno-use-cxa-atexit + # -fverbose-asm + # -fstrict-enums + -Wstack-protector + -Wdouble-promotion + ) + target_compile_options(AVQtStatic PRIVATE + -std=c++20 + -Werror=all + -Werror=extra + -Werror=pedantic + -ansi + -Werror=init-self + -Werror=old-style-cast + -Werror=overloaded-virtual + -Werror=uninitialized + -Werror=missing-declarations + -Werror=init-self + # -Wall -Wextra + -Wno-error=non-virtual-dtor + # -Wno-unused-result + #-Wpedantic + -Wshadow + -Wfatal-errors + -Wundef + -Wuninitialized + -Winit-self + -Wconversion + -Wfloat-equal + -Wstrict-aliasing + -Wtrigraphs + -Wodr # one definition rule + -Wzero-as-null-pointer-constant + -Wsizeof-array-argument + -Winline + -Wcast-qual + -Weffc++ + + + # -fno-common # each global variable is only declared once. + # -fmerge-all-constants + # -ffunction-sections + # -fdata-sections + # -fno-use-cxa-atexit + # -fverbose-asm + # -fstrict-enums + -Wstack-protector + -Wdouble-promotion + ) +endif () #target_link_libraries(AVQt Qt6::Core Qt6::Gui Qt6::Concurrent Qt6::Widgets Qt6::OpenGL Qt6::OpenGLWidgets avformat avfilter avutil avcodec avdevice swscale swresample GL openal) #target_link_libraries(AVQtStatic Qt6::Core Qt6::Gui Qt6::Concurrent Qt6::Widgets Qt6::OpenGL Qt6::OpenGLWidgets avformat avfilter avutil avcodec avdevice swscale swresample GL openal) -target_link_libraries(AVQt Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Widgets Qt5::OpenGL avformat avfilter avutil avcodec avdevice swscale swresample GL openal) -target_link_libraries(AVQtStatic Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Widgets Qt5::OpenGL avformat avfilter avutil avcodec avdevice swscale swresample GL openal) \ No newline at end of file +target_link_libraries(AVQt Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Widgets Qt5::OpenGL avformat avfilter avutil avcodec avdevice swscale swresample) +target_link_libraries(AVQtStatic Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Widgets Qt5::OpenGL avformat avfilter avutil avcodec avdevice swscale swresample) + +if (WIN32) + target_link_libraries(AVQt opengl32 OpenAL32) + target_link_libraries(AVQtStatic opengl32 OpenAL32) +else () + target_link_libraries(AVQt GL openal) + target_link_libraries(AVQtStatic GL openal) +endif () \ No newline at end of file diff --git a/AVQt/filter/DecoderVAAPI.cpp b/AVQt/filter/DecoderVAAPI.cpp index a68f3edf229d6faf9bf99d846ac56483e12743b3..d61dcecebc86002e501cdd314dbc8c3e104ba7d5 100644 --- a/AVQt/filter/DecoderVAAPI.cpp +++ b/AVQt/filter/DecoderVAAPI.cpp @@ -72,6 +72,13 @@ namespace AVQt { stop(); + { + QMutexLocker lock(&d->m_cbListMutex); + for (const auto &cb: d->m_cbList) { + cb->deinit(this); + } + } + if (d->m_pCodecParams) { avcodec_parameters_free(&d->m_pCodecParams); d->m_pCodecParams = nullptr; @@ -228,6 +235,7 @@ namespace AVQt { avcodec_parameters_to_context(d->m_pCodecCtx, d->m_pCodecParams); d->m_pCodecCtx->hw_device_ctx = av_buffer_ref(d->m_pDeviceCtx); + d->m_pCodecCtx->time_base = d->m_timebase; ret = avcodec_open2(d->m_pCodecCtx, d->m_pCodec, nullptr); if (ret != 0) { qFatal("%d: Could not open VAAPI decoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); diff --git a/AVQt/filter/EncoderVAAPI.cpp b/AVQt/filter/EncoderVAAPI.cpp index 05f1271c21db92be295fceb1a77aaf6549254dc5..e94235e68346c30b72239c85aa070701531039a6 100644 --- a/AVQt/filter/EncoderVAAPI.cpp +++ b/AVQt/filter/EncoderVAAPI.cpp @@ -45,6 +45,8 @@ namespace AVQt { int EncoderVAAPI::deinit() { Q_D(AVQt::EncoderVAAPI); + stop(); + { QMutexLocker lock(&d->m_cbListMutex); for (const auto &cb: d->m_cbList) { @@ -216,6 +218,10 @@ namespace AVQt { { 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; +// }); } } @@ -227,8 +233,9 @@ namespace AVQt { char strBuf[strBufSize]; while (d->m_running.load()) { - if (!d->m_paused.load() && !d->m_inputQueue.isEmpty()) { + if (!d->m_paused.load() && d->m_inputQueue.size() > 5) { bool shouldBe = true; + // Encoder init is only possible with frame parameters if (d->m_firstFrame.compare_exchange_strong(shouldBe, false)) { auto frame = d->m_inputQueue.first().first; @@ -238,27 +245,6 @@ namespace AVQt { } d->m_pCodecCtx->width = frame->width; d->m_pCodecCtx->height = frame->height; -// if (frame->hw_frames_ctx) { -// qDebug("Creating derived HW context..."); -// d->m_pDeviceCtx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI); -// ret = av_hwdevice_ctx_create(&d->m_pDeviceCtx, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD128", nullptr, 0); -// if (ret < 0) { -// qFatal("%i: Unable to create derived AVHWDeviceContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); -// } else if (!d->m_pDeviceCtx) { -// qFatal("Unable to create derived AVHWDeviceContext"); -// } -// -// d->m_pCodecCtx->sw_pix_fmt = reinterpret_cast<AVHWFramesContext*>(frame->hw_frames_ctx->data)->sw_format; -// ret = av_hwframe_ctx_create_derived(&d->m_pFramesCtx, d->m_pCodecCtx->sw_pix_fmt, d->m_pDeviceCtx, frame->hw_frames_ctx, 0); -// if (ret < 0) { -// qFatal("%i: Unable to create derived AVHWFramesContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); -// } else if (!d->m_pDeviceCtx) { -// qFatal("Unable to create derived AVHWFramesContext"); -// } -// d->m_pCodecCtx->pix_fmt = reinterpret_cast<AVHWFramesContext*>(d->m_pFramesCtx->data)->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); -// } else { qDebug("Creating new HW context..."); ret = av_hwdevice_ctx_create(&d->m_pDeviceCtx, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD128", nullptr, 0); if (ret < 0) { @@ -290,15 +276,25 @@ namespace AVQt { 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->pix_fmt = AV_PIX_FMT_VAAPI; +// d->m_pCodecCtx->bit_rate = 5000000; +// d->m_pCodecCtx->rc_min_rate = 4500000; +// d->m_pCodecCtx->rc_max_rate = 6000000; +// d->m_pCodecCtx->rc_buffer_size = 10000000; + 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; + + // 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 VAAPI encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); } qDebug("[AVQt::EncoderVAAPI] 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); if (ret != 0) { @@ -325,24 +321,44 @@ namespace AVQt { } else { av_hwframe_transfer_data(d->m_pHWFrame, frame.first, 0); } - d->m_pHWFrame->pts = av_rescale_q(frame.first->pts, av_make_q(1, 1000000), d->m_pCodecCtx->time_base); + d->m_pHWFrame->pts = av_rescale_q(frame.first->pts, av_make_q(1, 1000000), + d->m_pCodecCtx->time_base); // Incoming timestamps are always microseconds +// d->m_pHWFrame->pts = frame.first->pts; av_frame_free(&frame.first); ret = avcodec_send_frame(d->m_pCodecCtx, d->m_pHWFrame); - if (ret != 0) { + qDebug("[AVQt::EncoderVAAPI] Sent frame with PTS %lld to encoder", static_cast<long long>(d->m_pHWFrame->pts)); + /*if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else */if (ret != 0) { qFatal("%i: Could not send frame to VAAPI 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 VAAPI encoder: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); +// } } AVPacket *packet = av_packet_alloc(); while (true) { ret = avcodec_receive_packet(d->m_pCodecCtx, packet); - qDebug("[AVQt::EncoderVAAPI] Got packet from encoder with PTS: %lld, timebase: %d/%d", - static_cast<long long>(packet->pts), - d->m_pCodecCtx->time_base.num, d->m_pCodecCtx->time_base.den); 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::EncoderVAAPI] 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) { diff --git a/AVQt/input/Demuxer.cpp b/AVQt/input/Demuxer.cpp index d99e134901427e16e18385b13a8bdb749967497e..fe24e6fe396b7a8038b3aa6b01b432fc4bacb8dc 100644 --- a/AVQt/input/Demuxer.cpp +++ b/AVQt/input/Demuxer.cpp @@ -102,7 +102,7 @@ namespace AVQt { bool initialized = true; d->m_initialized.compare_exchange_strong(initialized, true); if (!initialized) { - d->m_pBuffer = new uint8_t[DemuxerPrivate::BUFFER_SIZE]; + d->m_pBuffer = static_cast<uint8_t *>(av_malloc(DemuxerPrivate::BUFFER_SIZE)); d->m_pIOCtx = avio_alloc_context(d->m_pBuffer, DemuxerPrivate::BUFFER_SIZE, 0, d->m_inputDevice, &DemuxerPrivate::readFromIO, nullptr, &DemuxerPrivate::seekIO); d->m_pFormatCtx = avformat_alloc_context(); diff --git a/AVQt/input/private/Demuxer_p.h b/AVQt/input/private/Demuxer_p.h index aa6cc518b355539e34baecb91b801520db1bc449..1ba35b36645ceaa09a54a9085624ff7a58bb161b 100644 --- a/AVQt/input/private/Demuxer_p.h +++ b/AVQt/input/private/Demuxer_p.h @@ -37,7 +37,7 @@ namespace AVQt { QList<int64_t> m_videoStreams{}, m_audioStreams{}, m_subtitleStreams{}; int64_t m_videoStream{-1}, m_audioStream{-1}, m_subtitleStream{-1}; - static constexpr size_t BUFFER_SIZE{1024}; + static constexpr size_t BUFFER_SIZE{32 * 1024}; uint8_t *m_pBuffer{nullptr}; AVFormatContext *m_pFormatCtx{nullptr}; AVIOContext *m_pIOCtx{nullptr}; diff --git a/AVQt/output/Muxer.cpp b/AVQt/output/Muxer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6f6788730b8b9ed4a43c5dff6f7a5ce48ec9fa53 --- /dev/null +++ b/AVQt/output/Muxer.cpp @@ -0,0 +1,280 @@ +// +// Created by silas on 5/24/21. +// +#include <input/IPacketSource.h> +#include "private/Muxer_p.h" +#include "Muxer.h" + +namespace AVQt { + Muxer::Muxer(QIODevice *outputDevice, QObject *parent) : QThread(parent), d_ptr(new MuxerPrivate(this)) { + Q_D(AVQt::Muxer); + d->m_outputDevice = outputDevice; + } + + Muxer::Muxer(AVQt::MuxerPrivate &p) : d_ptr(&p) { + + } + + AVQt::Muxer::Muxer(Muxer &&other) noexcept: d_ptr(other.d_ptr) { + other.d_ptr = nullptr; + } + + bool Muxer::isPaused() { + Q_D(AVQt::Muxer); + return d->m_paused.load(); + } + + void Muxer::init(IPacketSource *source, AVRational framerate, AVRational timebase, int64_t duration, AVCodecParameters *vParams, + AVCodecParameters *aParams, AVCodecParameters *sParams) { + Q_UNUSED(framerate) + Q_UNUSED(duration) + Q_D(AVQt::Muxer); + + if ((vParams && aParams) || (vParams && sParams) || (aParams && sParams)) { + qWarning("[AVQt::Muxer] init() called for multiple stream types at once. Ignoring call"); + return; + } + + if (d->m_sourceStreamMap.contains(source)) { + bool alreadyCalled = false; + for (const auto &stream: d->m_sourceStreamMap[source].keys()) { + if ((vParams && stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) || + (aParams && stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || + (sParams && stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)) { + alreadyCalled = true; + break; + } + } + if (alreadyCalled) { + qWarning("[AVQt::Muxer] init() called multiple times for the same stream type by the same source. Ignoring call"); + return; + } + } + QMutexLocker lock(&d->m_initMutex); + if (!d->m_pFormatContext) { + if (!d->m_outputDevice->isOpen()) { + if (!d->m_outputDevice->open((d->m_outputDevice->isSequential() ? QIODevice::WriteOnly : QIODevice::ReadWrite))) { + qFatal("[AVQt::Muxer] Could not open output device"); + } + } else if (!d->m_outputDevice->isWritable()) { + qFatal("[AVQt::Muxer] Output device is not writable"); + } + d->m_pFormatContext = avformat_alloc_context(); + d->m_pFormatContext->oformat = av_guess_format("mp4", "", nullptr); + d->m_pIOBuffer = static_cast<uint8_t *>(av_malloc(MuxerPrivate::IOBUF_SIZE)); + d->m_pIOContext = avio_alloc_context(d->m_pIOBuffer, MuxerPrivate::IOBUF_SIZE, 1, d->m_outputDevice, nullptr, + &MuxerPrivate::writeToIO, &MuxerPrivate::seekIO); + d->m_pIOContext->seekable = !d->m_outputDevice->isSequential(); + d->m_pFormatContext->pb = d->m_pIOContext; + d->m_pFormatContext->flags |= AVFMT_FLAG_CUSTOM_IO; + } + + if (!d->m_sourceStreamMap.contains(source)) { + d->m_sourceStreamMap[source] = QMap<AVStream *, AVRational>(); + } + if (vParams) { + AVStream *videoStream = avformat_new_stream(d->m_pFormatContext, avcodec_find_encoder(vParams->codec_id)); + videoStream->codecpar = avcodec_parameters_alloc(); + avcodec_parameters_copy(videoStream->codecpar, vParams); + videoStream->time_base = timebase; + d->m_sourceStreamMap[source].insert(videoStream, timebase); + } else if (aParams) { + AVStream *audioStream = avformat_new_stream(d->m_pFormatContext, avcodec_find_encoder(aParams->codec_id)); + audioStream->codecpar = avcodec_parameters_alloc(); + avcodec_parameters_copy(audioStream->codecpar, aParams); + audioStream->time_base = timebase; + d->m_sourceStreamMap[source].insert(audioStream, timebase); + } else if (sParams) { + AVStream *subtitleStream = avformat_new_stream(d->m_pFormatContext, avcodec_find_encoder(sParams->codec_id)); + subtitleStream->codecpar = avcodec_parameters_alloc(); + avcodec_parameters_copy(subtitleStream->codecpar, sParams); + subtitleStream->time_base = timebase; + d->m_sourceStreamMap[source].insert(subtitleStream, timebase); + } + int ret = avformat_write_header(d->m_pFormatContext, nullptr); + if (ret != 0) { + constexpr auto strBufSize = 32; + char strBuf[strBufSize]; + qWarning("[AVQt::Muxer] %d: Couldn't init AVFormatContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); + } + qDebug("[AVQt::Muxer] Initialized"); + } + + void Muxer::deinit(IPacketSource *source) { + Q_D(AVQt::Muxer); + + stop(source); + qDebug("[AVQt::Muxer] deinit() called"); + + if (d->m_sourceStreamMap.contains(source)) { + d->m_sourceStreamMap.remove(source); + } else { + qWarning("[AVQt::Muxer] deinit() called without preceding init() from source. Ignoring call"); + return; + } + + if (d->m_sourceStreamMap.isEmpty()) { + QMutexLocker lock(&d->m_initMutex); + if (d->m_pFormatContext) { + int ret = av_write_frame(d->m_pFormatContext, nullptr); + if (ret != 0) { + constexpr auto strBufSize = 32; + char strBuf[strBufSize]; + qWarning("%d: Couldn't flush AVFormatContext packet queue: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); + } +// if (d->m_headerWritten.load()) { + av_write_trailer(d->m_pFormatContext); + qDebug("[AVQt::Muxer] Wrote trailer"); +// } + avio_flush(d->m_pFormatContext->pb); + avformat_free_context(d->m_pFormatContext); + d->m_outputDevice->close(); + } + } + + } + + void Muxer::start(IPacketSource *source) { + Q_UNUSED(source) + Q_D(AVQt::Muxer); + + bool shouldBe = false; + if (d->m_running.compare_exchange_strong(shouldBe, true)) { + shouldBe = false; +// if (d->m_headerWritten.compare_exchange_strong(shouldBe, true)) { +// +// } + d->m_paused.store(false); + QThread::start(); + started(); + } + } + + void Muxer::stop(IPacketSource *source) { + Q_UNUSED(source) + Q_D(AVQt::Muxer); + + bool shouldBe = true; + if (d->m_running.compare_exchange_strong(shouldBe, false)) { + d->m_paused.store(false); + wait(); + { + QMutexLocker lock{&d->m_inputQueueMutex}; + while (!d->m_inputQueue.isEmpty()) { + auto packet = d->m_inputQueue.dequeue(); + av_packet_free(&packet.first); + } + } + stopped(); + } + } + + void Muxer::pause(bool p) { + Q_D(AVQt::Muxer); + + bool shouldBe = !p; + if (d->m_paused.compare_exchange_strong(shouldBe, p)) { + paused(p); + } + } + + void Muxer::onPacket(IPacketSource *source, AVPacket *packet, int8_t packetType) { + Q_D(AVQt::Muxer); + + if (packet->pts != AV_NOPTS_VALUE) { + + bool unknownSource = !d->m_sourceStreamMap.contains(source); + bool initStream = false; + AVStream *addStream; + if (!unknownSource) { + for (const auto &stream : d->m_sourceStreamMap[source].keys()) { + if ((packetType == IPacketSource::CB_VIDEO && stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) || + (packetType == IPacketSource::CB_AUDIO && stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || + (packetType == IPacketSource::CB_SUBTITLE && stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)) { + addStream = stream; + initStream = true; + break; + } + } + } + if (unknownSource || !initStream) { + qWarning("[AVQt::Muxer] onPacket() called without preceding call to init() for stream type. Ignoring packet"); + return; + } + + QPair<AVPacket *, AVStream *> queuePacket{av_packet_clone(packet), addStream}; + queuePacket.first->stream_index = addStream->index; + qDebug("[AVQt::Muxer] Getting packet with PTS: %lld", static_cast<long long>(packet->pts)); + av_packet_rescale_ts(queuePacket.first, av_make_q(1, 1000000), addStream->time_base); + + while (d->m_inputQueue.size() >= 100) { + QThread::msleep(2); + } + + QMutexLocker lock(&d->m_inputQueueMutex); + d->m_inputQueue.enqueue(queuePacket); +// std::sort(d->m_inputQueue.begin(), d->m_inputQueue.end(), &MuxerPrivate::packetQueueCompare); + } + } + + void Muxer::run() { + Q_D(AVQt::Muxer); + + while (d->m_running.load()) { + if (!d->m_paused.load() && d->m_inputQueue.size() > 5) { + QPair<AVPacket *, AVStream *> packet; + { + QMutexLocker lock(&d->m_inputQueueMutex); + packet = d->m_inputQueue.dequeue(); + } + int ret = av_interleaved_write_frame(d->m_pFormatContext, packet.first); + qDebug("Written packet"); + if (ret != 0) { + constexpr auto strBufSize = 32; + char strBuf[strBufSize]; + qWarning("%d: Couldn't write packet to AVFormatContext: %s", ret, av_make_error_string(strBuf, strBufSize, ret)); + } + } else { + msleep(4); + } + } + } + + int MuxerPrivate::writeToIO(void *opaque, uint8_t *buf, int buf_size) { + auto *outputDevice = reinterpret_cast<QIODevice *>(opaque); + + auto bytesWritten = outputDevice->write(reinterpret_cast<const char *>(buf), buf_size); + return bytesWritten == 0 ? AVERROR_UNKNOWN : static_cast<int>(bytesWritten); + } + + int64_t MuxerPrivate::seekIO(void *opaque, int64_t offset, int whence) { + auto *outputDevice = reinterpret_cast<QIODevice *>(opaque); + + if (outputDevice->isSequential()) { + return AVERROR_UNKNOWN; + } + + bool result; + switch (whence) { + case SEEK_SET: + result = outputDevice->seek(offset); + break; + case SEEK_CUR: + result = outputDevice->seek(outputDevice->pos() + offset); + break; + case SEEK_END: + result = outputDevice->seek(outputDevice->size() - offset); + break; + case AVSEEK_SIZE: + return outputDevice->size(); + default: + return AVERROR_UNKNOWN; + } + + return result ? outputDevice->pos() : AVERROR_UNKNOWN; + } + + bool MuxerPrivate::packetQueueCompare(const QPair<AVPacket *, AVStream *> &packet1, const QPair<AVPacket *, AVStream *> &packet2) { + return packet1.first->dts < packet2.first->dts; + } +} \ No newline at end of file diff --git a/AVQt/output/Muxer.h b/AVQt/output/Muxer.h new file mode 100644 index 0000000000000000000000000000000000000000..09d76da55fde2dd1b5408aeea2b2dc15d3349f67 --- /dev/null +++ b/AVQt/output/Muxer.h @@ -0,0 +1,64 @@ +// +// Created by silas on 5/24/21. +// + +#include "IPacketSink.h" + +#include <QThread> +#include <QIODevice> + +#ifndef LIBAVQT_MUXER_H +#define LIBAVQT_MUXER_H + +namespace AVQt { + class MuxerPrivate; + + class Muxer : public QThread, public IPacketSink { + Q_OBJECT + Q_INTERFACES(AVQt::IPacketSink) + + Q_DECLARE_PRIVATE(AVQt::Muxer) + + protected: + void run() Q_DECL_OVERRIDE; + + public: + explicit Muxer(QIODevice *outputDevice, QObject *parent = nullptr); + + Muxer(Muxer &) = delete; + + Muxer(Muxer &&other) noexcept; + + bool isPaused() Q_DECL_OVERRIDE; + + void init(IPacketSource *source, AVRational framerate, AVRational timebase, int64_t duration, AVCodecParameters *vParams, + AVCodecParameters *aParams, AVCodecParameters *sParams) Q_DECL_OVERRIDE; + + void deinit(IPacketSource *source) Q_DECL_OVERRIDE; + + void start(IPacketSource *source) Q_DECL_OVERRIDE; + + void stop(IPacketSource *source) Q_DECL_OVERRIDE; + + void pause(bool p) Q_DECL_OVERRIDE; + + void onPacket(IPacketSource *source, AVPacket *packet, int8_t packetType) Q_DECL_OVERRIDE; + + void operator=(const Muxer &) = delete; + + signals: + + void started() Q_DECL_OVERRIDE; + + void stopped() Q_DECL_OVERRIDE; + + void paused(bool pause) Q_DECL_OVERRIDE; + + protected: + [[maybe_unused]] explicit Muxer(MuxerPrivate &p); + + MuxerPrivate *d_ptr; + }; +} + +#endif //LIBAVQT_MUXER_H \ No newline at end of file diff --git a/AVQt/output/OpenGLRenderer.cpp b/AVQt/output/OpenGLRenderer.cpp index 8ce839be0e36325e412df022e94d4ed8f2a58982..d78699bc6edecea2909780f3839ffd8c735ef741 100644 --- a/AVQt/output/OpenGLRenderer.cpp +++ b/AVQt/output/OpenGLRenderer.cpp @@ -64,10 +64,10 @@ namespace AVQt { d->m_paused.store(false); qDebug("Started renderer"); - showNormal(); - requestActivate(); + QMetaObject::invokeMethod(this, "showNormal", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "requestActivate", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); - update(); started(); } return 0; diff --git a/AVQt/output/private/Muxer_p.h b/AVQt/output/private/Muxer_p.h new file mode 100644 index 0000000000000000000000000000000000000000..ca43c3509aa082d74f34d217019e48d850edd489 --- /dev/null +++ b/AVQt/output/private/Muxer_p.h @@ -0,0 +1,55 @@ +// +// Created by silas on 5/24/21. +// + +#include "../Muxer.h" + +extern "C" { +#include <libavformat/avformat.h> +#include <libavcodec/avcodec.h> +} + +#include <QtCore> + +#ifndef LIBAVQT_MUXER_P_H +#define LIBAVQT_MUXER_P_H + +namespace AVQt { + class MuxerPrivate { + public: + MuxerPrivate(const MuxerPrivate &) = delete; + + void operator=(const MuxerPrivate &) = delete; + + private: + explicit MuxerPrivate(Muxer *q) : q_ptr(q) {}; + + Muxer *q_ptr; + + QIODevice *m_outputDevice{nullptr}; + + QMutex m_initMutex{}; + static constexpr size_t IOBUF_SIZE{4 * 1024}; // 4 KB + uint8_t *m_pIOBuffer{nullptr}; + AVIOContext *m_pIOContext{nullptr}; + AVFormatContext *m_pFormatContext{nullptr}; + QMap<IPacketSource *, QMap<AVStream *, AVRational>> m_sourceStreamMap{}; + + QMutex m_inputQueueMutex{}; + QQueue<QPair<AVPacket *, AVStream *>> m_inputQueue{}; + + std::atomic_bool m_running{false}; + std::atomic_bool m_paused{false}; + std::atomic_bool m_headerWritten{false}; + + friend class Muxer; + + static int64_t seekIO(void *opaque, int64_t offset, int whence); + + static int writeToIO(void *opaque, uint8_t *buf, int buf_size); + + static bool packetQueueCompare(const QPair<AVPacket *, AVStream *> &packet1, const QPair<AVPacket *, AVStream *> &packet2); + }; +} + +#endif //LIBAVQT_MUXER_P_H \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a4414e8199c6c64be30ee0a20db1450da0d4bd2..ad04765f27265ee05af97c7128d92a21e2d5ead6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,10 +3,21 @@ project(LibAVQt) set(CMAKE_CXX_STANDARD 20) +if (WIN32) + include_directories(${OPENAL_INCLUDE_DIR} ${FFMPEG_INCLUDE_DIR}) + link_directories(${OPENAL_LIBRARY} ${FFMPEG_LIBRARY}) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS on) +endif () + +if (WIN32) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +endif () + set(CMAKE_AUTOMOC on) set(CMAKE_AUTORCC on) set(CMAKE_AUTOUIC on) add_subdirectory(AVQt) include_directories(AVQt) -add_subdirectory(Player) +add_subdirectory(Player) \ No newline at end of file diff --git a/Player/main.cpp b/Player/main.cpp index 7a4cef9d521e602b0ce52c88599296c90e85133a..83656d8347d3ce3f95d26da4e4d02202162c9c3c 100644 --- a/Player/main.cpp +++ b/Player/main.cpp @@ -43,6 +43,9 @@ int main(int argc, char *argv[]) { app = new QApplication(argc, argv); signal(SIGINT, &signalHandler); signal(SIGTERM, &signalHandler); + + av_log_set_level(AV_LOG_DEBUG); + av_log_set_flags(AV_LOG_SKIP_REPEATED); // signal(SIGQUIT, &signalHandler); start = std::chrono::system_clock::now(); @@ -57,7 +60,7 @@ int main(int argc, char *argv[]) { return 0; } - inputFile->open(QIODevice::ReadOnly); + inputFile->open(QIODevice::ReadWrite); AVQt::Demuxer demuxer(inputFile); // AVQt::AudioDecoder decoder; @@ -67,8 +70,10 @@ int main(int argc, char *argv[]) { // decoder.registerCallback(&output); AVQt::IDecoder *videoDecoder; + AVQt::IEncoder *videoEncoder; #ifdef Q_OS_LINUX videoDecoder = new AVQt::DecoderVAAPI; + videoEncoder = new AVQt::EncoderVAAPI("hevc_vaapi"); #elif defined(Q_OS_WINDOWS) videoDecoder = new AVQt::DecoderDXVA2(); #else @@ -76,16 +81,15 @@ int main(int argc, char *argv[]) { #endif AVQt::OpenGLRenderer renderer; -// AVQt::IEncoder *encoder = new AVQt::EncoderVAAPI("hevc_vaapi"); - demuxer.registerCallback(videoDecoder, AVQt::IPacketSource::CB_VIDEO); -// videoDecoder->registerCallback(encoder); +// videoDecoder->registerCallback(videoEncoder); -// QFile outputFile("output.ts"); +// QFile outputFile("output.mp4"); // outputFile.open(QIODevice::ReadWrite | QIODevice::Truncate); +// outputFile.seek(0); // AVQt::Muxer muxer(&outputFile); -// encoder->registerCallback(&muxer, AVQt::IPacketSource::CB_VIDEO); +// videoEncoder->registerCallback(&muxer, AVQt::IPacketSource::CB_VIDEO); videoDecoder->registerCallback(&renderer); renderer.setMinimumSize(QSize(360, 240)); @@ -105,7 +109,7 @@ int main(int argc, char *argv[]) { QObject::connect(app, &QApplication::aboutToQuit, [&] { demuxer.deinit(); -// delete encoder; +// delete videoEncoder; delete videoDecoder; });