Move FFMPEG Decoder to lib

This commit is contained in:
Florian Märkl 2020-12-25 17:58:56 +01:00
commit 673a2de9c1
No known key found for this signature in database
GPG key ID: 125BC8A5A6A1E857
24 changed files with 566 additions and 350 deletions

View file

@ -16,6 +16,12 @@ option(CHIAKI_ENABLE_ANDROID "Enable Android (Use only as part of the Gradle Pro
option(CHIAKI_ENABLE_BOREALIS "Enable Borealis GUI (For Nintendo Switch or PC)" OFF) option(CHIAKI_ENABLE_BOREALIS "Enable Borealis GUI (For Nintendo Switch or PC)" OFF)
tri_option(CHIAKI_ENABLE_SETSU "Enable libsetsu for touchpad input from controller" AUTO) tri_option(CHIAKI_ENABLE_SETSU "Enable libsetsu for touchpad input from controller" AUTO)
option(CHIAKI_LIB_ENABLE_OPUS "Use Opus as part of Chiaki Lib" ON) option(CHIAKI_LIB_ENABLE_OPUS "Use Opus as part of Chiaki Lib" ON)
if(CHIAKI_ENABLE_GUI OR CHIAKI_ENABLE_BOREALIS)
set(CHIAKI_FFMPEG_DEFAULT ON)
else()
set(CHIAKI_FFMPEG_DEFAULT AUTO)
endif()
tri_option(CHIAKI_ENABLE_FFMPEG_DECODER "Enable FFMPEG video decoder" ${CHIAKI_FFMPEG_DEFAULT})
tri_option(CHIAKI_ENABLE_PI_DECODER "Enable Raspberry Pi-specific video decoder (requires libraspberrypi0 and libraspberrypi-doc)" AUTO) tri_option(CHIAKI_ENABLE_PI_DECODER "Enable Raspberry Pi-specific video decoder (requires libraspberrypi0 and libraspberrypi-doc)" AUTO)
option(CHIAKI_LIB_ENABLE_MBEDTLS "Use mbedtls instead of OpenSSL as part of Chiaki Lib" OFF) option(CHIAKI_LIB_ENABLE_MBEDTLS "Use mbedtls instead of OpenSSL as part of Chiaki Lib" OFF)
option(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT "Use OpenSSL as CMake external project" OFF) option(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT "Use OpenSSL as CMake external project" OFF)
@ -85,6 +91,24 @@ if(CHIAKI_LIB_ENABLE_MBEDTLS)
add_definitions(-DCHIAKI_LIB_ENABLE_MBEDTLS) add_definitions(-DCHIAKI_LIB_ENABLE_MBEDTLS)
endif() endif()
if(CHIAKI_ENABLE_FFMPEG_DECODER)
find_package(FFMPEG COMPONENTS avcodec avutil)
if(FFMPEG_FOUND)
set(CHIAKI_ENABLE_FFMPEG_DECODER ON)
else()
if(NOT CHIAKI_ENABLE_FFMPEG_DECODER STREQUAL AUTO)
message(FATAL_ERROR "CHIAKI_ENABLE_FFMPEG_DECODER is set to ON, but ffmpeg could not be found.")
endif()
set(CHIAKI_ENABLE_FFMPEG_DECODER OFF)
endif()
endif()
if(CHIAKI_ENABLE_FFMPEG_DECODER)
message(STATUS "FFMPEG Decoder enabled")
else()
message(STATUS "FFMPEG Decoder disabled")
endif()
if(CHIAKI_ENABLE_PI_DECODER) if(CHIAKI_ENABLE_PI_DECODER)
find_package(ILClient) find_package(ILClient)
if(ILClient_FOUND) if(ILClient_FOUND)
@ -105,7 +129,6 @@ else()
message(STATUS "Pi Decoder disabled") message(STATUS "Pi Decoder disabled")
endif() endif()
add_subdirectory(lib) add_subdirectory(lib)
if(CHIAKI_ENABLE_CLI) if(CHIAKI_ENABLE_CLI)

View file

@ -14,8 +14,6 @@ if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN) add_definitions(-DWIN32_LEAN_AND_MEAN)
endif() endif()
find_package(FFMPEG REQUIRED COMPONENTS avcodec avutil)
set(RESOURCE_FILES "") set(RESOURCE_FILES "")
if(APPLE) if(APPLE)
@ -28,8 +26,6 @@ add_executable(chiaki WIN32
src/main.cpp src/main.cpp
include/streamwindow.h include/streamwindow.h
src/streamwindow.cpp src/streamwindow.cpp
include/videodecoder.h
src/videodecoder.cpp
include/mainwindow.h include/mainwindow.h
src/mainwindow.cpp src/mainwindow.cpp
include/dynamicgridwidget.h include/dynamicgridwidget.h
@ -73,7 +69,6 @@ if(CHIAKI_ENABLE_CLI)
target_link_libraries(chiaki chiaki-cli-lib) target_link_libraries(chiaki chiaki-cli-lib)
endif() endif()
target_link_libraries(chiaki FFMPEG::avcodec FFMPEG::avutil)
target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Concurrent Qt5::Multimedia Qt5::OpenGL Qt5::Svg) target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Concurrent Qt5::Multimedia Qt5::OpenGL Qt5::Svg)
if(APPLE) if(APPLE)
target_link_libraries(chiaki Qt5::MacExtras) target_link_libraries(chiaki Qt5::MacExtras)
@ -87,6 +82,9 @@ if(CHIAKI_ENABLE_SETSU)
target_link_libraries(chiaki setsu) target_link_libraries(chiaki setsu)
target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SETSU) target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SETSU)
endif() endif()
if(NOT CHIAKI_ENABLE_FFMPEG_DECODER)
message(FATAL_ERROR "Chiaki GUI requires CHIAKI_ENABLE_FFMPEG_DECODER=ON")
endif()
set_target_properties(chiaki PROPERTIES set_target_properties(chiaki PROPERTIES
MACOSX_BUNDLE TRUE MACOSX_BUNDLE TRUE

View file

@ -6,8 +6,10 @@
#include <QObject> #include <QObject>
#include <QOpenGLWidget> #include <QOpenGLWidget>
#include <chiaki/ffmpegdecoder.h>
class StreamSession;
class AVOpenGLWidget; class AVOpenGLWidget;
class VideoDecoder;
class QSurface; class QSurface;
class AVOpenGLFrameUploader: public QObject class AVOpenGLFrameUploader: public QObject
@ -15,16 +17,16 @@ class AVOpenGLFrameUploader: public QObject
Q_OBJECT Q_OBJECT
private: private:
VideoDecoder *decoder; StreamSession *session;
AVOpenGLWidget *widget; AVOpenGLWidget *widget;
QOpenGLContext *context; QOpenGLContext *context;
QSurface *surface; QSurface *surface;
private slots: private slots:
void UpdateFrame(); void UpdateFrameFromDecoder();
public: public:
AVOpenGLFrameUploader(VideoDecoder *decoder, AVOpenGLWidget *widget, QOpenGLContext *context, QSurface *surface); AVOpenGLFrameUploader(StreamSession *session, AVOpenGLWidget *widget, QOpenGLContext *context, QSurface *surface);
}; };
#endif // CHIAKI_AVOPENGLFRAMEUPLOADER_H #endif // CHIAKI_AVOPENGLFRAMEUPLOADER_H

View file

