This sample application for the Agora Recording SDK enables recording on your Linux server.
-
Ubuntu 12.04+ x64 or CentOS 6.5+ x64 (CentOS 7+ recommended)
-
GCC 4.4+
-
ARS IP (public IP)
-
1MB+ bandwidth for each simultaneous recording channel
-
Server access for
qos.agoralab.co
Note: If server access is denied, the Agora SDK may fail to transfer the required data.
This section shows you how to prepare and build the sample application.
To build and run the sample application, first obtain an app ID:
- Create a developer account at agora.io. Once you finish the sign-up process, you are redirected to the dashboard.
- In the dashboard tree on the left, navigate to Projects > Project List.
- Save the app ID from the Agora dashboard. The app ID is needed to run the sample application.
To open and build the sample application, first integrate the Agora SDK.
-
Download the Agora Recording SDK for Linux.
-
Unzip the downloaded SDK package.
-
Copy the following files/folders from the downloaded SDK package to the sample application:
SDK Path Sample Application Path bin/AgoraCoreService
fileCopy to bin
folderlibs/librecorder.a
fileCopy to libs
foldersamples/agorasdk
folderCopy to samples
foldersamples/cpp
folderCopy to samples
foldersamples/java
folderCopy to samples
folder -
Replace the following folders in the sample application with the folders from the downloaded SDK package.
include
foldertools
folderbase
folder
-
To add the required
libRecordEngine.a
file, decompress thelibRecordEngine.tar.gz
file in thelibs
directory of your sample application.
-
Ensure the following UDP ports are open. (Required for communication with the Agora servers.)
- 4001
- 4030
- 1080
- 8000
- 25000
-
Ensure local ports are not limited by firewalls. If limitations cannot be avoided, apply the following port settings:
Port type Setting Receiving port 4000 - 41000 Low UDP Port 40000 High UDP Port 41000 -
Navigate to the
samples/cpp
directory. ReplacePATH-TO-PROJECT
with the path to the sample application on your Linux server.cd PATH-TO-PROJECT/samples/cpp
-
Execute the
make
command to create arecord_local
application within the directory.make
-
Run
./record_local
, using the app ID and path to the sample app directory.- Replace
APPID
with the app ID from the earlier step. - Replace
PATH-TO-PROJECT
with the path to the sample app directory on the Linux server.
./recorder_local --appId APPID --uid 0 --channel mychannel --appliteDir PATH-TO-PROJECT/bin/AgoraCoreService
- Replace
Note: To view additional Agora SDK help commands, run cmds
.
cmds ./Record_local
The key code for the sample application is in the main.cpp
file. The most relevant code for the Agora Recording SDK is in the following sections:
Add the C++ libraries for variable definitions and streaming.
Library | Description |
---|---|
<csignal> |
Signal handling library |
<cstdint> |
Defines a set of integral type aliases |
<iostream> |
Defines the standard input/output stream objects |
<sstream> |
Provides string stream classes |
<string> |
Defines string types, character traits, and a set of converting functions |
<vector> |
Defines the vector container class |
<algorithm> |
Defines a collection of functions designed to be used on ranges of elements |
#include <csignal>
#include <cstdint>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
Add the Agora SDK libraries.
Library | Description |
---|---|
IAgoraLinuxSdkCommon.h |
Defines Agora variable types and classes |
IAgoraRecordingEngine.h |
Defines the Agora recording engine class |
base/atomic.h |
Defines the Agora namespace |
base/log.h |
Agora logging library |
base/opt_parser.h |
Agora communication helper library |
agorasdk/AgoraSdk.h |
Agora Recording SDK |
#include "IAgoraLinuxSdkCommon.h"
#include "IAgoraRecordingEngine.h"
#include "base/atomic.h"
#include "base/log.h"
#include "base/opt_parser.h"
#include "agorasdk/AgoraSdk.h"
Define standard and Agora classes, and specify namespaces to use in the code.
using std::string;
using std::cout;
using std::cerr;
using std::endl;
using agora::base::opt_parser;
using agora::linuxsdk::VideoFrame;
using agora::linuxsdk::AudioFrame;
Define global variables to determine signal and service starts and stops.
Variable | Description |
---|---|
g_bSignalStop |
Used to define if a signal is stopped |
g_bSignalStartService |
Used to define if a service has started |
g_bSignalStopService |
Used to define if a service has stopped |
atomic_bool_t g_bSignalStop;
atomic_bool_t g_bSignalStartService;
atomic_bool_t g_bSignalStopService;
The start_service()
and stop_service()
methods update the global g_bSignalStartService
and g_bSignalStopService
variables.
void start_service(int signo) {
(void)signo;
g_bSignalStartService = true;
}
void stop_service(int signo) {
(void)signo;
g_bSignalStopService = true;
}
The main()
function is called when the application initializes.
int main(int argc, char * const argv[]) {
...
}
- Define Variables
- Set the Signal Event Handlers
- Set Up the Parser Object
- Check Configuration Settings
- Set the Recording Configuration
- Set Up the Agora Recorder
Define Agora variables for the Agora SDK engine.
Variable | Description |
---|---|
uid |
User ID |
appId |
App ID |
channelKey |
Channel key |
name |
Channel name |
channelProfile |
Channel profile |
uint32_t uid = 0;
string appId;
string channelKey;
string name;
uint32_t channelProfile = 0;
Define the decryption mode decryptionMode
and decryption key secret
.
string decryptionMode;
string secret;
Define the resolution for the video mix and the idle time limit (in seconds).
string mixResolution("360,640,15,500");
int idleLimitSec=5*60;//300s
Define the paths for the application.
Variable | Description |
---|---|
applitePath |
Application path |
appliteLogPath |
Path to save application logs |
recordFileRootDir |
Directory to save the recording files |
cfgFilePath |
Path to the configuration file |
proxyServer |
IP and port for the proxy server |
string applitePath;
string appliteLogPath;
string recordFileRootDir = "";
string cfgFilePath = "";
string proxyServer;
Define the variables for the low and high UDP ports.
int lowUdpPort = 0;//40000;
int highUdpPort = 0;//40004;
Define the audio, video, and mixing variables for the settings in the Agora parser.
bool isAudioOnly=0;
bool isVideoOnly=0;
bool isMixingEnabled=0;
bool mixedVideoAudio=0;
Define the audio, video, and stream format types.
uint32_t getAudioFrame = agora::linuxsdk::AUDIO_FORMAT_DEFAULT_TYPE;
uint32_t getVideoFrame = agora::linuxsdk::VIDEO_FORMAT_DEFAULT_TYPE;
uint32_t streamType = agora::linuxsdk::REMOTE_VIDEO_STREAM_HIGH;
Define the video snapshot interval captureInterval
(in seconds). Set the trigger mode to automatic.
int captureInterval = 5;
int triggerMode = agora::linuxsdk::AUTOMATICALLY_MODE;
Define the following recording configuration parameters: width, height, frames per second, and KB per second.
int width = 0;
int height = 0;
int fps = 0;
int kbps = 0;
Define signal variables, which track the communication signal of the recording.
g_bSignalStop = false;
g_bSignalStartService = false;
g_bSignalStopService = false;
Use the signal()
method to set signal event handlers.
Event | Description |
---|---|
SIGQUIT |
The signal is terminated. |
SIGABRT |
The signal is aborted. |
SIGINT |
The signal is interrupted. |
SIGPIPE |
Broken pipe signal. Passing SIG_IGN as a handler ignores the broken pipe signal. |
signal(SIGQUIT, signal_handler);
signal(SIGABRT, signal_handler);
signal(SIGINT, signal_handler);
signal(SIGPIPE, SIG_IGN);
Define the parser
object, using the parser.add_long_opt()
method to apply the app ID, user ID, channel name, application path, channel key, and channel profile.
opt_parser parser;
parser.add_long_opt("appId", &appId, "App Id/must", agora::base::opt_parser::require_argu);
parser.add_long_opt("uid", &uid, "User Id default is 0/must", agora::base::opt_parser::require_argu);
parser.add_long_opt("channel", &name, "Channel Id/must", agora::base::opt_parser::require_argu);
parser.add_long_opt("appliteDir", &applitePath, "directory of app lite 'AgoraCoreService', Must pointer to 'Agora_Recording_SDK_for_Linux_FULL/bin/' folder/must",
agora::base::opt_parser::require_argu);
parser.add_long_opt("channelKey", &channelKey, "channelKey/option");
parser.add_long_opt("channelProfile", &channelProfile, "channel_profile:(0:COMMUNICATION),(1:broadcast) default is 0/option");
Set the audio, video, and mix settings.
parser.add_long_opt("isAudioOnly", &isAudioOnly, "Default 0:A/V, 1:AudioOnly (0:1)/option");
parser.add_long_opt("isVideoOnly", &isVideoOnly, "Default 0:A/V, 1:VideoOnly (0:1)/option");
parser.add_long_opt("isMixingEnabled", &isMixingEnabled, "Mixing Enable? (0:1)/option");
parser.add_long_opt("mixResolution", &mixResolution, "change default resolution for vdieo mix mode/option");
parser.add_long_opt("mixedVideoAudio", &mixedVideoAudio, "mixVideoAudio:(0:seperated Audio,Video) (1:mixed Audio & Video), default is 0 /option");
Set the decryption mode and decryption key secret
.
parser.add_long_opt("decryptionMode", &decryptionMode, "decryption Mode, default is NULL/option");
parser.add_long_opt("secret", &secret, "input secret when enable decryptionMode/option");
Apply the idle time limit and root directory for the recording files.
parser.add_long_opt("idle", &idleLimitSec, "Default 300s, should be above 3s/option");
parser.add_long_opt("recordFileRootDir", &recordFileRootDir, "recording file root dir/option");
Set the low and high UDP ports.
parser.add_long_opt("lowUdpPort", &lowUdpPort, "default is random value/option");
parser.add_long_opt("highUdpPort", &highUdpPort, "default is random value/option");
Apply the audio and video frame settings.
parser.add_long_opt("getAudioFrame", &getAudioFrame, "default 0 (0:save as file, 1:aac frame, 2:pcm frame, 3:mixed pcm frame) (Can't combine with isMixingEnabled) /option");
parser.add_long_opt("getVideoFrame", &getVideoFrame, "default 0 (0:save as file, 1:h.264, 2:yuv, 3:jpg buffer, 4:jpg file, 5:jpg file and video file) (Can't combine with isMixingEnabled) /option");
Set the video snapshot interval, configuration file path, and proxy server.
parser.add_long_opt("captureInterval", &captureInterval, "default 5 (Video snapshot interval (second))");
parser.add_long_opt("cfgFilePath", &cfgFilePath, "config file path / option");
parser.add_long_opt("proxyServer", &proxyServer, "proxyServer:format ip:port, eg,\"127.0.0.1:1080\"/option");
Set the video stream type and trigger mode.
parser.add_long_opt("streamType", &streamType, "remote video stream type(0:STREAM_HIGH,1:STREAM_LOW), default is 0/option");
parser.add_long_opt("triggerMode", &triggerMode, "triggerMode:(0: automatically mode, 1: manually mode) default is 0/option");
Ensure the parser
settings, appID
, and channel name
are all valid. If any of these are invalid, terminate the application.
if (!parser.parse_opts(argc, argv) || appId.empty() || name.empty()) {
std::ostringstream sout;
parser.print_usage(argv[0], sout);
cout<<sout.str()<<endl;
return -1;
}
If the trigger mode is set to manual, add additional signal event listeners to start and stop the service.
if(triggerMode == agora::linuxsdk::MANUALLY_MODE) {
signal(SIGUSR1, start_service);
signal(SIGUSR2, stop_service);
}
Check if the recording file directory and configuration file path are empty:
If the directories are not empty, log an error using the LOG()
method and terminate the application.
if(!recordFileRootDir.empty() && !cfgFilePath.empty()){
LOG(ERROR,"Client can't set both recordFileRootDir and cfgFilePath");
return -1;
}
If the directories are empty and mixing is enabled, set the mix resolution. If the resolution is set to a valid resolution, terminate the application.
if(recordFileRootDir.empty() && cfgFilePath.empty())
recordFileRootDir = ".";
//Once recording video under video mixing model, client needs to config width, height, fps and kbps
if(isMixingEnabled && !isAudioOnly) {
if(4 != sscanf(mixResolution.c_str(), "%d,%d,%d,%d", &width,
&height, &fps, &kbps)) {
LOG(ERROR, "Illegal resolution: %s", mixResolution.c_str());
return -1;
}
}
Add a log to track the users that join the channel, and define the Agora SDK recorder
and configuration.
LOG(INFO, "uid %" PRIu32 " from vendor %s is joining channel %s",
uid, appId.c_str(), name.c_str());
agora::AgoraSdk recorder;
agora::recording::RecordingConfig config;
Set the idle time limit and channel profile.
config.idleLimitSec = idleLimitSec;
config.channelProfile = static_cast<agora::linuxsdk::CHANNEL_PROFILE_TYPE>(channelProfile);
Apply the video, audio, and mix settings.
config.isVideoOnly = isVideoOnly;
config.isAudioOnly = isAudioOnly;
config.isMixingEnabled = isMixingEnabled;
config.mixResolution = (isMixingEnabled && !isAudioOnly)? const_cast<char*>(mixResolution.c_str()):NULL;
config.mixedVideoAudio = mixedVideoAudio;
Set the application directory, recording file directory, configuration file path, and proxy server.
config.appliteDir = const_cast<char*>(applitePath.c_str());
config.recordFileRootDir = const_cast<char*>(recordFileRootDir.c_str());
config.cfgFilePath = const_cast<char*>(cfgFilePath.c_str());
config.proxyServer = proxyServer.empty()? NULL:const_cast<char*>(proxyServer.c_str());
Set the decryption mode and decryption key secret
.
config.secret = secret.empty()? NULL:const_cast<char*>(secret.c_str());
config.decryptionMode = decryptionMode.empty()? NULL:const_cast<char*>(decryptionMode.c_str());
Set the low and high UDP ports and the video capture interval.
config.lowUdpPort = lowUdpPort;
config.highUdpPort = highUdpPort;
config.captureInterval = captureInterval;
Set the audio, video, and stream format types and the trigger mode.
config.decodeAudio = static_cast<agora::linuxsdk::AUDIO_FORMAT_TYPE>(getAudioFrame);
config.decodeVideo = static_cast<agora::linuxsdk::VIDEO_FORMAT_TYPE>(getVideoFrame);
config.streamType = static_cast<agora::linuxsdk::REMOTE_VIDEO_STREAM_TYPE>(streamType);
config.triggerMode = static_cast<agora::linuxsdk::TRIGGER_MODE_TYPE>(triggerMode);
Set the mix mode for the recorder
and create an Agora video channel using the recorder.createChannel()
method. If the channel creation fails, terminate the application.
recorder.updateMixModeSetting(width, height, isMixingEnabled ? !isAudioOnly:false);
if (!recorder.createChannel(appId, channelKey, name, uid, config)) {
cerr << "Failed to create agora channel: " << name << endl;
return -1;
}
Update the recording storage directory.
cout << "Recording directory is " << recorder.getRecorderProperties()->storageDir << endl;
recorder.updateStorageDir(recorder.getRecorderProperties()->storageDir);
While the recorder
is running, update the signal service using recorder.startService()
and recorder.stopService()
.
while (!recorder.stopped() && !g_bSignalStop) {
if(g_bSignalStartService) {
recorder.startService();
g_bSignalStartService = false;
}
if(g_bSignalStopService) {
recorder.stopService();
g_bSignalStopService = false;
}
sleep(1);
}
Once the signal stops, leave the channel using recorder.leaveChannel()
and release the recorder
object.
if (g_bSignalStop) {
recorder.leaveChannel();
recorder.release();
}
cerr << "Stopped \n";
return 0;
- See full API documentation in the Document Center
- File bugs about this sample
- See detailed Agora Linux Recording guides
This software is licensed under the MIT License (MIT). View the license.