付智勇

声网SDK更新版本

正在显示 21 个修改的文件 包含 912 行增加332 行删除
  1 +[repoFolder:AgoraRTCEngine, branch:release/1.14.0, commit:006e78c2faf6b8e940b1cf5f77b68f7237fc72b0]
  2 +[repoFolder:media_sdk2, branch:release/1.14.0, commit:ada1b263b3ad653865adda54a0adaed027a362b8]
  3 +[repoFolder:ServerSDK-Video, branch:release/1.14.0, commit:79be5171ac5731ebb060e5e72512335ddb545ace]
  1 +#pragma once
  2 +
  3 +namespace agora {
  4 +namespace recording {
  5 +
  6 +typedef unsigned char uchar_t;
  7 +typedef unsigned int uint_t;
  8 +typedef unsigned int uid_t;
  9 +
  10 +enum ERROR_CODE_TYPE {
  11 + ERR_OK = 0,
  12 + //1~1000
  13 + ERR_FAILED = 1,
  14 + ERR_INVALID_ARGUMENT = 2,
  15 + ERR_INTERNAL_FAILED = 3,
  16 +};
  17 +
  18 +enum STAT_CODE_TYPE {
  19 + STAT_OK = 0,
  20 + STAT_ERR_FROM_ENGINE = 1,
  21 + STAT_ERR_ARS_JOIN_CHANNEL = 2,
  22 + STAT_ERR_CREATE_PROCESS = 3,
  23 + STAT_ERR_MIXED_INVALID_VIDEO_PARAM = 4,
  24 +
  25 + STAT_POLL_ERR = 0x8,
  26 + STAT_POLL_HANG_UP = 0x10,
  27 + STAT_POLL_NVAL = 0x20,
  28 +};
  29 +
  30 +enum LEAVE_PATH_CODE {
  31 + LEAVE_CODE_INIT = 0,
  32 + LEAVE_CODE_SIG = 1<<1,
  33 + LEAVE_CODE_NO_USERS = 1<<2,
  34 + LEAVE_CODE_TIMER_CATCH = 1<<3,
  35 + LEAVE_CODE_CLIENT_LEAVE = 1 << 4,
  36 +};
  37 +
  38 +
  39 +enum WARN_CODE_TYPE {
  40 + WARN_NO_AVAILABLE_CHANNEL = 103,
  41 + WARN_LOOKUP_CHANNEL_TIMEOUT = 104,
  42 + WARN_LOOKUP_CHANNEL_REJECTED = 105,
  43 + WARN_OPEN_CHANNEL_TIMEOUT = 106,
  44 + WARN_OPEN_CHANNEL_REJECTED = 107,
  45 +};
  46 +
  47 +enum CHANNEL_PROFILE_TYPE
  48 +{
  49 + CHANNEL_PROFILE_COMMUNICATION = 0,
  50 + CHANNEL_PROFILE_LIVE_BROADCASTING = 1,
  51 +};
  52 +
  53 +enum USER_OFFLINE_REASON_TYPE
  54 +{
  55 + USER_OFFLINE_QUIT = 0,
  56 + USER_OFFLINE_DROPPED = 1,
  57 + USER_OFFLINE_BECOME_AUDIENCE = 2,
  58 +};
  59 +
  60 +enum REMOTE_VIDEO_STREAM_TYPE
  61 +{
  62 + REMOTE_VIDEO_STREAM_HIGH = 0,
  63 + REMOTE_VIDEO_STREAM_LOW = 1,
  64 +};
  65 +
  66 +enum AUDIO_FRAME_TYPE {
  67 + AUDIO_FRAME_RAW_PCM = 0,
  68 + AUDIO_FRAME_AAC = 1,
  69 +};
  70 +
  71 +enum VIDEO_FRAME_TYPE {
  72 + VIDEO_FRAME_RAW_YUV = 0,
  73 + VIDEO_FRAME_H264 = 1,
  74 +};
  75 +
  76 +class AudioPcmFrame {
  77 + friend class RecordingEngineImpl;
  78 + public:
  79 + AudioPcmFrame(uint_t frame_ms, uint_t sample_rates, uint_t samples);
  80 + ~AudioPcmFrame();
  81 + public:
  82 + uint_t frame_ms_;
  83 + uint_t channels_; // 1
  84 + uint_t sample_bits_; // 16
  85 + uint_t sample_rates_; // 8k, 16k, 32k
  86 + uint_t samples_;
  87 +
  88 + uchar_t *pcmBuf_;
  89 + uint_t pcmBufSize_;
  90 +
  91 + private:
  92 + std::string buf_; // samples * sample_bits_ / CHAR_BIT * channels_
  93 +};
  94 +
  95 +class AudioAacFrame {
  96 + public:
  97 + explicit AudioAacFrame(uint_t frame_ms);
  98 + ~AudioAacFrame();
  99 + public:
  100 + uint_t frame_ms_;
  101 + std::string buf_;
  102 +};
  103 +
  104 +struct AudioFrame {
  105 + AUDIO_FRAME_TYPE type;
  106 + union {
  107 + AudioPcmFrame *pcm;
  108 + AudioAacFrame *aac;
  109 + } frame;
  110 +};
  111 +
  112 +class VideoYuvFrame {
  113 + friend class RecordingEngineImpl;
  114 +public:
  115 + VideoYuvFrame(uint_t frame_ms, uint_t width, uint_t height, uint_t ystride,
  116 + uint_t ustride, uint_t vstride);
  117 + ~VideoYuvFrame();
  118 +
  119 + uint_t frame_ms_;
  120 +
  121 + uchar_t *ybuf_;
  122 + uchar_t *ubuf_;
  123 + uchar_t *vbuf_;
  124 +
  125 + uint_t width_;
  126 + uint_t height_;
  127 +
  128 + uint_t ystride_;
  129 + uint_t ustride_;
  130 + uint_t vstride_;
  131 +
  132 + //all
  133 + uchar_t *buf_;
  134 + uint_t bufSize_;
  135 +
  136 +private:
  137 + std::string data_;
  138 +};
  139 +
  140 +struct VideoH264Frame {
  141 + friend class RecordingEngineImpl;
  142 +public:
  143 + uint_t frame_ms;
  144 + uint_t frame_num;
  145 +
  146 + //all
  147 + uchar_t *buf_;
  148 + uint_t bufSize_;
  149 +
  150 +private:
  151 + std::string payload;
  152 +};
  153 +
  154 +struct VideoFrame {
  155 + VIDEO_FRAME_TYPE type;
  156 + union {
  157 + VideoYuvFrame *yuv;
  158 + VideoH264Frame *h264;
  159 + } frame;
  160 +
  161 + int rotation; // 0, 90, 180, 270
  162 +};
  163 +
  164 +
  165 +
  166 +typedef struct VideoMixingLayout
  167 +{
  168 + struct Region {
  169 + uid_t uid;
  170 + double x;//[0,1]
  171 + double y;//[0,1]
  172 + double width;//[0,1]
  173 + double height;//[0,1]
  174 + int zOrder; //optional, [0, 100] //0 (default): bottom most, 100: top most
  175 +
  176 + // Optional
  177 + // [0, 1.0] where 0 denotes throughly transparent, 1.0 opaque
  178 + double alpha;
  179 +
  180 + int renderMode;//RENDER_MODE_HIDDEN: Crop, RENDER_MODE_FIT: Zoom to fit
  181 + Region()
  182 + :uid(0)
  183 + , x(0)
  184 + , y(0)
  185 + , width(0)
  186 + , height(0)
  187 + , zOrder(0)
  188 + , alpha(1.0)
  189 + , renderMode(1)
  190 + {}
  191 +
  192 + };
  193 + int canvasWidth;
  194 + int canvasHeight;
  195 + const char* backgroundColor;//e.g. "#C0C0C0" in RGB
  196 + int regionCount;
  197 + const Region* regions;
  198 + const char* appData;
  199 + int appDataLength;
  200 + VideoMixingLayout()
  201 + :canvasWidth(0)
  202 + , canvasHeight(0)
  203 + , backgroundColor(NULL)
  204 + , regionCount(0)
  205 + , regions(NULL)
  206 + , appData(NULL)
  207 + , appDataLength(0)
  208 + {}
  209 +} VideoMixingLayout;
  210 +
  211 +typedef struct UserJoinInfos {
  212 + const char* recordingDir;
  213 + //new attached info add below
  214 +
  215 + UserJoinInfos():
  216 + recordingDir(NULL)
  217 + {}
  218 +}UserJoinInfos;
  219 +
  220 +
  221 +class IRecordingEngineEventHandler {
  222 +public:
  223 + virtual ~IRecordingEngineEventHandler() {}
  224 +
  225 + /**
  226 + * Callback when an error occurred during the runtime of recording engine
  227 + *
  228 + *
  229 + * @param error Error code
  230 + * @param stat_code state code
  231 + *
  232 + */
  233 + virtual void onError(int error, STAT_CODE_TYPE stat_code) = 0;
  234 +
  235 + /**
  236 + * Callback when an warning occurred during the runtime of recording engine
  237 + *
  238 + *
  239 + * @param warn warning code
  240 + *
  241 + */
  242 + virtual void onWarning(int warn) = 0;
  243 +
  244 + /**
  245 + * Callback when the user hase successfully joined the specified channel
  246 + *
  247 + *
  248 + * @param channelID channel ID
  249 + * @param uid User ID
  250 + *
  251 + */
  252 + virtual void onJoinChannelSuccess(const char * channelId, uid_t uid) = 0;
  253 +
  254 + /**
  255 + * Callback when recording application successfully left the channel
  256 + *
  257 + *
  258 + * @param code leave path code
  259 + *
  260 + */
  261 + virtual void onLeaveChannel(LEAVE_PATH_CODE code) = 0;
  262 +
  263 + /**
  264 + * Callback when another user successfully joined the channel
  265 + *
  266 + *
  267 + * @param uid user ID
  268 + * @param infos user join information
  269 + *
  270 + */
  271 + virtual void onUserJoined(uid_t uid, UserJoinInfos &infos) = 0;
  272 +
  273 + /**
  274 + * Callback when a user left the channel or gone offline
  275 + *
  276 + *
  277 + * @param uid user ID
  278 + * @param reason offline reason
  279 + *
  280 + */
  281 + virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) = 0;
  282 +
  283 + /**
  284 + * Callback when received a audio frame
  285 + *
  286 + *
  287 + * @param uid user ID
  288 + * @param frame pointer to received audio frame
  289 + *
  290 + */
  291 + virtual void audioFrameReceived(unsigned int uid, const AudioFrame *frame) const = 0;
  292 +
  293 + /**
  294 + * Callback when received a video frame
  295 + *
  296 + *
  297 + * @param uid user ID
  298 + * @param frame pointer to received video frame
  299 + *
  300 + */
  301 + virtual void videoFrameReceived(unsigned int uid, const VideoFrame *frame) const = 0;
  302 +};
  303 +
  304 +typedef struct RecordingConfig {
  305 + bool isAudioOnly;
  306 + bool isMixingEnabled;
  307 + bool mixedVideoAudio;
  308 + char * mixResolution;
  309 + char * decryptionMode;
  310 + char * secret;
  311 + char * appliteDir;
  312 + char * recordFileRootDir;
  313 + char * cfgFilePath;
  314 + bool decodeAudio;
  315 + bool decodeVideo;
  316 + int lowUdpPort;
  317 + int highUdpPort;
  318 + int idleLimitSec;
  319 + CHANNEL_PROFILE_TYPE channelProfile;
  320 +
  321 + RecordingConfig(): channelProfile(CHANNEL_PROFILE_COMMUNICATION),
  322 + isAudioOnly(false),
  323 + isMixingEnabled(false),
  324 + mixResolution(NULL),
  325 + decryptionMode(NULL),
  326 + secret(NULL),
  327 + idleLimitSec(300),
  328 + appliteDir(NULL),
  329 + recordFileRootDir(NULL),
  330 + cfgFilePath(NULL),
  331 + lowUdpPort(0),
  332 + highUdpPort(0),
  333 + decodeAudio(false),
  334 + decodeVideo(false),
  335 + mixedVideoAudio(false)
  336 + {}
  337 +} RecordingConfig;
  338 +
  339 +typedef struct RecordingEngineProperties {
  340 + char* recordingDir;
  341 + RecordingEngineProperties(): recordingDir(NULL)
  342 + {}
  343 +}RecordingEngineProperties;
  344 +
  345 +class IRecordingEngine{
  346 +public:
  347 +
  348 + /**
  349 + * create a new recording engine instance
  350 + *
  351 + * @param appId The App ID issued to the application developers by Agora.io.
  352 + * @param eventHandler the callback interface
  353 + *
  354 + * @return a recording engine instance pointer
  355 + */
  356 + static IRecordingEngine* createAgoraRecordingEngine(const char * appId, IRecordingEngineEventHandler *eventHandler);
  357 +
  358 + virtual ~IRecordingEngine() {}
  359 +
  360 + /**
  361 + * This method lets the recording engine join a channel, and start recording
  362 + *
  363 + * @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
  364 + * @param channelId A string providing the unique channel id for the AgoraRTC session
  365 + * @param uid The uid of recording client
  366 + * @param config The config of current recording
  367 + *
  368 + * @return 0: Method call succeeded. <0: Method call failed.
  369 + */
  370 + virtual int joinChannel(const char * channelKey, const char *channelId, uid_t uid, const RecordingConfig &config) = 0;
  371 +
  372 + /**
  373 + * set the layout of video mixing
  374 + *
  375 + * @param layout layout setting
  376 + *
  377 + * @return 0: Method call succeeded. <0: Method call failed.
  378 + */
  379 + virtual int setVideoMixingLayout(const VideoMixingLayout &layout) = 0;
  380 +
  381 + /**
  382 + * Stop recording
  383 + *
  384 + * @return 0: Method call succeeded. <0: Method call failed.
  385 + */
  386 + virtual int leaveChannel() = 0;
  387 +
  388 + /**
  389 + * release recording engine
  390 + *
  391 + * @return 0: Method call succeeded. <0: Method call failed.
  392 + */
  393 + virtual int release() = 0;
  394 +
  395 + /**
  396 + * Get recording properties
  397 + */
  398 + virtual const RecordingEngineProperties* getProperties() = 0;
  399 +};
  400 +
  401 +}
  402 +}
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 #include <sys/types.h> 3 #include <sys/types.h>
4 #include <unistd.h> 4 #include <unistd.h>
  5 +#include <fcntl.h>
