付智勇

添加agora

[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]
... ...
#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;
};
}
}
... ...
#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
... ...
#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__); \
}
... ...
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}
... ...
#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_;
};
}
}
... ...
#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;
}
... ...
#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;
}
}
}
}
... ...
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
... ...
#!/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')
... ...
... ... @@ -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;
};
... ...