@ -15,7 +15,7 @@ extern "C"
#define MAX_PANES 3 #define MAX_PANES 3
class VideoDecoder; class StreamSession;
class AVOpenGLFrameUploader; class AVOpenGLFrameUploader;
class QOffscreenSurface; class QOffscreenSurface;
@ -53,7 +53,7 @@ class AVOpenGLWidget: public QOpenGLWidget
Q_OBJECT Q_OBJECT
private: private:
VideoDecoder *decoder; StreamSession *session;
GLuint program; GLuint program;
GLuint vbo; GLuint vbo;
@ -74,7 +74,7 @@ class AVOpenGLWidget: public QOpenGLWidget
public: public:
static QSurfaceFormat CreateSurfaceFormat(); static QSurfaceFormat CreateSurfaceFormat();
explicit AVOpenGLWidget(VideoDecoder *decoder, QWidget *parent = nullptr); explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr);
~AVOpenGLWidget() override; ~AVOpenGLWidget() override;
void SwapFrames(); void SwapFrames();

81
gui/include/logsniffer.h Normal file
View file

@ -0,0 +1,81 @@
// SPDX-License-Identifier: LicenseRef-GPL-3.0-or-later-OpenSSL
#ifndef CHIAKI_LOG_SNIFFER_H
#define CHIAKI_LOG_SNIFFER_H
#include <chiaki/controller.h>
#include <QObject>
#include <QSet>
#include <QMap>
#include <QString>
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
#include <SDL.h>
#endif
class Controller;
class ControllerManager : public QObject
{
Q_OBJECT
friend class Controller;
private:
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
QSet<SDL_JoystickID> available_controllers;
#endif
QMap<int, Controller *> open_controllers;
void ControllerClosed(Controller *controller);
private slots:
void UpdateAvailableControllers();
void HandleEvents();
void ControllerEvent(int device_id);
public:
static ControllerManager *GetInstance();
ControllerManager(QObject *parent = nullptr);
~ControllerManager();
QSet<int> GetAvailableControllers();
Controller *OpenController(int device_id);
signals:
void AvailableControllersUpdated();
};
class Controller : public QObject
{
Q_OBJECT
friend class ControllerManager;
private:
Controller(int device_id, ControllerManager *manager);
void UpdateState();
ControllerManager *manager;
int id;
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
SDL_GameController *controller;
#endif
public:
~Controller();
bool IsConnected();
int GetDeviceID();
QString GetName();
ChiakiControllerState GetState();
signals:
void StateChanged();
};
#endif // CHIAKI_CONTROLLERMANAGER_H

View file

@ -6,7 +6,6 @@
#include <chiaki/session.h> #include <chiaki/session.h>
#include "host.h" #include "host.h"
#include "videodecoder.h"
#include <QSettings> #include <QSettings>
#include <QAudioDeviceInfo> #include <QAudioDeviceInfo>
@ -79,8 +78,8 @@ class Settings : public QObject
Decoder GetDecoder() const; Decoder GetDecoder() const;
void SetDecoder(Decoder decoder); void SetDecoder(Decoder decoder);
HardwareDecodeEngine GetHardwareDecodeEngine() const; QString GetHardwareDecoder() const;
void SetHardwareDecodeEngine(HardwareDecodeEngine enabled); void SetHardwareDecoder(const QString &hw_decoder);
unsigned int GetAudioBufferSizeDefault() const; unsigned int GetAudioBufferSizeDefault() const;

View file

@ -27,7 +27,7 @@ class SettingsDialog : public QDialog
QLineEdit *audio_buffer_size_edit; QLineEdit *audio_buffer_size_edit;
QComboBox *audio_device_combo_box; QComboBox *audio_device_combo_box;
QCheckBox *pi_decoder_check_box; QCheckBox *pi_decoder_check_box;
QComboBox *hardware_decode_combo_box; QComboBox *hw_decoder_combo_box;
QListWidget *registered_hosts_list_widget; QListWidget *registered_hosts_list_widget;
QPushButton *delete_registered_host_button; QPushButton *delete_registered_host_button;

View file

@ -5,6 +5,7 @@
#include <chiaki/session.h> #include <chiaki/session.h>
#include <chiaki/opusdecoder.h> #include <chiaki/opusdecoder.h>
#include <chiaki/ffmpegdecoder.h>
#if CHIAKI_LIB_ENABLE_PI_DECODER #if CHIAKI_LIB_ENABLE_PI_DECODER
#include <chiaki/pidecoder.h> #include <chiaki/pidecoder.h>
@ -14,7 +15,6 @@
#include <setsu.h> #include <setsu.h>
#endif #endif
#include "videodecoder.h"
#include "exception.h" #include "exception.h"
#include "sessionlog.h" #include "sessionlog.h"
#include "controllermanager.h" #include "controllermanager.h"
@ -41,7 +41,7 @@ struct StreamSessionConnectInfo
Settings *settings; Settings *settings;
QMap<Qt::Key, int> key_map; QMap<Qt::Key, int> key_map;
Decoder decoder; Decoder decoder;
HardwareDecodeEngine hw_decode_engine; QString hw_decoder;
QString audio_out_device; QString audio_out_device;
uint32_t log_level_mask; uint32_t log_level_mask;
QString log_file; QString log_file;
@ -78,7 +78,8 @@ class StreamSession : public QObject
ChiakiControllerState keyboard_state; ChiakiControllerState keyboard_state;
VideoDecoder *video_decoder; ChiakiFfmpegDecoder *ffmpeg_decoder;
void TriggerFfmpegFrameAvailable();
#if CHIAKI_LIB_ENABLE_PI_DECODER #if CHIAKI_LIB_ENABLE_PI_DECODER
ChiakiPiDecoder *pi_decoder; ChiakiPiDecoder *pi_decoder;
#endif #endif
@ -91,7 +92,6 @@ class StreamSession : public QObject
QMap<Qt::Key, int> key_map; QMap<Qt::Key, int> key_map;
void PushAudioFrame(int16_t *buf, size_t samples_count); void PushAudioFrame(int16_t *buf, size_t samples_count);
void PushVideoSample(uint8_t *buf, size_t buf_size);
void Event(ChiakiEvent *event); void Event(ChiakiEvent *event);
#if CHIAKI_GUI_ENABLE_SETSU #if CHIAKI_GUI_ENABLE_SETSU
void HandleSetsuEvent(SetsuEvent *event); void HandleSetsuEvent(SetsuEvent *event);
@ -112,8 +112,9 @@ class StreamSession : public QObject
void SetLoginPIN(const QString &pin); void SetLoginPIN(const QString &pin);
ChiakiLog *GetChiakiLog() { return log.GetChiakiLog(); }
QList<Controller *> GetControllers() { return controllers.values(); } QList<Controller *> GetControllers() { return controllers.values(); }
VideoDecoder *GetVideoDecoder() { return video_decoder; } ChiakiFfmpegDecoder *GetFfmpegDecoder() { return ffmpeg_decoder; }
#if CHIAKI_LIB_ENABLE_PI_DECODER #if CHIAKI_LIB_ENABLE_PI_DECODER
ChiakiPiDecoder *GetPiDecoder() { return pi_decoder; } ChiakiPiDecoder *GetPiDecoder() { return pi_decoder; }
#endif #endif
@ -122,7 +123,7 @@ class StreamSession : public QObject
void HandleMouseEvent(QMouseEvent *event); void HandleMouseEvent(QMouseEvent *event);
signals: signals:
void CurrentImageUpdated(); void FfmpegFrameAvailable();
void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); void SessionQuit(ChiakiQuitReason reason, const QString &reason_str);
void LoginPINRequested(bool incorrect); void LoginPINRequested(bool incorrect);