5 6
6 #include <cinttypes> 7 #include <cinttypes>
7 #include <cstdint> 8 #include <cstdint>
@@ -9,87 +10,138 @@ @@ -9,87 +10,138 @@
9 #include <stdarg.h> 10 #include <stdarg.h>
10 #include <cassert> 11 #include <cassert>
11 #include <syslog.h> 12 #include <syslog.h>
  13 +#include "base/atomic.h"
  14 +#include "base/mutexer.h"
12 15
13 namespace agora { 16 namespace agora {
14 namespace base { 17 namespace base {
15 18
16 enum log_levels { 19 enum log_levels {
17 - DEBUG_LOG = LOG_DEBUG, /* 7 debug-level messages */  
18 - INFO_LOG = LOG_INFO, /* 6 informational */  
19 - NOTICE_LOG = LOG_NOTICE, /* 5 normal but significant condition */  
20 - WARN_LOG = LOG_WARNING, /* 4 warning conditions */  
21 - ERROR_LOG = LOG_ERR, /* 3 error conditions */  
22 - FATAL_LOG = LOG_CRIT, /* 2 critical conditions */ 20 + DEBUG_LOG = LOG_DEBUG, /* 7 debug-level messages */
  21 + INFO_LOG = LOG_INFO, /* 6 informational */
  22 + NOTICE_LOG = LOG_NOTICE, /* 5 normal but significant condition */
  23 + WARN_LOG = LOG_WARNING, /* 4 warning conditions */
  24 + ERROR_LOG = LOG_ERR, /* 3 error conditions */
  25 + FATAL_LOG = LOG_CRIT, /* 2 critical conditions */
23 }; 26 };
24 27
25 -struct log_config {  
26 - static int enabled_level;  
27 - static uint64_t dropped_count; 28 +enum log_facility {
  29 + CRON_LOG_FCLT = LOG_CRON,
  30 + DAEMON_LOG_FCLT = LOG_DAEMON,
  31 + FTP_LOG_FCLT = LOG_FTP,
  32 + NEWS_LOG_FCLT = LOG_NEWS,
  33 + AUTH_LOG_FCLT = LOG_AUTH, /* DEPRECATED */
  34 + SYSLOG_LOG_FCLT = LOG_SYSLOG,
  35 + USER_LOG_FCLT = LOG_USER,
  36 + UUCP_LOG_FCLT = LOG_UUCP,
  37 +
  38 + LOCAL0_LOG_FCLT = LOG_LOCAL0,
  39 + LOCAL1_LOG_FCLT = LOG_LOCAL1,
  40 + LOCAL2_LOG_FCLT = LOG_LOCAL2,
  41 + LOCAL3_LOG_FCLT = LOG_LOCAL3,
  42 + LOCAL4_LOG_FCLT = LOG_LOCAL4,
  43 + LOCAL5_LOG_FCLT = LOG_LOCAL5,
  44 + LOCAL6_LOG_FCLT = LOG_LOCAL6,
  45 + LOCAL7_LOG_FCLT = LOG_LOCAL7,
  46 +
  47 +};
28 48
29 - static uint32_t drop_cancel;  
30 - const static uint32_t DROP_COUNT = 1000; 49 +#define LOG_FACILITY_MASK LOG_FACMASK
31 50
32 - static inline void enable_debug(bool enabled) {  
33 - if (enabled) {  
34 - log_config::enabled_level = DEBUG_LOG;  
35 - } else {  
36 - log_config::enabled_level = INFO_LOG; 51 +class log_config {
  52 +public:
  53 + static inline void enable_debug(bool enabled) {
  54 + if (enabled) {
  55 + log_config::enabled_level = DEBUG_LOG;
  56 + } else {
  57 + log_config::enabled_level = INFO_LOG;
  58 + }
37 } 59 }
38 - }  
39 60
40 - static bool set_drop_cannel(uint32_t cancel) {  
41 - if (cancel > DROP_COUNT) {  
42 - drop_cancel = DROP_COUNT;  
43 - return false; 61 + static bool set_drop_cannel(uint32_t cancel) {
  62 + if (cancel > DROP_COUNT) {
  63 + drop_cancel = DROP_COUNT;
  64 + return false;
  65 + }
  66 +
  67 + drop_cancel = cancel;
  68 + return true;
44 } 69 }
45 70
46 - drop_cancel = cancel;  
47 - return true;  
48 - } 71 + static inline bool log_enabled(log_levels level) {
  72 + if (level <= enabled_level) {
  73 + return true;
  74 + }
49 75
50 - static inline bool log_enabled(log_levels level) {  
51 - if (level <= enabled_level) {  
52 - return true; 76 + ++dropped_count;
  77 + return (dropped_count % DROP_COUNT < drop_cancel);
53 } 78 }
54 79
55 - ++dropped_count;  
56 - return (dropped_count % DROP_COUNT < drop_cancel);  
57 - } 80 + /**
  81 + * get log_config current facility
  82 + *
  83 + * @return uint32: get log_config current facility .
  84 + */
  85 + static uint32_t getFacility() { return log_config::facility; }
  86 +
  87 + /**
  88 + * change log_config Facility per your specific purpose like agora::base::LOCAL5_LOG_FCLT
  89 + * Default:USER_LOG_FCLT.
  90 + * eg,
  91 + * agora::base::log_config::setFacility(agora::base::USER_LOG_FCLT);
  92 + *
  93 + * @param fac facility setting
  94 + */
  95 + static void setFacility(uint32_t fac) { log_config::facility = fac & LOG_FACILITY_MASK; }
  96 +
  97 + static inline void lock(){ logger_mutex.lock(); }
  98 + static inline void unlock(){ logger_mutex.unlock(); }
  99 + static inline bool trylock(){ return logger_mutex.trylock(); }
  100 +
  101 +private:
  102 + static Mutexer logger_mutex;
  103 + static int enabled_level;
  104 + static uint64_t dropped_count;
  105 +
  106 + static uint32_t drop_cancel;
  107 + const static uint32_t DROP_COUNT = 1000;
  108 + static uint32_t facility;
58 }; 109 };
59 110
60 -inline void open_log() {  
61 - ::openlog(NULL, LOG_PID|LOG_NDELAY, LOG_USER|LOG_DAEMON);  
62 -}  
63 -  
64 -inline void log(log_levels level, const char* format, ...) {  
65 - if (! log_config::log_enabled(level)) {  
66 - return;  
67 - }  
68 111
69 - va_list args;  
70 - va_start(args, format);  
71 - ::vsyslog(level, format, args);  
72 - va_end(args);  
73 -}  
74 112
  113 +void open_log();
75 inline void close_log() { 114 inline void close_log() {
76 - ::closelog(); 115 + ::closelog();
77 } 116 }
  117 +void log_dir(const char* logdir, log_levels level, const char* format, ...);
  118 +void log(log_levels level, const char* format, ...);
  119 +
  120 +
78 121
79 } 122 }
80 } 123 }
81 124
82 #define LOG(level, fmt, ...) log(agora::base::level ## _LOG, \ 125 #define LOG(level, fmt, ...) log(agora::base::level ## _LOG, \
83 - "(%d) %s:%d: " fmt, getpid(), __FILE__, __LINE__, ##__VA_ARGS__) 126 + "(%d) %s:%d: " fmt, getpid(), __FILE__, __LINE__, ##__VA_ARGS__)
  127 +
  128 +#define LOG_DIR(logdir, level, fmt, ...) log_dir(logdir, agora::base::level ## _LOG, \
  129 + "(%d) %s:%d: " fmt, getpid(), __FILE__, __LINE__, ##__VA_ARGS__)
  130 +
  131 +#define LOG_DIR_IF(logdir, level, cond, fmt, ...) \
  132 + if (cond) { \
  133 + log_dir(logdir, agora::base::level ## _LOG, \
  134 + "(%d) %s:%d: " fmt, getpid(), __FILE__, __LINE__, ##__VA_ARGS__); \
  135 + }
84 136
85 #define LOG_IF(level, cond, ...) \ 137 #define LOG_IF(level, cond, ...) \
86 - if (cond) { \  
87 - LOG(level, __VA_ARGS__); \  
88 - } 138 + if (cond) { \
  139 + LOG(level, __VA_ARGS__); \
  140 + }
89 141
90 #define LOG_EVERY_N(level, N, ...) \ 142 #define LOG_EVERY_N(level, N, ...) \
91 - { \ 143 +{ \
92 static unsigned int count = 0; \ 144 static unsigned int count = 0; \
93 if (++count % N == 0) \ 145 if (++count % N == 0) \
94 - LOG(level, __VA_ARGS__); \  
95 - } 146 + LOG(level, __VA_ARGS__); \
  147 +}
  1 +#ifndef __MUTEXER_H__
  2 +#define __MUTEXER_H__
  3 +
  4 +#if defined(_WIN32)
  5 +#include <windows.h>
  6 +#else
  7 +#include <pthread.h>
  8 +#endif
  9 +
  10 +namespace agora {
  11 +namespace base {
  12 +
  13 +class Mutexer
  14 +{
  15 + public:
  16 + Mutexer();
  17 +
  18 + virtual ~Mutexer();
  19 +
  20 + virtual void lock();
  21 + virtual void unlock();
  22 + virtual bool trylock();
  23 +
  24 + private:
  25 +#ifdef _WIN32
  26 + CRITICAL_SECTION crit;
  27 +#else
  28 + pthread_mutex_t mutex_;
  29 +#endif
  30 +};
  31 +
  32 +}
  33 +}
  34 +
  35 +#endif//__MUTEXER_H__
@@ -18,6 +18,20 @@ using std::cerr; @@ -18,6 +18,20 @@ using std::cerr;
18 using std::endl; 18 using std::endl;
19 19
20 using agora::base::opt_parser; 20 using agora::base::opt_parser;
  21 +using agora::recording::VideoFrame;
  22 +using agora::recording::AudioFrame;
  23 +
  24 +struct MixModeSettings {
  25 + int m_height;
  26 + int m_width;
  27 + bool m_videoMix;
  28 + MixModeSettings():
  29 + m_height(0),
  30 + m_width(0),
  31 + m_videoMix(false)
  32 + {};
  33 +};
  34 +
21 35
22 class AgoraRecorder : public agora::recording::IRecordingEngineEventHandler { 36 class AgoraRecorder : public agora::recording::IRecordingEngineEventHandler {
23 public: 37 public:
@@ -26,35 +40,47 @@ class AgoraRecorder : public agora::recording::IRecordingEngineEventHandler { @@ -26,35 +40,47 @@ class AgoraRecorder : public agora::recording::IRecordingEngineEventHandler {
26 ~AgoraRecorder(); 40 ~AgoraRecorder();
27 41
28 bool createChannel(const string &appid, const string &channelKey, const string &name, uid_t uid, 42 bool createChannel(const string &appid, const string &channelKey, const string &name, uid_t uid,
29 - bool decodeAudio, bool decodeVideo, agora::recording::RecordingConfig &config); 43 + agora::recording::RecordingConfig &config);
30 44
31 - int setVideoMixLayout(); 45 + int setVideoMixLayout();
32 46
33 bool leaveChannel(); 47 bool leaveChannel();
34 bool release(); 48 bool release();
35 49
36 bool stopped() const; 50 bool stopped() const;
  51 + inline void updateMixModeSetting(int width, int height, bool isVideoMix) {
  52 + m_mixRes.m_width = width;
  53 + m_mixRes.m_height = height;
  54 + m_mixRes.m_videoMix = isVideoMix;
  55 + }
37 56
  57 + const agora::recording::RecordingEngineProperties* getRecorderProperties();
  58 +
38 private: 59 private:
39 - virtual void onError(int error); 60 + virtual void onError(int error, agora::recording::STAT_CODE_TYPE stat_code);
40 virtual void onWarning(int warn); 61 virtual void onWarning(int warn);
41 62
42 virtual void onJoinChannelSuccess(const char * channelId, uid_t uid); 63 virtual void onJoinChannelSuccess(const char * channelId, uid_t uid);
43 - virtual void onLeaveChannel(); 64 + virtual void onLeaveChannel(agora::recording::LEAVE_PATH_CODE code);
44 65
45 virtual void onUserJoined(uid_t uid, agora::recording::UserJoinInfos &infos); 66 virtual void onUserJoined(uid_t uid, agora::recording::UserJoinInfos &infos);
46 virtual void onUserOffline(uid_t uid, agora::recording::USER_OFFLINE_REASON_TYPE reason); 67 virtual void onUserOffline(uid_t uid, agora::recording::USER_OFFLINE_REASON_TYPE reason);
47 68
  69 + virtual void audioFrameReceived(unsigned int uid, const AudioFrame *frame) const;
  70 + virtual void videoFrameReceived(unsigned int uid, const VideoFrame *frame) const;
48 private: 71 private:
49 atomic_bool_t m_stopped; 72 atomic_bool_t m_stopped;
50 agora::recording::IRecordingEngine *m_recorder; 73 agora::recording::IRecordingEngine *m_recorder;
51 -  
52 std::vector<agora::recording::uid_t> m_peers; 74 std::vector<agora::recording::uid_t> m_peers;
  75 + std::string m_recordingDir;
  76 + std::string m_logdir;
  77 + MixModeSettings m_mixRes;
53 }; 78 };
54 79
55 AgoraRecorder::AgoraRecorder(): IRecordingEngineEventHandler() { 80 AgoraRecorder::AgoraRecorder(): IRecordingEngineEventHandler() {
56 m_recorder = NULL; 81 m_recorder = NULL;
57 m_stopped.store(false); 82 m_stopped.store(false);
  83 + m_recordingDir = "./";
58 } 84 }
59 85
60 AgoraRecorder::~AgoraRecorder() { 86 AgoraRecorder::~AgoraRecorder() {
@@ -78,7 +104,6 @@ bool AgoraRecorder::release() { @@ -78,7 +104,6 @@ bool AgoraRecorder::release() {
78 104
79 bool AgoraRecorder::createChannel(const string &appid, const string &channelKey, const string &name, 105 bool AgoraRecorder::createChannel(const string &appid, const string &channelKey, const string &name,
80 uint32_t uid, 106 uint32_t uid,
81 - bool decodeAudio, bool decodeVideo,  
82 agora::recording::RecordingConfig &config) 107 agora::recording::RecordingConfig &config)
83 { 108 {
84 if ((m_recorder = agora::recording::IRecordingEngine::createAgoraRecordingEngine(appid.c_str(), this)) == NULL) 109 if ((m_recorder = agora::recording::IRecordingEngine::createAgoraRecordingEngine(appid.c_str(), this)) == NULL)
@@ -97,19 +122,22 @@ bool AgoraRecorder::leaveChannel() { @@ -97,19 +122,22 @@ bool AgoraRecorder::leaveChannel() {
97 return true; 122 return true;
98 } 123 }
99 124
  125 +//Customize the layout of video under video mixing model
100 int AgoraRecorder::setVideoMixLayout() 126 int AgoraRecorder::setVideoMixLayout()
101 { 127 {
102 - LOG(INFO, "setVideoMixLayout: user size: %d", m_peers.size()); 128 + if(!m_mixRes.m_videoMix) return 0;
  129 +
  130 + LOG_DIR(m_logdir.c_str(), INFO, "setVideoMixLayout: user size: %d", m_peers.size());
103 131
104 agora::recording::VideoMixingLayout layout; 132 agora::recording::VideoMixingLayout layout;
105 - layout.canvasWidth = 360;  
106 - layout.canvasHeight = 640; 133 + layout.canvasWidth = m_mixRes.m_width;
  134 + layout.canvasHeight = m_mixRes.m_height;
107 layout.backgroundColor = "#23b9dc"; 135 layout.backgroundColor = "#23b9dc";
108 136
109 layout.regionCount = m_peers.size(); 137 layout.regionCount = m_peers.size();
110 138
111 if (!m_peers.empty()) { 139 if (!m_peers.empty()) {
112 - LOG(INFO, "setVideoMixLayout: peers not empty"); 140 + LOG_DIR(m_logdir.c_str(), INFO, "setVideoMixLayout: peers not empty");
113 agora::recording::VideoMixingLayout::Region * regionList = new agora::recording::VideoMixingLayout::Region[m_peers.size()]; 141 agora::recording::VideoMixingLayout::Region * regionList = new agora::recording::VideoMixingLayout::Region[m_peers.size()];
114 142
115 regionList[0].uid = m_peers[0]; 143 regionList[0].uid = m_peers[0];
@@ -121,11 +149,11 @@ int AgoraRecorder::setVideoMixLayout() @@ -121,11 +149,11 @@ int AgoraRecorder::setVideoMixLayout()
121 regionList[0].alpha = 1.f; 149 regionList[0].alpha = 1.f;
122 regionList[0].renderMode = 0; 150 regionList[0].renderMode = 0;
123 151
124 - 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); 152 + LOG_DIR(m_logdir.c_str(), 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);
125 153
126 154
127 - float canvasWidth = 360.0;  
128 - float canvasHeight = 640.0; 155 + float canvasWidth = m_mixRes.m_width;
  156 + float canvasHeight = m_mixRes.m_height;
129 157
130 float viewWidth = 0.3; 158 float viewWidth = 0.3;
131 float viewHEdge = 0.025; 159 float viewHEdge = 0.025;
@@ -150,7 +178,7 @@ int AgoraRecorder::setVideoMixLayout() @@ -150,7 +178,7 @@ int AgoraRecorder::setVideoMixLayout()
150 } 178 }
151 179
152 layout.regions = regionList; 180 layout.regions = regionList;
153 -// 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); 181 +// LOG_DIR(m_logdir.c_str(), 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);
154 } 182 }
155 else { 183 else {
156 layout.regions = NULL; 184 layout.regions = NULL;
@@ -159,8 +187,12 @@ int AgoraRecorder::setVideoMixLayout() @@ -159,8 +187,12 @@ int AgoraRecorder::setVideoMixLayout()
159 m_recorder->setVideoMixingLayout(layout); 187 m_recorder->setVideoMixingLayout(layout);
160 } 188 }
161 189
162 -void AgoraRecorder::onError(int error) {  
163 - cerr << "Error: " << error << endl; 190 +const agora::recording::RecordingEngineProperties* AgoraRecorder::getRecorderProperties(){
  191 + return m_recorder->getProperties();
  192 +}
  193 +
  194 +void AgoraRecorder::onError(int error, agora::recording::STAT_CODE_TYPE stat_code) {
  195 + cerr << "Error: " << error <<",with stat_code:"<< stat_code << endl;
164 leaveChannel(); 196 leaveChannel();
165 } 197 }
166 198
@@ -173,24 +205,88 @@ void AgoraRecorder::onJoinChannelSuccess(const char * channelId, uid_t uid) { @@ -173,24 +205,88 @@ void AgoraRecorder::onJoinChannelSuccess(const char * channelId, uid_t uid) {
173 cout << "join channel Id: " << channelId << ", with uid: " << uid << endl; 205 cout << "join channel Id: " << channelId << ", with uid: " << uid << endl;
174 } 206 }
175 207
176 -void AgoraRecorder::onLeaveChannel() {  
177 - cout << "leave channel" << endl; 208 +void AgoraRecorder::onLeaveChannel(agora::recording::LEAVE_PATH_CODE code) {
  209 + cout << "leave channel with code:" << code << endl;
178 } 210 }
179 211
180 void AgoraRecorder::onUserJoined(unsigned uid, agora::recording::UserJoinInfos &infos) { 212 void AgoraRecorder::onUserJoined(unsigned uid, agora::recording::UserJoinInfos &infos) {
181 cout << "User " << uid << " joined, RecordingDir:" << (infos.recordingDir? infos.recordingDir:"NULL") <<endl; 213 cout << "User " << uid << " joined, RecordingDir:" << (infos.recordingDir? infos.recordingDir:"NULL") <<endl;
182 - m_peers.push_back(uid); 214 + if(infos.recordingDir)
  215 + {
  216 + m_recordingDir = std::string(infos.recordingDir);
  217 + m_logdir = m_recordingDir;
  218 + }
183 219
  220 + m_peers.push_back(uid);
  221 +
  222 + //When the user joined, we can re-layout the canvas
184 setVideoMixLayout(); 223 setVideoMixLayout();
185 } 224 }
186 225
187 void AgoraRecorder::onUserOffline(unsigned uid, agora::recording::USER_OFFLINE_REASON_TYPE reason) { 226 void AgoraRecorder::onUserOffline(unsigned uid, agora::recording::USER_OFFLINE_REASON_TYPE reason) {
188 cout << "User " << uid << " offline, reason: " << reason << endl; 227 cout << "User " << uid << " offline, reason: " << reason << endl;
189 m_peers.erase(std::remove(m_peers.begin(), m_peers.end(), uid), m_peers.end()); 228 m_peers.erase(std::remove(m_peers.begin(), m_peers.end(), uid), m_peers.end());
190 - 229 +
  230 + //When the user is offline, we can re-layout the canvas
191 setVideoMixLayout(); 231 setVideoMixLayout();
192 } 232 }
193 233
  234 +void AgoraRecorder::audioFrameReceived(unsigned int uid, const AudioFrame *pframe) const {
  235 + char uidbuf[65];
  236 + snprintf(uidbuf, sizeof(uidbuf),"%u", uid);
  237 + std::string info_name = m_recordingDir + std::string(uidbuf) /*+ timestamp_per_join_*/ +".pcm";
  238 + FILE *fp = fopen(info_name.c_str(), "a+b");
  239 +
  240 + if (pframe->type == agora::recording::AUDIO_FRAME_RAW_PCM) {
  241 + cout << "User " << uid << ", received a raw PCM frame" << endl;
  242 + //cout << "User " << uid << ", received a raw PCM frame, timestamp: " << f->frame_ms_ << endl;
  243 + agora::recording::AudioPcmFrame *f = pframe->frame.pcm;
  244 + ::fwrite(f->pcmBuf_, 1, f->pcmBufSize_, fp);
  245 +
  246 + } else if (pframe->type == agora::recording::AUDIO_FRAME_AAC) {
  247 + cout << "User " << uid << ", received an AAC frame" << endl;
  248 + }
  249 + ::fclose(fp);
  250 +}
  251 +
  252 +void AgoraRecorder::videoFrameReceived(unsigned int uid, const VideoFrame *pframe) const {
  253 + char uidbuf[65];
  254 + snprintf(uidbuf, sizeof(uidbuf),"%u", uid);
  255 + char * suffix=".vtmp";
  256 + if (pframe->type == agora::recording::VIDEO_FRAME_RAW_YUV) {
  257 + agora::recording::VideoYuvFrame *f = pframe->frame.yuv;
  258 + suffix=".yuv";
  259 +
  260 + cout << "User " << uid << ", received a yuv frame, width: "
  261 + << f->width_ << ", height: " << f->height_ ;
  262 + cout<<",ystide:"<<f->ystride_<< ",ustride:"<<f->ustride_<<",vstride:"<<f->vstride_;
  263 + cout<< endl;
  264 + } else {
  265 + suffix=".h264";
  266 + agora::recording::VideoH264Frame *f = pframe->frame.h264;
  267 +
  268 + cout << "User " << uid << ", received an h264 frame, timestamp: "
  269 + << f->frame_ms << ", frame no: " << f->frame_num << endl;
  270 + }
  271 +
  272 + std::string info_name = m_recordingDir + std::string(uidbuf) /*+ timestamp_per_join_ */+ std::string(suffix);
  273 + FILE *fp = fopen(info_name.c_str(), "a+b");
  274 +
  275 + //store it as file
  276 + if (pframe->type == agora::recording::VIDEO_FRAME_RAW_YUV) {
  277 + agora::recording::VideoYuvFrame *f = pframe->frame.yuv;
  278 + ::fwrite(f->buf_, 1, f->bufSize_, fp);
  279 + }
  280 + else {
  281 + agora::recording::VideoH264Frame *f = pframe->frame.h264;
  282 + ::fwrite(f->buf_, 1, f->bufSize_, fp);
  283 + }
  284 + ::fclose(fp);
  285 +
  286 +}
  287 +
  288 +
  289 +
194 atomic_bool_t g_bSignalStop; 290 atomic_bool_t g_bSignalStop;
195 291
196 void signal_handler(int signo) { 292 void signal_handler(int signo) {
@@ -205,27 +301,38 @@ int main(int argc, char * const argv[]) { @@ -205,27 +301,38 @@ int main(int argc, char * const argv[]) {
205 string appId; 301 string appId;
206 string channelKey; 302 string channelKey;
207 string name; 303 string name;
208 - bool decodeAudio = false;  
209 - bool decodeVideo = false;  
210 -  
211 - uint32_t channelProfile; 304 + uint32_t channelProfile = 0;
212 305
213 string decryptionMode; 306 string decryptionMode;
214 string secret; 307 string secret;
  308 + string mixResolution("360,640,15,500");
215 309
216 - int idleLimitSec=30*60;//30min 310 + int idleLimitSec=5*60;//300s
217 311
218 string applitePath; 312 string applitePath;
219 string appliteLogPath; 313 string appliteLogPath;
220 - string recordFileRootDir="."; 314 + string recordFileRootDir = "";
  315 + string cfgFilePath = "";
221 316
222 int lowUdpPort = 0;//40000; 317 int lowUdpPort = 0;//40000;
223 int highUdpPort = 0;//40004; 318 int highUdpPort = 0;//40004;
224 319
225 bool isAudioOnly=0; 320 bool isAudioOnly=0;
226 bool isMixingEnabled=0; 321 bool isMixingEnabled=0;
227 -  
228 - 322 + bool mixedVideoAudio=0;
  323 +
  324 + bool getAudioFrame = false;
  325 + bool getVideoFrame = false;
  326 + int width = 0;
  327 + int height = 0;
  328 + int fps = 0;
  329 + int kbps = 0;
  330 +
  331 + /**
  332 + * change log_config Facility per your specific purpose like agora::base::LOCAL5_LOG_FCLT
  333 + * Default:USER_LOG_FCLT.
  334 + * agora::base::log_config::setFacility(agora::base::LOCAL5_LOG_FCLT);
  335 + */
229 g_bSignalStop = false; 336 g_bSignalStop = false;
230 signal(SIGQUIT, signal_handler); 337 signal(SIGQUIT, signal_handler);
231 signal(SIGABRT, signal_handler); 338 signal(SIGABRT, signal_handler);
@@ -234,8 +341,6 @@ int main(int argc, char * const argv[]) { @@ -234,8 +341,6 @@ int main(int argc, char * const argv[]) {
234 341
235 opt_parser parser; 342 opt_parser parser;
236 343
237 -  
238 -  
239 parser.add_long_opt("appId", &appId, "App Id/must", agora::base::opt_parser::require_argu); 344 parser.add_long_opt("appId", &appId, "App Id/must", agora::base::opt_parser::require_argu);
240 parser.add_long_opt("uid", &uid, "User Id default is 0/must", agora::base::opt_parser::require_argu); 345 parser.add_long_opt("uid", &uid, "User Id default is 0/must", agora::base::opt_parser::require_argu);
241 346
@@ -248,20 +353,25 @@ int main(int argc, char * const argv[]) { @@ -248,20 +353,25 @@ int main(int argc, char * const argv[]) {
248 353
249 parser.add_long_opt("isAudioOnly", &isAudioOnly, "Default 0:ARS (0:1)/option"); 354 parser.add_long_opt("isAudioOnly", &isAudioOnly, "Default 0:ARS (0:1)/option");
250 parser.add_long_opt("isMixingEnabled", &isMixingEnabled, "Mixing Enable? (0:1)/option"); 355 parser.add_long_opt("isMixingEnabled", &isMixingEnabled, "Mixing Enable? (0:1)/option");
  356 + parser.add_long_opt("mixResolution", &mixResolution, "change default resolution for vdieo mix mode/option");
  357 + parser.add_long_opt("mixedVideoAudio", &mixedVideoAudio, "mixVideoAudio:(0:seperated Audio,Video) (1:mixed Audio & Video), default is 0 /option");
251 358
252 parser.add_long_opt("decryptionMode", &decryptionMode, "decryption Mode, default is NULL/option"); 359 parser.add_long_opt("decryptionMode", &decryptionMode, "decryption Mode, default is NULL/option");
253 parser.add_long_opt("secret", &secret, "input secret when enable decryptionMode/option"); 360 parser.add_long_opt("secret", &secret, "input secret when enable decryptionMode/option");
254 361
255 - parser.add_long_opt("idle", &idleLimitSec, "Default 300s/option"); 362 + parser.add_long_opt("idle", &idleLimitSec, "Default 300s, should be above 3s/option");
256 parser.add_long_opt("recordFileRootDir", &recordFileRootDir, "recording file root dir/option"); 363 parser.add_long_opt("recordFileRootDir", &recordFileRootDir, "recording file root dir/option");
257 364
258 parser.add_long_opt("lowUdpPort", &lowUdpPort, "default is random value/option"); 365 parser.add_long_opt("lowUdpPort", &lowUdpPort, "default is random value/option");
259 parser.add_long_opt("highUdpPort", &highUdpPort, "default is random value/option"); 366 parser.add_long_opt("highUdpPort", &highUdpPort, "default is random value/option");
260 367
  368 + parser.add_long_opt("getAudioFrame", &getAudioFrame, "default 0 (0:save as file, 1:get framebuffer) /option");
  369 + parser.add_long_opt("getVideoFrame", &getVideoFrame, "default 0 (0:save as file, 1:get framebuffer) /option");
  370 + parser.add_long_opt("cfgFilePath", &cfgFilePath, "config file path / option");
261 371
262 if (!parser.parse_opts(argc, argv) || appId.empty() || name.empty()) { 372 if (!parser.parse_opts(argc, argv) || appId.empty() || name.empty()) {
263 std::string usage = "Usage: \n\ 373 std::string usage = "Usage: \n\
264 - ./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 \ 374 + ./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 --cfgFilePath STRING --lowUdpPort INTEGER32 --highUdpPort INTEGER32\n \
265 --appId (App Id/must) \n \ 375 --appId (App Id/must) \n \
266 --uid (User Id default is 0/must) \n \ 376 --uid (User Id default is 0/must) \n \
267 --channel (Channel Id/must) \n \ 377 --channel (Channel Id/must) \n \
@@ -270,16 +380,39 @@ int main(int argc, char * const argv[]) { @@ -270,16 +380,39 @@ int main(int argc, char * const argv[]) {
270 --channelProfile (channel_profile:(0:COMMUNICATION),(1:broadcast) default is 0/option) \n \ 380 --channelProfile (channel_profile:(0:COMMUNICATION),(1:broadcast) default is 0/option) \n \
271 --isAudioOnly (Default 0:ARS (0:1)/option) \n \ 381 --isAudioOnly (Default 0:ARS (0:1)/option) \n \
272 --isMixingEnabled (Mixing Enable? (0:1)/option) \n \ 382 --isMixingEnabled (Mixing Enable? (0:1)/option) \n \
  383 + --mixResolution (format:width,high,fps,kbps 'change default resolution for video mix mode/option') \n \
  384 + --mixedVideoAudio (0:seperated Audio,Video) (1:mixed Audio & Video), default is 0, only for mix mode \n \
273 --decryptionMode (decryption Mode, default is NULL/option) \n \ 385 --decryptionMode (decryption Mode, default is NULL/option) \n \
274 --secret (input secret when enable decryptionMode/option) \n \ 386 --secret (input secret when enable decryptionMode/option) \n \
275 - --idle (Default 300s/option) \n \ 387 + --idle (Default 300s, should be above 3s/option) \n \
276 --recordFileRootDir (recording file root dir/option) \n \ 388 --recordFileRootDir (recording file root dir/option) \n \
277 --lowUdpPort (default is random value/option) \n \ 389 --lowUdpPort (default is random value/option) \n \
278 - --highUdpPort (default is random value/option)"; 390 + --highUdpPort (default is random value/option) \n \
  391 + --getAudioFrame (default 0 (0:save as file, 1:get framebuffer) /option) \n \
  392 + --getVideoFrame (default 0 (0:save as file, 1:get framebuffer) /option)\n \
  393 + --cfgFilePath (config file path/option_)";
279 394
280 std::cerr << usage << std::endl; 395 std::cerr << usage << std::endl;
281 return -1; 396 return -1;
282 } 397 }
  398 +
  399 +
  400 + if(!recordFileRootDir.empty() && !cfgFilePath.empty()){
  401 + LOG(ERROR,"Client can't set both recordFileRootDir and cfgFilePath");
  402 + return -1;
  403 + }
  404 +
  405 + if(recordFileRootDir.empty() && cfgFilePath.empty())
  406 + recordFileRootDir = ".";
  407 +
  408 + //Once recording video under video mixing model, client needs to config width, height, fps and kbps
  409 + if(isMixingEnabled && !isAudioOnly) {
  410 + if(4 != sscanf(mixResolution.c_str(), "%d,%d,%d,%d", &width,
  411 + &height, &fps, &kbps)) {
  412 + LOG(ERROR, "Illegal resolution: %s", mixResolution.c_str());
  413 + return -1;
  414 + }
  415 + }
283 416
284 LOG(INFO, "uid %" PRIu32 " from vendor %s is joining channel %s", 417 LOG(INFO, "uid %" PRIu32 " from vendor %s is joining channel %s",
285 uid, appId.c_str(), name.c_str()); 418 uid, appId.c_str(), name.c_str());
@@ -291,9 +424,12 @@ int main(int argc, char * const argv[]) { @@ -291,9 +424,12 @@ int main(int argc, char * const argv[]) {
291 424
292 config.isAudioOnly = isAudioOnly; 425 config.isAudioOnly = isAudioOnly;
293 config.isMixingEnabled = isMixingEnabled; 426 config.isMixingEnabled = isMixingEnabled;
  427 + config.mixResolution = (isMixingEnabled && !isAudioOnly)? const_cast<char*>(mixResolution.c_str()):NULL;
  428 + config.mixedVideoAudio = mixedVideoAudio;
294 429
295 config.appliteDir = const_cast<char*>(applitePath.c_str()); 430 config.appliteDir = const_cast<char*>(applitePath.c_str());
296 config.recordFileRootDir = const_cast<char*>(recordFileRootDir.c_str()); 431 config.recordFileRootDir = const_cast<char*>(recordFileRootDir.c_str());
  432 + config.cfgFilePath = const_cast<char*>(cfgFilePath.c_str());
297 433
298 config.secret = secret.empty()? NULL:const_cast<char*>(secret.c_str()); 434 config.secret = secret.empty()? NULL:const_cast<char*>(secret.c_str());
299 config.decryptionMode = decryptionMode.empty()? NULL:const_cast<char*>(decryptionMode.c_str()); 435 config.decryptionMode = decryptionMode.empty()? NULL:const_cast<char*>(decryptionMode.c_str());
@@ -301,12 +437,17 @@ int main(int argc, char * const argv[]) { @@ -301,12 +437,17 @@ int main(int argc, char * const argv[]) {
301 config.lowUdpPort = lowUdpPort; 437 config.lowUdpPort = lowUdpPort;
302 config.highUdpPort = highUdpPort; 438 config.highUdpPort = highUdpPort;
303 439
  440 + config.decodeAudio = getAudioFrame;
  441 + config.decodeVideo = getVideoFrame;
  442 + recorder.updateMixModeSetting(width, height, isMixingEnabled ? !isAudioOnly:false);
304 443
305 - if (!recorder.createChannel(appId, channelKey, name, uid, decodeAudio, decodeVideo, config)) { 444 + if (!recorder.createChannel(appId, channelKey, name, uid, config)) {
306 cerr << "Failed to create agora channel: " << name << endl; 445 cerr << "Failed to create agora channel: " << name << endl;
307 return -1; 446 return -1;
308 } 447 }
309 448
  449 + cout << "Recording directory is " << recorder.getRecorderProperties()->recordingDir << endl;
  450 +
310 while (!recorder.stopped() && !g_bSignalStop) { 451 while (!recorder.stopped() && !g_bSignalStop) {
311 sleep(1); 452 sleep(1);
312 } 453 }
  1 +const callfile = require('child_process');
  2 +const recordInfo =require( '../../../model/RecordInfoModel');
  3 +const recordStatus =require( '../../../model/RecordStatusModel')
  4 +const uuid = require('../../UuidUtil');
  5 +const moment = require('moment')
  6 +
  7 +
  8 +function shell(){
  9 +
  10 +}
  11 +
  12 +shell.prototype.Recording =async(appId,uid,channel,channelKey,body)=>{
  13 + // return new Promise((resolve, reject) =>{
  14 + try {
  15 + body.id = uuid.db32()
  16 + body.createTime = new Date().getTime();
  17 + var channelInfo = await recordStatus.findOne({where:{channel:channel}});
  18 + let r = await recordInfo.create(body);
  19 + if(channelInfo){
  20 + if(channelInfo.status == 0){
  21 + var channelInfo = await recordStatus.update({status:1},{where:{channel:channel}});
  22 + }else if(channelInfo.status == 1){
  23 + throw false ;
  24 + }
  25 + }else{
  26 + var channelInfo = await recordStatus.create({id:uuid.db32(),channel:channel,status:1});
  27 + }
  28 + let shell =`cd /netWorkSchool/Agora_Recording_SDK_for_Linux_FULL/samples/ && ./Recorder_local`+
  29 + ` --appId ` +appId +
  30 + //` --uid ` +uid +
  31 + ` --channel ` + channel +
  32 + //` --channelKey ` + channelKey +
  33 + " --appliteDir \`pwd\`/../bin";
  34 + console.log(shell)
  35 +
  36 + await callfile.exec(shell)
  37 + return true
  38 + } catch (error) {
  39 + throw error
  40 + }
  41 +
  42 + // })
  43 +
  44 +}
  45 +
  46 +
  47 +module.exports = new shell();
@@ -17,6 +17,10 @@ class AudioClip: @@ -17,6 +17,10 @@ class AudioClip:
17 self.start_time = [] 17 self.start_time = []
18 self.end_time = [] 18 self.end_time = []
19 19
  20 + def update_audio_info(self, i, stime, etime):
  21 + self.start_time[i] = stime
  22 + self.end_time[i] = etime
  23 +
20 def put_file(self, name): 24 def put_file(self, name):
21 if not (name in self.filename): 25 if not (name in self.filename):
22 self.filename.append(name) 26 self.filename.append(name)
@@ -31,27 +35,24 @@ class AudioClip: @@ -31,27 +35,24 @@ class AudioClip:
31 def print_filename(self): 35 def print_filename(self):
32 str = "" 36 str = ""
33 for i in range(self.num): 37 for i in range(self.num):
  38 + if i > 0:
  39 + len = self.start_time[i] - self.end_time[i-1]
  40 + else:
  41 + len = self.start_time[0]
  42 + if len < 0.001:
  43 + len = 0.001
  44 + str = str + ("-f lavfi -t %.3f -i anullsrc=channel_layout=mono:sample_rate=48000 " % len)
34 str = str + ("-i %s " % self.filename[i]) 45 str = str + ("-i %s " % self.filename[i])
35 return str 46 return str
36 47
37 - def print_filter(self):  
38 - str = ""  
39 - allch = ""  
40 - for i in range(self.num):  
41 - tmp = "[%d]adelay=%d[ad%d];" % ( (i), int(self.start_time[i]*1000)+1, (i))  
42 - allch = allch + ("[ad%d]" % i)  
43 - str = str + tmp  
44 - str = str + ("%s amix=inputs=%d:dropout_transition=0.5[audio]" % (allch, self.num))  
45 - return str  
46 -  
47 def print_audio_info(self, i): 48 def print_audio_info(self, i):
48 print "Audio Clip %d: %s: start_time=%.3f, end_time=%.3f" % (i, self.filename[i], self.start_time[i], self.end_time[i]) 49 print "Audio Clip %d: %s: start_time=%.3f, end_time=%.3f" % (i, self.filename[i], self.start_time[i], self.end_time[i])
49 50
50 def print_ffmpeg(self, output_file): 51 def print_ffmpeg(self, output_file):
51 if self.num > 1: 52 if self.num > 1:
52 str = "ffmpeg " + self.print_filename() 53 str = "ffmpeg " + self.print_filename()
53 - str = str + "-filter_complex \"%s\" " % self.print_filter()  
54 - str = str + "-map \"[audio]\" -to %f -y %s" % (self.max_length(), output_file) 54 + str = str + "-filter_complex \"concat=n=%d:v=0:a=1[audio]\" " % (self.num * 2)
  55 + str = str + " -map \"[audio]\" -to %f -y %s" % (self.max_length(), output_file)
55 elif self.num == 1: 56 elif self.num == 1:
56 str = "ffmpeg -i %s -c:a copy %s" % (self.filename[0], output_file) 57 str = "ffmpeg -i %s -c:a copy %s" % (self.filename[0], output_file)
57 else: 58 else:
@@ -70,6 +71,14 @@ class VideoClip: @@ -70,6 +71,14 @@ class VideoClip:
70 self.audio_start_time = 0.0 71 self.audio_start_time = 0.0
71 self.audio_end_time = 0.0 72 self.audio_end_time = 0.0
72 73
  74 + def update_audio_info(self, audio_stime, audio_etime):
  75 + self.audio_start_time = audio_stime
  76 + self.audio_end_time = audio_etime
  77 +
  78 + def update_video_info(self, i, video_stime, video_etime):
  79 + self.start_time[i] = video_stime
  80 + self.end_time[i] = video_etime
  81 +
73 def put_file(self, name): 82 def put_file(self, name):
74 if not (name in self.filename): 83 if not (name in self.filename):
75 self.filename.append(name) 84 self.filename.append(name)
@@ -130,7 +139,7 @@ class VideoClip: @@ -130,7 +139,7 @@ class VideoClip:
130 map_option = "-map \"[audio]\" -map \"[video]\" -c:a aac" 139 map_option = "-map \"[audio]\" -map \"[video]\" -c:a aac"
131 else: 140 else:
132 map_option = "-map 0:a:0 -map \"[video]\" -c:a copy" 141 map_option = "-map 0:a:0 -map \"[video]\" -c:a copy"
133 - str = str + "%s -c:v libx264 -preset veryfast -to %f -y %s" % (map_option, self.max_length(), output_file) 142 + str = str + " %s -c:v libx264 -preset veryfast -to %f -y %s" % (map_option, self.max_length(), output_file)
134 return str 143 return str
135 144
136 def print_audio_info(self): 145 def print_audio_info(self):
@@ -141,42 +150,37 @@ class VideoClip: @@ -141,42 +150,37 @@ class VideoClip:
141 (i, self.filename[i], self.start_time[i], self.end_time[i], self.width[i], self.height[i]) 150 (i, self.filename[i], self.start_time[i], self.end_time[i], self.width[i], self.height[i])
142 151
143 152
144 -if len(sys.argv) <= 1:  
145 - print "Usage: python video_convert.py path_of_folder"  
146 - quit() 153 +def UidFileConvert(uid_file, suffix, option):
  154 + child_env = os.environ.copy()
147 155
148 -folder_name = sys.argv[1]  
149 -print "Folder name:"+folder_name  
150 -  
151 -if not os.path.isdir(folder_name):  
152 - print "Folder "+folder_name+" does not exit"  
153 - quit()  
154 -  
155 -os.chdir(folder_name)  
156 -child_env = os.environ.copy()  
157 -all_uid_file = glob.glob("uid_*.txt")  
158 -  
159 -for uid_file in all_uid_file:  
160 uid = os.path.splitext(uid_file)[0][4:] 156 uid = os.path.splitext(uid_file)[0][4:]
161 print "UID:"+uid 157 print "UID:"+uid
162 - 158 +
163 clip = VideoClip() 159 clip = VideoClip()
164 audio_clip = AudioClip() 160 audio_clip = AudioClip()
165 with open(uid_file) as f: 161 with open(uid_file) as f:
  162 + av_1st_stime = [False]
166 for line in f: 163 for line in f:
167 items = line.split(" ") 164 items = line.split(" ")
168 #audio file 165 #audio file
169 if items[1][-3:] == "aac": 166 if items[1][-3:] == "aac":
170 index = audio_clip.put_file(items[1]) 167 index = audio_clip.put_file(items[1])
171 if items[2] == "create": 168 if items[2] == "create":
172 - audio_clip.start_time[index] = float(items[0]) 169 + if not av_1st_stime[0]:
  170 + av_1st_stime.append(float(items[0])) #mark it.
  171 +
  172 + audio_clip.start_time[index] = float(items[0])
173 elif items[2] == "close": 173 elif items[2] == "close":
174 - audio_clip.end_time[index] = float(items[0]) 174 + audio_clip.end_time[index] = float(items[0])
  175 +
175 #video file 176 #video file
176 if items[1][-3:] == "mp4": 177 if items[1][-3:] == "mp4":
177 index = clip.put_file(items[1]) 178 index = clip.put_file(items[1])
178 if items[2] == "create": 179 if items[2] == "create":
179 - clip.start_time[index] = float(items[0]) 180 + if not av_1st_stime[0]:
  181 + av_1st_stime.append(float(items[0])) #mark it.
  182 +
  183 + clip.start_time[index] = float(items[0])
180 elif items[2] == "info": 184 elif items[2] == "info":
181 clip.start_time[index] = float(items[0]) 185 clip.start_time[index] = float(items[0])
182 clip.width[index] = int(items[3][6:]) 186 clip.width[index] = int(items[3][6:])
@@ -190,24 +194,37 @@ for uid_file in all_uid_file: @@ -190,24 +194,37 @@ for uid_file in all_uid_file:
190 if items[1][-4:] == "webm": 194 if items[1][-4:] == "webm":
191 index = clip.put_file(items[1]) 195 index = clip.put_file(items[1])
192 if items[2] == "create": 196 if items[2] == "create":
193 - clip.start_time[index] = float(items[0]) 197 + if not av_1st_stime[0]:
  198 + av_1st_stime.append(float(items[0])) #mark it.
  199 + clip.start_time[index] = float(items[0])
194 elif items[2] == "info": 200 elif items[2] == "info":
195 - clip.start_time[index] = float(items[0])  
196 - clip.width[index] = int(items[3][6:])  
197 - clip.height[index] = int(items[4][7:])  
198 - rotation = int(items[5][9:])  
199 - if rotation == 90 or rotation == 270:  
200 - clip.width[index], clip.height[index] = clip.height[index], clip.width[index] 201 + clip.start_time[index] = float(items[0])
  202 + clip.width[index] = int(items[3][6:])
  203 + clip.height[index] = int(items[4][7:])
  204 + rotation = int(items[5][9:])
  205 + if rotation == 90 or rotation == 270:
  206 + clip.width[index], clip.height[index] = clip.height[index], clip.width[index]
201 elif items[2] == "close": 207 elif items[2] == "close":
202 clip.end_time[index] = float(items[0]) 208 clip.end_time[index] = float(items[0])
203 209
204 - 210 + if not option:
  211 + clip.update_audio_info(clip.audio_start_time - av_1st_stime[1],
  212 + clip.audio_end_time - av_1st_stime[1])
  213 +
  214 + for i in range(audio_clip.num):
  215 + audio_clip.update_audio_info(i, audio_clip.start_time[i] - av_1st_stime[1],
  216 + audio_clip.end_time[i] - av_1st_stime[1])
  217 +
  218 + for i in range(clip.num):
  219 + clip.update_video_info(i, clip.start_time[i] - av_1st_stime[1],
  220 + clip.end_time[i] - av_1st_stime[1])
  221 +
205 clip.print_audio_info() 222 clip.print_audio_info()
206 for i in range(audio_clip.num): 223 for i in range(audio_clip.num):
207 audio_clip.print_audio_info(i) 224 audio_clip.print_audio_info(i)
208 for i in range(clip.num): 225 for i in range(clip.num):
209 clip.print_video_info(i) 226 clip.print_video_info(i)
210 - 227 +
211 if audio_clip.num > 1: 228 if audio_clip.num > 1:
212 print "Generate Audio File" 229 print "Generate Audio File"
213 tmp_audio = uid+"_tmp.m4a" 230 tmp_audio = uid+"_tmp.m4a"
@@ -225,7 +242,7 @@ for uid_file in all_uid_file: @@ -225,7 +242,7 @@ for uid_file in all_uid_file:
225 if clip.num > 0: 242 if clip.num > 0:
226 print "Generate MP4 file:" 243 print "Generate MP4 file:"
227 print "Output resolution:", clip.max_resolution() 244 print "Output resolution:", clip.max_resolution()
228 - output_file = uid+"_av"+".mp4" 245 + output_file = uid + suffix + ".mp4"
229 #print clip.print_filter() 246 #print clip.print_filter()
230 command = clip.print_ffmpeg(output_file) 247 command = clip.print_ffmpeg(output_file)
231 else: 248 else:
@@ -239,10 +256,92 @@ for uid_file in all_uid_file: @@ -239,10 +256,92 @@ for uid_file in all_uid_file:
239 print subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, env=child_env).stdout.read() 256 print subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, env=child_env).stdout.read()
240 print "\n\n" 257 print "\n\n"
241 258
242 -#write a convert done file  
243 -f = open("convert-done.txt", "w+")  
244 -f.close() 259 + #remove tmp files
  260 + os.system('rm -f *_tmp.m4a')
  261 + return
  262 +
  263 +class UserInfo:
  264 + def __init__(self, ts, path):
  265 + self.last_ts = ts
  266 + self.path = path
  267 +
  268 +def SessionConvert(folder_name, opt):
  269 + if not os.path.isdir(folder_name):
  270 + print "Folder " + folder_name + " does not exit"
  271 + return
  272 +
  273 + os.chdir(folder_name)
  274 + os.system('rm -f *_merge.txt')
  275 + all_uid_file = sorted(glob.glob("uid_*.txt"))
  276 + if opt == 0:
  277 + for uid_file in all_uid_file:
  278 + UidFileConvert(uid_file, "_av", 0)
  279 + #write a convert done file
  280 + f = open("convert-done.txt", "w")
  281 + f.close()
  282 + return
  283 +
  284 + #merge
  285 + dict_uid = dict()
  286 + for uid_file in all_uid_file:
  287 + uid = uid_file.split("_")[1]
  288 + if not dict_uid.has_key(uid):
  289 + dict_uid[uid] = UserInfo(0.0, "uid_" + uid + "_merge.txt")
245 290
246 -#remove tmp files  
247 -os.system('rm -f *_tmp.m4a') 291 + start_ts = -1.0
  292 + last_ts = 0
  293 + with open(uid_file) as f:
  294 + for line in f:
  295 + items = line.split(" ")
  296 + if opt == 2 and items[1].split(".")[1] != "aac":
  297 + continue
  298 + elif opt == 3 and items[1].split(".")[1] == "aac":
  299 + continue
  300 + if start_ts < 0:
  301 + if float(items[0]) > 0:
  302 + start_ts = float(items[0])
  303 + else:
  304 + start_ts = 0
  305 + items[0] = "%.3f" % (float(items[0]))
  306 + last_ts = float(items[0])
  307 + file = open(dict_uid[uid].path, "a")
  308 + file.write(' '.join(items))
  309 + file.close()
  310 + dict_uid[uid].last_ts = last_ts + 0.1
  311 +
  312 + all_merge_file = glob.glob("*_merge.txt")
  313 + for merge_file in all_merge_file:
  314 + if opt == 1:
  315 + UidFileConvert(merge_file, "_av", opt)
  316 + else:
  317 + UidFileConvert(merge_file, "", opt)
  318 +
  319 + os.system('rm -f *_merge.txt')
  320 +
  321 + #write a convert done file
  322 + f = open("convert-done.txt", "w")
  323 + f.close()
  324 + return
  325 +
  326 +def Usage():
  327 + print "Usage: python video_convert.py session_folder [-m/-am/-vm]"
  328 + quit()
  329 +
  330 +#Entrance
  331 +if len(sys.argv) <= 1:
  332 + Usage()
  333 +
  334 +folder_name = sys.argv[1]
  335 +if len(sys.argv) == 2:
  336 + SessionConvert(folder_name, 0)
  337 +elif sys.argv[2] == "-m": # merge audio and video
  338 + SessionConvert(folder_name, 1)
  339 +elif sys.argv[2] == "-am": # merge audio
  340 + SessionConvert(folder_name, 2)
  341 +elif sys.argv[2] == "-vm": #merge video
  342 + SessionConvert(folder_name, 3)
  343 +else:
  344 + Usage()
  345 +
  346 +
248 347
1 -[repoFolder:AgoraRTCEngine, branch:release/1.12.0, commit:89171d96ae055f7809ad9b455256272eee0a0b43]  
2 -[repoFolder:media_sdk2, branch:release/1.12.0, commit:a442f9afe9b468f3f14930db4f3c01f73de90ea3]  
3 -[repoFolder:ServerSDK-Video, branch:release/1.12.0, commit:a4f0d4c22aa9d93b9cd0f71967d4f4b763e46190]  
1 -#pragma once  
2 -  
3 -namespace agora {  
4 -namespace recording {  
5 -  
6 -typedef unsigned char uchar_t;  
7 -typedef unsigned int uint_t;  
8 -typedef unsigned int uid_t;  
9 -  
10 -enum ERROR_CODE_TYPE {  
11 - ERR_OK = 0,  
12 - //1~1000  
13 - ERR_FAILED = 1,  
14 - ERR_INVALID_ARGUMENT = 2,  
15 - ERR_INTERNAL_FAILED = 3,  
16 -};  
17 -  
18 -enum WARN_CODE_TYPE {  
19 - WARN_NO_AVAILABLE_CHANNEL = 103,  
20 - WARN_LOOKUP_CHANNEL_TIMEOUT = 104,  
21 - WARN_LOOKUP_CHANNEL_REJECTED = 105,  
22 - WARN_OPEN_CHANNEL_TIMEOUT = 106,  
23 - WARN_OPEN_CHANNEL_REJECTED = 107,  
24 -};  
25 -  
26 -enum CHANNEL_PROFILE_TYPE  
27 -{  
28 - CHANNEL_PROFILE_COMMUNICATION = 0,  
29 - CHANNEL_PROFILE_LIVE_BROADCASTING = 1,  
30 -};  
31 -  
32 -enum USER_OFFLINE_REASON_TYPE  
33 -{  
34 - USER_OFFLINE_QUIT = 0,  
35 - USER_OFFLINE_DROPPED = 1,  
36 - USER_OFFLINE_BECOME_AUDIENCE = 2,  
37 -};  
38 -  
39 -enum REMOTE_VIDEO_STREAM_TYPE  
40 -{  
41 - REMOTE_VIDEO_STREAM_HIGH = 0,  
42 - REMOTE_VIDEO_STREAM_LOW = 1,  
43 -};  
44 -  
45 -  
46 -typedef struct VideoMixingLayout  
47 -{  
48 - struct Region {  
49 - uid_t uid;  
50 - double x;//[0,1]  
51 - double y;//[0,1]  
52 - double width;//[0,1]  
53 - double height;//[0,1]  
54 - int zOrder; //optional, [0, 100] //0 (default): bottom most, 100: top most  
55 -  
56 - // Optional  
57 - // [0, 1.0] where 0 denotes throughly transparent, 1.0 opaque  
58 - double alpha;  
59 -  
60 - int renderMode;//RENDER_MODE_HIDDEN: Crop, RENDER_MODE_FIT: Zoom to fit  
61 - Region()  
62 - :uid(0)  
63 - , x(0)  
64 - , y(0)  
65 - , width(0)  
66 - , height(0)  
67 - , zOrder(0)  
68 - , alpha(1.0)  
69 - , renderMode(1)  
70 - {}  
71 -  
72 - };  
73 - int canvasWidth;  
74 - int canvasHeight;  
75 - const char* backgroundColor;//e.g. "#C0C0C0" in RGB  
76 - int regionCount;  
77 - const Region* regions;  
78 - const char* appData;  
79 - int appDataLength;  
80 - VideoMixingLayout()  
81 - :canvasWidth(0)  
82 - , canvasHeight(0)  
83 - , backgroundColor(NULL)  
84 - , regionCount(0)  
85 - , regions(NULL)  
86 - , appData(NULL)  
87 - , appDataLength(0)  
88 - {}  
89 -} VideoMixingLayout;  
90 -  
91 -typedef struct UserJoinInfos {  
92 - const char* recordingDir;  
93 - //new attached info add below  
94 -  
95 - UserJoinInfos():  
96 - recordingDir(NULL)  
97 - {}  
98 -}UserJoinInfos;  
99 -  
100 -  
101 -class IRecordingEngineEventHandler {  
102 -public:  
103 - virtual ~IRecordingEngineEventHandler() {}  
104 -  
105 - virtual void onError(int error) = 0;  
106 - virtual void onWarning(int warn) = 0;  
107 -  
108 - virtual void onJoinChannelSuccess(const char * channelId, uid_t uid) = 0;  
109 - virtual void onLeaveChannel() = 0;  
110 -  
111 - virtual void onUserJoined(uid_t uid, UserJoinInfos &infos) = 0;  
112 - virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) = 0;  
113 -};  
114 -  
115 -typedef struct RecordingConfig {  
116 - CHANNEL_PROFILE_TYPE channelProfile;  
117 - bool isAudioOnly;  
118 - bool isMixingEnabled;  
119 - char * decryptionMode;  
120 - char * secret;  
121 - int idleLimitSec;  
122 - char * appliteDir;  
123 -// char * appliteLogDir;  
124 - char * recordFileRootDir;  
125 - int lowUdpPort;  
126 - int highUdpPort;  
127 -  
128 - RecordingConfig(): channelProfile(CHANNEL_PROFILE_COMMUNICATION),  
129 - isAudioOnly(false),  
130 - isMixingEnabled(false),  
131 - decryptionMode(NULL),  
132 - secret(NULL),  
133 - idleLimitSec(300),  
134 - appliteDir(NULL),  
135 -// appliteLogDir(NULL),  
136 - recordFileRootDir(NULL),  
137 - lowUdpPort(0),  
138 - highUdpPort(0)  
139 - {}  
140 -} RecordingConfig;  
141 -  
142 -class IRecordingEngine{  
143 -public:  
144 -  
145 - /**  
146 - * create a new recording engine instance  
147 - *  
148 - * @param appId The App ID issued to the application developers by Agora.io.  
149 - * @param eventHandler the callback interface  
150 - *  
151 - * @return a recording engine instance pointer  
152 - */  
153 - static IRecordingEngine* createAgoraRecordingEngine(const char * appId, IRecordingEngineEventHandler *eventHandler);  
154 -  
155 - virtual ~IRecordingEngine() {}  
156 -  
157 - /**  
158 - * This method lets the recording engine join a channel, and start recording  
159 - *  
160 - * @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  
161 - * @param channelId A string providing the unique channel id for the AgoraRTC session  
162 - * @param uid The uid of recording client  
163 - * @param config The config of current recording  
164 - *  
165 - * @return 0: Method call succeeded. <0: Method call failed.  
166 - */  
167 - virtual int joinChannel(const char * channelKey, const char *channelId, uid_t uid, const RecordingConfig &config) = 0;  
168 -  
169 -  
170 -  
171 - /**  
172 - * set the layout of video mixing  
173 - *  
174 - * @param layout layout setting  
175 - *  
176 - * @return 0: Method call succeeded. <0: Method call failed.  
177 - */  
178 - virtual int setVideoMixingLayout(const VideoMixingLayout &layout) = 0;  
179 -  
180 - /**  
181 - * Stop recording  
182 - *  
183 - * @return 0: Method call succeeded. <0: Method call failed.  
184 - */  
185 - virtual int leaveChannel() = 0;  
186 -  
187 - /**  
188 - * release recording engine  
189 - *  
190 - * @return 0: Method call succeeded. <0: Method call failed.  
191 - */  
192 - virtual int release() = 0;  
193 -};  
194 -  
195 -}  
196 -}