From cdd0e195285098ba00200ab60f2bb1d77328a37b Mon Sep 17 00:00:00 2001 From: Ramen2X <rmn@legoisland.org> Date: Mon, 9 Dec 2024 14:39:10 -0500 Subject: [PATCH 1/4] lib: fix 64-bit integer typedefs for old msvc --- lib/types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/types.h b/lib/types.h index f09c15d..4bd6f5f 100644 --- a/lib/types.h +++ b/lib/types.h @@ -38,8 +38,8 @@ typedef unsigned short uint16_t; typedef short int16_t; typedef unsigned int uint32_t; typedef int int32_t; -typedef unsigned long long uint64_t; -typedef long long int64_t; +typedef unsigned __int64 uint64_t; +typedef __int64 int64_t; #else #include <stdint.h> #endif From 4d143a5f57c9e31e7ff27dee3f0305ce846ca29f Mon Sep 17 00:00:00 2001 From: Ramen2X <rmn@legoisland.org> Date: Wed, 11 Dec 2024 18:21:07 -0500 Subject: [PATCH 2/4] ci: try to fix ci (#23) * ci: try to fix deprecated workflow * ci: update ffmpeg version surely this won't break anything Clueless * ci: let's try this * ci: try this * ci: try different ffmpeg cmake arg * ci: temporarily disable releases upload qt 6 version is a bit unstable right now so we wanna keep the qt 5 version available --- .github/workflows/ci.yml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03debce..e50cf35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: runs-on: windows-latest env: - FFMPEG_DIR: ffmpeg-n4.4-latest-win64-gpl-shared-4.4 + FFMPEG_DIR: ffmpeg-n5.1-latest-win64-gpl-shared-5.1 steps: - name: Checkout @@ -39,12 +39,11 @@ jobs: run: | curl -fLOSs https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/$FFMPEG_DIR.zip 7z x $FFMPEG_DIR.zip - echo "$FFMPEG_DIR" >> $GITHUB_PATH - name: Build shell: bash run: | - cmake . -G Ninja -DCMAKE_BUILD_TYPE=Release + cmake . -G Ninja -DCMAKE_BUILD_TYPE=Release -DFFMPEG_ROOT=$FFMPEG_DIR ninja - name: Deploy @@ -58,20 +57,20 @@ jobs: windeployqt si-edit.exe libweaver.dll - name: Upload Build Artifact - uses: actions/upload-artifact@v2.2.1 + uses: actions/upload-artifact@v4 with: path: deploy - - name: Upload to Releases - shell: bash - if: github.event_name == 'push' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TRAVIS_REPO_SLUG: itsmattkc/siedit - TRAVIS_COMMIT: ${{ github.sha }} - run: | - cd deploy - 7z a libweaver.zip * - curl -fLOSs --retry 2 --retry-delay 60 https://github.com/probonopd/uploadtool/raw/master/upload.sh - ./upload.sh libweaver.zip +# - name: Upload to Releases +# shell: bash +# if: github.event_name == 'push' +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# TRAVIS_REPO_SLUG: isledecomp/siedit +# TRAVIS_COMMIT: ${{ github.sha }} +# run: | +# cd deploy +# 7z a si-edit.zip * +# curl -fLOSs --retry 2 --retry-delay 60 https://github.com/probonopd/uploadtool/raw/master/upload.sh +# ./upload.sh si-edit.zip From 04dc68a7e9df0a45180c37b33da388d27da54704 Mon Sep 17 00:00:00 2001 From: MattKC <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:42:15 -0800 Subject: [PATCH 3/4] Fixes for Qt 6 on non-Linux platforms (#24) * implement QIODevice::size() and call QIODevice::seek() * implement mixer * ensure MediaInstance is audio before using it --- app/viewer/mediapanel.cpp | 165 ++++++++++++++++++++++++++------------ app/viewer/mediapanel.h | 42 +++++++--- 2 files changed, 148 insertions(+), 59 deletions(-) diff --git a/app/viewer/mediapanel.cpp b/app/viewer/mediapanel.cpp index d806f32..bbf50cd 100644 --- a/app/viewer/mediapanel.cpp +++ b/app/viewer/mediapanel.cpp @@ -52,6 +52,11 @@ MediaPanel::MediaPanel(QWidget *parent) : m_PlaybackTimer = new QTimer(this); m_PlaybackTimer->setInterval(10); connect(m_PlaybackTimer, &QTimer::timeout, this, &MediaPanel::TimerUpdate); + + m_audioSink = nullptr; + + m_audioDevice = new MediaAudioMixer(this); + m_audioDevice->SetMediaInstances(&m_mediaInstances); } MediaPanel::~MediaPanel() @@ -371,15 +376,6 @@ void MediaPanel::OpenMediaInstance(si::Object *o) void MediaPanel::Play(bool e) { - { - // No matter what, stop any current audio - std::vector<QAudioSink*> copy = m_audioSinks; - for (auto it=copy.cbegin(); it!=copy.cend(); it++) { - auto o = *it; - o->stop(); - } - } - if (e) { bool has_video = false; bool has_audio = false; @@ -394,10 +390,11 @@ void MediaPanel::Play(bool e) auto output_dev = QAudioDevice(QMediaDevices::defaultAudioOutput()); auto fmt = output_dev.preferredFormat(); - ClearAudioSinks(); - - for (auto it=m_mediaInstances.cbegin(); it!=m_mediaInstances.cend(); it++) { - auto m = *it; + // Require float output (makes our lives easier) + fmt.setSampleFormat(QAudioFormat::Float); + + for (size_t i = 0; i < m_mediaInstances.size(); i++) { + auto m = m_mediaInstances[i]; m->ResetEOF(); @@ -405,11 +402,11 @@ void MediaPanel::Play(bool e) has_video = true; } else if (m->codec_type() == AVMEDIA_TYPE_AUDIO) { if (m_PlaybackOffset < (m->GetDuration() + m->GetStartOffset())) { - if (m->StartPlayingAudio(output_dev, fmt)) { - auto out = new QAudioSink(output_dev, fmt, this); - out->setVolume(m->GetVolume()); - out->start(m); - m_audioSinks.push_back(out); + if (m->SetUpResampleContext(fmt)) { + // auto out = new QAudioSink(output_dev, fmt, this); + // out->setVolume(m->GetVolume()); + // out->start(m); + // m_audioSinks.push_back(out); has_audio = true; } } else { @@ -418,12 +415,28 @@ void MediaPanel::Play(bool e) } } + if (has_audio) { + m_audioDevice->SetAudioFormat(fmt); + m_audioDevice->open(QIODevice::ReadOnly); + m_audioDevice->SeekInSeconds(GetSecondsFromSlider()); + + m_audioSink = new QAudioSink(output_dev, fmt, this); + m_audioSink->start(m_audioDevice); + } + m_PlaybackStart = QDateTime::currentMSecsSinceEpoch(); m_PlaybackTimer->start(); m_PlayBtn->setText("Pause"); } else { m_PlayBtn->setText("Play"); m_PlaybackTimer->stop(); + + if (m_audioSink) { + m_audioDevice->close(); + m_audioSink->stop(); + m_audioSink->deleteLater(); + m_audioSink = nullptr; + } } m_PlayBtn->setChecked(e); } @@ -452,7 +465,6 @@ void MediaPanel::TimerUpdate() } if (all_eof) { - ClearAudioSinks(); Play(false); m_PlayheadSlider->setValue(m_PlayheadSlider->maximum()); } @@ -507,26 +519,6 @@ void MediaPanel::LabelContextMenuTriggered(const QPoint &pos) m.exec(static_cast<QWidget*>(sender())->mapToGlobal(pos)); } -void MediaPanel::ClearAudioSinks() -{ - if (m_audioSinks.size() != 0) { - for (auto s : m_audioSinks) - delete s; - - m_audioSinks.clear(); - } -} - -qint64 MediaInstance::readData(char *data, qint64 maxSize) -{ - return ReadAudio(data, maxSize); -} - -qint64 MediaInstance::writeData(const char *data, qint64 maxSize) -{ - return -1; -} - ClickableSlider::ClickableSlider(Qt::Orientation orientation, QWidget *parent) : QSlider(orientation, parent) { @@ -547,6 +539,7 @@ void ClickableSlider::mousePressEvent(QMouseEvent *e) } MediaInstance::MediaInstance(QObject *parent) : + QObject(parent), m_FmtCtx(nullptr), m_Packet(nullptr), m_CodecCtx(nullptr), @@ -557,7 +550,6 @@ MediaInstance::MediaInstance(QObject *parent) : m_IoCtx(nullptr), m_startOffset(0.0f) { - this->open(QIODevice::ReadOnly); } void MediaInstance::Open(const si::bytearray &buf) @@ -681,7 +673,7 @@ void MediaInstance::Close() m_Data.Close(); } -bool MediaInstance::StartPlayingAudio(const QAudioDevice &output_dev, const QAudioFormat &fmt) +bool MediaInstance::SetUpResampleContext(const QAudioFormat &fmt) { if (m_SwrCtx) { swr_free(&m_SwrCtx); @@ -724,6 +716,8 @@ bool MediaInstance::StartPlayingAudio(const QAudioDevice &output_dev, const QAud 0, nullptr); if (r < 0) { qCritical() << "Failed to alloc swr ctx:" << r; + return false; + } #else m_SwrCtx = swr_alloc_set_opts(nullptr, av_get_default_channel_layout(fmt.channelCount()), @@ -735,19 +729,19 @@ bool MediaInstance::StartPlayingAudio(const QAudioDevice &output_dev, const QAud 0, nullptr); if (!m_SwrCtx) { qCritical() << "Failed to alloc swr ctx"; + return false; + } #endif - } else { - if (swr_init(m_SwrCtx) < 0) { - qCritical() << "Failed to init swr ctx"; - } else { - m_AudioFlushed = false; - m_AudioBuffer.clear(); - return true; - } + if (swr_init(m_SwrCtx) < 0) { + qCritical() << "Failed to init swr ctx"; + return false; } - return false; + m_AudioFlushed = false; + m_AudioBuffer.clear(); + + return true; } void MediaInstance::Seek(float seconds) @@ -788,3 +782,74 @@ void MediaInstance::SetVirtualTime(float f) { m_virtualPosition = f - m_startOffset; } + +MediaAudioMixer::MediaAudioMixer(QObject *parent) : + QIODevice(parent) +{ + m_mediaInstances = nullptr; +} + +void MediaAudioMixer::SeekInSeconds(float f) +{ + seek(m_audioFormat.bytesForDuration(f * 1000000)); +} + +qint64 MediaAudioMixer::readData(char *data, qint64 maxSize) +{ + if (!m_mediaInstances) { + return 0; + } + + // Media instances should be set to same sample rate and channel count as output, but we may need to convert format + float *output = reinterpret_cast<float *>(data); + + qint64 maxSamples = maxSize / m_audioFormat.bytesPerSample(); + + float *tmp = new float[maxSamples]; + + qint64 touchedBytes = 0; + + for (auto it = m_mediaInstances->cbegin(); it != m_mediaInstances->cend(); it++) { + auto m = *it; + + if (m->codec_type() == AVMEDIA_TYPE_AUDIO) { + qint64 thisRead = m->ReadAudio(reinterpret_cast<char *>(tmp), maxSize); + if (thisRead > touchedBytes) { + memset(data + touchedBytes, 0, thisRead - touchedBytes); + touchedBytes = thisRead; + } + + // TODO: Optimize with SSE and NEON + qint64 thisSamples = thisRead / m_audioFormat.bytesPerSample(); + for (qint64 j = 0; j < thisSamples; j++) { + output[j] += tmp[j] * m->GetVolume(); + } + } + } + + delete [] tmp; + + return touchedBytes; +} + +qint64 MediaAudioMixer::writeData(const char *data, qint64 maxSize) +{ + return -1; +} + +qint64 MediaAudioMixer::size() const +{ + if (!m_mediaInstances) { + return 0; + } + + // Calculate maximum duration in seconds + float maxLength = 0; + for (auto it = m_mediaInstances->cbegin(); it != m_mediaInstances->cend(); it++) { + auto m = *it; + maxLength = qMax(maxLength, m->GetDuration() + m->GetStartOffset()); + } + + // Convert seconds to bytes in the output format + return m_audioFormat.bytesForDuration(maxLength * 1000000); +} diff --git a/app/viewer/mediapanel.h b/app/viewer/mediapanel.h index 7e9dd19..6d41988 100644 --- a/app/viewer/mediapanel.h +++ b/app/viewer/mediapanel.h @@ -21,7 +21,7 @@ extern "C" { #include <QTimer> #include "panel.h" -class MediaInstance : public QIODevice +class MediaInstance : public QObject { Q_OBJECT public: @@ -36,7 +36,7 @@ public: return m_Stream ? m_Stream->codecpar->codec_type : AVMEDIA_TYPE_UNKNOWN; } - bool StartPlayingAudio(const QAudioDevice &output_dev, const QAudioFormat &fmt); + bool SetUpResampleContext(const QAudioFormat &fmt); void Seek(float seconds); @@ -82,10 +82,6 @@ public: signals: void EndOfFile(); -protected: - virtual qint64 readData(char *data, qint64 maxSize) override; - virtual qint64 writeData(const char *data, qint64 maxSize) override; - private: void ClearQueue(); @@ -123,6 +119,34 @@ private: }; +class MediaAudioMixer : public QIODevice +{ + Q_OBJECT +public: + MediaAudioMixer(QObject *parent = nullptr); + + void SetMediaInstances(std::vector<MediaInstance *> *mi) + { + m_mediaInstances = mi; + } + + void SetAudioFormat(const QAudioFormat &fmt) + { + m_audioFormat = fmt; + } + + void SeekInSeconds(float f); + +protected: + virtual qint64 readData(char *data, qint64 maxSize) override; + virtual qint64 writeData(const char *data, qint64 maxSize) override; + virtual qint64 size() const override; + +private: + std::vector<MediaInstance *> *m_mediaInstances; + QAudioFormat m_audioFormat; +}; + class MediaPanel : public Panel { Q_OBJECT @@ -158,7 +182,9 @@ private: std::vector<QLabel *> m_imgViewers; std::vector<MediaInstance *> m_mediaInstances; - std::vector<QAudioSink *> m_audioSinks; + + QAudioSink *m_audioSink; + MediaAudioMixer *m_audioDevice; QSlider *m_PlayheadSlider; QPushButton *m_PlayBtn; @@ -180,8 +206,6 @@ private slots: void LabelContextMenuTriggered(const QPoint &pos); - void ClearAudioSinks(); - }; class ClickableSlider : public QSlider From b41863750437c4949811466a83b14ad32de5c4c8 Mon Sep 17 00:00:00 2001 From: MattKC <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:42:53 -0800 Subject: [PATCH 4/4] ci: re-enable releases --- .github/workflows/ci.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e50cf35..db666ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,15 +62,15 @@ jobs: path: deploy -# - name: Upload to Releases -# shell: bash -# if: github.event_name == 'push' -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# TRAVIS_REPO_SLUG: isledecomp/siedit -# TRAVIS_COMMIT: ${{ github.sha }} -# run: | -# cd deploy -# 7z a si-edit.zip * -# curl -fLOSs --retry 2 --retry-delay 60 https://github.com/probonopd/uploadtool/raw/master/upload.sh -# ./upload.sh si-edit.zip + - name: Upload to Releases + shell: bash + if: github.event_name == 'push' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TRAVIS_REPO_SLUG: isledecomp/siedit + TRAVIS_COMMIT: ${{ github.sha }} + run: | + cd deploy + 7z a si-edit.zip * + curl -fLOSs --retry 2 --retry-delay 60 https://github.com/probonopd/uploadtool/raw/master/upload.sh + ./upload.sh si-edit.zip