View file

@ -1,77 +0,0 @@
// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_VIDEODECODER_H
#define CHIAKI_VIDEODECODER_H
#include <chiaki/log.h>
#include "exception.h"
#include <QMap>
#include <QMutex>
#include <QObject>
extern "C"
{
#include <libavcodec/avcodec.h>
}
#include <cstdint>
typedef enum {
HW_DECODE_NONE = 0,
HW_DECODE_VAAPI = 1,
HW_DECODE_VDPAU = 2,
HW_DECODE_VIDEOTOOLBOX = 3,
HW_DECODE_CUDA = 4,
} HardwareDecodeEngine;
static const QMap<HardwareDecodeEngine, const char *> hardware_decode_engine_names = {
{ HW_DECODE_NONE, "none"},
{ HW_DECODE_VAAPI, "vaapi"},
{ HW_DECODE_VDPAU, "vdpau"},
{ HW_DECODE_VIDEOTOOLBOX, "videotoolbox"},
{ HW_DECODE_CUDA, "cuda"},
};
class VideoDecoderException: public Exception
{
public:
explicit VideoDecoderException(const QString &msg) : Exception(msg) {};
};
class VideoDecoder: public QObject
{
Q_OBJECT
public:
VideoDecoder(HardwareDecodeEngine hw_decode_engine, ChiakiLog *log);
~VideoDecoder();
void PushFrame(uint8_t *buf, size_t buf_size);
AVFrame *PullFrame();
AVFrame *GetFromHardware(AVFrame *hw_frame);
ChiakiLog *GetChiakiLog() { return log; }
enum AVPixelFormat PixelFormat() { return hw_decode_engine?AV_PIX_FMT_NV12:AV_PIX_FMT_YUV420P; }
signals:
void FramesAvailable();
private:
HardwareDecodeEngine hw_decode_engine;
ChiakiLog *log;
QMutex mutex;
AVCodec *codec;
AVCodecContext *codec_context;
enum AVPixelFormat hw_pix_fmt;
AVBufferRef *hw_device_ctx;
};
#endif // CHIAKI_VIDEODECODER_H

View file

@ -2,31 +2,38 @@
#include <avopenglframeuploader.h> #include <avopenglframeuploader.h>
#include <avopenglwidget.h> #include <avopenglwidget.h>
#include <videodecoder.h> #include <streamsession.h>
#include <QOpenGLContext> #include <QOpenGLContext>
#include <QOpenGLFunctions> #include <QOpenGLFunctions>
AVOpenGLFrameUploader::AVOpenGLFrameUploader(VideoDecoder *decoder, AVOpenGLWidget *widget, QOpenGLContext *context, QSurface *surface) AVOpenGLFrameUploader::AVOpenGLFrameUploader(StreamSession *session, AVOpenGLWidget *widget, QOpenGLContext *context, QSurface *surface)
: QObject(nullptr), : QObject(nullptr),
decoder(decoder), session(session),
widget(widget), widget(widget),
context(context), context(context),
surface(surface) surface(surface)
{ {
connect(decoder, SIGNAL(FramesAvailable()), this, SLOT(UpdateFrame())); connect(session, &StreamSession::FfmpegFrameAvailable, this, &AVOpenGLFrameUploader::UpdateFrameFromDecoder);
} }
void AVOpenGLFrameUploader::UpdateFrame() void AVOpenGLFrameUploader::UpdateFrameFromDecoder()
{ {
ChiakiFfmpegDecoder *decoder = session->GetFfmpegDecoder();
if(!decoder)
{
CHIAKI_LOGE(session->GetChiakiLog(), "Session has no ffmpeg decoder");
return;
}
if(QOpenGLContext::currentContext() != context) if(QOpenGLContext::currentContext() != context)
context->makeCurrent(surface); context->makeCurrent(surface);
AVFrame *next_frame = decoder->PullFrame(); AVFrame *next_frame = chiaki_ffmpeg_decoder_pull_frame(decoder);
if(!next_frame) if(!next_frame)
return; return;
bool success = widget->GetBackgroundFrame()->Update(next_frame, decoder->GetChiakiLog()); bool success = widget->GetBackgroundFrame()->Update(next_frame, decoder->log);
av_frame_free(&next_frame); av_frame_free(&next_frame);
if(success) if(success)

View file

@ -1,8 +1,8 @@
// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL // SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include <avopenglwidget.h> #include <avopenglwidget.h>
#include <videodecoder.h>
#include <avopenglframeuploader.h> #include <avopenglframeuploader.h>
#include <streamsession.h>
#include <QOffscreenSurface> #include <QOffscreenSurface>
#include <QOpenGLContext> #include <QOpenGLContext>
@ -123,14 +123,15 @@ QSurfaceFormat AVOpenGLWidget::CreateSurfaceFormat()
return format; return format;
} }
AVOpenGLWidget::AVOpenGLWidget(VideoDecoder *decoder, QWidget *parent) AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent)
: QOpenGLWidget(parent), : QOpenGLWidget(parent),
decoder(decoder) session(session)
{ {
enum AVPixelFormat pixel_format = chiaki_ffmpeg_decoder_get_pixel_format(session->GetFfmpegDecoder());
conversion_config = nullptr; conversion_config = nullptr;
for(auto &cc: conversion_configs) for(auto &cc: conversion_configs)
{ {
if(decoder->PixelFormat() == cc.pixel_format) if(pixel_format == cc.pixel_format)
{ {
conversion_config = &cc; conversion_config = &cc;
break; break;
@ -245,7 +246,7 @@ void AVOpenGLWidget::initializeGL()
auto f = QOpenGLContext::currentContext()->extraFunctions(); auto f = QOpenGLContext::currentContext()->extraFunctions();
const char *gl_version = (const char *)f->glGetString(GL_VERSION); const char *gl_version = (const char *)f->glGetString(GL_VERSION);
CHIAKI_LOGI(decoder->GetChiakiLog(), "OpenGL initialized with version \"%s\"", gl_version ? gl_version : "(null)"); CHIAKI_LOGI(session->GetChiakiLog(), "OpenGL initialized with version \"%s\"", gl_version ? gl_version : "(null)");
#ifdef DEBUG_OPENGL #ifdef DEBUG_OPENGL
auto logger = new QOpenGLDebugLogger(this); auto logger = new QOpenGLDebugLogger(this);
@ -266,7 +267,7 @@ void AVOpenGLWidget::initializeGL()
QVector<GLchar> info_log(info_log_size); QVector<GLchar> info_log(info_log_size);
f->glGetShaderInfoLog(shader, info_log_size, &info_log_size, info_log.data()); f->glGetShaderInfoLog(shader, info_log_size, &info_log_size, info_log.data());
f->glDeleteShader(shader); f->glDeleteShader(shader);
CHIAKI_LOGE(decoder->GetChiakiLog(), "Failed to Compile Shader:\n%s", info_log.data()); CHIAKI_LOGE(session->GetChiakiLog(), "Failed to Compile Shader:\n%s", info_log.data());
}; };
GLuint shader_vert = f->glCreateShader(GL_VERTEX_SHADER); GLuint shader_vert = f->glCreateShader(GL_VERTEX_SHADER);
@ -294,7 +295,7 @@ void AVOpenGLWidget::initializeGL()
QVector<GLchar> info_log(info_log_size); QVector<GLchar> info_log(info_log_size);
f->glGetProgramInfoLog(program, info_log_size, &info_log_size, info_log.data()); f->glGetProgramInfoLog(program, info_log_size, &info_log_size, info_log.data());
f->glDeleteProgram(program); f->glDeleteProgram(program);
CHIAKI_LOGE(decoder->GetChiakiLog(), "Failed to Link Shader Program:\n%s", info_log.data()); CHIAKI_LOGE(session->GetChiakiLog(), "Failed to Link Shader Program:\n%s", info_log.data());
return; return;
} }
@ -346,14 +347,14 @@ void AVOpenGLWidget::initializeGL()
frame_uploader_context->setShareContext(context()); frame_uploader_context->setShareContext(context());
if(!frame_uploader_context->create()) if(!frame_uploader_context->create())
{ {
CHIAKI_LOGE(decoder->GetChiakiLog(), "Failed to create upload OpenGL context"); CHIAKI_LOGE(session->GetChiakiLog(), "Failed to create upload OpenGL context");
return; return;
} }
frame_uploader_surface = new QOffscreenSurface(); frame_uploader_surface = new QOffscreenSurface();
frame_uploader_surface->setFormat(context()->format()); frame_uploader_surface->setFormat(context()->format());
frame_uploader_surface->create(); frame_uploader_surface->create();
frame_uploader = new AVOpenGLFrameUploader(decoder, this, frame_uploader_context, frame_uploader_surface); frame_uploader = new AVOpenGLFrameUploader(session, this, frame_uploader_context, frame_uploader_surface);
frame_fg = 0; frame_fg = 0;
frame_uploader_thread = new QThread(this); frame_uploader_thread = new QThread(this);

View file

@ -4,7 +4,6 @@ int real_main(int argc, char *argv[]);
int main(int argc, char *argv[]) { return real_main(argc, argv); } int main(int argc, char *argv[]) { return real_main(argc, argv); }
#include <streamwindow.h> #include <streamwindow.h>
#include <videodecoder.h>
#include <mainwindow.h> #include <mainwindow.h>
#include <streamsession.h> #include <streamsession.h>
#include <settings.h> #include <settings.h>

View file

@ -37,6 +37,10 @@ static void MigrateSettingsTo2(QSettings *settings)
i++; i++;
} }
settings->endArray(); settings->endArray();
QString hw_decoder = settings->value("settings/hw_decode_engine").toString();
settings->remove("settings/hw_decode_engine");
if(hw_decoder != "none")
settings->setValue("settings/hw_decoder", hw_decoder);
} }
static void MigrateSettings(QSettings *settings) static void MigrateSettings(QSettings *settings)
@ -160,25 +164,14 @@ void Settings::SetDecoder(Decoder decoder)
settings.setValue("settings/decoder", decoder_values[decoder]); settings.setValue("settings/decoder", decoder_values[decoder]);
} }
static const QMap<HardwareDecodeEngine, QString> hw_decode_engine_values = { QString Settings::GetHardwareDecoder() const
{ HW_DECODE_NONE, "none" },
{ HW_DECODE_VAAPI, "vaapi" },
{ HW_DECODE_VDPAU, "vdpau" },
{ HW_DECODE_VIDEOTOOLBOX, "videotoolbox" },
{ HW_DECODE_CUDA, "cuda" }
};
static const HardwareDecodeEngine hw_decode_engine_default = HW_DECODE_NONE;
HardwareDecodeEngine Settings::GetHardwareDecodeEngine() const
{ {
auto v = settings.value("settings/hw_decode_engine", hw_decode_engine_values[hw_decode_engine_default]).toString(); return settings.value("settings/hw_decoder").toString();
return hw_decode_engine_values.key(v, hw_decode_engine_default);
} }
void Settings::SetHardwareDecodeEngine(HardwareDecodeEngine engine) void Settings::SetHardwareDecoder(const QString &hw_decoder)
{ {
settings.setValue("settings/hw_decode_engine", hw_decode_engine_values[engine]); settings.setValue("settings/hw_decoder", hw_decoder);
} }
unsigned int Settings::GetAudioBufferSize() const unsigned int Settings::GetAudioBufferSize() const

