diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/.build.txt b/util/Agora_Recording_SDK_for_Linux_FULL/.build.txt new file mode 100644 index 0000000..3ae0d37 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/.build.txt @@ -0,0 +1,3 @@ +[repoFolder:AgoraRTCEngine, branch:release/1.12.0, commit:89171d96ae055f7809ad9b455256272eee0a0b43] +[repoFolder:media_sdk2, branch:release/1.12.0, commit:a442f9afe9b468f3f14930db4f3c01f73de90ea3] +[repoFolder:ServerSDK-Video, branch:release/1.12.0, commit:a4f0d4c22aa9d93b9cd0f71967d4f4b763e46190] diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/bin/video_recorder b/util/Agora_Recording_SDK_for_Linux_FULL/bin/video_recorder new file mode 100755 index 0000000..bd03765 Binary files /dev/null and b/util/Agora_Recording_SDK_for_Linux_FULL/bin/video_recorder differ diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/include/IAgoraRecordingEngine.h b/util/Agora_Recording_SDK_for_Linux_FULL/include/IAgoraRecordingEngine.h new file mode 100644 index 0000000..542d1d6 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/include/IAgoraRecordingEngine.h @@ -0,0 +1,196 @@ +#pragma once + +namespace agora { +namespace recording { + +typedef unsigned char uchar_t; +typedef unsigned int uint_t; +typedef unsigned int uid_t; + +enum ERROR_CODE_TYPE { + ERR_OK = 0, + //1~1000 + ERR_FAILED = 1, + ERR_INVALID_ARGUMENT = 2, + ERR_INTERNAL_FAILED = 3, +}; + +enum WARN_CODE_TYPE { + WARN_NO_AVAILABLE_CHANNEL = 103, + WARN_LOOKUP_CHANNEL_TIMEOUT = 104, + WARN_LOOKUP_CHANNEL_REJECTED = 105, + WARN_OPEN_CHANNEL_TIMEOUT = 106, + WARN_OPEN_CHANNEL_REJECTED = 107, +}; + +enum CHANNEL_PROFILE_TYPE +{ + CHANNEL_PROFILE_COMMUNICATION = 0, + CHANNEL_PROFILE_LIVE_BROADCASTING = 1, +}; + +enum USER_OFFLINE_REASON_TYPE +{ + USER_OFFLINE_QUIT = 0, + USER_OFFLINE_DROPPED = 1, + USER_OFFLINE_BECOME_AUDIENCE = 2, +}; + +enum REMOTE_VIDEO_STREAM_TYPE +{ + REMOTE_VIDEO_STREAM_HIGH = 0, + REMOTE_VIDEO_STREAM_LOW = 1, +}; + + +typedef struct VideoMixingLayout +{ + struct Region { + uid_t uid; + double x;//[0,1] + double y;//[0,1] + double width;//[0,1] + double height;//[0,1] + int zOrder; //optional, [0, 100] //0 (default): bottom most, 100: top most + + // Optional + // [0, 1.0] where 0 denotes throughly transparent, 1.0 opaque + double alpha; + + int renderMode;//RENDER_MODE_HIDDEN: Crop, RENDER_MODE_FIT: Zoom to fit + Region() + :uid(0) + , x(0) + , y(0) + , width(0) + , height(0) + , zOrder(0) + , alpha(1.0) + , renderMode(1) + {} + + }; + int canvasWidth; + int canvasHeight; + const char* backgroundColor;//e.g. "#C0C0C0" in RGB + int regionCount; + const Region* regions; + const char* appData; + int appDataLength; + VideoMixingLayout() + :canvasWidth(0) + , canvasHeight(0) + , backgroundColor(NULL) + , regionCount(0) + , regions(NULL) + , appData(NULL) + , appDataLength(0) + {} +} VideoMixingLayout; + +typedef struct UserJoinInfos { + const char* recordingDir; + //new attached info add below + + UserJoinInfos(): + recordingDir(NULL) + {} +}UserJoinInfos; + + +class IRecordingEngineEventHandler { +public: + virtual ~IRecordingEngineEventHandler() {} + + virtual void onError(int error) = 0; + virtual void onWarning(int warn) = 0; + + virtual void onJoinChannelSuccess(const char * channelId, uid_t uid) = 0; + virtual void onLeaveChannel() = 0; + + virtual void onUserJoined(uid_t uid, UserJoinInfos &infos) = 0; + virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) = 0; +}; + +typedef struct RecordingConfig { + CHANNEL_PROFILE_TYPE channelProfile; + bool isAudioOnly; + bool isMixingEnabled; + char * decryptionMode; + char * secret; + int idleLimitSec; + char * appliteDir; +// char * appliteLogDir; + char * recordFileRootDir; + int lowUdpPort; + int highUdpPort; + + RecordingConfig(): channelProfile(CHANNEL_PROFILE_COMMUNICATION), + isAudioOnly(false), + isMixingEnabled(false), + decryptionMode(NULL), + secret(NULL), + idleLimitSec(300), + appliteDir(NULL), +// appliteLogDir(NULL), + recordFileRootDir(NULL), + lowUdpPort(0), + highUdpPort(0) + {} +} RecordingConfig; + +class IRecordingEngine{ +public: + + /** + * create a new recording engine instance + * + * @param appId The App ID issued to the application developers by Agora.io. + * @param eventHandler the callback interface + * + * @return a recording engine instance pointer + */ + static IRecordingEngine* createAgoraRecordingEngine(const char * appId, IRecordingEngineEventHandler *eventHandler); + + virtual ~IRecordingEngine() {} + + /** + * This method lets the recording engine join a channel, and start recording + * + * @param channelKey This parameter is optional if the user uses a static key, or App ID. In this case, pass NULL as the parameter value. More details refer to http://docs-origin.agora.io/en/user_guide/Component_and_Others/Dynamic_Key_User_Guide.html + * @param channelId A string providing the unique channel id for the AgoraRTC session + * @param uid The uid of recording client + * @param config The config of current recording + * + * @return 0: Method call succeeded. <0: Method call failed. + */ + virtual int joinChannel(const char * channelKey, const char *channelId, uid_t uid, const RecordingConfig &config) = 0; + + + + /** + * set the layout of video mixing + * + * @param layout layout setting + * + * @return 0: Method call succeeded. <0: Method call failed. + */ + virtual int setVideoMixingLayout(const VideoMixingLayout &layout) = 0; + + /** + * Stop recording + * + * @return 0: Method call succeeded. <0: Method call failed. + */ + virtual int leaveChannel() = 0; + + /** + * release recording engine + * + * @return 0: Method call succeeded. <0: Method call failed. + */ + virtual int release() = 0; +}; + +} +} diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/include/base/atomic.h b/util/Agora_Recording_SDK_for_Linux_FULL/include/base/atomic.h new file mode 100644 index 0000000..5518fd8 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/include/base/atomic.h @@ -0,0 +1,69 @@ +#pragma once + +#ifdef __GNUC__ +#if __GNUC_PREREQ(4, 6) +#include <atomic> +typedef std::atomic<bool> atomic_bool_t; + +#elif __GNUC_PREREQ(4, 4) +namespace agora { +namespace base { + +template <typename T> class atomic; + +template <> +class atomic<bool> { + public: + explicit atomic(bool value=false); + ~atomic(); + + operator bool() const; + atomic<bool>& operator=(bool value); + bool load(); + void store(bool value); + private: + bool value_; +}; + +inline +atomic<bool>::atomic(bool value) { + value_ = value; + __sync_synchronize(); +} + +inline +atomic<bool>::~atomic() { +} + +inline +atomic<bool>::operator bool() const { + return value_; +} + +inline +atomic<bool>& atomic<bool>::operator=(bool value) { + value_ = value; + __sync_synchronize(); + return *this; +} + +inline bool atomic<bool>::load() { + __sync_synchronize(); + return value_; +} + +inline void atomic<bool>::store(bool value) { + value_ = value; + __sync_synchronize(); +} + +} +} + +typedef agora::base::atomic<bool> atomic_bool_t; +#else +#error "version should be at least 4.4" +//__sync_synchronize() doesn't produce mfence instruction +//when gcc version less than 4.4 +#endif +#endif diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/include/base/log.h b/util/Agora_Recording_SDK_for_Linux_FULL/include/base/log.h new file mode 100644 index 0000000..60db218 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/include/base/log.h @@ -0,0 +1,95 @@ +#pragma once + +#include <sys/types.h> +#include <unistd.h> + +#include <cinttypes> +#include <cstdint> +#include <cstdlib> +#include <stdarg.h> +#include <cassert> +#include <syslog.h> + +namespace agora { +namespace base { + +enum log_levels { + DEBUG_LOG = LOG_DEBUG, /* 7 debug-level messages */ + INFO_LOG = LOG_INFO, /* 6 informational */ + NOTICE_LOG = LOG_NOTICE, /* 5 normal but significant condition */ + WARN_LOG = LOG_WARNING, /* 4 warning conditions */ + ERROR_LOG = LOG_ERR, /* 3 error conditions */ + FATAL_LOG = LOG_CRIT, /* 2 critical conditions */ +}; + +struct log_config { + static int enabled_level; + static uint64_t dropped_count; + + static uint32_t drop_cancel; + const static uint32_t DROP_COUNT = 1000; + + static inline void enable_debug(bool enabled) { + if (enabled) { + log_config::enabled_level = DEBUG_LOG; + } else { + log_config::enabled_level = INFO_LOG; + } + } + + static bool set_drop_cannel(uint32_t cancel) { + if (cancel > DROP_COUNT) { + drop_cancel = DROP_COUNT; + return false; + } + + drop_cancel = cancel; + return true; + } + + static inline bool log_enabled(log_levels level) { + if (level <= enabled_level) { + return true; + } + + ++dropped_count; + return (dropped_count % DROP_COUNT < drop_cancel); + } +}; + +inline void open_log() { + ::openlog(NULL, LOG_PID|LOG_NDELAY, LOG_USER|LOG_DAEMON); +} + +inline void log(log_levels level, const char* format, ...) { + if (! log_config::log_enabled(level)) { + return; + } + + va_list args; + va_start(args, format); + ::vsyslog(level, format, args); + va_end(args); +} + +inline void close_log() { + ::closelog(); +} + +} +} + +#define LOG(level, fmt, ...) log(agora::base::level ## _LOG, \ + "(%d) %s:%d: " fmt, getpid(), __FILE__, __LINE__, ##__VA_ARGS__) + +#define LOG_IF(level, cond, ...) \ + if (cond) { \ + LOG(level, __VA_ARGS__); \ + } + +#define LOG_EVERY_N(level, N, ...) \ + { \ + static unsigned int count = 0; \ + if (++count % N == 0) \ + LOG(level, __VA_ARGS__); \ + } diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/libs/libRecordEngine.a b/util/Agora_Recording_SDK_for_Linux_FULL/libs/libRecordEngine.a new file mode 100644 index 0000000..d53bdfc Binary files /dev/null and b/util/Agora_Recording_SDK_for_Linux_FULL/libs/libRecordEngine.a differ diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/samples/Makefile b/util/Agora_Recording_SDK_for_Linux_FULL/samples/Makefile new file mode 100644 index 0000000..7d77792 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/samples/Makefile @@ -0,0 +1,32 @@ +ifeq (${CXX},) +CXX=g++ +endif +LINK=${CXX} + +TOPDIR=`pwd`/.. +LIBPATH=${TOPDIR}/libs +#-static-libstdc++ +LDFLAGS= -static-libgcc -std=c++11 +CXXFLAGS = -pipe -std=c++0x -fPIC -g -fno-omit-frame-pointer \ + -DNDEBUG=1 -Wconversion -O3 -Wall -W -fvisibility=hidden + +LIB = -pthread -lpthread -L$(LIBPATH) -lRecordEngine -lrt +INCPATH =-I. -I${TOPDIR}/include + +SRC =$(wildcard *.cpp) +OBJ=$(addsuffix .o, $(basename $(SRC))) +TARGET=Recorder_local + + +.PHONY: all clean +all: $(TARGET) + +$(TARGET): $(OBJ) + $(LINK) $(LDFLAGS) $(INCPATH) $^ -o "$@" $(LIB) + +%.o: %.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<" + +clean: + rm -f $(TARGET) + rm -f ${OBJ} diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/samples/base/opt_parser.h b/util/Agora_Recording_SDK_for_Linux_FULL/samples/base/opt_parser.h new file mode 100644 index 0000000..866285b --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/samples/base/opt_parser.h @@ -0,0 +1,97 @@ +#pragma once + +#include <cstdint> +#include <iostream> +#include <string> +#include <unordered_map> + +namespace agora { +namespace base { +struct ipv4 { + union { + uint32_t ip; + uint8_t repr[4]; + }; +}; + +struct mac_addr { + uint8_t addr_bytes[6]; +}; + +class opt_parser { + public: + enum opt_type { + no_argu=0, + require_argu=1, + opt_argu=2, + }; + + private: + enum pointer_type {kBool, kInt32, kUInt32, kInt64, kUInt64, kDouble, kString, + kIPv4, kMacAddr}; + + struct internal_opt { + pointer_type type; + union { + bool *bool_ptr; + int32_t *int32_ptr; + uint32_t *uint32_ptr; + int64_t *int64_ptr; + uint64_t *uint64_ptr; + double *double_ptr; + std::string *string_ptr; + ipv4 *ipv4_ptr; + mac_addr *addr_ptr; + }; + const char *help; + opt_type optType; + }; + public: + opt_parser() {} + + // bool add_short_arg(bool *store, char short_arg); + // bool add_short_arg(int *store, char short_arg); + // bool add_short_arg(std::string *store, char short_arg); + + bool add_long_opt(const char *long_opt, bool *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + bool add_long_opt(const char *long_opt, int32_t *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + bool add_long_opt(const char *long_opt, uint32_t *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + bool add_long_opt(const char *long_opt, int64_t *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + bool add_long_opt(const char *long_opt, uint64_t *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + bool add_long_opt(const char *long_opt, double *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + bool add_long_opt(const char *long_opt, std::string *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + bool add_long_opt(const char *long_opt, ipv4 *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + bool add_long_opt(const char *long_opt, mac_addr *store, const char *help, + opt_parser::opt_type optParamType = opt_parser::opt_argu); + + // NOTE(liuyong): the FIRST argument must be supplied as a place holder + bool parse_opts(int argc, char *const argv[]); + + void clear(); + + void print_usage(const char *exec_file, std::ostream &sout) const; + private: + // bool insert_short_arg(internal_opt arg, char short_arg); + bool insert_long_opt(internal_opt &opt, const char *long_opt); + + static bool fill_arg(const char *opt_name, const internal_opt &opt, + const char *opt_arg); + + static bool parse_ipv4(const char *arg, ipv4 *ip); + static bool parse_mac_addr(const char *arg, mac_addr *addr); + private: + // std::unordered_map<char, internal_opt> short_args_; + std::unordered_map<const char *, internal_opt> long_opts_; +}; + +} +} + diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/samples/main.cpp b/util/Agora_Recording_SDK_for_Linux_FULL/samples/main.cpp new file mode 100644 index 0000000..5164472 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/samples/main.cpp @@ -0,0 +1,322 @@ +#include <csignal> +#include <cstdint> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> +#include <algorithm> + +#include "IAgoraRecordingEngine.h" + +#include "base/atomic.h" +#include "base/log.h" +#include "base/opt_parser.h" + +using std::string; +using std::cout; +using std::cerr; +using std::endl; + +using agora::base::opt_parser; + +class AgoraRecorder : public agora::recording::IRecordingEngineEventHandler { + public: + // appId + AgoraRecorder(); + ~AgoraRecorder(); + + bool createChannel(const string &appid, const string &channelKey, const string &name, uid_t uid, + bool decodeAudio, bool decodeVideo, agora::recording::RecordingConfig &config); + + int setVideoMixLayout(); + + bool leaveChannel(); + bool release(); + + bool stopped() const; + + private: + virtual void onError(int error); + virtual void onWarning(int warn); + + virtual void onJoinChannelSuccess(const char * channelId, uid_t uid); + virtual void onLeaveChannel(); + + virtual void onUserJoined(uid_t uid, agora::recording::UserJoinInfos &infos); + virtual void onUserOffline(uid_t uid, agora::recording::USER_OFFLINE_REASON_TYPE reason); + + private: + atomic_bool_t m_stopped; + agora::recording::IRecordingEngine *m_recorder; + + std::vector<agora::recording::uid_t> m_peers; +}; + +AgoraRecorder::AgoraRecorder(): IRecordingEngineEventHandler() { + m_recorder = NULL; + m_stopped.store(false); +} + +AgoraRecorder::~AgoraRecorder() { + if (m_recorder) { + m_recorder->release(); + } +} + +bool AgoraRecorder::stopped() const { + return m_stopped; +} + +bool AgoraRecorder::release() { + if (m_recorder) { + m_recorder->release(); + m_recorder = NULL; + } + + return true; +} + +bool AgoraRecorder::createChannel(const string &appid, const string &channelKey, const string &name, + uint32_t uid, + bool decodeAudio, bool decodeVideo, + agora::recording::RecordingConfig &config) +{ + if ((m_recorder = agora::recording::IRecordingEngine::createAgoraRecordingEngine(appid.c_str(), this)) == NULL) + return false; + + + return 0 == m_recorder->joinChannel(channelKey.c_str(), name.c_str(), uid, config); +} + +bool AgoraRecorder::leaveChannel() { + if (m_recorder) { + m_recorder->leaveChannel(); + m_stopped = true; + } + + return true; +} + +int AgoraRecorder::setVideoMixLayout() +{ + LOG(INFO, "setVideoMixLayout: user size: %d", m_peers.size()); + + agora::recording::VideoMixingLayout layout; + layout.canvasWidth = 360; + layout.canvasHeight = 640; + layout.backgroundColor = "#23b9dc"; + + layout.regionCount = m_peers.size(); + + if (!m_peers.empty()) { + LOG(INFO, "setVideoMixLayout: peers not empty"); + agora::recording::VideoMixingLayout::Region * regionList = new agora::recording::VideoMixingLayout::Region[m_peers.size()]; + + regionList[0].uid = m_peers[0]; + regionList[0].x = 0.f; + regionList[0].y = 0.f; + regionList[0].width = 1.f; + regionList[0].height = 1.f; + regionList[0].zOrder = 0; + regionList[0].alpha = 1.f; + regionList[0].renderMode = 0; + + LOG(INFO, "region 0 uid: %d, x: %f, y: %f, width: %f, height: %f, zOrder: %d, alpha: %f", regionList[0].uid, regionList[0].x, regionList[0].y, regionList[0].width, regionList[0].height, regionList[0].zOrder, regionList[0].alpha); + + + float canvasWidth = 360.0; + float canvasHeight = 640.0; + + float viewWidth = 0.3; + float viewHEdge = 0.025; + float viewHeight = viewWidth * (canvasWidth / canvasHeight); + float viewVEdge = viewHEdge * (canvasWidth / canvasHeight); + + for (int i=1; i<m_peers.size(); i++) { + if (i >= 7) + break; + + regionList[i].uid = m_peers[i]; + + float xIndex = i % 3; + float yIndex = i / 3; + regionList[i].x = xIndex * (viewWidth + viewHEdge) + viewHEdge; + regionList[i].y = 1 - (yIndex + 1) * (viewHeight + viewVEdge); + regionList[i].width = viewWidth; + regionList[i].height = viewHeight; + regionList[i].zOrder = 0; + regionList[i].alpha = i + 1; + regionList[i].renderMode = 0; + } + + layout.regions = regionList; +// LOG(INFO, "region 0 uid: %d, x: %f, y: %f, width: %f, height: %f, zOrder: %d, alpha: %f", regionList[0].uid, regionList[0].x, regionList[0].y, regionList[0].width, regionList[0].height, regionList[0].zOrder, regionList[0].alpha); + } + else { + layout.regions = NULL; + } + + m_recorder->setVideoMixingLayout(layout); +} + +void AgoraRecorder::onError(int error) { + cerr << "Error: " << error << endl; + leaveChannel(); +} + +void AgoraRecorder::onWarning(int warn) { + cerr << "warn: " << warn << endl; + // leaveChannel(); +} + +void AgoraRecorder::onJoinChannelSuccess(const char * channelId, uid_t uid) { + cout << "join channel Id: " << channelId << ", with uid: " << uid << endl; +} + +void AgoraRecorder::onLeaveChannel() { + cout << "leave channel" << endl; +} + +void AgoraRecorder::onUserJoined(unsigned uid, agora::recording::UserJoinInfos &infos) { + cout << "User " << uid << " joined, RecordingDir:" << (infos.recordingDir? infos.recordingDir:"NULL") <<endl; + m_peers.push_back(uid); + + setVideoMixLayout(); +} + +void AgoraRecorder::onUserOffline(unsigned uid, agora::recording::USER_OFFLINE_REASON_TYPE reason) { + cout << "User " << uid << " offline, reason: " << reason << endl; + m_peers.erase(std::remove(m_peers.begin(), m_peers.end(), uid), m_peers.end()); + + setVideoMixLayout(); +} + +atomic_bool_t g_bSignalStop; + +void signal_handler(int signo) { + (void)signo; + + // cerr << "Signal " << signo << endl; + g_bSignalStop = true; +} + +int main(int argc, char * const argv[]) { + uint32_t uid = 0; + string appId; + string channelKey; + string name; + bool decodeAudio = false; + bool decodeVideo = false; + + uint32_t channelProfile; + + string decryptionMode; + string secret; + + int idleLimitSec=30*60;//30min + + string applitePath; + string appliteLogPath; + string recordFileRootDir="."; + + int lowUdpPort = 0;//40000; + int highUdpPort = 0;//40004; + + bool isAudioOnly=0; + bool isMixingEnabled=0; + + + g_bSignalStop = false; + signal(SIGQUIT, signal_handler); + signal(SIGABRT, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGPIPE, SIG_IGN); + + opt_parser parser; + + + + parser.add_long_opt("appId", &appId, "App Id/must", agora::base::opt_parser::require_argu); + parser.add_long_opt("uid", &uid, "User Id default is 0/must", agora::base::opt_parser::require_argu); + + parser.add_long_opt("channel", &name, "Channel Id/must", agora::base::opt_parser::require_argu); + parser.add_long_opt("appliteDir", &applitePath, "directory of app lite 'video_recorder', Must pointer to 'Agora_Recording_SDK_for_Linux_FULL/bin/' folder/must", + agora::base::opt_parser::require_argu); + + parser.add_long_opt("channelKey", &channelKey, "channelKey/option"); + parser.add_long_opt("channelProfile", &channelProfile, "channel_profile:(0:COMMUNICATION),(1:broadcast) default is 0/option"); + + parser.add_long_opt("isAudioOnly", &isAudioOnly, "Default 0:ARS (0:1)/option"); + parser.add_long_opt("isMixingEnabled", &isMixingEnabled, "Mixing Enable? (0:1)/option"); + + parser.add_long_opt("decryptionMode", &decryptionMode, "decryption Mode, default is NULL/option"); + parser.add_long_opt("secret", &secret, "input secret when enable decryptionMode/option"); + + parser.add_long_opt("idle", &idleLimitSec, "Default 300s/option"); + parser.add_long_opt("recordFileRootDir", &recordFileRootDir, "recording file root dir/option"); + + parser.add_long_opt("lowUdpPort", &lowUdpPort, "default is random value/option"); + parser.add_long_opt("highUdpPort", &highUdpPort, "default is random value/option"); + + + if (!parser.parse_opts(argc, argv) || appId.empty() || name.empty()) { + std::string usage = "Usage: \n\ + ./RECORD_APP --appId STRING --uid UINTEGER32 --channel STRING --appliteDir STRING --channelKey STRING --channelProfile UINTEGER32 --isAudioOnly 0/1 --isMixingEnabled 0/1 --decryptionMode STRING --secret STRING --idle INTEGER32 --recordFileRootDir STRING --lowUdpPort INTEGER32 --highUdpPort INTEGER32\n \ + --appId (App Id/must) \n \ + --uid (User Id default is 0/must) \n \ + --channel (Channel Id/must) \n \ + --appliteDir (directory of app lite 'video_recorder', Must pointer to 'Agora_Recording_SDK_for_Linux_FULL/bin/' folder/must) \n \ + --channelKey (channelKey/option) \n \ + --channelProfile (channel_profile:(0:COMMUNICATION),(1:broadcast) default is 0/option) \n \ + --isAudioOnly (Default 0:ARS (0:1)/option) \n \ + --isMixingEnabled (Mixing Enable? (0:1)/option) \n \ + --decryptionMode (decryption Mode, default is NULL/option) \n \ + --secret (input secret when enable decryptionMode/option) \n \ + --idle (Default 300s/option) \n \ + --recordFileRootDir (recording file root dir/option) \n \ + --lowUdpPort (default is random value/option) \n \ + --highUdpPort (default is random value/option)"; + + std::cerr << usage << std::endl; + return -1; + } + + LOG(INFO, "uid %" PRIu32 " from vendor %s is joining channel %s", + uid, appId.c_str(), name.c_str()); + + AgoraRecorder recorder; + agora::recording::RecordingConfig config; + config.idleLimitSec = idleLimitSec; + config.channelProfile = static_cast<agora::recording::CHANNEL_PROFILE_TYPE>(channelProfile); + + config.isAudioOnly = isAudioOnly; + config.isMixingEnabled = isMixingEnabled; + + config.appliteDir = const_cast<char*>(applitePath.c_str()); + config.recordFileRootDir = const_cast<char*>(recordFileRootDir.c_str()); + + config.secret = secret.empty()? NULL:const_cast<char*>(secret.c_str()); + config.decryptionMode = decryptionMode.empty()? NULL:const_cast<char*>(decryptionMode.c_str()); + + config.lowUdpPort = lowUdpPort; + config.highUdpPort = highUdpPort; + + + if (!recorder.createChannel(appId, channelKey, name, uid, decodeAudio, decodeVideo, config)) { + cerr << "Failed to create agora channel: " << name << endl; + return -1; + } + + while (!recorder.stopped() && !g_bSignalStop) { + sleep(1); + } + + if (g_bSignalStop) { + recorder.leaveChannel(); + recorder.release(); + } + + cerr << "Stopped \n"; + return 0; +} + diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/samples/opt_parser.cpp b/util/Agora_Recording_SDK_for_Linux_FULL/samples/opt_parser.cpp new file mode 100644 index 0000000..6f34768 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/samples/opt_parser.cpp @@ -0,0 +1,472 @@ +#include <cassert> +#include <cctype> +#include <cstdlib> +#include <cstring> +#include <iostream> +#include <vector> +#include <cstring> + +#if (defined(_WIN32) || defined(_WIN64)) && !defined(__GNUC__) +enum {ERROR=-1, INFO=0, WARNING, FATAL}; +#define LOG(level, fmt, ...) fprintf(stderr, #level fmt "\n", __VA_ARGS__) +#define strtoll _strtoi64 +#define strtoull _strtoui64 +#else +#include <getopt.h> +#include "base/log.h" +#endif + +#include "base/opt_parser.h" + +using namespace std; + +namespace agora { +namespace base { +#if defined(_WIN32) && !defined(__GNUC__) + +enum {no_argument = 0, required_argument, optional_argument}; + +int optind = 0; +const char *optarg = NULL; + +struct option { + const char *name; + int has_arg; + int *flag; + int val; +}; + +int getopt_long_only(int argc, char *const argv[], const char *short_opts, + const option *long_opts, int *index) { + if (short_opts && *short_opts != '\0') { + LOG(ERROR, "short options have not been implemented yet!"); + return -1; + } + + optind = optind == 0 ? 1 : optind; + optarg = NULL; + + if (optind >= argc) + return -1; + + const char *a = argv[optind]; + if (a[0] != '-' || a[1] != '-') + return -1; + + a += 2; + + int i = 0; + + for (; long_opts[i].name != NULL; ++i) { + if (!strcmp(long_opts[i].name, a)) { + ++optind; + *index = i; + return 0; + } + } + + return '?'; +} + +#endif + +// bool opt_parser::insert_short_arg(internal_opt arg, char short_arg) { +// if (!isalpha(short_arg)) { +// LOG(ERROR, "short parameters should be alphabetic!"); +// return false; +// } +// +// if (short_args_.count(short_arg) > 0) { +// LOG(ERROR, "short parameter[%c] has been occupied!", short_arg); +// return false; +// } +// +// short_args_[short_arg] = arg; +// return true; +// } + +bool opt_parser::insert_long_opt(internal_opt &opt, const char *long_opt) { + if (!long_opt) { + LOG(ERROR, "A full parameter should be supplied!"); + return false; + } + + if (long_opts_.count(long_opt) > 0) { + LOG(ERROR, "{%s} has been occupied yet!", long_opt); + return false; + } + if(opt.help == NULL || !strlen(opt.help)) + opt.help = "NA"; + + long_opts_[long_opt] = opt; + return true; +} + +// bool opt_parser::add_short_arg(bool *store, char short_arg) { +// *store = false; +// +// internal_opt arg = {kBool, {store}}; +// return insert_short_arg(arg, short_arg); +// } +// +// bool opt_parser::add_short_arg(int *store, char short_arg) { +// internal_opt arg; +// arg.type = kInt; +// arg.int_ptr = store; +// +// return insert_short_arg(arg, short_arg); +// } +// +// bool opt_parser::add_short_arg(string *store, char short_arg) { +// internal_opt arg; +// arg.type = kString; +// arg.string_ptr = store; +// +// return insert_short_arg(arg, short_arg); +// } + +bool opt_parser::add_long_opt(const char *long_opt, bool *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kBool; + arg.bool_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::add_long_opt(const char *long_opt, int32_t *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kInt32; + arg.int32_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::add_long_opt(const char *long_opt, uint32_t *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kUInt32; + arg.uint32_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::add_long_opt(const char *long_opt, int64_t *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kInt64; + arg.int64_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::add_long_opt(const char *long_opt, uint64_t *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kUInt64; + arg.uint64_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::add_long_opt(const char *long_opt, double *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kDouble; + arg.double_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::add_long_opt(const char *long_opt, string *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kString; + arg.string_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::add_long_opt(const char *long_opt, ipv4 *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kIPv4; + arg.ipv4_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::add_long_opt(const char *long_opt, mac_addr *store, + const char *help, opt_parser::opt_type optParamType) +{ + internal_opt arg; + arg.type = kMacAddr; + arg.addr_ptr = store; + arg.help = help; + arg.optType = optParamType; + + return insert_long_opt(arg, long_opt); +} + +bool opt_parser::parse_ipv4(const char *arg, ipv4 *ip) { + uint8_t (&a)[4] = ip->repr; + if (sscanf(arg, "%hhu.%hhu.%hhu.%hhu", &a[0], &a[1], &a[2], &a[3]) != 4) { + LOG(ERROR, "Illegal IP Format: %s", arg); + return false; + } + + return true; +} + +bool opt_parser::parse_mac_addr(const char *arg, mac_addr *addr) { + uint8_t (&b)[6] = addr->addr_bytes; + if (sscanf(arg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &b[0], &b[1], + &b[2], &b[3], &b[4], &b[5]) != 6) { + LOG(ERROR, "Illegal ethernet physical address: %s", arg); + return false; + } + + return true; +} + +bool opt_parser::fill_arg(const char *opt_name, const internal_opt &opt, + const char *opt_arg) { + + if (opt_arg == NULL) { + LOG(ERROR, "No argument available for %s", opt_name); + return false; + } + + char *end_ptr = NULL; + + switch (opt.type) { + case kBool: { + unsigned long n = strtoul(opt_arg, &end_ptr, 10); + if (*end_ptr != '\0') { + LOG(ERROR, "Invalid integer argument: %s", opt_arg); + return false; + } + *opt.bool_ptr = n? true:false; + } + break; + + case kInt32: { + long n = strtol(opt_arg, &end_ptr, 10); + if (*end_ptr != '\0') { + LOG(ERROR, "Invalid integer argument: %s", opt_arg); + return false; + } + *opt.int32_ptr = int32_t(n); + break; + } + case kUInt32: { + unsigned long n = strtoul(opt_arg, &end_ptr, 10); + if (*end_ptr != '\0') { + LOG(ERROR, "Invalid integer argument: %s", opt_arg); + return false; + } + *opt.uint32_ptr = uint32_t(n); + break; + } + case kInt64: { + long long n = strtoll(opt_arg, &end_ptr, 10); + if (*end_ptr != '\0') { + LOG(ERROR, "Invalid integer argument: %s", opt_arg); + return false; + } + *opt.int64_ptr = int64_t(n); + break; + } + case kUInt64: { + unsigned long long n = strtoull(opt_arg, &end_ptr, 10); + if (*end_ptr != '\0') { + LOG(ERROR, "Invalid integer argument: %s", opt_arg); + return false; + } + *opt.uint64_ptr = uint64_t(n); + break; + } + case kDouble: { + double n = strtod(opt_arg, &end_ptr); + if (*end_ptr != '\0') { + LOG(ERROR, "Invalid double argument: %s", opt_arg); + return false; + } + *opt.double_ptr = n; + break; + } + case kString: { + *opt.string_ptr = opt_arg; + break; + } + case kIPv4: { + if (!parse_ipv4(opt_arg, opt.ipv4_ptr)) + return false; + break; + } + case kMacAddr: { + if (!parse_mac_addr(opt_arg, opt.addr_ptr)) + return false; + break; + } + default: assert(false); break; + } + + return true; +} + +bool opt_parser::parse_opts(int argc, char* const argv[]) { +// vector<char> options; +// options.reserve(short_args_.size() + 1); +// +// unordered_map<char, internal_opt>::const_iterator it; +// for (it = short_args_.begin(); it != short_args_.end(); ++it) { +// options.push_back(it->first); +// if (it->second.type != kBool) +// options.push_back(':'); +// } +// +// options.push_back('\0'); + + vector<option> long_opt; + long_opt.reserve(long_opts_.size() + 1); + + unordered_map<const char *, internal_opt>::const_iterator f; + for (f = long_opts_.begin(); f != long_opts_.end(); ++f) { + option arg = {f->first, f->second.optType == opt_argu ? + optional_argument : required_argument, 0, 0}; + long_opt.push_back(arg); + } + + option end_indicator = {NULL, 0, NULL, 0}; + long_opt.push_back(end_indicator); + int index, ret; + + // NOTE(liuyong): MUST initialize this variable first! + optind = 1; + optarg = NULL; + + while ((ret = getopt_long_only(argc, argv, "", &long_opt[0], + &index)) == 0 || (ret > 0 && ret != '?')) { + if (ret == 0) { // handle long options + const option &opt = long_opt[index]; + const internal_opt &a = long_opts_[opt.name]; + const char *arg = optarg; + if (!arg && optind < argc) { + arg = argv[optind]; + ++optind; + } + + if (!fill_arg(opt.name, a, arg)) { + optind = 0; + return false; + } + }/* else if (short_args_.count(ret) > 0) { // handle short options + const internal_opt &a = short_args_[ret]; + if (!fill_arg(a, optarg)) + return false; + } */ + } + + index = optind; + optind = 0; + + + if (index < argc) { + LOG(ERROR, "Unrecognized option argument: %s", argv[index]); + return false; + } + + if (ret != -1) { + LOG(ERROR, "Unregconized option argument %s", argv[index - 1]); + return false; + } + + return true; +} + +void opt_parser::clear() { +// short_args_.clear(); + long_opts_.clear(); +} + +void opt_parser::print_usage(const char *exec_file, ostream &sout) const { + sout << "Usage: \n " << exec_file << " "; + int fileLength = strlen(exec_file); + typedef unordered_map<const char*, internal_opt>::const_iterator It; + for (It f = long_opts_.begin(); f != long_opts_.end(); ++f) { + sout << "--" << f->first << " "; + const internal_opt &opt = f->second; + const char *opt_arg = NULL; + + switch (opt.type) { + case kBool: continue; + case kInt32: + opt_arg = "INTEGER32"; + break; + case kUInt32: + opt_arg = "UINTEGER32"; + break; + case kInt64: + opt_arg = "INTEGER64"; + break; + case kUInt64: + opt_arg = "UINTEGER64"; + break; + case kDouble: + opt_arg = "DOUBLE"; + break; + case kString: + opt_arg = "STRING"; + break; + case kIPv4: + opt_arg = "ddd.ddd.ddd.ddd"; + break; + case kMacAddr: + opt_arg = "xx:xx:xx:xx:xx:xx"; + break; + default: + assert(false); + break; + } + + sout << opt_arg << " "; + } + + sout << endl; + + for (It f = long_opts_.begin(); f != long_opts_.end(); ++f) { + sout<<" "; + for(int i = 0; i < fileLength; i++) sout<<" "; + sout << "--" << f->first << " ("<<f->second.help<<")"<<endl; + } +} + +} +} + diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/samples/release/bin/recorder b/util/Agora_Recording_SDK_for_Linux_FULL/samples/release/bin/recorder new file mode 100755 index 0000000..c5bec36 Binary files /dev/null and b/util/Agora_Recording_SDK_for_Linux_FULL/samples/release/bin/recorder differ diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/samples/shellUtil.js b/util/Agora_Recording_SDK_for_Linux_FULL/samples/shellUtil.js new file mode 100644 index 0000000..c323868 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/samples/shellUtil.js @@ -0,0 +1,25 @@ +const callfile = require('child_process'); + + +function shell(){ + +} + +shell.prototype.Recording =async(appId,uid,channel)=>{ + try { + let shell =`./RECORD_APP`+ + ` --appId ` +appId + ` --uid ` +uid + ` --channel ` + channel + " --appliteDir \`pwd\`/../bin"; + + let backShell =callfile.exec(shell); + return backShell; + } catch (error) { + throw error + } + +} + + +module.exports = new shell(); \ No newline at end of file diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/tools/ffmpeg.zip b/util/Agora_Recording_SDK_for_Linux_FULL/tools/ffmpeg.zip new file mode 100644 index 0000000..4240701 Binary files /dev/null and b/util/Agora_Recording_SDK_for_Linux_FULL/tools/ffmpeg.zip differ diff --git a/util/Agora_Recording_SDK_for_Linux_FULL/tools/video_convert.py b/util/Agora_Recording_SDK_for_Linux_FULL/tools/video_convert.py new file mode 100644 index 0000000..4870a38 --- /dev/null +++ b/util/Agora_Recording_SDK_for_Linux_FULL/tools/video_convert.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python + +import os +import sys +import glob +import subprocess + +HOME = os.path.dirname(os.path.realpath(__file__)) +pathEnv=os.getenv('PATH') +os.environ['PATH']= "%s" %(HOME) + ":" + pathEnv + + +class AudioClip: + def __init__(self): + self.num = 0 + self.filename = [] + self.start_time = [] + self.end_time = [] + + def put_file(self, name): + if not (name in self.filename): + self.filename.append(name) + self.start_time.append(0.0) + self.end_time.append(0.0) + self.num = self.num + 1 + return self.filename.index(name) + + def max_length(self): + return max(self.end_time) + + def print_filename(self): + str = "" + for i in range(self.num): + str = str + ("-i %s " % self.filename[i]) + return str + + def print_filter(self): + str = "" + allch = "" + for i in range(self.num): + tmp = "[%d]adelay=%d[ad%d];" % ( (i), int(self.start_time[i]*1000)+1, (i)) + allch = allch + ("[ad%d]" % i) + str = str + tmp + str = str + ("%s amix=inputs=%d:dropout_transition=0.5[audio]" % (allch, self.num)) + return str + + def print_audio_info(self, i): + print "Audio Clip %d: %s: start_time=%.3f, end_time=%.3f" % (i, self.filename[i], self.start_time[i], self.end_time[i]) + + def print_ffmpeg(self, output_file): + if self.num > 1: + str = "ffmpeg " + self.print_filename() + str = str + "-filter_complex \"%s\" " % self.print_filter() + str = str + "-map \"[audio]\" -to %f -y %s" % (self.max_length(), output_file) + elif self.num == 1: + str = "ffmpeg -i %s -c:a copy %s" % (self.filename[0], output_file) + else: + str = "" + return str + +class VideoClip: + def __init__(self): + self.num = 0 + self.filename = [] + self.start_time = [] + self.end_time = [] + self.width = [] + self.height = [] + self.audio_file = "" + self.audio_start_time = 0.0 + self.audio_end_time = 0.0 + + def put_file(self, name): + if not (name in self.filename): + self.filename.append(name) + self.start_time.append(0.0) + self.end_time.append(0.0) + self.width.append(0) + self.height.append(0) + self.num = self.num + 1 + return self.filename.index(name) + + def max_resolution(self): + self.max_width = max(self.width) + self.max_height = max(self.height) + return self.max_width, self.max_height + + def max_length(self): + return max(max(self.end_time), self.audio_end_time) + + def audio_delay_needed(self): + return self.audio_file != "" and self.audio_start_time > 0.05 + + def print_filter(self): + if self.audio_delay_needed(): + audio_delay = int(self.audio_start_time*1000) + str = "[0]adelay=%d[audio];" % audio_delay + else: + str = "" + source = "1" + sink = "out2" + for i in range(self.num): + sink = "out%d" % (i+2) + if i == self.num - 1: + sink = "video" + tmp = "[%d]scale=%dx%d,setpts=PTS-STARTPTS+%.3f/TB[scale%d];[%s][scale%d]overlay=eof_action=pass[%s];" % \ + ( (i+2), self.max_width, self.max_height, self.start_time[i], (i+2), source, (i+2), sink ) + str = str + tmp + source = sink + return str[:-1] + + def print_filename(self): + str = "" + for i in range(self.num): + str = str + ("-i %s " % self.filename[i]) + return str + + def print_ffmpeg(self, output_file): + if self.audio_file == "": + str = "ffmpeg -f lavfi -i anullsrc " + else: + str = "ffmpeg -i %s " % self.audio_file + str = str + "-f lavfi -i \"color=black:s=%dx%d:r=15\" " % (self.max_width, self.max_height) + str = str + self.print_filename() + str = str + "-filter_complex \"%s\" " % self.print_filter() + if self.audio_file == "": + map_option = "-map \"[video]\"" + else: + if self.audio_delay_needed(): + map_option = "-map \"[audio]\" -map \"[video]\" -c:a aac" + else: + map_option = "-map 0:a:0 -map \"[video]\" -c:a copy" + str = str + "%s -c:v libx264 -preset veryfast -to %f -y %s" % (map_option, self.max_length(), output_file) + return str + + def print_audio_info(self): + print "Audio Clip: %s: start_time=%.3f, end_time=%.3f" % (self.audio_file, self.audio_start_time, self.audio_end_time) + + def print_video_info(self, i): + print "Video Clip %d: %s: start_time=%.3f, end_time=%.3f, width=%d, height=%d" % \ + (i, self.filename[i], self.start_time[i], self.end_time[i], self.width[i], self.height[i]) + + +if len(sys.argv) <= 1: + print "Usage: python video_convert.py path_of_folder" + quit() + +folder_name = sys.argv[1] +print "Folder name:"+folder_name + +if not os.path.isdir(folder_name): + print "Folder "+folder_name+" does not exit" + quit() + +os.chdir(folder_name) +child_env = os.environ.copy() +all_uid_file = glob.glob("uid_*.txt") + +for uid_file in all_uid_file: + uid = os.path.splitext(uid_file)[0][4:] + print "UID:"+uid + + clip = VideoClip() + audio_clip = AudioClip() + with open(uid_file) as f: + for line in f: + items = line.split(" ") + #audio file + if items[1][-3:] == "aac": + index = audio_clip.put_file(items[1]) + if items[2] == "create": + audio_clip.start_time[index] = float(items[0]) + elif items[2] == "close": + audio_clip.end_time[index] = float(items[0]) + #video file + if items[1][-3:] == "mp4": + index = clip.put_file(items[1]) + if items[2] == "create": + clip.start_time[index] = float(items[0]) + elif items[2] == "info": + clip.start_time[index] = float(items[0]) + clip.width[index] = int(items[3][6:]) + clip.height[index] = int(items[4][7:]) + rotation = int(items[5][9:]) + if rotation == 90 or rotation == 270: + clip.width[index], clip.height[index] = clip.height[index], clip.width[index] + elif items[2] == "close": + clip.end_time[index] = float(items[0]) + #video file + if items[1][-4:] == "webm": + index = clip.put_file(items[1]) + if items[2] == "create": + clip.start_time[index] = float(items[0]) + elif items[2] == "info": + clip.start_time[index] = float(items[0]) + clip.width[index] = int(items[3][6:]) + clip.height[index] = int(items[4][7:]) + rotation = int(items[5][9:]) + if rotation == 90 or rotation == 270: + clip.width[index], clip.height[index] = clip.height[index], clip.width[index] + elif items[2] == "close": + clip.end_time[index] = float(items[0]) + + + clip.print_audio_info() + for i in range(audio_clip.num): + audio_clip.print_audio_info(i) + for i in range(clip.num): + clip.print_video_info(i) + + if audio_clip.num > 1: + print "Generate Audio File" + tmp_audio = uid+"_tmp.m4a" + command = audio_clip.print_ffmpeg(tmp_audio) + clip.audio_file = tmp_audio + clip.audio_start_time = 0.0 + clip.audio_end_time = audio_clip.max_length() + print command + print subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, env=child_env).stdout.read() + elif audio_clip.num == 1: + clip.audio_file = audio_clip.filename[0] + clip.audio_start_time = audio_clip.start_time[0] + clip.audio_end_time = audio_clip.end_time[0] + + if clip.num > 0: + print "Generate MP4 file:" + print "Output resolution:", clip.max_resolution() + output_file = uid+"_av"+".mp4" + #print clip.print_filter() + command = clip.print_ffmpeg(output_file) + else: + tmp_audio = uid+"_tmp.m4a" + output_file = uid+".m4a" + if audio_clip.num > 1: + command = "mv %s %s" % (tmp_audio, output_file) + elif audio_clip.num == 1: + command = "ffmpeg -i %s -c:a copy -y %s" % (clip.audio_file, output_file) + print command + print subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, env=child_env).stdout.read() + print "\n\n" + +#write a convert done file +f = open("convert-done.txt", "w+") +f.close() + +#remove tmp files +os.system('rm -f *_tmp.m4a') + diff --git a/util/inspectMediaChannelKey.js b/util/inspectMediaChannelKey.js index 6e3f965..4bbd7c8 100644 --- a/util/inspectMediaChannelKey.js +++ b/util/inspectMediaChannelKey.js @@ -20,10 +20,6 @@ var hexDecode = function(str) { return new Buffer(str, 'hex'); }; - - - - var Message = function(options) { options.pack = function() { var out = ByteBuf(); @@ -52,10 +48,10 @@ var IN_CHANNEL_PERMISSION = 4; var inspectMediaChannelKey = function async(appID, appCertificate, channelName, unixTs, randomInt, uid, expiredTs) { var rawAppID = hexDecode(appID); - console.log('App ID:\t\t\t ' + rawAppID.toString('hex').toUpperCase()); + // console.log('App ID:\t\t\t ' + rawAppID.toString('hex').toUpperCase()); var rawAppCertificate = hexDecode(appCertificate); - console.log('App Certificate:\t ' + rawAppCertificate.toString('hex').toUpperCase()); + // console.log('App Certificate:\t ' + rawAppCertificate.toString('hex').toUpperCase()); var serviceType = MEDIA_CHANNEL_SERVICE; var extra = null; @@ -120,7 +116,6 @@ var inspectMediaChannelKey = function async(appID, appCertificate, channelName, return that; } var Message = function(options) { - console.log('s生成buffer') options.pack = function() { var out = ByteBuf(); @@ -150,10 +145,10 @@ var inspectMediaChannelKey = function async(appID, appCertificate, channelName, }); var toSign = m.pack(); - console.log("Message to sign:\t " + toSign.toString('hex').toUpperCase()); + // console.log("Message to sign:\t " + toSign.toString('hex').toUpperCase()); var signature = encodeHMac(rawAppCertificate, toSign); - console.log("Signature:\t\t " + signature.toString('hex').toUpperCase()); + // console.log("Signature:\t\t " + signature.toString('hex').toUpperCase()); var DynamicKey5Content = function(options) { options.pack = function() { var out = ByteBuf(); @@ -177,10 +172,10 @@ var inspectMediaChannelKey = function async(appID, appCertificate, channelName, , salt: randomInt , expiredTs: expiredTs , extra: extra}).pack(); - console.log("Content to encode:\t " + content.toString('hex').toUpperCase()); + // console.log("Content to encode:\t " + content.toString('hex').toUpperCase()); var channelKey = version + content.toString('base64'); - console.log("Channel key:\t\t " + channelKey); + // console.log("Channel key:\t\t " + channelKey); return channelKey; };