Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found
Sélectionner une révision Git
Loading items

Cible

Sélectionner le projet cible
  • Elliu/Vivy
1 résultat
Sélectionner une révision Git
Loading items
Afficher les modifications
Validations sur la source (64)
Affichage de
avec 557 ajouts et 390 suppressions
......@@ -67,24 +67,9 @@ target_link_libraries(Vivy PRIVATE ${MPV_LIBRARY})
target_link_libraries(Vivy PRIVATE lua)
# Headers related things
include("${CMAKE_CURRENT_SOURCE_DIR}/PreCompiledHeaders.cmake")
target_include_directories(Vivy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc)
target_precompile_headers(Vivy PRIVATE
# Private Vivy headers
${Vivy_INC}
# STL headers
<memory>
<vector>
<atomic>
# Qt headers
<QString>
<QList>
<QVector>
<QMap>
<QWidget>
<QIcon>
)
target_precompile_headers(Vivy PRIVATE ${Vivy_PRECOMPILED_INC})
# Set Vivy's needed C++ features
target_compile_features(Vivy PRIVATE
......@@ -121,7 +106,7 @@ target_link_libraries(Vivy PRIVATE -fopenmp)
target_compile_definitions(Vivy PRIVATE
QT_DISABLE_DEPRECATED_BEFORE=0x050F00
QT_NO_CAST_TO_ASCII
# QT_RESTRICTED_CAST_FROM_ASCII
QT_RESTRICTED_CAST_FROM_ASCII
QTCREATOR_UTILS_STATIC_LIB
)
......@@ -130,9 +115,10 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
target_compile_options(Vivy PRIVATE
-Weverything
# Disable some things because we want C++20 and don't constrol some Qt
# generated files... Also permit VLA
# Disable some things because we want C++20 and don't control some Qt
# generated files...
-Wno-c++98-compat -Wno-c++98-c++11-c++14-c++17-compat-pedantic
-Wno-c++98-compat-pedantic
-Wno-extra-semi-stmt
-Wno-redundant-parens
-Wno-padded
......@@ -143,7 +129,13 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
-fopenmp
)
elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
target_compile_options(Vivy PRIVATE -fopenmp)
target_compile_options(Vivy PRIVATE
-fopenmp
-Wno-subobject-linkage # Kubat: Some proglems here, it seems they
# occure because of the usage of "using" for
# type aliasing... As I won't get ride of the
# "using"s the warning is disabled
)
endif()
set_target_properties(Vivy PROPERTIES
......
# System headers
set(STL_INC
PRIVATE
<memory>
<vector>
<atomic>
<algorithm>
<stdexcept>
<sstream>
<map>
<locale>
<functional>
<initializer_list>
<cstring>
<string>
<string_view>
<cstdio>
<iostream>
<optional>
<type_traits>
<chrono>
<climits>
<ctype.h>
<mutex>
)
set(EXT_INC PRIVATE
<mpv/client.h>
"${CMAKE_CURRENT_SOURCE_DIR}/vendor/lua-5.4.3/src/lua.hpp"
)
set(QT_STRUCT_INC
PRIVATE
<QtGlobal>
<QObject>
<QRegularExpression>
<QRegExp>
<QString>
<QVariant>
<QFile>
<QDebug>
<QHash>
<QDir>
<QTextStream>
<QFileInfo>
<QMessageLogger>
<QUuid>
<QPointer>
<QProcess>
<QTimer>
<QStack>
<QTemporaryFile>
<QStandardPaths>
<QList>
<QVector>
<QMap>
<QStringList>
<QJsonObject>
<QJsonArray>
<QJsonDocument>
<QJsonValue>
)
set(QT_WIDGET_INC
PRIVATE
<QCoreApplication>
<QTextDocument>
<QColor>
<QWidget>
<QIcon>
<QFont>
<QPixmap>
<QApplication>
<QCloseEvent>
<QLabel>
<QPushButton>
<QVBoxLayout>
<QHBoxLayout>
<QEvent>
<QMouseEvent>
<QTextEdit>
<QDockWidget>
<QMenu>
<QFileDialog>
<QMutex>
<QMainWindow>
<QPainter>
<QPlainTextEdit>
<QSlider>
<QAbstractItemModel>
<QHeaderView>
<QPaintEvent>
<QTableView>
<QStyledItemDelegate>
<QMessageBox>
<QScrollBar>
<QScrollArea>
<QGraphicsPixmapItem>
<QGraphicsSceneMouseEvent>
<QPen>
<QAction>
<QGraphicsItem>
<QGraphicsLineItem>
<QTreeView>
<QGraphicsView>
<QGraphicsScene>
<QWindow>
<QAbstractScrollArea>
<QGraphicsLineItem>
<QGraphicsPixmapItem>
<QClipboard>
<QInputMethodEvent>
<QKeyEvent>
<QLineEdit>
<QTextBlock>
<QTextCursor>
<QTextDocumentFragment>
<QMimeData>
<QSharedPointer>
<QStatusBar>
<QBrush>
<QFrame>
<QSyntaxHighlighter>
<QTextCharFormat>
<QTabWidget>
<QLayoutItem>
<QLayout>
<QEventLoop>
<QImage>
<QToolBar>
<QMenuBar>
<QDialogButtonBox>
<QFileIconProvider>
<QFontDatabase>
<QTextCodec>
)
set(Vivy_PRECOMPILED_INC PRIVATE
${STL_INC}
${QT_STRUCT_INC}
${QT_WIDGET_INC}
${EXT_INC}
)
......@@ -33,6 +33,7 @@
<file alias="document-open.svg">icons/breeze-dark/document-open.svg</file>
<file alias="document-save.svg">icons/breeze-dark/document-save.svg</file>
<file alias="document-save-as.svg">icons/breeze-dark/document-save-as.svg</file>
<file alias="document-rename.svg">icons/breeze-dark/edit-rename.svg</file>
<file alias="folder.svg">icons/breeze-dark/folder.svg</file>
<file alias="text-x-generic.svg">icons/breeze-dark/text-x-generic.svg</file>
<file alias="help-about.svg">icons/breeze-dark/help-about.svg</file>
......@@ -45,6 +46,7 @@
<file alias="document-open.svg">icons/breeze-light/document-open.svg</file>
<file alias="document-save.svg">icons/breeze-light/document-save.svg</file>
<file alias="document-save-as.svg">icons/breeze-light/document-save-as.svg</file>
<file alias="document-rename.svg">icons/breeze-light/edit-rename.svg</file>
<file alias="folder.svg">icons/breeze-light/folder.svg</file>
<file alias="text-x-generic.svg">icons/breeze-light/text-x-generic.svg</file>
<file alias="help-about.svg">icons/breeze-light/help-about.svg</file>
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
class="ColorScheme-Text"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
class="ColorScheme-Text"
/>
</svg>
#include "AbstractDocument.hh"
bool
Vivy::operator==(const AbstractDocument &a, const AbstractDocument &b) noexcept
operator==(const Vivy::AbstractDocument &a, const Vivy::AbstractDocument &b) noexcept
{
return a.getUuid() == b.getUuid();
}
......@@ -7,8 +7,6 @@
#include "Utils.hh"
#include "Uuid.hh"
#include <QFileInfo>
#include <QDir>
namespace Vivy
{
......@@ -16,6 +14,41 @@ class AbstractDocument : public QObject {
Q_OBJECT
VIVY_UNMOVABLE_OBJECT(AbstractDocument)
void copyOrRenameWith(const QFileInfo &newFile, auto action, auto success)
{
const QFileInfo oldFile(getName());
// Create folder if needed
QDir dirOp;
const QString newAbsDirPath = newFile.dir().absolutePath();
if (!dirOp.exists(newAbsDirPath)) {
qInfo() << "Create folder " << newAbsDirPath;
dirOp.mkpath(newAbsDirPath);
}
if (newFile.absoluteFilePath() == oldFile.absoluteFilePath()) {
throw std::runtime_error("Can't rename or copy a file to itself!");
}
if (newFile.exists()) {
qWarning() << "Deleting the already existing" << newFile;
if (!dirOp.remove(newFile.absoluteFilePath()))
throw std::runtime_error("Failed to remove " +
newFile.absoluteFilePath().toStdString());
}
if (action(oldFile.absoluteFilePath(), newFile.absoluteFilePath())) {
success();
save();
}
else {
throw std::runtime_error("Failed to copy or rename " +
oldFile.absoluteFilePath().toStdString() + " to " +
newFile.absoluteFilePath().toStdString());
}
}
public:
enum class Type : quint64 {
Vivy = Utils::toUnderlying(Utils::DocumentType::Vivy),
......@@ -31,25 +64,22 @@ protected:
// Automate a part of the rename process, just need to provide a "success"
// callback and the new file info.
bool renameWith(const QFileInfo &newFile, auto success) noexcept
void copyWith(const QFileInfo &newFile, auto success)
{
const QFileInfo oldFile(getName());
// Create folder if needed
QDir dirOp;
const QString newAbsDirPath = newFile.dir().absolutePath();
if (!dirOp.exists(newAbsDirPath)) {
qInfo() << "Create folder " << newAbsDirPath;
dirOp.mkpath(newAbsDirPath);
}
if (dirOp.rename(oldFile.absoluteFilePath(), newFile.absoluteFilePath())) {
success();
return true;
auto action = [](const QString &oldFile, const QString &newFileName) noexcept -> bool {
return QFile::copy(oldFile, newFileName);
};
copyOrRenameWith(newFile, action, success);
}
qCritical() << "Failed to rename" << oldFile << "to" << newFile;
return false;
// Automate a part of the rename process, just need to provide a "success"
// callback and the new file info.
void renameWith(const QFileInfo &newFile, auto success)
{
auto action = [](const QString &oldFile, const QString &newFileName) noexcept -> bool {
return QFile::rename(oldFile, newFileName);
};
copyOrRenameWith(newFile, action, success);
}
Type type;
......@@ -57,7 +87,9 @@ protected:
const Uuid uuid{};
public:
virtual bool rename(const QString &) noexcept = 0;
virtual void copy(const QString &) = 0;
virtual void rename(const QString &) = 0;
virtual void save() = 0;
QString getName() const noexcept { return name; }
Type getType() const noexcept { return type; }
......@@ -66,8 +98,8 @@ public:
signals:
void documentChanged();
};
bool operator==(const AbstractDocument &a, const AbstractDocument &b) noexcept;
}
bool operator==(const Vivy::AbstractDocument &a, const Vivy::AbstractDocument &b) noexcept;
#endif // VIVY_ABSTRACT_DOCUMENT_H
#pragma once
#ifndef __cplusplus
#error "This is a C++ header"
#endif
extern "C" {
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavcodec/avfft.h>
#include <memory.h>
}
#include "Utils.hh"
namespace Vivy
{
static inline constexpr auto codecContexteleter = [](AVCodecContext *ptr) noexcept -> void {
if (ptr)
avcodec_free_context(&ptr);
};
static constexpr inline auto dataDeleter = [](double *ptr) noexcept -> void {
if (ptr)
av_free(ptr);
};
static constexpr inline auto avFrameDeleter = [](AVFrame *ptr) noexcept -> void {
if (ptr)
av_frame_free(&ptr);
};
static constexpr inline auto swrContenxtDeleter = [](SwrContext *swr) noexcept -> void {
if (swr)
swr_free(&swr);
};
static inline constexpr auto avFormatContextDeleter = [](AVFormatContext *ptr) noexcept -> void {
if (ptr)
avformat_free_context(ptr);
};
using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>;
using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>;
using AVFramePtr = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>;
using SwrContextPtr = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>;
// Simple abstract class to be inherited by video streams and audio streams.
template <size_t AVMEDIA_TYPE> class AbstractMediaStream {
VIVY_UNMOVABLE_OBJECT(AbstractMediaStream)
public:
static inline constexpr AVMediaType avMediaType = static_cast<AVMediaType>(AVMEDIA_TYPE);
protected:
using Super = AbstractMediaStream<AVMEDIA_TYPE>;
protected:
AbstractMediaStream(AVCodec *streamCodec, AVFormatContext *formatArg, AVStream *streamArg,
int index)
: codecId(streamArg->codecpar->codec_id)
, codec(streamCodec)
, codecParams(streamArg->codecpar)
, dataFormat(formatArg)
, stream(streamArg)
, streamIndexInContext(index)
{
if (codec == nullptr)
throw std::runtime_error("failed to find a decoder for stream");
codecContext.reset(avcodec_alloc_context3(codec));
if (!codecContext)
throw std::runtime_error("failed to allocate codec context");
if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0)
throw std::runtime_error("failed to copy parameters to codec context");
if (avcodec_open2(codecContext.get(), codec, nullptr) < 0)
throw std::runtime_error("failed to open audio decoder for a stream");
qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId;
qDebug() << "[Stream] sample rate:" << codecParams->sample_rate;
qDebug() << "[Stream] bit rate: " << codecParams->bit_rate;
qDebug() << "[Stream] channels: " << codecParams->channels;
}
public:
virtual ~AbstractMediaStream() noexcept {}
int getStreamIndex() const noexcept { return streamIndexInContext; }
QString getName() const noexcept { return QString::fromUtf8(codec->name); }
AVCodecID getCodecId() const noexcept { return codecId; }
virtual QJsonObject getProperties() const noexcept
{
QJsonObject ret;
ret.insert("Codec name", codec->name);
return ret;
}
protected:
// Codec related informations
AVCodecID codecId{ AV_CODEC_ID_NONE };
AVCodec *codec{ nullptr };
AVCodecParameters *codecParams{ nullptr };
AVCodecContextPtr codecContext{ nullptr };
AVFormatContext *dataFormat{ nullptr };
// Stream is held by AudioContext
AVStream *stream{ nullptr };
// Store the index of this stream
int streamIndexInContext;
};
template <typename Stream> class AbstractMediaContext {
VIVY_UNMOVABLE_OBJECT(AbstractMediaContext)
public:
static inline constexpr AVMediaType avMediaType = Stream::avMediaType;
using StreamPtr = std::shared_ptr<Stream>;
using StreamWeakPtr = std::weak_ptr<Stream>;
protected:
using Super = AbstractMediaContext<Stream>;
public:
AbstractMediaContext(const QString &path)
: filePath(path)
{
if (!format)
throw std::runtime_error("out of memory, can't create allocate the AVFormatContext");
const std::string filePathStdHolder = filePath.toStdString();
const char *filename = filePathStdHolder.c_str();
AVFormatContext *formatPtr = format.get();
// Get the format from the file
if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) {
[[maybe_unused]] void *relatedOnFailure =
format.release(); // freed by avformat_open_input
throw std::runtime_error("failed to open file");
}
if (avformat_find_stream_info(formatPtr, nullptr) < 0) {
throw std::runtime_error("failed to get audio stream info");
}
// Populate all the stream indexes
for (uint i = 0; i < format->nb_streams; ++i) {
AVStream *itFmt = format->streams[i];
AVCodecParameters *params = itFmt->codecpar;
AVCodec *streamCodec = avcodec_find_decoder(params->codec_id);
if (streamCodec && streamCodec->type == avMediaType)
audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFmt, i));
}
// Get the default stream
defaultStreamIndex = av_find_best_stream(formatPtr, avMediaType,
-1, // Let AV find one stream
-1, // We don't want related streams
nullptr, 0);
if (defaultStreamIndex < 0)
qCritical() << "Could not find the best stream";
qDebug() << "Opened context for" << path << "with duration" << formatPtr->duration
<< "and default stream index" << defaultStreamIndex;
}
virtual ~AbstractMediaContext() {}
StreamWeakPtr getStream(int index) const noexcept
{
if (index < 0)
return StreamWeakPtr{ spareNullSreamPtr };
uint unsignedIndex = static_cast<uint>(index);
const auto found = audioStreams.find(unsignedIndex);
if (found != audioStreams.end())
return StreamWeakPtr{ *found };
return StreamWeakPtr{ spareNullSreamPtr };
}
StreamWeakPtr getDefaultStream() const noexcept
{
return (defaultStreamIndex < 0) ? StreamWeakPtr{ spareNullSreamPtr }
: getStream(defaultStreamIndex);
}
virtual QString getElementName() const noexcept
{
return "Context: " + QFileInfo(filePath).baseName();
}
virtual QJsonDocument getProperties() const noexcept
{
QJsonDocument ret;
QJsonArray streams;
QFileInfo file(filePath);
for (const auto &audioStreamPtr : audioStreams) {
streams.append(audioStreamPtr->getProperties());
}
QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } };
ret.setObject(self);
return ret;
}
private:
AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter };
const QString filePath; // Usefull information
QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file
int defaultStreamIndex{ -1 };
// Spare always null shared pointer, to be used when the audioStream[i] was
// not found.
StreamPtr spareNullSreamPtr{ nullptr };
};
}
......@@ -7,6 +7,5 @@
#include "AssPrivate.hh"
#include "AssFactory.hh"
#include "StyleProperties.hh"
#include <memory.h>
#endif // VIVY_ASS_ASS_H
#include "AssFactory.hh"
#include <algorithm>
#include <stdexcept>
using namespace Vivy::Ass;
bool
......
......@@ -6,12 +6,6 @@
#include "Line.hh"
#include "AssPrivate.hh"
#include <memory>
#include <QFile>
#include <QMap>
#include <QVariant>
#include <QString>
// An ASS file is basically an INI file, but some keys are present multiple
// times (V4+ Styles/Style, Events/Dialogue, Events/Comment).
......@@ -21,11 +15,7 @@ class AssFactory final {
VIVY_UNMOVABLE_OBJECT(AssFactory)
public:
enum class Section {
ScriptInfo = 1,
Styles = 2,
Events = 3,
};
enum class Section { ScriptInfo = 1, Styles = 2, Events = 3 };
using SectionContent = QMap<QString, QVariant>;
......
#pragma once
#include <memory>
namespace Vivy::Ass
{
class Style;
......
#include "Line.hh"
#include "AssFactory.hh"
#include <QRegularExpression>
using namespace Vivy::Ass;
Line::Line(AssFactory *const factory, const QString &lineString)
......
#ifndef VIVY_ASS_LINE_H
#define VIVY_ASS_LINE_H
#pragma once
#include <QString>
#include <QtGlobal>
#include "Syl.hh"
#include "StyleProperties.hh"
#include "Style.hh"
......@@ -47,7 +44,4 @@ public:
private:
void initSylFromString(const QString &) noexcept;
};
}
#endif // VIVY_ASS_LINE_H
#include "Style.hh"
#include <stdexcept>
#include <QJsonDocument>
#include <QJsonObject>
#include "../Utils.hh"
using namespace Vivy::Ass;
......@@ -179,13 +176,13 @@ Style::getProperties() const noexcept
};
QJsonObject styleFontProperties{
{ "Scale X", static_cast<const double>(scaleX) },
{ "Scale Y", static_cast<const double>(scaleY) },
{ "Spacing", static_cast<const double>(spacing) },
{ "Angle", static_cast<const double>(angle) },
{ "Border Style", static_cast<const double>(borderStyle) },
{ "Outline", static_cast<const double>(outline) },
{ "Shadow", static_cast<const double>(shadow) },
{ "Scale X", static_cast<double>(scaleX) },
{ "Scale Y", static_cast<double>(scaleY) },
{ "Spacing", static_cast<double>(spacing) },
{ "Angle", static_cast<double>(angle) },
{ "Border Style", static_cast<double>(borderStyle) },
{ "Outline", static_cast<double>(outline) },
{ "Shadow", static_cast<double>(shadow) },
};
QJsonObject styleMargins{
......
#ifndef VIVY_STYLE_H
#define VIVY_STYLE_H
#include <QString>
#include <QVector>
#include <QtGlobal>
#include <QColor>
#include <QObject>
#pragma once
#include "AssPrivate.hh"
namespace Vivy::Ass
{
namespace Color
{
QColor fromString(const QString &) noexcept;
QString toString(const QColor &) noexcept;
struct Color {
static QColor fromString(const QString &) noexcept;
static QString toString(const QColor &) noexcept;
static inline const QColor defaultValue = QColor(0, 0, 0, 0);
private:
Color() = default;
};
class Style final {
......@@ -47,7 +44,4 @@ public:
QString getElementName() const noexcept;
QJsonDocument getProperties() const noexcept;
};
}
#endif
#ifndef VIVY_ASS_STYLE_PROPERTIES_H
#define VIVY_ASS_STYLE_PROPERTIES_H
#include <QColor>
#include <QString>
#pragma once
#include "Style.hh"
namespace Vivy::Ass
......@@ -31,7 +27,4 @@ struct StyleProperties final {
// Alignement is bottom center
int alignment{ 2 };
};
}
#endif // VIVY_ASS_STYLE_PROPERTIES_H
......@@ -22,7 +22,7 @@ public:
public:
enum class ConstructMode {
Raw, // Don't read ASS tags
ReadAssTags, // Read ass tags
ReadAssTags // Read ass tags
};
explicit Syl(const Syl &) noexcept = default;
......
#include "Audio.hh"
#include <QJsonObject>
#include <QJsonArray>
using namespace Vivy;
// AudioContext class implementation
// Create an audio contecxt from a file
AudioContext::AudioContext(const QString &path)
: filePath(path)
{
if (!format)
throw std::runtime_error("out of memory, can't create allocate the AVFormatContext");
const std::string filePathStdHolder = filePath.toStdString();
const char *filename = filePathStdHolder.c_str();
AVFormatContext *formatPtr = format.get();
// Get the format from the audio file
if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) {
[[maybe_unused]] void *relatedOnFailure = format.release(); // freed by avformat_open_input
throw std::runtime_error("failed to open file");
}
if (avformat_find_stream_info(formatPtr, nullptr) < 0) {
throw std::runtime_error("failed to get audio stream info");
}
// Populate all the stream indexes
for (uint i = 0; i < format->nb_streams; ++i) {
AVStream *itFormat = format->streams[i];
AVCodecParameters *params = itFormat->codecpar;
AVCodec *streamCodec = avcodec_find_decoder(params->codec_id);
if (streamCodec && streamCodec->type == AVMEDIA_TYPE_AUDIO) {
audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i));
}
}
// Get the default stream
defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_AUDIO,
-1, // Let AV find one stream
-1, // We don't want related streams
nullptr, 0);
if (defaultStreamIndex < 0) {
qCritical() << "Could not find the best audio stream";
}
qDebug() << "Opened audio context for" << path << "with duration" << formatPtr->duration
<< "and default stream index" << defaultStreamIndex;
}
// Get a specific audio stream, try to lock the weak pointer. if the index was
// not present the used_count will be 0 and you won't be able to lock it.
AudioContext::StreamWeakPtr
AudioContext::getStream(int index) const noexcept
: Super(path)
{
if (index < 0)
return StreamWeakPtr{ spareNullSreamPtr };
uint unsignedIndex = static_cast<uint>(index);
const auto found = audioStreams.find(unsignedIndex);
if (found != audioStreams.end())
return StreamWeakPtr{ *found };
return StreamWeakPtr{ spareNullSreamPtr };
}
// Get the default stream of this audio context. If no default audio stream is
// present you won't be able to lock the weak pointer.
AudioContext::StreamWeakPtr
AudioContext::getDefaultStream() const noexcept
{
if (defaultStreamIndex < 0)
return StreamWeakPtr{ spareNullSreamPtr };
return getStream(defaultStreamIndex);
}
QString
AudioContext::getElementName() const noexcept
{
QFileInfo file(filePath);
return "AudioContext: " + file.baseName();
return "Audio" + Super::getElementName();
}
QJsonDocument
AudioContext::getProperties() const noexcept
{
QJsonDocument ret;
QJsonArray streams;
QFileInfo file(filePath);
for (const auto &audioStreamPtr : audioStreams) {
streams.append(audioStreamPtr->getProperties());
}
QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } };
ret.setObject(self);
return ret;
return Super::getProperties();
}
// AudioContext::Stream class implementation
// AudioStream class implementation
// Constructor, need an AVFormat and an AVStream
AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *stream,
AudioStream::AudioStream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *streamArg,
int index)
: codecId(stream->codecpar->codec_id)
, codec(streamCodec)
, codecParams(stream->codecpar)
, audioStream(stream)
, streamIndexInAudioContext(index)
, dataFormat(formatPtr)
: Super(streamCodec, formatPtr, streamArg, index)
{
if (codec == nullptr)
throw std::runtime_error("failed to find a decoder for stream");
codecContext.reset(avcodec_alloc_context3(codec));
if (!codecContext)
throw std::runtime_error("failed to allocate codec context");
if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0)
throw std::runtime_error("failed to copy parameters to codec context");
if (avcodec_open2(codecContext.get(), codec, nullptr) < 0)
throw std::runtime_error("failed to open audio decoder for a stream");
SwrContext *s = dataSwrContext.get();
av_opt_set_int(s, "in_channel_count", codecContext->channels, 0);
av_opt_set_int(s, "out_channel_count", 1, 0);
......@@ -140,14 +41,9 @@ AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, A
swr_init(s);
if (!swr_is_initialized(s))
throw std::runtime_error("failed to initialize SwrContext resampler");
qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId;
qDebug() << "[Stream] sample rate:" << codecParams->sample_rate;
qDebug() << "[Stream] bit rate: " << codecParams->bit_rate;
qDebug() << "[Stream] channels: " << codecParams->channels;
}
AudioContext::Stream::~Stream() noexcept
AudioStream::~AudioStream() noexcept
{
if (dataPtr) {
qDebug() << "Free data ptr";
......@@ -157,10 +53,9 @@ AudioContext::Stream::~Stream() noexcept
}
QJsonObject
AudioContext::Stream::getProperties() const noexcept
AudioStream::getProperties() const noexcept
{
QJsonObject ret;
ret.insert("Codec name", codec->name);
QJsonObject ret = Super::getProperties();
ret.insert("Sample rate", codecParams->sample_rate);
ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate));
ret.insert("Channels", codecParams->channels);
......@@ -169,11 +64,11 @@ AudioContext::Stream::getProperties() const noexcept
// Decode the data, don't call it if data already decoded
void
AudioContext::Stream::decodeData()
AudioStream::decodeData()
{
if (isDecoded())
throw std::logic_error("audio stream is already resampled");
qDebug() << "Launch decoding of stream" << streamIndexInAudioContext;
qDebug() << "Launch decoding of stream" << streamIndexInContext;
AVPacket packet;
av_init_packet(&packet);
......@@ -181,7 +76,7 @@ AudioContext::Stream::decodeData()
// Iterate through frames
while (av_read_frame(dataFormat, &packet) >= 0) {
// Only decode audio
if (packet.stream_index != streamIndexInAudioContext) {
if (packet.stream_index != streamIndexInContext) {
av_packet_unref(&packet);
continue;
}
......@@ -242,13 +137,13 @@ AudioContext::Stream::decodeData()
av_packet_unref(&packet);
}
qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext
qDebug() << "Decoding data finished for stream" << streamIndexInContext
<< "dataPtr =" << dataPtr << "with dataSize =" << dataSize;
}
// Delete decoded data, clean up thing
void
AudioContext::Stream::cleanUpData() noexcept
AudioStream::cleanUpData() noexcept
{
free(dataPtr);
dataPtr = nullptr;
......@@ -257,14 +152,14 @@ AudioContext::Stream::cleanUpData() noexcept
// Get the number of channels in the audio context stream
int
AudioContext::Stream::getChannels() const noexcept
AudioStream::getChannels() const noexcept
{
return codecContext->channels;
}
// Get the sample rate
int
AudioContext::Stream::getSampleRate() const noexcept
AudioStream::getSampleRate() const noexcept
{
return codecContext->sample_rate;
}
......@@ -272,35 +167,21 @@ AudioContext::Stream::getSampleRate() const noexcept
// Get the sample rate of the decoded data (need to call decodeData for that
// value to have a meaning).
int
AudioContext::Stream::getDataSampleRate() const noexcept
AudioStream::getDataSampleRate() const noexcept
{
return resamplerSampleRate;
}
// Get the name of the codec used to decode the stream
QString
AudioContext::Stream::getName() const noexcept
{
return QString(codec->name);
}
// Get the codec's ID used to decode the stream
AVCodecID
AudioContext::Stream::getCodecId() const noexcept
{
return codecId;
}
// Get the bit rate of the stream
qint64
AudioContext::Stream::getBitRate() const noexcept
AudioStream::getBitRate() const noexcept
{
return codecContext->bit_rate;
}
// Get the information about the decoded state of this stream
bool
AudioContext::Stream::isDecoded() const noexcept
AudioStream::isDecoded() const noexcept
{
return dataPtr != nullptr;
}
......@@ -308,7 +189,7 @@ AudioContext::Stream::isDecoded() const noexcept
// Get the decoded data's size, will return 0 if the data is not decoded. Don't
// rely on this behaviour to get the decoded state of the stream!
size_t
AudioContext::Stream::getDecodedDataSize() const noexcept
AudioStream::getDecodedDataSize() const noexcept
{
return dataSize;
}
......@@ -316,28 +197,22 @@ AudioContext::Stream::getDecodedDataSize() const noexcept
// Get the decoded data, safe to call even if the data is not decoded has the
// pointer will be null. You must check it with an `if` statement.
double *
AudioContext::Stream::getDecodedData() const noexcept
AudioStream::getDecodedData() const noexcept
{
return dataPtr;
}
// Get the chunk size of the decoded data
size_t
AudioContext::Stream::getDecodedChunkSize() const noexcept
AudioStream::getDecodedChunkSize() const noexcept
{
return 512;
}
// Get the decalage of the decoded data
size_t
AudioContext::Stream::getDecodedDecalage() const noexcept
AudioStream::getDecodedDecalage() const noexcept
{
constexpr size_t overlap = 128; // The overlap
return getDecodedChunkSize() - overlap;
}
int
AudioContext::Stream::getStreamIndex() const noexcept
{
return streamIndexInAudioContext;
}
#ifndef VIVY_AUDIO_H
#define VIVY_AUDIO_H
#pragma once
#ifndef __cplusplus
#error "This is a C++ header"
#endif
extern "C" {
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavcodec/avfft.h>
}
#include "Utils.hh"
#include <QtGlobal>
#include <QMap>
#include <QVector>
#include <QString>
#include <memory.h>
#include "AbstractMediaContext.hh"
namespace Vivy
{
class AudioContext;
// The memory representation of an audio file. It is created inplace and should
// not be moved around. It should be used to get the properties of an audio
// file and its streams. The user can use a stream from the file and get data
// from it.
class AudioContext final {
VIVY_UNMOVABLE_OBJECT(AudioContext)
public:
// Hold all the data for a stream in an audio file. Should only be owned by
// the AudioContext class.
class Stream final {
VIVY_UNMOVABLE_OBJECT(Stream)
// All the needed deleters
static inline constexpr auto codecContexteleter = [](AVCodecContext *ptr) noexcept -> void {
if (ptr)
avcodec_free_context(&ptr);
};
static constexpr inline auto dataDeleter = [](double *ptr) noexcept -> void {
if (ptr)
av_free(ptr);
};
static constexpr inline auto avFrameDeleter = [](AVFrame *ptr) noexcept -> void {
if (ptr)
av_frame_free(&ptr);
};
static constexpr inline auto swrContenxtDeleter = [](SwrContext *swr) noexcept -> void {
if (swr)
swr_free(&swr);
};
// All the used types
using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>;
using DataPtr = std::shared_ptr<double[]>;
using AVFramePtr = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>;
using SwrContextPtr = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>;
class AudioStream final : public AbstractMediaStream<AVMEDIA_TYPE_AUDIO> {
VIVY_UNMOVABLE_OBJECT(AudioStream)
public:
Stream(AVCodec *, AVFormatContext *, AVStream *, int index);
~Stream() noexcept;
// The non-owning view of the stream's data
using DataWeakPtr = std::weak_ptr<double[]>;
AudioStream(AVCodec *, AVFormatContext *, AVStream *, int index);
~AudioStream() noexcept override;
// Decode the stream
void decodeData();
......@@ -82,30 +32,11 @@ public:
// Some getters
int getChannels() const noexcept;
int getSampleRate() const noexcept;
QString getName() const noexcept;
AVCodecID getCodecId() const noexcept;
qint64 getBitRate() const noexcept;
// Get the index from the audio context
int getStreamIndex() const noexcept;
QJsonObject getProperties() const noexcept;
QJsonObject getProperties() const noexcept override;
private:
// Codec related informations
AVCodecID codecId{ AV_CODEC_ID_NONE };
AVCodec *codec{ nullptr };
AVCodecParameters *codecParams{ nullptr };
AVCodecContextPtr codecContext{ nullptr };
// Stream is held by AudioContext
AVStream *audioStream{ nullptr };
// Store the index of this stream
int streamIndexInAudioContext;
// Resampled frame data
AVFormatContext *dataFormat;
SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter };
AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter };
double *dataPtr{ nullptr };
......@@ -118,40 +49,21 @@ public:
static constexpr uint resamplerSampleRate = 44100;
};
using StreamPtr = std::shared_ptr<Stream>;
// The memory representation of an audio file. It is created inplace and should
// not be moved around. It should be used to get the properties of an audio
// file and its streams. The user can use a stream from the file and get data
// from it.
class AudioContext final : public AbstractMediaContext<AudioStream> {
VIVY_UNMOVABLE_OBJECT(AudioContext)
public:
AudioContext(const QString &path);
~AudioContext() noexcept = default;
// The stream non-owning view pointer
using StreamWeakPtr = std::weak_ptr<Stream>;
using StreamPtr = std::shared_ptr<AudioStream>;
using StreamWeakPtr = std::weak_ptr<AudioStream>;
StreamWeakPtr getStream(int) const noexcept;
StreamWeakPtr getDefaultStream() const noexcept;
QString getElementName() const noexcept;
QJsonDocument getProperties() const noexcept;
private:
// Regarding the format
static inline constexpr auto avFormatContextDeleter =
[](AVFormatContext *ptr) noexcept -> void {
if (ptr)
avformat_free_context(ptr);
};
using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>;
AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter };
QString filePath; // Usefull information
QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file
int defaultStreamIndex{ -1 };
public:
AudioContext(const QString &path);
// Spare always null shared pointer, to be used when the audioStream[i] was
// not found.
StreamPtr spareNullSreamPtr{ nullptr };
QString getElementName() const noexcept override;
QJsonDocument getProperties() const noexcept override;
};
}
#endif // VIVY_AUDIO_H