View file

@ -5,7 +5,6 @@
#include <settingskeycapturedialog.h> #include <settingskeycapturedialog.h>
#include <registdialog.h> #include <registdialog.h>
#include <sessionlog.h> #include <sessionlog.h>
#include <videodecoder.h>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -23,6 +22,7 @@
#include <QFutureWatcher> #include <QFutureWatcher>
#include <chiaki/config.h> #include <chiaki/config.h>
#include <chiaki/ffmpegdecoder.h>
const char * const about_string = const char * const about_string =
"<h1>Chiaki</h1> by thestr4ng3r, version " CHIAKI_VERSION "<h1>Chiaki</h1> by thestr4ng3r, version " CHIAKI_VERSION
@ -202,22 +202,22 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa
pi_decoder_check_box = nullptr; pi_decoder_check_box = nullptr;
#endif #endif
hardware_decode_combo_box = new QComboBox(this); hw_decoder_combo_box = new QComboBox(this);
static const QList<QPair<HardwareDecodeEngine, const char *>> hardware_decode_engines = { hw_decoder_combo_box->addItem("none", QString());
{ HW_DECODE_NONE, "none"}, auto current_hw_decoder = settings->GetHardwareDecoder();
{ HW_DECODE_VAAPI, "vaapi"}, enum AVHWDeviceType hw_dev = AV_HWDEVICE_TYPE_NONE;
{ HW_DECODE_VIDEOTOOLBOX, "videotoolbox"}, while(true)
{ HW_DECODE_CUDA, "cuda"}
};
auto current_hardware_decode_engine = settings->GetHardwareDecodeEngine();
for(const auto &p : hardware_decode_engines)
{ {
hardware_decode_combo_box->addItem(p.second, (int)p.first); hw_dev = av_hwdevice_iterate_types(hw_dev);
if(current_hardware_decode_engine == p.first) if(hw_dev == AV_HWDEVICE_TYPE_NONE)
hardware_decode_combo_box->setCurrentIndex(hardware_decode_combo_box->count() - 1); break;
QString name = QString::fromUtf8(av_hwdevice_get_type_name(hw_dev));
hw_decoder_combo_box->addItem(name, name);
if(current_hw_decoder == name)
hw_decoder_combo_box->setCurrentIndex(hw_decoder_combo_box->count() - 1);
} }
connect(hardware_decode_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(HardwareDecodeEngineSelected())); connect(hw_decoder_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(HardwareDecodeEngineSelected()));
decode_settings_layout->addRow(tr("Hardware decode method:"), hardware_decode_combo_box); decode_settings_layout->addRow(tr("Hardware decode method:"), hw_decoder_combo_box);
UpdateHardwareDecodeEngineComboBox(); UpdateHardwareDecodeEngineComboBox();
// Registered Consoles // Registered Consoles
@ -329,12 +329,12 @@ void SettingsDialog::AudioOutputSelected()
void SettingsDialog::HardwareDecodeEngineSelected() void SettingsDialog::HardwareDecodeEngineSelected()
{ {
settings->SetHardwareDecodeEngine((HardwareDecodeEngine)hardware_decode_combo_box->currentData().toInt()); settings->SetHardwareDecoder(hw_decoder_combo_box->currentData().toString());
} }
void SettingsDialog::UpdateHardwareDecodeEngineComboBox() void SettingsDialog::UpdateHardwareDecodeEngineComboBox()
{ {
hardware_decode_combo_box->setEnabled(settings->GetDecoder() == Decoder::Ffmpeg); hw_decoder_combo_box->setEnabled(settings->GetDecoder() == Decoder::Ffmpeg);
} }
void SettingsDialog::UpdateBitratePlaceholder() void SettingsDialog::UpdateBitratePlaceholder()

