1416 lines
49 KiB
C++
1416 lines
49 KiB
C++
/*
|
|
* Copyright (c) 2022 Taner Sener
|
|
*
|
|
* This file is part of FFmpegKit.
|
|
*
|
|
* FFmpegKit is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* FFmpegKit 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <pthread.h>
|
|
extern "C" {
|
|
#include "libavutil/ffversion.h"
|
|
#include "libavutil/bprint.h"
|
|
#include "fftools_ffmpeg.h"
|
|
}
|
|
#include "ArchDetect.h"
|
|
#include "FFmpegKit.h"
|
|
#include "FFmpegKitConfig.h"
|
|
#include "FFmpegSession.h"
|
|
#include "FFprobeKit.h"
|
|
#include "FFprobeSession.h"
|
|
#include "Level.h"
|
|
#include "LogRedirectionStrategy.h"
|
|
#include "MediaInformationSession.h"
|
|
#include "Packages.h"
|
|
#include "SessionState.h"
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <future>
|
|
#include <condition_variable>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
|
|
/**
|
|
* Generates ids for named ffmpeg kit pipes.
|
|
*/
|
|
static std::atomic<long> pipeIndexGenerator(1);
|
|
|
|
/* Session history variables */
|
|
static int sessionHistorySize;
|
|
static std::map<long, std::shared_ptr<ffmpegkit::Session>> sessionHistoryMap;
|
|
static std::list<std::shared_ptr<ffmpegkit::Session>> sessionHistoryList;
|
|
static std::recursive_mutex sessionMutex;
|
|
|
|
/** Session control variables */
|
|
#define SESSION_MAP_SIZE 1000
|
|
static std::atomic<short> sessionMap[SESSION_MAP_SIZE];
|
|
static std::atomic<int> sessionInTransitMessageCountMap[SESSION_MAP_SIZE];
|
|
|
|
/** Holds callback defined to redirect logs */
|
|
static ffmpegkit::LogCallback logCallback;
|
|
|
|
/** Holds callback defined to redirect statistics */
|
|
static ffmpegkit::StatisticsCallback statisticsCallback;
|
|
|
|
/** Holds complete callbacks defined to redirect asynchronous execution results */
|
|
static ffmpegkit::FFmpegSessionCompleteCallback ffmpegSessionCompleteCallback;
|
|
static ffmpegkit::FFprobeSessionCompleteCallback ffprobeSessionCompleteCallback;
|
|
static ffmpegkit::MediaInformationSessionCompleteCallback mediaInformationSessionCompleteCallback;
|
|
|
|
static ffmpegkit::LogRedirectionStrategy globalLogRedirectionStrategy;
|
|
|
|
/** Redirection control variables */
|
|
static int redirectionEnabled;
|
|
static std::recursive_mutex callbackDataMutex;
|
|
static std::mutex callbackMutex;
|
|
static std::condition_variable callbackMonitor;
|
|
class CallbackData;
|
|
static std::list<CallbackData*> callbackDataList;
|
|
|
|
/** Fields that control the handling of SIGNALs */
|
|
volatile int handleSIGQUIT = 1;
|
|
volatile int handleSIGINT = 1;
|
|
volatile int handleSIGTERM = 1;
|
|
volatile int handleSIGXCPU = 1;
|
|
volatile int handleSIGPIPE = 1;
|
|
|
|
/** Holds the id of the current execution */
|
|
__thread volatile long globalSessionId = 0;
|
|
|
|
/** Holds the default log level */
|
|
int configuredLogLevel = ffmpegkit::LevelAVLogInfo;
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/** Forward declaration for function defined in fftools_ffmpeg.c */
|
|
int ffmpeg_execute(int argc, char **argv);
|
|
|
|
/** Forward declaration for function defined in fftools_ffprobe.c */
|
|
int ffprobe_execute(int argc, char **argv);
|
|
|
|
void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, va_list vargs);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
static std::once_flag ffmpegKitInitializerFlag;
|
|
static pthread_t callbackThread;
|
|
|
|
void* ffmpegKitInitialize();
|
|
|
|
const void* _ffmpegKitConfigInitializer{ffmpegKitInitialize()};
|
|
|
|
enum CallbackType {
|
|
LogType,
|
|
StatisticsType
|
|
};
|
|
|
|
static bool fs_exists(const std::string& s, const bool isFile, const bool isDirectory) {
|
|
struct stat dir_info;
|
|
|
|
if (stat(s.c_str(), &dir_info) == 0) {
|
|
if (isFile && S_ISREG(dir_info.st_mode)) {
|
|
return true;
|
|
}
|
|
if (isDirectory && S_ISDIR(dir_info.st_mode)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool fs_create_dir(const std::string& s) {
|
|
if (!fs_exists(s, false, true)) {
|
|
if (mkdir(s.c_str(), S_IRWXU | S_IRWXG | S_IROTH) != 0) {
|
|
std::cout << "Failed to create directory: " << s << ". Operation failed with " << errno << "." << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void addSessionToSessionHistory(const std::shared_ptr<ffmpegkit::Session> session) {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
|
|
const long sessionId = session->getSessionId();
|
|
|
|
lock.lock();
|
|
|
|
/*
|
|
* ASYNC SESSIONS CALL THIS METHOD TWICE
|
|
* THIS CHECK PREVENTS ADDING THE SAME SESSION TWICE
|
|
*/
|
|
if (sessionHistoryMap.count(sessionId) == 0) {
|
|
sessionHistoryMap.insert({sessionId, session});
|
|
sessionHistoryList.push_back(session);
|
|
if (sessionHistoryList.size() > sessionHistorySize) {
|
|
auto first = sessionHistoryList.front();
|
|
if (first != nullptr) {
|
|
auto expiredSessionId = first->getSessionId();
|
|
sessionHistoryList.pop_front();
|
|
sessionHistoryMap.erase(expiredSessionId);
|
|
}
|
|
}
|
|
}
|
|
|
|
lock.unlock();
|
|
}
|
|
|
|
/**
|
|
* Callback data class.
|
|
*/
|
|
class CallbackData {
|
|
public:
|
|
CallbackData(const long sessionId, const int logLevel, const AVBPrint* data) :
|
|
_type{LogType}, _sessionId{sessionId}, _logLevel{logLevel} {
|
|
av_bprint_init(&_logData, 0, AV_BPRINT_SIZE_UNLIMITED);
|
|
av_bprintf(&_logData, "%s", data->str);
|
|
}
|
|
|
|
CallbackData(const long sessionId,
|
|
const int videoFrameNumber,
|
|
const float videoFps,
|
|
const float videoQuality,
|
|
const int64_t size,
|
|
const int time,
|
|
const double bitrate,
|
|
const double speed) :
|
|
_type{StatisticsType},
|
|
_sessionId{sessionId},
|
|
_statisticsFrameNumber{videoFrameNumber},
|
|
_statisticsFps{videoFps},
|
|
_statisticsQuality{videoQuality},
|
|
_statisticsSize{size},
|
|
_statisticsTime{time},
|
|
_statisticsBitrate{bitrate},
|
|
_statisticsSpeed{speed} {
|
|
}
|
|
|
|
CallbackType getType() {
|
|
return _type;
|
|
}
|
|
|
|
long getSessionId() {
|
|
return _sessionId;
|
|
}
|
|
|
|
int getLogLevel() {
|
|
return _logLevel;
|
|
}
|
|
|
|
AVBPrint* getLogData() {
|
|
return &_logData;
|
|
}
|
|
|
|
int getStatisticsFrameNumber() {
|
|
return _statisticsFrameNumber;
|
|
}
|
|
|
|
float getStatisticsFps() {
|
|
return _statisticsFps;
|
|
}
|
|
|
|
float getStatisticsQuality() {
|
|
return _statisticsQuality;
|
|
}
|
|
|
|
int64_t getStatisticsSize() {
|
|
return _statisticsSize;
|
|
}
|
|
|
|
int getStatisticsTime() {
|
|
return _statisticsTime;
|
|
}
|
|
|
|
double getStatisticsBitrate() {
|
|
return _statisticsBitrate;
|
|
}
|
|
|
|
double getStatisticsSpeed() {
|
|
return _statisticsSpeed;
|
|
}
|
|
|
|
private:
|
|
CallbackType _type;
|
|
long _sessionId; // session id
|
|
|
|
int _logLevel; // log level
|
|
AVBPrint _logData; // log data
|
|
|
|
int _statisticsFrameNumber; // statistics frame number
|
|
float _statisticsFps; // statistics fps
|
|
float _statisticsQuality; // statistics quality
|
|
int64_t _statisticsSize; // statistics size
|
|
int _statisticsTime; // statistics time
|
|
double _statisticsBitrate; // statistics bitrate
|
|
double _statisticsSpeed; // statistics speed
|
|
};
|
|
|
|
/**
|
|
* Waits on the callback semaphore for the given time.
|
|
*
|
|
* @param milliSeconds wait time in milliseconds
|
|
*/
|
|
static void callbackWait(int milliSeconds) {
|
|
std::unique_lock<std::mutex> callbackLock{callbackMutex};
|
|
callbackMonitor.wait_for(callbackLock, std::chrono::milliseconds(milliSeconds));
|
|
}
|
|
|
|
/**
|
|
* Notifies threads waiting on callback semaphore.
|
|
*/
|
|
static void callbackNotify() {
|
|
callbackMonitor.notify_one();
|
|
}
|
|
|
|
static const char *avutil_log_get_level_str(int level) {
|
|
switch (level) {
|
|
case AV_LOG_STDERR:
|
|
return "stderr";
|
|
case AV_LOG_QUIET:
|
|
return "quiet";
|
|
case AV_LOG_DEBUG:
|
|
return "debug";
|
|
case AV_LOG_VERBOSE:
|
|
return "verbose";
|
|
case AV_LOG_INFO:
|
|
return "info";
|
|
case AV_LOG_WARNING:
|
|
return "warning";
|
|
case AV_LOG_ERROR:
|
|
return "error";
|
|
case AV_LOG_FATAL:
|
|
return "fatal";
|
|
case AV_LOG_PANIC:
|
|
return "panic";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
static void avutil_log_format_line(void *avcl, int level, const char *fmt, va_list vl, AVBPrint part[4], int *print_prefix) {
|
|
int flags = av_log_get_flags();
|
|
AVClass* avc = avcl ? *(AVClass **) avcl : NULL;
|
|
av_bprint_init(part+0, 0, 1);
|
|
av_bprint_init(part+1, 0, 1);
|
|
av_bprint_init(part+2, 0, 1);
|
|
av_bprint_init(part+3, 0, 65536);
|
|
|
|
if (*print_prefix && avc) {
|
|
if (avc->parent_log_context_offset) {
|
|
AVClass** parent = *(AVClass ***) (((uint8_t *) avcl) +
|
|
avc->parent_log_context_offset);
|
|
if (parent && *parent) {
|
|
av_bprintf(part+0, "[%s @ %p] ",
|
|
(*parent)->item_name(parent), parent);
|
|
}
|
|
}
|
|
av_bprintf(part+1, "[%s @ %p] ",
|
|
avc->item_name(avcl), avcl);
|
|
}
|
|
|
|
if (*print_prefix && (level > AV_LOG_QUIET) && (flags & AV_LOG_PRINT_LEVEL))
|
|
av_bprintf(part+2, "[%s] ", avutil_log_get_level_str(level));
|
|
|
|
av_vbprintf(part+3, fmt, vl);
|
|
|
|
if(*part[0].str || *part[1].str || *part[2].str || *part[3].str) {
|
|
char lastc = part[3].len && part[3].len <= part[3].size ? part[3].str[part[3].len - 1] : 0;
|
|
*print_prefix = lastc == '\n' || lastc == '\r';
|
|
}
|
|
}
|
|
|
|
static void avutil_log_sanitize(char *line) {
|
|
while(*line){
|
|
if(*line < 0x08 || (*line > 0x0D && *line < 0x20))
|
|
*line='?';
|
|
line++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds log data to the end of callback data list.
|
|
*
|
|
* @param level log level
|
|
* @param data log data
|
|
*/
|
|
static void logCallbackDataAdd(int level, AVBPrint *data) {
|
|
std::unique_lock<std::recursive_mutex> lock(callbackDataMutex, std::defer_lock);
|
|
CallbackData* callbackData = new CallbackData(globalSessionId, level, data);
|
|
|
|
lock.lock();
|
|
callbackDataList.push_back(callbackData);
|
|
lock.unlock();
|
|
|
|
callbackNotify();
|
|
|
|
std::atomic_fetch_add(&sessionInTransitMessageCountMap[globalSessionId % SESSION_MAP_SIZE], 1);
|
|
}
|
|
|
|
/**
|
|
* Adds statistics data to the end of callback data list.
|
|
*/
|
|
static void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
|
|
std::unique_lock<std::recursive_mutex> lock(callbackDataMutex, std::defer_lock);
|
|
CallbackData* callbackData = new CallbackData(globalSessionId, frameNumber, fps, quality, size, time, bitrate, speed);
|
|
|
|
lock.lock();
|
|
callbackDataList.push_back(callbackData);
|
|
lock.unlock();
|
|
|
|
callbackNotify();
|
|
|
|
std::atomic_fetch_add(&sessionInTransitMessageCountMap[globalSessionId % SESSION_MAP_SIZE], 1);
|
|
}
|
|
|
|
/**
|
|
* Removes head of callback data list.
|
|
*/
|
|
static CallbackData *callbackDataRemove() {
|
|
std::unique_lock<std::recursive_mutex> lock(callbackDataMutex, std::defer_lock);
|
|
CallbackData* newData = nullptr;
|
|
|
|
lock.lock();
|
|
if (callbackDataList.size() > 0) {
|
|
newData = callbackDataList.front();
|
|
callbackDataList.pop_front();
|
|
}
|
|
lock.unlock();
|
|
|
|
return newData;
|
|
}
|
|
|
|
/**
|
|
* Registers a session id to the session map.
|
|
*
|
|
* @param sessionId session id
|
|
*/
|
|
static void registerSessionId(long sessionId) {
|
|
std::atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 1);
|
|
}
|
|
|
|
/**
|
|
* Removes a session id from the session map.
|
|
*
|
|
* @param sessionId session id
|
|
*/
|
|
static void removeSession(long sessionId) {
|
|
std::atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 0);
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/**
|
|
* Adds a cancel session request to the session map.
|
|
*
|
|
* @param sessionId session id
|
|
*/
|
|
void cancelSession(long sessionId) {
|
|
std::atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 2);
|
|
}
|
|
|
|
/**
|
|
* Checks whether a cancel request for the given session id exists in the session map.
|
|
*
|
|
* @param sessionId session id
|
|
* @return 1 if exists, false otherwise
|
|
*/
|
|
int cancelRequested(long sessionId) {
|
|
if (std::atomic_load(&sessionMap[sessionId % SESSION_MAP_SIZE]) == 2) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Resets the number of messages in transmit for this session.
|
|
*
|
|
* @param sessionId session id
|
|
*/
|
|
static void resetMessagesInTransmit(long sessionId) {
|
|
std::atomic_store(&sessionInTransitMessageCountMap[sessionId % SESSION_MAP_SIZE], 0);
|
|
}
|
|
|
|
/**
|
|
* Callback function for FFmpeg/FFprobe logs.
|
|
*
|
|
* @param ptr pointer to AVClass struct
|
|
* @param level log level
|
|
* @param format format string
|
|
* @param vargs arguments
|
|
*/
|
|
void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, va_list vargs) {
|
|
AVBPrint fullLine;
|
|
AVBPrint part[4];
|
|
int print_prefix = 1;
|
|
|
|
// DO NOT PROCESS UNWANTED LOGS
|
|
if (level >= 0) {
|
|
level &= 0xff;
|
|
}
|
|
int activeLogLevel = av_log_get_level();
|
|
|
|
// LevelAVLogStdErr logs are always redirected
|
|
if ((activeLogLevel == ffmpegkit::LevelAVLogQuiet && level != ffmpegkit::LevelAVLogStdErr) || (level > activeLogLevel)) {
|
|
return;
|
|
}
|
|
|
|
av_bprint_init(&fullLine, 0, AV_BPRINT_SIZE_UNLIMITED);
|
|
|
|
avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix);
|
|
avutil_log_sanitize(part[0].str);
|
|
avutil_log_sanitize(part[1].str);
|
|
avutil_log_sanitize(part[2].str);
|
|
avutil_log_sanitize(part[3].str);
|
|
|
|
// COMBINE ALL 4 LOG PARTS
|
|
av_bprintf(&fullLine, "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
|
|
|
|
if (fullLine.len > 0) {
|
|
logCallbackDataAdd(level, &fullLine);
|
|
}
|
|
|
|
av_bprint_finalize(part, NULL);
|
|
av_bprint_finalize(part+1, NULL);
|
|
av_bprint_finalize(part+2, NULL);
|
|
av_bprint_finalize(part+3, NULL);
|
|
av_bprint_finalize(&fullLine, NULL);
|
|
}
|
|
|
|
/**
|
|
* Callback function for FFmpeg statistics.
|
|
*
|
|
* @param frameNumber last processed frame number
|
|
* @param fps frames processed per second
|
|
* @param quality quality of the output stream (video only)
|
|
* @param size size in bytes
|
|
* @param time processed output duration
|
|
* @param bitrate output bit rate in kbits/s
|
|
* @param speed processing speed = processed duration / operation duration
|
|
*/
|
|
void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
|
|
statisticsCallbackDataAdd(frameNumber, fps, quality, size, time, bitrate, speed);
|
|
}
|
|
|
|
static void process_log(long sessionId, int levelValueInt, AVBPrint* logMessage) {
|
|
int activeLogLevel = av_log_get_level();
|
|
ffmpegkit::Level levelValue = static_cast<ffmpegkit::Level>(levelValueInt);
|
|
std::shared_ptr<ffmpegkit::Log> log = std::make_shared<ffmpegkit::Log>(sessionId, levelValue, logMessage->str);
|
|
bool globalCallbackDefined = false;
|
|
bool sessionCallbackDefined = false;
|
|
ffmpegkit::LogRedirectionStrategy activeLogRedirectionStrategy = globalLogRedirectionStrategy;
|
|
|
|
// LevelAVLogStdErr logs are always redirected
|
|
if ((activeLogLevel == ffmpegkit::LevelAVLogQuiet && levelValue != ffmpegkit::LevelAVLogStdErr) || (levelValue > activeLogLevel)) {
|
|
// LOG NEITHER PRINTED NOR FORWARDED
|
|
return;
|
|
}
|
|
|
|
auto session = ffmpegkit::FFmpegKitConfig::getSession(sessionId);
|
|
if (session != nullptr) {
|
|
activeLogRedirectionStrategy = session->getLogRedirectionStrategy();
|
|
session->addLog(log);
|
|
|
|
ffmpegkit::LogCallback sessionLogCallback = session->getLogCallback();
|
|
if (sessionLogCallback != nullptr) {
|
|
sessionCallbackDefined = true;
|
|
|
|
try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
sessionLogCallback(log);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside session log callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
ffmpegkit::LogCallback globalLogCallback = logCallback;
|
|
if (globalLogCallback != nullptr) {
|
|
globalCallbackDefined = true;
|
|
|
|
try {
|
|
// NOTIFY GLOBAL CALLBACK DEFINED
|
|
globalLogCallback(log);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside global log callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
// EXECUTE THE LOG STRATEGY
|
|
switch (activeLogRedirectionStrategy) {
|
|
case ffmpegkit::LogRedirectionStrategyNeverPrintLogs: {
|
|
return;
|
|
}
|
|
case ffmpegkit::LogRedirectionStrategyPrintLogsWhenGlobalCallbackNotDefined: {
|
|
if (globalCallbackDefined) {
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case ffmpegkit::LogRedirectionStrategyPrintLogsWhenSessionCallbackNotDefined: {
|
|
if (sessionCallbackDefined) {
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case ffmpegkit::LogRedirectionStrategyPrintLogsWhenNoCallbacksDefined: {
|
|
if (globalCallbackDefined || sessionCallbackDefined) {
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case ffmpegkit::LogRedirectionStrategyAlwaysPrintLogs: {
|
|
}
|
|
break;
|
|
}
|
|
|
|
// PRINT LOGS
|
|
switch (levelValue) {
|
|
case ffmpegkit::LevelAVLogQuiet:
|
|
// PRINT NO OUTPUT
|
|
break;
|
|
default:
|
|
// WRITE TO STDOUT
|
|
std::cout << ffmpegkit::FFmpegKitConfig::logLevelToString(levelValue) << ": " << logMessage->str;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void process_statistics(long sessionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) {
|
|
std::shared_ptr<ffmpegkit::Statistics> statistics = std::make_shared<ffmpegkit::Statistics>(sessionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
|
|
|
|
auto session = ffmpegkit::FFmpegKitConfig::getSession(sessionId);
|
|
if (session != nullptr && session->isFFmpeg()) {
|
|
std::shared_ptr<ffmpegkit::FFmpegSession> ffmpegSession = std::static_pointer_cast<ffmpegkit::FFmpegSession>(session);
|
|
ffmpegSession->addStatistics(statistics);
|
|
|
|
ffmpegkit::StatisticsCallback sessionStatisticsCallback = ffmpegSession->getStatisticsCallback();
|
|
if (sessionStatisticsCallback != nullptr) {
|
|
try {
|
|
sessionStatisticsCallback(statistics);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside session statistics callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
ffmpegkit::StatisticsCallback globalStatisticsCallback = statisticsCallback;
|
|
if (globalStatisticsCallback != nullptr) {
|
|
try {
|
|
globalStatisticsCallback(statistics);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside global statistics callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forwards asynchronous messages to Callbacks.
|
|
*/
|
|
void *callbackThreadFunction(void *pointer) {
|
|
int activeLogLevel = av_log_get_level();
|
|
if ((activeLogLevel != ffmpegkit::LevelAVLogQuiet) && (ffmpegkit::LevelAVLogDebug <= activeLogLevel)) {
|
|
std::cout << "Async callback block started." << std::endl;
|
|
}
|
|
|
|
while(redirectionEnabled) {
|
|
try {
|
|
CallbackData* callbackData = callbackDataRemove();
|
|
|
|
if (callbackData != nullptr) {
|
|
|
|
if (callbackData->getType() == LogType) {
|
|
process_log(callbackData->getSessionId(), callbackData->getLogLevel(), callbackData->getLogData());
|
|
av_bprint_finalize(callbackData->getLogData(), NULL);
|
|
} else {
|
|
process_statistics(callbackData->getSessionId(),
|
|
callbackData->getStatisticsFrameNumber(),
|
|
callbackData->getStatisticsFps(),
|
|
callbackData->getStatisticsQuality(),
|
|
callbackData->getStatisticsSize(),
|
|
callbackData->getStatisticsTime(),
|
|
callbackData->getStatisticsBitrate(),
|
|
callbackData->getStatisticsSpeed());
|
|
}
|
|
|
|
std::atomic_fetch_sub(&sessionInTransitMessageCountMap[callbackData->getSessionId() % SESSION_MAP_SIZE], 1);
|
|
|
|
} else {
|
|
callbackWait(100);
|
|
}
|
|
|
|
} catch(const std::exception& exception) {
|
|
activeLogLevel = av_log_get_level();
|
|
if ((activeLogLevel != ffmpegkit::LevelAVLogQuiet) && (ffmpegkit::LevelAVLogWarning <= activeLogLevel)) {
|
|
std::cout << "Async callback block received error: " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
activeLogLevel = av_log_get_level();
|
|
if ((activeLogLevel != ffmpegkit::LevelAVLogQuiet) && (ffmpegkit::LevelAVLogDebug <= activeLogLevel)) {
|
|
std::cout << "Async callback block stopped." << std::endl;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int executeFFmpeg(const long sessionId, const std::shared_ptr<std::list<std::string>> arguments) {
|
|
const char* LIB_NAME = "ffmpeg";
|
|
|
|
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW RUN
|
|
av_log_set_level(configuredLogLevel);
|
|
|
|
char **commandCharPArray = (char **)av_malloc(sizeof(char*) * (arguments->size() + 1));
|
|
|
|
/* PRESERVE USAGE FORMAT
|
|
*
|
|
* ffmpeg <arguments>
|
|
*/
|
|
commandCharPArray[0] = (char *)av_malloc(sizeof(char) * (strlen(LIB_NAME) + 1));
|
|
strcpy(commandCharPArray[0], LIB_NAME);
|
|
|
|
// PREPARE ARRAY ELEMENTS
|
|
int i = 0;
|
|
for (auto it=arguments->begin(); it != arguments->end(); it++, i++) {
|
|
commandCharPArray[i + 1] = (char*)it->c_str();
|
|
}
|
|
|
|
// REGISTER THE ID BEFORE STARTING THE SESSION
|
|
globalSessionId = sessionId;
|
|
registerSessionId(sessionId);
|
|
|
|
resetMessagesInTransmit(sessionId);
|
|
|
|
// RUN
|
|
int returnCode = ffmpeg_execute((arguments->size() + 1), commandCharPArray);
|
|
|
|
// ALWAYS REMOVE THE ID FROM THE MAP
|
|
removeSession(sessionId);
|
|
|
|
// CLEANUP
|
|
av_free(commandCharPArray[0]);
|
|
av_free(commandCharPArray);
|
|
|
|
return returnCode;
|
|
}
|
|
|
|
int executeFFprobe(const long sessionId, const std::shared_ptr<std::list<std::string>> arguments) {
|
|
const char* LIB_NAME = "ffprobe";
|
|
|
|
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW RUN
|
|
av_log_set_level(configuredLogLevel);
|
|
|
|
char **commandCharPArray = (char **)av_malloc(sizeof(char*) * (arguments->size() + 1));
|
|
|
|
/* PRESERVE USAGE FORMAT
|
|
*
|
|
* ffprobe <arguments>
|
|
*/
|
|
commandCharPArray[0] = (char *)av_malloc(sizeof(char) * (strlen(LIB_NAME) + 1));
|
|
strcpy(commandCharPArray[0], LIB_NAME);
|
|
|
|
// PREPARE ARRAY ELEMENTS
|
|
int i = 0;
|
|
for (auto it=arguments->begin(); it != arguments->end(); it++, i++) {
|
|
commandCharPArray[i + 1] = (char*)it->c_str();
|
|
}
|
|
|
|
// REGISTER THE ID BEFORE STARTING THE SESSION
|
|
globalSessionId = sessionId;
|
|
registerSessionId(sessionId);
|
|
|
|
resetMessagesInTransmit(sessionId);
|
|
|
|
// RUN
|
|
int returnCode = ffprobe_execute((arguments->size() + 1), commandCharPArray);
|
|
|
|
// ALWAYS REMOVE THE ID FROM THE MAP
|
|
removeSession(sessionId);
|
|
|
|
// CLEANUP
|
|
av_free(commandCharPArray[0]);
|
|
av_free(commandCharPArray);
|
|
|
|
return returnCode;
|
|
}
|
|
|
|
void* ffmpegKitInitialize() {
|
|
std::call_once(ffmpegKitInitializerFlag, [](){
|
|
std::cout << "Loading ffmpeg-kit." << std::endl;
|
|
|
|
sessionHistorySize = 10;
|
|
|
|
for(int i = 0; i<SESSION_MAP_SIZE; i++) {
|
|
std::atomic_init(&sessionMap[i], 0);
|
|
std::atomic_init(&sessionInTransitMessageCountMap[i], 0);
|
|
}
|
|
|
|
logCallback = nullptr;
|
|
statisticsCallback = nullptr;
|
|
ffmpegSessionCompleteCallback = nullptr;
|
|
ffprobeSessionCompleteCallback = nullptr;
|
|
mediaInformationSessionCompleteCallback = nullptr;
|
|
|
|
globalLogRedirectionStrategy = ffmpegkit::LogRedirectionStrategyPrintLogsWhenNoCallbacksDefined;
|
|
|
|
redirectionEnabled = 0;
|
|
|
|
ffmpegkit::FFmpegKitConfig::enableRedirection();
|
|
|
|
std::cout << "Loaded ffmpeg-kit-" << ffmpegkit::Packages::getPackageName() << "-" << ffmpegkit::ArchDetect::getArch() << "-" << ffmpegkit::FFmpegKitConfig::getVersion() << "-" << ffmpegkit::FFmpegKitConfig::getBuildDate() << "." << std::endl;
|
|
});
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::enableRedirection() {
|
|
std::unique_lock<std::recursive_mutex> lock(callbackDataMutex, std::defer_lock);
|
|
lock.lock();
|
|
|
|
if (redirectionEnabled != 0) {
|
|
lock.unlock();
|
|
return;
|
|
}
|
|
redirectionEnabled = 1;
|
|
|
|
lock.unlock();
|
|
|
|
int rc = pthread_create(&callbackThread, NULL, callbackThreadFunction, NULL);
|
|
if (rc != 0) {
|
|
std::cout << "Failed to create async callback block: %d" << rc << std::endl;
|
|
lock.unlock();
|
|
return;
|
|
}
|
|
|
|
av_log_set_callback(ffmpegkit_log_callback_function);
|
|
set_report_callback(ffmpegkit_statistics_callback_function);
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::disableRedirection() {
|
|
std::unique_lock<std::recursive_mutex> lock(callbackDataMutex, std::defer_lock);
|
|
|
|
lock.lock();
|
|
|
|
if (redirectionEnabled != 1) {
|
|
lock.unlock();
|
|
return;
|
|
}
|
|
redirectionEnabled = 0;
|
|
|
|
lock.unlock();
|
|
|
|
callbackNotify();
|
|
|
|
pthread_detach(callbackThread);
|
|
|
|
av_log_set_callback(av_log_default_callback);
|
|
set_report_callback(NULL);
|
|
}
|
|
|
|
int ffmpegkit::FFmpegKitConfig::setFontconfigConfigurationPath(const std::string& path) {
|
|
return ffmpegkit::FFmpegKitConfig::setEnvironmentVariable("FONTCONFIG_PATH", path);
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::setFontDirectory(const std::string& fontDirectoryPath, const std::map<std::string,std::string>& fontNameMapping) {
|
|
ffmpegkit::FFmpegKitConfig::setFontDirectoryList(std::list<std::string>{fontDirectoryPath}, fontNameMapping);
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::setFontDirectoryList(const std::list<std::string>& fontDirectoryList, const std::map<std::string,std::string>& fontNameMapping) {
|
|
int validFontNameMappingCount = 0;
|
|
|
|
const char *parentDirectory = std::getenv("HOME");
|
|
if (parentDirectory == NULL) {
|
|
parentDirectory = std::getenv("TMPDIR");
|
|
if (parentDirectory == NULL) {
|
|
parentDirectory = ".";
|
|
}
|
|
}
|
|
|
|
std::string cacheDir = std::string(parentDirectory) + "/.cache";
|
|
std::string ffmpegKitDir = cacheDir + "/ffmpegkit";
|
|
auto tempConfigurationDirectory = ffmpegKitDir + "/fontconfig";
|
|
auto fontConfigurationFile = std::string(tempConfigurationDirectory) + "/fonts.conf";
|
|
|
|
if (!fs_create_dir(cacheDir) || !fs_create_dir(ffmpegKitDir) || !fs_create_dir(tempConfigurationDirectory)) {
|
|
return;
|
|
}
|
|
std::cout << "Created temporary font conf directory: TRUE." << std::endl;
|
|
|
|
if (fs_exists(fontConfigurationFile, true, false)) {
|
|
bool fontConfigurationDeleted = std::remove(fontConfigurationFile.c_str());
|
|
std::cout << "Deleted old temporary font configuration: " << (fontConfigurationDeleted == 0?"TRUE":"FALSE") << "." << std::endl;
|
|
}
|
|
|
|
/* PROCESS MAPPINGS FIRST */
|
|
std::string fontNameMappingBlock = "";
|
|
for (auto const& pair : fontNameMapping) {
|
|
if ((pair.first.size() > 0) && (pair.second.size() > 0)) {
|
|
|
|
fontNameMappingBlock += " <match target=\"pattern\">\n";
|
|
fontNameMappingBlock += " <test qual=\"any\" name=\"family\">\n";
|
|
fontNameMappingBlock += " <string>";
|
|
fontNameMappingBlock += pair.first;
|
|
fontNameMappingBlock += "</string>\n";
|
|
fontNameMappingBlock += " </test>\n";
|
|
fontNameMappingBlock += " <edit name=\"family\" mode=\"assign\" binding=\"same\">\n";
|
|
fontNameMappingBlock += " <string>";
|
|
fontNameMappingBlock += pair.second;
|
|
fontNameMappingBlock += "</string>\n";
|
|
fontNameMappingBlock += " </edit>\n";
|
|
fontNameMappingBlock += " </match>\n";
|
|
|
|
validFontNameMappingCount++;
|
|
}
|
|
}
|
|
|
|
std::string fontConfiguration;
|
|
fontConfiguration += "<?xml version=\"1.0\"?>\n";
|
|
fontConfiguration += "<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">\n";
|
|
fontConfiguration += "<fontconfig>\n";
|
|
fontConfiguration += " <dir prefix=\"cwd\">.</dir>\n";
|
|
|
|
for (const auto& fontDirectoryPath : fontDirectoryList) {
|
|
fontConfiguration += " <dir>";
|
|
fontConfiguration += fontDirectoryPath;
|
|
fontConfiguration += "</dir>\n";
|
|
}
|
|
fontConfiguration += fontNameMappingBlock;
|
|
fontConfiguration += "</fontconfig>\n";
|
|
|
|
std::ofstream fontConfigurationStream(fontConfigurationFile, std::ios::out | std::ios::trunc);
|
|
if (fontConfigurationStream) {
|
|
fontConfigurationStream << fontConfiguration;
|
|
}
|
|
if (fontConfigurationStream.bad()) {
|
|
std::cout << "Failed to set font directory. Error received while saving font configuration: " << fontConfigurationStream.rdbuf() << "." << std::endl;
|
|
}
|
|
fontConfigurationStream.close();
|
|
|
|
std::cout << "Saved new temporary font configuration with " << validFontNameMappingCount << " font name mappings." << std::endl;
|
|
|
|
ffmpegkit::FFmpegKitConfig::setFontconfigConfigurationPath(tempConfigurationDirectory.c_str());
|
|
|
|
for (const auto& fontDirectoryPath : fontDirectoryList) {
|
|
std::cout << "Font directory " << fontDirectoryPath << " registered successfully." << std::endl;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<std::string> ffmpegkit::FFmpegKitConfig::registerNewFFmpegPipe() {
|
|
const char *parentDirectory = std::getenv("HOME");
|
|
if (parentDirectory == NULL) {
|
|
parentDirectory = std::getenv("TMPDIR");
|
|
if (parentDirectory == NULL) {
|
|
parentDirectory = ".";
|
|
}
|
|
}
|
|
|
|
// PIPES ARE CREATED UNDER THE PIPES DIRECTORY
|
|
std::string cacheDir = std::string(parentDirectory) + "/.cache";
|
|
std::string ffmpegKitDir = cacheDir + "/ffmpegkit";
|
|
std::string pipesDir = ffmpegKitDir + "/pipes";
|
|
|
|
if (!fs_create_dir(cacheDir) || !fs_create_dir(ffmpegKitDir) || !fs_create_dir(pipesDir)) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<std::string> newFFmpegPipePath = std::make_shared<std::string>(pipesDir + "/" + FFmpegKitNamedPipePrefix + std::to_string(pipeIndexGenerator++));
|
|
|
|
// FIRST CLOSE OLD PIPES WITH THE SAME NAME
|
|
ffmpegkit::FFmpegKitConfig::closeFFmpegPipe(newFFmpegPipePath->c_str());
|
|
|
|
int rc = mkfifo(newFFmpegPipePath->c_str(), S_IRWXU | S_IRWXG | S_IROTH);
|
|
if (rc == 0) {
|
|
return newFFmpegPipePath;
|
|
} else {
|
|
std::cout << "Failed to register new FFmpeg pipe " << newFFmpegPipePath << ". Operation failed with rc=" << rc << "." << std::endl;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::closeFFmpegPipe(const std::string& ffmpegPipePath) {
|
|
std::remove(ffmpegPipePath.c_str());
|
|
}
|
|
|
|
std::string ffmpegkit::FFmpegKitConfig::getFFmpegVersion() {
|
|
return FFMPEG_VERSION;
|
|
}
|
|
|
|
std::string ffmpegkit::FFmpegKitConfig::getVersion() {
|
|
if (FFmpegKitConfig::isLTSBuild()) {
|
|
return std::string("").append(FFmpegKitVersion).append("-lts");
|
|
} else {
|
|
return FFmpegKitVersion;
|
|
}
|
|
}
|
|
|
|
bool ffmpegkit::FFmpegKitConfig::isLTSBuild() {
|
|
#if defined(FFMPEG_KIT_LTS)
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
std::string ffmpegkit::FFmpegKitConfig::getBuildDate() {
|
|
char buildDate[10];
|
|
sprintf(buildDate, "%d", FFMPEG_KIT_BUILD_DATE);
|
|
return std::string(buildDate);
|
|
}
|
|
|
|
int ffmpegkit::FFmpegKitConfig::setEnvironmentVariable(const std::string& variableName, const std::string& variableValue) {
|
|
return setenv(variableName.c_str(), variableValue.c_str(), true);
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::ignoreSignal(const ffmpegkit::Signal signal) {
|
|
if (signal == ffmpegkit::SignalQuit) {
|
|
handleSIGQUIT = 0;
|
|
} else if (signal == ffmpegkit::SignalInt) {
|
|
handleSIGINT = 0;
|
|
} else if (signal == ffmpegkit::SignalTerm) {
|
|
handleSIGTERM = 0;
|
|
} else if (signal == ffmpegkit::SignalXcpu) {
|
|
handleSIGXCPU = 0;
|
|
} else if (signal == ffmpegkit::SignalPipe) {
|
|
handleSIGPIPE = 0;
|
|
}
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::ffmpegExecute(const std::shared_ptr<ffmpegkit::FFmpegSession> ffmpegSession) {
|
|
ffmpegSession->startRunning();
|
|
|
|
try {
|
|
int returnCode = executeFFmpeg(ffmpegSession->getSessionId(), ffmpegSession->getArguments());
|
|
ffmpegSession->complete(std::make_shared<ffmpegkit::ReturnCode>(returnCode));
|
|
} catch(const std::exception& exception) {
|
|
ffmpegSession->fail(exception.what());
|
|
std::cout << "FFmpeg execute failed: " << ffmpegkit::FFmpegKitConfig::argumentsToString(ffmpegSession->getArguments()) << "." << exception.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::ffprobeExecute(const std::shared_ptr<ffmpegkit::FFprobeSession> ffprobeSession) {
|
|
ffprobeSession->startRunning();
|
|
|
|
try {
|
|
int returnCode = executeFFprobe(ffprobeSession->getSessionId(), ffprobeSession->getArguments());
|
|
ffprobeSession->complete(std::make_shared<ffmpegkit::ReturnCode>(returnCode));
|
|
} catch(const std::exception& exception) {
|
|
ffprobeSession->fail(exception.what());
|
|
std::cout << "FFprobe execute failed: " << ffmpegkit::FFmpegKitConfig::argumentsToString(ffprobeSession->getArguments()) << "." << exception.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::getMediaInformationExecute(const std::shared_ptr<ffmpegkit::MediaInformationSession> mediaInformationSession, const int waitTimeout) {
|
|
mediaInformationSession->startRunning();
|
|
|
|
try {
|
|
int returnCodeValue = executeFFprobe(mediaInformationSession->getSessionId(), mediaInformationSession->getArguments());
|
|
auto returnCode = std::make_shared<ffmpegkit::ReturnCode>(returnCodeValue);
|
|
mediaInformationSession->complete(returnCode);
|
|
if (returnCode->isValueSuccess()) {
|
|
auto mediaInformation = ffmpegkit::MediaInformationJsonParser::from(mediaInformationSession->getAllLogsAsStringWithTimeout(waitTimeout).c_str());
|
|
mediaInformationSession->setMediaInformation(mediaInformation);
|
|
}
|
|
} catch(const std::exception& exception) {
|
|
mediaInformationSession->fail(exception.what());
|
|
std::cout << "Get media information execute failed: " << ffmpegkit::FFmpegKitConfig::argumentsToString(mediaInformationSession->getArguments()) << "." << exception.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::asyncFFmpegExecute(const std::shared_ptr<ffmpegkit::FFmpegSession> ffmpegSession) {
|
|
auto thread = std::thread([ffmpegSession]() {
|
|
ffmpegkit::FFmpegKitConfig::ffmpegExecute(ffmpegSession);
|
|
|
|
ffmpegkit::FFmpegSessionCompleteCallback completeCallback = ffmpegSession->getCompleteCallback();
|
|
if (completeCallback != nullptr) {
|
|
try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
completeCallback(ffmpegSession);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside session complete callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
ffmpegkit::FFmpegSessionCompleteCallback globalFFmpegSessionCompleteCallback = ffmpegkit::FFmpegKitConfig::getFFmpegSessionCompleteCallback();
|
|
if (globalFFmpegSessionCompleteCallback != nullptr) {
|
|
try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
globalFFmpegSessionCompleteCallback(ffmpegSession);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside global complete callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
});
|
|
|
|
thread.detach();
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::asyncFFprobeExecute(const std::shared_ptr<ffmpegkit::FFprobeSession> ffprobeSession) {
|
|
auto thread = std::thread([ffprobeSession]() {
|
|
ffmpegkit::FFmpegKitConfig::ffprobeExecute(ffprobeSession);
|
|
|
|
ffmpegkit::FFprobeSessionCompleteCallback completeCallback = ffprobeSession->getCompleteCallback();
|
|
if (completeCallback != nullptr) {
|
|
try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
completeCallback(ffprobeSession);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside session complete callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
ffmpegkit::FFprobeSessionCompleteCallback globalFFprobeSessionCompleteCallback = ffmpegkit::FFmpegKitConfig::getFFprobeSessionCompleteCallback();
|
|
if (globalFFprobeSessionCompleteCallback != nullptr) {
|
|
try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
globalFFprobeSessionCompleteCallback(ffprobeSession);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside global complete callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
});
|
|
|
|
thread.detach();
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::asyncGetMediaInformationExecute(const std::shared_ptr<ffmpegkit::MediaInformationSession> mediaInformationSession, const int waitTimeout) {
|
|
auto thread = std::thread([mediaInformationSession,waitTimeout]() {
|
|
ffmpegkit::FFmpegKitConfig::getMediaInformationExecute(mediaInformationSession, waitTimeout);
|
|
|
|
ffmpegkit::MediaInformationSessionCompleteCallback completeCallback = mediaInformationSession->getCompleteCallback();
|
|
if (completeCallback != nullptr) {
|
|
try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
completeCallback(mediaInformationSession);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside session complete callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
ffmpegkit::MediaInformationSessionCompleteCallback globalMediaInformationSessionCompleteCallback = ffmpegkit::FFmpegKitConfig::getMediaInformationSessionCompleteCallback();
|
|
if (globalMediaInformationSessionCompleteCallback != nullptr) {
|
|
try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
globalMediaInformationSessionCompleteCallback(mediaInformationSession);
|
|
} catch(const std::exception& exception) {
|
|
std::cout << "Exception thrown inside global complete callback. " << exception.what() << std::endl;
|
|
}
|
|
}
|
|
});
|
|
|
|
thread.detach();
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::enableLogCallback(const ffmpegkit::LogCallback callback) {
|
|
logCallback = callback;
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::enableStatisticsCallback(const ffmpegkit::StatisticsCallback callback) {
|
|
statisticsCallback = callback;
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::enableFFmpegSessionCompleteCallback(const FFmpegSessionCompleteCallback completeCallback) {
|
|
ffmpegSessionCompleteCallback = completeCallback;
|
|
}
|
|
|
|
ffmpegkit::FFmpegSessionCompleteCallback ffmpegkit::FFmpegKitConfig::getFFmpegSessionCompleteCallback() {
|
|
return ffmpegSessionCompleteCallback;
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::enableFFprobeSessionCompleteCallback(const FFprobeSessionCompleteCallback completeCallback) {
|
|
ffprobeSessionCompleteCallback = completeCallback;
|
|
}
|
|
|
|
ffmpegkit::FFprobeSessionCompleteCallback ffmpegkit::FFmpegKitConfig::getFFprobeSessionCompleteCallback() {
|
|
return ffprobeSessionCompleteCallback;
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::enableMediaInformationSessionCompleteCallback(const MediaInformationSessionCompleteCallback completeCallback) {
|
|
mediaInformationSessionCompleteCallback = completeCallback;
|
|
}
|
|
|
|
ffmpegkit::MediaInformationSessionCompleteCallback ffmpegkit::FFmpegKitConfig::getMediaInformationSessionCompleteCallback() {
|
|
return mediaInformationSessionCompleteCallback;
|
|
}
|
|
|
|
ffmpegkit::Level ffmpegkit::FFmpegKitConfig::getLogLevel() {
|
|
return static_cast<ffmpegkit::Level>(configuredLogLevel);
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::setLogLevel(const ffmpegkit::Level level) {
|
|
configuredLogLevel = level;
|
|
}
|
|
|
|
std::string ffmpegkit::FFmpegKitConfig::logLevelToString(const ffmpegkit::Level level) {
|
|
switch (level) {
|
|
case ffmpegkit::LevelAVLogStdErr: return "STDERR";
|
|
case ffmpegkit::LevelAVLogTrace: return "TRACE";
|
|
case ffmpegkit::LevelAVLogDebug: return "DEBUG";
|
|
case ffmpegkit::LevelAVLogVerbose: return "VERBOSE";
|
|
case ffmpegkit::LevelAVLogInfo: return "INFO";
|
|
case ffmpegkit::LevelAVLogWarning: return "WARNING";
|
|
case ffmpegkit::LevelAVLogError: return "ERROR";
|
|
case ffmpegkit::LevelAVLogFatal: return "FATAL";
|
|
case ffmpegkit::LevelAVLogPanic: return "PANIC";
|
|
case ffmpegkit::LevelAVLogQuiet: return "QUIET";
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
int ffmpegkit::FFmpegKitConfig::getSessionHistorySize() {
|
|
return sessionHistorySize;
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::setSessionHistorySize(const int newSessionHistorySize) {
|
|
if (newSessionHistorySize >= SESSION_MAP_SIZE) {
|
|
|
|
/*
|
|
* THERE IS A HARD LIMIT ON THE NATIVE SIDE. HISTORY SIZE MUST BE SMALLER THAN SESSION_MAP_SIZE
|
|
*/
|
|
throw std::runtime_error("Session history size must not exceed the hard limit!");
|
|
} else if (newSessionHistorySize > 0) {
|
|
sessionHistorySize = newSessionHistorySize;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<ffmpegkit::Session> ffmpegkit::FFmpegKitConfig::getSession(const long sessionId) {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
lock.lock();
|
|
|
|
auto session = sessionHistoryMap.find(sessionId);
|
|
if (session != sessionHistoryMap.end()) {
|
|
return session->second;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<ffmpegkit::Session> ffmpegkit::FFmpegKitConfig::getLastSession() {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
lock.lock();
|
|
|
|
return sessionHistoryList.front();
|
|
}
|
|
|
|
std::shared_ptr<ffmpegkit::Session> ffmpegkit::FFmpegKitConfig::getLastCompletedSession() {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
|
|
lock.lock();
|
|
|
|
for(auto rit=sessionHistoryList.rbegin(); rit != sessionHistoryList.rend(); ++rit) {
|
|
auto session = *rit;
|
|
if (session->getState() == SessionStateCompleted) {
|
|
return session;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<std::list<std::shared_ptr<ffmpegkit::Session>>> ffmpegkit::FFmpegKitConfig::getSessions() {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
lock.lock();
|
|
|
|
auto sessionHistoryListCopy = std::make_shared<std::list<std::shared_ptr<ffmpegkit::Session>>>(sessionHistoryList);
|
|
|
|
lock.unlock();
|
|
|
|
return sessionHistoryListCopy;
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::clearSessions() {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
lock.lock();
|
|
|
|
sessionHistoryList.clear();
|
|
sessionHistoryMap.clear();
|
|
|
|
lock.unlock();
|
|
}
|
|
|
|
std::shared_ptr<std::list<std::shared_ptr<ffmpegkit::FFmpegSession>>> ffmpegkit::FFmpegKitConfig::getFFmpegSessions() {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
const auto ffmpegSessions = std::make_shared<std::list<std::shared_ptr<ffmpegkit::FFmpegSession>>>();
|
|
|
|
lock.lock();
|
|
|
|
for(auto it=sessionHistoryList.begin(); it != sessionHistoryList.end(); ++it) {
|
|
auto session = *it;
|
|
if (session->isFFmpeg()) {
|
|
ffmpegSessions->push_back(std::static_pointer_cast<ffmpegkit::FFmpegSession>(session));
|
|
}
|
|
}
|
|
|
|
lock.unlock();
|
|
|
|
return ffmpegSessions;
|
|
}
|
|
|
|
std::shared_ptr<std::list<std::shared_ptr<ffmpegkit::FFprobeSession>>> ffmpegkit::FFmpegKitConfig::getFFprobeSessions() {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
const auto ffprobeSessions = std::make_shared<std::list<std::shared_ptr<ffmpegkit::FFprobeSession>>>();
|
|
|
|
lock.lock();
|
|
|
|
for(auto it=sessionHistoryList.begin(); it != sessionHistoryList.end(); ++it) {
|
|
auto session = *it;
|
|
if (session->isFFprobe()) {
|
|
ffprobeSessions->push_back(std::static_pointer_cast<ffmpegkit::FFprobeSession>(session));
|
|
}
|
|
}
|
|
|
|
lock.unlock();
|
|
|
|
return ffprobeSessions;
|
|
}
|
|
|
|
std::shared_ptr<std::list<std::shared_ptr<ffmpegkit::MediaInformationSession>>> ffmpegkit::FFmpegKitConfig::getMediaInformationSessions() {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
const auto mediaInformationSessions = std::make_shared<std::list<std::shared_ptr<ffmpegkit::MediaInformationSession>>>();
|
|
|
|
lock.lock();
|
|
|
|
for(auto it=sessionHistoryList.begin(); it != sessionHistoryList.end(); ++it) {
|
|
auto session = *it;
|
|
if (session->isMediaInformation()) {
|
|
mediaInformationSessions->push_back(std::static_pointer_cast<ffmpegkit::MediaInformationSession>(session));
|
|
}
|
|
}
|
|
|
|
lock.unlock();
|
|
|
|
return mediaInformationSessions;
|
|
}
|
|
|
|
std::shared_ptr<std::list<std::shared_ptr<ffmpegkit::Session>>> ffmpegkit::FFmpegKitConfig::getSessionsByState(const SessionState state) {
|
|
std::unique_lock<std::recursive_mutex> lock(sessionMutex, std::defer_lock);
|
|
auto sessions = std::make_shared<std::list<std::shared_ptr<ffmpegkit::Session>>>();
|
|
|
|
lock.lock();
|
|
|
|
for(auto it=sessionHistoryList.begin(); it != sessionHistoryList.end(); ++it) {
|
|
auto session = *it;
|
|
if (session->getState() == state) {
|
|
sessions->push_back(session);
|
|
}
|
|
}
|
|
|
|
lock.unlock();
|
|
|
|
return sessions;
|
|
}
|
|
|
|
ffmpegkit::LogRedirectionStrategy ffmpegkit::FFmpegKitConfig::getLogRedirectionStrategy() {
|
|
return globalLogRedirectionStrategy;
|
|
}
|
|
|
|
void ffmpegkit::FFmpegKitConfig::setLogRedirectionStrategy(const LogRedirectionStrategy logRedirectionStrategy) {
|
|
globalLogRedirectionStrategy = logRedirectionStrategy;
|
|
}
|
|
|
|
int ffmpegkit::FFmpegKitConfig::messagesInTransmit(const long sessionId) {
|
|
return std::atomic_load(&sessionInTransitMessageCountMap[sessionId % SESSION_MAP_SIZE]);
|
|
}
|
|
|
|
std::string ffmpegkit::FFmpegKitConfig::sessionStateToString(SessionState state) {
|
|
switch (state) {
|
|
case SessionStateCreated: return "CREATED";
|
|
case SessionStateRunning: return "RUNNING";
|
|
case SessionStateFailed: return "FAILED";
|
|
case SessionStateCompleted: return "COMPLETED";
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<std::list<std::string>> ffmpegkit::FFmpegKitConfig::parseArguments(const std::string& command) {
|
|
auto argumentList = std::make_shared<std::list<std::string>>();
|
|
std::string currentArgument;
|
|
|
|
bool singleQuoteStarted = false;
|
|
bool doubleQuoteStarted = false;
|
|
|
|
for (int i = 0; i < command.size(); i++) {
|
|
char previousChar;
|
|
if (i > 0) {
|
|
previousChar = command[i - 1];
|
|
} else {
|
|
previousChar = 0;
|
|
}
|
|
char currentChar = command[i];
|
|
|
|
if (currentChar == ' ') {
|
|
if (singleQuoteStarted || doubleQuoteStarted) {
|
|
currentArgument += currentChar;
|
|
} else if (currentArgument.size() > 0) {
|
|
argumentList->push_back(currentArgument);
|
|
currentArgument = "";
|
|
}
|
|
} else if (currentChar == '\'' && (previousChar == 0 || previousChar != '\\')) {
|
|
if (singleQuoteStarted) {
|
|
singleQuoteStarted = false;
|
|
} else if (doubleQuoteStarted) {
|
|
currentArgument += currentChar;
|
|
} else {
|
|
singleQuoteStarted = true;
|
|
}
|
|
} else if (currentChar == '\"' && (previousChar == 0 || previousChar != '\\')) {
|
|
if (doubleQuoteStarted) {
|
|
doubleQuoteStarted = false;
|
|
} else if (singleQuoteStarted) {
|
|
currentArgument += currentChar;
|
|
} else {
|
|
doubleQuoteStarted = true;
|
|
}
|
|
} else {
|
|
currentArgument += currentChar;
|
|
}
|
|
}
|
|
|
|
if (currentArgument.size() > 0) {
|
|
argumentList->push_back(currentArgument);
|
|
}
|
|
|
|
return argumentList;
|
|
}
|
|
|
|
std::string ffmpegkit::FFmpegKitConfig::argumentsToString(std::shared_ptr<std::list<std::string>> arguments) {
|
|
if (arguments == nullptr) {
|
|
return "null";
|
|
}
|
|
|
|
std::string string;
|
|
for(auto it=arguments->begin(); it != arguments->end(); ++it) {
|
|
auto argument = *it;
|
|
if (it != arguments->begin()) {
|
|
string += " ";
|
|
}
|
|
string += argument;
|
|
}
|
|
|
|
return string;
|
|
}
|