Merge branch 'olive-editor:master' into master
This commit is contained in:
+4
-2
@@ -2,5 +2,7 @@
|
||||
* text=auto
|
||||
|
||||
# Enforce LF line endings on source files
|
||||
*.h text eol=lf
|
||||
*.cpp text eol=lf
|
||||
*.h text eol=lf
|
||||
*.cpp text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.desktop text eol=lf
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: olivevideoeditor
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Default issue template
|
||||
labels: 'Triage'
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Legacy Olive 0.1.x issue ⛔
|
||||
about: >-
|
||||
Olive 0.1 is no longer supported and issues made about it will be
|
||||
automatically closed.
|
||||
title: '[UNSUPPORTED] '
|
||||
labels: 'Legacy (Unsupported)'
|
||||
assignees: ''
|
||||
---
|
||||
# Olive 0.1 is unsupported
|
||||
|
||||
Unfortunately no one is supporting Olive 0.1 at this time. Any reports
|
||||
pertaining to it will be closed to keep the issue tracker focused on supported
|
||||
versions.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Crash
|
||||
about: Report a fatal crash that resulted in Olive unexpectedly closing.
|
||||
title: '[CRASH] '
|
||||
labels: 'Crash, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Steps to Reproduce**
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
<details><summary><strong>Crash Report</strong></summary><pre><code>
|
||||
|
||||
<!-- You should have received a crash report from Olive to paste here. -->
|
||||
|
||||
</code></pre></details>
|
||||
|
||||
**Additional Information**
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Build/Packaging
|
||||
about: >-
|
||||
Report an issue related to compiling or packaging.
|
||||
Note that we do not officially support custom build
|
||||
configurations and may not address issues involving them.
|
||||
title: '[BUILD] '
|
||||
labels: 'Building/Packaging, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Disk Cache
|
||||
about: >-
|
||||
Report an issue related to the disk cache system, including failure to cache,
|
||||
frames being set to appear at the wrong time, disk space or memory usage
|
||||
issues, etc.
|
||||
title: '[CACHE] '
|
||||
labels: 'Disk Cache, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Codec
|
||||
about: >-
|
||||
Report an issue related to codec handling, including importing footage or any
|
||||
footage usage issues while editing.
|
||||
title: '[CODEC] '
|
||||
labels: 'Codec, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Color Management
|
||||
about: >-
|
||||
Report an issue related to the management of pixels and color, including
|
||||
inaccurate results, color inconsistencies, etc.
|
||||
title: '[COLOR] '
|
||||
labels: 'Color Management, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Timeline/Editing
|
||||
about: >-
|
||||
Report an issue related to the overall editing experience, including usage of
|
||||
the timeline, interchange, synchronization, multi-camera support, etc.
|
||||
title: '[EDIT] '
|
||||
labels: 'Timeline/Editing, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Export
|
||||
about: >-
|
||||
Report an issue related to exporting videos from Olive, including errors
|
||||
while exporting, issues with the resulting video, etc.
|
||||
title: '[EXPORT] '
|
||||
labels: 'Export, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Node/Compositing
|
||||
about: >-
|
||||
Report an issue related to the node-based compositing system, including node graph
|
||||
usability issues, issues working with node effects, issues with composited output, etc.
|
||||
title: '[NODES] '
|
||||
labels: 'Nodes/Compositing, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Playback
|
||||
about: >-
|
||||
Report an issue related to the playback of video or audio, including laggy or
|
||||
inconsistent playback, failure to playback, etc.
|
||||
title: '[PLAYBACK] '
|
||||
labels: 'Playback, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Project Management
|
||||
about: >-
|
||||
Report an issue related to project management, including working with and
|
||||
organizing imported files.
|
||||
title: '[PROJECT] '
|
||||
labels: 'Project, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Renderer
|
||||
about: >-
|
||||
Report an issue related to rendering, including corrupted frames, incorrect
|
||||
render results, unexpected black or white frames, etc.
|
||||
title: '[RENDER] '
|
||||
labels: 'Renderer, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: User Interface
|
||||
about: >-
|
||||
Report an issue related to general user interface usability, including
|
||||
behavior issues, usability issues, look and feel, etc.
|
||||
title: '[UI] '
|
||||
labels: 'User Interface, Triage'
|
||||
assignees: ''
|
||||
---
|
||||
<!-- ⚠ Do not delete this issue template! ⚠ -->
|
||||
|
||||
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
|
||||
|
||||
|
||||
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
|
||||
|
||||
|
||||
**Summary**
|
||||
|
||||
|
||||
**Additional Information / Output**
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[Bug]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Your Setup:**
|
||||
- Olive version (window title or Help > About):
|
||||
- Source: (e.g. AppImage, PPA, AUR, website etc.)
|
||||
- Operating system: (e.g. Ubuntu 18.04 64-bit)
|
||||
- CPU: (e.g. Intel i5-4300U)
|
||||
- RAM: (e.g. 8GB)
|
||||
- GPU: (e.g. NVIDIA Geforce GT 1030 2GB (Driver ver xxx.xx.xx))
|
||||
|
||||
**Describe the Bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
** Debug Log and/or Crash Report **
|
||||
|
||||
```
|
||||
[Paste log/report]
|
||||
```
|
||||
|
||||
**Additional Details**
|
||||
Add any other information about the problem here.
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Community Support
|
||||
url: https://discordapp.com/invite/4Ae9KZn
|
||||
about: Join the Olive Discord server
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature Request]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Do you know any libraries or sources that can help the Olive Team include the feature?**
|
||||
List open-source libraries related to the feature.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,451 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.github/FUNDING.yml'
|
||||
- 'docker/**'
|
||||
- 'CONTRIBUTING.md'
|
||||
- 'README.md'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.github/FUNDING.yml'
|
||||
- 'app/ts/*.ts'
|
||||
- 'docker/**'
|
||||
- 'CONTRIBUTING.md'
|
||||
- 'README.md'
|
||||
|
||||
env:
|
||||
DOWNLOAD_TOOL: curl -fLOSs --retry 2 --retry-delay 60
|
||||
UPLOAD_TOOL: curl -X POST --retry 2 --retry-delay 60
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- build-type: RelWithDebInfo
|
||||
cc-compiler: gcc
|
||||
cxx-compiler: g++
|
||||
compiler-name: GCC 9.3.1
|
||||
cmake-gen: Ninja
|
||||
os-name: Linux (CentOS 7)
|
||||
- build-type: RelWithDebInfo
|
||||
cc-compiler: clang
|
||||
cxx-compiler: clang++
|
||||
compiler-name: Clang 10.0.0
|
||||
cmake-gen: Ninja
|
||||
os-name: Linux (CentOS 7)
|
||||
name: |
|
||||
${{ matrix.os-name }}
|
||||
<${{ matrix.compiler-name }},
|
||||
${{ matrix.build-type }},
|
||||
${{ matrix.cmake-gen }}>
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: olivevideoeditor/ci-olive:2021.4
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Generate Patreon List
|
||||
env:
|
||||
PATREON_KEY: ${{ secrets.PATREON_KEY }}
|
||||
run: |
|
||||
pip3 install requests
|
||||
cd $GITHUB_WORKSPACE/app/dialog/about
|
||||
python3 patreon.py
|
||||
if: github.event_name == 'push'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \
|
||||
-DCMAKE_C_COMPILER="${{ matrix.cc-compiler }}" \
|
||||
-DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}"
|
||||
|
||||
- name: Build
|
||||
working-directory: build
|
||||
run: |
|
||||
cmake --build .
|
||||
|
||||
- name: Test
|
||||
working-directory: build
|
||||
run: |
|
||||
ctest -C ${{ matrix.build-type }} -V
|
||||
|
||||
- name: Create Package
|
||||
id: package
|
||||
working-directory: build
|
||||
env:
|
||||
ARCH: x86_64
|
||||
run: |
|
||||
# Create install tree
|
||||
cmake --install app --prefix appdir/usr
|
||||
|
||||
# Inject custom AppRun (linuxdeployqt won't replace if it already exists)
|
||||
cp $GITHUB_WORKSPACE/app/packaging/linux/AppRun appdir
|
||||
|
||||
# Process AppDir
|
||||
/usr/local/linuxdeployqt-x86_64.AppImage \
|
||||
appdir/usr/share/applications/org.olivevideoeditor.Olive.desktop \
|
||||
-exclude-libs=libQt5Pdf.so,libQt5Qml.so,libQt5QmlModels.so,libQt5Quick.so,libQt5VirtualKeyboard.so \
|
||||
-bundle-non-qt-libs \
|
||||
-executable=appdir/usr/bin/crashpad_handler \
|
||||
-executable=appdir/usr/bin/minidump_stackwalk \
|
||||
-executable=appdir/usr/bin/olive-crashhandler \
|
||||
--appimage-extract-and-run
|
||||
|
||||
# Dump Crashpad symbols
|
||||
dump_syms appdir/usr/bin/olive-editor > olive-editor.sym
|
||||
# HACK: For some reason, minidump_stackwalk reads identifier as all 0s
|
||||
SYM_DIR=appdir/usr/share/olive-editor/symbols/olive-editor/000000000000000000000000000000000
|
||||
mkdir -p "$SYM_DIR"
|
||||
mv olive-editor.sym "$SYM_DIR"
|
||||
|
||||
# Package AppImage
|
||||
$DOWNLOAD_TOOL https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
VERSION=${GITHUB_SHA::8} ./appimagetool-x86_64.AppImage appdir --appimage-extract-and-run
|
||||
|
||||
# Set env variables
|
||||
filename=$(echo Olive*.AppImage)
|
||||
pkgname="${filename/x86_64/Linux-x86_64}"
|
||||
mv "${filename}" "${pkgname}"
|
||||
echo "::set-output name=pkgname::${pkgname}"
|
||||
echo "::set-output name=artifact::${filename/x86_64/Linux-x86_64-${{ matrix.cc-compiler }}}"
|
||||
|
||||
- name: Upload Artifact to GitHub
|
||||
uses: actions/upload-artifact@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: ${{ steps.package.outputs.artifact }}
|
||||
path: build/Olive*.AppImage
|
||||
|
||||
windows:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- build-type: RelWithDebInfo
|
||||
compiler-name: MSVC 16.x
|
||||
os-name: Windows
|
||||
os-arch: x86_64
|
||||
os: windows-latest
|
||||
cmake-gen: Ninja
|
||||
name: |
|
||||
${{ matrix.os-name }}
|
||||
<${{ matrix.compiler-name }},
|
||||
${{ matrix.build-type }},
|
||||
${{ matrix.cmake-gen }}>
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Automatically Generate Package Name
|
||||
shell: bash
|
||||
env:
|
||||
PLATFORM: ${{ matrix.os-name }}
|
||||
ARCH: ${{ matrix.os-arch }}
|
||||
run: |
|
||||
echo "PKGNAME=$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Build Folder
|
||||
run: |
|
||||
cmake -E make_directory ${{ runner.workspace }}/build
|
||||
|
||||
- name: Enable Developer Command Prompt (Windows)
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Acquire Dependencies
|
||||
shell: bash
|
||||
working-directory: ${{ runner.workspace }}
|
||||
run: |
|
||||
$DOWNLOAD_TOOL https://github.com/olive-editor/vcpkg-registry/releases/download/continuous/olive-dep-x64-windows.zip
|
||||
7z x olive-dep-x64-windows.zip
|
||||
VCPKG_DIR=(vcpkg-export-*)
|
||||
echo "VCPKG_ROOT=$(pwd -W)/$VCPKG_DIR" >> $GITHUB_ENV
|
||||
|
||||
- name: Acquire Google Crashpad
|
||||
shell: bash
|
||||
working-directory: ${{ runner.workspace }}
|
||||
run: |
|
||||
$DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-win32-RelWithDebInfo.tar.gz
|
||||
tar xzf crashpad-win32-RelWithDebInfo.tar.gz
|
||||
echo "$(pwd -W)/install" >> $GITHUB_PATH
|
||||
echo "$(pwd -W)/install/bin" >> $GITHUB_PATH
|
||||
echo "$(pwd -W)/install/crashpad" >> $GITHUB_PATH
|
||||
|
||||
- name: Generate Patreon List
|
||||
shell: bash
|
||||
env:
|
||||
PATREON_KEY: ${{ secrets.PATREON_KEY }}
|
||||
run: |
|
||||
pip3 install requests
|
||||
cd $GITHUB_WORKSPACE/app/dialog/about
|
||||
python3 patreon.py
|
||||
if: github.event_name == 'push'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
run: |
|
||||
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build .
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
run: |
|
||||
PATH=$PATH:$VCPKG_ROOT/installed/x64-windows/bin ctest -C ${{ matrix.build-type }} -V
|
||||
|
||||
- name: Create Package
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
env:
|
||||
ORIGINAL_WORKSPACE: ${{ runner.workspace }}
|
||||
run: |
|
||||
mkdir olive-editor
|
||||
cp app/olive-editor.exe olive-editor
|
||||
cp app/crashhandler/olive-crashhandler.exe olive-editor
|
||||
cp app/olive-editor.pdb olive-editor
|
||||
$VCPKG_ROOT/installed/x64-windows/tools/qt5/bin/windeployqt --no-angle olive-editor/olive-crashhandler.exe
|
||||
$VCPKG_ROOT/installed/x64-windows/tools/qt5/bin/windeployqt --no-angle olive-editor/olive-editor.exe
|
||||
cp $(cygpath $VCPKG_ROOT)/installed/x64-windows/bin/*.dll olive-editor
|
||||
cp $(cygpath $ORIGINAL_WORKSPACE)/install/bin/*.dll olive-editor
|
||||
cp $(cygpath $ORIGINAL_WORKSPACE)/install/crashpad/out/Default/crashpad_handler.exe olive-editor
|
||||
cp $(cygpath $ORIGINAL_WORKSPACE)/install/bin/minidump_stackwalk.exe olive-editor
|
||||
|
||||
- name: Export Crashpad symbols
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
run: |
|
||||
curl -fLSs https://github.com/google/breakpad/blob/master/src/tools/windows/binaries/dump_syms.exe?raw=true > dump_syms.exe
|
||||
./dump_syms app/olive-editor.pdb > olive-editor.sym
|
||||
SYM_HEADER=($(head -n 1 olive-editor.sym)) # Read first line of symbol file
|
||||
SYM_DIR=olive-editor/symbols/olive-editor.pdb/${SYM_HEADER[3]}
|
||||
mkdir -p "$SYM_DIR"
|
||||
mv olive-editor.sym "$SYM_DIR"
|
||||
|
||||
- name: Deploy Packages
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
run: |
|
||||
# Create Installer Executable
|
||||
#$DOWNLOAD_TOOL http://web.archive.org/web/20210226132532/http://download.microsoft.com/download/3/2/2/3224B87F-CFA0-4E70-BDA3-3DE650EFEBA5/vcredist_x64.exe
|
||||
cp $(cygpath $GITHUB_WORKSPACE)/app/packaging/windows/nsis/* .
|
||||
cp $(cygpath $GITHUB_WORKSPACE)/LICENSE .
|
||||
$DOWNLOAD_TOOL https://nsis.sourceforge.io/mediawiki/images/6/68/ShellExecAsUser_amd64-Unicode.7z
|
||||
7z e ShellExecAsUser_amd64-Unicode.7z Plugins/x86-unicode/ShellExecAsUser.dll
|
||||
makensis -V4 -DX64 "-XOutFile $PKGNAME.exe" "-X!AddPluginDir /x86-unicode $(pwd -W)" olive.nsi
|
||||
|
||||
# Create Portable ZIP
|
||||
echo -n > olive-editor/portable
|
||||
7z a $PKGNAME.zip olive-editor
|
||||
|
||||
- name: Upload Installer Artifact to GitHub
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ env.PKGNAME }}.exe
|
||||
path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.exe
|
||||
|
||||
- name: Upload Portable Artifact to GitHub
|
||||
uses: actions/upload-artifact@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: ${{ env.PKGNAME }}.zip
|
||||
path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.zip
|
||||
|
||||
macos:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- build-type: RelWithDebInfo
|
||||
compiler-name: Clang LLVM
|
||||
os-name: macOS
|
||||
os-arch: x86_64
|
||||
os: macos-10.15
|
||||
cmake-gen: Ninja
|
||||
min-deploy: 10.13
|
||||
- build-type: RelWithDebInfo
|
||||
compiler-name: Clang LLVM
|
||||
os-name: macOS
|
||||
os-arch: arm64
|
||||
os: macos-11.0
|
||||
cmake-gen: Ninja
|
||||
min-deploy: 11.0
|
||||
env:
|
||||
DEP_LOCATION: /opt/olive-editor
|
||||
name: |
|
||||
${{ matrix.os-name }}
|
||||
<${{ matrix.os-arch }},
|
||||
${{ matrix.compiler-name }},
|
||||
${{ matrix.build-type }},
|
||||
${{ matrix.cmake-gen }}>
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Automatically Generate Package Name
|
||||
shell: bash
|
||||
env:
|
||||
PLATFORM: ${{ matrix.os-name }}
|
||||
ARCH: ${{ matrix.os-arch }}
|
||||
run: |
|
||||
echo "PKGNAME=$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Build Folder
|
||||
run: |
|
||||
cmake -E make_directory ${{ runner.workspace }}/build
|
||||
|
||||
- name: Acquire Dependencies
|
||||
shell: bash
|
||||
working-directory: ${{ runner.workspace }}
|
||||
run: |
|
||||
$DOWNLOAD_TOOL https://github.com/olive-editor/dependencies/releases/download/continuous/olive-dep-mac-${{ matrix.os-arch }}.tar.gz
|
||||
sudo tar xzf olive-dep-mac-${{ matrix.os-arch }}.tar.gz -C /
|
||||
|
||||
- name: Acquire Google Crashpad
|
||||
shell: bash
|
||||
working-directory: ${{ runner.workspace }}
|
||||
run: |
|
||||
$DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-mac-${{ matrix.os-arch }}.tar.gz
|
||||
sudo tar xzf crashpad-mac-${{ matrix.os-arch }}.tar.gz -C /
|
||||
if: matrix.os-arch == 'x86_64' # TEMPORARY: Crashpad isn't compiling on ARM64 yet for some reason
|
||||
|
||||
- name: Generate Patreon List
|
||||
env:
|
||||
PATREON_KEY: ${{ secrets.PATREON_KEY }}
|
||||
run: |
|
||||
pip3 install requests
|
||||
cd $GITHUB_WORKSPACE/app/dialog/about
|
||||
python3 patreon.py
|
||||
if: github.event_name == 'push'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
run: |
|
||||
brew install ninja
|
||||
PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \
|
||||
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}"
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build .
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
run: |
|
||||
ctest -C ${{ matrix.build-type }} -V
|
||||
if: matrix.os-arch == 'x86_64' # ARM64 tests naturally won't be able to run on x86_64 runners
|
||||
|
||||
- name: Create Package
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
run: |
|
||||
BUNDLE_NAME="Olive.app"
|
||||
brew install dylibbundler
|
||||
|
||||
if [ "${{ matrix.os-arch }}" == "x86_64" ]
|
||||
then
|
||||
DYLIBBUNDLER_EXTRA_ARGS="-x $BUNDLE_NAME/Contents/MacOS/olive-crashhandler"
|
||||
fi
|
||||
|
||||
mv app/$BUNDLE_NAME .
|
||||
mkdir $BUNDLE_NAME/Contents/Frameworks
|
||||
dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" $DYLIBBUNDLER_EXTRA_ARGS
|
||||
|
||||
# Copy Qt frameworks and plugins
|
||||
cp -Ra $DEP_LOCATION/lib/Qt*.framework $BUNDLE_NAME/Contents/Frameworks
|
||||
cp -Ra $DEP_LOCATION/plugins $BUNDLE_NAME/Contents
|
||||
|
||||
# HACK: On x86_64, dylibbundler doesn't resolve this symlink. Weirdly it does on ARM64,
|
||||
# but perhaps I'll bring it up with them soon.
|
||||
cp -a $BUNDLE_NAME/Contents/Frameworks/libpng16.16.37.0.dylib $BUNDLE_NAME/Contents/Frameworks/libpng16.16.dylib
|
||||
|
||||
if [ "${{ matrix.os-arch }}" == "x86_64" ]
|
||||
then
|
||||
# Crashpad symbols
|
||||
$DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym
|
||||
SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file
|
||||
SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]}
|
||||
mkdir -p "$SYM_DIR"
|
||||
mv Olive.sym "$SYM_DIR"
|
||||
fi
|
||||
|
||||
- name: Sign Application
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
|
||||
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
BUNDLE_NAME="Olive.app"
|
||||
|
||||
# Install certificate
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
|
||||
|
||||
# import certificate from secrets
|
||||
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH
|
||||
|
||||
# create temporary keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
|
||||
# import certificate to keychain
|
||||
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||
|
||||
# HACK: Remove unsignable frameworks
|
||||
rm -r $BUNDLE_NAME/Contents/Frameworks/QtUiPlugin.framework
|
||||
if [ "${{ matrix.os-arch }}" == "arm64" ]
|
||||
then
|
||||
rm -r $BUNDLE_NAME/Contents/Frameworks/QtZlib.framework
|
||||
fi
|
||||
|
||||
# Sign application
|
||||
codesign --deep --sign "Developer ID Application: Olive Studios LLC" $BUNDLE_NAME
|
||||
|
||||
- name: Deploy Packages
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
run: |
|
||||
zip -r $PKGNAME.zip Olive.app
|
||||
|
||||
- name: Upload Artifact to GitHub
|
||||
uses: actions/upload-artifact@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: ${{ env.PKGNAME }}.zip
|
||||
path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.zip
|
||||
+42
-9
@@ -1,15 +1,43 @@
|
||||
*.pro.user*
|
||||
Makefile
|
||||
.qmake.stash
|
||||
effects/frei0r
|
||||
ts/*.qm
|
||||
docs
|
||||
history
|
||||
# CMake artifacts
|
||||
/build*/
|
||||
|
||||
.vscode
|
||||
# Doxygen
|
||||
/docs/
|
||||
|
||||
# Visual Studio (Code)
|
||||
.localhistory/
|
||||
.history/
|
||||
.vscode/
|
||||
.vs/
|
||||
/out/
|
||||
CmakeSettings.json
|
||||
*.code-workspace
|
||||
|
||||
# clangd's index and likely other things that need not be in the repository
|
||||
.cache/
|
||||
|
||||
# macOS General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Windows folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares (Windows)
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
#
|
||||
# Qt ignores taken from https://github.com/github/gitignore
|
||||
# Qt ignores taken from https://github.com/github/gitignore/blob/master/Qt.gitignore
|
||||
#
|
||||
|
||||
# C++ objects and libs
|
||||
@@ -20,6 +48,7 @@ history
|
||||
*.la
|
||||
*.lai
|
||||
*.so
|
||||
*.so.*
|
||||
*.dll
|
||||
*.dylib
|
||||
|
||||
@@ -42,6 +71,8 @@ ui_*.h
|
||||
*.jsc
|
||||
Makefile*
|
||||
*build-*
|
||||
*.qm
|
||||
*.prl
|
||||
|
||||
# Qt unit tests
|
||||
target_wrapper.*
|
||||
@@ -61,3 +92,5 @@ compile_commands.json
|
||||
|
||||
# QtCreator local machine specific files for imported projects
|
||||
*creator.user*
|
||||
|
||||
*_qmlcache.qrc
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
env: ARCH=x86_64
|
||||
compiler: gcc
|
||||
sudo: require
|
||||
dist: trusty
|
||||
- os: osx
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- ffmpeg
|
||||
- qt5
|
||||
- grep
|
||||
- opencolorio
|
||||
- openimageio
|
||||
update: true
|
||||
# Can't build on Windows - affected by https://travis-ci.community/t/current-known-issues-please-read-this-before-posting-a-new-topic/264/10
|
||||
# - os: windows
|
||||
|
||||
before_install:
|
||||
- source ./.travis/before_install.sh
|
||||
|
||||
install:
|
||||
- source ./.travis/install.sh
|
||||
|
||||
script:
|
||||
- source ./.travis/script.sh
|
||||
|
||||
after_success:
|
||||
- source ./.travis/after_success.sh
|
||||
|
||||
branches:
|
||||
except:
|
||||
- # Do not build tags that we create when we upload to GitHub Releases
|
||||
- /^(?i:continuous)/
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check if there's been a new commit since this build, and if so don't upload it
|
||||
|
||||
GREP_PATH=grep
|
||||
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
GREP_PATH=ggrep
|
||||
fi
|
||||
|
||||
if [ "$TRAVIS_TAG" != "" ]; then
|
||||
|
||||
fi
|
||||
|
||||
# Get current repo commit from GitHub (problems arose from trying to pipe cURL directly into grep, so we buffer it through a file)
|
||||
REMOTE=$(curl -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/olive-editor/olive/commits/master | $GREP_PATH -Po '(?<=: \")(([a-z0-9])\w+)(?=\")' -m 1 --)
|
||||
LOCAL=$(git rev-parse HEAD)
|
||||
|
||||
if [ "$TRAVIS_TAG" != "" || "$REMOTE" == "$LOCAL" ]
|
||||
then
|
||||
echo "[INFO] Still current. Uploading..."
|
||||
|
||||
# retrieve upload tool
|
||||
#wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
|
||||
|
||||
#if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
|
||||
# upload final package
|
||||
#bash upload.sh Olive*.zip
|
||||
|
||||
#elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
|
||||
#find appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq
|
||||
|
||||
# upload final package
|
||||
#bash upload.sh Olive*.AppImage*
|
||||
|
||||
#fi
|
||||
|
||||
else
|
||||
|
||||
echo "[INFO] No longer current. $REMOTE vs $LOCAL - aborting upload."
|
||||
|
||||
fi
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
|
||||
# Qt 5.10.1 (may have to source newer version at some point)
|
||||
sudo add-apt-repository ppa:beineri/opt-qt-5.10.1-trusty -y
|
||||
|
||||
# FFmpeg 4.x (uses libs from 3.x repo)
|
||||
sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y
|
||||
sudo add-apt-repository ppa:jonathonf/ffmpeg-4 -y
|
||||
|
||||
# OpenImageIO
|
||||
sudo add-apt-repository ppa:olive-editor/openimageio -y
|
||||
|
||||
# Update apt
|
||||
sudo apt-get update -qq
|
||||
|
||||
fi
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
|
||||
#export PATH="/usr/local/opt/qt/bin:/usr/local/opt/python@2/libexec/bin:$PATH"
|
||||
|
||||
elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
|
||||
sudo apt-get -y -o Dpkg::Options::="--force-overwrite" install qt510base qt510multimedia qt510svg qttools5-dev libavformat-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev cmake libopencolorio-dev libopenimageio-dev libopenexr-dev
|
||||
source /opt/qt*/bin/qt*-env.sh
|
||||
|
||||
fi
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
|
||||
# Generate Makefile
|
||||
cmake .
|
||||
|
||||
# Make
|
||||
make -j$(sysctl -n hw.ncpu)
|
||||
|
||||
BUNDLE_PATH=$(find . -name "Olive.app")
|
||||
|
||||
echo Found app at: $BUNDLE_PATH
|
||||
|
||||
# Move Qt deps into bundle
|
||||
macdeployqt $BUNDLE_PATH
|
||||
|
||||
# Fix other deps that macdeployqt missed
|
||||
wget -c -nv https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py
|
||||
python2 macdeployqtfix.py $BUNDLE_PATH/Contents/MacOS/Olive /usr/local/Cellar/qt5/5.*/
|
||||
|
||||
# Distribute in zip
|
||||
zip -r Olive-$(git rev-parse --short HEAD)-macOS.zip $BUNDLE_PATH
|
||||
|
||||
elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
|
||||
# Generate Makefile
|
||||
cmake . -DQt5LinguistTools_DIR=/usr/lib/x86_64-linux-gnu/cmake/Qt5LinguistTools
|
||||
|
||||
# Make
|
||||
make -j$(nproc)
|
||||
|
||||
# Use `make install` on `appdir` to place files in the correct place
|
||||
make DESTDIR=appdir -j$(nproc) install
|
||||
|
||||
# Download linuxdeployqt
|
||||
wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
|
||||
chmod a+x linuxdeployqt-continuous-x86_64.AppImage
|
||||
|
||||
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
|
||||
|
||||
# linuxdeployqt uses this for naming the file
|
||||
export VERSION=$(git rev-parse --short HEAD)
|
||||
|
||||
# Use linuxdeployqt to set up dependencies
|
||||
./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/local/share/applications/*.desktop -extra-plugins=imageformats/libqsvg.so -appimage
|
||||
|
||||
fi
|
||||
+144
-26
@@ -1,5 +1,5 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -14,14 +14,15 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
||||
|
||||
project(olive-editor VERSION 0.2.0 LANGUAGES CXX)
|
||||
|
||||
option(UPDATE_TS "Update translations" OFF)
|
||||
option(BUILD_DOXYGEN "Build Doxygen documentation" OFF)
|
||||
option(BUILD_TESTS "Build unit tests" ON)
|
||||
option(USE_WERROR "Error on compile warning" ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
@@ -29,32 +30,97 @@ set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
# Set compiler options
|
||||
if(MSVC)
|
||||
set(OLIVE_COMPILE_OPTIONS
|
||||
/wd4267
|
||||
/wd4244
|
||||
/experimental:external
|
||||
/external:anglebrackets
|
||||
/external:W0
|
||||
"$<$<CONFIG:RELEASE>:/O2>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:/MP>"
|
||||
)
|
||||
if (USE_WERROR)
|
||||
list(APPEND OLIVE_COMPILE_OPTIONS "/WX")
|
||||
endif()
|
||||
else()
|
||||
set(OLIVE_COMPILE_OPTIONS
|
||||
"$<$<CONFIG:RELEASE>:-O2>"
|
||||
-Wuninitialized
|
||||
-pedantic-errors
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-unused-parameter
|
||||
-Wshadow
|
||||
)
|
||||
if (USE_WERROR)
|
||||
list(APPEND OLIVE_COMPILE_OPTIONS "-Werror")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(OLIVE_DEFINITIONS -DQT_DEPRECATED_WARNINGS)
|
||||
if (WIN32)
|
||||
list(APPEND OLIVE_DEFINITIONS -DUNICODE -D_UNICODE)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
# Link OpenGL
|
||||
if(UNIX AND NOT APPLE AND NOT DEFINED OpenGL_GL_PREFERENCE)
|
||||
set(OpenGL_GL_PREFERENCE LEGACY)
|
||||
endif()
|
||||
|
||||
find_package(OpenGL REQUIRED)
|
||||
list(APPEND OLIVE_LIBRARIES OpenGL::GL)
|
||||
|
||||
find_package(OpenColorIO REQUIRED)
|
||||
# Link OpenColorIO
|
||||
find_package(OpenColorIO 2.0.0 REQUIRED)
|
||||
list(APPEND OLIVE_LIBRARIES ${OCIO_LIBRARIES})
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OCIO_INCLUDE_DIRS})
|
||||
|
||||
find_package(OpenImageIO REQUIRED)
|
||||
# Link OpenImageIO
|
||||
find_package(OpenImageIO 2.1.12 REQUIRED)
|
||||
list(APPEND OLIVE_LIBRARIES ${OIIO_LIBRARIES})
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OIIO_INCLUDE_DIRS})
|
||||
|
||||
find_package(Qt5 5.6 REQUIRED
|
||||
COMPONENTS
|
||||
# Link OpenEXR
|
||||
find_package(OpenEXR REQUIRED)
|
||||
list(APPEND OLIVE_LIBRARIES ${OPENEXR_LIBRARIES})
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES})
|
||||
|
||||
# Link Qt 5
|
||||
set(QT_LIBRARIES
|
||||
Core
|
||||
Gui
|
||||
Widgets
|
||||
Multimedia
|
||||
OpenGL
|
||||
Svg
|
||||
LinguistTools
|
||||
Concurrent
|
||||
)
|
||||
if (UNIX AND NOT APPLE)
|
||||
list(APPEND QT_LIBRARIES DBus)
|
||||
endif()
|
||||
find_package(Qt5 5.6 REQUIRED
|
||||
COMPONENTS
|
||||
${QT_LIBRARIES}
|
||||
OPTIONAL_COMPONENTS
|
||||
Network
|
||||
)
|
||||
if (NOT Qt5Network_FOUND)
|
||||
message(" Qt5::Network module not found, crash reporting will be disabled.")
|
||||
endif()
|
||||
list(APPEND OLIVE_LIBRARIES
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
Qt5::OpenGL
|
||||
Qt5::Svg
|
||||
Qt5::Concurrent
|
||||
)
|
||||
|
||||
find_package(FFMPEG 3.4 REQUIRED
|
||||
# Link FFmpeg
|
||||
find_package(FFMPEG 3.0 REQUIRED
|
||||
COMPONENTS
|
||||
avutil
|
||||
avcodec
|
||||
@@ -63,36 +129,88 @@ find_package(FFMPEG 3.4 REQUIRED
|
||||
swscale
|
||||
swresample
|
||||
)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS})
|
||||
list(APPEND OLIVE_LIBRARIES
|
||||
FFMPEG::avutil
|
||||
FFMPEG::avcodec
|
||||
FFMPEG::avformat
|
||||
FFMPEG::avfilter
|
||||
FFMPEG::swscale
|
||||
FFMPEG::swresample
|
||||
)
|
||||
|
||||
# Link PortAudio
|
||||
find_package(PortAudio REQUIRED)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${PORTAUDIO_INCLUDE_DIRS})
|
||||
include(CheckIncludeFileCXX)
|
||||
check_include_file_cxx( "pa_jack.h" PA_HAS_JACK)
|
||||
if (PA_HAS_JACK)
|
||||
list(APPEND OLIVE_DEFINITIONS PA_HAS_JACK)
|
||||
endif()
|
||||
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIRS})
|
||||
list(APPEND OLIVE_LIBRARIES ${PORTAUDIO_LIBRARIES})
|
||||
|
||||
# Optional: Link OpenTimelineIO
|
||||
find_package(OpenTimelineIO)
|
||||
if (OpenTimelineIO_FOUND)
|
||||
list(APPEND OLIVE_DEFINITIONS USE_OTIO)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OTIO_INCLUDE_DIRS})
|
||||
list(APPEND OLIVE_LIBRARIES ${OTIO_LIBRARIES})
|
||||
else()
|
||||
message(" OpenTimelineIO interchange will be disabled.")
|
||||
endif()
|
||||
|
||||
# Optional: Link Google Crashpad
|
||||
find_package(GoogleCrashpad)
|
||||
if (GoogleCrashpad_FOUND)
|
||||
list(APPEND OLIVE_DEFINITIONS USE_CRASHPAD)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${CRASHPAD_INCLUDE_DIRS})
|
||||
list(APPEND OLIVE_LIBRARIES ${CRASHPAD_LIBRARIES})
|
||||
else()
|
||||
message(" Automatic crash reporting will be disabled.")
|
||||
|
||||
if (APPLE)
|
||||
# Enables use of special functions for slider dragging, only linked if Crashpad isn't found
|
||||
# because Crashpad links it itself and will cause duplicate references if we also link it
|
||||
list(APPEND OLIVE_LIBRARIES "-framework ApplicationServices")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND OLIVE_DEFINITIONS "-DUNICODE -D_UNICODE")
|
||||
elseif (APPLE)
|
||||
list(APPEND OLIVE_LIBRARIES "-framework IOKit")
|
||||
elseif(UNIX)
|
||||
list(APPEND OLIVE_LIBRARIES Qt5::DBus)
|
||||
endif()
|
||||
|
||||
# Generate Git hash
|
||||
set(PROJECT_LONG_VERSION ${PROJECT_VERSION})
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
find_package(Git)
|
||||
if(GIT_FOUND)
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short=8 HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
set(PROJECT_LONG_VERSION ${PROJECT_VERSION}-${GIT_HASH})
|
||||
endif()
|
||||
elseif(UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
|
||||
# Fallback for Ubuntu/Launchpad (extracts Git hash from debian/changelog rather than Git repo)
|
||||
# (see https://answers.launchpad.net/launchpad/+question/678556)
|
||||
execute_process(COMMAND sh -c "grep -Po '(?<=-)(([a-z0-9])\\w+)(?=\\+)' -m 1 changelog"
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/debian
|
||||
OUTPUT_VARIABLE GIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
if(DEFINED GIT_HASH)
|
||||
message("Olive: Git hash = " "${GIT_HASH}")
|
||||
list(APPEND OLIVE_DEFINITIONS -DGITHASH="${GIT_HASH}")
|
||||
else()
|
||||
message("Olive: No Git hash defined!")
|
||||
endif()
|
||||
|
||||
# Optional: Find Doxygen if requested
|
||||
if(BUILD_DOXYGEN)
|
||||
find_package(Doxygen)
|
||||
endif()
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/ext)
|
||||
|
||||
add_subdirectory(app)
|
||||
|
||||
if (BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
+32
-4
@@ -1,8 +1,36 @@
|
||||
# Contributing to Olive
|
||||
|
||||
### Standards
|
||||
Thank you for your interest in contributing to Olive!
|
||||
|
||||
When contributing to Olive, it's recommended to use the following rules:
|
||||
## Reporting issues
|
||||
|
||||
* [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
|
||||
* 120 column limit
|
||||
Bug reports help to make the software more stable and usable.
|
||||
Please read the pinned [issue #1175](https://github.com/olive-editor/olive/issues/1175)
|
||||
for guidelines before you create a new issue.
|
||||
|
||||
## Writing code
|
||||
|
||||
Code contributions are welcome. Note that the code base is rapidly changing in
|
||||
the current stage of development however. There is some documentation in the form
|
||||
of code comments, including Javadoc in header files. Feel free to reach out via
|
||||
an issue or pull request if you have questions about the architecture or
|
||||
implementation details.
|
||||
|
||||
### Code Standards
|
||||
|
||||
In order to keep the code as readable and maintainable as possible, code
|
||||
submitted should abide by the following standards:
|
||||
|
||||
* The code style generally follows the
|
||||
[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
|
||||
including, but not limited to:
|
||||
* Indentation is 2 spaces wide, spaces only (no tabs)
|
||||
* `lowercase_underscored_variable_names`
|
||||
* `lowercase_underscored_functions()` or `SentenceCaseFunctions()`
|
||||
* `class SentenceCaseClassesAndStructs {}`
|
||||
* `kSentenceCaseConstants` prepended with a lowercase `k`
|
||||
* `UPPERCASE_UNDERSCORED_MACROS` for variables or same style as functions for macro functions
|
||||
* `class_member_variables_` end with a `_`
|
||||
* 100 column limit (where it doesn't impair readability)
|
||||
* Unix line endings (only LF no CRLF)
|
||||
* Javadoc documentation where appropriate
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
# Olive Video Editor [](https://travis-ci.org/olive-editor/olive) [](https://ci.appveyor.com/project/itsmattkc/olive)
|
||||
# Olive Video Editor [](https://github.com/olive-editor/olive/actions?query=branch%3Amaster)
|
||||
|
||||
Olive is a free non-linear video editor for Windows, macOS, and Linux.
|
||||
|
||||

|
||||

|
||||
|
||||
**Discover more:** [Website](https://www.olivevideoeditor.org/) | [Twitter](https://twitter.com/oliveteam) | [Discord](https://discord.gg/4Ae9KZn) | [Code Documentation](http://olivevideoeditor.org/doxygen/)
|
||||
**Discover more:** [Website](https://www.olivevideoeditor.org/) | [Binaries](https://olivevideoeditor.org/download.php) | [Patreon](https://www.patreon.com/olivevideoeditor) | [Twitter](https://twitter.com/oliveteam) | [Wiki](https://github.com/olive-editor/olive/wiki/Overview-Guide) | [Community Discord (Unofficial)](https://discord.gg/4Ae9KZn)
|
||||
|
||||
**NOTE: The issue tracker is temporarily closed while Olive's core is getting rewritten. We apologize for any inconvenience. Please check back soon for the next release, as well as the [Patreon page](https://www.patreon.com/olivevideoeditor) for news and updates.**
|
||||
**NOTE: Olive is alpha software and is considered highly unstable. While we highly appreciate users testing and providing usage information, please use at your own risk.**
|
||||
|
||||
## Binaries
|
||||
|
||||
|
||||
## Getting Started with Olive:
|
||||
|
||||
Installation of Olive is available at: https://olivevideoeditor.org/download.php
|
||||
|
||||
Instructions on how to use Olive are available on our wiki: https://github.com/olive-editor/olive/wiki/Overview-Guide
|
||||
|
||||
**NOTE: Olive is Alpha Software, and it should be treated as highly unstable. Work at your own risk.**
|
||||
[Nightly binaries are available on the website.](https://olivevideoeditor.org/download.php)
|
||||
|
||||
## Support Olive:
|
||||
|
||||
@@ -24,10 +18,6 @@ Please consider supporting Olive:
|
||||
|
||||
[](https://www.patreon.com/olivevideoeditor)
|
||||
|
||||
**NOTE: It is strongly discouraged to use or compile the `master` branch in its current state as it's under heavy restructuring. Please use the 0.1.x code from the "Releases" tab instead.**
|
||||
|
||||
## Compiling from Source:
|
||||
|
||||
Compiling instructions for Windows, macOS, and Linux can be found [on the main site](https://olivevideoeditor.org/compile.php).
|
||||
|
||||
Olive has Doxygen-compatible documentation hosted at http://olivevideoeditor.org/doxygen/. You can also run `doxygen` in the source root directory to generate a local copy.
|
||||
|
||||
+94
-75
@@ -1,5 +1,5 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -14,112 +14,78 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Set Olive sources and resources
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
core.h
|
||||
core.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
#set(OLIVE_RESOURCES)
|
||||
|
||||
# Add subdirectories, which will populate the above variables
|
||||
add_subdirectory(audio)
|
||||
add_subdirectory(cli)
|
||||
add_subdirectory(codec)
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(config)
|
||||
add_subdirectory(decoder)
|
||||
add_subdirectory(dialog)
|
||||
add_subdirectory(node)
|
||||
add_subdirectory(packaging)
|
||||
add_subdirectory(panel)
|
||||
add_subdirectory(project)
|
||||
add_subdirectory(render)
|
||||
add_subdirectory(shaders)
|
||||
add_subdirectory(task)
|
||||
add_subdirectory(threading)
|
||||
add_subdirectory(timeline)
|
||||
add_subdirectory(ts)
|
||||
add_subdirectory(tool)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(undo)
|
||||
add_subdirectory(widget)
|
||||
add_subdirectory(window)
|
||||
|
||||
set(OLIVE_TARGET "olive-editor")
|
||||
if(APPLE)
|
||||
set(OLIVE_TARGET "Olive")
|
||||
endif()
|
||||
# Add translations
|
||||
qt5_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES})
|
||||
|
||||
set(QRC_BODY "")
|
||||
foreach(QM_FILE ${OLIVE_QM_FILES})
|
||||
get_filename_component(QM_FILENAME_COMPONENT ${QM_FILE} NAME_WE)
|
||||
string(APPEND QRC_BODY "<file alias=\"${QM_FILENAME_COMPONENT}\">${QM_FILE}</file>\n")
|
||||
endforeach()
|
||||
configure_file(ts/translations.qrc.in ts/translations.qrc @ONLY)
|
||||
|
||||
set(OLIVE_RESOURCES
|
||||
${OLIVE_RESOURCES}
|
||||
#effects/internal/internalshaders.qrc
|
||||
${CMAKE_CURRENT_BINARY_DIR}/ts/translations.qrc
|
||||
)
|
||||
|
||||
add_executable(${OLIVE_TARGET}
|
||||
# Add version object
|
||||
add_library(olive-version-obj
|
||||
OBJECT
|
||||
version.cpp
|
||||
version.h
|
||||
)
|
||||
target_link_libraries(olive-version-obj PRIVATE Qt5::Core)
|
||||
target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" )
|
||||
|
||||
# Add main library
|
||||
add_library(libolive-editor
|
||||
OBJECT
|
||||
${OLIVE_SOURCES}
|
||||
${OLIVE_RESOURCES}
|
||||
${OLIVE_EFFECTS}
|
||||
${OLIVE_QM_FILES}
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
SET_TARGET_PROPERTIES(${OLIVE_TARGET} PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_FRAMEWORK_IDENTIFIER org.olivevideoeditor.Olive
|
||||
)
|
||||
endif()
|
||||
# Remove prefix - prevents CMake calling it "liblibolive-editor"
|
||||
set_target_properties(libolive-editor PROPERTIES PREFIX "")
|
||||
|
||||
target_compile_definitions(${OLIVE_TARGET} PRIVATE ${OLIVE_DEFINITIONS})
|
||||
|
||||
if(MSVC)
|
||||
# Enabling this threw warnings from FFmpeg headers that broke compilation. See if this is fixable.
|
||||
#target_compile_options(${OLIVE_TARGET} PRIVATE /W4 /WX)
|
||||
else()
|
||||
target_compile_options(${OLIVE_TARGET} PRIVATE -Werror -Wuninitialized -pedantic-errors -Wall -Wextra -Wconversion -Wsign-conversion)
|
||||
endif()
|
||||
|
||||
target_include_directories(
|
||||
${OLIVE_TARGET}
|
||||
PRIVATE
|
||||
${OPENCOLORIO_INCLUDE_DIR}
|
||||
${OIIO_INCLUDE_DIRS}
|
||||
${FFMPEG_INCLUDE_DIRS}
|
||||
# Add application
|
||||
add_executable(olive-editor
|
||||
main.cpp
|
||||
$<TARGET_OBJECTS:libolive-editor>
|
||||
$<TARGET_OBJECTS:olive-version-obj>
|
||||
)
|
||||
|
||||
target_link_libraries(${OLIVE_TARGET}
|
||||
PRIVATE
|
||||
OpenGL::GL
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
Qt5::Multimedia
|
||||
Qt5::OpenGL
|
||||
Qt5::Svg
|
||||
FFMPEG::avutil
|
||||
FFMPEG::avcodec
|
||||
FFMPEG::avformat
|
||||
FFMPEG::avfilter
|
||||
FFMPEG::swscale
|
||||
FFMPEG::swresample
|
||||
${OPENCOLORIO_LIBRARIES}
|
||||
${OIIO_LIBRARIES}
|
||||
)
|
||||
|
||||
set(OLIVE_EFFECTS
|
||||
# FIXME: Empty variable
|
||||
)
|
||||
|
||||
set(OLIVE_TS_FILES
|
||||
# FIXME: Empty variable
|
||||
)
|
||||
|
||||
if(UPDATE_TS)
|
||||
qt5_create_translation(OLIVE_QM_FILES ${CMAKE_SOURCE_DIR} ${OLIVE_TS_FILES})
|
||||
else()
|
||||
qt5_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES})
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(TARGETS ${OLIVE_TARGET} RUNTIME DESTINATION bin)
|
||||
install(FILES ${OLIVE_EFFECTS} DESTINATION share/olive-editor/effects)
|
||||
install(FILES ${OLIVE_QM_FILES} DESTINATION share/olive-editor/ts)
|
||||
endif()
|
||||
|
||||
add_subdirectory(packaging)
|
||||
|
||||
# Create docs if doxygen was found
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_PROJECT_NAME "Olive")
|
||||
set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/docs")
|
||||
@@ -127,3 +93,56 @@ if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_EXTRACT_PRIVATE "YES")
|
||||
doxygen_add_docs(docs ALL ${OLIVE_SOURCES})
|
||||
endif()
|
||||
|
||||
# Platform-specific deployment preferences
|
||||
if (WIN32)
|
||||
# Set Windows application icon
|
||||
target_sources(olive-editor PRIVATE packaging/windows/resources.rc)
|
||||
|
||||
# Preserve folder structure in visual studio
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${OLIVE_SOURCES})
|
||||
|
||||
elseif(APPLE)
|
||||
# Set Mac application icon
|
||||
set(OLIVE_ICON packaging/macos/olive.icns)
|
||||
target_sources(olive-editor PRIVATE ${OLIVE_ICON})
|
||||
|
||||
# Set Mac bundle properties
|
||||
set_target_properties(olive-editor PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/MacOSXBundleInfo.plist.in
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER org.olivevideoeditor.Olive
|
||||
MACOSX_BUNDLE_ICON_FILE olive.icns
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "Olive"
|
||||
MACOSX_BUNDLE_INFO_STRING "Olive ${PROJECT_LONG_VERSION}"
|
||||
MACOSX_BUNDLE_COPYRIGHT "©2018-2021 Olive Studios LLC and others. Published under the GNU General Public License version 3.0."
|
||||
RESOURCE "${OLIVE_ICON}"
|
||||
OUTPUT_NAME "Olive"
|
||||
)
|
||||
elseif(UNIX)
|
||||
# Set Linux-specific properties for application
|
||||
install(TARGETS olive-editor RUNTIME DESTINATION bin)
|
||||
endif()
|
||||
|
||||
# Set link libraries
|
||||
target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES})
|
||||
target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES})
|
||||
|
||||
# Set compile options
|
||||
target_compile_options(olive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
target_compile_options(libolive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
|
||||
# Set global definitions
|
||||
target_compile_definitions(olive-editor PRIVATE ${OLIVE_DEFINITIONS})
|
||||
target_compile_definitions(libolive-editor PRIVATE ${OLIVE_DEFINITIONS})
|
||||
|
||||
# Set include dirs
|
||||
target_include_directories(olive-editor PRIVATE ${OLIVE_INCLUDE_DIRS})
|
||||
target_include_directories(libolive-editor PRIVATE ${OLIVE_INCLUDE_DIRS})
|
||||
|
||||
# Add crash handler
|
||||
if (GoogleCrashpad_FOUND AND Qt5Network_FOUND)
|
||||
add_subdirectory(crashhandler)
|
||||
endif()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -16,11 +16,15 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
audio/audiohybriddevice.h
|
||||
audio/audiohybriddevice.cpp
|
||||
audio/audiomanager.h
|
||||
audio/audiomanager.cpp
|
||||
audio/sampleformat.h
|
||||
audio/sampleformat.cpp
|
||||
audio/audiomanager.h
|
||||
audio/audiovisualwaveform.cpp
|
||||
audio/audiovisualwaveform.h
|
||||
audio/packedprocessor.cpp
|
||||
audio/packedprocessor.h
|
||||
audio/planarprocessor.cpp
|
||||
audio/planarprocessor.h
|
||||
audio/tempoprocessor.cpp
|
||||
audio/tempoprocessor.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "audiohybriddevice.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtMath>
|
||||
|
||||
AudioHybridDevice::AudioHybridDevice(QObject *parent) :
|
||||
QIODevice(parent),
|
||||
device_(nullptr),
|
||||
sample_index_(0),
|
||||
enable_sending_samples_(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AudioHybridDevice::Push(const QByteArray& samples)
|
||||
{
|
||||
Stop();
|
||||
|
||||
pushed_samples_ = samples;
|
||||
sample_index_ = 0;
|
||||
|
||||
if (!pushed_samples_.isEmpty()) {
|
||||
emit HasSamples();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioHybridDevice::Stop()
|
||||
{
|
||||
// Whatever is happening, stop it
|
||||
pushed_samples_.clear();
|
||||
|
||||
if (device_ != nullptr) {
|
||||
device_->close();
|
||||
device_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioHybridDevice::ConnectDevice(QIODevice *device)
|
||||
{
|
||||
// Clear any previous device or pushed sample
|
||||
Stop();
|
||||
|
||||
device_ = device;
|
||||
|
||||
if (device_ != nullptr) {
|
||||
emit HasSamples();
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioHybridDevice::IsIdle()
|
||||
{
|
||||
return device_ == nullptr && pushed_samples_.isEmpty();
|
||||
}
|
||||
|
||||
void AudioHybridDevice::SetEnableSendingSamples(bool e)
|
||||
{
|
||||
enable_sending_samples_ = e;
|
||||
}
|
||||
|
||||
qint64 AudioHybridDevice::readData(char *data, qint64 maxSize)
|
||||
{
|
||||
qint64 read_size = read_internal(data, maxSize);
|
||||
|
||||
if (enable_sending_samples_ && read_size > 0) {
|
||||
// FIXME: Assumes float and stereo
|
||||
float* samples = reinterpret_cast<float*>(data);
|
||||
int sample_count = static_cast<int>(read_size / static_cast<int>(sizeof(float)));
|
||||
int channels = 2;
|
||||
|
||||
// Create array of samples to send
|
||||
QVector<double> averages(channels);
|
||||
averages.fill(0);
|
||||
|
||||
// Add all samples together
|
||||
for (int i=0;i<sample_count;i++) {
|
||||
averages[i%channels] = qMax(averages[i%channels], static_cast<double>(qAbs(samples[i])));
|
||||
}
|
||||
|
||||
emit SentSamples(averages);
|
||||
}
|
||||
|
||||
return read_size;
|
||||
}
|
||||
|
||||
qint64 AudioHybridDevice::writeData(const char *data, qint64 maxSize)
|
||||
{
|
||||
Q_UNUSED(data)
|
||||
Q_UNUSED(maxSize)
|
||||
|
||||
// This device doesn't support writing
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
qint64 AudioHybridDevice::read_internal(char *data, qint64 maxSize)
|
||||
{
|
||||
// If a device is connected, passthrough to it
|
||||
if (device_ != nullptr) {
|
||||
qint64 read_count = device_->read(data, maxSize);
|
||||
|
||||
// Stop reading this device
|
||||
if (device_->atEnd()) {
|
||||
device_->close();
|
||||
device_ = nullptr;
|
||||
}
|
||||
|
||||
return read_count;
|
||||
}
|
||||
|
||||
// If there are samples to push, push those
|
||||
if (!pushed_samples_.isEmpty()) {
|
||||
qint64 read_count = qMin(maxSize, pushed_samples_.size() - sample_index_);
|
||||
|
||||
memcpy(data, pushed_samples_.data()+sample_index_, static_cast<size_t>(read_count));
|
||||
|
||||
sample_index_ += read_count;
|
||||
|
||||
if (sample_index_ == pushed_samples_.size()) {
|
||||
pushed_samples_.clear();
|
||||
}
|
||||
|
||||
if (read_count < maxSize) {
|
||||
memset(data + read_count, 0, static_cast<size_t>(maxSize - read_count));
|
||||
}
|
||||
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
memset(data, 0, static_cast<size_t>(maxSize));
|
||||
return maxSize;
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef AUDIOHYBRIDDEVICE_H
|
||||
#define AUDIOHYBRIDDEVICE_H
|
||||
|
||||
#include <QIODevice>
|
||||
|
||||
/**
|
||||
* @brief A device that can be connected to QAudioOutput and provides both "push" and "pull" functionality
|
||||
*
|
||||
* By default, a QAudioOutput works in either "push" or "pull" mode. Either it can automatically "pull" from a
|
||||
* QIODevice (reading from it whenever it requires samples), or we can "push" samples to it constantly (on a timer and
|
||||
* often in another thread) to make sure the buffer never underruns.
|
||||
*
|
||||
* This class functions as a "pull" device for the QAudioOutput, so the audio device automatically reads from it, but
|
||||
* then provides both "pull" and "push" functionality to the rest of the system. A QIODevice (like a raw PCM file)
|
||||
* can be connected and pulled from (good for continuous audio playback), and any amount of samples can also be pushed
|
||||
* (good for short bursts of sound, e.g. audio scrubbing).
|
||||
*
|
||||
* This does not support any "queuing", running ConnectDevice() or Push() will immediately discard anything that's
|
||||
* currently being sent to the device and replace it.
|
||||
*/
|
||||
class AudioHybridDevice : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioHybridDevice(QObject* parent = nullptr);
|
||||
|
||||
void Push(const QByteArray &samples);
|
||||
|
||||
/**
|
||||
* @brief Stop all audio output
|
||||
*
|
||||
* Whatever is being done (pulling from QIODevice or samples), it is stopped and cleared placing this into
|
||||
* "idle" state.
|
||||
*
|
||||
* Note that audio playback may not stop immediately after calling this function as the audio output may still have
|
||||
* samples in its buffer to output (QAudioOutput::stop() should be called as well for more immediate feedback). This
|
||||
* will prevent any further samples from being sent however.
|
||||
*
|
||||
* \see IsIdle()
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* @brief Connect a QIODevice (e.g. QFile) to start sending to the audio output
|
||||
*
|
||||
* This will clear any pushed samples or QIODevices currently being read and will start reading from this next time
|
||||
* the audio output requests data.
|
||||
*/
|
||||
void ConnectDevice(QIODevice* device);
|
||||
|
||||
/**
|
||||
* @brief Returns true if there are no more samples to be sent
|
||||
*
|
||||
* This is true if a device was connected or samples were pushed but we reached the end and no more data is available
|
||||
* to be sent.
|
||||
*
|
||||
* In this state, this device will continue returning an empty buffer (all zeroes) to the device to workaround
|
||||
* QAudioOutput's buffer underrun prevention with pushed samples. Therefore the QAudioOutput will never be put into
|
||||
* QAudio::IdleState. Therefore only way to determine whether the output is no longer receiving usable audio is to
|
||||
* check this function.
|
||||
*/
|
||||
bool IsIdle();
|
||||
|
||||
/**
|
||||
* @brief If enabled, this will emit the SentSamples() signal whenever samples are sent to the output device
|
||||
*/
|
||||
void SetEnableSendingSamples(bool e);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted when this leaves "idle" state and has valid audio data that is ready to be sent
|
||||
*/
|
||||
void HasSamples();
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when samples are sent to the output device
|
||||
*
|
||||
* This sends an array of values corresponding to the channel count. Each value is an average of all the samples just
|
||||
* sent to that channel. Useful for connecting to AudioMonitor.
|
||||
*
|
||||
* Can be disabled with SetEnableSendingSamples() to save CPU usage.
|
||||
*/
|
||||
void SentSamples(QVector<double> averages);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Internal QIODevice function for reading
|
||||
*
|
||||
* Returns either QIODevice::read() from the connected device, or samples from the pushed sample buffer if there is
|
||||
* none.
|
||||
*
|
||||
* If there is neither, this function returns a complete buffer of zeroes (i.e. silence). This is to allow
|
||||
* pushed samples that don't fulfill the QAudioOutput's internal buffer size. If it cannot fill its internal buffer
|
||||
* with the samples we send, it won't play anything until it receives more samples (enough to fill the buffer)
|
||||
* in an attempt to prevent buffer underrun).
|
||||
*
|
||||
* Since we don't care about buffer underrun with short bursts of sound, we return silent samples if the QAudioOutput
|
||||
* requests it. However this does mean the QAudioOutput never returns to QAudio::IdleState (since we never know if a
|
||||
* read is just to fill the "remainder" of a buffer - which needs zeroes - or just a general read - which technically
|
||||
* doesn't). It's recommended to check IsIdle() in tandem with the QAudioOutput::notify() signal to
|
||||
* determine whether the QAudioOutput is effectively idle and can be stopped.
|
||||
*/
|
||||
virtual qint64 readData(char *data, qint64 maxSize) override;
|
||||
|
||||
/**
|
||||
* @brief Internal QIODevice function for writing
|
||||
*
|
||||
* This class does not support writing, so this always returns -1 (QIODevice's documented error code for writing)
|
||||
*/
|
||||
virtual qint64 writeData(const char *data, qint64 maxSize) override;
|
||||
|
||||
private:
|
||||
qint64 read_internal(char *data, qint64 maxSize);
|
||||
|
||||
QIODevice* device_;
|
||||
|
||||
QByteArray pushed_samples_;
|
||||
qint64 sample_index_;
|
||||
|
||||
bool enable_sending_samples_;
|
||||
|
||||
};
|
||||
|
||||
#endif // AUDIOHYBRIDDEVICE_H
|
||||
+149
-181
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -20,8 +20,16 @@
|
||||
|
||||
#include "audiomanager.h"
|
||||
|
||||
#ifdef PA_HAS_JACK
|
||||
#include <pa_jack.h>
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "config/config.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
AudioManager* AudioManager::instance_ = nullptr;
|
||||
|
||||
void AudioManager::CreateInstance()
|
||||
@@ -42,223 +50,183 @@ AudioManager *AudioManager::instance()
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void AudioManager::RefreshDevices()
|
||||
void AudioManager::SetOutputNotifyInterval(int n)
|
||||
{
|
||||
if (refreshing_devices_) {
|
||||
output_buffer_->set_notify_interval(n);
|
||||
}
|
||||
|
||||
int OutputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
|
||||
{
|
||||
PreviewAudioDevice *device = static_cast<PreviewAudioDevice*>(userData);
|
||||
|
||||
qint64 max_read = frameCount * device->bytes_per_frame();
|
||||
qint64 read_count = device->read(reinterpret_cast<char*>(output), max_read);
|
||||
if (read_count < max_read) {
|
||||
memset(reinterpret_cast<uint8_t*>(output) + read_count, 0, max_read - read_count);
|
||||
}
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples)
|
||||
{
|
||||
if (output_device_ == paNoDevice) {
|
||||
return;
|
||||
}
|
||||
|
||||
input_devices_.clear();
|
||||
output_devices_.clear();
|
||||
if (output_params_ != params || output_stream_ == nullptr) {
|
||||
output_params_ = params;
|
||||
|
||||
refreshing_devices_ = true;
|
||||
CloseOutputStream();
|
||||
|
||||
refresh_thread_.start(QThread::LowPriority);
|
||||
}
|
||||
PaStreamParameters p;
|
||||
|
||||
bool AudioManager::IsRefreshing()
|
||||
{
|
||||
return refreshing_devices_;
|
||||
}
|
||||
p.channelCount = output_params_.channel_count();
|
||||
p.device = output_device_;
|
||||
p.hostApiSpecificStreamInfo = nullptr;
|
||||
p.sampleFormat = GetPortAudioSampleFormat(output_params_.format());
|
||||
p.suggestedLatency = Pa_GetDeviceInfo(output_device_)->defaultLowOutputLatency;
|
||||
|
||||
void AudioManager::PushToOutput(const QByteArray &samples)
|
||||
{
|
||||
output_manager_.Push(samples);
|
||||
}
|
||||
Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_);
|
||||
|
||||
void AudioManager::StartOutput(QIODevice *device)
|
||||
{
|
||||
if (output_ == nullptr) {
|
||||
return;
|
||||
output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1));
|
||||
}
|
||||
|
||||
output_manager_.ConnectDevice(device);
|
||||
output_buffer_->write(samples);
|
||||
|
||||
if (!Pa_IsStreamActive(output_stream_)) {
|
||||
Pa_StartStream(output_stream_);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::ClearBufferedOutput()
|
||||
{
|
||||
output_buffer_->clear();
|
||||
}
|
||||
|
||||
PaSampleFormat AudioManager::GetPortAudioSampleFormat(AudioParams::Format fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
case AudioParams::kFormatUnsigned8:
|
||||
return paUInt8;
|
||||
case AudioParams::kFormatSigned16:
|
||||
return paInt16;
|
||||
case AudioParams::kFormatSigned32:
|
||||
return paInt32;
|
||||
case AudioParams::kFormatFloat32:
|
||||
return paFloat32;
|
||||
case AudioParams::kFormatSigned64:
|
||||
case AudioParams::kFormatFloat64:
|
||||
case AudioParams::kFormatInvalid:
|
||||
case AudioParams::kFormatCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioManager::CloseOutputStream()
|
||||
{
|
||||
if (output_stream_) {
|
||||
if (Pa_IsStreamActive(output_stream_)) {
|
||||
StopOutput();
|
||||
}
|
||||
Pa_CloseStream(output_stream_);
|
||||
output_stream_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::StopOutput()
|
||||
{
|
||||
output_manager_.Stop();
|
||||
|
||||
if (output_ != nullptr) {
|
||||
output_->stop();
|
||||
// Abort the stream so playback stops immediately
|
||||
if (output_stream_) {
|
||||
Pa_AbortStream(output_stream_);
|
||||
ClearBufferedOutput();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info)
|
||||
void AudioManager::SetOutputDevice(PaDeviceIndex device)
|
||||
{
|
||||
qInfo() << "Setting output audio device to" << info.deviceName();
|
||||
if (device == paNoDevice) {
|
||||
qInfo() << "No output device found";
|
||||
} else {
|
||||
qInfo() << "Setting output audio device to" << Pa_GetDeviceInfo(device)->name;
|
||||
}
|
||||
|
||||
StopOutput();
|
||||
output_device_ = device;
|
||||
|
||||
output_device_info_ = info;
|
||||
CloseOutputStream();
|
||||
}
|
||||
|
||||
if (output_params_.is_valid()) {
|
||||
QAudioFormat format;
|
||||
format.setSampleRate(output_params_.sample_rate());
|
||||
format.setChannelCount(output_params_.channel_count());
|
||||
format.setCodec("audio/pcm");
|
||||
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||
void AudioManager::SetInputDevice(PaDeviceIndex device)
|
||||
{
|
||||
if (device == paNoDevice) {
|
||||
qInfo() << "No input device found";
|
||||
} else {
|
||||
qInfo() << "Setting input audio device to" << Pa_GetDeviceInfo(device)->name;
|
||||
}
|
||||
|
||||
switch (output_params_.format()) {
|
||||
case SAMPLE_FMT_U8:
|
||||
format.setSampleSize(8);
|
||||
format.setSampleType(QAudioFormat::UnSignedInt);
|
||||
break;
|
||||
case SAMPLE_FMT_S16:
|
||||
format.setSampleSize(16);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
break;
|
||||
case SAMPLE_FMT_S32:
|
||||
format.setSampleSize(32);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
break;
|
||||
case SAMPLE_FMT_S64:
|
||||
format.setSampleSize(64);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
break;
|
||||
case SAMPLE_FMT_FLT:
|
||||
format.setSampleSize(32);
|
||||
format.setSampleType(QAudioFormat::Float);
|
||||
break;
|
||||
case SAMPLE_FMT_DBL:
|
||||
format.setSampleSize(64);
|
||||
format.setSampleType(QAudioFormat::Float);
|
||||
break;
|
||||
case SAMPLE_FMT_COUNT:
|
||||
case SAMPLE_FMT_INVALID:
|
||||
abort();
|
||||
input_device_ = device;
|
||||
}
|
||||
|
||||
void AudioManager::HardReset()
|
||||
{
|
||||
CloseOutputStream();
|
||||
Pa_Terminate();
|
||||
Pa_Initialize();
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
|
||||
{
|
||||
QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput");
|
||||
|
||||
return FindDeviceByName(Config::Current()[entry].toString(), is_output_device);
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_device)
|
||||
{
|
||||
if (!s.isEmpty()) {
|
||||
for (PaDeviceIndex i=0, end=Pa_GetDeviceCount(); i<end; i++) {
|
||||
const PaDeviceInfo *device = Pa_GetDeviceInfo(i);
|
||||
|
||||
if (((is_output_device && device->maxOutputChannels) || (!is_output_device && device->maxInputChannels))
|
||||
&& !s.compare(device->name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
output_ = std::unique_ptr<QAudioOutput>(new QAudioOutput(info, format, this));
|
||||
connect(output_.get(), SIGNAL(notify()), this, SLOT(OutputNotified()));
|
||||
}
|
||||
|
||||
// Un-comment this to get debug information about what the audio output is doing
|
||||
//connect(output_.get(), SIGNAL(stateChanged(QAudio::State)), this, SLOT(OutputStateChanged(QAudio::State)));
|
||||
}
|
||||
|
||||
void AudioManager::SetOutputParams(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
if (output_params_ != params) {
|
||||
output_params_ = params;
|
||||
|
||||
// Refresh output device
|
||||
SetOutputDevice(output_device_info_);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::SetInputDevice(const QAudioDeviceInfo &info)
|
||||
{
|
||||
input_ = std::unique_ptr<QAudioInput>(new QAudioInput(info, QAudioFormat(), this));
|
||||
input_device_info_ = info;
|
||||
}
|
||||
|
||||
const QList<QAudioDeviceInfo> &AudioManager::ListInputDevices()
|
||||
{
|
||||
return input_devices_;
|
||||
}
|
||||
|
||||
const QList<QAudioDeviceInfo> &AudioManager::ListOutputDevices()
|
||||
{
|
||||
return output_devices_;
|
||||
return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice();
|
||||
}
|
||||
|
||||
AudioManager::AudioManager() :
|
||||
output_(nullptr),
|
||||
input_(nullptr),
|
||||
input_file_(nullptr),
|
||||
refreshing_devices_(false)
|
||||
output_stream_(nullptr)
|
||||
{
|
||||
connect(&refresh_thread_, SIGNAL(ListsReady()), this, SLOT(RefreshThreadDone()));
|
||||
#ifdef PA_HAS_JACK
|
||||
// PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not
|
||||
// a QString converted to UTF-8)
|
||||
PaJack_SetClientName("Olive");
|
||||
#endif
|
||||
|
||||
RefreshDevices();
|
||||
Pa_Initialize();
|
||||
|
||||
connect(&output_manager_, SIGNAL(HasSamples()), this, SLOT(OutputManagerHasSamples()));
|
||||
connect(&output_manager_, SIGNAL(SentSamples(QVector<double>)), this, SIGNAL(SentSamples(QVector<double>)));
|
||||
// Get device from config
|
||||
PaDeviceIndex output_device = FindConfigDeviceByName(true);
|
||||
PaDeviceIndex input_device = FindConfigDeviceByName(false);
|
||||
|
||||
output_manager_.SetEnableSendingSamples(true);
|
||||
output_manager_.open(AudioHybridDevice::ReadOnly);
|
||||
SetOutputDevice(output_device);
|
||||
SetInputDevice(input_device);
|
||||
|
||||
output_buffer_ = new PreviewAudioDevice(this);
|
||||
output_buffer_->open(PreviewAudioDevice::ReadWrite);
|
||||
connect(output_buffer_, &PreviewAudioDevice::Notify, this, &AudioManager::OutputNotify);
|
||||
}
|
||||
|
||||
void AudioManager::RefreshThreadDone()
|
||||
AudioManager::~AudioManager()
|
||||
{
|
||||
output_devices_ = refresh_thread_.output_devices();
|
||||
input_devices_ = refresh_thread_.input_devices();
|
||||
CloseOutputStream();
|
||||
|
||||
refreshing_devices_ = false;
|
||||
|
||||
QString preferred_audio_output = Config::Current()["PreferredAudioOutput"].toString();
|
||||
|
||||
if (output_ == nullptr
|
||||
|| (!preferred_audio_output.isEmpty() && output_device_info_.deviceName() != preferred_audio_output)) {
|
||||
if (preferred_audio_output.isEmpty()) {
|
||||
SetOutputDevice(QAudioDeviceInfo::defaultOutputDevice());
|
||||
} else {
|
||||
foreach (const QAudioDeviceInfo& info, output_devices_) {
|
||||
if (info.deviceName() == preferred_audio_output) {
|
||||
SetOutputDevice(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString preferred_audio_input = Config::Current()["PreferredAudioInput"].toString();
|
||||
|
||||
if (input_ == nullptr
|
||||
|| (!preferred_audio_input.isEmpty() && input_device_info_.deviceName() != preferred_audio_input)) {
|
||||
if (preferred_audio_input.isEmpty()) {
|
||||
SetInputDevice(QAudioDeviceInfo::defaultInputDevice());
|
||||
} else {
|
||||
foreach (const QAudioDeviceInfo& info, input_devices_) {
|
||||
if (info.deviceName() == preferred_audio_input) {
|
||||
SetInputDevice(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit DeviceListReady();
|
||||
Pa_Terminate();
|
||||
}
|
||||
|
||||
void AudioManager::OutputManagerHasSamples()
|
||||
{
|
||||
if (output_ != nullptr && output_->state() != QAudio::ActiveState) {
|
||||
output_->start(&output_manager_);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::OutputStateChanged(QAudio::State state)
|
||||
{
|
||||
qDebug() << state;
|
||||
}
|
||||
|
||||
void AudioManager::OutputNotified()
|
||||
{
|
||||
if (output_manager_.IsIdle()) {
|
||||
static_cast<QAudioOutput*>(sender())->stop();
|
||||
}
|
||||
}
|
||||
|
||||
AudioRefreshDevicesThread::AudioRefreshDevicesThread()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioRefreshDevicesThread::run()
|
||||
{
|
||||
output_devices_ = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
|
||||
input_devices_ = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
|
||||
|
||||
emit ListsReady();
|
||||
}
|
||||
|
||||
const QList<QAudioDeviceInfo>& AudioRefreshDevicesThread::input_devices()
|
||||
{
|
||||
return input_devices_;
|
||||
}
|
||||
|
||||
const QList<QAudioDeviceInfo>& AudioRefreshDevicesThread::output_devices()
|
||||
{
|
||||
return output_devices_;
|
||||
}
|
||||
|
||||
+38
-70
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -22,35 +22,17 @@
|
||||
#define AUDIOMANAGER_H
|
||||
|
||||
#include <memory>
|
||||
#include <QAudioInput>
|
||||
#include <QAudioOutput>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QThread>
|
||||
#include <portaudio.h>
|
||||
|
||||
#include "audiohybriddevice.h"
|
||||
#include "audiovisualwaveform.h"
|
||||
#include "common/define.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/audioplaybackcache.h"
|
||||
#include "render/previewaudiodevice.h"
|
||||
|
||||
/**
|
||||
* @brief A thread for refreshing the total list of devices on the system
|
||||
*
|
||||
* Refreshing devices causes a noticeable pause in execution. Doing it another thread is intended to avoid this.
|
||||
*/
|
||||
class AudioRefreshDevicesThread : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioRefreshDevicesThread();
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
const QList<QAudioDeviceInfo>& input_devices();
|
||||
const QList<QAudioDeviceInfo>& output_devices();
|
||||
|
||||
signals:
|
||||
void ListsReady();
|
||||
|
||||
private:
|
||||
QList<QAudioDeviceInfo> input_devices_;
|
||||
QList<QAudioDeviceInfo> output_devices_;
|
||||
};
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief Audio input and output management class
|
||||
@@ -67,70 +49,56 @@ public:
|
||||
|
||||
static AudioManager* instance();
|
||||
|
||||
void RefreshDevices();
|
||||
void SetOutputNotifyInterval(int n);
|
||||
|
||||
bool IsRefreshing();
|
||||
void PushToOutput(const AudioParams ¶ms, const QByteArray& samples);
|
||||
|
||||
void PushToOutput(const QByteArray& samples);
|
||||
void ClearBufferedOutput();
|
||||
|
||||
/**
|
||||
* @brief Start playing audio from QIODevice
|
||||
*
|
||||
* This takes ownership of the QIODevice and will delete it when StopOutput() is called
|
||||
*/
|
||||
void StartOutput(QIODevice* device);
|
||||
|
||||
/**
|
||||
* @brief Stop audio output immediately
|
||||
*/
|
||||
void StopOutput();
|
||||
|
||||
void SetOutputDevice(const QAudioDeviceInfo& info);
|
||||
PaDeviceIndex GetOutputDevice() const
|
||||
{
|
||||
return output_device_;
|
||||
}
|
||||
|
||||
void SetOutputParams(const AudioRenderingParams& params);
|
||||
PaDeviceIndex GetInputDevice() const
|
||||
{
|
||||
return input_device_;
|
||||
}
|
||||
|
||||
void SetInputDevice(const QAudioDeviceInfo& info);
|
||||
void SetOutputDevice(PaDeviceIndex device);
|
||||
|
||||
const QList<QAudioDeviceInfo>& ListInputDevices();
|
||||
const QList<QAudioDeviceInfo>& ListOutputDevices();
|
||||
void SetInputDevice(PaDeviceIndex device);
|
||||
|
||||
void HardReset();
|
||||
|
||||
static PaDeviceIndex FindConfigDeviceByName(bool is_output_device);
|
||||
static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device);
|
||||
|
||||
signals:
|
||||
void DeviceListReady();
|
||||
|
||||
void SentSamples(QVector<double> averages);
|
||||
void OutputNotify();
|
||||
|
||||
private:
|
||||
AudioManager();
|
||||
|
||||
QList<QAudioDeviceInfo> input_devices_;
|
||||
virtual ~AudioManager() override;
|
||||
|
||||
QList<QAudioDeviceInfo> output_devices_;
|
||||
static PaSampleFormat GetPortAudioSampleFormat(AudioParams::Format fmt);
|
||||
|
||||
void CloseOutputStream();
|
||||
|
||||
static AudioManager* instance_;
|
||||
|
||||
AudioHybridDevice output_manager_;
|
||||
PaDeviceIndex output_device_;
|
||||
PaStream *output_stream_;
|
||||
AudioParams output_params_;
|
||||
PreviewAudioDevice *output_buffer_;
|
||||
|
||||
std::unique_ptr<QAudioOutput> output_;
|
||||
QAudioDeviceInfo output_device_info_;
|
||||
AudioRenderingParams output_params_;
|
||||
|
||||
std::unique_ptr<QAudioInput> input_;
|
||||
QAudioDeviceInfo input_device_info_;
|
||||
QIODevice* input_file_;
|
||||
|
||||
bool refreshing_devices_;
|
||||
|
||||
AudioRefreshDevicesThread refresh_thread_;
|
||||
|
||||
private slots:
|
||||
void RefreshThreadDone();
|
||||
|
||||
void OutputManagerHasSamples();
|
||||
|
||||
void OutputStateChanged(QAudio::State state);
|
||||
|
||||
void OutputNotified();
|
||||
PaDeviceIndex input_device_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // AUDIOMANAGER_H
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "audiovisualwaveform.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "config/config.h"
|
||||
#include "common/cpuoptimize.h"
|
||||
#include "common/functiontimer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const rational AudioVisualWaveform::kMinimumSampleRate = rational(1, 8);
|
||||
const rational AudioVisualWaveform::kMaximumSampleRate = 1024;
|
||||
|
||||
AudioVisualWaveform::AudioVisualWaveform() :
|
||||
channels_(0)
|
||||
{
|
||||
for (rational i=kMinimumSampleRate; i<=kMaximumSampleRate; i*=2) {
|
||||
mipmapped_data_.insert({i, Sample()});
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational &start, double target_rate, Sample& data, int &start_index, int &samples_length)
|
||||
{
|
||||
start_index = time_to_samples(start, target_rate);
|
||||
samples_length = time_to_samples(static_cast<double>(samples->sample_count()) / static_cast<double>(sample_rate), target_rate);
|
||||
|
||||
int end_index = start_index + samples_length;
|
||||
if (data.size() < end_index) {
|
||||
data.resize(end_index);
|
||||
}
|
||||
|
||||
double chunk_size = double(sample_rate) / double(target_rate);
|
||||
|
||||
for (int i=0; i<samples_length; i+=channels_) {
|
||||
int src_start = qRound((double(i) * chunk_size)) / channels_;
|
||||
int src_end = qMin(qRound((double(i + channels_) * chunk_size)) / channels_, samples->sample_count());
|
||||
|
||||
Sample summary = SumSamples(samples,
|
||||
src_start,
|
||||
src_end - src_start);
|
||||
|
||||
memcpy(&data.data()[i + start_index],
|
||||
summary.constData(),
|
||||
summary.size() * sizeof(SamplePerChannel));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSamplesFromMipmap(const AudioVisualWaveform::Sample &input, double input_sample_rate, int &input_start, int &input_length, const rational &start, double output_rate, AudioVisualWaveform::Sample &output_data)
|
||||
{
|
||||
int start_index = time_to_samples(start, output_rate);
|
||||
int samples_length = time_to_samples(static_cast<double>(input_length / channels_) / input_sample_rate, output_rate);
|
||||
|
||||
int end_index = start_index + samples_length;
|
||||
if (output_data.size() < end_index) {
|
||||
output_data.resize(end_index);
|
||||
}
|
||||
|
||||
// We guarantee mipmaps are powers of two so integer division should be perfectly accurate here
|
||||
int chunk_size = input_sample_rate / output_rate;
|
||||
|
||||
for (int i=0; i<samples_length; i+=channels_) {
|
||||
Sample summary = ReSumSamples(&input.constData()[input_start + (i*chunk_size)], chunk_size * channels_, channels_);
|
||||
|
||||
memcpy(&output_data.data()[i + start_index],
|
||||
summary.constData(),
|
||||
summary.size() * sizeof(SamplePerChannel));
|
||||
}
|
||||
|
||||
input_start = start_index;
|
||||
input_length = samples_length;
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational &start)
|
||||
{
|
||||
if (!channels_) {
|
||||
qWarning() << "Failed to write samples - channel count is zero";
|
||||
return;
|
||||
}
|
||||
|
||||
// Old less optimized code. Keeping this around as a reference, but the below code is at least
|
||||
// 10x faster so this shouldn't be used in production.
|
||||
//
|
||||
// int input_start, input_length;
|
||||
// for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
// OverwriteSamplesFromBuffer(samples, sample_rate, start, it->first.toDouble(), it->second, input_start, input_length);
|
||||
// }
|
||||
|
||||
// Process the largest mipmap directly for the samples
|
||||
auto current_mipmap = mipmapped_data_.rbegin();
|
||||
int input_start, input_length;
|
||||
OverwriteSamplesFromBuffer(samples, sample_rate, start, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length);
|
||||
|
||||
while (true) {
|
||||
// For each smaller mipmap, we just process from the mipmap before it, making each one
|
||||
// exponentially faster to create
|
||||
auto previous_mipmap = current_mipmap;
|
||||
current_mipmap++;
|
||||
if (current_mipmap == mipmapped_data_.rend()) {
|
||||
break;
|
||||
}
|
||||
|
||||
OverwriteSamplesFromMipmap(previous_mipmap->second, previous_mipmap->first.toDouble(),
|
||||
input_start, input_length, start, current_mipmap->first.toDouble(),
|
||||
current_mipmap->second);
|
||||
}
|
||||
|
||||
rational sample_length(samples->sample_count(), sample_rate);
|
||||
length_ = qMax(length_, start + sample_length);
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const rational &dest, const rational& offset, const rational& length)
|
||||
{
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
|
||||
Sample& our_arr = it->second;
|
||||
const Sample& their_arr = sums.mipmapped_data_.at(rate);
|
||||
|
||||
double rate_dbl = rate.toDouble();
|
||||
|
||||
// Get our destination sample
|
||||
int our_start_index = time_to_samples(dest, rate_dbl);
|
||||
|
||||
// Get our source sample
|
||||
int their_start_index = time_to_samples(offset, rate_dbl);
|
||||
if (their_start_index >= their_arr.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine how much we're copying
|
||||
int copy_len = their_arr.size() - their_start_index;
|
||||
if (!length.isNull()) {
|
||||
copy_len = qMin(copy_len, time_to_samples(length, rate_dbl));
|
||||
}
|
||||
|
||||
// Determine end index of our array
|
||||
int end_index = our_start_index + copy_len;
|
||||
if (our_arr.size() < end_index) {
|
||||
our_arr.resize(end_index);
|
||||
}
|
||||
|
||||
memcpy(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel),
|
||||
reinterpret_cast<const char*>(their_arr.constData()) + their_start_index * sizeof(SamplePerChannel),
|
||||
copy_len * sizeof(SamplePerChannel));
|
||||
}
|
||||
|
||||
length_ = qMax(length_, dest + ((length.isNull()) ? sums.length() - offset : length));
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length)
|
||||
{
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
|
||||
Sample& our_arr = it->second;
|
||||
|
||||
double rate_dbl = rate.toDouble();
|
||||
|
||||
// Get our destination sample
|
||||
int our_start_index = time_to_samples(start, rate_dbl);
|
||||
int our_length_index = time_to_samples(length, rate_dbl);
|
||||
int our_end_index = our_start_index + our_length_index;
|
||||
|
||||
if (our_arr.size() < our_end_index) {
|
||||
our_arr.resize(our_end_index);
|
||||
}
|
||||
|
||||
memset(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), 0, our_length_index * sizeof(SamplePerChannel));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
||||
{
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
Sample& data = it->second;
|
||||
|
||||
int from_index = time_to_samples(from, rate_dbl);
|
||||
int to_index = time_to_samples(to, rate_dbl);
|
||||
|
||||
if (from_index == to_index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (from_index >= data.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (from_index > to_index) {
|
||||
// Shifting backwards <-
|
||||
data.remove(to_index, from_index - to_index);
|
||||
} else {
|
||||
// Shifting forwards ->
|
||||
data.insert(from_index, to_index - from_index, {0, 0});
|
||||
}
|
||||
}
|
||||
|
||||
length_ = qMax(rational(0), length_ + (to-from));
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::TrimIn(const rational &length)
|
||||
{
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
Sample& data = it->second;
|
||||
|
||||
int chop_length = time_to_samples(length, rate_dbl);
|
||||
|
||||
if (chop_length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chop_length > 0) {
|
||||
data = data.mid(chop_length);
|
||||
} else {
|
||||
data.insert(0, -chop_length, SamplePerChannel());
|
||||
}
|
||||
}
|
||||
|
||||
length_ = qMax(rational(0), length_ - length);
|
||||
}
|
||||
|
||||
AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const
|
||||
{
|
||||
AudioVisualWaveform mid = *this;
|
||||
|
||||
mid.TrimIn(offset);
|
||||
|
||||
return mid;
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const rational &start, const rational &length) const
|
||||
{
|
||||
// Find mipmap that requires
|
||||
auto using_mipmap = GetMipmapForScale(length.flipped().toDouble());
|
||||
|
||||
double rate_dbl = using_mipmap->first.toDouble();
|
||||
|
||||
int start_sample = time_to_samples(start, rate_dbl);
|
||||
int sample_length = time_to_samples(length, rate_dbl);
|
||||
|
||||
const QVector<AudioVisualWaveform::SamplePerChannel> &mipmap_data = using_mipmap->second;
|
||||
|
||||
// Determine if the array actually has this sample
|
||||
sample_length = qMin(sample_length, mipmap_data.size() - start_sample);
|
||||
|
||||
// Based on the above `min`, if sample length <= 0, that means start_sample >= the size of the
|
||||
// array and nothing can be returned.
|
||||
if (sample_length > 0) {
|
||||
return ReSumSamples(&mipmap_data.constData()[start_sample], sample_length, channels_);
|
||||
}
|
||||
|
||||
// Return null samples
|
||||
return AudioVisualWaveform::Sample(channel_count(), {0, 0});
|
||||
}
|
||||
|
||||
void ExpandMinMaxChannel(float *a, int start, int length, float &min_val, float &max_val)
|
||||
{
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
// SSE optimized
|
||||
|
||||
// load the first 4 elements of 'a' into min and max (they are 4 * 32 = 128 bits)
|
||||
__m128 max = _mm_loadu_ps(a + start);
|
||||
__m128 min = _mm_loadu_ps(a + start);
|
||||
|
||||
// loop over 'a' and compare current elements with min and max 4 by 4.
|
||||
// we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4
|
||||
for(int i = 4; i < length-4; i+=4) {
|
||||
__m128 cur = _mm_loadu_ps(a + start + i);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
}
|
||||
// so we read the last 4 (or less) elements in a safe manner.
|
||||
__m128 cur = _mm_loadu_ps(a + length - 4);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
// this potentially overlaps up to the last 3 elements but it's not an issue.
|
||||
|
||||
// min and max will contain 4 min and max. To get the absolute min and max
|
||||
// we need to compare the 4 values over themselves by shuffling each time.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93));
|
||||
min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93));
|
||||
}
|
||||
// now min and max contain 4 identical items each representing min and max value respectively.
|
||||
|
||||
// and we store the first one into a float variable.
|
||||
_mm_store_ss(&max_val, max);
|
||||
_mm_store_ss(&min_val, min);
|
||||
// I bet you don't find annotated low level code very often.
|
||||
#else
|
||||
// Standard unoptimized function
|
||||
int end = start + length;
|
||||
for (int i=start; i<end; i++) {
|
||||
min_val = std::min(min_val, a[i]);
|
||||
max_val = std::max(max_val, a[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samples, int start_index, int length)
|
||||
{
|
||||
int channels = samples->audio_params().channel_count();
|
||||
AudioVisualWaveform::Sample summed_samples(channels);
|
||||
|
||||
for (int channel=0; channel<samples->audio_params().channel_count(); channel++) {
|
||||
ExpandMinMaxChannel(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max);
|
||||
}
|
||||
|
||||
// for reference: this approximation is n x faster (and less accurate) for a n-tracks clip
|
||||
// for (int i=start_index; i<end_index; i++) {
|
||||
// ExpandMinMax(summed_samples[i%channels], samples->data(i%channels)[i]);
|
||||
// }
|
||||
|
||||
return summed_samples;
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::ReSumSamples(const SamplePerChannel* samples,
|
||||
int nb_samples,
|
||||
int nb_channels)
|
||||
{
|
||||
AudioVisualWaveform::Sample summed_samples(nb_channels);
|
||||
|
||||
for (int i=0;i<nb_samples;i+=nb_channels) {
|
||||
for (int j=0;j<nb_channels;j++) {
|
||||
const AudioVisualWaveform::SamplePerChannel& sample = samples[i + j];
|
||||
|
||||
if (sample.min < summed_samples[j].min) {
|
||||
summed_samples[j].min = sample.min;
|
||||
}
|
||||
|
||||
if (sample.max > summed_samples[j].max) {
|
||||
summed_samples[j].max = sample.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return summed_samples;
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::DrawSample(QPainter *painter, const Sample& sample, int x, int y, int height, bool rectified)
|
||||
{
|
||||
if (sample.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int channel_height = height / sample.size();
|
||||
int channel_half_height = channel_height / 2;
|
||||
|
||||
for (int i=0;i<sample.size();i++) {
|
||||
float max = qMin(sample.at(i).max, 1.0f);
|
||||
float min = qMax(sample.at(i).min, -1.0f);
|
||||
|
||||
if (rectified) {
|
||||
int channel_bottom = y + channel_height * (i + 1);
|
||||
|
||||
int diff = qRound((max - min) * channel_half_height);
|
||||
|
||||
painter->drawLine(x,
|
||||
channel_bottom - diff,
|
||||
x,
|
||||
channel_bottom);
|
||||
} else {
|
||||
int channel_mid = y + channel_height * i + channel_half_height;
|
||||
|
||||
// We subtract the sample so that positive Y values go up on the screen rather than down,
|
||||
// which is how waveforms are usually rendered
|
||||
painter->drawLine(x,
|
||||
channel_mid - qRound(min * static_cast<float>(channel_half_height)),
|
||||
x,
|
||||
channel_mid - qRound(max * static_cast<float>(channel_half_height)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const AudioVisualWaveform &samples, const rational& start_time)
|
||||
{
|
||||
if (samples.mipmapped_data_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto using_mipmap = samples.GetMipmapForScale(scale);
|
||||
|
||||
rational rate = using_mipmap->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
const Sample& arr = using_mipmap->second;
|
||||
|
||||
int start_sample_index = samples.time_to_samples(start_time, rate_dbl);
|
||||
|
||||
if (start_sample_index >= arr.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int next_sample_index = start_sample_index;
|
||||
int sample_index;
|
||||
|
||||
Sample summary;
|
||||
int summary_index = -1;
|
||||
|
||||
const QRect& viewport = painter->viewport();
|
||||
QPoint top_left = painter->transform().map(viewport.topLeft());
|
||||
|
||||
int start = qMax(rect.x(), -top_left.x());
|
||||
int end = qMin(rect.right(), -top_left.x() + viewport.width());
|
||||
|
||||
bool rectified = Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool();
|
||||
|
||||
for (int i=start;i<end;i++) {
|
||||
sample_index = next_sample_index;
|
||||
|
||||
if (sample_index == arr.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
next_sample_index = qMin(arr.size(),
|
||||
start_sample_index + qFloor(rate_dbl * static_cast<double>(i - rect.x() + 1) / scale) * samples.channel_count());
|
||||
|
||||
if (summary_index != sample_index) {
|
||||
summary = AudioVisualWaveform::ReSumSamples(&arr.at(sample_index),
|
||||
qMax(samples.channel_count(), next_sample_index - sample_index),
|
||||
samples.channel_count());
|
||||
summary_index = sample_index;
|
||||
}
|
||||
|
||||
DrawSample(painter, summary, i, rect.y(), rect.height(), rectified);
|
||||
}
|
||||
}
|
||||
|
||||
int AudioVisualWaveform::time_to_samples(const rational &time, double sample_rate) const
|
||||
{
|
||||
return time_to_samples(time.toDouble(), sample_rate);
|
||||
}
|
||||
|
||||
int AudioVisualWaveform::time_to_samples(const double &time, double sample_rate) const
|
||||
{
|
||||
return qFloor(time * sample_rate) * channels_;
|
||||
}
|
||||
|
||||
std::map<rational, AudioVisualWaveform::Sample>::const_iterator AudioVisualWaveform::GetMipmapForScale(double scale) const
|
||||
{
|
||||
// Find largest mipmap for this scale (or the largest if we don't find one sufficient)
|
||||
for (auto it=mipmapped_data_.cbegin(); it!=mipmapped_data_.cend(); it++) {
|
||||
if (it->first.toDouble() >= scale) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have a mipmap large enough for this scale, so just return the largest we have
|
||||
return std::prev(mipmapped_data_.cend());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef SUMSAMPLES_H
|
||||
#define SUMSAMPLES_H
|
||||
|
||||
#include <QPainter>
|
||||
#include <QVector>
|
||||
|
||||
#include "codec/samplebuffer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief A buffer of data used to store a visual representation of audio
|
||||
*
|
||||
* This differs from a SampleBuffer as the data in an AudioVisualWaveform has been reduced
|
||||
* significantly and optimized for visual display.
|
||||
*/
|
||||
class AudioVisualWaveform {
|
||||
public:
|
||||
AudioVisualWaveform();
|
||||
|
||||
struct SamplePerChannel {
|
||||
float min;
|
||||
float max;
|
||||
};
|
||||
|
||||
using Sample = QVector<SamplePerChannel>;
|
||||
|
||||
int channel_count() const
|
||||
{
|
||||
return channels_;
|
||||
}
|
||||
|
||||
void set_channel_count(int channels)
|
||||
{
|
||||
channels_ = channels;
|
||||
}
|
||||
|
||||
const rational& length() const
|
||||
{
|
||||
return length_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes samples into the visual waveform buffer
|
||||
*
|
||||
* Starting at `start`, writes samples over anything in the buffer, expanding it if necessary.
|
||||
*/
|
||||
void OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational& start = 0);
|
||||
|
||||
/**
|
||||
* @brief Replaces sums at a certain range in this visual waveform
|
||||
*
|
||||
* @param sums
|
||||
*
|
||||
* The sums to write over our current ones with.
|
||||
*
|
||||
* @param dest
|
||||
*
|
||||
* Where in this visual waveform these sums should START being written to.
|
||||
*
|
||||
* @param offset
|
||||
*
|
||||
* Where in the `sums` parameter this should start reading from. Defaults to 0.
|
||||
*
|
||||
* @param length
|
||||
*
|
||||
* Maximum length of `sums` to overwrite with.
|
||||
*/
|
||||
void OverwriteSums(const AudioVisualWaveform& sums, const rational& dest, const rational& offset = 0, const rational &length = 0);
|
||||
|
||||
void OverwriteSilence(const rational &start, const rational &length);
|
||||
|
||||
void Shift(const rational& from, const rational& to);
|
||||
|
||||
void TrimIn(const rational &length);
|
||||
|
||||
AudioVisualWaveform Mid(const rational &offset) const;
|
||||
|
||||
Sample GetSummaryFromTime(const rational& start, const rational& length) const;
|
||||
|
||||
static Sample SumSamples(SampleBufferPtr samples, int start_index, int length);
|
||||
|
||||
static Sample ReSumSamples(const SamplePerChannel *samples, int nb_samples, int nb_channels);
|
||||
|
||||
static void DrawSample(QPainter* painter, const Sample &sample, int x, int y, int height, bool rectified);
|
||||
|
||||
static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const AudioVisualWaveform& samples, const rational &start_time);
|
||||
|
||||
// Must be a power of 2
|
||||
static const rational kMinimumSampleRate;
|
||||
static const rational kMaximumSampleRate;
|
||||
|
||||
private:
|
||||
void OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational& start, double target_rate, Sample &data, int &start_index, int &samples_length);
|
||||
|
||||
void OverwriteSamplesFromMipmap(const Sample& input, double input_sample_rate, int &input_start, int &input_length, const rational& start, double output_rate, Sample &output_data);
|
||||
|
||||
int time_to_samples(const rational& time, double sample_rate) const;
|
||||
int time_to_samples(const double& time, double sample_rate) const;
|
||||
|
||||
std::map<rational, Sample>::const_iterator GetMipmapForScale(double scale) const;
|
||||
|
||||
int channels_;
|
||||
|
||||
std::map<rational, Sample> mipmapped_data_;
|
||||
|
||||
rational length_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::AudioVisualWaveform)
|
||||
|
||||
#endif // SUMSAMPLES_H
|
||||
@@ -0,0 +1,101 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "packedprocessor.h"
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
PackedProcessor::PackedProcessor() :
|
||||
swr_ctx_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
PackedProcessor::~PackedProcessor()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool PackedProcessor::Open(const AudioParams ¶ms)
|
||||
{
|
||||
if (IsOpen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
swr_ctx_ = swr_alloc_set_opts(nullptr,
|
||||
params.channel_layout(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
|
||||
params.sample_rate(),
|
||||
params.channel_layout(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
|
||||
params.sample_rate(),
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
if (!swr_ctx_) {
|
||||
qCritical() << "Failed to allocate resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (swr_init(swr_ctx_) < 0) {
|
||||
qCritical() << "Failed to init resample context";
|
||||
swr_free(&swr_ctx_);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray PackedProcessor::Convert(SampleBufferPtr planar)
|
||||
{
|
||||
if (!IsOpen()) {
|
||||
qCritical() << "Tried to convert while closed";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
int nb_samples = planar->sample_count();
|
||||
if (nb_samples == 0) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray output(planar->audio_params().samples_to_bytes(nb_samples), Qt::Uninitialized);
|
||||
uint8_t *output_data = reinterpret_cast<uint8_t*>(output.data());
|
||||
|
||||
int ret = swr_convert(swr_ctx_, &output_data, nb_samples,
|
||||
const_cast<const uint8_t**>(reinterpret_cast<uint8_t**>(planar->to_raw_ptrs())),
|
||||
nb_samples);
|
||||
if (ret < 0) {
|
||||
char buf[200];
|
||||
av_strerror(ret, buf, 200);
|
||||
qDebug() << "Packed processor failed with error:" << buf << ret;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void PackedProcessor::Close()
|
||||
{
|
||||
if (swr_ctx_) {
|
||||
swr_free(&swr_ctx_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PACKEDPROCESSOR_H
|
||||
#define PACKEDPROCESSOR_H
|
||||
|
||||
extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "render/audioparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class PackedProcessor
|
||||
{
|
||||
public:
|
||||
PackedProcessor();
|
||||
|
||||
~PackedProcessor();
|
||||
|
||||
DISABLE_COPY_MOVE(PackedProcessor)
|
||||
|
||||
bool Open(const AudioParams ¶ms);
|
||||
|
||||
QByteArray Convert(SampleBufferPtr planar);
|
||||
|
||||
void Close();
|
||||
|
||||
bool IsOpen() const
|
||||
{
|
||||
return swr_ctx_;
|
||||
}
|
||||
|
||||
private:
|
||||
SwrContext *swr_ctx_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PACKEDPROCESSOR_H
|
||||
@@ -0,0 +1,104 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "planarprocessor.h"
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
PlanarProcessor::PlanarProcessor() :
|
||||
swr_ctx_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
PlanarProcessor::~PlanarProcessor()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool PlanarProcessor::Open(const AudioParams ¶ms)
|
||||
{
|
||||
if (IsOpen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
swr_ctx_ = swr_alloc_set_opts(nullptr,
|
||||
params.channel_layout(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
|
||||
params.sample_rate(),
|
||||
params.channel_layout(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
|
||||
params.sample_rate(),
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
if (!swr_ctx_) {
|
||||
qCritical() << "Failed to allocate resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (swr_init(swr_ctx_) < 0) {
|
||||
qCritical() << "Failed to init resample context";
|
||||
swr_free(&swr_ctx_);
|
||||
return false;
|
||||
}
|
||||
|
||||
params_ = params;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SampleBufferPtr PlanarProcessor::Convert(const QByteArray &packed)
|
||||
{
|
||||
if (!IsOpen()) {
|
||||
qCritical() << "Tried to convert while closed";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (packed.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int nb_samples_per_channel = params_.bytes_to_samples(packed.size());
|
||||
|
||||
SampleBufferPtr output = SampleBuffer::CreateAllocated(params_, nb_samples_per_channel);
|
||||
|
||||
const uint8_t *input = reinterpret_cast<const uint8_t*>(packed.constData());
|
||||
int ret = swr_convert(swr_ctx_,
|
||||
reinterpret_cast<uint8_t**>(output->to_raw_ptrs()), nb_samples_per_channel,
|
||||
&input, nb_samples_per_channel);
|
||||
if (ret < 0) {
|
||||
char buf[200];
|
||||
av_strerror(ret, buf, 200);
|
||||
qDebug() << "Planar processor failed with error:" << buf << ret;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void PlanarProcessor::Close()
|
||||
{
|
||||
if (swr_ctx_) {
|
||||
swr_free(&swr_ctx_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PLANARPROCESSOR_H
|
||||
#define PLANARPROCESSOR_H
|
||||
|
||||
extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "render/audioparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class PlanarProcessor
|
||||
{
|
||||
public:
|
||||
PlanarProcessor();
|
||||
|
||||
~PlanarProcessor();
|
||||
|
||||
DISABLE_COPY_MOVE(PlanarProcessor)
|
||||
|
||||
bool Open(const AudioParams ¶ms);
|
||||
|
||||
SampleBufferPtr Convert(const QByteArray &packed);
|
||||
|
||||
void Close();
|
||||
|
||||
bool IsOpen() const
|
||||
{
|
||||
return swr_ctx_;
|
||||
}
|
||||
|
||||
private:
|
||||
SwrContext *swr_ctx_;
|
||||
|
||||
AudioParams params_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PLANARPROCESSOR_H
|
||||
@@ -1 +0,0 @@
|
||||
#include "sampleformat.h"
|
||||
@@ -0,0 +1,269 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "tempoprocessor.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
TempoProcessor::TempoProcessor() :
|
||||
filter_graph_(nullptr),
|
||||
buffersrc_ctx_(nullptr),
|
||||
buffersink_ctx_(nullptr),
|
||||
open_(false)
|
||||
{
|
||||
}
|
||||
|
||||
TempoProcessor::~TempoProcessor()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool TempoProcessor::IsOpen() const
|
||||
{
|
||||
return open_;
|
||||
}
|
||||
|
||||
const double &TempoProcessor::GetSpeed() const
|
||||
{
|
||||
return speed_;
|
||||
}
|
||||
|
||||
bool TempoProcessor::Open(const AudioParams ¶ms, const double& speed)
|
||||
{
|
||||
if (open_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
params_ = params;
|
||||
speed_ = speed;
|
||||
|
||||
// Create AVFilterGraph instance
|
||||
filter_graph_ = avfilter_graph_alloc();
|
||||
if (!filter_graph_) {
|
||||
qCritical() << "Failed to create AVFilterGraph";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up audio buffer args
|
||||
char filter_args[200];
|
||||
snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
|
||||
1,
|
||||
params_.sample_rate(),
|
||||
params_.sample_rate(),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(params_.format()),
|
||||
params.channel_layout());
|
||||
|
||||
// Create buffer and buffersink
|
||||
if (avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph_) < 0) {
|
||||
qCritical() << "Failed to create audio buffer source";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph_) < 0) {
|
||||
qCritical() << "Failed to create audio buffer sink";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside
|
||||
// those boundaries, we need to daisychain more than one together.
|
||||
double base = (speed_ > 1.0) ? 2.0 : 0.5;
|
||||
double speed_log = log(speed_) / log(base);
|
||||
|
||||
// This is the number of how many 0.5 or 2.0 tempos we need to daisychain
|
||||
int whole = qFloor(speed_log);
|
||||
|
||||
// Set speed_log to the remainder
|
||||
speed_log -= whole;
|
||||
|
||||
AVFilterContext* previous_filter = buffersrc_ctx_;
|
||||
|
||||
for (int i=0;i<=whole;i++) {
|
||||
double filter_tempo = (i == whole) ? qPow(base, speed_log) : base;
|
||||
|
||||
if (qFuzzyCompare(filter_tempo, 1.0)) {
|
||||
// This filter would do nothing
|
||||
continue;
|
||||
}
|
||||
|
||||
previous_filter = CreateTempoFilter(filter_graph_,
|
||||
previous_filter,
|
||||
filter_tempo);
|
||||
|
||||
if (!previous_filter) {
|
||||
qCritical() << "Failed to create audio tempo filter";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Link the last filter to the buffersink
|
||||
if (avfilter_link(previous_filter, 0, buffersink_ctx_, 0) != 0) {
|
||||
qCritical() << "Failed to link final filter and buffer sink";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Config graph
|
||||
if (avfilter_graph_config(filter_graph_, nullptr) < 0) {
|
||||
qCritical() << "Failed to configure filter graph";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamp_ = 0;
|
||||
|
||||
open_ = true;
|
||||
|
||||
flushed_ = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TempoProcessor::Push(const QByteArray &packed)
|
||||
{
|
||||
if (!IsOpen()) {
|
||||
qWarning() << "Tried to push to closed TempoProcessor";
|
||||
return;
|
||||
}
|
||||
|
||||
if (flushed_) {
|
||||
qWarning() << "Tried to push to flushed TempoProcessor";
|
||||
return;
|
||||
}
|
||||
|
||||
AVFrame* src_frame = av_frame_alloc();
|
||||
|
||||
if (!src_frame) {
|
||||
qCritical() << "Failed to allocate source frame";
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate a buffer for the number of samples we got
|
||||
src_frame->sample_rate = params_.sample_rate();
|
||||
src_frame->format = FFmpegUtils::GetFFmpegSampleFormat(params_.format());
|
||||
src_frame->channel_layout = params_.channel_layout();
|
||||
src_frame->nb_samples = params_.bytes_to_samples(packed.size());
|
||||
src_frame->pts = timestamp_;
|
||||
timestamp_ += src_frame->nb_samples;
|
||||
|
||||
if (av_frame_get_buffer(src_frame, 0) < 0) {
|
||||
qCritical() << "Failed to allocate buffer for source frame";
|
||||
av_frame_free(&src_frame);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy buffer from data array to frame
|
||||
memcpy(src_frame->data[0], packed, packed.size());
|
||||
|
||||
int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, src_frame, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
|
||||
if (ret < 0) {
|
||||
qCritical() << "Failed to feed buffer source" << ret;
|
||||
}
|
||||
|
||||
if (src_frame) {
|
||||
av_frame_free(&src_frame);
|
||||
}
|
||||
}
|
||||
|
||||
void TempoProcessor::Flush()
|
||||
{
|
||||
if (!flushed_) {
|
||||
int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (ret < 0) {
|
||||
qCritical() << "Failed to feed buffer source" << ret;
|
||||
}
|
||||
flushed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray TempoProcessor::Pull()
|
||||
{
|
||||
QByteArray b;
|
||||
AVFrame *processed_frame = av_frame_alloc();
|
||||
|
||||
// Try to pull samples from the buffersink
|
||||
int ret = av_buffersink_get_frame(buffersink_ctx_, processed_frame);
|
||||
|
||||
if (ret < 0) {
|
||||
// We couldn't pull for some reason, if the error was EAGAIN, we just need to send more samples. Otherwise the
|
||||
// error might be fatal...
|
||||
if (ret != AVERROR(EAGAIN)) {
|
||||
qCritical() << "Failed to pull from buffersink" << ret;
|
||||
}
|
||||
|
||||
av_frame_free(&processed_frame);
|
||||
return b;
|
||||
}
|
||||
|
||||
b.resize(params_.samples_to_bytes(processed_frame->nb_samples));
|
||||
|
||||
// Copy the bytes
|
||||
memcpy(b.data(), processed_frame->data[0], b.size());
|
||||
|
||||
// If the index has reached the limit of this processed frame, we can dispose of the frame now
|
||||
av_frame_free(&processed_frame);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
void TempoProcessor::Close()
|
||||
{
|
||||
open_ = false;
|
||||
|
||||
if (filter_graph_) {
|
||||
avfilter_graph_free(&filter_graph_);
|
||||
filter_graph_ = nullptr;
|
||||
}
|
||||
|
||||
buffersrc_ctx_ = nullptr;
|
||||
buffersink_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
AVFilterContext *TempoProcessor::CreateTempoFilter(AVFilterGraph* graph, AVFilterContext* link, const double &tempo)
|
||||
{
|
||||
// Set up tempo param, which is taken as a C string
|
||||
char speed_param[20];
|
||||
snprintf(speed_param, 20, "%f", tempo);
|
||||
|
||||
AVFilterContext* tempo_ctx = nullptr;
|
||||
|
||||
if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, graph) >= 0
|
||||
&& avfilter_link(link, 0, tempo_ctx, 0) == 0) {
|
||||
return tempo_ctx;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TEMPOPROCESSOR_H
|
||||
#define TEMPOPROCESSOR_H
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#ifndef __USE_MINGW_ANSI_STDIO
|
||||
#define __USE_MINGW_ANSI_STDIO
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/avfilter.h>
|
||||
}
|
||||
|
||||
#include "render/audioparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TempoProcessor
|
||||
{
|
||||
public:
|
||||
TempoProcessor();
|
||||
|
||||
~TempoProcessor();
|
||||
|
||||
DISABLE_COPY_MOVE(TempoProcessor)
|
||||
|
||||
bool IsOpen() const;
|
||||
|
||||
const double& GetSpeed() const;
|
||||
|
||||
bool Open(const AudioParams& params, const double &speed);
|
||||
|
||||
void Push(const QByteArray &packed);
|
||||
|
||||
void Flush();
|
||||
|
||||
QByteArray Pull();
|
||||
|
||||
void Close();
|
||||
|
||||
private:
|
||||
static AVFilterContext* CreateTempoFilter(AVFilterGraph *graph, AVFilterContext *link, const double& tempo);
|
||||
|
||||
AVFilterGraph* filter_graph_;
|
||||
|
||||
AVFilterContext* buffersrc_ctx_;
|
||||
|
||||
AVFilterContext* buffersink_ctx_;
|
||||
|
||||
AudioParams params_;
|
||||
|
||||
int64_t timestamp_;
|
||||
|
||||
double speed_;
|
||||
|
||||
bool open_;
|
||||
|
||||
bool flushed_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TEMPOPROCESSOR_H
|
||||
@@ -1,5 +1,5 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -14,9 +14,10 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(cliprogress)
|
||||
add_subdirectory(clitask)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
decoder/oiio/oiiodecoder.h
|
||||
decoder/oiio/oiiodecoder.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -18,11 +18,13 @@
|
||||
|
||||
***/
|
||||
|
||||
#include "menu.h"
|
||||
#include "cliexportmanager.h"
|
||||
|
||||
#include "blend/alphaover/alphaover.h"
|
||||
namespace olive {
|
||||
|
||||
NodeMenu::NodeMenu(QWidget *parent) :
|
||||
QMenu(parent)
|
||||
CLIExportManager::CLIExportManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef CLIEXPORTMANAGER_H
|
||||
#define CLIEXPORTMANAGER_H
|
||||
|
||||
#include "task/export/export.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class CLIExportManager : public QObject
|
||||
{
|
||||
public:
|
||||
CLIExportManager();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CLIEXPORTMANAGER_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
cli/cliprogress/cliprogressdialog.h
|
||||
cli/cliprogress/cliprogressdialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,106 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "cliprogressdialog.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace olive {
|
||||
|
||||
CLIProgressDialog::CLIProgressDialog(const QString& title, QObject *parent) :
|
||||
QObject(parent),
|
||||
title_(title),
|
||||
progress_(-1),
|
||||
drawn_(false)
|
||||
{
|
||||
SetProgress(0);
|
||||
}
|
||||
|
||||
void CLIProgressDialog::Update()
|
||||
{
|
||||
if (drawn_) {
|
||||
// We've been here before, do a carriage return back to the start of the terminal line
|
||||
std::cout << "\r";
|
||||
} else {
|
||||
drawn_ = true;
|
||||
}
|
||||
|
||||
// FIXME: Get real column count
|
||||
int columns = 80;
|
||||
|
||||
int title_columns = columns / 2 - 1;
|
||||
|
||||
// Print "title" text
|
||||
QString sized_title = title_;
|
||||
|
||||
if (title_.size() > title_columns) {
|
||||
sized_title = title_.left(title_columns - 3).append(QStringLiteral("..."));
|
||||
} else {
|
||||
sized_title = title_;
|
||||
}
|
||||
|
||||
std::cout << sized_title.toUtf8().constData();
|
||||
|
||||
// Pad out the rest of the title area if necessary
|
||||
for (int i=sized_title.size(); i<title_columns; i++) {
|
||||
std::cout << " ";
|
||||
}
|
||||
|
||||
// Percentage counter " 100% " is 5 characters + the enclosing brackets [] are 2 characters
|
||||
int progress_bar_columns = columns / 2 - 7;
|
||||
|
||||
std::cout << "[";
|
||||
|
||||
// Get UI bar progress
|
||||
int bar_prog = qRound(progress_ * progress_bar_columns);
|
||||
|
||||
// Draw filled in bar
|
||||
for (int i=0;i<bar_prog;i++) {
|
||||
std::cout << "=";
|
||||
}
|
||||
|
||||
// Draw empty space
|
||||
for (int i=bar_prog;i<progress_bar_columns;i++) {
|
||||
std::cout << " ";
|
||||
}
|
||||
|
||||
std::cout << "] ";
|
||||
|
||||
if (progress_ < 100) {
|
||||
std::cout << " ";
|
||||
}
|
||||
|
||||
if (progress_ < 10) {
|
||||
std::cout << " ";
|
||||
}
|
||||
|
||||
std::cout << qRound(progress_ * 100.0) << "% " << std::endl << std::flush;
|
||||
}
|
||||
|
||||
void CLIProgressDialog::SetProgress(double p)
|
||||
{
|
||||
if (progress_ != p) {
|
||||
progress_ = p;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -18,28 +18,35 @@
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TRACKREFERENCE_H
|
||||
#define TRACKREFERENCE_H
|
||||
#ifndef CLIPROGRESSDIALOG_H
|
||||
#define CLIPROGRESSDIALOG_H
|
||||
|
||||
#include "timeline/tracktypes.h"
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class TrackReference
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class CLIProgressDialog : public QObject
|
||||
{
|
||||
public:
|
||||
TrackReference();
|
||||
CLIProgressDialog(const QString &title, QObject* parent = nullptr);
|
||||
|
||||
TrackReference(const TrackType& type, const int& index);
|
||||
|
||||
const TrackType& type() const;
|
||||
|
||||
const int& index() const;
|
||||
|
||||
bool operator==(const TrackReference& ref) const;
|
||||
public slots:
|
||||
void SetProgress(double p);
|
||||
|
||||
private:
|
||||
TrackType type_;
|
||||
void Update();
|
||||
|
||||
QString title_;
|
||||
|
||||
double progress_;
|
||||
|
||||
bool drawn_;
|
||||
|
||||
int index_;
|
||||
};
|
||||
|
||||
#endif // TRACKREFERENCE_H
|
||||
}
|
||||
|
||||
#endif // CLIPROGRESSDIALOG_H
|
||||
@@ -1,5 +1,5 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/color/opacity/opacity.h
|
||||
node/color/opacity/opacity.cpp
|
||||
cli/clitask/clitaskdialog.h
|
||||
cli/clitask/clitaskdialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "clitaskdialog.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
CLITaskDialog::CLITaskDialog(Task *task, QObject* parent) :
|
||||
CLIProgressDialog(task->GetTitle(), parent),
|
||||
task_(task)
|
||||
{
|
||||
connect(task_, &Task::ProgressChanged, this, &CLITaskDialog::SetProgress);
|
||||
}
|
||||
|
||||
bool CLITaskDialog::Run()
|
||||
{
|
||||
return task_->Start();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef CLITASKDIALOG_H
|
||||
#define CLITASKDIALOG_H
|
||||
|
||||
#include "cli/cliprogress/cliprogressdialog.h"
|
||||
#include "task/task.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class CLITaskDialog : public CLIProgressDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CLITaskDialog(Task *task, QObject* parent = nullptr);
|
||||
|
||||
bool Run();
|
||||
|
||||
private:
|
||||
Task* task_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CLITASKDIALOG_H
|
||||
@@ -0,0 +1,39 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(ffmpeg)
|
||||
add_subdirectory(oiio)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
codec/conformmanager.cpp
|
||||
codec/conformmanager.h
|
||||
codec/decoder.cpp
|
||||
codec/decoder.h
|
||||
codec/encoder.cpp
|
||||
codec/encoder.h
|
||||
codec/exportcodec.cpp
|
||||
codec/exportcodec.h
|
||||
codec/exportformat.cpp
|
||||
codec/exportformat.h
|
||||
codec/frame.cpp
|
||||
codec/frame.h
|
||||
codec/planarfiledevice.cpp
|
||||
codec/planarfiledevice.h
|
||||
codec/samplebuffer.cpp
|
||||
codec/samplebuffer.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,126 @@
|
||||
#include "conformmanager.h"
|
||||
|
||||
#include <QDir>
|
||||
|
||||
#include "task/taskmanager.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
ConformManager *ConformManager::instance_ = nullptr;
|
||||
|
||||
ConformManager::Conform ConformManager::GetConformState(const QString &decoder_id, const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms, bool wait)
|
||||
{
|
||||
// Mutex because we'll need to check the status of a conform task
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
// Return existing conform if exists
|
||||
QVector<QString> filenames = GetConformedFilename(cache_path, stream, params);
|
||||
if (AllConformsExist(filenames)) {
|
||||
return {kConformExists, filenames, nullptr};
|
||||
}
|
||||
|
||||
ConformTask *conforming_task = nullptr;
|
||||
|
||||
foreach (const ConformData &data, conforming_) {
|
||||
if (data.stream == stream && data.params == params) {
|
||||
// Already creating conform in a task
|
||||
conforming_task = data.task;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!conforming_task) {
|
||||
// Not conforming yet, create a task to do so
|
||||
|
||||
// We conform to a different filename until it's done to make it clear even across sessions
|
||||
// whether this conform is ready or not
|
||||
QVector<QString> working_filenames = filenames;
|
||||
for (int i=0; i<working_filenames.size(); i++) {
|
||||
working_filenames[i].append(QStringLiteral(".working"));
|
||||
}
|
||||
|
||||
conforming_task = new ConformTask(decoder_id, stream, params, working_filenames);
|
||||
connect(conforming_task, &ConformTask::Finished, this, &ConformManager::ConformTaskFinished);
|
||||
conforming_task->moveToThread(TaskManager::instance()->thread());
|
||||
QMetaObject::invokeMethod(TaskManager::instance(), "AddTask", Qt::QueuedConnection, Q_ARG(Task *, conforming_task));
|
||||
|
||||
conforming_.append({stream, params, conforming_task, working_filenames, filenames});
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
do {
|
||||
conform_done_condition_.wait(&mutex_);
|
||||
} while (!AllConformsExist(filenames));
|
||||
return {kConformExists, filenames, nullptr};
|
||||
}
|
||||
|
||||
return {kConformGenerating, QVector<QString>(), conforming_task};
|
||||
}
|
||||
|
||||
QVector<QString> ConformManager::GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms)
|
||||
{
|
||||
QVector<QString> filenames(params.channel_count());
|
||||
|
||||
for (int i=0; i<filenames.size(); i++) {
|
||||
QString index_fn = QStringLiteral("%1-%2.%3.%4.%5.%6.pcm").arg(FileFunctions::GetUniqueFileIdentifier(stream.filename()),
|
||||
QString::number(stream.stream()),
|
||||
QString::number(params.sample_rate()),
|
||||
QString::number(params.format()),
|
||||
QString::number(params.channel_layout()),
|
||||
QString::number(i));
|
||||
|
||||
filenames[i] = QDir(cache_path).filePath(index_fn);
|
||||
}
|
||||
|
||||
return filenames;
|
||||
}
|
||||
|
||||
bool ConformManager::AllConformsExist(const QVector<QString> &filenames)
|
||||
{
|
||||
foreach (const QString &fn, filenames) {
|
||||
if (!QFileInfo::exists(fn)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConformManager::ConformTaskFinished(Task *task, bool succeeded)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
ConformData data;
|
||||
|
||||
// Remove conform data from list
|
||||
for (int i=0; i<conforming_.size(); i++) {
|
||||
const ConformData &c = conforming_.at(i);
|
||||
if (c.task == task) {
|
||||
data = c;
|
||||
conforming_.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (succeeded) {
|
||||
// Move file to standard conform name, making it clear this conform is ready for use
|
||||
for (int i=0; i<data.finished_filename.size(); i++) {
|
||||
const QString &finished = data.finished_filename.at(i);
|
||||
const QString &working = data.working_filename.at(i);
|
||||
|
||||
QFile::remove(finished);
|
||||
QFile::rename(working, finished);
|
||||
}
|
||||
|
||||
conform_done_condition_.wakeAll();
|
||||
locker.unlock();
|
||||
emit ConformReady();
|
||||
} else {
|
||||
// Failed, just delete the working filename if exists
|
||||
for (int i=0; i<data.working_filename.size(); i++) {
|
||||
QFile::remove(data.working_filename.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef CONFORMMANAGER_H
|
||||
#define CONFORMMANAGER_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
#include "decoder.h"
|
||||
#include "task/conform/conform.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ConformManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static void CreateInstance()
|
||||
{
|
||||
if (!instance_) {
|
||||
instance_ = new ConformManager();
|
||||
}
|
||||
}
|
||||
|
||||
static void DestroyInstance()
|
||||
{
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
static ConformManager *instance()
|
||||
{
|
||||
return instance_;
|
||||
}
|
||||
|
||||
enum ConformState {
|
||||
kConformExists,
|
||||
kConformGenerating
|
||||
};
|
||||
|
||||
struct Conform {
|
||||
ConformState state;
|
||||
QVector<QString> filenames;
|
||||
ConformTask *task;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get conform state, and start conforming if no conform exists
|
||||
*
|
||||
* Thread-safe.
|
||||
*/
|
||||
Conform GetConformState(const QString &decoder_id, const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms, bool wait);
|
||||
|
||||
signals:
|
||||
void ConformReady();
|
||||
|
||||
private:
|
||||
ConformManager() = default;
|
||||
|
||||
static ConformManager *instance_;
|
||||
|
||||
QMutex mutex_;
|
||||
|
||||
QWaitCondition conform_done_condition_;
|
||||
|
||||
struct ConformData {
|
||||
Decoder::CodecStream stream;
|
||||
AudioParams params;
|
||||
ConformTask *task;
|
||||
QVector<QString> working_filename;
|
||||
QVector<QString> finished_filename;
|
||||
};
|
||||
|
||||
QVector<ConformData> conforming_;
|
||||
|
||||
/**
|
||||
* @brief Get the destination filename of an audio stream conformed to a set of parameters
|
||||
*/
|
||||
static QVector<QString> GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms);
|
||||
|
||||
static bool AllConformsExist(const QVector<QString> &filenames);
|
||||
|
||||
private slots:
|
||||
void ConformTaskFinished(Task *task, bool succeeded);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CONFORMMANAGER_H
|
||||
@@ -0,0 +1,332 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "decoder.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "codec/ffmpeg/ffmpegdecoder.h"
|
||||
#include "codec/planarfiledevice.h"
|
||||
#include "codec/oiio/oiiodecoder.h"
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "common/filefunctions.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "conformmanager.h"
|
||||
#include "node/project/project.h"
|
||||
#include "task/taskmanager.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const rational Decoder::kAnyTimecode = RATIONAL_MIN;
|
||||
|
||||
Decoder::Decoder()
|
||||
{
|
||||
UpdateLastAccessed();
|
||||
}
|
||||
|
||||
bool Decoder::Open(const CodecStream &stream)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
UpdateLastAccessed();
|
||||
|
||||
if (stream_.IsValid()) {
|
||||
// Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not.
|
||||
if (stream_ == stream) {
|
||||
return true;
|
||||
} else {
|
||||
qWarning() << "Tried to open a decoder that was already open with another stream";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Stream was not open, try opening it now
|
||||
if (!stream.IsValid()) {
|
||||
// Cannot open null stream
|
||||
qCritical() << "Decoder attempted to open null stream";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stream.Exists()) {
|
||||
// Cannot open file that doesn't exist
|
||||
qCritical() << "Decoder attempted to open file that doesn't exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set stream
|
||||
stream_ = stream;
|
||||
|
||||
// Try open internal
|
||||
if (OpenInternal()) {
|
||||
return true;
|
||||
} else {
|
||||
// Unset stream
|
||||
qCritical() << "Failed to open" << stream_.filename() << "stream" << stream_.stream();
|
||||
CloseInternal();
|
||||
stream_.Reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FramePtr Decoder::RetrieveVideo(const rational &timecode, const RetrieveVideoParams ÷r, const QAtomicInt *cancelled)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
UpdateLastAccessed();
|
||||
|
||||
if (!stream_.IsValid()) {
|
||||
qCritical() << "Can't retrieve video on a closed decoder";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!SupportsVideo()) {
|
||||
qCritical() << "Decoder doesn't support video";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cancelled && *cancelled) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return RetrieveVideoInternal(timecode, divider, cancelled);
|
||||
}
|
||||
|
||||
Decoder::RetrieveAudioData Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
UpdateLastAccessed();
|
||||
|
||||
if (!stream_.IsValid()) {
|
||||
qCritical() << "Can't retrieve audio on a closed decoder";
|
||||
return {kInvalid, nullptr, nullptr};
|
||||
}
|
||||
|
||||
if (!SupportsAudio()) {
|
||||
qCritical() << "Decoder doesn't support audio";
|
||||
return {kInvalid, nullptr, nullptr};
|
||||
}
|
||||
|
||||
// Get conform state from ConformManager
|
||||
ConformManager::Conform conform = ConformManager::instance()->GetConformState(id(), cache_path, stream_, params, (mode == RenderMode::kOnline));
|
||||
if (conform.state == ConformManager::kConformGenerating) {
|
||||
return {kWaitingForConform, nullptr, conform.task};
|
||||
}
|
||||
|
||||
// See if we got the conform
|
||||
SampleBufferPtr out_buffer = RetrieveAudioFromConform(conform.filenames, range, loop_mode, params);
|
||||
|
||||
return {kOK, out_buffer, nullptr};
|
||||
}
|
||||
|
||||
qint64 Decoder::GetLastAccessedTime()
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
return last_accessed_;
|
||||
}
|
||||
|
||||
void Decoder::Close()
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
UpdateLastAccessed();
|
||||
|
||||
if (stream_.IsValid()) {
|
||||
CloseInternal();
|
||||
stream_.Reset();
|
||||
} else {
|
||||
qWarning() << "Tried to close a decoder that wasn't open";
|
||||
}
|
||||
}
|
||||
|
||||
bool Decoder::ConformAudio(const QVector<QString> &output_filenames, const AudioParams ¶ms, const QAtomicInt *cancelled)
|
||||
{
|
||||
return ConformAudioInternal(output_filenames, params, cancelled);
|
||||
}
|
||||
|
||||
/*
|
||||
* DECODER STATIC PUBLIC MEMBERS
|
||||
*/
|
||||
|
||||
QVector<DecoderPtr> Decoder::ReceiveListOfAllDecoders()
|
||||
{
|
||||
QVector<DecoderPtr> decoders;
|
||||
|
||||
// The order in which these decoders are added is their priority when probing. Hence FFmpeg should usually be last,
|
||||
// since it supports so many formats and we presumably want to override those formats with a more specific decoder.
|
||||
decoders.append(std::make_shared<OIIODecoder>());
|
||||
decoders.append(std::make_shared<FFmpegDecoder>());
|
||||
|
||||
return decoders;
|
||||
}
|
||||
|
||||
DecoderPtr Decoder::CreateFromID(const QString &id)
|
||||
{
|
||||
if (id.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create list to iterate through
|
||||
QVector<DecoderPtr> decoder_list = ReceiveListOfAllDecoders();
|
||||
|
||||
foreach (DecoderPtr d, decoder_list) {
|
||||
if (d->id() == id) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time)
|
||||
{
|
||||
return Timecode::time_to_timestamp(time, timebase) + start_time;
|
||||
}
|
||||
|
||||
void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration)
|
||||
{
|
||||
if (duration != AV_NOPTS_VALUE && duration != 0) {
|
||||
emit IndexProgress(static_cast<double>(ts) / static_cast<double>(duration));
|
||||
}
|
||||
}
|
||||
|
||||
QString Decoder::TransformImageSequenceFileName(const QString &filename, const int64_t& number)
|
||||
{
|
||||
int digit_count = GetImageSequenceDigitCount(filename);
|
||||
|
||||
QFileInfo file_info(filename);
|
||||
|
||||
QString original_basename = file_info.completeBaseName();
|
||||
|
||||
QString new_basename = original_basename.left(original_basename.size() - digit_count)
|
||||
.append(QStringLiteral("%1").arg(number, digit_count, 10, QChar('0')));
|
||||
|
||||
return file_info.dir().filePath(file_info.fileName().replace(original_basename, new_basename));
|
||||
}
|
||||
|
||||
int Decoder::GetImageSequenceDigitCount(const QString &filename)
|
||||
{
|
||||
QString basename = QFileInfo(filename).completeBaseName();
|
||||
|
||||
// See if basename contains a number at the end
|
||||
int digit_count = 0;
|
||||
|
||||
for (int i=basename.size()-1;i>=0;i--) {
|
||||
if (basename.at(i).isDigit()) {
|
||||
digit_count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return digit_count;
|
||||
}
|
||||
|
||||
int64_t Decoder::GetImageSequenceIndex(const QString &filename)
|
||||
{
|
||||
int digit_count = GetImageSequenceDigitCount(filename);
|
||||
|
||||
QFileInfo file_info(filename);
|
||||
|
||||
QString original_basename = file_info.completeBaseName();
|
||||
|
||||
QString number_only = original_basename.mid(original_basename.size() - digit_count);
|
||||
|
||||
return number_only.toLongLong();
|
||||
}
|
||||
|
||||
FramePtr Decoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams ÷r, const QAtomicInt *cancelled)
|
||||
{
|
||||
Q_UNUSED(timecode)
|
||||
Q_UNUSED(divider)
|
||||
Q_UNUSED(cancelled)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Decoder::ConformAudioInternal(const QVector<QString> &filenames, const AudioParams ¶ms, const QAtomicInt* cancelled)
|
||||
{
|
||||
Q_UNUSED(filenames)
|
||||
Q_UNUSED(cancelled)
|
||||
Q_UNUSED(params)
|
||||
return false;
|
||||
}
|
||||
|
||||
SampleBufferPtr Decoder::RetrieveAudioFromConform(const QVector<QString> &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params)
|
||||
{
|
||||
PlanarFileDevice input;
|
||||
if (input.open(conform_filenames, QFile::ReadOnly)) {
|
||||
SampleBufferPtr sample_buffer = SampleBuffer::CreateAllocated(input_params, range.length());
|
||||
|
||||
qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count();
|
||||
qint64 write_index = 0;
|
||||
|
||||
const qint64 buffer_length_in_bytes = sample_buffer->sample_count() * input_params.bytes_per_sample_per_channel();
|
||||
|
||||
while (write_index < buffer_length_in_bytes) {
|
||||
if (loop_mode == Footage::kLoopModeLoop) {
|
||||
while (read_index >= input.size()) {
|
||||
read_index -= input.size();
|
||||
}
|
||||
|
||||
while (read_index < 0) {
|
||||
read_index += input.size();
|
||||
}
|
||||
}
|
||||
|
||||
qint64 write_count = 0;
|
||||
|
||||
if (read_index < 0) {
|
||||
// Reading before 0, write silence here until audio data would actually start
|
||||
write_count = qMin(-read_index, buffer_length_in_bytes);
|
||||
sample_buffer->silence_bytes(write_index, write_index + write_count);
|
||||
} else if (read_index >= input.size()) {
|
||||
// Reading after data length, write silence until the end of the buffer
|
||||
write_count = buffer_length_in_bytes - write_index;
|
||||
sample_buffer->silence_bytes(write_index, write_index + write_count);
|
||||
} else {
|
||||
write_count = qMin(input.size() - read_index, buffer_length_in_bytes - write_index);
|
||||
input.seek(read_index);
|
||||
input.read(reinterpret_cast<char**>(sample_buffer->to_raw_ptrs()), write_count, write_index);
|
||||
}
|
||||
|
||||
read_index += write_count;
|
||||
write_index += write_count;
|
||||
}
|
||||
|
||||
input.close();
|
||||
|
||||
return sample_buffer;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Decoder::UpdateLastAccessed()
|
||||
{
|
||||
last_accessed_ = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
uint qHash(Decoder::CodecStream stream, uint seed)
|
||||
{
|
||||
return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef DECODER_H
|
||||
#define DECODER_H
|
||||
|
||||
extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QWaitCondition>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "common/rational.h"
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "node/project/footage/footagedescription.h"
|
||||
#include "task/task.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class Decoder;
|
||||
using DecoderPtr = std::shared_ptr<Decoder>;
|
||||
|
||||
#define DECODER_DEFAULT_DESTRUCTOR(x) virtual ~x() override {CloseInternal();}
|
||||
|
||||
/**
|
||||
* @brief A decoder's is the main class for bringing external media into Olive
|
||||
*
|
||||
* Its responsibilities are to serve as
|
||||
* abstraction from codecs/decoders and provide complete frames. These frames can be video or audio data and are
|
||||
* provided as Frame objects in shared pointers to alleviate the responsibility of memory handling.
|
||||
*
|
||||
* The main function in a decoder is Retrieve() which should return complete image/audio data. A decoder should
|
||||
* alleviate all the complexities of codec compression from the rest of the application (i.e. a decoder should never
|
||||
* return a partial frame or require other parts of the system to interface directly with the codec). Often this will
|
||||
* necessitate pre-emptively caching, indexing, or even fully transcoding media before using it which can be implemented
|
||||
* through the Analyze() function.
|
||||
*
|
||||
* A decoder does NOT perform any pixel/sample format conversion. Frames should pass through the PixelService
|
||||
* to be utilized in the rest of the rendering pipeline.
|
||||
*/
|
||||
class Decoder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum RetrieveState {
|
||||
kReady,
|
||||
kFailedToOpen,
|
||||
kIndexUnavailable
|
||||
};
|
||||
|
||||
Decoder();
|
||||
|
||||
/**
|
||||
* @brief Unique decoder ID
|
||||
*/
|
||||
virtual QString id() const = 0;
|
||||
|
||||
virtual bool SupportsVideo(){return false;}
|
||||
virtual bool SupportsAudio(){return false;}
|
||||
|
||||
class CodecStream
|
||||
{
|
||||
public:
|
||||
CodecStream() :
|
||||
stream_(-1)
|
||||
{
|
||||
}
|
||||
|
||||
CodecStream(const QString& filename, int stream) :
|
||||
filename_(filename),
|
||||
stream_(stream)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return !filename_.isEmpty() && stream_ >= 0;
|
||||
}
|
||||
|
||||
bool Exists() const
|
||||
{
|
||||
return QFileInfo::exists(filename_);
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
*this = CodecStream();
|
||||
}
|
||||
|
||||
bool operator==(const CodecStream& rhs) const
|
||||
{
|
||||
return filename_ == rhs.filename_ && stream_ == rhs.stream_;
|
||||
}
|
||||
|
||||
const QString& filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
|
||||
int stream() const
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString filename_;
|
||||
|
||||
int stream_;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open stream for decoding
|
||||
*
|
||||
* This function is thread safe.
|
||||
*
|
||||
* Returns TRUE if stream could be opened successfully. Also returns TRUE if the decoder is
|
||||
* already open and the stream == the stream provided. Returns FALSE if the stream couldn't
|
||||
* be opened OR if already open and the stream is NOT the same.
|
||||
*/
|
||||
bool Open(const CodecStream& stream);
|
||||
|
||||
static const rational kAnyTimecode;
|
||||
|
||||
struct RetrieveVideoParams
|
||||
{
|
||||
RetrieveVideoParams()
|
||||
{
|
||||
divider = 1;
|
||||
src_interlacing = VideoParams::kInterlaceNone;
|
||||
dst_interlacing = VideoParams::kInterlaceNone;
|
||||
}
|
||||
|
||||
int divider;
|
||||
VideoParams::Interlacing src_interlacing;
|
||||
VideoParams::Interlacing dst_interlacing;
|
||||
|
||||
void reset()
|
||||
{
|
||||
*this = RetrieveVideoParams();
|
||||
}
|
||||
|
||||
bool operator==(const RetrieveVideoParams& rhs) const
|
||||
{
|
||||
return divider == rhs.divider && src_interlacing == rhs.src_interlacing && dst_interlacing == rhs.dst_interlacing;
|
||||
}
|
||||
|
||||
bool operator!=(const RetrieveVideoParams& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Retrieves a video frame from footage
|
||||
*
|
||||
* This function will always return a valid frame unless a fatal error occurs (in such case,
|
||||
* nullptr will return). If the timecode is before the start of the footage, this function should
|
||||
* return the first frame. Likewise, if it is after the timecode, this function should return the
|
||||
* last frame.
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
FramePtr RetrieveVideo(const rational& timecode, const RetrieveVideoParams& divider, const QAtomicInt *cancelled = nullptr);
|
||||
|
||||
enum RetrieveAudioStatus {
|
||||
kInvalid = -1,
|
||||
kOK,
|
||||
kWaitingForConform
|
||||
};
|
||||
|
||||
struct RetrieveAudioData {
|
||||
RetrieveAudioStatus status;
|
||||
SampleBufferPtr samples;
|
||||
Task *task;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Retrieve audio data from footage
|
||||
*
|
||||
* This function will always return a sample buffer unless a fatal error occurs (in such case,
|
||||
* nullptr will return). The SampleBuffer should always have enough audio for the range provided.
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
RetrieveAudioData RetrieveAudio(const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode);
|
||||
|
||||
/**
|
||||
* @brief Determine the last time this decoder instance was used in any way
|
||||
*/
|
||||
qint64 GetLastAccessedTime();
|
||||
|
||||
/**
|
||||
* @brief Generate a Footage object from a file
|
||||
*
|
||||
* If this decoder is able to parse this file, it will return a valid FootagePtr. Otherwise, it
|
||||
* will return nullptr.
|
||||
*
|
||||
* For sub-classes, this function should be effectively static. We can't do virtual static
|
||||
* functions in C++, but it should hold and access no state during its run.
|
||||
*
|
||||
* This function is re-entrant.
|
||||
*/
|
||||
virtual FootageDescription Probe(const QString& filename, const QAtomicInt* cancelled) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Closes media/deallocates memory
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
void Close();
|
||||
|
||||
/**
|
||||
* @brief Conform audio stream
|
||||
*/
|
||||
bool ConformAudio(const QVector<QString> &output_filenames, const AudioParams ¶ms, const QAtomicInt *cancelled = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Create a Decoder instance using a Decoder ID
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* A Decoder instance or nullptr if a Decoder with this ID does not exist
|
||||
*/
|
||||
static DecoderPtr CreateFromID(const QString& id);
|
||||
|
||||
static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number);
|
||||
|
||||
static int GetImageSequenceDigitCount(const QString& filename);
|
||||
|
||||
static int64_t GetImageSequenceIndex(const QString& filename);
|
||||
|
||||
static QVector<DecoderPtr> ReceiveListOfAllDecoders();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Internal open function
|
||||
*
|
||||
* Sub-classes must override this function. Function will already be mutexed, so there is no need
|
||||
* to worry about thread safety. Also many other sanity checks will be done before this, so
|
||||
* sub-classes only need to worry about their own opening functions. It is guaranteed that the
|
||||
* decoder is not open yet and that the footage stream was from that sub-classes probe function.
|
||||
*
|
||||
* Return TRUE if everything opened successfully and the decoder is ready to work. Otherwise,
|
||||
* return FALSE. If this function returns false, Decoder will call CloseInternal to clean any
|
||||
* memory allocated during OpenInternal.
|
||||
*/
|
||||
virtual bool OpenInternal() = 0;
|
||||
|
||||
/**
|
||||
* @brief Internal close function
|
||||
*
|
||||
* Sub-classes must override this function. Function should be able to safely clear all allocated
|
||||
* memory. It may be called even if Open() didn't complete or RetrieveVideo() was never called.
|
||||
*/
|
||||
virtual void CloseInternal() = 0;
|
||||
|
||||
/**
|
||||
* @brief Internal frame retrieval function
|
||||
*
|
||||
* Sub-classes must override this function IF they support video. Function is already mutexed
|
||||
* so sub-classes don't need to worry about thread safety.
|
||||
*/
|
||||
virtual FramePtr RetrieveVideoInternal(const rational& timecode, const RetrieveVideoParams& divider, const QAtomicInt *cancelled);
|
||||
|
||||
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled);
|
||||
|
||||
void SignalProcessingProgress(int64_t ts, int64_t duration);
|
||||
|
||||
/**
|
||||
* @brief Return currently open stream
|
||||
*
|
||||
* This function is NOT thread safe and should therefore only be called by thread safe functions.
|
||||
*/
|
||||
const CodecStream& stream() const
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if
|
||||
* available
|
||||
*/
|
||||
void IndexProgress(double);
|
||||
|
||||
private:
|
||||
void UpdateLastAccessed();
|
||||
|
||||
SampleBufferPtr RetrieveAudioFromConform(const QVector<QString> &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms);
|
||||
|
||||
CodecStream stream_;
|
||||
|
||||
QMutex mutex_;
|
||||
|
||||
qint64 last_accessed_;
|
||||
|
||||
};
|
||||
|
||||
uint qHash(Decoder::CodecStream stream, uint seed = 0);
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::Decoder::RetrieveState)
|
||||
|
||||
#endif // DECODER_H
|
||||
@@ -0,0 +1,353 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "ffmpeg/ffmpegencoder.h"
|
||||
#include "oiio/oiioencoder.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QRegularExpression Encoder::kImageSequenceContainsDigits = QRegularExpression(QStringLiteral("\\[[#]+\\]"));
|
||||
const QRegularExpression Encoder::kImageSequenceRemoveDigits = QRegularExpression(QStringLiteral("[\\-\\.\\ \\_]?\\[[#]+\\]"));
|
||||
|
||||
Encoder::Encoder(const EncodingParams ¶ms) :
|
||||
params_(params)
|
||||
{
|
||||
}
|
||||
|
||||
const EncodingParams &Encoder::params() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
QString Encoder::GetFilenameForFrame(const rational &frame)
|
||||
{
|
||||
if (params().video_is_image_sequence()) {
|
||||
// Transform!
|
||||
int64_t frame_index = Timecode::time_to_timestamp(frame, params().video_params().frame_rate_as_time_base());
|
||||
int digits = GetImageSequencePlaceholderDigitCount(params().filename());
|
||||
QString frame_index_str = QStringLiteral("%1").arg(frame_index, digits, 10, QChar('0'));
|
||||
|
||||
QString f = params_.filename();
|
||||
f.replace(kImageSequenceContainsDigits, frame_index_str);
|
||||
return f;
|
||||
} else {
|
||||
// Keep filename
|
||||
return params_.filename();
|
||||
}
|
||||
}
|
||||
|
||||
int Encoder::GetImageSequencePlaceholderDigitCount(const QString &filename)
|
||||
{
|
||||
int start = filename.indexOf(kImageSequenceContainsDigits);
|
||||
int digit_count = 0;
|
||||
for (int i=start+1; i<filename.size(); i++) {
|
||||
if (filename.at(i) == '#') {
|
||||
digit_count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return digit_count;
|
||||
}
|
||||
|
||||
bool Encoder::FilenameContainsDigitPlaceholder(const QString& filename)
|
||||
{
|
||||
return filename.contains(kImageSequenceContainsDigits);
|
||||
}
|
||||
|
||||
QString Encoder::FilenameRemoveDigitPlaceholder(QString filename)
|
||||
{
|
||||
return filename.remove(kImageSequenceRemoveDigits);
|
||||
}
|
||||
|
||||
EncodingParams::EncodingParams() :
|
||||
video_enabled_(false),
|
||||
video_bit_rate_(0),
|
||||
video_min_bit_rate_(0),
|
||||
video_max_bit_rate_(0),
|
||||
video_buffer_size_(0),
|
||||
video_threads_(0),
|
||||
video_is_image_sequence_(false),
|
||||
audio_enabled_(false),
|
||||
audio_bit_rate_(0),
|
||||
subtitles_enabled_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void EncodingParams::SetFilename(const QString &filename)
|
||||
{
|
||||
filename_ = filename;
|
||||
}
|
||||
|
||||
void EncodingParams::EnableVideo(const VideoParams &video_params, const ExportCodec::Codec &vcodec)
|
||||
{
|
||||
video_enabled_ = true;
|
||||
video_params_ = video_params;
|
||||
video_codec_ = vcodec;
|
||||
}
|
||||
|
||||
void EncodingParams::EnableAudio(const AudioParams &audio_params, const ExportCodec::Codec &acodec)
|
||||
{
|
||||
audio_enabled_ = true;
|
||||
audio_params_ = audio_params;
|
||||
audio_codec_ = acodec;
|
||||
}
|
||||
|
||||
void EncodingParams::EnableSubtitles(const ExportCodec::Codec &scodec)
|
||||
{
|
||||
subtitles_enabled_ = true;
|
||||
subtitles_codec_ = scodec;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_option(const QString &key, const QString &value)
|
||||
{
|
||||
video_opts_.insert(key, value);
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_bit_rate(const int64_t &rate)
|
||||
{
|
||||
video_bit_rate_ = rate;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_min_bit_rate(const int64_t &rate)
|
||||
{
|
||||
video_min_bit_rate_ = rate;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_max_bit_rate(const int64_t &rate)
|
||||
{
|
||||
video_max_bit_rate_ = rate;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_buffer_size(const int64_t &sz)
|
||||
{
|
||||
video_buffer_size_ = sz;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_threads(const int &threads)
|
||||
{
|
||||
video_threads_ = threads;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_pix_fmt(const QString &s)
|
||||
{
|
||||
video_pix_fmt_ = s;
|
||||
}
|
||||
|
||||
const QString &EncodingParams::filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
|
||||
bool EncodingParams::video_enabled() const
|
||||
{
|
||||
return video_enabled_;
|
||||
}
|
||||
|
||||
const ExportCodec::Codec &EncodingParams::video_codec() const
|
||||
{
|
||||
return video_codec_;
|
||||
}
|
||||
|
||||
const VideoParams &EncodingParams::video_params() const
|
||||
{
|
||||
return video_params_;
|
||||
}
|
||||
|
||||
const QHash<QString, QString> &EncodingParams::video_opts() const
|
||||
{
|
||||
return video_opts_;
|
||||
}
|
||||
|
||||
const int64_t &EncodingParams::video_bit_rate() const
|
||||
{
|
||||
return video_bit_rate_;
|
||||
}
|
||||
|
||||
const int64_t &EncodingParams::video_min_bit_rate() const
|
||||
{
|
||||
return video_min_bit_rate_;
|
||||
}
|
||||
|
||||
const int64_t &EncodingParams::video_max_bit_rate() const
|
||||
{
|
||||
return video_max_bit_rate_;
|
||||
}
|
||||
|
||||
const int64_t &EncodingParams::video_buffer_size() const
|
||||
{
|
||||
return video_buffer_size_;
|
||||
}
|
||||
|
||||
const int &EncodingParams::video_threads() const
|
||||
{
|
||||
return video_threads_;
|
||||
}
|
||||
|
||||
const QString &EncodingParams::video_pix_fmt() const
|
||||
{
|
||||
return video_pix_fmt_;
|
||||
}
|
||||
|
||||
bool EncodingParams::audio_enabled() const
|
||||
{
|
||||
return audio_enabled_;
|
||||
}
|
||||
|
||||
const ExportCodec::Codec &EncodingParams::audio_codec() const
|
||||
{
|
||||
return audio_codec_;
|
||||
}
|
||||
|
||||
const AudioParams &EncodingParams::audio_params() const
|
||||
{
|
||||
return audio_params_;
|
||||
}
|
||||
|
||||
bool EncodingParams::subtitles_enabled() const
|
||||
{
|
||||
return subtitles_enabled_;
|
||||
}
|
||||
|
||||
ExportCodec::Codec EncodingParams::subtitles_codec() const
|
||||
{
|
||||
return subtitles_codec_;
|
||||
}
|
||||
|
||||
const rational &EncodingParams::GetExportLength() const
|
||||
{
|
||||
return export_length_;
|
||||
}
|
||||
|
||||
void EncodingParams::SetExportLength(const rational &export_length)
|
||||
{
|
||||
export_length_ = export_length;
|
||||
}
|
||||
|
||||
void EncodingParams::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeTextElement(QStringLiteral("filename"), filename_);
|
||||
|
||||
writer->writeStartElement(QStringLiteral("video"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("enabled"), QString::number(video_enabled_));
|
||||
|
||||
if (video_enabled_) {
|
||||
writer->writeTextElement(QStringLiteral("codec"), QString::number(video_codec_));
|
||||
writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width()));
|
||||
writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height()));
|
||||
writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format()));
|
||||
writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString());
|
||||
writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider()));
|
||||
writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("minbitrate"), QString::number(video_max_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("maxbitrate"), QString::number(video_max_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("bufsize"), QString::number(video_buffer_size_));
|
||||
writer->writeTextElement(QStringLiteral("threads"), QString::number(video_threads_));
|
||||
|
||||
if (!video_opts_.isEmpty()) {
|
||||
writer->writeStartElement(QStringLiteral("opts"));
|
||||
|
||||
QHash<QString, QString>::const_iterator i;
|
||||
for (i=video_opts_.constBegin(); i!=video_opts_.constEnd(); i++) {
|
||||
writer->writeStartElement(QStringLiteral("entry"));
|
||||
|
||||
writer->writeTextElement(QStringLiteral("key"), i.key());
|
||||
writer->writeTextElement(QStringLiteral("value"), i.value());
|
||||
|
||||
writer->writeEndElement(); // entry
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // opts
|
||||
}
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // video
|
||||
|
||||
writer->writeStartElement(QStringLiteral("audio"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("enabled"), QString::number(audio_enabled_));
|
||||
|
||||
if (audio_enabled_) {
|
||||
writer->writeTextElement(QStringLiteral("codec"), QString::number(audio_codec_));
|
||||
writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate()));
|
||||
writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout()));
|
||||
writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format()));
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // audio
|
||||
}
|
||||
|
||||
Encoder* Encoder::CreateFromID(Type id, const EncodingParams& params)
|
||||
{
|
||||
switch (id) {
|
||||
case kEncoderTypeNone:
|
||||
break;
|
||||
case kEncoderTypeFFmpeg:
|
||||
return new FFmpegEncoder(params);
|
||||
case kEncoderTypeOIIO:
|
||||
return new OIIOEncoder(params);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Encoder::Type Encoder::GetTypeFromFormat(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case ExportFormat::kFormatDNxHD:
|
||||
case ExportFormat::kFormatMatroska:
|
||||
case ExportFormat::kFormatQuickTime:
|
||||
case ExportFormat::kFormatMPEG4:
|
||||
case ExportFormat::kFormatWAV:
|
||||
case ExportFormat::kFormatAIFF:
|
||||
case ExportFormat::kFormatMP3:
|
||||
case ExportFormat::kFormatFLAC:
|
||||
case ExportFormat::kFormatOgg:
|
||||
case ExportFormat::kFormatWebM:
|
||||
case ExportFormat::kFormatSRT:
|
||||
return kEncoderTypeFFmpeg;
|
||||
case ExportFormat::kFormatOpenEXR:
|
||||
case ExportFormat::kFormatPNG:
|
||||
case ExportFormat::kFormatTIFF:
|
||||
return kEncoderTypeOIIO;
|
||||
case ExportFormat::kFormatCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return kEncoderTypeNone;
|
||||
}
|
||||
|
||||
Encoder *Encoder::CreateFromFormat(ExportFormat::Format f, const EncodingParams ¶ms)
|
||||
{
|
||||
return CreateFromID(GetTypeFromFormat(f), params);
|
||||
}
|
||||
|
||||
QStringList Encoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
|
||||
{
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef ENCODER_H
|
||||
#define ENCODER_H
|
||||
|
||||
#include <memory>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "codec/exportcodec.h"
|
||||
#include "codec/exportformat.h"
|
||||
#include "codec/frame.h"
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "common/timerange.h"
|
||||
#include "node/block/subtitle/subtitle.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/subtitleparams.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class Encoder;
|
||||
using EncoderPtr = std::shared_ptr<Encoder>;
|
||||
|
||||
class EncodingParams {
|
||||
public:
|
||||
EncodingParams();
|
||||
|
||||
void SetFilename(const QString& filename);
|
||||
|
||||
void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec);
|
||||
void EnableAudio(const AudioParams& audio_params, const ExportCodec::Codec &acodec);
|
||||
void EnableSubtitles(const ExportCodec::Codec &scodec);
|
||||
|
||||
void set_video_option(const QString& key, const QString& value);
|
||||
void set_video_bit_rate(const int64_t& rate);
|
||||
void set_video_min_bit_rate(const int64_t& rate);
|
||||
void set_video_max_bit_rate(const int64_t& rate);
|
||||
void set_video_buffer_size(const int64_t& sz);
|
||||
void set_video_threads(const int& threads);
|
||||
void set_video_pix_fmt(const QString& s);
|
||||
void set_video_is_image_sequence(bool s)
|
||||
{
|
||||
video_is_image_sequence_ = s;
|
||||
}
|
||||
|
||||
const QString& filename() const;
|
||||
|
||||
bool video_enabled() const;
|
||||
const ExportCodec::Codec& video_codec() const;
|
||||
const VideoParams& video_params() const;
|
||||
const QHash<QString, QString>& video_opts() const;
|
||||
const int64_t& video_bit_rate() const;
|
||||
const int64_t& video_min_bit_rate() const;
|
||||
const int64_t& video_max_bit_rate() const;
|
||||
const int64_t& video_buffer_size() const;
|
||||
const int& video_threads() const;
|
||||
const QString& video_pix_fmt() const;
|
||||
bool video_is_image_sequence() const
|
||||
{
|
||||
return video_is_image_sequence_;
|
||||
}
|
||||
|
||||
bool audio_enabled() const;
|
||||
const ExportCodec::Codec &audio_codec() const;
|
||||
const AudioParams& audio_params() const;
|
||||
|
||||
const int64_t& audio_bit_rate() const
|
||||
{
|
||||
return audio_bit_rate_;
|
||||
}
|
||||
|
||||
void set_audio_bit_rate(const int64_t& b)
|
||||
{
|
||||
audio_bit_rate_ = b;
|
||||
}
|
||||
|
||||
bool subtitles_enabled() const;
|
||||
ExportCodec::Codec subtitles_codec() const;
|
||||
|
||||
const rational& GetExportLength() const;
|
||||
void SetExportLength(const rational& GetExportLength);
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const;
|
||||
|
||||
private:
|
||||
QString filename_;
|
||||
|
||||
bool video_enabled_;
|
||||
ExportCodec::Codec video_codec_;
|
||||
VideoParams video_params_;
|
||||
QHash<QString, QString> video_opts_;
|
||||
int64_t video_bit_rate_;
|
||||
int64_t video_min_bit_rate_;
|
||||
int64_t video_max_bit_rate_;
|
||||
int64_t video_buffer_size_;
|
||||
int video_threads_;
|
||||
QString video_pix_fmt_;
|
||||
bool video_is_image_sequence_;
|
||||
|
||||
bool audio_enabled_;
|
||||
ExportCodec::Codec audio_codec_;
|
||||
AudioParams audio_params_;
|
||||
int64_t audio_bit_rate_;
|
||||
|
||||
bool subtitles_enabled_;
|
||||
ExportCodec::Codec subtitles_codec_;
|
||||
|
||||
rational export_length_;
|
||||
|
||||
};
|
||||
|
||||
class Encoder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Encoder(const EncodingParams& params);
|
||||
|
||||
enum Type {
|
||||
kEncoderTypeNone = -1,
|
||||
kEncoderTypeFFmpeg,
|
||||
kEncoderTypeOIIO
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create a Encoder instance using a Encoder ID
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* A Encoder instance or nullptr if a Decoder with this ID does not exist
|
||||
*/
|
||||
static Encoder *CreateFromID(Type id, const EncodingParams ¶ms);
|
||||
|
||||
static Type GetTypeFromFormat(ExportFormat::Format f);
|
||||
|
||||
static Encoder *CreateFromFormat(ExportFormat::Format f, const EncodingParams ¶ms);
|
||||
|
||||
virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const;
|
||||
|
||||
const EncodingParams& params() const;
|
||||
|
||||
virtual VideoParams::Format GetDesiredPixelFormat() const
|
||||
{
|
||||
return VideoParams::kFormatInvalid;
|
||||
}
|
||||
|
||||
const QString& GetError() const
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
QString GetFilenameForFrame(const rational& frame);
|
||||
|
||||
static int GetImageSequencePlaceholderDigitCount(const QString& filename);
|
||||
|
||||
static bool FilenameContainsDigitPlaceholder(const QString &filename);
|
||||
static QString FilenameRemoveDigitPlaceholder(QString filename);
|
||||
|
||||
static const QRegularExpression kImageSequenceContainsDigits;
|
||||
static const QRegularExpression kImageSequenceRemoveDigits;
|
||||
|
||||
public slots:
|
||||
virtual bool Open() = 0;
|
||||
|
||||
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) = 0;
|
||||
virtual bool WriteAudio(olive::SampleBufferPtr audio) = 0;
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) = 0;
|
||||
|
||||
virtual void Close() = 0;
|
||||
|
||||
protected:
|
||||
void SetError(const QString& err)
|
||||
{
|
||||
error_ = err;
|
||||
}
|
||||
|
||||
private:
|
||||
EncodingParams params_;
|
||||
|
||||
QString error_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // ENCODER_H
|
||||
@@ -0,0 +1,106 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "exportcodec.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
namespace olive {
|
||||
|
||||
QString ExportCodec::GetCodecName(ExportCodec::Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case kCodecDNxHD:
|
||||
return tr("DNxHD");
|
||||
case kCodecH264:
|
||||
return tr("H.264");
|
||||
case kCodecH264rgb:
|
||||
return tr("H.264 RGB");
|
||||
case kCodecH265:
|
||||
return tr("H.265");
|
||||
case kCodecOpenEXR:
|
||||
return tr("OpenEXR");
|
||||
case kCodecPNG:
|
||||
return tr("PNG");
|
||||
case kCodecProRes:
|
||||
return tr("ProRes");
|
||||
case kCodecCineform:
|
||||
return tr("Cineform");
|
||||
case kCodecTIFF:
|
||||
return tr("TIFF");
|
||||
case kCodecMP2:
|
||||
return tr("MP2");
|
||||
case kCodecMP3:
|
||||
return tr("MP3");
|
||||
case kCodecAAC:
|
||||
return tr("AAC");
|
||||
case kCodecPCM:
|
||||
return tr("PCM (Uncompressed)");
|
||||
case kCodecFLAC:
|
||||
return tr("FLAC");
|
||||
case kCodecOpus:
|
||||
return tr("Opus");
|
||||
case kCodecVorbis:
|
||||
return tr("Vorbis");
|
||||
case kCodecVP9:
|
||||
return tr("VP9");
|
||||
case kCodecSRT:
|
||||
return tr("SubRip SRT");
|
||||
case kCodecCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return tr("Unknown");
|
||||
}
|
||||
|
||||
bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case kCodecDNxHD:
|
||||
case kCodecH264:
|
||||
case kCodecH264rgb:
|
||||
case kCodecH265:
|
||||
case kCodecProRes:
|
||||
case kCodecCineform:
|
||||
case kCodecMP2:
|
||||
case kCodecMP3:
|
||||
case kCodecAAC:
|
||||
case kCodecPCM:
|
||||
case kCodecVorbis:
|
||||
case kCodecOpus:
|
||||
case kCodecFLAC:
|
||||
case kCodecVP9:
|
||||
case kCodecSRT:
|
||||
return false;
|
||||
case kCodecOpenEXR:
|
||||
case kCodecPNG:
|
||||
case kCodecTIFF:
|
||||
return true;
|
||||
case kCodecCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EXPORTCODEC_H
|
||||
#define EXPORTCODEC_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "render/subtitleparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ExportCodec : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Codec {
|
||||
// Video codecs
|
||||
kCodecDNxHD,
|
||||
kCodecH264,
|
||||
kCodecH264rgb,
|
||||
kCodecH265,
|
||||
kCodecOpenEXR,
|
||||
kCodecPNG,
|
||||
kCodecProRes,
|
||||
kCodecCineform,
|
||||
kCodecTIFF,
|
||||
kCodecVP9,
|
||||
|
||||
// Audio codecs
|
||||
kCodecMP2,
|
||||
kCodecMP3,
|
||||
kCodecAAC,
|
||||
kCodecPCM,
|
||||
kCodecOpus,
|
||||
kCodecVorbis,
|
||||
kCodecFLAC,
|
||||
|
||||
// Subtitle codecs
|
||||
kCodecSRT,
|
||||
|
||||
kCodecCount
|
||||
};
|
||||
|
||||
static QString GetCodecName(Codec c);
|
||||
|
||||
static bool IsCodecAStillImage(Codec c);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EXPORTCODEC_H
|
||||
@@ -0,0 +1,214 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "exportformat.h"
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
QString ExportFormat::GetName(olive::ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case kFormatDNxHD:
|
||||
return tr("DNxHD");
|
||||
case kFormatMatroska:
|
||||
return tr("Matroska Video");
|
||||
case kFormatMPEG4:
|
||||
return tr("MPEG-4 Video");
|
||||
case kFormatOpenEXR:
|
||||
return tr("OpenEXR");
|
||||
case kFormatPNG:
|
||||
return tr("PNG");
|
||||
case kFormatTIFF:
|
||||
return tr("TIFF");
|
||||
case kFormatQuickTime:
|
||||
return tr("QuickTime");
|
||||
case kFormatWAV:
|
||||
return tr("Wave Audio");
|
||||
case kFormatAIFF:
|
||||
return tr("AIFF");
|
||||
case kFormatMP3:
|
||||
return tr("MP3");
|
||||
case kFormatFLAC:
|
||||
return tr("FLAC");
|
||||
case kFormatOgg:
|
||||
return tr("Ogg");
|
||||
case kFormatWebM:
|
||||
return tr("WebM");
|
||||
case kFormatSRT:
|
||||
return tr("SubRip SRT");
|
||||
|
||||
case kFormatCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return tr("Unknown");
|
||||
}
|
||||
|
||||
QString ExportFormat::GetExtension(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case kFormatDNxHD:
|
||||
return QStringLiteral("mxf");
|
||||
case kFormatMatroska:
|
||||
return QStringLiteral("mkv");
|
||||
case kFormatMPEG4:
|
||||
return QStringLiteral("mp4");
|
||||
case kFormatOpenEXR:
|
||||
return QStringLiteral("exr");
|
||||
case kFormatPNG:
|
||||
return QStringLiteral("png");
|
||||
case kFormatTIFF:
|
||||
return QStringLiteral("tiff");
|
||||
case kFormatQuickTime:
|
||||
return QStringLiteral("mov");
|
||||
case kFormatWAV:
|
||||
return QStringLiteral("wav");
|
||||
case kFormatAIFF:
|
||||
return QStringLiteral("aiff");
|
||||
case kFormatMP3:
|
||||
return QStringLiteral("mp3");
|
||||
case kFormatFLAC:
|
||||
return QStringLiteral("flac");
|
||||
case kFormatOgg:
|
||||
return QStringLiteral("ogg");
|
||||
case kFormatWebM:
|
||||
return QStringLiteral("webm");
|
||||
case kFormatSRT:
|
||||
return QStringLiteral("srt");
|
||||
case kFormatCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case kFormatDNxHD:
|
||||
return {ExportCodec::kCodecDNxHD};
|
||||
case kFormatMatroska:
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecVP9};
|
||||
case kFormatMPEG4:
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265};
|
||||
case kFormatOpenEXR:
|
||||
return {ExportCodec::kCodecOpenEXR};
|
||||
case kFormatPNG:
|
||||
return {ExportCodec::kCodecPNG};
|
||||
case kFormatTIFF:
|
||||
return {ExportCodec::kCodecTIFF};
|
||||
case kFormatQuickTime:
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecProRes, ExportCodec::kCodecCineform};
|
||||
case kFormatWebM:
|
||||
return {ExportCodec::kCodecVP9};
|
||||
case kFormatOgg:
|
||||
case kFormatWAV:
|
||||
case kFormatAIFF:
|
||||
case kFormatMP3:
|
||||
case kFormatFLAC:
|
||||
case kFormatSRT:
|
||||
case kFormatCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<ExportCodec::Codec> ExportFormat::GetAudioCodecs(ExportFormat::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
// Video/audio formats
|
||||
case kFormatDNxHD:
|
||||
return {ExportCodec::kCodecPCM};
|
||||
case kFormatMatroska:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus, ExportCodec::kCodecFLAC};
|
||||
case kFormatMPEG4:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3};
|
||||
case kFormatQuickTime:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM};
|
||||
case kFormatWebM:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus};
|
||||
|
||||
// Audio only formats
|
||||
case kFormatWAV:
|
||||
return {ExportCodec::kCodecPCM};
|
||||
case kFormatAIFF:
|
||||
return {ExportCodec::kCodecPCM};
|
||||
case kFormatMP3:
|
||||
return {ExportCodec::kCodecMP3};
|
||||
case kFormatFLAC:
|
||||
return {ExportCodec::kCodecFLAC};
|
||||
case kFormatOgg:
|
||||
return {ExportCodec::kCodecOpus, ExportCodec::kCodecVorbis, ExportCodec::kCodecPCM};
|
||||
|
||||
// Video only formats
|
||||
case kFormatOpenEXR:
|
||||
case kFormatPNG:
|
||||
case kFormatTIFF:
|
||||
case kFormatSRT:
|
||||
case kFormatCount:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<ExportCodec::Codec> ExportFormat::GetSubtitleCodecs(Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case kFormatDNxHD:
|
||||
case kFormatMPEG4:
|
||||
case kFormatOpenEXR:
|
||||
case kFormatQuickTime:
|
||||
case kFormatPNG:
|
||||
case kFormatTIFF:
|
||||
case kFormatWAV:
|
||||
case kFormatAIFF:
|
||||
case kFormatMP3:
|
||||
case kFormatFLAC:
|
||||
case kFormatOgg:
|
||||
case kFormatWebM:
|
||||
case kFormatCount:
|
||||
break;
|
||||
case kFormatMatroska:
|
||||
case kFormatSRT:
|
||||
return {ExportCodec::kCodecSRT};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QStringList ExportFormat::GetPixelFormatsForCodec(ExportFormat::Format f, ExportCodec::Codec c)
|
||||
{
|
||||
Encoder* e = Encoder::CreateFromFormat(f, EncodingParams());
|
||||
QStringList list;
|
||||
|
||||
if (e) {
|
||||
list = e->GetPixelFormatsForCodec(c);
|
||||
delete e;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EXPORTFORMAT_H
|
||||
#define EXPORTFORMAT_H
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "exportcodec.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ExportFormat : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Format {
|
||||
kFormatDNxHD,
|
||||
kFormatMatroska,
|
||||
kFormatMPEG4,
|
||||
kFormatOpenEXR,
|
||||
kFormatQuickTime,
|
||||
kFormatPNG,
|
||||
kFormatTIFF,
|
||||
kFormatWAV,
|
||||
kFormatAIFF,
|
||||
kFormatMP3,
|
||||
kFormatFLAC,
|
||||
kFormatOgg,
|
||||
kFormatWebM,
|
||||
kFormatSRT,
|
||||
|
||||
kFormatCount
|
||||
};
|
||||
|
||||
static QString GetName(Format f);
|
||||
static QString GetExtension(Format f);
|
||||
static QList<ExportCodec::Codec> GetVideoCodecs(ExportFormat::Format f);
|
||||
static QList<ExportCodec::Codec> GetAudioCodecs(ExportFormat::Format f);
|
||||
static QList<ExportCodec::Codec> GetSubtitleCodecs(ExportFormat::Format f);
|
||||
|
||||
static QStringList GetPixelFormatsForCodec(Format f, ExportCodec::Codec c);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EXPORTFORMAT_H
|
||||
@@ -0,0 +1,26 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
codec/ffmpeg/ffmpegdecoder.h
|
||||
codec/ffmpeg/ffmpegdecoder.cpp
|
||||
codec/ffmpeg/ffmpegencoder.h
|
||||
codec/ffmpeg/ffmpegencoder.cpp
|
||||
codec/ffmpeg/ffmpegframepool.h
|
||||
codec/ffmpeg/ffmpegframepool.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef FFMPEGDECODER_H
|
||||
#define FFMPEGDECODER_H
|
||||
|
||||
// Fixes weird define issue when including <avfilter.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include <QAtomicInt>
|
||||
#include <QTimer>
|
||||
#include <QVector>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "codec/decoder.h"
|
||||
#include "ffmpegframepool.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder
|
||||
*/
|
||||
class FFmpegDecoder : public Decoder
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Constructor
|
||||
FFmpegDecoder();
|
||||
|
||||
// Destructor
|
||||
DECODER_DEFAULT_DESTRUCTOR(FFmpegDecoder)
|
||||
|
||||
virtual QString id() const override;
|
||||
|
||||
virtual bool SupportsVideo() override{return true;}
|
||||
virtual bool SupportsAudio() override{return true;}
|
||||
|
||||
virtual FootageDescription Probe(const QString &filename, const QAtomicInt *cancelled) const override;
|
||||
|
||||
protected:
|
||||
virtual bool OpenInternal() override;
|
||||
virtual FramePtr RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled) override;
|
||||
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled) override;
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
private:
|
||||
class Instance
|
||||
{
|
||||
public:
|
||||
Instance();
|
||||
|
||||
~Instance()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool Open(const char* filename, int stream_index);
|
||||
|
||||
void Close();
|
||||
|
||||
/**
|
||||
* @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_)
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* An FFmpeg error code, or >= 0 on success
|
||||
*/
|
||||
int GetFrame(AVPacket* pkt, AVFrame* frame);
|
||||
|
||||
void Seek(int64_t timestamp);
|
||||
|
||||
AVFormatContext* fmt_ctx() const
|
||||
{
|
||||
return fmt_ctx_;
|
||||
}
|
||||
|
||||
AVStream* avstream() const
|
||||
{
|
||||
return avstream_;
|
||||
}
|
||||
|
||||
private:
|
||||
AVFormatContext* fmt_ctx_;
|
||||
AVCodecContext* codec_ctx_;
|
||||
AVStream* avstream_;
|
||||
AVDictionary* opts_;
|
||||
|
||||
};
|
||||
|
||||
int GetFilteredFrame(AVPacket *packet, AVFrame *frame);
|
||||
|
||||
/**
|
||||
* @brief Handle an FFmpeg error code
|
||||
*
|
||||
* Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
|
||||
* function also automatically closes the Decoder.
|
||||
*
|
||||
* @param error_code
|
||||
*/
|
||||
static QString FFmpegError(int error_code);
|
||||
|
||||
bool InitScaler(const RetrieveVideoParams ¶ms);
|
||||
void FreeScaler();
|
||||
|
||||
static VideoParams::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
|
||||
static int GetNativeChannelCount(AVPixelFormat pix_fmt);
|
||||
|
||||
static uint64_t ValidateChannelLayout(AVStream *stream);
|
||||
|
||||
static const char* GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing);
|
||||
|
||||
FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const;
|
||||
|
||||
void ClearFrameCache();
|
||||
|
||||
FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time, const QAtomicInt *cancelled);
|
||||
|
||||
void RemoveFirstFrame();
|
||||
|
||||
RetrieveVideoParams filter_params_;
|
||||
AVFilterGraph* filter_graph_;
|
||||
AVFilterContext* buffersrc_ctx_;
|
||||
AVFilterContext* buffersink_ctx_;
|
||||
AVPixelFormat ideal_pix_fmt_;
|
||||
VideoParams::Format native_pix_fmt_;
|
||||
int native_channel_count_;
|
||||
|
||||
FFmpegFramePool pool_;
|
||||
|
||||
int64_t second_ts_;
|
||||
|
||||
QList<FFmpegFramePool::ElementPtr> cached_frames_;
|
||||
|
||||
bool is_working_;
|
||||
QMutex is_working_mutex_;
|
||||
|
||||
bool cache_at_zero_;
|
||||
bool cache_at_eof_;
|
||||
|
||||
Instance instance_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FFMPEGDECODER_H
|
||||
@@ -0,0 +1,883 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ffmpegencoder.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) :
|
||||
Encoder(params),
|
||||
fmt_ctx_(nullptr),
|
||||
video_stream_(nullptr),
|
||||
video_codec_ctx_(nullptr),
|
||||
video_alpha_scale_ctx_(nullptr),
|
||||
video_noalpha_scale_ctx_(nullptr),
|
||||
audio_stream_(nullptr),
|
||||
audio_codec_ctx_(nullptr),
|
||||
audio_resample_ctx_(nullptr),
|
||||
audio_frame_(nullptr),
|
||||
open_(false)
|
||||
{
|
||||
}
|
||||
|
||||
QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
|
||||
{
|
||||
QStringList pix_fmts;
|
||||
|
||||
const AVCodec* codec_info = GetEncoder(c);
|
||||
|
||||
if (codec_info) {
|
||||
for (int i=0; codec_info->pix_fmts[i]!=-1; i++) {
|
||||
const char* pix_fmt_name = av_get_pix_fmt_name(codec_info->pix_fmts[i]);
|
||||
pix_fmts.append(pix_fmt_name);
|
||||
}
|
||||
}
|
||||
|
||||
return pix_fmts;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::Open()
|
||||
{
|
||||
if (open_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int error_code;
|
||||
|
||||
// Convert QString to C string that FFmpeg expects
|
||||
QByteArray filename_bytes = params().filename().toUtf8();
|
||||
const char* filename_c_str = filename_bytes.constData();
|
||||
|
||||
// Create output format context
|
||||
error_code = avformat_alloc_output_context2(&fmt_ctx_, nullptr, nullptr, filename_c_str);
|
||||
|
||||
// Check error code
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to allocate output context"), error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize a video stream if it's enabled
|
||||
if (params().video_enabled()) {
|
||||
if (!InitializeStream(AVMEDIA_TYPE_VIDEO, &video_stream_, &video_codec_ctx_, params().video_codec())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is the format we will expect frames received in Write() to be in
|
||||
VideoParams::Format native_pixel_fmt = params().video_params().format();
|
||||
|
||||
// This is the format we will need to convert the frame to for swscale to understand it
|
||||
video_conversion_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(native_pixel_fmt);
|
||||
|
||||
// This is the equivalent pixel format above as an AVPixelFormat that swscale can understand
|
||||
AVPixelFormat src_alpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_,
|
||||
VideoParams::kRGBAChannelCount);
|
||||
|
||||
AVPixelFormat src_noalpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_,
|
||||
VideoParams::kRGBChannelCount);
|
||||
|
||||
if (src_alpha_pix_fmt == AV_PIX_FMT_NONE || src_noalpha_pix_fmt == AV_PIX_FMT_NONE) {
|
||||
SetError(tr("Failed to find suitable pixel format for this buffer"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is the pixel format the encoder wants to encode to
|
||||
AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt;
|
||||
|
||||
// Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it
|
||||
// before encoding. Even if we don't, this may be useful for converting between linesizes, etc.
|
||||
video_alpha_scale_ctx_ = sws_getContext(params().video_params().width(),
|
||||
params().video_params().height(),
|
||||
src_alpha_pix_fmt,
|
||||
params().video_params().width(),
|
||||
params().video_params().height(),
|
||||
encoder_pix_fmt,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
video_noalpha_scale_ctx_ = sws_getContext(params().video_params().width(),
|
||||
params().video_params().height(),
|
||||
src_noalpha_pix_fmt,
|
||||
params().video_params().width(),
|
||||
params().video_params().height(),
|
||||
encoder_pix_fmt,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
// Initialize an audio stream if it's enabled
|
||||
if (params().audio_enabled()) {
|
||||
if (!InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, &audio_codec_ctx_, params().audio_codec())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize a subtitle stream if it's enabled
|
||||
if (params().subtitles_enabled()) {
|
||||
if (!InitializeStream(AVMEDIA_TYPE_SUBTITLE, &subtitle_stream_, &subtitle_codec_ctx_, params().subtitles_codec())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
av_dump_format(fmt_ctx_, 0, filename_c_str, 1);
|
||||
|
||||
// Open output file for writing
|
||||
error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE);
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to open IO context"), error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write header
|
||||
error_code = avformat_write_header(fmt_ctx_, nullptr);
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to write format header"), error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
open_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
AVFrame* encoded_frame = av_frame_alloc();
|
||||
|
||||
int error_code;
|
||||
const char* input_data;
|
||||
int input_linesize;
|
||||
|
||||
// Frame must be video
|
||||
encoded_frame->width = frame->width();
|
||||
encoded_frame->height = frame->height();
|
||||
encoded_frame->format = video_codec_ctx_->pix_fmt;
|
||||
|
||||
// Set interlacing
|
||||
if (frame->video_params().interlacing() != VideoParams::kInterlaceNone) {
|
||||
encoded_frame->interlaced_frame = 1;
|
||||
|
||||
if (frame->video_params().interlacing() == VideoParams::kInterlacedTopFirst) {
|
||||
encoded_frame->top_field_first = 1;
|
||||
} else {
|
||||
encoded_frame->top_field_first = 0;
|
||||
}
|
||||
}
|
||||
|
||||
error_code = av_frame_get_buffer(encoded_frame, 0);
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to create AVFrame buffer"), error_code);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// We may need to convert this frame to a frame that swscale will understand
|
||||
if (frame->format() != video_conversion_fmt_) {
|
||||
frame = frame->convert(video_conversion_fmt_);
|
||||
}
|
||||
|
||||
// Use swscale context to convert formats/linesizes
|
||||
input_data = frame->const_data();
|
||||
input_linesize = frame->linesize_bytes();
|
||||
|
||||
error_code = sws_scale((frame->channel_count() == VideoParams::kRGBAChannelCount) ? video_alpha_scale_ctx_ : video_noalpha_scale_ctx_,
|
||||
reinterpret_cast<const uint8_t**>(&input_data),
|
||||
&input_linesize,
|
||||
0,
|
||||
frame->height(),
|
||||
encoded_frame->data,
|
||||
encoded_frame->linesize);
|
||||
|
||||
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to scale frame"), error_code);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
encoded_frame->pts = qRound64(time.toDouble() / av_q2d(video_codec_ctx_->time_base));
|
||||
|
||||
success = WriteAVFrame(encoded_frame, video_codec_ctx_, video_stream_);
|
||||
|
||||
fail:
|
||||
av_frame_free(&encoded_frame);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
{
|
||||
if (!InitializeResampleContext(audio)) {
|
||||
qCritical() << "Failed to initialize resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
|
||||
// Create input buffer
|
||||
int input_sample_count = 0;
|
||||
uint8_t** input_data = nullptr;
|
||||
if (audio) {
|
||||
input_sample_count = audio->sample_count();
|
||||
int input_linesize;
|
||||
|
||||
av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio->audio_params().channel_count(),
|
||||
input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true), 0);
|
||||
|
||||
for (int i=0; i<audio->audio_params().channel_count(); i++) {
|
||||
memcpy(input_data[i], audio->data(i), input_sample_count * audio->audio_params().bytes_per_sample_per_channel());
|
||||
}
|
||||
}
|
||||
|
||||
// Create output buffer
|
||||
int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400;
|
||||
uint8_t** output_data = nullptr;
|
||||
int output_linesize;
|
||||
av_samples_alloc_array_and_samples(&output_data, &output_linesize, audio_stream_->codecpar->channels,
|
||||
output_sample_count, static_cast<AVSampleFormat>(audio_stream_->codecpar->format), 0);
|
||||
|
||||
// Perform conversion
|
||||
int converted = swr_convert(audio_resample_ctx_, output_data, output_sample_count, const_cast<const uint8_t**>(input_data), input_sample_count);
|
||||
if (converted > 0) {
|
||||
// Split sample buffer into frames
|
||||
for (int i=0; i<converted; ) {
|
||||
int frame_remaining_samples = audio_max_samples_ - audio_frame_offset_;
|
||||
int converted_remaining_samples = converted - i;
|
||||
|
||||
int copy_length = qMin(frame_remaining_samples, converted_remaining_samples);
|
||||
|
||||
av_samples_copy(audio_frame_->data, output_data, audio_frame_offset_, i,
|
||||
copy_length,
|
||||
audio_frame_->channels, static_cast<AVSampleFormat>(audio_frame_->format));
|
||||
|
||||
audio_frame_offset_ += copy_length;
|
||||
i += copy_length;
|
||||
|
||||
if (audio_frame_offset_ == audio_max_samples_ || (i == converted && !input_data)) {
|
||||
// Got all the samples we needed, write the frame
|
||||
audio_frame_->pts = av_rescale_q(audio_write_count_, {1, audio_codec_ctx_->sample_rate}, audio_codec_ctx_->time_base);
|
||||
|
||||
WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_);
|
||||
audio_write_count_ += audio_frame_offset_;
|
||||
audio_frame_offset_ = 0;
|
||||
}
|
||||
}
|
||||
} else if (converted < 0) {
|
||||
FFmpegError(tr("Failed to resample audio"), converted);
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (!input_data && audio_frame_offset_ > 0) {
|
||||
audio_frame_->nb_samples = audio_frame_offset_;
|
||||
audio_frame_->pts = av_rescale_q(audio_write_count_, {1, audio_codec_ctx_->sample_rate}, audio_codec_ctx_->time_base);
|
||||
WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_);
|
||||
}
|
||||
|
||||
// Free buffers created
|
||||
if (output_data) {
|
||||
av_freep(&output_data[0]);
|
||||
av_freep(&output_data);
|
||||
}
|
||||
|
||||
if (input_data) {
|
||||
av_freep(&input_data[0]);
|
||||
av_freep(&input_data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString GetAssTime(const rational &time)
|
||||
{
|
||||
int64_t total_centiseconds = qRound64(time.toDouble() * 100);
|
||||
|
||||
int64_t cs = total_centiseconds % 100;
|
||||
int64_t ss = (total_centiseconds / 100) % 60;
|
||||
int64_t mm = (total_centiseconds / 6000) % 60;
|
||||
int64_t hh = total_centiseconds / 360000;
|
||||
|
||||
return QStringLiteral("%1:%2:%3.%4").arg(
|
||||
QString::number(hh),
|
||||
QStringLiteral("%1").arg(mm, 2, 10, QLatin1Char('0')),
|
||||
QStringLiteral("%1").arg(ss, 2, 10, QLatin1Char('0')),
|
||||
QStringLiteral("%1").arg(cs, 2, 10, QLatin1Char('0'))
|
||||
);
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block)
|
||||
{
|
||||
AVSubtitle subtitle;
|
||||
memset(&subtitle, 0, sizeof(subtitle));
|
||||
|
||||
AVSubtitleRect rect;
|
||||
memset(&rect, 0, sizeof(rect));
|
||||
|
||||
QString ass_line = QStringLiteral("Dialogue: 0,%1,%2,Default,,0,0,0,,%3").arg(
|
||||
GetAssTime(sub_block->in()),
|
||||
GetAssTime(sub_block->out()),
|
||||
sub_block->GetText()
|
||||
);
|
||||
|
||||
QByteArray utf8_sub = sub_block->GetText().toUtf8();
|
||||
QByteArray utf8_ass = ass_line.toUtf8();
|
||||
|
||||
rect.type = SUBTITLE_ASS;
|
||||
rect.text = utf8_sub.data();
|
||||
rect.ass = utf8_ass.data();
|
||||
|
||||
AVSubtitleRect *rect_array = ▭
|
||||
subtitle.num_rects = 1;
|
||||
subtitle.rects = &rect_array;
|
||||
|
||||
subtitle.pts = Timecode::time_to_timestamp(sub_block->in(), subtitle_codec_ctx_->time_base, Timecode::kFloor);
|
||||
subtitle.end_display_time = qRound64(sub_block->length().toDouble() * 1000);
|
||||
|
||||
QVector<uint8_t> out_buf(1024 * 1024);
|
||||
|
||||
int sub_sz = avcodec_encode_subtitle(subtitle_codec_ctx_, out_buf.data(), out_buf.size(), &subtitle);
|
||||
if (sub_sz < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AVPacket *pkt = av_packet_alloc();
|
||||
|
||||
pkt->stream_index = subtitle_stream_->index;
|
||||
pkt->data = out_buf.data();
|
||||
pkt->size = sub_sz;
|
||||
pkt->pts = subtitle.pts;
|
||||
pkt->duration = av_rescale_q(subtitle.end_display_time, {1, 1000}, subtitle_codec_ctx_->time_base);
|
||||
pkt->dts = pkt->pts;
|
||||
av_packet_rescale_ts(pkt, subtitle_codec_ctx_->time_base, subtitle_stream_->time_base);
|
||||
|
||||
int err = av_interleaved_write_frame(fmt_ctx_, pkt);
|
||||
bool ret = true;
|
||||
|
||||
if (err < 0) {
|
||||
FFmpegError(tr("Failed to write interleaved packet"), err);
|
||||
ret = false;
|
||||
}
|
||||
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file)
|
||||
{
|
||||
|
||||
|
||||
// Keep track of sample count to use as each frame's timebase
|
||||
int sample_counter = 0;
|
||||
|
||||
while (true) {
|
||||
// Calculate how many samples should input this frame
|
||||
int64_t samples_needed = av_rescale_rnd(maximum_frame_samples + swr_get_delay(swr_ctx, pcm_info.sample_rate()),
|
||||
audio_codec_ctx_->sample_rate,
|
||||
pcm_info.sample_rate(),
|
||||
AV_ROUND_UP);
|
||||
|
||||
// Calculate how many bytes this is
|
||||
int max_read = pcm_info.samples_to_bytes(samples_needed);
|
||||
|
||||
// Read bytes from PCM
|
||||
QByteArray input_data = file->read(max_read);
|
||||
|
||||
// Use swresample to convert the data into the correct format
|
||||
const char* input_data_array = input_data.constData();
|
||||
int converted = swr_convert(swr_ctx,
|
||||
|
||||
// output data
|
||||
frame->data,
|
||||
|
||||
// output sample count (maximum amount of samples in output)
|
||||
maximum_frame_samples,
|
||||
|
||||
// input data
|
||||
reinterpret_cast<const uint8_t**>(&input_data_array),
|
||||
|
||||
// input sample count (maximum amount of samples we read from pcm file)
|
||||
pcm_info.bytes_to_samples(input_data.size()));
|
||||
|
||||
// Update the frame's number of samples to the amount we actually received
|
||||
frame->nb_samples = converted;
|
||||
|
||||
// Update frame timestamp
|
||||
frame->pts = sample_counter;
|
||||
|
||||
// Increment timestamp for the next frame by the amount of samples in this one
|
||||
sample_counter += converted;
|
||||
|
||||
// Write the frame
|
||||
if (!WriteAVFrame(frame, audio_codec_ctx_, audio_stream_)) {
|
||||
qCritical() << "Failed to write audio AVFrame";
|
||||
break;
|
||||
}
|
||||
|
||||
// Break if we've reached the end point
|
||||
if (file->atEnd()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void FFmpegEncoder::Close()
|
||||
{
|
||||
if (open_) {
|
||||
// Flush encoders
|
||||
FlushEncoders();
|
||||
|
||||
// We've written a header, so we'll write a trailer
|
||||
av_write_trailer(fmt_ctx_);
|
||||
avio_closep(&fmt_ctx_->pb);
|
||||
|
||||
open_ = false;
|
||||
}
|
||||
|
||||
if (audio_resample_ctx_) {
|
||||
swr_init(audio_resample_ctx_);
|
||||
audio_resample_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (audio_frame_) {
|
||||
av_frame_free(&audio_frame_);
|
||||
audio_frame_ = nullptr;
|
||||
}
|
||||
|
||||
if (video_alpha_scale_ctx_) {
|
||||
sws_freeContext(video_alpha_scale_ctx_);
|
||||
video_alpha_scale_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (video_noalpha_scale_ctx_) {
|
||||
sws_freeContext(video_noalpha_scale_ctx_);
|
||||
video_noalpha_scale_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (video_codec_ctx_) {
|
||||
avcodec_free_context(&video_codec_ctx_);
|
||||
video_codec_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (audio_codec_ctx_) {
|
||||
avcodec_free_context(&audio_codec_ctx_);
|
||||
audio_codec_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (fmt_ctx_) {
|
||||
// NOTE: This also frees video_stream_ and audio_stream_
|
||||
avformat_free_context(fmt_ctx_);
|
||||
fmt_ctx_ = nullptr;
|
||||
video_stream_ = nullptr;
|
||||
audio_stream_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void FFmpegEncoder::FFmpegError(const QString& context, int error_code)
|
||||
{
|
||||
char err[1024];
|
||||
av_strerror(error_code, err, 1024);
|
||||
|
||||
QString formatted_err = tr("%1: %2 %3").arg(context, err, QString::number(error_code));
|
||||
qDebug() << formatted_err;
|
||||
SetError(formatted_err);
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteAVFrame(AVFrame *frame, AVCodecContext* codec_ctx, AVStream* stream)
|
||||
{
|
||||
// Send raw frame to the encoder
|
||||
int error_code = avcodec_send_frame(codec_ctx, frame);
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to send frame to encoder"), error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool succeeded = false;
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
|
||||
// Retrieve packets from encoder
|
||||
while (error_code >= 0) {
|
||||
error_code = avcodec_receive_packet(codec_ctx, pkt);
|
||||
|
||||
// EAGAIN just means the encoder wants another frame before encoding
|
||||
if (error_code == AVERROR(EAGAIN)) {
|
||||
break;
|
||||
} else if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to receive packet from decoder"), error_code);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Set packet stream index
|
||||
pkt->stream_index = stream->index;
|
||||
|
||||
av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
|
||||
|
||||
// Write packet to file
|
||||
error_code = av_interleaved_write_frame(fmt_ctx_, pkt);
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to write interleaved packet"), error_code);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Unref packet in case we're getting another
|
||||
av_packet_unref(pkt);
|
||||
}
|
||||
|
||||
succeeded = true;
|
||||
|
||||
fail:
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AVCodecContext** codec_ctx_ptr, const ExportCodec::Codec& codec)
|
||||
{
|
||||
if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO && type != AVMEDIA_TYPE_SUBTITLE) {
|
||||
SetError(tr("Cannot initialize a stream that is not a video, audio, or subtitle type"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find encoder
|
||||
const AVCodec* encoder = GetEncoder(codec);
|
||||
if (!encoder) {
|
||||
SetError(tr("Failed to find codec for 0x%1").arg(codec, 16));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (encoder->type != type) {
|
||||
SetError(tr("Retrieved unexpected codec type %1 for codec %2").arg(QString::number(encoder->type), codec));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitializeCodecContext(stream_ptr, codec_ctx_ptr, encoder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set codec parameters
|
||||
AVCodecContext* codec_ctx = *codec_ctx_ptr;
|
||||
AVStream* stream = *stream_ptr;
|
||||
|
||||
if (type == AVMEDIA_TYPE_VIDEO) {
|
||||
codec_ctx->width = params().video_params().width();
|
||||
codec_ctx->height = params().video_params().height();
|
||||
codec_ctx->sample_aspect_ratio = params().video_params().pixel_aspect_ratio().toAVRational();
|
||||
codec_ctx->time_base = params().video_params().frame_rate_as_time_base().toAVRational();
|
||||
codec_ctx->framerate = params().video_params().frame_rate().toAVRational();
|
||||
codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8());
|
||||
|
||||
if (params().video_params().interlacing() != VideoParams::kInterlaceNone) {
|
||||
// FIXME: I actually don't know what these flags do, the documentation helpfully doesn't
|
||||
// explain them at all. I hope using both of them is the right thing to do.
|
||||
codec_ctx->flags |= AV_CODEC_FLAG_INTERLACED_DCT | AV_CODEC_FLAG_INTERLACED_ME;
|
||||
|
||||
|
||||
if (params().video_params().interlacing() == VideoParams::kInterlacedTopFirst) {
|
||||
codec_ctx->field_order = AV_FIELD_TT;
|
||||
} else {
|
||||
codec_ctx->field_order = AV_FIELD_BB;
|
||||
|
||||
if (codec == ExportCodec::kCodecH264 || codec == ExportCodec::kCodecH264rgb) {
|
||||
// For some reason, FFmpeg doesn't set libx264's bff flag so we have to do it ourselves
|
||||
av_opt_set(codec_ctx->priv_data, "x264opts", "bff=1", AV_OPT_SEARCH_CHILDREN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set custom options
|
||||
{
|
||||
for (auto i=params().video_opts().begin();i!=params().video_opts().end();i++) {
|
||||
av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN);
|
||||
}
|
||||
|
||||
if (params().video_bit_rate() > 0) {
|
||||
codec_ctx->bit_rate = params().video_bit_rate();
|
||||
}
|
||||
|
||||
if (params().video_min_bit_rate() > 0) {
|
||||
codec_ctx->rc_min_rate = params().video_min_bit_rate();
|
||||
}
|
||||
|
||||
if (params().video_max_bit_rate() > 0) {
|
||||
codec_ctx->rc_max_rate = params().video_max_bit_rate();
|
||||
}
|
||||
|
||||
if (params().video_buffer_size() > 0) {
|
||||
codec_ctx->rc_buffer_size = static_cast<int>(params().video_buffer_size());
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type == AVMEDIA_TYPE_AUDIO) {
|
||||
|
||||
// Assume audio stream
|
||||
codec_ctx->sample_rate = params().audio_params().sample_rate();
|
||||
codec_ctx->channel_layout = params().audio_params().channel_layout();
|
||||
codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);
|
||||
codec_ctx->sample_fmt = encoder->sample_fmts[0];
|
||||
codec_ctx->time_base = {1, codec_ctx->sample_rate};
|
||||
|
||||
if (params().audio_bit_rate() > 0) {
|
||||
codec_ctx->bit_rate = params().audio_bit_rate();
|
||||
}
|
||||
|
||||
} else if (type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
|
||||
codec_ctx->time_base = av_get_time_base_q();
|
||||
|
||||
QByteArray ass_header = SubtitleParams::GenerateASSHeader().toUtf8();
|
||||
codec_ctx->subtitle_header = new uint8_t[ass_header.size()];
|
||||
memcpy(codec_ctx->subtitle_header, ass_header.constData(), ass_header.size());
|
||||
codec_ctx->subtitle_header_size = ass_header.size();
|
||||
|
||||
}
|
||||
|
||||
if (!SetupCodecContext(stream, codec_ctx, encoder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, const AVCodec* codec)
|
||||
{
|
||||
*stream = avformat_new_stream(fmt_ctx_, nullptr);
|
||||
if (!(*stream)) {
|
||||
SetError(tr("Failed to allocate AVStream"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate a codec context
|
||||
*codec_ctx = avcodec_alloc_context3(codec);
|
||||
if (!(*codec_ctx)) {
|
||||
SetError(tr("Failed to allocate AVCodecContext"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ctx, const AVCodec* codec)
|
||||
{
|
||||
int error_code;
|
||||
|
||||
if (fmt_ctx_->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
AVDictionary* codec_opts = nullptr;
|
||||
|
||||
// Set thread count
|
||||
if (params().video_threads() == 0) {
|
||||
av_dict_set(&codec_opts, "threads", "auto", 0);
|
||||
} else {
|
||||
QString thread_val = QString::number(params().video_threads());
|
||||
av_dict_set(&codec_opts, "threads", thread_val.toUtf8(), 0);
|
||||
}
|
||||
|
||||
// Try to open encoder
|
||||
error_code = avcodec_open2(codec_ctx, codec, &codec_opts);
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to open encoder"), error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy context settings to codecpar object
|
||||
error_code = avcodec_parameters_from_context(stream->codecpar, codec_ctx);
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to copy codec parameters to stream"), error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||
stream->avg_frame_rate = codec_ctx->framerate;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FFmpegEncoder::FlushEncoders()
|
||||
{
|
||||
if (video_codec_ctx_) {
|
||||
FlushCodecCtx(video_codec_ctx_, video_stream_);
|
||||
}
|
||||
|
||||
if (audio_codec_ctx_) {
|
||||
WriteAudio(nullptr);
|
||||
|
||||
FlushCodecCtx(audio_codec_ctx_, audio_stream_);
|
||||
}
|
||||
|
||||
if (fmt_ctx_) {
|
||||
if (fmt_ctx_->oformat->flags & AVFMT_ALLOW_FLUSH) {
|
||||
int r = av_interleaved_write_frame(fmt_ctx_, nullptr);
|
||||
if (r < 0) {
|
||||
FFmpegError(tr("Failed to write interleaved packet"), r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream)
|
||||
{
|
||||
avcodec_send_frame(codec_ctx, nullptr);
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
|
||||
int error_code;
|
||||
do {
|
||||
error_code = avcodec_receive_packet(codec_ctx, pkt);
|
||||
|
||||
if (error_code < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
pkt->stream_index = stream->index;
|
||||
av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
|
||||
int r = av_interleaved_write_frame(fmt_ctx_, pkt);
|
||||
if (r < 0) {
|
||||
FFmpegError(tr("Failed to write interleaved packet"), r);
|
||||
break;
|
||||
}
|
||||
av_packet_unref(pkt);
|
||||
} while (error_code >= 0);
|
||||
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
|
||||
{
|
||||
if (audio_resample_ctx_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create resample context
|
||||
audio_resample_ctx_ = swr_alloc_set_opts(nullptr,
|
||||
static_cast<int64_t>(audio_codec_ctx_->channel_layout),
|
||||
audio_codec_ctx_->sample_fmt,
|
||||
audio_codec_ctx_->sample_rate,
|
||||
static_cast<int64_t>(audio->audio_params().channel_layout()),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true),
|
||||
audio->audio_params().sample_rate(),
|
||||
0,
|
||||
nullptr);
|
||||
if (!audio_resample_ctx_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = swr_init(audio_resample_ctx_);
|
||||
if (err < 0) {
|
||||
FFmpegError(tr("Failed to create resampling context"), err);
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_max_samples_ = audio_codec_ctx_->frame_size;
|
||||
if (!audio_max_samples_) {
|
||||
// If not, use another frame size
|
||||
if (params().video_enabled()) {
|
||||
// If we're encoding video, use enough samples to cover roughly one frame of video
|
||||
audio_max_samples_ = params().audio_params().time_to_samples(params().video_params().frame_rate_as_time_base());
|
||||
} else {
|
||||
// If no video, just use an arbitrary number
|
||||
audio_max_samples_ = 256;
|
||||
}
|
||||
}
|
||||
|
||||
audio_frame_ = av_frame_alloc();
|
||||
if (!audio_frame_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_frame_->channel_layout = audio_codec_ctx_->channel_layout;
|
||||
audio_frame_->format = audio_codec_ctx_->sample_fmt;
|
||||
audio_frame_->nb_samples = audio_max_samples_;
|
||||
|
||||
err = av_frame_get_buffer(audio_frame_, 0);
|
||||
if (err < 0) {
|
||||
FFmpegError(tr("Failed to create audio frame"), err);
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_frame_offset_ = 0;
|
||||
audio_write_count_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case ExportCodec::kCodecH264:
|
||||
return avcodec_find_encoder_by_name("libx264");
|
||||
case ExportCodec::kCodecH264rgb:
|
||||
return avcodec_find_encoder_by_name("libx264rgb");
|
||||
case ExportCodec::kCodecDNxHD:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_DNXHD);
|
||||
case ExportCodec::kCodecProRes:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_PRORES);
|
||||
case ExportCodec::kCodecCineform:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_CFHD);
|
||||
case ExportCodec::kCodecH265:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_HEVC);
|
||||
case ExportCodec::kCodecVP9:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_VP9);
|
||||
case ExportCodec::kCodecOpenEXR:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_EXR);
|
||||
case ExportCodec::kCodecPNG:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_PNG);
|
||||
case ExportCodec::kCodecTIFF:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_TIFF);
|
||||
case ExportCodec::kCodecMP2:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_MP2);
|
||||
case ExportCodec::kCodecMP3:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_MP3);
|
||||
case ExportCodec::kCodecAAC:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_AAC);
|
||||
case ExportCodec::kCodecPCM:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
|
||||
case ExportCodec::kCodecFLAC:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_FLAC);
|
||||
case ExportCodec::kCodecOpus:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_OPUS);
|
||||
case ExportCodec::kCodecVorbis:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_VORBIS);
|
||||
case ExportCodec::kCodecSRT:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_SUBRIP);
|
||||
case ExportCodec::kCodecCount:
|
||||
// These are audio or invalid codecs and therefore have no pixel formats
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef FFMPEGENCODER_H
|
||||
#define FFMPEGENCODER_H
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
|
||||
#include "codec/encoder.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class FFmpegEncoder : public Encoder
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FFmpegEncoder(const EncodingParams ¶ms);
|
||||
|
||||
virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const override;
|
||||
|
||||
virtual bool Open() override;
|
||||
|
||||
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override;
|
||||
|
||||
virtual bool WriteAudio(olive::SampleBufferPtr audio) override;
|
||||
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
virtual VideoParams::Format GetDesiredPixelFormat() const override
|
||||
{
|
||||
return video_conversion_fmt_;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Handle an FFmpeg error code
|
||||
*
|
||||
* Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
|
||||
* function also automatically closes the Decoder.
|
||||
*
|
||||
* @param error_code
|
||||
*/
|
||||
void FFmpegError(const QString &context, int error_code);
|
||||
|
||||
bool WriteAVFrame(AVFrame* frame, AVCodecContext *codec_ctx, AVStream *stream);
|
||||
|
||||
bool InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const ExportCodec::Codec &codec);
|
||||
bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, const AVCodec* codec);
|
||||
bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, const AVCodec *codec);
|
||||
|
||||
void FlushEncoders();
|
||||
void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream);
|
||||
|
||||
bool InitializeResampleContext(SampleBufferPtr audio);
|
||||
|
||||
static const AVCodec *GetEncoder(ExportCodec::Codec c);
|
||||
|
||||
AVFormatContext* fmt_ctx_;
|
||||
|
||||
AVStream* video_stream_;
|
||||
AVCodecContext* video_codec_ctx_;
|
||||
SwsContext* video_alpha_scale_ctx_;
|
||||
SwsContext* video_noalpha_scale_ctx_;
|
||||
VideoParams::Format video_conversion_fmt_;
|
||||
|
||||
AVStream* audio_stream_;
|
||||
AVCodecContext* audio_codec_ctx_;
|
||||
SwrContext* audio_resample_ctx_;
|
||||
AVFrame* audio_frame_;
|
||||
int audio_max_samples_;
|
||||
int audio_frame_offset_;
|
||||
int audio_write_count_;
|
||||
|
||||
AVStream* subtitle_stream_;
|
||||
AVCodecContext* subtitle_codec_ctx_;
|
||||
|
||||
bool open_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FFMPEGENCODER_H
|
||||
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -18,43 +18,34 @@
|
||||
|
||||
***/
|
||||
|
||||
#include "dependency.h"
|
||||
#include "ffmpegframepool.h"
|
||||
|
||||
#include "node.h"
|
||||
#include "codec/frame.h"
|
||||
|
||||
NodeDependency::NodeDependency() :
|
||||
node_(nullptr)
|
||||
namespace olive {
|
||||
|
||||
FFmpegFramePool::FFmpegFramePool(int element_count) :
|
||||
MemoryPool(element_count),
|
||||
width_(0),
|
||||
height_(0),
|
||||
format_(VideoParams::kFormatInvalid),
|
||||
channel_count_(0)
|
||||
{
|
||||
}
|
||||
|
||||
NodeDependency::NodeDependency(Node *node, const TimeRange &range) :
|
||||
node_(node),
|
||||
range_(range)
|
||||
void FFmpegFramePool::SetParameters(int width, int height, VideoParams::Format format, int channel_count)
|
||||
{
|
||||
Clear();
|
||||
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
channel_count_ = channel_count;
|
||||
}
|
||||
|
||||
NodeDependency::NodeDependency(Node *node, const rational &in, const rational &out) :
|
||||
node_(node),
|
||||
range_(in, out)
|
||||
size_t FFmpegFramePool::GetElementSize()
|
||||
{
|
||||
return Frame::generate_linesize_bytes(width_, format_, channel_count_) * height_;
|
||||
}
|
||||
|
||||
Node *NodeDependency::node() const
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
|
||||
const rational& NodeDependency::in() const
|
||||
{
|
||||
return range_.in();
|
||||
}
|
||||
|
||||
const rational &NodeDependency::out() const
|
||||
{
|
||||
return range_.out();
|
||||
}
|
||||
|
||||
const TimeRange &NodeDependency::range() const
|
||||
{
|
||||
return range_;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef FFMPEGFRAMEPOOL_H
|
||||
#define FFMPEGFRAMEPOOL_H
|
||||
|
||||
#include "common/memorypool.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class FFmpegFramePool : public MemoryPool
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FFmpegFramePool(int element_count);
|
||||
|
||||
void SetParameters(int width, int height, VideoParams::Format format, int channel_count);
|
||||
|
||||
const int& width() const
|
||||
{
|
||||
return width_;
|
||||
}
|
||||
|
||||
const int& height() const
|
||||
{
|
||||
return height_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual size_t GetElementSize() override;
|
||||
|
||||
private:
|
||||
int width_;
|
||||
|
||||
int height_;
|
||||
|
||||
VideoParams::Format format_;
|
||||
|
||||
int channel_count_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FFMPEGFRAMEPOOL_H
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef FOOTAGEMETA_H
|
||||
#define FOOTAGEMETA_H
|
||||
|
||||
struct FootageData {
|
||||
struct StreamData {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
#endif // FOOTAGEMETA_H
|
||||
@@ -0,0 +1,180 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "frame.h"
|
||||
|
||||
#include <OpenImageIO/imagebuf.h>
|
||||
#include <QDebug>
|
||||
#include <QtGlobal>
|
||||
#include <QtMath>
|
||||
|
||||
#include "common/oiioutils.h"
|
||||
#include "render/framemanager.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
Frame::Frame() :
|
||||
data_(nullptr),
|
||||
data_size_(0),
|
||||
timestamp_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Frame::~Frame()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
FramePtr Frame::Create()
|
||||
{
|
||||
return std::make_shared<Frame>();
|
||||
}
|
||||
|
||||
const VideoParams &Frame::video_params() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
void Frame::set_video_params(const VideoParams ¶ms)
|
||||
{
|
||||
params_ = params;
|
||||
|
||||
linesize_ = generate_linesize_bytes(width(), params_.format(), params_.channel_count());
|
||||
linesize_pixels_ = linesize_ / params_.GetBytesPerPixel();
|
||||
}
|
||||
|
||||
FramePtr Frame::Interlace(FramePtr top, FramePtr bottom)
|
||||
{
|
||||
if (top->video_params() != bottom->video_params()) {
|
||||
qCritical() << "Tried to interlace two frames that had incompatible parameters";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FramePtr interlaced = Frame::Create();
|
||||
interlaced->set_video_params(top->video_params());
|
||||
interlaced->allocate();
|
||||
|
||||
int linesize = interlaced->linesize_bytes();
|
||||
|
||||
for (int i=0; i<interlaced->height(); i++) {
|
||||
FramePtr which = (i%2 == 0) ? top : bottom;
|
||||
|
||||
memcpy(interlaced->data() + i*linesize,
|
||||
which->const_data() + i*linesize,
|
||||
linesize);
|
||||
}
|
||||
|
||||
return interlaced;
|
||||
}
|
||||
|
||||
int Frame::generate_linesize_bytes(int width, VideoParams::Format format, int channel_count)
|
||||
{
|
||||
// Align to 32 bytes (not sure if this is necessary?)
|
||||
return VideoParams::GetBytesPerPixel(format, channel_count) * ((width + 31) & ~31);
|
||||
}
|
||||
|
||||
Color Frame::get_pixel(int x, int y) const
|
||||
{
|
||||
if (!contains_pixel(x, y)) {
|
||||
return Color();
|
||||
}
|
||||
|
||||
int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel();
|
||||
|
||||
return Color(reinterpret_cast<const char*>(data_ + byte_offset), video_params().format(), video_params().channel_count());
|
||||
}
|
||||
|
||||
bool Frame::contains_pixel(int x, int y) const
|
||||
{
|
||||
return (is_allocated() && x >= 0 && x < width() && y >= 0 && y < height());
|
||||
}
|
||||
|
||||
void Frame::set_pixel(int x, int y, const Color &c)
|
||||
{
|
||||
if (!contains_pixel(x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel();
|
||||
|
||||
c.toData(reinterpret_cast<char*>(data_ + byte_offset), video_params().format(), video_params().channel_count());
|
||||
}
|
||||
|
||||
bool Frame::allocate()
|
||||
{
|
||||
// Assume this frame is intended to be a video frame
|
||||
if (!params_.is_valid()) {
|
||||
qWarning() << "Tried to allocate a frame with invalid parameters";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_allocated()) {
|
||||
// Already allocated
|
||||
return true;
|
||||
}
|
||||
|
||||
data_size_ = linesize_ * height();
|
||||
data_ = FrameManager::Allocate(data_size_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Frame::destroy()
|
||||
{
|
||||
if (is_allocated()) {
|
||||
FrameManager::Deallocate(data_size_, data_);
|
||||
|
||||
data_size_ = 0;
|
||||
data_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
FramePtr Frame::convert(VideoParams::Format format) const
|
||||
{
|
||||
// Create new params with destination format
|
||||
VideoParams params = params_;
|
||||
params.set_format(format);
|
||||
|
||||
// Create new frame
|
||||
FramePtr converted = Frame::Create();
|
||||
converted->set_video_params(params);
|
||||
converted->set_timestamp(timestamp_);
|
||||
converted->allocate();
|
||||
|
||||
// Do the conversion through OIIO for convenience
|
||||
OIIO::ImageBuf src(OIIO::ImageSpec(width(), height(),
|
||||
channel_count(),
|
||||
OIIOUtils::GetOIIOBaseTypeFromFormat(this->format())));
|
||||
|
||||
OIIOUtils::FrameToBuffer(this, &src);
|
||||
|
||||
OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(),
|
||||
channel_count(),
|
||||
OIIOUtils::GetOIIOBaseTypeFromFormat(format)));
|
||||
|
||||
if (dst.copy_pixels(src)) {
|
||||
OIIOUtils::BufferToFrame(&dst, converted.get());
|
||||
return converted;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -24,83 +24,101 @@
|
||||
#include <memory>
|
||||
#include <QVector>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "common/rational.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/pixelformat.h"
|
||||
#include "render/color.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class Frame;
|
||||
using FramePtr = std::shared_ptr<Frame>;
|
||||
|
||||
/**
|
||||
* @brief Video frame data or audio sample data from a Decoder
|
||||
*
|
||||
* Abstraction from AVFrame. Currently a simple AVFrame wrapper.
|
||||
*
|
||||
* This class does not support copying at this time.
|
||||
*/
|
||||
class Frame
|
||||
{
|
||||
public:
|
||||
/// Normal constructor
|
||||
Frame();
|
||||
|
||||
~Frame();
|
||||
|
||||
DISABLE_COPY_MOVE(Frame)
|
||||
|
||||
static FramePtr Create();
|
||||
|
||||
/**
|
||||
* @brief Get frame's width in pixels
|
||||
*/
|
||||
const int& width();
|
||||
void set_width(const int& width);
|
||||
const VideoParams& video_params() const;
|
||||
void set_video_params(const VideoParams& params);
|
||||
|
||||
/**
|
||||
* @brief Get frame's height in pixels
|
||||
*/
|
||||
const int& height();
|
||||
void set_height(const int& height);
|
||||
static FramePtr Interlace(FramePtr top, FramePtr bottom);
|
||||
|
||||
const AudioRenderingParams& audio_params();
|
||||
void set_audio_params(const AudioRenderingParams& params);
|
||||
static int generate_linesize_bytes(int width, VideoParams::Format format, int channel_count);
|
||||
|
||||
const int &sample_count();
|
||||
void set_sample_count(const int &sample_count);
|
||||
int linesize_pixels() const
|
||||
{
|
||||
return linesize_pixels_;
|
||||
}
|
||||
|
||||
int linesize_bytes() const
|
||||
{
|
||||
return linesize_;
|
||||
}
|
||||
|
||||
int width() const
|
||||
{
|
||||
return params_.effective_width();
|
||||
}
|
||||
|
||||
int height() const
|
||||
{
|
||||
return params_.effective_height();
|
||||
}
|
||||
|
||||
VideoParams::Format format() const
|
||||
{
|
||||
return params_.format();
|
||||
}
|
||||
|
||||
int channel_count() const
|
||||
{
|
||||
return params_.channel_count();
|
||||
}
|
||||
|
||||
Color get_pixel(int x, int y) const;
|
||||
bool contains_pixel(int x, int y) const;
|
||||
void set_pixel(int x, int y, const Color& c);
|
||||
|
||||
/**
|
||||
* @brief Get frame's timestamp.
|
||||
*
|
||||
* This timestamp is always a rational that will equate to the time in seconds.
|
||||
*/
|
||||
const rational& timestamp();
|
||||
void set_timestamp(const rational& timestamp);
|
||||
const rational& timestamp() const
|
||||
{
|
||||
return timestamp_;
|
||||
}
|
||||
|
||||
/*const int64_t& native_timestamp();
|
||||
void set_native_timestamp(const int64_t& timestamp);*/
|
||||
|
||||
/**
|
||||
* @brief Get frame's format
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* Currently this will either be an olive::PixelFormat (video) or an olive::SampleFormat (audio).
|
||||
*/
|
||||
const olive::PixelFormat& format();
|
||||
void set_format(const olive::PixelFormat& format);
|
||||
|
||||
/**
|
||||
* @brief Returns a copy of the data in this frame as a QByteArray
|
||||
*
|
||||
* Will always do a deep copy. If you want to affect the data directly, use data() instead.
|
||||
*/
|
||||
QByteArray ToByteArray();
|
||||
void set_timestamp(const rational& timestamp)
|
||||
{
|
||||
timestamp_ = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the data buffer of this frame
|
||||
*/
|
||||
char* data();
|
||||
char* data()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the const data buffer of this frame
|
||||
*/
|
||||
const char* const_data();
|
||||
const char* const_data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate memory buffer to store data based on parameters
|
||||
@@ -109,7 +127,15 @@ public:
|
||||
*
|
||||
* If a memory buffer has been previously allocated without destroying, this function will destroy it.
|
||||
*/
|
||||
void allocate();
|
||||
bool allocate();
|
||||
|
||||
/**
|
||||
* @brief Return whether the frame is allocated or not
|
||||
*/
|
||||
bool is_allocated() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy a memory buffer allocated with allocate()
|
||||
@@ -121,25 +147,29 @@ public:
|
||||
*
|
||||
* Returns 0 if nothing is allocated.
|
||||
*/
|
||||
int allocated_size() const;
|
||||
int allocated_size() const
|
||||
{
|
||||
return data_size_;
|
||||
}
|
||||
|
||||
FramePtr convert(VideoParams::Format format) const;
|
||||
|
||||
private:
|
||||
int width_;
|
||||
VideoParams params_;
|
||||
|
||||
int height_;
|
||||
|
||||
olive::PixelFormat format_;
|
||||
|
||||
AudioRenderingParams audio_params_;
|
||||
|
||||
int sample_count_;
|
||||
|
||||
QByteArray data_;
|
||||
char* data_;
|
||||
int data_size_;
|
||||
|
||||
rational timestamp_;
|
||||
|
||||
int64_t native_timestamp_;
|
||||
int linesize_;
|
||||
|
||||
int linesize_pixels_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::FramePtr)
|
||||
|
||||
#endif // FRAME_H
|
||||
@@ -0,0 +1,24 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
codec/oiio/oiiodecoder.cpp
|
||||
codec/oiio/oiiodecoder.h
|
||||
codec/oiio/oiioencoder.cpp
|
||||
codec/oiio/oiioencoder.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,220 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "oiiodecoder.h"
|
||||
|
||||
#include <OpenImageIO/imagebufalgo.h>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "common/oiioutils.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
QStringList OIIODecoder::supported_formats_;
|
||||
|
||||
OIIODecoder::OIIODecoder() :
|
||||
image_(nullptr),
|
||||
buffer_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
QString OIIODecoder::id() const
|
||||
{
|
||||
return QStringLiteral("oiio");
|
||||
}
|
||||
|
||||
FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt* cancelled) const
|
||||
{
|
||||
Q_UNUSED(cancelled)
|
||||
|
||||
FootageDescription desc(id());
|
||||
|
||||
// Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying
|
||||
// to open a file that it can't if it's given one
|
||||
if (!FileTypeIsSupported(filename)) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
std::string std_filename = filename.toStdString();
|
||||
|
||||
auto in = OIIO::ImageInput::open(std_filename);
|
||||
|
||||
if (!in) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
// Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle
|
||||
// it better
|
||||
if (!strcmp(in->format_name(), "FFmpeg movie")) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
VideoParams video_params;
|
||||
|
||||
video_params.set_stream_index(0);
|
||||
video_params.set_width(in->spec().width);
|
||||
video_params.set_height(in->spec().height);
|
||||
video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(in->spec().format.basetype)));
|
||||
video_params.set_channel_count(in->spec().nchannels);
|
||||
video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec()));
|
||||
video_params.set_video_type(VideoParams::kVideoTypeStill);
|
||||
|
||||
// OIIO automatically premultiplies alpha
|
||||
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this
|
||||
// likely reduces the fidelity?
|
||||
video_params.set_premultiplied_alpha(true);
|
||||
|
||||
desc.AddVideoStream(video_params);
|
||||
|
||||
// If we're here, we have a successful image open
|
||||
in->close();
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
bool OIIODecoder::OpenInternal()
|
||||
{
|
||||
// If we can open the filename provided, assume everything is working
|
||||
return OpenImageHandler(stream().filename());
|
||||
}
|
||||
|
||||
FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams ÷r, const QAtomicInt *cancelled)
|
||||
{
|
||||
Q_UNUSED(timecode)
|
||||
Q_UNUSED(cancelled)
|
||||
|
||||
FramePtr frame = Frame::Create();
|
||||
|
||||
frame->set_video_params(VideoParams(buffer_->spec().width,
|
||||
buffer_->spec().height,
|
||||
pix_fmt_,
|
||||
channel_count_,
|
||||
OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()),
|
||||
VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us?
|
||||
divider.divider));
|
||||
frame->allocate();
|
||||
|
||||
if (divider.divider == 1) {
|
||||
|
||||
OIIOUtils::BufferToFrame(buffer_, frame.get());
|
||||
|
||||
} else {
|
||||
|
||||
// Will need to resize the image
|
||||
OIIO::ImageBuf dst(OIIO::ImageSpec(frame->width(), frame->height(), buffer_->spec().nchannels, buffer_->spec().format));
|
||||
|
||||
if (!OIIO::ImageBufAlgo::resample(dst, *buffer_)) {
|
||||
qWarning() << "OIIO resize failed";
|
||||
}
|
||||
|
||||
OIIOUtils::BufferToFrame(&dst, frame.get());
|
||||
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void OIIODecoder::CloseInternal()
|
||||
{
|
||||
CloseImageHandle();
|
||||
}
|
||||
|
||||
bool OIIODecoder::FileTypeIsSupported(const QString& fn)
|
||||
{
|
||||
// We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG)
|
||||
// will segfault entirely if given unexpected data (an MPEG-4 for instance). To workaround this issue, we use OIIO's
|
||||
// "extension_list" attribute and match it with the extension of the file.
|
||||
|
||||
// Check if we've created the supported formats list, create it if not
|
||||
if (supported_formats_.isEmpty()) {
|
||||
QStringList extension_list = QString::fromStdString(OIIO::get_string_attribute("extension_list")).split(';');
|
||||
|
||||
// The format of "extension_list" is "format:ext", we want to separate it into a simple list of extensions
|
||||
foreach (const QString& ext, extension_list) {
|
||||
QStringList format_and_ext = ext.split(':');
|
||||
|
||||
supported_formats_.append(format_and_ext.at(1).split(','));
|
||||
}
|
||||
}
|
||||
|
||||
if (!supported_formats_.contains(QFileInfo(fn).suffix(), Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OIIODecoder::OpenImageHandler(const QString &fn)
|
||||
{
|
||||
image_ = OIIO::ImageInput::open(fn.toStdString());
|
||||
|
||||
if (!image_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we can work with this pixel format
|
||||
const OIIO::ImageSpec& spec = image_->spec();
|
||||
|
||||
// Store channel count
|
||||
channel_count_ = spec.nchannels;
|
||||
|
||||
// We use RGBA frames because that tends to be the native format of GPUs
|
||||
pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(spec.format.basetype));
|
||||
|
||||
if (pix_fmt_ == VideoParams::kFormatInvalid) {
|
||||
qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format";
|
||||
return false;
|
||||
}
|
||||
|
||||
OIIO::TypeDesc::BASETYPE type = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_);
|
||||
|
||||
if (type == OIIO::TypeDesc::UNKNOWN) {
|
||||
qCritical() << "Failed to determine appropriate OIIO basetype from native format";
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type),
|
||||
OIIO::InitializePixels::No);
|
||||
|
||||
image_->read_image(type, buffer_->localpixels());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OIIODecoder::CloseImageHandle()
|
||||
{
|
||||
if (image_) {
|
||||
image_->close();
|
||||
image_ = nullptr;
|
||||
}
|
||||
|
||||
if (buffer_) {
|
||||
delete buffer_;
|
||||
buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -22,44 +22,50 @@
|
||||
#define OIIODECODER_H
|
||||
|
||||
#include <OpenImageIO/imageio.h>
|
||||
#include <OpenImageIO/imagebuf.h>
|
||||
|
||||
#include "decoder/decoder.h"
|
||||
#include "render/pixelservice.h"
|
||||
#include "codec/decoder.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class OIIODecoder : public Decoder
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OIIODecoder();
|
||||
|
||||
virtual QString id() override;
|
||||
DECODER_DEFAULT_DESTRUCTOR(OIIODecoder)
|
||||
|
||||
virtual bool Probe(Footage *f) override;
|
||||
virtual QString id() const override;
|
||||
|
||||
virtual bool Open() override;
|
||||
virtual bool SupportsVideo() override{return true;}
|
||||
|
||||
virtual FramePtr RetrieveVideo(const rational &timecode) override;
|
||||
virtual FootageDescription Probe(const QString& filename, const QAtomicInt* cancelled) const override;
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
virtual int64_t GetTimestampFromTime(const rational &time) override;
|
||||
|
||||
virtual bool SupportsVideo() override;
|
||||
protected:
|
||||
virtual bool OpenInternal() override;
|
||||
virtual FramePtr RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams& divider, const QAtomicInt *cancelled) override;
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<OIIO::ImageInput> image_;
|
||||
|
||||
int width_;
|
||||
static bool FileTypeIsSupported(const QString& fn);
|
||||
|
||||
int height_;
|
||||
bool OpenImageHandler(const QString& fn);
|
||||
|
||||
olive::PixelFormat pix_fmt_;
|
||||
void CloseImageHandle();
|
||||
|
||||
PixelFormatInfo pix_fmt_info_;
|
||||
VideoParams::Format pix_fmt_;
|
||||
|
||||
bool is_rgba_;
|
||||
int channel_count_;
|
||||
|
||||
FramePtr frame_;
|
||||
OIIO::ImageBuf* buffer_;
|
||||
|
||||
static QStringList supported_formats_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OIIODECODER_H
|
||||
@@ -0,0 +1,81 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "oiioencoder.h"
|
||||
|
||||
#include "common/oiioutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
OIIOEncoder::OIIOEncoder(const EncodingParams ¶ms) :
|
||||
Encoder(params)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool OIIOEncoder::Open()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OIIOEncoder::WriteFrame(FramePtr frame, rational time)
|
||||
{
|
||||
std::string filename = GetFilenameForFrame(time).toStdString();
|
||||
|
||||
auto output = OIIO::ImageOutput::create(filename);
|
||||
if (!output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OIIO::TypeDesc type = OIIOUtils::GetOIIOBaseTypeFromFormat(frame->format());
|
||||
OIIO::ImageSpec spec(frame->width(), frame->height(), frame->channel_count(), type);
|
||||
|
||||
if (!output->open(filename, spec)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!output->write_image(type, frame->data(), OIIO::AutoStride, frame->linesize_bytes())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!output->close()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OIIOEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
{
|
||||
// Do nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OIIOEncoder::WriteSubtitle(const SubtitleBlock *sub_block)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void OIIOEncoder::Close()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OIIOENCODER_H
|
||||
#define OIIOENCODER_H
|
||||
|
||||
#include "codec/encoder.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class OIIOEncoder : public Encoder
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OIIOEncoder(const EncodingParams ¶ms);
|
||||
|
||||
public slots:
|
||||
virtual bool Open() override;
|
||||
|
||||
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override;
|
||||
virtual bool WriteAudio(SampleBufferPtr audio) override;
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OIIOENCODER_H
|
||||
@@ -0,0 +1,119 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "planarfiledevice.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
PlanarFileDevice::PlanarFileDevice(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PlanarFileDevice::~PlanarFileDevice()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool PlanarFileDevice::open(const QVector<QString> &filenames, QIODevice::OpenMode mode)
|
||||
{
|
||||
if (isOpen()) {
|
||||
// Already open
|
||||
return false;
|
||||
}
|
||||
|
||||
files_.resize(filenames.size());
|
||||
files_.fill(nullptr);
|
||||
|
||||
for (int i=0; i<files_.size(); i++) {
|
||||
files_[i] = new QFile(filenames.at(i));
|
||||
if (!files_[i]->open(mode)) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 PlanarFileDevice::read(char **data, qint64 bytes_per_channel, qint64 offset)
|
||||
{
|
||||
qint64 ret = -1;
|
||||
|
||||
if (isOpen()) {
|
||||
for (int i=0; i<files_.size(); i++) {
|
||||
// Kind of clunky but should be largely fine
|
||||
ret = files_[i]->read(data[i] + offset, bytes_per_channel);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
qint64 PlanarFileDevice::write(const char **data, qint64 bytes_per_channel, qint64 offset)
|
||||
{
|
||||
qint64 ret = -1;
|
||||
|
||||
if (isOpen()) {
|
||||
for (int i=0; i<files_.size(); i++) {
|
||||
// Kind of clunky but should be largely fine
|
||||
ret = files_[i]->write(data[i] + offset, bytes_per_channel);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
qint64 PlanarFileDevice::size() const
|
||||
{
|
||||
if (isOpen()) {
|
||||
return files_.first()->size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool PlanarFileDevice::seek(qint64 pos)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
for (int i=0; i<files_.size(); i++) {
|
||||
ret = files_[i]->seek(pos) & ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PlanarFileDevice::close()
|
||||
{
|
||||
for (int i=0; i<files_.size(); i++) {
|
||||
QFile *f = files_.at(i);
|
||||
if (f) {
|
||||
if (f->isOpen()) {
|
||||
f->close();
|
||||
}
|
||||
delete f;
|
||||
}
|
||||
}
|
||||
files_.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PLANARFILEDEVICE_H
|
||||
#define PLANARFILEDEVICE_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class PlanarFileDevice : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PlanarFileDevice(QObject *parent = nullptr);
|
||||
|
||||
virtual ~PlanarFileDevice() override;
|
||||
|
||||
bool isOpen() const
|
||||
{
|
||||
return !files_.isEmpty();
|
||||
}
|
||||
|
||||
bool open(const QVector<QString> &filenames, QIODevice::OpenMode mode);
|
||||
|
||||
qint64 read(char **data, qint64 bytes_per_channel, qint64 offset = 0);
|
||||
|
||||
qint64 write(const char **data, qint64 bytes_per_channel, qint64 offset = 0);
|
||||
|
||||
qint64 size() const;
|
||||
|
||||
bool seek(qint64 pos);
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
QVector<QFile*> files_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PLANARFILEDEVICE_H
|
||||
@@ -0,0 +1,231 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "samplebuffer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
SampleBuffer::SampleBuffer() :
|
||||
sample_count_per_channel_(0)
|
||||
{
|
||||
}
|
||||
|
||||
SampleBufferPtr SampleBuffer::Create()
|
||||
{
|
||||
return std::make_shared<SampleBuffer>();
|
||||
}
|
||||
|
||||
SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, const rational &length)
|
||||
{
|
||||
return CreateAllocated(audio_params, audio_params.time_to_samples(length));
|
||||
}
|
||||
|
||||
SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, int samples_per_channel)
|
||||
{
|
||||
SampleBufferPtr buffer = Create();
|
||||
|
||||
buffer->set_audio_params(audio_params);
|
||||
buffer->set_sample_count(samples_per_channel);
|
||||
buffer->allocate();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const AudioParams &SampleBuffer::audio_params() const
|
||||
{
|
||||
return audio_params_;
|
||||
}
|
||||
|
||||
void SampleBuffer::set_audio_params(const AudioParams ¶ms)
|
||||
{
|
||||
if (is_allocated()) {
|
||||
qWarning() << "Tried to set parameters on allocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
audio_params_ = params;
|
||||
}
|
||||
|
||||
const int &SampleBuffer::sample_count() const
|
||||
{
|
||||
return sample_count_per_channel_;
|
||||
}
|
||||
|
||||
void SampleBuffer::set_sample_count(const int &sample_count)
|
||||
{
|
||||
if (is_allocated()) {
|
||||
qWarning() << "Tried to set sample count on allocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
sample_count_per_channel_ = sample_count;
|
||||
}
|
||||
|
||||
bool SampleBuffer::is_allocated() const
|
||||
{
|
||||
return !data_.isEmpty();
|
||||
}
|
||||
|
||||
void SampleBuffer::allocate()
|
||||
{
|
||||
if (!audio_params_.is_valid()) {
|
||||
qWarning() << "Tried to allocate sample buffer with invalid audio parameters";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sample_count_per_channel_) {
|
||||
qWarning() << "Tried to allocate sample buffer with zero sample count";
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_allocated()) {
|
||||
qWarning() << "Tried to allocate already allocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
data_.resize(audio_params_.channel_count());
|
||||
for (int i=0; i<audio_params_.channel_count(); i++) {
|
||||
data_[i].resize(sample_count_per_channel_);
|
||||
}
|
||||
|
||||
update_raw();
|
||||
}
|
||||
|
||||
void SampleBuffer::destroy()
|
||||
{
|
||||
data_.clear();
|
||||
raw_ptrs_.clear();
|
||||
}
|
||||
|
||||
void SampleBuffer::reverse()
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
qWarning() << "Tried to reverse an unallocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
int half_nb_sample = sample_count_per_channel_ / 2;
|
||||
|
||||
for (int i=0;i<half_nb_sample;i++) {
|
||||
int opposite_ind = sample_count_per_channel_ - i - 1;
|
||||
|
||||
for (int j=0;j<audio_params_.channel_count();j++) {
|
||||
std::swap(data_[j][i], data_[j][opposite_ind]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::speed(double speed)
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
qWarning() << "Tried to speed an unallocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
sample_count_per_channel_ = qRound(static_cast<double>(sample_count_per_channel_) / speed);
|
||||
|
||||
QVector< QVector<float> > output_data;
|
||||
|
||||
output_data.resize(audio_params_.channel_count());
|
||||
for (int i=0; i<audio_params_.channel_count(); i++) {
|
||||
output_data[i].resize(sample_count_per_channel_);
|
||||
}
|
||||
|
||||
for (int i=0;i<sample_count_per_channel_;i++) {
|
||||
int input_index = qFloor(static_cast<double>(i) * speed);
|
||||
|
||||
for (int j=0;j<audio_params_.channel_count();j++) {
|
||||
output_data[j][i] = data_[j][input_index];
|
||||
}
|
||||
}
|
||||
|
||||
data_ = output_data;
|
||||
update_raw();
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume(float f)
|
||||
{
|
||||
for (int i=0;i<audio_params().channel_count();i++) {
|
||||
for (int j=0;j<sample_count_per_channel_;j++) {
|
||||
data_[i][j] *= f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_channel(int channel, float volume)
|
||||
{
|
||||
for (int i=0;i<sample_count_per_channel_;i++) {
|
||||
data_[channel][i] *= volume;
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_sample(int sample_index, float volume)
|
||||
{
|
||||
for (int i=0;i<audio_params().channel_count();i++) {
|
||||
data_[i][sample_index] *= volume;
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_sample_on_channel(int sample_index, int channel, float volume)
|
||||
{
|
||||
data_[channel][sample_index] *= volume;
|
||||
}
|
||||
|
||||
void SampleBuffer::silence()
|
||||
{
|
||||
silence(0, sample_count_per_channel_);
|
||||
}
|
||||
|
||||
void SampleBuffer::silence(int start_sample, int end_sample)
|
||||
{
|
||||
silence_bytes(start_sample * sizeof(float), end_sample * sizeof(float));
|
||||
}
|
||||
|
||||
void SampleBuffer::silence_bytes(int start_byte, int end_byte)
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
qWarning() << "Tried to fill an unallocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0;i<audio_params().channel_count();i++) {
|
||||
memset(reinterpret_cast<char*>(data_[i].data()) + start_byte, 0, end_byte - start_byte);
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::set(int channel, const float *data, int sample_offset, int sample_length)
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
qWarning() << "Tried to fill an unallocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&data_[channel].data()[sample_offset], data, sizeof(float) * sample_length);
|
||||
}
|
||||
|
||||
void SampleBuffer::update_raw()
|
||||
{
|
||||
raw_ptrs_.resize(data_.size());
|
||||
for (int i=0; i<raw_ptrs_.size(); i++) {
|
||||
raw_ptrs_[i] = data_[i].data();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef SAMPLEBUFFER_H
|
||||
#define SAMPLEBUFFER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "render/audioparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class SampleBuffer;
|
||||
using SampleBufferPtr = std::shared_ptr<SampleBuffer>;
|
||||
|
||||
/**
|
||||
* @brief A buffer of audio samples
|
||||
*
|
||||
* Audio samples in this structure are always stored in PLANAR (separated by channel). This is done to simplify audio
|
||||
* rendering code. This replaces the old system of using QByteArrays (containing packed audio) and while SampleBuffer
|
||||
* replaces many of those in the rendering/processing side of things, QByteArrays are currently still in use for
|
||||
* playback, including reading to and from the cache.
|
||||
*/
|
||||
class SampleBuffer
|
||||
{
|
||||
public:
|
||||
SampleBuffer();
|
||||
|
||||
static SampleBufferPtr Create();
|
||||
static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, const rational& length);
|
||||
static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, int samples_per_channel);
|
||||
|
||||
DISABLE_COPY_MOVE(SampleBuffer)
|
||||
|
||||
const AudioParams& audio_params() const;
|
||||
void set_audio_params(const AudioParams& params);
|
||||
|
||||
const int &sample_count() const;
|
||||
void set_sample_count(const int &sample_count);
|
||||
|
||||
float* data(int channel)
|
||||
{
|
||||
return data_[channel].data();
|
||||
}
|
||||
|
||||
const float* data(int channel) const
|
||||
{
|
||||
return data_.at(channel).constData();
|
||||
}
|
||||
|
||||
float **to_raw_ptrs()
|
||||
{
|
||||
return raw_ptrs_.data();
|
||||
}
|
||||
|
||||
bool is_allocated() const;
|
||||
void allocate();
|
||||
void destroy();
|
||||
|
||||
void reverse();
|
||||
void speed(double speed);
|
||||
void transform_volume(float f);
|
||||
void transform_volume_for_channel(int channel, float volume);
|
||||
void transform_volume_for_sample(int sample_index, float volume);
|
||||
void transform_volume_for_sample_on_channel(int sample_index, int channel, float volume);
|
||||
|
||||
void silence();
|
||||
void silence(int start_sample, int end_sample);
|
||||
void silence_bytes(int start_byte, int end_byte);
|
||||
|
||||
void set(int channel, const float* data, int sample_offset, int sample_length);
|
||||
void set(int channel, const float* data, int sample_length)
|
||||
{
|
||||
set(channel, data, 0, sample_length);
|
||||
}
|
||||
|
||||
private:
|
||||
void update_raw();
|
||||
|
||||
AudioParams audio_params_;
|
||||
|
||||
int sample_count_per_channel_;
|
||||
|
||||
QVector< QVector<float> > data_;
|
||||
QVector<float*> raw_ptrs_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::SampleBufferPtr)
|
||||
|
||||
#endif // SAMPLEBUFFER_H
|
||||
+37
-10
@@ -1,5 +1,5 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
# Copyright (C) 2021 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -16,25 +16,52 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
common/bezier.cpp
|
||||
common/bezier.h
|
||||
common/cancelableobject.h
|
||||
common/channellayout.h
|
||||
common/clamp.h
|
||||
common/debug.h
|
||||
common/commandlineparser.cpp
|
||||
common/commandlineparser.h
|
||||
common/crashpadinterface.cpp
|
||||
common/crashpadinterface.h
|
||||
common/crashpadutils.h
|
||||
common/debug.cpp
|
||||
common/filefunctions.h
|
||||
common/debug.h
|
||||
common/decibel.h
|
||||
common/define.h
|
||||
common/ffmpegutils.cpp
|
||||
common/ffmpegutils.h
|
||||
common/filefunctions.cpp
|
||||
common/flipmodifiers.h
|
||||
common/filefunctions.h
|
||||
common/flipmodifiers.cpp
|
||||
common/flipmodifiers.h
|
||||
common/functiontimer.h
|
||||
common/jobtime.cpp
|
||||
common/jobtime.h
|
||||
common/lerp.h
|
||||
common/memorypool.h
|
||||
common/ocioutils.cpp
|
||||
common/ocioutils.h
|
||||
common/oiioutils.cpp
|
||||
common/oiioutils.h
|
||||
common/otioutils.h
|
||||
common/qtutils.cpp
|
||||
common/qtutils.h
|
||||
common/range.h
|
||||
common/rational.h
|
||||
common/ratiodialog.cpp
|
||||
common/ratiodialog.h
|
||||
common/rational.cpp
|
||||
common/qtversionabstraction.h
|
||||
common/qtversionabstraction.cpp
|
||||
common/threadedobject.h
|
||||
common/rational.h
|
||||
common/threadedobject.cpp
|
||||
common/timecodefunctions.h
|
||||
common/threadedobject.h
|
||||
common/threadsafemap.h
|
||||
common/timecodefunctions.cpp
|
||||
common/timerange.h
|
||||
common/timecodefunctions.h
|
||||
common/timerange.cpp
|
||||
common/timerange.h
|
||||
common/tohex.h
|
||||
common/xmlutils.cpp
|
||||
common/xmlutils.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef AUTOSCROLL_H
|
||||
#define AUTOSCROLL_H
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class AutoScroll {
|
||||
public:
|
||||
enum Method {
|
||||
kNone,
|
||||
kPage,
|
||||
kSmooth
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // AUTOSCROLL_H
|
||||
@@ -0,0 +1,110 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "bezier.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
#include "common/clamp.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
Bezier::Bezier() :
|
||||
x_(0),
|
||||
y_(0),
|
||||
cp1_x_(0),
|
||||
cp1_y_(0),
|
||||
cp2_x_(0),
|
||||
cp2_y_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Bezier::Bezier(double x, double y) :
|
||||
x_(x),
|
||||
y_(y),
|
||||
cp1_x_(0),
|
||||
cp1_y_(0),
|
||||
cp2_x_(0),
|
||||
cp2_y_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Bezier::Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, double cp2_y) :
|
||||
x_(x),
|
||||
y_(y),
|
||||
cp1_x_(cp1_x),
|
||||
cp1_y_(cp1_y),
|
||||
cp2_x_(cp2_x),
|
||||
cp2_y_(cp2_y)
|
||||
{
|
||||
}
|
||||
|
||||
double Bezier::QuadraticXtoT(double x, double a, double b, double c)
|
||||
{
|
||||
// Clamp to prevent infinite loop
|
||||
x = clamp(x, a, c);
|
||||
|
||||
return CalculateTFromX(false, x, a, b, c, 0);
|
||||
}
|
||||
|
||||
double Bezier::QuadraticTtoY(double a, double b, double c, double t)
|
||||
{
|
||||
return qPow(1.0 - t, 2)*a + 2*(1.0 - t)*t*b + qPow(t, 2)*c;
|
||||
}
|
||||
|
||||
double Bezier::CubicXtoT(double x, double a, double b, double c, double d)
|
||||
{
|
||||
// Clamp to prevent infinite loop
|
||||
x = clamp(x, a, d);
|
||||
|
||||
return CalculateTFromX(true, x, a, b, c, d);
|
||||
}
|
||||
|
||||
double Bezier::CubicTtoY(double a, double b, double c, double d, double t)
|
||||
{
|
||||
return qPow(1.0 - t, 3)*a + 3*qPow(1.0 - t, 2)*t*b + 3*(1.0 - t)*qPow(t, 2)*c + qPow(t, 3)*d;
|
||||
}
|
||||
|
||||
double Bezier::CalculateTFromX(bool cubic, double x, double a, double b, double c, double d)
|
||||
{
|
||||
double bottom = 0.0;
|
||||
double top = 1.0;
|
||||
|
||||
while (true) {
|
||||
if (bottom == top) {
|
||||
return bottom;
|
||||
}
|
||||
|
||||
double mid = (bottom + top) * 0.5;
|
||||
double test = cubic ? CubicTtoY(a, b, c, d, mid) : QuadraticTtoY(a, b, c, mid);
|
||||
|
||||
if (qAbs(test - x) < 0.000001) {
|
||||
return mid;
|
||||
} else if (x > test) {
|
||||
bottom = mid;
|
||||
} else {
|
||||
top = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return qSNaN();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef BEZIER_H
|
||||
#define BEZIER_H
|
||||
|
||||
#include <QPointF>
|
||||
#include <QObject>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class Bezier
|
||||
{
|
||||
public:
|
||||
Bezier();
|
||||
Bezier(double x, double y);
|
||||
Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, double cp2_y);
|
||||
|
||||
const double &x() const {return x_; }
|
||||
const double &y() const {return y_; }
|
||||
const double &cp1_x() const { return cp1_x_; }
|
||||
const double &cp1_y() const { return cp1_y_; }
|
||||
const double &cp2_x() const { return cp2_x_; }
|
||||
const double &cp2_y() const { return cp2_y_; }
|
||||
|
||||
QPointF ToPointF() const
|
||||
{
|
||||
return QPointF(x_, y_);
|
||||
}
|
||||
|
||||
QPointF ControlPoint1ToPointF() const
|
||||
{
|
||||
return QPointF(cp1_x_, cp1_y_);
|
||||
}
|
||||
|
||||
QPointF ControlPoint2ToPointF() const
|
||||
{
|
||||
return QPointF(cp2_x_, cp2_y_);
|
||||
}
|
||||
|
||||
void set_x(const double &x) { x_ = x; }
|
||||
void set_y(const double &y) { y_ = y; }
|
||||
void set_cp1_x(const double &cp1_x) { cp1_x_ = cp1_x; }
|
||||
void set_cp1_y(const double &cp1_y) { cp1_y_ = cp1_y; }
|
||||
void set_cp2_x(const double &cp2_x) { cp2_x_ = cp2_x; }
|
||||
void set_cp2_y(const double &cp2_y) { cp2_y_ = cp2_y; }
|
||||
|
||||
static double QuadraticXtoT(double x, double a, double b, double c);
|
||||
|
||||
static double QuadraticTtoY(double a, double b, double c, double t);
|
||||
|
||||
static double QuadraticXtoY(double x, const QPointF &a, const QPointF &b, const QPointF &c)
|
||||
{
|
||||
return QuadraticTtoY(a.y(), b.y(), c.y(), QuadraticXtoT(x, a.x(), b.x(), c.x()));
|
||||
}
|
||||
|
||||
static double CubicXtoT(double x, double a, double b, double c, double d);
|
||||
|
||||
static double CubicTtoY(double a, double b, double c, double d, double t);
|
||||
|
||||
static double CubicXtoY(double x, const QPointF &a, const QPointF &b, const QPointF &c, const QPointF &d)
|
||||
{
|
||||
return CubicTtoY(a.y(), b.y(), c.y(), d.y(), CubicXtoT(x, a.x(), b.x(), c.x(), d.x()));
|
||||
}
|
||||
|
||||
private:
|
||||
static double CalculateTFromX(bool cubic, double x, double a, double b, double c, double d);
|
||||
|
||||
double x_;
|
||||
double y_;
|
||||
|
||||
double cp1_x_;
|
||||
double cp1_y_;
|
||||
|
||||
double cp2_x_;
|
||||
double cp2_y_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::Bezier)
|
||||
|
||||
#endif // BEZIER_H
|
||||
@@ -0,0 +1,58 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef CANCELABLEOBJECT_H
|
||||
#define CANCELABLEOBJECT_H
|
||||
|
||||
#include <QAtomicInt>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class CancelableObject {
|
||||
public:
|
||||
CancelableObject() :
|
||||
cancelled_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void Cancel()
|
||||
{
|
||||
cancelled_ = true;
|
||||
CancelEvent();
|
||||
}
|
||||
|
||||
const QAtomicInt& IsCancelled() const
|
||||
{
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void CancelEvent(){}
|
||||
|
||||
private:
|
||||
QAtomicInt cancelled_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CANCELABLEOBJECT_H
|
||||
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "commandlineparser.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
CommandLineParser::~CommandLineParser()
|
||||
{
|
||||
foreach (const KnownOption& o, options_) {
|
||||
delete o.option;
|
||||
}
|
||||
|
||||
foreach (const KnownPositionalArgument& a, positional_args_) {
|
||||
delete a.option;
|
||||
}
|
||||
}
|
||||
|
||||
const CommandLineParser::Option *CommandLineParser::AddOption(const QStringList &strings, const QString &description, bool takes_arg, const QString &arg_placeholder, bool hidden)
|
||||
{
|
||||
Option* o = new Option();
|
||||
|
||||
options_.append({strings, description, o, takes_arg, arg_placeholder, hidden});
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
const CommandLineParser::PositionalArgument *CommandLineParser::AddPositionalArgument(const QString &name, const QString &description, bool required)
|
||||
{
|
||||
PositionalArgument* a = new PositionalArgument();
|
||||
|
||||
positional_args_.append({name, description, a, required});
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
void CommandLineParser::Process(const QVector<QString> &argv)
|
||||
{
|
||||
int positional_index = 0;
|
||||
|
||||
for (int i=1; i<argv.size(); i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
// Must be an option
|
||||
|
||||
// Skip past first dashes
|
||||
QString arg_basename = argv[i].mid(1);
|
||||
|
||||
bool matched_known = false;
|
||||
|
||||
for (int j=0; j<options_.size(); j++) {
|
||||
KnownOption& o = options_[j];
|
||||
|
||||
foreach (const QString& s, o.args) {
|
||||
if (!s.compare(arg_basename, Qt::CaseInsensitive)) {
|
||||
// Flag discovered!
|
||||
o.option->Set();
|
||||
|
||||
if (o.takes_arg && i+1 < argv.size()) {
|
||||
o.option->SetSetting(argv[i+1]);
|
||||
i++;
|
||||
}
|
||||
|
||||
matched_known = true;
|
||||
goto found_flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
found_flag:
|
||||
if (!matched_known) {
|
||||
qWarning() << "Unknown parameter:" << argv[i];
|
||||
}
|
||||
|
||||
} else {
|
||||
// Must be a positional flag
|
||||
if (positional_index < positional_args_.size()) {
|
||||
positional_args_[positional_index].option->SetSetting(argv[i]);
|
||||
positional_index++;
|
||||
} else {
|
||||
qWarning() << "Unknown parameter:" << argv[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandLineParser::PrintHelp(const char* filename)
|
||||
{
|
||||
printf("%s %s\n",
|
||||
QCoreApplication::applicationName().toUtf8().constData(),
|
||||
QCoreApplication::applicationVersion().toUtf8().constData());
|
||||
|
||||
printf("Copyright (C) 2018-2021 Olive Team\n");
|
||||
|
||||
QString positional_args;
|
||||
for (int i=0; i<positional_args_.size(); i++) {
|
||||
if (i > 0) {
|
||||
positional_args.append(' ');
|
||||
}
|
||||
|
||||
positional_args.append('[');
|
||||
positional_args.append(positional_args_.at(i).name);
|
||||
positional_args.append(']');
|
||||
}
|
||||
|
||||
const char* basename;
|
||||
#ifdef Q_OS_WINDOWS
|
||||
basename = strrchr(filename, '\\');
|
||||
if (!basename) {
|
||||
basename = strrchr(filename, '/');
|
||||
}
|
||||
#else
|
||||
basename = strrchr(filename, '/');
|
||||
#endif
|
||||
|
||||
if (basename) {
|
||||
// Slash found, increment pointer to avoid showing the slash itself
|
||||
basename++;
|
||||
} else {
|
||||
// If no slashes are found, assume string is already a basename
|
||||
basename = filename;
|
||||
}
|
||||
|
||||
printf("Usage: %s [options] %s\n\n", basename, positional_args.toUtf8().constData());
|
||||
foreach (const KnownOption& o, options_) {
|
||||
if (o.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString all_args;
|
||||
|
||||
for (int i=0; i<o.args.size(); i++) {
|
||||
if (i > 0) {
|
||||
all_args.append(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
const QString& this_arg = o.args.at(i);
|
||||
|
||||
all_args.append('-');
|
||||
all_args.append(this_arg);
|
||||
}
|
||||
|
||||
if (o.arg_placeholder.isEmpty()) {
|
||||
printf(" %s\n", all_args.toUtf8().constData());
|
||||
} else {
|
||||
printf(" %s <%s>\n", all_args.toUtf8().constData(), o.arg_placeholder.toUtf8().constData());
|
||||
}
|
||||
|
||||
printf(" %s\n\n", o.description.toUtf8().constData());
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef COMMANDLINEPARSER_H
|
||||
#define COMMANDLINEPARSER_H
|
||||
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
/**
|
||||
* @brief Command-line argument parser
|
||||
*
|
||||
* You may be wondering why we don't use QCommandLineParser instead of a custom implementation like
|
||||
* this. The reason why is because QCommandLineParser requires a QApplication object of some kind
|
||||
* to already have been created before it can parse anything, but we need to be able to control
|
||||
* whether a QApplication (GUI-mode) or a QCoreApplication (CLI-mode) is created which is set by
|
||||
* the user as a command line argument. Therefore we needed a custom implementation that could
|
||||
* parse arguments without the need for a Q(Core)Application to be present already.
|
||||
*/
|
||||
class CommandLineParser
|
||||
{
|
||||
public:
|
||||
~CommandLineParser();
|
||||
|
||||
DISABLE_COPY_MOVE(CommandLineParser)
|
||||
|
||||
class PositionalArgument
|
||||
{
|
||||
public:
|
||||
PositionalArgument() = default;
|
||||
|
||||
const QString& GetSetting() const
|
||||
{
|
||||
return setting_;
|
||||
}
|
||||
|
||||
void SetSetting(const QString& s)
|
||||
{
|
||||
setting_ = s;
|
||||
}
|
||||
|
||||
private:
|
||||
QString setting_;
|
||||
|
||||
};
|
||||
|
||||
class Option : public PositionalArgument {
|
||||
public:
|
||||
Option()
|
||||
{
|
||||
is_set_ = false;
|
||||
}
|
||||
|
||||
bool IsSet() const
|
||||
{
|
||||
return is_set_;
|
||||
}
|
||||
|
||||
void Set()
|
||||
{
|
||||
is_set_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_set_;
|
||||
|
||||
};
|
||||
|
||||
CommandLineParser() = default;
|
||||
|
||||
const Option* AddOption(const QStringList& strings, const QString& description, bool takes_arg = false, const QString& arg_placeholder = QString(), bool hidden = false);
|
||||
|
||||
const PositionalArgument* AddPositionalArgument(const QString& name, const QString& description, bool required = false);
|
||||
|
||||
void Process(const QVector<QString> &argv);
|
||||
|
||||
void PrintHelp(const char* filename);
|
||||
|
||||
private:
|
||||
struct KnownOption {
|
||||
QStringList args;
|
||||
QString description;
|
||||
Option* option;
|
||||
bool takes_arg;
|
||||
QString arg_placeholder;
|
||||
bool hidden;
|
||||
};
|
||||
|
||||
struct KnownPositionalArgument {
|
||||
QString name;
|
||||
QString description;
|
||||
PositionalArgument* option;
|
||||
bool required;
|
||||
};
|
||||
|
||||
QVector<KnownOption> options_;
|
||||
|
||||
QVector<KnownPositionalArgument> positional_args_;
|
||||
|
||||
};
|
||||
|
||||
#endif // COMMANDLINEPARSER_H
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef CONSTRUCTORS_H
|
||||
#define CONSTRUCTORS_H
|
||||
|
||||
/**
|
||||
* Copy/move deleters. Similar to Q_DISABLE_COPY_MOVE, et al. but those functions are not present in Qt < 5.13 so we
|
||||
* use our own functions for portability.
|
||||
*/
|
||||
|
||||
#define DISABLE_COPY(Class) \
|
||||
Class(const Class &) = delete;\
|
||||
Class &operator=(const Class &) = delete;
|
||||
|
||||
#define DISABLE_MOVE(Class) \
|
||||
Class(Class &&) = delete; \
|
||||
Class &operator=(Class &&) = delete;
|
||||
|
||||
#define DISABLE_COPY_MOVE(Class) \
|
||||
DISABLE_COPY(Class) \
|
||||
DISABLE_MOVE(Class)
|
||||
|
||||
#endif // CONSTRUCTORS_H
|
||||
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -18,12 +18,13 @@
|
||||
|
||||
***/
|
||||
|
||||
#include "qtversionabstraction.h"
|
||||
#ifndef CPUOPTIMIZE_H
|
||||
#define CPUOPTIMIZE_H
|
||||
|
||||
int QFontMetricsWidth(QFontMetrics fm, const QString& s) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
||||
return fm.width(s);
|
||||
#else
|
||||
return fm.horizontalAdvance(s);
|
||||
#if defined(Q_PROCESSOR_X86)
|
||||
#include <xmmintrin.h>
|
||||
#elif defined(Q_PROCESSOR_ARM)
|
||||
#include <sse2neon.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // CPUOPTIMIZE_H
|
||||
@@ -0,0 +1,91 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "crashpadinterface.h"
|
||||
|
||||
#ifdef USE_CRASHPAD
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
|
||||
#include "crashpadutils.h"
|
||||
#include "filefunctions.h"
|
||||
|
||||
#ifdef OS_WIN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
crashpad::CrashpadClient *client;
|
||||
|
||||
bool InitializeCrashpad()
|
||||
{
|
||||
QString report_path = QDir(olive::FileFunctions::GetTempFilePath()).filePath(QStringLiteral("reports"));
|
||||
|
||||
QString handler_fn = olive::FileFunctions::GetFormattedExecutableForPlatform(QStringLiteral("crashpad_handler"));
|
||||
|
||||
// Generate absolute path
|
||||
QString handler_abs_path = QDir(QCoreApplication::applicationDirPath()).filePath(handler_fn);
|
||||
|
||||
bool status = false;
|
||||
|
||||
if (QFileInfo::exists(handler_abs_path)) {
|
||||
base::FilePath handler(QSTRING_TO_BASE_STRING(handler_abs_path));
|
||||
|
||||
base::FilePath reports_dir(QSTRING_TO_BASE_STRING(report_path));
|
||||
|
||||
base::FilePath metrics_dir(QSTRING_TO_BASE_STRING(QDir(olive::FileFunctions::GetTempFilePath()).filePath(QStringLiteral("metrics"))));
|
||||
|
||||
// Metadata that will be posted to the server with the crash report map
|
||||
std::map<std::string, std::string> annotations;
|
||||
|
||||
// Disable crashpad rate limiting so that all crashes have dmp files
|
||||
std::vector<std::string> arguments;
|
||||
arguments.push_back("--no-rate-limit");
|
||||
arguments.push_back("--no-upload-gzip");
|
||||
|
||||
// Initialize Crashpad database
|
||||
std::unique_ptr<crashpad::CrashReportDatabase> database = crashpad::CrashReportDatabase::Initialize(reports_dir);
|
||||
if (database == NULL) return false;
|
||||
|
||||
// Disable automated crash uploads
|
||||
crashpad::Settings *settings = database->GetSettings();
|
||||
if (settings == NULL) return false;
|
||||
settings->SetUploadsEnabled(false);
|
||||
|
||||
// Start crash handler
|
||||
client = new crashpad::CrashpadClient();
|
||||
status = client->StartHandler(handler, reports_dir, metrics_dir,
|
||||
"https://olivevideoeditor.org/crashpad/report.php",
|
||||
annotations, arguments, true, true);
|
||||
}
|
||||
|
||||
// Override Crashpad exception filter with our own
|
||||
if (!status) {
|
||||
qWarning() << "Failed to start Crashpad, automatic crash reporting will be disabled";
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#endif // USE_CRASHPAD
|
||||
@@ -1,7 +1,7 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -18,22 +18,16 @@
|
||||
|
||||
***/
|
||||
|
||||
#include "taskthread.h"
|
||||
#ifndef CRASHPAD_INTERFACE_H
|
||||
#define CRASHPAD_INTERFACE_H
|
||||
|
||||
#include "task/task.h"
|
||||
#ifdef USE_CRASHPAD
|
||||
|
||||
TaskThread::TaskThread(Task *parent) :
|
||||
parent_(parent),
|
||||
result_(false)
|
||||
{
|
||||
}
|
||||
#include <client/crash_report_database.h>
|
||||
#include <client/settings.h>
|
||||
|
||||
void TaskThread::run()
|
||||
{
|
||||
result_ = parent_->Action();
|
||||
}
|
||||
bool InitializeCrashpad();
|
||||
|
||||
bool TaskThread::result()
|
||||
{
|
||||
return result_;
|
||||
}
|
||||
#endif // USE_CRASHPAD
|
||||
|
||||
#endif // CRASHPAD_INTERFACE_H
|
||||
@@ -0,0 +1,40 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef CRASHPADUTILS_H
|
||||
#define CRASHPADUTILS_H
|
||||
|
||||
#include <client/crashpad_client.h>
|
||||
|
||||
// Copied from base::FilePath to match its macro
|
||||
#if defined(OS_POSIX)
|
||||
// On most platforms, native pathnames are char arrays, and the encoding
|
||||
// may or may not be specified. On Mac OS X, native pathnames are encoded
|
||||
// in UTF-8.
|
||||
#define QSTRING_TO_BASE_STRING(x) x.toStdString()
|
||||
#define BASE_STRING_TO_QSTRING(x) QString::fromStdString(x)
|
||||
#elif defined(OS_WIN)
|
||||
// On Windows, for Unicode-aware applications, native pathnames are wchar_t
|
||||
// arrays encoded in UTF-16.
|
||||
#define QSTRING_TO_BASE_STRING(x) x.toStdWString()
|
||||
#define BASE_STRING_TO_QSTRING(x) QString::fromStdWString(x)
|
||||
#endif // OS_WIN
|
||||
|
||||
#endif // CRASHPADUTILS_H
|
||||
@@ -1,5 +1,26 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
void DebugHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
@@ -32,3 +53,5 @@ void DebugHandler(QtMsgType type, const QMessageLogContext &context, const QStri
|
||||
fflush(stderr);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,34 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef DEBUG_H
|
||||
#define DEBUG_H
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
void DebugHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
|
||||
|
||||
}
|
||||
|
||||
#endif // DEBUG_H
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "decibel.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
double amplitude_to_db(double amplitude) {
|
||||
return (20.0*(qLn(amplitude)/qLn(10.0)));
|
||||
}
|
||||
|
||||
double db_to_amplitude(double db) {
|
||||
return qPow(M_E, (db*qLn(10.0))/20.0);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user