View file

@ -19,7 +19,7 @@ StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTar
{ {
key_map = settings->GetControllerMappingForDecoding(); key_map = settings->GetControllerMappingForDecoding();
decoder = settings->GetDecoder(); decoder = settings->GetDecoder();
hw_decode_engine = settings->GetHardwareDecodeEngine(); hw_decoder = settings->GetHardwareDecoder();
audio_out_device = settings->GetAudioOutDevice(); audio_out_device = settings->GetAudioOutDevice();
log_level_mask = settings->GetLogLevelMask(); log_level_mask = settings->GetLogLevelMask();
log_file = CreateLogFilename(); log_file = CreateLogFilename();
@ -35,16 +35,16 @@ StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTar
static void AudioSettingsCb(uint32_t channels, uint32_t rate, void *user); static void AudioSettingsCb(uint32_t channels, uint32_t rate, void *user);
static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user); static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user);
static bool VideoSampleCb(uint8_t *buf, size_t buf_size, void *user);
static void EventCb(ChiakiEvent *event, void *user); static void EventCb(ChiakiEvent *event, void *user);
#if CHIAKI_GUI_ENABLE_SETSU #if CHIAKI_GUI_ENABLE_SETSU
static void SessionSetsuCb(SetsuEvent *event, void *user); static void SessionSetsuCb(SetsuEvent *event, void *user);
#endif #endif
static void FfmpegFrameCb(ChiakiFfmpegDecoder *decoder, void *user);
StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObject *parent) StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObject *parent)
: QObject(parent), : QObject(parent),
log(this, connect_info.log_level_mask, connect_info.log_file), log(this, connect_info.log_level_mask, connect_info.log_file),
video_decoder(nullptr), ffmpeg_decoder(nullptr),
#if CHIAKI_LIB_ENABLE_PI_DECODER #if CHIAKI_LIB_ENABLE_PI_DECODER
pi_decoder(nullptr), pi_decoder(nullptr),
#endif #endif
@ -63,7 +63,22 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
else else
{ {
#endif #endif
video_decoder = new VideoDecoder(connect_info.hw_decode_engine, log.GetChiakiLog()); ffmpeg_decoder = new ChiakiFfmpegDecoder;
ChiakiLogSniffer sniffer;
chiaki_log_sniffer_init(&sniffer, CHIAKI_LOG_ALL, GetChiakiLog());
ChiakiErrorCode err = chiaki_ffmpeg_decoder_init(ffmpeg_decoder,
chiaki_log_sniffer_get_log(&sniffer),
connect_info.video_profile.codec,
connect_info.hw_decoder.isEmpty() ? NULL : connect_info.hw_decoder.toUtf8().constData(),
FfmpegFrameCb, this);
if(err != CHIAKI_ERR_SUCCESS)
{
QString log = QString::fromUtf8(chiaki_log_sniffer_get_buffer(&sniffer));
chiaki_log_sniffer_fini(&sniffer);
throw ChiakiException("Failed to initialize FFMPEG Decoder:\n" + log);
}
chiaki_log_sniffer_fini(&sniffer);
ffmpeg_decoder->log = GetChiakiLog();
#if CHIAKI_LIB_ENABLE_PI_DECODER #if CHIAKI_LIB_ENABLE_PI_DECODER
} }
#endif #endif
@ -101,7 +116,7 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
chiaki_controller_state_set_idle(&keyboard_state); chiaki_controller_state_set_idle(&keyboard_state);
ChiakiErrorCode err = chiaki_session_init(&session, &chiaki_connect_info, log.GetChiakiLog()); err = chiaki_session_init(&session, &chiaki_connect_info, GetChiakiLog());
if(err != CHIAKI_ERR_SUCCESS) if(err != CHIAKI_ERR_SUCCESS)
throw ChiakiException("Chiaki Session Init failed: " + QString::fromLocal8Bit(chiaki_error_string(err))); throw ChiakiException("Chiaki Session Init failed: " + QString::fromLocal8Bit(chiaki_error_string(err)));
@ -116,7 +131,7 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
else else
{ {
#endif #endif
chiaki_session_set_video_sample_cb(&session, VideoSampleCb, this); chiaki_session_set_video_sample_cb(&session, chiaki_ffmpeg_decoder_video_sample_cb, ffmpeg_decoder);
#if CHIAKI_LIB_ENABLE_PI_DECODER #if CHIAKI_LIB_ENABLE_PI_DECODER
} }
#endif #endif
@ -160,7 +175,11 @@ StreamSession::~StreamSession()
free(pi_decoder); free(pi_decoder);
} }
#endif #endif
delete video_decoder; if(ffmpeg_decoder)
{
chiaki_ffmpeg_decoder_fini(ffmpeg_decoder);
delete ffmpeg_decoder;
}
} }
void StreamSession::Start() void StreamSession::Start()
@ -346,11 +365,6 @@ void StreamSession::PushAudioFrame(int16_t *buf, size_t samples_count)
audio_io->write((const char *)buf, static_cast<qint64>(samples_count * 2 * 2)); audio_io->write((const char *)buf, static_cast<qint64>(samples_count * 2 * 2));
} }
void StreamSession::PushVideoSample(uint8_t *buf, size_t buf_size)
{
video_decoder->PushFrame(buf, buf_size);
}
void StreamSession::Event(ChiakiEvent *event) void StreamSession::Event(ChiakiEvent *event)
{ {
switch(event->type) switch(event->type)
@ -431,6 +445,11 @@ void StreamSession::HandleSetsuEvent(SetsuEvent *event)
} }
#endif #endif
void StreamSession::TriggerFfmpegFrameAvailable()
{
emit FfmpegFrameAvailable();
}
class StreamSessionPrivate class StreamSessionPrivate
{ {
public: public:
@ -440,11 +459,11 @@ class StreamSessionPrivate
} }
static void PushAudioFrame(StreamSession *session, int16_t *buf, size_t samples_count) { session->PushAudioFrame(buf, samples_count); } static void PushAudioFrame(StreamSession *session, int16_t *buf, size_t samples_count) { session->PushAudioFrame(buf, samples_count); }
static void PushVideoSample(StreamSession *session, uint8_t *buf, size_t buf_size) { session->PushVideoSample(buf, buf_size); }
static void Event(StreamSession *session, ChiakiEvent *event) { session->Event(event); } static void Event(StreamSession *session, ChiakiEvent *event) { session->Event(event); }
#if CHIAKI_GUI_ENABLE_SETSU #if CHIAKI_GUI_ENABLE_SETSU
static void HandleSetsuEvent(StreamSession *session, SetsuEvent *event) { session->HandleSetsuEvent(event); } static void HandleSetsuEvent(StreamSession *session, SetsuEvent *event) { session->HandleSetsuEvent(event); }
#endif #endif
static void TriggerFfmpegFrameAvailable(StreamSession *session) { session->TriggerFfmpegFrameAvailable(); }
}; };
static void AudioSettingsCb(uint32_t channels, uint32_t rate, void *user) static void AudioSettingsCb(uint32_t channels, uint32_t rate, void *user)
@ -459,13 +478,6 @@ static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user)
StreamSessionPrivate::PushAudioFrame(session, buf, samples_count); StreamSessionPrivate::PushAudioFrame(session, buf, samples_count);
} }
static bool VideoSampleCb(uint8_t *buf, size_t buf_size, void *user)
{
auto session = reinterpret_cast<StreamSession *>(user);
StreamSessionPrivate::PushVideoSample(session, buf, buf_size);
return true;
}
static void EventCb(ChiakiEvent *event, void *user) static void EventCb(ChiakiEvent *event, void *user)
{ {
auto session = reinterpret_cast<StreamSession *>(user); auto session = reinterpret_cast<StreamSession *>(user);
@ -479,3 +491,9 @@ static void SessionSetsuCb(SetsuEvent *event, void *user)
StreamSessionPrivate::HandleSetsuEvent(session, event); StreamSessionPrivate::HandleSetsuEvent(session, event);
} }
#endif #endif
static void FfmpegFrameCb(ChiakiFfmpegDecoder *decoder, void *user)
{
auto session = reinterpret_cast<StreamSession *>(user);
StreamSessionPrivate::TriggerFfmpegFrameAvailable(session);
}

