正在显示
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] |
This file is too large to display.
| 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__ |
This file is too large to display.
| @@ -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(); |
This file is too large to display.
util/Agora_Recording_SDK_for_Linux_FULL/tools/video_convert.py
→
util/Agora_Recording_SDK_for_Linux_FULL 2/tools/video_convert.py
100644 → 100755
| @@ -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 | -} |
不能预览此文件类型
不能预览此文件类型
This file is too large to display.
-
请 注册 或 登录 后发表评论