View file

@ -47,9 +47,9 @@ void StreamWindow::Init()
connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit); connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit);
connect(session, &StreamSession::LoginPINRequested, this, &StreamWindow::LoginPINRequested); connect(session, &StreamSession::LoginPINRequested, this, &StreamWindow::LoginPINRequested);
if(session->GetVideoDecoder()) if(session->GetFfmpegDecoder())
{ {
av_widget = new AVOpenGLWidget(session->GetVideoDecoder(), this); av_widget = new AVOpenGLWidget(session, this);
setCentralWidget(av_widget); setCentralWidget(av_widget);
} }
else else

View file

@ -1,177 +0,0 @@
// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include <videodecoder.h>
#include <libavcodec/avcodec.h>
#include <QImage>
VideoDecoder::VideoDecoder(HardwareDecodeEngine hw_decode_engine, ChiakiLog *log) : hw_decode_engine(hw_decode_engine), log(log)
{
enum AVHWDeviceType type;
hw_device_ctx = nullptr;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
avcodec_register_all();
#endif
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if(!codec)
throw VideoDecoderException("H264 Codec not available");
codec_context = avcodec_alloc_context3(codec);
if(!codec_context)
throw VideoDecoderException("Failed to alloc codec context");
if(hw_decode_engine)
{
if(!hardware_decode_engine_names.contains(hw_decode_engine))
throw VideoDecoderException("Unknown hardware decode engine!");
const char *hw_dec_eng = hardware_decode_engine_names[hw_decode_engine];
CHIAKI_LOGI(log, "Using hardware decode %s", hw_dec_eng);
type = av_hwdevice_find_type_by_name(hw_dec_eng);
if (type == AV_HWDEVICE_TYPE_NONE)
throw VideoDecoderException("Can't initialize vaapi");
for(int i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(codec, i);
if(!config)
throw VideoDecoderException("avcodec_get_hw_config failed");
if(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
config->device_type == type)
{
hw_pix_fmt = config->pix_fmt;
break;
}
}
if(av_hwdevice_ctx_create(&hw_device_ctx, type, NULL, NULL, 0) < 0)
throw VideoDecoderException("Failed to create hwdevice context");
codec_context->hw_device_ctx = av_buffer_ref(hw_device_ctx);
}
if(avcodec_open2(codec_context, codec, nullptr) < 0)
{
avcodec_free_context(&codec_context);
throw VideoDecoderException("Failed to open codec context");
}
}
VideoDecoder::~VideoDecoder()
{
avcodec_close(codec_context);
avcodec_free_context(&codec_context);
if(hw_device_ctx)
{
av_buffer_unref(&hw_device_ctx);
}
}
void VideoDecoder::PushFrame(uint8_t *buf, size_t buf_size)
{
{
QMutexLocker locker(&mutex);
AVPacket packet;
av_init_packet(&packet);
packet.data = buf;
packet.size = buf_size;
int r;
send_packet:
r = avcodec_send_packet(codec_context, &packet);
if(r != 0)
{
if(r == AVERROR(EAGAIN))
{
CHIAKI_LOGE(log, "AVCodec internal buffer is full removing frames before pushing");
AVFrame *frame = av_frame_alloc();
if(!frame)
{
CHIAKI_LOGE(log, "Failed to alloc AVFrame");
return;
}
r = avcodec_receive_frame(codec_context, frame);
av_frame_free(&frame);
if(r != 0)
{
CHIAKI_LOGE(log, "Failed to pull frame");
return;
}
goto send_packet;
}
else
{
char errbuf[128];
av_make_error_string(errbuf, sizeof(errbuf), r);
CHIAKI_LOGE(log, "Failed to push frame: %s", errbuf);
return;
}
}
}
emit FramesAvailable();
}
AVFrame *VideoDecoder::PullFrame()
{
QMutexLocker locker(&mutex);
// always try to pull as much as possible and return only the very last frame
AVFrame *frame_last = nullptr;
AVFrame *sw_frame = nullptr;
AVFrame *frame = nullptr;
while(true)
{
AVFrame *next_frame;
if(frame_last)
{
av_frame_unref(frame_last);
next_frame = frame_last;
}
else
{
next_frame = av_frame_alloc();
if(!next_frame)
return frame;
}
frame_last = frame;
frame = next_frame;
int r = avcodec_receive_frame(codec_context, frame);
if(r == 0)
{
frame = hw_decode_engine ? GetFromHardware(frame) : frame;
}
else
{
if(r != AVERROR(EAGAIN))
CHIAKI_LOGE(log, "Decoding with FFMPEG failed");
av_frame_free(&frame);
return frame_last;
}
}
}
AVFrame *VideoDecoder::GetFromHardware(AVFrame *hw_frame)
{
AVFrame *frame;
AVFrame *sw_frame;
sw_frame = av_frame_alloc();
int ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
if(ret < 0)
{
CHIAKI_LOGE(log, "Failed to transfer frame from hardware");
}
av_frame_unref(hw_frame);
if(sw_frame->width <= 0)
{
av_frame_unref(sw_frame);
return nullptr;
}
return sw_frame;
}

View file

@ -75,6 +75,12 @@ set(SOURCE_FILES
src/regist.c src/regist.c
src/opusdecoder.c) src/opusdecoder.c)
if(CHIAKI_ENABLE_FFMPEG_DECODER)
list(APPEND HEADER_FILES include/chiaki/ffmpegdecoder.h)
list(APPEND SOURCE_FILES src/ffmpegdecoder.c)
endif()
set(CHIAKI_LIB_ENABLE_PI_DECODER "${CHIAKI_ENABLE_FFMPEG_DECODER}")
if(CHIAKI_ENABLE_PI_DECODER) if(CHIAKI_ENABLE_PI_DECODER)
list(APPEND HEADER_FILES include/chiaki/pidecoder.h) list(APPEND HEADER_FILES include/chiaki/pidecoder.h)
list(APPEND SOURCE_FILES src/pidecoder.c) list(APPEND SOURCE_FILES src/pidecoder.c)
@ -127,6 +133,10 @@ endif()
target_link_libraries(chiaki-lib Nanopb::nanopb) target_link_libraries(chiaki-lib Nanopb::nanopb)
target_link_libraries(chiaki-lib Jerasure::Jerasure) target_link_libraries(chiaki-lib Jerasure::Jerasure)
if(CHIAKI_ENABLE_FFMPEG_DECODER)
target_link_libraries(chiaki-lib FFMPEG::avcodec FFMPEG::avutil)
endif()
if(CHIAKI_ENABLE_PI_DECODER) if(CHIAKI_ENABLE_PI_DECODER)
target_link_libraries(chiaki-lib ILClient::ILClient) target_link_libraries(chiaki-lib ILClient::ILClient)
endif() endif()

View file

@ -100,6 +100,8 @@ static inline bool chiaki_codec_is_hdr(ChiakiCodec codec)
return codec == CHIAKI_CODEC_H265_HDR; return codec == CHIAKI_CODEC_H265_HDR;
} }
CHIAKI_EXPORT const char *chiaki_codec_name(ChiakiCodec codec);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -0,0 +1,43 @@
#ifndef CHIAKI_FFMPEG_DECODER_H
#define CHIAKI_FFMPEG_DECODER_H
#include <chiaki/config.h>
#include <chiaki/log.h>
#include <chiaki/thread.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
typedef struct chiaki_ffmpeg_decoder_t ChiakiFfmpegDecoder;
typedef void (*ChiakiFfmpegFrameAvailable)(ChiakiFfmpegDecoder *decover, void *user);
struct chiaki_ffmpeg_decoder_t
{
ChiakiLog *log;
ChiakiMutex mutex;
AVCodec *av_codec;
AVCodecContext *codec_context;
enum AVPixelFormat hw_pix_fmt;
AVBufferRef *hw_device_ctx;
ChiakiMutex cb_mutex;
ChiakiFfmpegFrameAvailable frame_available_cb;
void *frame_available_cb_user;
};
CHIAKI_EXPORT ChiakiErrorCode chiaki_ffmpeg_decoder_init(ChiakiFfmpegDecoder *decoder, ChiakiLog *log,
ChiakiCodec codec, const char *hw_decoder_name,
ChiakiFfmpegFrameAvailable frame_available_cb, void *frame_available_cb_user);
CHIAKI_EXPORT void chiaki_ffmpeg_decoder_fini(ChiakiFfmpegDecoder *decoder);
CHIAKI_EXPORT bool chiaki_ffmpeg_decoder_video_sample_cb(uint8_t *buf, size_t buf_size, void *user);
CHIAKI_EXPORT AVFrame *chiaki_ffmpeg_decoder_pull_frame(ChiakiFfmpegDecoder *decoder);
CHIAKI_EXPORT enum AVPixelFormat chiaki_ffmpeg_decoder_get_pixel_format(ChiakiFfmpegDecoder *decoder);
#ifdef __cplusplus
}
#endif
#endif // CHIAKI_FFMPEG_DECODER_H

View file

@ -50,6 +50,20 @@ CHIAKI_EXPORT void chiaki_log_hexdump_raw(ChiakiLog *log, ChiakiLogLevel level,
#define CHIAKI_LOGW(log, ...) do { chiaki_log((log), CHIAKI_LOG_WARNING, __VA_ARGS__); } while(0) #define CHIAKI_LOGW(log, ...) do { chiaki_log((log), CHIAKI_LOG_WARNING, __VA_ARGS__); } while(0)
#define CHIAKI_LOGE(log, ...) do { chiaki_log((log), CHIAKI_LOG_ERROR, __VA_ARGS__); } while(0) #define CHIAKI_LOGE(log, ...) do { chiaki_log((log), CHIAKI_LOG_ERROR, __VA_ARGS__); } while(0)
typedef struct chiaki_log_sniffer_t
{
ChiakiLog *forward_log; // The original log, where everything is forwarded
ChiakiLog sniff_log; // The log where others will log into
uint32_t sniff_level_mask;
char *buf; // always null-terminated
size_t buf_len; // strlen(buf)
} ChiakiLogSniffer;
CHIAKI_EXPORT void chiaki_log_sniffer_init(ChiakiLogSniffer *sniffer, uint32_t level_mask, ChiakiLog *forward_log);
CHIAKI_EXPORT void chiaki_log_sniffer_fini(ChiakiLogSniffer *sniffer);
static inline ChiakiLog *chiaki_log_sniffer_get_log(ChiakiLogSniffer *sniffer) { return &sniffer->sniff_log; }
static inline const char *chiaki_log_sniffer_get_buffer(ChiakiLogSniffer *sniffer) { return sniffer->buf; }
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -105,3 +105,18 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_lib_init()
return CHIAKI_ERR_SUCCESS; return CHIAKI_ERR_SUCCESS;
} }
CHIAKI_EXPORT const char *chiaki_codec_name(ChiakiCodec codec)
{
switch(codec)
{
case CHIAKI_CODEC_H264:
return "H264";
case CHIAKI_CODEC_H265:
return "H265";
case CHIAKI_CODEC_H265_HDR:
return "H265/HDR";
default:
return "unknown";
}
}

216
lib/src/ffmpegdecoder.c Normal file
View file

@ -0,0 +1,216 @@
#include <chiaki/ffmpegdecoder.h>
#include <libavcodec/avcodec.h>
static enum AVCodecID chiaki_codec_av_codec_id(ChiakiCodec codec)
{
switch(codec)
{
case CHIAKI_CODEC_H265:
case CHIAKI_CODEC_H265_HDR:
return AV_CODEC_ID_H265;
default:
return AV_CODEC_ID_H264;
}
}
CHIAKI_EXPORT ChiakiErrorCode chiaki_ffmpeg_decoder_init(ChiakiFfmpegDecoder *decoder, ChiakiLog *log,
ChiakiCodec codec, const char *hw_decoder_name,
ChiakiFfmpegFrameAvailable frame_available_cb, void *frame_available_cb_user)
{
decoder->log = log;
decoder->frame_available_cb = frame_available_cb;
decoder->frame_available_cb_user = frame_available_cb_user;
ChiakiErrorCode err = chiaki_mutex_init(&decoder->mutex, false);
if(err != CHIAKI_ERR_SUCCESS)
return err;
decoder->hw_device_ctx = NULL;
decoder->hw_pix_fmt = AV_PIX_FMT_NONE;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
avcodec_register_all();
#endif
enum AVCodecID av_codec = chiaki_codec_av_codec_id(codec);
decoder->av_codec = avcodec_find_decoder(av_codec);
if(!decoder->av_codec)
{
CHIAKI_LOGE(log, "%s Codec not available", chiaki_codec_name(codec));
goto error_mutex;
}
decoder->codec_context = avcodec_alloc_context3(decoder->av_codec);
if(!decoder->codec_context)
{
CHIAKI_LOGE(log, "Failed to alloc codec context");
goto error_mutex;
}
if(hw_decoder_name)
{
CHIAKI_LOGI(log, "Using hardware decoder \"%s\"", hw_decoder_name);
enum AVHWDeviceType type = av_hwdevice_find_type_by_name(hw_decoder_name);
if(type == AV_HWDEVICE_TYPE_NONE)
{
CHIAKI_LOGE(log, "Hardware decoder \"%s\" not found", hw_decoder_name);
goto error_codec_context;
}
for(int i = 0;; i++)
{
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder->av_codec, i);
if(!config)
{
CHIAKI_LOGE(log, "avcodec_get_hw_config failed");
goto error_codec_context;
}
if(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == type)
{
decoder->hw_pix_fmt = config->pix_fmt;
break;
}
}
if(av_hwdevice_ctx_create(&decoder->hw_device_ctx, type, NULL, NULL, 0) < 0)
{
CHIAKI_LOGE(log, "Failed to create hwdevice context");
goto error_codec_context;
}
decoder->codec_context->hw_device_ctx = av_buffer_ref(decoder->hw_device_ctx);
}
if(avcodec_open2(decoder->codec_context, decoder->av_codec, NULL) < 0)
{
CHIAKI_LOGE(log, "Failed to open codec context");
goto error_codec_context;
}
return CHIAKI_ERR_SUCCESS;
error_codec_context:
if(decoder->hw_device_ctx)
av_buffer_unref(&decoder->hw_device_ctx);
avcodec_free_context(&decoder->codec_context);
error_mutex:
chiaki_mutex_fini(&decoder->mutex);
return CHIAKI_ERR_UNKNOWN;
}
CHIAKI_EXPORT void chiaki_ffmpeg_decoder_fini(ChiakiFfmpegDecoder *decoder)
{
avcodec_close(decoder->codec_context);
avcodec_free_context(&decoder->codec_context);
if(decoder->hw_device_ctx)
av_buffer_unref(&decoder->hw_device_ctx);
}
CHIAKI_EXPORT bool chiaki_ffmpeg_decoder_video_sample_cb(uint8_t *buf, size_t buf_size, void *user)
{
ChiakiFfmpegDecoder *decoder = user;
chiaki_mutex_lock(&decoder->mutex);
AVPacket packet;
av_init_packet(&packet);
packet.data = buf;
packet.size = buf_size;
int r;
send_packet:
r = avcodec_send_packet(decoder->codec_context, &packet);
if(r != 0)
{
if(r == AVERROR(EAGAIN))
{
CHIAKI_LOGE(decoder->log, "AVCodec internal buffer is full removing frames before pushing");
AVFrame *frame = av_frame_alloc();
if(!frame)
{
CHIAKI_LOGE(decoder->log, "Failed to alloc AVFrame");
goto hell;
}
r = avcodec_receive_frame(decoder->codec_context, frame);
av_frame_free(&frame);
if(r != 0)
{
CHIAKI_LOGE(decoder->log, "Failed to pull frame");
goto hell;
}
goto send_packet;
}
else
{
char errbuf[128];
av_make_error_string(errbuf, sizeof(errbuf), r);
CHIAKI_LOGE(decoder->log, "Failed to push frame: %s", errbuf);
goto hell;
}
}
chiaki_mutex_unlock(&decoder->mutex);
decoder->frame_available_cb(decoder, decoder->frame_available_cb_user);
return true;
hell:
chiaki_mutex_unlock(&decoder->mutex);
return false;
}
static AVFrame *pull_from_hw(ChiakiFfmpegDecoder *decoder, AVFrame *hw_frame)
{
AVFrame *sw_frame = av_frame_alloc();
if(av_hwframe_transfer_data(sw_frame, hw_frame, 0) < 0)
{
CHIAKI_LOGE(decoder->log, "Failed to transfer frame from hardware");
av_frame_unref(sw_frame);
sw_frame = NULL;
}
av_frame_unref(hw_frame);
return sw_frame;
}
CHIAKI_EXPORT AVFrame *chiaki_ffmpeg_decoder_pull_frame(ChiakiFfmpegDecoder *decoder)
{
chiaki_mutex_lock(&decoder->mutex);
// always try to pull as much as possible and return only the very last frame
AVFrame *frame_last = NULL;
AVFrame *frame = NULL;
while(true)
{
AVFrame *next_frame;
if(frame_last)
{
av_frame_unref(frame_last);
next_frame = frame_last;
}
else
{
next_frame = av_frame_alloc();
if(!next_frame)
break;
}
frame_last = frame;
frame = next_frame;
int r = avcodec_receive_frame(decoder->codec_context, frame);
if(!r)
frame = decoder->hw_device_ctx ? pull_from_hw(decoder, frame) : frame;
else
{
if(r != AVERROR(EAGAIN))
CHIAKI_LOGE(decoder->log, "Decoding with FFMPEG failed");
av_frame_free(&frame);
frame = frame_last;
break;
}
}
chiaki_mutex_unlock(&decoder->mutex);
return frame;
}
CHIAKI_EXPORT enum AVPixelFormat chiaki_ffmpeg_decoder_get_pixel_format(ChiakiFfmpegDecoder *decoder)
{
// TODO: this is probably very wrong, especially for hdr
return decoder->hw_device_ctx
? AV_PIX_FMT_NV12
: AV_PIX_FMT_YUV420P;
}

View file

@ -4,6 +4,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <string.h>
CHIAKI_EXPORT char chiaki_log_level_char(ChiakiLogLevel level) CHIAKI_EXPORT char chiaki_log_level_char(ChiakiLogLevel level)
{ {
@ -177,3 +178,50 @@ CHIAKI_EXPORT void chiaki_log_hexdump_raw(ChiakiLog *log, ChiakiLogLevel level,
chiaki_log(log, level, "%s", str); chiaki_log(log, level, "%s", str);
free(str); free(str);
} }
static void log_sniffer_cb(ChiakiLogLevel level, const char *msg, void *user);
CHIAKI_EXPORT void chiaki_log_sniffer_init(ChiakiLogSniffer *sniffer, uint32_t level_mask, ChiakiLog *forward_log)
{
sniffer->forward_log = forward_log;
// level_mask is applied later and everything is forwarded unmasked, so use ALL here:
chiaki_log_init(&sniffer->sniff_log, CHIAKI_LOG_ALL, log_sniffer_cb, sniffer);
sniffer->sniff_level_mask = level_mask;
sniffer->buf = calloc(1, 1);
sniffer->buf_len = '\0';
}
CHIAKI_EXPORT void chiaki_log_sniffer_fini(ChiakiLogSniffer *sniffer)
{
free(sniffer->buf);
}
static void log_sniffer_push(ChiakiLogSniffer *sniffer, ChiakiLogLevel level, const char *msg)
{
size_t len = strlen(msg);
if(!len)
return;
bool nl = sniffer->buf_len != 0;
char *new_buf = realloc(sniffer->buf, sniffer->buf_len + (nl ? 1 : 0) + 4 + len + 1);
if(!new_buf)
return;
sniffer->buf = new_buf;
if(nl)
sniffer->buf[sniffer->buf_len++] = '\n';
sniffer->buf[sniffer->buf_len++] = '[';
sniffer->buf[sniffer->buf_len++] = chiaki_log_level_char(level);
sniffer->buf[sniffer->buf_len++] = ']';
sniffer->buf[sniffer->buf_len++] = ' ';
memcpy(sniffer->buf + sniffer->buf_len, msg, len);
sniffer->buf_len += len;
sniffer->buf[sniffer->buf_len] = '\0';
}
static void log_sniffer_cb(ChiakiLogLevel level, const char *msg, void *user)
{
ChiakiLogSniffer *sniffer = user;
if(level & sniffer->sniff_level_mask)
log_sniffer_push(sniffer, level, msg);
if(sniffer->forward_log)
chiaki_log(sniffer->forward_log, level, "%s", msg);
}