mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-25 12:11:36 +08:00
tab统一替换为4个空格键:#242
This commit is contained in:
parent
2a0d78fd12
commit
1168174c2b
@ -1 +1 @@
|
|||||||
Subproject commit 34b42499ce9ee055b5c7cac9a270239984d0e839
|
Subproject commit d985d49f28b441216bf50020b7c6587edb5f43dc
|
@ -31,12 +31,12 @@
|
|||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#if defined(MediaKitApi_EXPORTS)
|
#if defined(MediaKitApi_EXPORTS)
|
||||||
#define API_EXPORT __declspec(dllexport)
|
#define API_EXPORT __declspec(dllexport)
|
||||||
#else
|
#else
|
||||||
#define API_EXPORT __declspec(dllimport)
|
#define API_EXPORT __declspec(dllimport)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define API_CALL __cdecl
|
#define API_CALL __cdecl
|
||||||
#else
|
#else
|
||||||
#define API_EXPORT
|
#define API_EXPORT
|
||||||
#define API_CALL
|
#define API_CALL
|
||||||
|
@ -32,41 +32,41 @@ using namespace toolkit;
|
|||||||
using namespace mediakit;
|
using namespace mediakit;
|
||||||
|
|
||||||
API_EXPORT mk_player API_CALL mk_player_create() {
|
API_EXPORT mk_player API_CALL mk_player_create() {
|
||||||
MediaPlayer::Ptr *obj = new MediaPlayer::Ptr(new MediaPlayer());
|
MediaPlayer::Ptr *obj = new MediaPlayer::Ptr(new MediaPlayer());
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
API_EXPORT void API_CALL mk_player_release(mk_player ctx) {
|
API_EXPORT void API_CALL mk_player_release(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr *obj = (MediaPlayer::Ptr *)ctx;
|
MediaPlayer::Ptr *obj = (MediaPlayer::Ptr *)ctx;
|
||||||
delete obj;
|
delete obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT void API_CALL mk_player_set_option(mk_player ctx,const char* key,const char *val){
|
API_EXPORT void API_CALL mk_player_set_option(mk_player ctx,const char* key,const char *val){
|
||||||
assert(ctx && key && val);
|
assert(ctx && key && val);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
string key_str(key), val_str(val);
|
string key_str(key), val_str(val);
|
||||||
player->getPoller()->async([key_str,val_str,player](){
|
player->getPoller()->async([key_str,val_str,player](){
|
||||||
//切换线程后再操作
|
//切换线程后再操作
|
||||||
(*player)[key_str] = val_str;
|
(*player)[key_str] = val_str;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
API_EXPORT void API_CALL mk_player_play(mk_player ctx, const char *url) {
|
API_EXPORT void API_CALL mk_player_play(mk_player ctx, const char *url) {
|
||||||
assert(ctx && url);
|
assert(ctx && url);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
string url_str(url);
|
string url_str(url);
|
||||||
player->getPoller()->async([url_str,player](){
|
player->getPoller()->async([url_str,player](){
|
||||||
//切换线程后再操作
|
//切换线程后再操作
|
||||||
player->play(url_str);
|
player->play(url_str);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT void API_CALL mk_player_pause(mk_player ctx, int pause) {
|
API_EXPORT void API_CALL mk_player_pause(mk_player ctx, int pause) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
player->getPoller()->async([pause,player](){
|
player->getPoller()->async([pause,player](){
|
||||||
//切换线程后再操作
|
//切换线程后再操作
|
||||||
player->pause(pause);
|
player->pause(pause);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT void API_CALL mk_player_seekto(mk_player ctx, float progress) {
|
API_EXPORT void API_CALL mk_player_seekto(mk_player ctx, float progress) {
|
||||||
@ -79,100 +79,100 @@ API_EXPORT void API_CALL mk_player_seekto(mk_player ctx, float progress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void mk_player_set_on_event(mk_player ctx, on_mk_play_event cb, void *user_data, int type) {
|
static void mk_player_set_on_event(mk_player ctx, on_mk_play_event cb, void *user_data, int type) {
|
||||||
assert(ctx && cb);
|
assert(ctx && cb);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
player->getPoller()->async([cb,user_data,type,player](){
|
player->getPoller()->async([cb,user_data,type,player](){
|
||||||
//切换线程后再操作
|
//切换线程后再操作
|
||||||
if(type == 0){
|
if(type == 0){
|
||||||
player->setOnPlayResult([cb,user_data](const SockException &ex){
|
player->setOnPlayResult([cb,user_data](const SockException &ex){
|
||||||
cb(user_data,ex.getErrCode(),ex.what());
|
cb(user_data,ex.getErrCode(),ex.what());
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
player->setOnShutdown([cb,user_data](const SockException &ex){
|
player->setOnShutdown([cb,user_data](const SockException &ex){
|
||||||
cb(user_data,ex.getErrCode(),ex.what());
|
cb(user_data,ex.getErrCode(),ex.what());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT void API_CALL mk_player_set_on_result(mk_player ctx, on_mk_play_event cb, void *user_data) {
|
API_EXPORT void API_CALL mk_player_set_on_result(mk_player ctx, on_mk_play_event cb, void *user_data) {
|
||||||
mk_player_set_on_event(ctx,cb,user_data,0);
|
mk_player_set_on_event(ctx,cb,user_data,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT void API_CALL mk_player_set_on_shutdown(mk_player ctx, on_mk_play_event cb, void *user_data) {
|
API_EXPORT void API_CALL mk_player_set_on_shutdown(mk_player ctx, on_mk_play_event cb, void *user_data) {
|
||||||
mk_player_set_on_event(ctx,cb,user_data,1);
|
mk_player_set_on_event(ctx,cb,user_data,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT void API_CALL mk_player_set_on_data(mk_player ctx, on_mk_play_data cb, void *user_data) {
|
API_EXPORT void API_CALL mk_player_set_on_data(mk_player ctx, on_mk_play_data cb, void *user_data) {
|
||||||
assert(ctx && cb);
|
assert(ctx && cb);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
player->getPoller()->async([player,cb,user_data](){
|
player->getPoller()->async([player,cb,user_data](){
|
||||||
//切换线程后再操作
|
//切换线程后再操作
|
||||||
auto delegate = std::make_shared<FrameWriterInterfaceHelper>([cb,user_data](const Frame::Ptr &frame){
|
auto delegate = std::make_shared<FrameWriterInterfaceHelper>([cb,user_data](const Frame::Ptr &frame){
|
||||||
cb(user_data,frame->getTrackType(),frame->getCodecId(),frame->data(),frame->size(),frame->dts(),frame->pts());
|
cb(user_data,frame->getTrackType(),frame->getCodecId(),frame->data(),frame->size(),frame->dts(),frame->pts());
|
||||||
});
|
});
|
||||||
for(auto &track : player->getTracks()){
|
for(auto &track : player->getTracks()){
|
||||||
track->addDelegate(delegate);
|
track->addDelegate(delegate);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT int API_CALL mk_player_video_width(mk_player ctx) {
|
API_EXPORT int API_CALL mk_player_video_width(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
||||||
return track ? track->getVideoWidth() : 0;
|
return track ? track->getVideoWidth() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT int API_CALL mk_player_video_height(mk_player ctx) {
|
API_EXPORT int API_CALL mk_player_video_height(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
||||||
return track ? track->getVideoHeight() : 0;
|
return track ? track->getVideoHeight() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT int API_CALL mk_player_video_fps(mk_player ctx) {
|
API_EXPORT int API_CALL mk_player_video_fps(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
||||||
return track ? track->getVideoFps() : 0;
|
return track ? track->getVideoFps() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT int API_CALL mk_player_audio_samplerate(mk_player ctx) {
|
API_EXPORT int API_CALL mk_player_audio_samplerate(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
||||||
return track ? track->getAudioSampleRate() : 0;
|
return track ? track->getAudioSampleRate() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT int API_CALL mk_player_audio_bit(mk_player ctx) {
|
API_EXPORT int API_CALL mk_player_audio_bit(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
||||||
return track ? track->getAudioSampleBit() : 0;
|
return track ? track->getAudioSampleBit() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT int API_CALL mk_player_audio_channel(mk_player ctx) {
|
API_EXPORT int API_CALL mk_player_audio_channel(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
||||||
return track ? track->getAudioChannel() : 0;
|
return track ? track->getAudioChannel() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT float API_CALL mk_player_duration(mk_player ctx) {
|
API_EXPORT float API_CALL mk_player_duration(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
return player->getDuration();
|
return player->getDuration();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT float API_CALL mk_player_progress(mk_player ctx) {
|
API_EXPORT float API_CALL mk_player_progress(mk_player ctx) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
return player->getProgress();
|
return player->getProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT float API_CALL mk_player_loss_rate(mk_player ctx, int track_type) {
|
API_EXPORT float API_CALL mk_player_loss_rate(mk_player ctx, int track_type) {
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||||
return player->getPacketLossRate((TrackType)track_type);
|
return player->getPacketLossRate((TrackType)track_type);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ onceToken token([]() {
|
|||||||
//windows下先关闭FFmpeg日志(目前不支持日志重定向)
|
//windows下先关闭FFmpeg日志(目前不支持日志重定向)
|
||||||
mINI::Instance()[kCmd] = "%s -re -i \"%s\" -loglevel quiet -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s ";
|
mINI::Instance()[kCmd] = "%s -re -i \"%s\" -loglevel quiet -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s ";
|
||||||
#else
|
#else
|
||||||
string ffmpeg_bin = System::execute("which ffmpeg");
|
string ffmpeg_bin = System::execute("which ffmpeg");
|
||||||
mINI::Instance()[kCmd] = "%s -re -i \"%s\" -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s ";
|
mINI::Instance()[kCmd] = "%s -re -i \"%s\" -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s ";
|
||||||
#endif
|
#endif
|
||||||
//默认ffmpeg命令路径为环境变量中路径
|
//默认ffmpeg命令路径为环境变量中路径
|
||||||
|
@ -46,92 +46,92 @@
|
|||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
void Process::run(const string &cmd, const string &log_file_tmp) {
|
void Process::run(const string &cmd, const string &log_file_tmp) {
|
||||||
kill(2000);
|
kill(2000);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
STARTUPINFO si;
|
STARTUPINFO si;
|
||||||
PROCESS_INFORMATION pi;
|
PROCESS_INFORMATION pi;
|
||||||
ZeroMemory(&si, sizeof(si)); //结构体初始化;
|
ZeroMemory(&si, sizeof(si)); //结构体初始化;
|
||||||
ZeroMemory(&pi, sizeof(pi));
|
ZeroMemory(&pi, sizeof(pi));
|
||||||
|
|
||||||
LPTSTR lpDir = const_cast<char*>(cmd.data());
|
LPTSTR lpDir = const_cast<char*>(cmd.data());
|
||||||
|
|
||||||
if (CreateProcess(NULL, lpDir, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){
|
if (CreateProcess(NULL, lpDir, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){
|
||||||
//下面两行关闭句柄,解除本进程和新进程的关系,不然有可能 不小心调用TerminateProcess函数关掉子进程
|
//下面两行关闭句柄,解除本进程和新进程的关系,不然有可能 不小心调用TerminateProcess函数关掉子进程
|
||||||
CloseHandle(pi.hProcess);
|
CloseHandle(pi.hProcess);
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
_pid = pi.dwProcessId;
|
_pid = pi.dwProcessId;
|
||||||
InfoL << "start child proces " << _pid;
|
InfoL << "start child proces " << _pid;
|
||||||
} else {
|
} else {
|
||||||
WarnL << "start child proces fail: " << GetLastError();
|
WarnL << "start child proces fail: " << GetLastError();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
_pid = fork();
|
_pid = fork();
|
||||||
if (_pid < 0) {
|
if (_pid < 0) {
|
||||||
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
|
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
|
||||||
}
|
}
|
||||||
if (_pid == 0) {
|
if (_pid == 0) {
|
||||||
//子进程关闭core文件生成
|
//子进程关闭core文件生成
|
||||||
struct rlimit rlim = { 0,0 };
|
struct rlimit rlim = { 0,0 };
|
||||||
setrlimit(RLIMIT_CORE, &rlim);
|
setrlimit(RLIMIT_CORE, &rlim);
|
||||||
|
|
||||||
//在启动子进程时,暂时禁用SIGINT、SIGTERM信号
|
//在启动子进程时,暂时禁用SIGINT、SIGTERM信号
|
||||||
// ignore the SIGINT and SIGTERM
|
// ignore the SIGINT and SIGTERM
|
||||||
signal(SIGINT, SIG_IGN);
|
signal(SIGINT, SIG_IGN);
|
||||||
signal(SIGTERM, SIG_IGN);
|
signal(SIGTERM, SIG_IGN);
|
||||||
|
|
||||||
string log_file;
|
string log_file;
|
||||||
if (log_file_tmp.empty()) {
|
if (log_file_tmp.empty()) {
|
||||||
log_file = "/dev/null";
|
log_file = "/dev/null";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log_file = StrPrinter << log_file_tmp << "." << getpid();
|
log_file = StrPrinter << log_file_tmp << "." << getpid();
|
||||||
}
|
}
|
||||||
|
|
||||||
int log_fd = -1;
|
int log_fd = -1;
|
||||||
int flags = O_CREAT | O_WRONLY | O_APPEND;
|
int flags = O_CREAT | O_WRONLY | O_APPEND;
|
||||||
mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
|
mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
|
||||||
File::createfile_path(log_file.data(), mode);
|
File::createfile_path(log_file.data(), mode);
|
||||||
if ((log_fd = ::open(log_file.c_str(), flags, mode)) < 0) {
|
if ((log_fd = ::open(log_file.c_str(), flags, mode)) < 0) {
|
||||||
fprintf(stderr, "open log file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
fprintf(stderr, "open log file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// dup to stdout and stderr.
|
// dup to stdout and stderr.
|
||||||
if (dup2(log_fd, STDOUT_FILENO) < 0) {
|
if (dup2(log_fd, STDOUT_FILENO) < 0) {
|
||||||
fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
||||||
}
|
}
|
||||||
if (dup2(log_fd, STDERR_FILENO) < 0) {
|
if (dup2(log_fd, STDERR_FILENO) < 0) {
|
||||||
fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
||||||
}
|
}
|
||||||
// close log fd
|
// close log fd
|
||||||
::close(log_fd);
|
::close(log_fd);
|
||||||
}
|
}
|
||||||
fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data());
|
fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data());
|
||||||
|
|
||||||
// close other fds
|
// close other fds
|
||||||
// TODO: do in right way.
|
// TODO: do in right way.
|
||||||
for (int i = 3; i < 1024; i++) {
|
for (int i = 3; i < 1024; i++) {
|
||||||
::close(i);
|
::close(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto params = split(cmd, " ");
|
auto params = split(cmd, " ");
|
||||||
// memory leak in child process, it's ok.
|
// memory leak in child process, it's ok.
|
||||||
char **charpv_params = new char *[params.size() + 1];
|
char **charpv_params = new char *[params.size() + 1];
|
||||||
for (int i = 0; i < (int)params.size(); i++) {
|
for (int i = 0; i < (int)params.size(); i++) {
|
||||||
std::string &p = params[i];
|
std::string &p = params[i];
|
||||||
charpv_params[i] = (char *)p.data();
|
charpv_params[i] = (char *)p.data();
|
||||||
}
|
}
|
||||||
// EOF: NULL
|
// EOF: NULL
|
||||||
charpv_params[params.size()] = NULL;
|
charpv_params[params.size()] = NULL;
|
||||||
|
|
||||||
// TODO: execv or execvp
|
// TODO: execv or execvp
|
||||||
auto ret = execv(params[0].c_str(), charpv_params);
|
auto ret = execv(params[0].c_str(), charpv_params);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno));
|
fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno));
|
||||||
}
|
}
|
||||||
exit(ret);
|
exit(ret);
|
||||||
}
|
}
|
||||||
InfoL << "start child proces " << _pid;
|
InfoL << "start child proces " << _pid;
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,27 +148,27 @@ static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) {
|
|||||||
}
|
}
|
||||||
int status = 0;
|
int status = 0;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HANDLE hProcess = NULL;
|
HANDLE hProcess = NULL;
|
||||||
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程
|
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程
|
||||||
if (hProcess == NULL) {
|
if (hProcess == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(hProcess);
|
CloseHandle(hProcess);
|
||||||
#else
|
#else
|
||||||
pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG);
|
pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG);
|
||||||
int exit_code = (status & 0xFF00) >> 8;
|
int exit_code = (status & 0xFF00) >> 8;
|
||||||
if (exit_code_ptr) {
|
if (exit_code_ptr) {
|
||||||
*exit_code_ptr = (status & 0xFF00) >> 8;
|
*exit_code_ptr = (status & 0xFF00) >> 8;
|
||||||
}
|
}
|
||||||
if (p < 0) {
|
if (p < 0) {
|
||||||
WarnL << "waitpid failed, pid=" << pid << ", err=" << get_uv_errmsg();
|
WarnL << "waitpid failed, pid=" << pid << ", err=" << get_uv_errmsg();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (p > 0) {
|
if (p > 0) {
|
||||||
InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code;
|
InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -180,22 +180,22 @@ static void s_kill(pid_t pid,int max_delay,bool force){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HANDLE hProcess = NULL;
|
HANDLE hProcess = NULL;
|
||||||
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程
|
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程
|
||||||
if (hProcess == NULL) {
|
if (hProcess == NULL) {
|
||||||
WarnL << "\nOpen Process fAiled: " << GetLastError();
|
WarnL << "\nOpen Process fAiled: " << GetLastError();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DWORD ret = TerminateProcess(hProcess, 0); //结束目标进程
|
DWORD ret = TerminateProcess(hProcess, 0); //结束目标进程
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
WarnL << GetLastError;
|
WarnL << GetLastError;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) {
|
if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) {
|
||||||
//进程可能已经退出了
|
//进程可能已经退出了
|
||||||
WarnL << "kill process " << pid << " failed:" << get_uv_errmsg();
|
WarnL << "kill process " << pid << " failed:" << get_uv_errmsg();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
|
|
||||||
|
@ -774,9 +774,9 @@ void installWebApi() {
|
|||||||
val["status"] = (int)status;
|
val["status"] = (int)status;
|
||||||
});
|
});
|
||||||
|
|
||||||
//获取录像文件夹列表或mp4文件列表
|
//获取录像文件夹列表或mp4文件列表
|
||||||
//http://127.0.0.1/index/api/getMp4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01
|
//http://127.0.0.1/index/api/getMp4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01
|
||||||
api_regist1("/index/api/getMp4RecordFile", [](API_ARGS1){
|
api_regist1("/index/api/getMp4RecordFile", [](API_ARGS1){
|
||||||
CHECK_SECRET();
|
CHECK_SECRET();
|
||||||
CHECK_ARGS("vhost", "app", "stream");
|
CHECK_ARGS("vhost", "app", "stream");
|
||||||
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"],allArgs["stream"]);
|
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"],allArgs["stream"]);
|
||||||
@ -809,7 +809,7 @@ void installWebApi() {
|
|||||||
|
|
||||||
val["data"]["rootPath"] = record_path;
|
val["data"]["rootPath"] = record_path;
|
||||||
val["data"]["paths"] = paths;
|
val["data"]["paths"] = paths;
|
||||||
});
|
});
|
||||||
|
|
||||||
////////////以下是注册的Hook API////////////
|
////////////以下是注册的Hook API////////////
|
||||||
api_regist1("/index/hook/on_publish",[](API_ARGS1){
|
api_regist1("/index/hook/on_publish",[](API_ARGS1){
|
||||||
|
@ -359,7 +359,7 @@ int start_main(int argc,char *argv[]) {
|
|||||||
InfoL << "程序退出中,请等待...";
|
InfoL << "程序退出中,请等待...";
|
||||||
sleep(1);
|
sleep(1);
|
||||||
InfoL << "程序退出完毕!";
|
InfoL << "程序退出完毕!";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef DISABLE_MAIN
|
#ifndef DISABLE_MAIN
|
||||||
|
@ -47,73 +47,73 @@ AACEncoder::AACEncoder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AACEncoder::~AACEncoder() {
|
AACEncoder::~AACEncoder() {
|
||||||
if (_hEncoder != nullptr) {
|
if (_hEncoder != nullptr) {
|
||||||
faacEncClose(_hEncoder);
|
faacEncClose(_hEncoder);
|
||||||
_hEncoder = nullptr;
|
_hEncoder = nullptr;
|
||||||
}
|
}
|
||||||
if (_pucAacBuf != nullptr) {
|
if (_pucAacBuf != nullptr) {
|
||||||
delete[] _pucAacBuf;
|
delete[] _pucAacBuf;
|
||||||
_pucAacBuf = nullptr;
|
_pucAacBuf = nullptr;
|
||||||
}
|
}
|
||||||
if (_pucPcmBuf != nullptr) {
|
if (_pucPcmBuf != nullptr) {
|
||||||
delete[] _pucPcmBuf;
|
delete[] _pucPcmBuf;
|
||||||
_pucPcmBuf = nullptr;
|
_pucPcmBuf = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AACEncoder::init(int iSampleRate, int iChannels, int iSampleBit) {
|
bool AACEncoder::init(int iSampleRate, int iChannels, int iSampleBit) {
|
||||||
if (iSampleBit != 16) {
|
if (iSampleBit != 16) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// (1) Open FAAC engine
|
// (1) Open FAAC engine
|
||||||
_hEncoder = faacEncOpen(iSampleRate, iChannels, &_ulInputSamples,
|
_hEncoder = faacEncOpen(iSampleRate, iChannels, &_ulInputSamples,
|
||||||
&_ulMaxOutputBytes);
|
&_ulMaxOutputBytes);
|
||||||
if (_hEncoder == NULL) {
|
if (_hEncoder == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_pucAacBuf = new unsigned char[_ulMaxOutputBytes];
|
_pucAacBuf = new unsigned char[_ulMaxOutputBytes];
|
||||||
_ulMaxInputBytes = _ulInputSamples * iSampleBit / 8;
|
_ulMaxInputBytes = _ulInputSamples * iSampleBit / 8;
|
||||||
_pucPcmBuf = new unsigned char[_ulMaxInputBytes * 4];
|
_pucPcmBuf = new unsigned char[_ulMaxInputBytes * 4];
|
||||||
|
|
||||||
// (2.1) Get current encoding configuration
|
// (2.1) Get current encoding configuration
|
||||||
faacEncConfigurationPtr pConfiguration = faacEncGetCurrentConfiguration(_hEncoder);
|
faacEncConfigurationPtr pConfiguration = faacEncGetCurrentConfiguration(_hEncoder);
|
||||||
if (pConfiguration == NULL) {
|
if (pConfiguration == NULL) {
|
||||||
faacEncClose(_hEncoder);
|
faacEncClose(_hEncoder);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pConfiguration->aacObjectType =LOW;
|
pConfiguration->aacObjectType =LOW;
|
||||||
pConfiguration->mpegVersion = 4;
|
pConfiguration->mpegVersion = 4;
|
||||||
pConfiguration->useTns = 1;
|
pConfiguration->useTns = 1;
|
||||||
pConfiguration->shortctl = SHORTCTL_NORMAL;
|
pConfiguration->shortctl = SHORTCTL_NORMAL;
|
||||||
pConfiguration->useLfe = 1;
|
pConfiguration->useLfe = 1;
|
||||||
pConfiguration->allowMidside = 1;
|
pConfiguration->allowMidside = 1;
|
||||||
pConfiguration->bitRate = 0;
|
pConfiguration->bitRate = 0;
|
||||||
pConfiguration->bandWidth = 0;
|
pConfiguration->bandWidth = 0;
|
||||||
pConfiguration->quantqual = 50;
|
pConfiguration->quantqual = 50;
|
||||||
pConfiguration->outputFormat = 1;
|
pConfiguration->outputFormat = 1;
|
||||||
pConfiguration->inputFormat = FAAC_INPUT_16BIT;
|
pConfiguration->inputFormat = FAAC_INPUT_16BIT;
|
||||||
|
|
||||||
// (2.2) Set encoding configuration
|
// (2.2) Set encoding configuration
|
||||||
if(!faacEncSetConfiguration(_hEncoder, pConfiguration)){
|
if(!faacEncSetConfiguration(_hEncoder, pConfiguration)){
|
||||||
ErrorL << "faacEncSetConfiguration failed";
|
ErrorL << "faacEncSetConfiguration failed";
|
||||||
faacEncClose(_hEncoder);
|
faacEncClose(_hEncoder);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AACEncoder::inputData(char *pcPcmBufr, int iLen, unsigned char **ppucOutBuffer) {
|
int AACEncoder::inputData(char *pcPcmBufr, int iLen, unsigned char **ppucOutBuffer) {
|
||||||
memcpy(_pucPcmBuf + _uiPcmLen, pcPcmBufr, iLen);
|
memcpy(_pucPcmBuf + _uiPcmLen, pcPcmBufr, iLen);
|
||||||
_uiPcmLen += iLen;
|
_uiPcmLen += iLen;
|
||||||
if (_uiPcmLen < _ulMaxInputBytes) {
|
if (_uiPcmLen < _ulMaxInputBytes) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nRet = faacEncEncode(_hEncoder, (int32_t *) (_pucPcmBuf), _ulInputSamples, _pucAacBuf, _ulMaxOutputBytes);
|
int nRet = faacEncEncode(_hEncoder, (int32_t *) (_pucPcmBuf), _ulInputSamples, _pucAacBuf, _ulMaxOutputBytes);
|
||||||
_uiPcmLen -= _ulMaxInputBytes;
|
_uiPcmLen -= _ulMaxInputBytes;
|
||||||
memmove(_pucPcmBuf, _pucPcmBuf + _ulMaxInputBytes, _uiPcmLen);
|
memmove(_pucPcmBuf, _pucPcmBuf + _ulMaxInputBytes, _uiPcmLen);
|
||||||
*ppucOutBuffer = _pucAacBuf;
|
*ppucOutBuffer = _pucAacBuf;
|
||||||
return nRet;
|
return nRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -32,21 +32,21 @@ namespace mediakit {
|
|||||||
|
|
||||||
class AACEncoder {
|
class AACEncoder {
|
||||||
public:
|
public:
|
||||||
AACEncoder(void);
|
AACEncoder(void);
|
||||||
virtual ~AACEncoder(void);
|
virtual ~AACEncoder(void);
|
||||||
bool init(int iSampleRate, int iAudioChannel, int iAudioSampleBit);
|
bool init(int iSampleRate, int iAudioChannel, int iAudioSampleBit);
|
||||||
int inputData(char *pcData, int iLen, unsigned char **ppucOutBuffer);
|
int inputData(char *pcData, int iLen, unsigned char **ppucOutBuffer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned char *_pucPcmBuf = nullptr;
|
unsigned char *_pucPcmBuf = nullptr;
|
||||||
unsigned int _uiPcmLen = 0;
|
unsigned int _uiPcmLen = 0;
|
||||||
|
|
||||||
unsigned char *_pucAacBuf = nullptr;
|
unsigned char *_pucAacBuf = nullptr;
|
||||||
void *_hEncoder = nullptr;
|
void *_hEncoder = nullptr;
|
||||||
|
|
||||||
unsigned long _ulInputSamples = 0;
|
unsigned long _ulInputSamples = 0;
|
||||||
unsigned long _ulMaxInputBytes = 0;
|
unsigned long _ulMaxInputBytes = 0;
|
||||||
unsigned long _ulMaxOutputBytes = 0;
|
unsigned long _ulMaxOutputBytes = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,21 +38,21 @@ H264Encoder::H264Encoder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
H264Encoder::~H264Encoder() {
|
H264Encoder::~H264Encoder() {
|
||||||
//* 清除图像区域
|
//* 清除图像区域
|
||||||
if (_pPicIn) {
|
if (_pPicIn) {
|
||||||
delete _pPicIn;
|
delete _pPicIn;
|
||||||
_pPicIn = nullptr;
|
_pPicIn = nullptr;
|
||||||
}
|
}
|
||||||
if (_pPicOut) {
|
if (_pPicOut) {
|
||||||
delete _pPicOut;
|
delete _pPicOut;
|
||||||
_pPicOut = nullptr;
|
_pPicOut = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
//* 关闭编码器句柄
|
//* 关闭编码器句柄
|
||||||
if (_pX264Handle) {
|
if (_pX264Handle) {
|
||||||
x264_encoder_close(_pX264Handle);
|
x264_encoder_close(_pX264Handle);
|
||||||
_pX264Handle = nullptr;
|
_pX264Handle = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -229,122 +229,122 @@ Value的值就是fps。
|
|||||||
} x264_param_t;*/
|
} x264_param_t;*/
|
||||||
|
|
||||||
bool H264Encoder::init(int iWidth, int iHeight, int iFps) {
|
bool H264Encoder::init(int iWidth, int iHeight, int iFps) {
|
||||||
if (_pX264Handle) {
|
if (_pX264Handle) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
x264_param_t X264Param, *pX264Param = &X264Param;
|
x264_param_t X264Param, *pX264Param = &X264Param;
|
||||||
//* 配置参数
|
//* 配置参数
|
||||||
//* 使用默认参数
|
//* 使用默认参数
|
||||||
x264_param_default_preset(pX264Param, "ultrafast", "zerolatency");
|
x264_param_default_preset(pX264Param, "ultrafast", "zerolatency");
|
||||||
|
|
||||||
//* cpuFlags
|
//* cpuFlags
|
||||||
pX264Param->i_threads = X264_SYNC_LOOKAHEAD_AUTO; //* 取空缓冲区继续使用不死锁的保证.
|
pX264Param->i_threads = X264_SYNC_LOOKAHEAD_AUTO; //* 取空缓冲区继续使用不死锁的保证.
|
||||||
//* video Properties
|
//* video Properties
|
||||||
pX264Param->i_width = iWidth; //* 宽度.
|
pX264Param->i_width = iWidth; //* 宽度.
|
||||||
pX264Param->i_height = iHeight; //* 高度
|
pX264Param->i_height = iHeight; //* 高度
|
||||||
pX264Param->i_frame_total = 0; //* 编码总帧数.不知道用0.
|
pX264Param->i_frame_total = 0; //* 编码总帧数.不知道用0.
|
||||||
pX264Param->i_keyint_max = iFps * 3; //ffmpeg:gop_size 关键帧最大间隔
|
pX264Param->i_keyint_max = iFps * 3; //ffmpeg:gop_size 关键帧最大间隔
|
||||||
pX264Param->i_keyint_min = iFps * 1; //ffmpeg:keyint_min 关键帧最小间隔
|
pX264Param->i_keyint_min = iFps * 1; //ffmpeg:keyint_min 关键帧最小间隔
|
||||||
//* Rate control Parameters
|
//* Rate control Parameters
|
||||||
pX264Param->rc.i_bitrate = 5000; //* 码率(比特率,单位Kbps)
|
pX264Param->rc.i_bitrate = 5000; //* 码率(比特率,单位Kbps)
|
||||||
pX264Param->rc.i_qp_step = 1; //最大的在帧与帧之间进行切变的量化因子的变化量。ffmpeg:max_qdiff
|
pX264Param->rc.i_qp_step = 1; //最大的在帧与帧之间进行切变的量化因子的变化量。ffmpeg:max_qdiff
|
||||||
pX264Param->rc.i_qp_min = 10; //ffmpeg:qmin;最小的量化因子。取值范围1-51。建议在10-30之间。
|
pX264Param->rc.i_qp_min = 10; //ffmpeg:qmin;最小的量化因子。取值范围1-51。建议在10-30之间。
|
||||||
pX264Param->rc.i_qp_max = 41; //ffmpeg:qmax;最大的量化因子。取值范围1-51。建议在10-30之间。
|
pX264Param->rc.i_qp_max = 41; //ffmpeg:qmax;最大的量化因子。取值范围1-51。建议在10-30之间。
|
||||||
pX264Param->rc.f_qcompress = 0.6;//ffmpeg:qcompress 量化器压缩比率0-1.越小则比特率越区域固定,但是越高越使量化器参数越固定
|
pX264Param->rc.f_qcompress = 0.6;//ffmpeg:qcompress 量化器压缩比率0-1.越小则比特率越区域固定,但是越高越使量化器参数越固定
|
||||||
pX264Param->analyse.i_me_range = 16; //ffmpeg:me_range 运动侦测的半径
|
pX264Param->analyse.i_me_range = 16; //ffmpeg:me_range 运动侦测的半径
|
||||||
pX264Param->i_frame_reference = 3; //ffmpeg:refsB和P帧向前预测参考的帧数。取值范围1-16。
|
pX264Param->i_frame_reference = 3; //ffmpeg:refsB和P帧向前预测参考的帧数。取值范围1-16。
|
||||||
//该值不影响解码的速度,但是越大解码
|
//该值不影响解码的速度,但是越大解码
|
||||||
//所需的内存越大。这个值在一般情况下
|
//所需的内存越大。这个值在一般情况下
|
||||||
//越大效果越好,但是超过6以后效果就
|
//越大效果越好,但是超过6以后效果就
|
||||||
//不明显了。
|
//不明显了。
|
||||||
|
|
||||||
pX264Param->analyse.i_trellis = 1; //ffmpeg:trellis
|
pX264Param->analyse.i_trellis = 1; //ffmpeg:trellis
|
||||||
//pX264Param->analyse.i_me_method=X264_ME_DIA;//ffmpeg:me_method ME_ZERO 运动侦测的方式
|
//pX264Param->analyse.i_me_method=X264_ME_DIA;//ffmpeg:me_method ME_ZERO 运动侦测的方式
|
||||||
pX264Param->rc.f_qblur = 0.5; //ffmpeg:qblur
|
pX264Param->rc.f_qblur = 0.5; //ffmpeg:qblur
|
||||||
|
|
||||||
//* bitstream parameters
|
//* bitstream parameters
|
||||||
/*open-GOP
|
/*open-GOP
|
||||||
码流里面包含B帧的时候才会出现open-GOP。
|
码流里面包含B帧的时候才会出现open-GOP。
|
||||||
一个GOP里面的某一帧在解码时要依赖于前一个GOP的某些帧,
|
一个GOP里面的某一帧在解码时要依赖于前一个GOP的某些帧,
|
||||||
这个GOP就称为open-GOP。
|
这个GOP就称为open-GOP。
|
||||||
有些解码器不能完全支持open-GOP码流,
|
有些解码器不能完全支持open-GOP码流,
|
||||||
例如蓝光解码器,因此在x264里面open-GOP是默认关闭的。
|
例如蓝光解码器,因此在x264里面open-GOP是默认关闭的。
|
||||||
对于解码端,接收到的码流如果如下:I0 B0 B1 P0 B2 B3...这就是一个open-GOP码流(I帧后面紧跟B帧)。
|
对于解码端,接收到的码流如果如下:I0 B0 B1 P0 B2 B3...这就是一个open-GOP码流(I帧后面紧跟B帧)。
|
||||||
因此B0 B1的解码需要用到I0前面一个GOP的数据,B0 B1的dts是小于I0的。
|
因此B0 B1的解码需要用到I0前面一个GOP的数据,B0 B1的dts是小于I0的。
|
||||||
如果码流如下: I0 P0 B0 B1 P1 B2 B3...这就是一个close-GOP码流,
|
如果码流如下: I0 P0 B0 B1 P1 B2 B3...这就是一个close-GOP码流,
|
||||||
I0后面所有帧的解码不依赖于I0前面的帧,I0后面所有帧的dts都比I0的大。
|
I0后面所有帧的解码不依赖于I0前面的帧,I0后面所有帧的dts都比I0的大。
|
||||||
如果码流是IDR0 B0 B1 P0 B2 B3...那个这个GOP是close-GOP,B0,B1虽然dst比IDR0小,
|
如果码流是IDR0 B0 B1 P0 B2 B3...那个这个GOP是close-GOP,B0,B1虽然dst比IDR0小,
|
||||||
但编解码端都刷新了参考缓冲,B0,B1参考不到前向GOP帧。
|
但编解码端都刷新了参考缓冲,B0,B1参考不到前向GOP帧。
|
||||||
对于编码端,如果编码帧类型决定如下: ...P0 B1 B2 P3 B4 B5 I6这就会输出open-Gop码流 (P0 P3 B1 B2 I6 B4 B5...),
|
对于编码端,如果编码帧类型决定如下: ...P0 B1 B2 P3 B4 B5 I6这就会输出open-Gop码流 (P0 P3 B1 B2 I6 B4 B5...),
|
||||||
B4 B5的解码依赖P3。
|
B4 B5的解码依赖P3。
|
||||||
如果编码帧类型决定如下...P0 B1 B2 P3 B4 P5 I6这样就不会输出open-GOP码流(P0 P3 B1 B2 P5 B4 I6...)。
|
如果编码帧类型决定如下...P0 B1 B2 P3 B4 P5 I6这样就不会输出open-GOP码流(P0 P3 B1 B2 P5 B4 I6...)。
|
||||||
两者区别在于I6前面的第5帧是设置为B帧还是P帧,
|
两者区别在于I6前面的第5帧是设置为B帧还是P帧,
|
||||||
如果一个GOP的最后一帧(上例中是第5帧)设置为B帧,
|
如果一个GOP的最后一帧(上例中是第5帧)设置为B帧,
|
||||||
这个码流就是open-GOP,设置为P帧就是close-GOP。
|
这个码流就是open-GOP,设置为P帧就是close-GOP。
|
||||||
由于B帧压缩性能好于P帧,因此open-GOP在编码性能上稍微优于close-GOP,
|
由于B帧压缩性能好于P帧,因此open-GOP在编码性能上稍微优于close-GOP,
|
||||||
但为了兼容性和少一些麻烦,还是把opne-GOP关闭的好。*/
|
但为了兼容性和少一些麻烦,还是把opne-GOP关闭的好。*/
|
||||||
pX264Param->b_open_gop = 0;
|
pX264Param->b_open_gop = 0;
|
||||||
pX264Param->i_bframe = 0; //最大B帧数.
|
pX264Param->i_bframe = 0; //最大B帧数.
|
||||||
pX264Param->i_bframe_pyramid = 0;
|
pX264Param->i_bframe_pyramid = 0;
|
||||||
pX264Param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
|
pX264Param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
|
||||||
//* Log
|
//* Log
|
||||||
pX264Param->i_log_level = X264_LOG_ERROR;
|
pX264Param->i_log_level = X264_LOG_ERROR;
|
||||||
|
|
||||||
//* muxing parameters
|
//* muxing parameters
|
||||||
pX264Param->i_fps_den = 1; //* 帧率分母
|
pX264Param->i_fps_den = 1; //* 帧率分母
|
||||||
pX264Param->i_fps_num = iFps; //* 帧率分子
|
pX264Param->i_fps_num = iFps; //* 帧率分子
|
||||||
pX264Param->i_timebase_den = pX264Param->i_fps_num;
|
pX264Param->i_timebase_den = pX264Param->i_fps_num;
|
||||||
pX264Param->i_timebase_num = pX264Param->i_fps_den;
|
pX264Param->i_timebase_num = pX264Param->i_fps_den;
|
||||||
|
|
||||||
pX264Param->analyse.i_subpel_refine = 1; //这个参数控制在运动估算过程中质量和速度的权衡。Subq=5可以压缩>10%于subq=1。1-7
|
pX264Param->analyse.i_subpel_refine = 1; //这个参数控制在运动估算过程中质量和速度的权衡。Subq=5可以压缩>10%于subq=1。1-7
|
||||||
pX264Param->analyse.b_fast_pskip = 1; //在P帧内执行早期快速跳跃探测。这个经常在没有任何损失的前提下提高了速度。
|
pX264Param->analyse.b_fast_pskip = 1; //在P帧内执行早期快速跳跃探测。这个经常在没有任何损失的前提下提高了速度。
|
||||||
|
|
||||||
pX264Param->b_annexb = 1; //1前面为0x00000001,0为nal长度
|
pX264Param->b_annexb = 1; //1前面为0x00000001,0为nal长度
|
||||||
pX264Param->b_repeat_headers = 1; //关键帧前面是否放sps跟pps帧,0 否 1,放
|
pX264Param->b_repeat_headers = 1; //关键帧前面是否放sps跟pps帧,0 否 1,放
|
||||||
|
|
||||||
//* 设置Profile.使用baseline
|
//* 设置Profile.使用baseline
|
||||||
x264_param_apply_profile(pX264Param, "high");
|
x264_param_apply_profile(pX264Param, "high");
|
||||||
|
|
||||||
//* 打开编码器句柄,通过x264_encoder_parameters得到设置给X264
|
//* 打开编码器句柄,通过x264_encoder_parameters得到设置给X264
|
||||||
//* 的参数.通过x264_encoder_reconfig更新X264的参数
|
//* 的参数.通过x264_encoder_reconfig更新X264的参数
|
||||||
_pX264Handle = x264_encoder_open(pX264Param);
|
_pX264Handle = x264_encoder_open(pX264Param);
|
||||||
if (!_pX264Handle) {
|
if (!_pX264Handle) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_pPicIn = new x264_picture_t;
|
_pPicIn = new x264_picture_t;
|
||||||
_pPicOut = new x264_picture_t;
|
_pPicOut = new x264_picture_t;
|
||||||
x264_picture_init(_pPicIn);
|
x264_picture_init(_pPicIn);
|
||||||
x264_picture_init(_pPicOut);
|
x264_picture_init(_pPicOut);
|
||||||
_pPicIn->img.i_csp = X264_CSP_I420;
|
_pPicIn->img.i_csp = X264_CSP_I420;
|
||||||
_pPicIn->img.i_plane = 3;
|
_pPicIn->img.i_plane = 3;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int H264Encoder::inputData(char* apcYuv[3], int aiYuvLen[3], int64_t i64Pts, H264Frame** ppFrame) {
|
int H264Encoder::inputData(char* apcYuv[3], int aiYuvLen[3], int64_t i64Pts, H264Frame** ppFrame) {
|
||||||
//TimeTicker1(5);
|
//TimeTicker1(5);
|
||||||
_pPicIn->img.i_stride[0] = aiYuvLen[0];
|
_pPicIn->img.i_stride[0] = aiYuvLen[0];
|
||||||
_pPicIn->img.i_stride[1] = aiYuvLen[1];
|
_pPicIn->img.i_stride[1] = aiYuvLen[1];
|
||||||
_pPicIn->img.i_stride[2] = aiYuvLen[2];
|
_pPicIn->img.i_stride[2] = aiYuvLen[2];
|
||||||
_pPicIn->img.plane[0] = (uint8_t *) apcYuv[0];
|
_pPicIn->img.plane[0] = (uint8_t *) apcYuv[0];
|
||||||
_pPicIn->img.plane[1] = (uint8_t *) apcYuv[1];
|
_pPicIn->img.plane[1] = (uint8_t *) apcYuv[1];
|
||||||
_pPicIn->img.plane[2] = (uint8_t *) apcYuv[2];
|
_pPicIn->img.plane[2] = (uint8_t *) apcYuv[2];
|
||||||
_pPicIn->i_pts = i64Pts;
|
_pPicIn->i_pts = i64Pts;
|
||||||
int iNal;
|
int iNal;
|
||||||
x264_nal_t* pNals;
|
x264_nal_t* pNals;
|
||||||
|
|
||||||
int iResult = x264_encoder_encode(_pX264Handle, &pNals, &iNal, _pPicIn,
|
int iResult = x264_encoder_encode(_pX264Handle, &pNals, &iNal, _pPicIn,
|
||||||
_pPicOut);
|
_pPicOut);
|
||||||
if (iResult <= 0) {
|
if (iResult <= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < iNal; i++) {
|
for (int i = 0; i < iNal; i++) {
|
||||||
x264_nal_t pNal = pNals[i];
|
x264_nal_t pNal = pNals[i];
|
||||||
_aFrames[i].iType = pNal.i_type;
|
_aFrames[i].iType = pNal.i_type;
|
||||||
_aFrames[i].iLength = pNal.i_payload;
|
_aFrames[i].iLength = pNal.i_payload;
|
||||||
_aFrames[i].pucData = pNal.p_payload;
|
_aFrames[i].pucData = pNal.p_payload;
|
||||||
}
|
}
|
||||||
*ppFrame = _aFrames;
|
*ppFrame = _aFrames;
|
||||||
return iNal;
|
return iNal;
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -42,21 +42,21 @@ namespace mediakit {
|
|||||||
|
|
||||||
class H264Encoder {
|
class H264Encoder {
|
||||||
public:
|
public:
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int iType;
|
int iType;
|
||||||
int iLength;
|
int iLength;
|
||||||
uint8_t *pucData;
|
uint8_t *pucData;
|
||||||
} H264Frame;
|
} H264Frame;
|
||||||
|
|
||||||
H264Encoder(void);
|
H264Encoder(void);
|
||||||
virtual ~H264Encoder(void);
|
virtual ~H264Encoder(void);
|
||||||
bool init(int iWidth, int iHeight, int iFps);
|
bool init(int iWidth, int iHeight, int iFps);
|
||||||
int inputData(char *apcYuv[3], int aiYuvLen[3], int64_t i64Pts, H264Frame **ppFrame);
|
int inputData(char *apcYuv[3], int aiYuvLen[3], int64_t i64Pts, H264Frame **ppFrame);
|
||||||
private:
|
private:
|
||||||
x264_t* _pX264Handle = nullptr;
|
x264_t* _pX264Handle = nullptr;
|
||||||
x264_picture_t* _pPicIn = nullptr;
|
x264_picture_t* _pPicIn = nullptr;
|
||||||
x264_picture_t* _pPicOut = nullptr;
|
x264_picture_t* _pPicOut = nullptr;
|
||||||
H264Frame _aFrames[10];
|
H264Frame _aFrames[10];
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -42,8 +42,8 @@ DevChannel::DevChannel(const string &strVhost,
|
|||||||
const string &strApp,
|
const string &strApp,
|
||||||
const string &strId,
|
const string &strId,
|
||||||
float fDuration,
|
float fDuration,
|
||||||
bool bEanbleRtsp,
|
bool bEanbleRtsp,
|
||||||
bool bEanbleRtmp,
|
bool bEanbleRtmp,
|
||||||
bool bEanbleHls,
|
bool bEanbleHls,
|
||||||
bool bEnableMp4) :
|
bool bEnableMp4) :
|
||||||
MultiMediaSourceMuxer(strVhost, strApp, strId, fDuration, bEanbleRtsp, bEanbleRtmp, bEanbleHls, bEnableMp4) {}
|
MultiMediaSourceMuxer(strVhost, strApp, strId, fDuration, bEanbleRtsp, bEanbleRtmp, bEanbleHls, bEnableMp4) {}
|
||||||
@ -52,50 +52,50 @@ DevChannel::~DevChannel() {}
|
|||||||
|
|
||||||
#ifdef ENABLE_X264
|
#ifdef ENABLE_X264
|
||||||
void DevChannel::inputYUV(char* apcYuv[3], int aiYuvLen[3], uint32_t uiStamp) {
|
void DevChannel::inputYUV(char* apcYuv[3], int aiYuvLen[3], uint32_t uiStamp) {
|
||||||
//TimeTicker1(50);
|
//TimeTicker1(50);
|
||||||
if (!_pH264Enc) {
|
if (!_pH264Enc) {
|
||||||
_pH264Enc.reset(new H264Encoder());
|
_pH264Enc.reset(new H264Encoder());
|
||||||
if (!_pH264Enc->init(_video->iWidth, _video->iHeight, _video->iFrameRate)) {
|
if (!_pH264Enc->init(_video->iWidth, _video->iHeight, _video->iFrameRate)) {
|
||||||
_pH264Enc.reset();
|
_pH264Enc.reset();
|
||||||
WarnL << "H264Encoder init failed!";
|
WarnL << "H264Encoder init failed!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_pH264Enc) {
|
if (_pH264Enc) {
|
||||||
H264Encoder::H264Frame *pOut;
|
H264Encoder::H264Frame *pOut;
|
||||||
int iFrames = _pH264Enc->inputData(apcYuv, aiYuvLen, uiStamp, &pOut);
|
int iFrames = _pH264Enc->inputData(apcYuv, aiYuvLen, uiStamp, &pOut);
|
||||||
for (int i = 0; i < iFrames; i++) {
|
for (int i = 0; i < iFrames; i++) {
|
||||||
inputH264((char *) pOut[i].pucData, pOut[i].iLength, uiStamp);
|
inputH264((char *) pOut[i].pucData, pOut[i].iLength, uiStamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif //ENABLE_X264
|
#endif //ENABLE_X264
|
||||||
|
|
||||||
#ifdef ENABLE_FAAC
|
#ifdef ENABLE_FAAC
|
||||||
void DevChannel::inputPCM(char* pcData, int iDataLen, uint32_t uiStamp) {
|
void DevChannel::inputPCM(char* pcData, int iDataLen, uint32_t uiStamp) {
|
||||||
if (!_pAacEnc) {
|
if (!_pAacEnc) {
|
||||||
_pAacEnc.reset(new AACEncoder());
|
_pAacEnc.reset(new AACEncoder());
|
||||||
if (!_pAacEnc->init(_audio->iSampleRate, _audio->iChannel, _audio->iSampleBit)) {
|
if (!_pAacEnc->init(_audio->iSampleRate, _audio->iChannel, _audio->iSampleBit)) {
|
||||||
_pAacEnc.reset();
|
_pAacEnc.reset();
|
||||||
WarnL << "AACEncoder init failed!";
|
WarnL << "AACEncoder init failed!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_pAacEnc) {
|
if (_pAacEnc) {
|
||||||
unsigned char *pucOut;
|
unsigned char *pucOut;
|
||||||
int iRet = _pAacEnc->inputData(pcData, iDataLen, &pucOut);
|
int iRet = _pAacEnc->inputData(pcData, iDataLen, &pucOut);
|
||||||
if (iRet > 0) {
|
if (iRet > 0) {
|
||||||
inputAAC((char *) pucOut, iRet, uiStamp);
|
inputAAC((char *) pucOut, iRet, uiStamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif //ENABLE_FAAC
|
#endif //ENABLE_FAAC
|
||||||
|
|
||||||
void DevChannel::inputH264(const char* pcData, int iDataLen, uint32_t dts,uint32_t pts) {
|
void DevChannel::inputH264(const char* pcData, int iDataLen, uint32_t dts,uint32_t pts) {
|
||||||
if(dts == 0){
|
if(dts == 0){
|
||||||
dts = (uint32_t)_aTicker[0].elapsedTime();
|
dts = (uint32_t)_aTicker[0].elapsedTime();
|
||||||
|
}
|
||||||
|
if(pts == 0){
|
||||||
|
pts = dts;
|
||||||
}
|
}
|
||||||
if(pts == 0){
|
|
||||||
pts = dts;
|
|
||||||
}
|
|
||||||
int prefixeSize;
|
int prefixeSize;
|
||||||
if (memcmp("\x00\x00\x00\x01", pcData, 4) == 0) {
|
if (memcmp("\x00\x00\x00\x01", pcData, 4) == 0) {
|
||||||
prefixeSize = 4;
|
prefixeSize = 4;
|
||||||
@ -105,46 +105,46 @@ void DevChannel::inputH264(const char* pcData, int iDataLen, uint32_t dts,uint32
|
|||||||
prefixeSize = 0;
|
prefixeSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
H264Frame::Ptr frame = std::make_shared<H264Frame>();
|
H264Frame::Ptr frame = std::make_shared<H264Frame>();
|
||||||
frame->_dts = dts;
|
frame->_dts = dts;
|
||||||
frame->_pts = pts;
|
frame->_pts = pts;
|
||||||
frame->_buffer.assign("\x00\x00\x00\x01",4);
|
frame->_buffer.assign("\x00\x00\x00\x01",4);
|
||||||
frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize);
|
frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize);
|
||||||
frame->_prefix_size = 4;
|
frame->_prefix_size = 4;
|
||||||
inputFrame(frame);
|
inputFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevChannel::inputH265(const char* pcData, int iDataLen, uint32_t dts,uint32_t pts) {
|
void DevChannel::inputH265(const char* pcData, int iDataLen, uint32_t dts,uint32_t pts) {
|
||||||
if(dts == 0){
|
if(dts == 0){
|
||||||
dts = (uint32_t)_aTicker[0].elapsedTime();
|
dts = (uint32_t)_aTicker[0].elapsedTime();
|
||||||
}
|
}
|
||||||
if(pts == 0){
|
if(pts == 0){
|
||||||
pts = dts;
|
pts = dts;
|
||||||
}
|
}
|
||||||
int prefixeSize;
|
int prefixeSize;
|
||||||
if (memcmp("\x00\x00\x00\x01", pcData, 4) == 0) {
|
if (memcmp("\x00\x00\x00\x01", pcData, 4) == 0) {
|
||||||
prefixeSize = 4;
|
prefixeSize = 4;
|
||||||
} else if (memcmp("\x00\x00\x01", pcData, 3) == 0) {
|
} else if (memcmp("\x00\x00\x01", pcData, 3) == 0) {
|
||||||
prefixeSize = 3;
|
prefixeSize = 3;
|
||||||
} else {
|
} else {
|
||||||
prefixeSize = 0;
|
prefixeSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
H265Frame::Ptr frame = std::make_shared<H265Frame>();
|
H265Frame::Ptr frame = std::make_shared<H265Frame>();
|
||||||
frame->_dts = dts;
|
frame->_dts = dts;
|
||||||
frame->_pts = pts;
|
frame->_pts = pts;
|
||||||
frame->_buffer.assign("\x00\x00\x00\x01",4);
|
frame->_buffer.assign("\x00\x00\x00\x01",4);
|
||||||
frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize);
|
frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize);
|
||||||
frame->_prefix_size = 4;
|
frame->_prefix_size = 4;
|
||||||
inputFrame(frame);
|
inputFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevChannel::inputAAC(const char* pcData, int iDataLen, uint32_t uiStamp,bool withAdtsHeader) {
|
void DevChannel::inputAAC(const char* pcData, int iDataLen, uint32_t uiStamp,bool withAdtsHeader) {
|
||||||
if(withAdtsHeader){
|
if(withAdtsHeader){
|
||||||
inputAAC(pcData+7,iDataLen-7,uiStamp,pcData);
|
inputAAC(pcData+7,iDataLen-7,uiStamp,pcData);
|
||||||
} else if(_audio) {
|
} else if(_audio) {
|
||||||
inputAAC(pcData,iDataLen,uiStamp,(char *)_adtsHeader);
|
inputAAC(pcData,iDataLen,uiStamp,(char *)_adtsHeader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevChannel::inputAAC(const char *pcDataWithoutAdts,int iDataLen, uint32_t uiStamp,const char *pcAdtsHeader){
|
void DevChannel::inputAAC(const char *pcDataWithoutAdts,int iDataLen, uint32_t uiStamp,const char *pcAdtsHeader){
|
||||||
@ -152,54 +152,54 @@ void DevChannel::inputAAC(const char *pcDataWithoutAdts,int iDataLen, uint32_t u
|
|||||||
uiStamp = (uint32_t)_aTicker[1].elapsedTime();
|
uiStamp = (uint32_t)_aTicker[1].elapsedTime();
|
||||||
}
|
}
|
||||||
if(pcAdtsHeader + 7 == pcDataWithoutAdts){
|
if(pcAdtsHeader + 7 == pcDataWithoutAdts){
|
||||||
inputFrame(std::make_shared<AACFrameNoCacheAble>((char *)pcDataWithoutAdts - 7,iDataLen + 7,uiStamp,7));
|
inputFrame(std::make_shared<AACFrameNoCacheAble>((char *)pcDataWithoutAdts - 7,iDataLen + 7,uiStamp,7));
|
||||||
} else {
|
} else {
|
||||||
char *dataWithAdts = new char[iDataLen + 7];
|
char *dataWithAdts = new char[iDataLen + 7];
|
||||||
memcpy(dataWithAdts,pcAdtsHeader,7);
|
memcpy(dataWithAdts,pcAdtsHeader,7);
|
||||||
memcpy(dataWithAdts + 7 , pcDataWithoutAdts , iDataLen);
|
memcpy(dataWithAdts + 7 , pcDataWithoutAdts , iDataLen);
|
||||||
inputFrame(std::make_shared<AACFrameNoCacheAble>(dataWithAdts,iDataLen + 7,uiStamp,7));
|
inputFrame(std::make_shared<AACFrameNoCacheAble>(dataWithAdts,iDataLen + 7,uiStamp,7));
|
||||||
delete [] dataWithAdts;
|
delete [] dataWithAdts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DevChannel::initVideo(const VideoInfo& info) {
|
void DevChannel::initVideo(const VideoInfo& info) {
|
||||||
_video = std::make_shared<VideoInfo>(info);
|
_video = std::make_shared<VideoInfo>(info);
|
||||||
addTrack(std::make_shared<H264Track>());
|
addTrack(std::make_shared<H264Track>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevChannel::initH265Video(const VideoInfo &info){
|
void DevChannel::initH265Video(const VideoInfo &info){
|
||||||
_video = std::make_shared<VideoInfo>(info);
|
_video = std::make_shared<VideoInfo>(info);
|
||||||
addTrack(std::make_shared<H265Track>());
|
addTrack(std::make_shared<H265Track>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevChannel::initAudio(const AudioInfo& info) {
|
void DevChannel::initAudio(const AudioInfo& info) {
|
||||||
_audio = std::make_shared<AudioInfo>(info);
|
_audio = std::make_shared<AudioInfo>(info);
|
||||||
addTrack(std::make_shared<AACTrack>());
|
addTrack(std::make_shared<AACTrack>());
|
||||||
|
|
||||||
AACFrame adtsHeader;
|
AACFrame adtsHeader;
|
||||||
adtsHeader.syncword = 0x0FFF;
|
adtsHeader.syncword = 0x0FFF;
|
||||||
adtsHeader.id = 0;
|
adtsHeader.id = 0;
|
||||||
adtsHeader.layer = 0;
|
adtsHeader.layer = 0;
|
||||||
adtsHeader.protection_absent = 1;
|
adtsHeader.protection_absent = 1;
|
||||||
adtsHeader.profile = info.iProfile;//audioObjectType - 1;
|
adtsHeader.profile = info.iProfile;//audioObjectType - 1;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(auto rate : samplingFrequencyTable){
|
for(auto rate : samplingFrequencyTable){
|
||||||
if(rate == info.iSampleRate){
|
if(rate == info.iSampleRate){
|
||||||
adtsHeader.sf_index = i;
|
adtsHeader.sf_index = i;
|
||||||
};
|
};
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
adtsHeader.private_bit = 0;
|
adtsHeader.private_bit = 0;
|
||||||
adtsHeader.channel_configuration = info.iChannel;
|
adtsHeader.channel_configuration = info.iChannel;
|
||||||
adtsHeader.original = 0;
|
adtsHeader.original = 0;
|
||||||
adtsHeader.home = 0;
|
adtsHeader.home = 0;
|
||||||
adtsHeader.copyright_identification_bit = 0;
|
adtsHeader.copyright_identification_bit = 0;
|
||||||
adtsHeader.copyright_identification_start = 0;
|
adtsHeader.copyright_identification_start = 0;
|
||||||
adtsHeader.aac_frame_length = 7;
|
adtsHeader.aac_frame_length = 7;
|
||||||
adtsHeader.adts_buffer_fullness = 2047;
|
adtsHeader.adts_buffer_fullness = 2047;
|
||||||
adtsHeader.no_raw_data_blocks_in_frame = 0;
|
adtsHeader.no_raw_data_blocks_in_frame = 0;
|
||||||
writeAdtsHeader(adtsHeader,_adtsHeader);
|
writeAdtsHeader(adtsHeader,_adtsHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -51,16 +51,16 @@ namespace mediakit {
|
|||||||
|
|
||||||
class VideoInfo {
|
class VideoInfo {
|
||||||
public:
|
public:
|
||||||
int iWidth;
|
int iWidth;
|
||||||
int iHeight;
|
int iHeight;
|
||||||
float iFrameRate;
|
float iFrameRate;
|
||||||
};
|
};
|
||||||
class AudioInfo {
|
class AudioInfo {
|
||||||
public:
|
public:
|
||||||
int iChannel;
|
int iChannel;
|
||||||
int iSampleBit;
|
int iSampleBit;
|
||||||
int iSampleRate;
|
int iSampleRate;
|
||||||
int iProfile;
|
int iProfile;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,82 +68,82 @@ public:
|
|||||||
*/
|
*/
|
||||||
class DevChannel : public MultiMediaSourceMuxer{
|
class DevChannel : public MultiMediaSourceMuxer{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<DevChannel> Ptr;
|
typedef std::shared_ptr<DevChannel> Ptr;
|
||||||
//fDuration<=0为直播,否则为点播
|
//fDuration<=0为直播,否则为点播
|
||||||
DevChannel(const string &strVhost,
|
DevChannel(const string &strVhost,
|
||||||
const string &strApp,
|
const string &strApp,
|
||||||
const string &strId,
|
const string &strId,
|
||||||
float fDuration = 0,
|
float fDuration = 0,
|
||||||
bool bEanbleRtsp = true,
|
bool bEanbleRtsp = true,
|
||||||
bool bEanbleRtmp = true,
|
bool bEanbleRtmp = true,
|
||||||
bool bEanbleHls = true,
|
bool bEanbleHls = true,
|
||||||
bool bEnableMp4 = false);
|
bool bEnableMp4 = false);
|
||||||
|
|
||||||
virtual ~DevChannel();
|
virtual ~DevChannel();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化h264视频Track
|
* 初始化h264视频Track
|
||||||
* 相当于MultiMediaSourceMuxer::addTrack(H264Track::Ptr );
|
* 相当于MultiMediaSourceMuxer::addTrack(H264Track::Ptr );
|
||||||
* @param info
|
* @param info
|
||||||
*/
|
*/
|
||||||
void initVideo(const VideoInfo &info);
|
void initVideo(const VideoInfo &info);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化h265视频Track
|
* 初始化h265视频Track
|
||||||
* @param info
|
* @param info
|
||||||
*/
|
*/
|
||||||
void initH265Video(const VideoInfo &info);
|
void initH265Video(const VideoInfo &info);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化aac音频Track
|
* 初始化aac音频Track
|
||||||
* 相当于MultiMediaSourceMuxer::addTrack(AACTrack::Ptr );
|
* 相当于MultiMediaSourceMuxer::addTrack(AACTrack::Ptr );
|
||||||
* @param info
|
* @param info
|
||||||
*/
|
*/
|
||||||
void initAudio(const AudioInfo &info);
|
void initAudio(const AudioInfo &info);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入264帧
|
* 输入264帧
|
||||||
* @param pcData 264单帧数据指针
|
* @param pcData 264单帧数据指针
|
||||||
* @param iDataLen 数据指针长度
|
* @param iDataLen 数据指针长度
|
||||||
* @param dts 解码时间戳,单位毫秒;等于0时内部会自动生成时间戳
|
* @param dts 解码时间戳,单位毫秒;等于0时内部会自动生成时间戳
|
||||||
* @param pts 播放时间戳,单位毫秒;等于0时内部会赋值为dts
|
* @param pts 播放时间戳,单位毫秒;等于0时内部会赋值为dts
|
||||||
*/
|
*/
|
||||||
void inputH264(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0);
|
void inputH264(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入265帧
|
* 输入265帧
|
||||||
* @param pcData 265单帧数据指针
|
* @param pcData 265单帧数据指针
|
||||||
* @param iDataLen 数据指针长度
|
* @param iDataLen 数据指针长度
|
||||||
* @param dts 解码时间戳,单位毫秒;等于0时内部会自动生成时间戳
|
* @param dts 解码时间戳,单位毫秒;等于0时内部会自动生成时间戳
|
||||||
* @param pts 播放时间戳,单位毫秒;等于0时内部会赋值为dts
|
* @param pts 播放时间戳,单位毫秒;等于0时内部会赋值为dts
|
||||||
*/
|
*/
|
||||||
void inputH265(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0);
|
void inputH265(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入可能带adts头的aac帧
|
* 输入可能带adts头的aac帧
|
||||||
* @param pcDataWithAdts 可能带adts头的aac帧
|
* @param pcDataWithAdts 可能带adts头的aac帧
|
||||||
* @param iDataLen 帧数据长度
|
* @param iDataLen 帧数据长度
|
||||||
* @param uiStamp 时间戳,单位毫秒,等于0时内部会自动生成时间戳
|
* @param uiStamp 时间戳,单位毫秒,等于0时内部会自动生成时间戳
|
||||||
* @param withAdtsHeader 是否带adts头
|
* @param withAdtsHeader 是否带adts头
|
||||||
*/
|
*/
|
||||||
void inputAAC(const char *pcDataWithAdts, int iDataLen, uint32_t uiStamp, bool withAdtsHeader = true);
|
void inputAAC(const char *pcDataWithAdts, int iDataLen, uint32_t uiStamp, bool withAdtsHeader = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入不带adts头的aac帧
|
* 输入不带adts头的aac帧
|
||||||
* @param pcDataWithoutAdts 不带adts头的aac帧
|
* @param pcDataWithoutAdts 不带adts头的aac帧
|
||||||
* @param iDataLen 帧数据长度
|
* @param iDataLen 帧数据长度
|
||||||
* @param uiStamp 时间戳,单位毫秒
|
* @param uiStamp 时间戳,单位毫秒
|
||||||
* @param pcAdtsHeader adts头
|
* @param pcAdtsHeader adts头
|
||||||
*/
|
*/
|
||||||
void inputAAC(const char *pcDataWithoutAdts,int iDataLen, uint32_t uiStamp,const char *pcAdtsHeader);
|
void inputAAC(const char *pcDataWithoutAdts,int iDataLen, uint32_t uiStamp,const char *pcAdtsHeader);
|
||||||
|
|
||||||
#ifdef ENABLE_X264
|
#ifdef ENABLE_X264
|
||||||
/**
|
/**
|
||||||
* 输入yuv420p视频帧,内部会完成编码并调用inputH264方法
|
* 输入yuv420p视频帧,内部会完成编码并调用inputH264方法
|
||||||
* @param apcYuv
|
* @param apcYuv
|
||||||
* @param aiYuvLen
|
* @param aiYuvLen
|
||||||
* @param uiStamp
|
* @param uiStamp
|
||||||
*/
|
*/
|
||||||
void inputYUV(char *apcYuv[3], int aiYuvLen[3], uint32_t uiStamp);
|
void inputYUV(char *apcYuv[3], int aiYuvLen[3], uint32_t uiStamp);
|
||||||
#endif //ENABLE_X264
|
#endif //ENABLE_X264
|
||||||
|
|
||||||
@ -160,11 +160,11 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef ENABLE_X264
|
#ifdef ENABLE_X264
|
||||||
std::shared_ptr<H264Encoder> _pH264Enc;
|
std::shared_ptr<H264Encoder> _pH264Enc;
|
||||||
#endif //ENABLE_X264
|
#endif //ENABLE_X264
|
||||||
|
|
||||||
#ifdef ENABLE_FAAC
|
#ifdef ENABLE_FAAC
|
||||||
std::shared_ptr<AACEncoder> _pAacEnc;
|
std::shared_ptr<AACEncoder> _pAacEnc;
|
||||||
#endif //ENABLE_FAAC
|
#endif //ENABLE_FAAC
|
||||||
std::shared_ptr<VideoInfo> _video;
|
std::shared_ptr<VideoInfo> _video;
|
||||||
std::shared_ptr<AudioInfo> _audio;
|
std::shared_ptr<AudioInfo> _audio;
|
||||||
|
@ -42,15 +42,15 @@ bool loadIniConfig(const char *ini_path){
|
|||||||
}else{
|
}else{
|
||||||
ini = exePath() + ".ini";
|
ini = exePath() + ".ini";
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
mINI::Instance().parseFile(ini);
|
mINI::Instance().parseFile(ini);
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
||||||
return true;
|
return true;
|
||||||
}catch (std::exception &ex) {
|
}catch (std::exception &ex) {
|
||||||
InfoL << "dump ini file to:" << ini;
|
InfoL << "dump ini file to:" << ini;
|
||||||
mINI::Instance().dumpFile(ini);
|
mINI::Instance().dumpFile(ini);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
////////////广播名称///////////
|
////////////广播名称///////////
|
||||||
namespace Broadcast {
|
namespace Broadcast {
|
||||||
@ -90,12 +90,12 @@ onceToken token([](){
|
|||||||
mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
|
mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
|
||||||
mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
|
mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
|
||||||
mINI::Instance()[kEnableVhost] = 0;
|
mINI::Instance()[kEnableVhost] = 0;
|
||||||
mINI::Instance()[kUltraLowDelay] = 1;
|
mINI::Instance()[kUltraLowDelay] = 1;
|
||||||
mINI::Instance()[kAddMuteAudio] = 1;
|
mINI::Instance()[kAddMuteAudio] = 1;
|
||||||
mINI::Instance()[kResetWhenRePlay] = 1;
|
mINI::Instance()[kResetWhenRePlay] = 1;
|
||||||
mINI::Instance()[kPublishToRtxp] = 1;
|
mINI::Instance()[kPublishToRtxp] = 1;
|
||||||
mINI::Instance()[kPublishToHls] = 1;
|
mINI::Instance()[kPublishToHls] = 1;
|
||||||
mINI::Instance()[kPublishToMP4] = 0;
|
mINI::Instance()[kPublishToMP4] = 0;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
|
|
||||||
}//namespace General
|
}//namespace General
|
||||||
@ -117,26 +117,26 @@ const string kRootPath = HTTP_FIELD"rootPath";
|
|||||||
const string kNotFound = HTTP_FIELD"notFound";
|
const string kNotFound = HTTP_FIELD"notFound";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kSendBufSize] = 64 * 1024;
|
mINI::Instance()[kSendBufSize] = 64 * 1024;
|
||||||
mINI::Instance()[kMaxReqSize] = 4*1024;
|
mINI::Instance()[kMaxReqSize] = 4*1024;
|
||||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
mINI::Instance()[kCharSet] = "gb2312";
|
mINI::Instance()[kCharSet] = "gb2312";
|
||||||
#else
|
#else
|
||||||
mINI::Instance()[kCharSet] ="utf-8";
|
mINI::Instance()[kCharSet] ="utf-8";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mINI::Instance()[kRootPath] = "./www";
|
mINI::Instance()[kRootPath] = "./www";
|
||||||
mINI::Instance()[kNotFound] =
|
mINI::Instance()[kNotFound] =
|
||||||
"<html>"
|
"<html>"
|
||||||
"<head><title>404 Not Found</title></head>"
|
"<head><title>404 Not Found</title></head>"
|
||||||
"<body bgcolor=\"white\">"
|
"<body bgcolor=\"white\">"
|
||||||
"<center><h1>您访问的资源不存在!</h1></center>"
|
"<center><h1>您访问的资源不存在!</h1></center>"
|
||||||
"<hr><center>"
|
"<hr><center>"
|
||||||
SERVER_NAME
|
SERVER_NAME
|
||||||
"</center>"
|
"</center>"
|
||||||
"</body>"
|
"</body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
},nullptr);
|
},nullptr);
|
||||||
|
|
||||||
}//namespace Http
|
}//namespace Http
|
||||||
@ -147,7 +147,7 @@ namespace Shell {
|
|||||||
const string kMaxReqSize = SHELL_FIELD"maxReqSize";
|
const string kMaxReqSize = SHELL_FIELD"maxReqSize";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kMaxReqSize] = 1024;
|
mINI::Instance()[kMaxReqSize] = 1024;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace Shell
|
} //namespace Shell
|
||||||
|
|
||||||
@ -160,11 +160,11 @@ const string kKeepAliveSecond = RTSP_FIELD"keepAliveSecond";
|
|||||||
const string kDirectProxy = RTSP_FIELD"directProxy";
|
const string kDirectProxy = RTSP_FIELD"directProxy";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
//默认Md5方式认证
|
//默认Md5方式认证
|
||||||
mINI::Instance()[kAuthBasic] = 0;
|
mINI::Instance()[kAuthBasic] = 0;
|
||||||
mINI::Instance()[kHandshakeSecond] = 15;
|
mINI::Instance()[kHandshakeSecond] = 15;
|
||||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||||
mINI::Instance()[kDirectProxy] = 1;
|
mINI::Instance()[kDirectProxy] = 1;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace Rtsp
|
} //namespace Rtsp
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ const string kHandshakeSecond = RTMP_FIELD"handshakeSecond";
|
|||||||
const string kKeepAliveSecond = RTMP_FIELD"keepAliveSecond";
|
const string kKeepAliveSecond = RTMP_FIELD"keepAliveSecond";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kModifyStamp] = false;
|
mINI::Instance()[kModifyStamp] = false;
|
||||||
mINI::Instance()[kHandshakeSecond] = 15;
|
mINI::Instance()[kHandshakeSecond] = 15;
|
||||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
@ -197,11 +197,11 @@ const string kClearCount = RTP_FIELD"clearCount";
|
|||||||
const string kCycleMS = RTP_FIELD"cycleMS";
|
const string kCycleMS = RTP_FIELD"cycleMS";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kVideoMtuSize] = 1400;
|
mINI::Instance()[kVideoMtuSize] = 1400;
|
||||||
mINI::Instance()[kAudioMtuSize] = 600;
|
mINI::Instance()[kAudioMtuSize] = 600;
|
||||||
mINI::Instance()[kMaxRtpCount] = 50;
|
mINI::Instance()[kMaxRtpCount] = 50;
|
||||||
mINI::Instance()[kClearCount] = 10;
|
mINI::Instance()[kClearCount] = 10;
|
||||||
mINI::Instance()[kCycleMS] = 13*60*60*1000;
|
mINI::Instance()[kCycleMS] = 13*60*60*1000;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace Rtsp
|
} //namespace Rtsp
|
||||||
|
|
||||||
@ -216,9 +216,9 @@ const string kAddrMax = MULTI_FIELD"addrMax";
|
|||||||
const string kUdpTTL = MULTI_FIELD"udpTTL";
|
const string kUdpTTL = MULTI_FIELD"udpTTL";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kAddrMin] = "239.0.0.0";
|
mINI::Instance()[kAddrMin] = "239.0.0.0";
|
||||||
mINI::Instance()[kAddrMax] = "239.255.255.255";
|
mINI::Instance()[kAddrMax] = "239.255.255.255";
|
||||||
mINI::Instance()[kUdpTTL] = 64;
|
mINI::Instance()[kUdpTTL] = 64;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace MultiCast
|
} //namespace MultiCast
|
||||||
|
|
||||||
@ -241,13 +241,13 @@ const string kFastStart = RECORD_FIELD"fastStart";
|
|||||||
const string kFileRepeat = RECORD_FIELD"fileRepeat";
|
const string kFileRepeat = RECORD_FIELD"fileRepeat";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kAppName] = "record";
|
mINI::Instance()[kAppName] = "record";
|
||||||
mINI::Instance()[kSampleMS] = 500;
|
mINI::Instance()[kSampleMS] = 500;
|
||||||
mINI::Instance()[kFileSecond] = 60*60;
|
mINI::Instance()[kFileSecond] = 60*60;
|
||||||
mINI::Instance()[kFilePath] = "./www";
|
mINI::Instance()[kFilePath] = "./www";
|
||||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||||
mINI::Instance()[kFastStart] = false;
|
mINI::Instance()[kFastStart] = false;
|
||||||
mINI::Instance()[kFileRepeat] = false;
|
mINI::Instance()[kFileRepeat] = false;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace Record
|
} //namespace Record
|
||||||
|
|
||||||
@ -266,11 +266,11 @@ const string kFileBufSize = HLS_FIELD"fileBufSize";
|
|||||||
const string kFilePath = HLS_FIELD"filePath";
|
const string kFilePath = HLS_FIELD"filePath";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kSegmentDuration] = 2;
|
mINI::Instance()[kSegmentDuration] = 2;
|
||||||
mINI::Instance()[kSegmentNum] = 3;
|
mINI::Instance()[kSegmentNum] = 3;
|
||||||
mINI::Instance()[kSegmentRetain] = 5;
|
mINI::Instance()[kSegmentRetain] = 5;
|
||||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||||
mINI::Instance()[kFilePath] = "./www";
|
mINI::Instance()[kFilePath] = "./www";
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace Hls
|
} //namespace Hls
|
||||||
|
|
||||||
@ -286,9 +286,9 @@ const string kCheckSource = RTP_PROXY_FIELD"checkSource";
|
|||||||
const string kTimeoutSec = RTP_PROXY_FIELD"timeoutSec";
|
const string kTimeoutSec = RTP_PROXY_FIELD"timeoutSec";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kDumpDir] = "";
|
mINI::Instance()[kDumpDir] = "";
|
||||||
mINI::Instance()[kCheckSource] = 1;
|
mINI::Instance()[kCheckSource] = 1;
|
||||||
mINI::Instance()[kTimeoutSec] = 15;
|
mINI::Instance()[kTimeoutSec] = 15;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace RtpProxy
|
} //namespace RtpProxy
|
||||||
|
|
||||||
|
@ -88,24 +88,24 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
* Type==7:SPS frame
|
* Type==7:SPS frame
|
||||||
* Type==8:PPS frame
|
* Type==8:PPS frame
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
RTF3984 5.2节 Common Structure of the RTP Payload Format
|
RTF3984 5.2节 Common Structure of the RTP Payload Format
|
||||||
Table 1. Summary of NAL unit types and their payload structures
|
Table 1. Summary of NAL unit types and their payload structures
|
||||||
|
|
||||||
Type Packet Type name Section
|
Type Packet Type name Section
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
0 undefined -
|
0 undefined -
|
||||||
1-23 NAL unit Single NAL unit packet per H.264 5.6
|
1-23 NAL unit Single NAL unit packet per H.264 5.6
|
||||||
24 STAP-A Single-time aggregation packet 5.7.1
|
24 STAP-A Single-time aggregation packet 5.7.1
|
||||||
25 STAP-B Single-time aggregation packet 5.7.1
|
25 STAP-B Single-time aggregation packet 5.7.1
|
||||||
26 MTAP16 Multi-time aggregation packet 5.7.2
|
26 MTAP16 Multi-time aggregation packet 5.7.2
|
||||||
27 MTAP24 Multi-time aggregation packet 5.7.2
|
27 MTAP24 Multi-time aggregation packet 5.7.2
|
||||||
28 FU-A Fragmentation unit 5.8
|
28 FU-A Fragmentation unit 5.8
|
||||||
29 FU-B Fragmentation unit 5.8
|
29 FU-B Fragmentation unit 5.8
|
||||||
30-31 undefined -
|
30-31 undefined -
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const uint8_t *frame = (uint8_t *) rtppack->data() + rtppack->offset;
|
const uint8_t *frame = (uint8_t *) rtppack->data() + rtppack->offset;
|
||||||
int length = rtppack->size() - rtppack->offset;
|
int length = rtppack->size() - rtppack->offset;
|
||||||
NALU nal;
|
NALU nal;
|
||||||
|
@ -34,36 +34,36 @@ bool getHEVCInfo(const char * vps, int vps_len,const char * sps,int sps_len,int
|
|||||||
T_GetBitContext tGetBitBuf;
|
T_GetBitContext tGetBitBuf;
|
||||||
T_HEVCSPS tH265SpsInfo;
|
T_HEVCSPS tH265SpsInfo;
|
||||||
T_HEVCVPS tH265VpsInfo;
|
T_HEVCVPS tH265VpsInfo;
|
||||||
if ( vps_len > 2 ){
|
if ( vps_len > 2 ){
|
||||||
memset(&tGetBitBuf,0,sizeof(tGetBitBuf));
|
memset(&tGetBitBuf,0,sizeof(tGetBitBuf));
|
||||||
memset(&tH265VpsInfo,0,sizeof(tH265VpsInfo));
|
memset(&tH265VpsInfo,0,sizeof(tH265VpsInfo));
|
||||||
tGetBitBuf.pu8Buf = (uint8_t*)vps+2;
|
tGetBitBuf.pu8Buf = (uint8_t*)vps+2;
|
||||||
tGetBitBuf.iBufSize = vps_len-2;
|
tGetBitBuf.iBufSize = vps_len-2;
|
||||||
if(0 != h265DecVideoParameterSet((void *) &tGetBitBuf, &tH265VpsInfo)){
|
if(0 != h265DecVideoParameterSet((void *) &tGetBitBuf, &tH265VpsInfo)){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( sps_len > 2 ){
|
if ( sps_len > 2 ){
|
||||||
memset(&tGetBitBuf,0,sizeof(tGetBitBuf));
|
memset(&tGetBitBuf,0,sizeof(tGetBitBuf));
|
||||||
memset(&tH265SpsInfo,0,sizeof(tH265SpsInfo));
|
memset(&tH265SpsInfo,0,sizeof(tH265SpsInfo));
|
||||||
tGetBitBuf.pu8Buf = (uint8_t*)sps+2;
|
tGetBitBuf.pu8Buf = (uint8_t*)sps+2;
|
||||||
tGetBitBuf.iBufSize = sps_len-2;
|
tGetBitBuf.iBufSize = sps_len-2;
|
||||||
if(0 != h265DecSeqParameterSet((void *) &tGetBitBuf, &tH265SpsInfo)){
|
if(0 != h265DecSeqParameterSet((void *) &tGetBitBuf, &tH265SpsInfo)){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
h265GetWidthHeight(&tH265SpsInfo, &iVideoWidth, &iVideoHeight);
|
h265GetWidthHeight(&tH265SpsInfo, &iVideoWidth, &iVideoHeight);
|
||||||
iVideoFps = 0;
|
iVideoFps = 0;
|
||||||
h265GeFramerate(&tH265VpsInfo, &tH265SpsInfo, &iVideoFps);
|
h265GeFramerate(&tH265VpsInfo, &tH265SpsInfo, &iVideoFps);
|
||||||
// ErrorL << iVideoWidth << " " << iVideoHeight << " " << iVideoFps;
|
// ErrorL << iVideoWidth << " " << iVideoHeight << " " << iVideoFps;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getHEVCInfo(const string &strVps, const string &strSps, int &iVideoWidth, int &iVideoHeight, float &iVideoFps) {
|
bool getHEVCInfo(const string &strVps, const string &strSps, int &iVideoWidth, int &iVideoHeight, float &iVideoFps) {
|
||||||
return getHEVCInfo(strVps.data(),strVps.size(),strSps.data(),strSps.size(),iVideoWidth,iVideoHeight,iVideoFps);
|
return getHEVCInfo(strVps.data(),strVps.size(),strSps.data(),strSps.size(),iVideoWidth,iVideoHeight,iVideoFps);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sdp::Ptr H265Track::getSdp() {
|
Sdp::Ptr H265Track::getSdp() {
|
||||||
|
@ -202,7 +202,7 @@ public:
|
|||||||
_vps = vps.substr(vps_prefix_len);
|
_vps = vps.substr(vps_prefix_len);
|
||||||
_sps = sps.substr(sps_prefix_len);
|
_sps = sps.substr(sps_prefix_len);
|
||||||
_pps = pps.substr(pps_prefix_len);
|
_pps = pps.substr(pps_prefix_len);
|
||||||
onReady();
|
onReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -267,10 +267,10 @@ public:
|
|||||||
* @param frame 数据帧
|
* @param frame 数据帧
|
||||||
*/
|
*/
|
||||||
void inputFrame(const Frame::Ptr &frame) override{
|
void inputFrame(const Frame::Ptr &frame) override{
|
||||||
int type = H265_TYPE(*((uint8_t *)frame->data() + frame->prefixSize()));
|
int type = H265_TYPE(*((uint8_t *)frame->data() + frame->prefixSize()));
|
||||||
if(type == H265Frame::NAL_VPS){
|
if(type == H265Frame::NAL_VPS){
|
||||||
bool first_frame = true;
|
bool first_frame = true;
|
||||||
splitH264(frame->data() + frame->prefixSize(),
|
splitH264(frame->data() + frame->prefixSize(),
|
||||||
frame->size() - frame->prefixSize(),
|
frame->size() - frame->prefixSize(),
|
||||||
[&](const char *ptr, int len){
|
[&](const char *ptr, int len){
|
||||||
if(first_frame){
|
if(first_frame){
|
||||||
@ -288,9 +288,9 @@ public:
|
|||||||
inputFrame_l(sub_frame);
|
inputFrame_l(sub_frame);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
inputFrame_l(frame);
|
inputFrame_l(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -336,7 +336,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析sps获取宽高fps
|
* 解析sps获取宽高fps
|
||||||
*/
|
*/
|
||||||
void onReady(){
|
void onReady(){
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -434,7 +434,7 @@ typedef struct T_HEVCSPS {
|
|||||||
|
|
||||||
int iQpBdOffset;
|
int iQpBdOffset;
|
||||||
|
|
||||||
int iVuiPresent;
|
int iVuiPresent;
|
||||||
}T_HEVCSPS;
|
}T_HEVCSPS;
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,10 +137,10 @@ public:
|
|||||||
virtual ~TrackSource(){}
|
virtual ~TrackSource(){}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取全部的Track
|
* 获取全部的Track
|
||||||
* @param trackReady 是否获取全部已经准备好的Track
|
* @param trackReady 是否获取全部已经准备好的Track
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
virtual vector<Track::Ptr> getTracks(bool trackReady = true) const = 0;
|
virtual vector<Track::Ptr> getTracks(bool trackReady = true) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,7 +148,7 @@ protected:
|
|||||||
* 接收http回复完毕,
|
* 接收http回复完毕,
|
||||||
*/
|
*/
|
||||||
virtual void onResponseCompleted(){
|
virtual void onResponseCompleted(){
|
||||||
DebugL;
|
DebugL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,11 +29,11 @@
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
void HttpClientImp::onConnect(const SockException &ex) {
|
void HttpClientImp::onConnect(const SockException &ex) {
|
||||||
if(!_isHttps){
|
if(!_isHttps){
|
||||||
HttpClient::onConnect(ex);
|
HttpClient::onConnect(ex);
|
||||||
} else {
|
} else {
|
||||||
TcpClientWithSSL<HttpClient>::onConnect(ex);
|
TcpClientWithSSL<HttpClient>::onConnect(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,11 +36,11 @@ namespace mediakit {
|
|||||||
|
|
||||||
class HttpClientImp: public TcpClientWithSSL<HttpClient> {
|
class HttpClientImp: public TcpClientWithSSL<HttpClient> {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<HttpClientImp> Ptr;
|
typedef std::shared_ptr<HttpClientImp> Ptr;
|
||||||
HttpClientImp() {}
|
HttpClientImp() {}
|
||||||
virtual ~HttpClientImp() {}
|
virtual ~HttpClientImp() {}
|
||||||
protected:
|
protected:
|
||||||
void onConnect(const SockException &ex) override ;
|
void onConnect(const SockException &ex) override ;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -36,75 +36,75 @@ HttpDownloader::HttpDownloader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HttpDownloader::~HttpDownloader() {
|
HttpDownloader::~HttpDownloader() {
|
||||||
closeFile();
|
closeFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpDownloader::startDownload(const string& url, const string& filePath,bool bAppend,float timeOutSecond) {
|
void HttpDownloader::startDownload(const string& url, const string& filePath,bool bAppend,float timeOutSecond) {
|
||||||
_filePath = filePath;
|
_filePath = filePath;
|
||||||
if(_filePath.empty()){
|
if(_filePath.empty()){
|
||||||
_filePath = exeDir() + "HttpDownloader/" + MD5(url).hexdigest();
|
_filePath = exeDir() + "HttpDownloader/" + MD5(url).hexdigest();
|
||||||
}
|
}
|
||||||
_saveFile = File::createfile_file(_filePath.data(),bAppend ? "ab" : "wb");
|
_saveFile = File::createfile_file(_filePath.data(),bAppend ? "ab" : "wb");
|
||||||
if(!_saveFile){
|
if(!_saveFile){
|
||||||
auto strErr = StrPrinter << "打开文件失败:" << filePath << endl;
|
auto strErr = StrPrinter << "打开文件失败:" << filePath << endl;
|
||||||
throw std::runtime_error(strErr);
|
throw std::runtime_error(strErr);
|
||||||
}
|
}
|
||||||
_bDownloadSuccess = false;
|
_bDownloadSuccess = false;
|
||||||
if(bAppend){
|
if(bAppend){
|
||||||
auto currentLen = ftell(_saveFile);
|
auto currentLen = ftell(_saveFile);
|
||||||
if(currentLen){
|
if(currentLen){
|
||||||
//最少续传一个字节,怕遇到http 416的错误
|
//最少续传一个字节,怕遇到http 416的错误
|
||||||
currentLen -= 1;
|
currentLen -= 1;
|
||||||
fseek(_saveFile,-1,SEEK_CUR);
|
fseek(_saveFile,-1,SEEK_CUR);
|
||||||
}
|
}
|
||||||
addHeader("Range", StrPrinter << "bytes=" << currentLen << "-" << endl);
|
addHeader("Range", StrPrinter << "bytes=" << currentLen << "-" << endl);
|
||||||
}
|
}
|
||||||
setMethod("GET");
|
setMethod("GET");
|
||||||
sendRequest(url,timeOutSecond);
|
sendRequest(url,timeOutSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t HttpDownloader::onResponseHeader(const string& status,const HttpHeader& headers) {
|
int64_t HttpDownloader::onResponseHeader(const string& status,const HttpHeader& headers) {
|
||||||
if(status != "200" && status != "206"){
|
if(status != "200" && status != "206"){
|
||||||
//失败
|
//失败
|
||||||
shutdown(SockException(Err_shutdown,StrPrinter << "Http Status:" << status));
|
shutdown(SockException(Err_shutdown,StrPrinter << "Http Status:" << status));
|
||||||
}
|
}
|
||||||
//后续全部是content
|
//后续全部是content
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpDownloader::onResponseBody(const char* buf, int64_t size, int64_t recvedSize, int64_t totalSize) {
|
void HttpDownloader::onResponseBody(const char* buf, int64_t size, int64_t recvedSize, int64_t totalSize) {
|
||||||
if(_saveFile){
|
if(_saveFile){
|
||||||
fwrite(buf,size,1,_saveFile);
|
fwrite(buf,size,1,_saveFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpDownloader::onResponseCompleted() {
|
void HttpDownloader::onResponseCompleted() {
|
||||||
closeFile();
|
closeFile();
|
||||||
//InfoL << "md5Sum:" << getMd5Sum(_filePath);
|
//InfoL << "md5Sum:" << getMd5Sum(_filePath);
|
||||||
_bDownloadSuccess = true;
|
_bDownloadSuccess = true;
|
||||||
if(_onResult){
|
if(_onResult){
|
||||||
_onResult(Err_success,"success",_filePath);
|
_onResult(Err_success,"success",_filePath);
|
||||||
_onResult = nullptr;
|
_onResult = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpDownloader::onDisconnect(const SockException &ex) {
|
void HttpDownloader::onDisconnect(const SockException &ex) {
|
||||||
closeFile();
|
closeFile();
|
||||||
if(!_bDownloadSuccess){
|
if(!_bDownloadSuccess){
|
||||||
File::delete_file(_filePath.data());
|
File::delete_file(_filePath.data());
|
||||||
}
|
}
|
||||||
if(_onResult){
|
if(_onResult){
|
||||||
_onResult(ex.getErrCode(),ex.what(),_filePath);
|
_onResult(ex.getErrCode(),ex.what(),_filePath);
|
||||||
_onResult = nullptr;
|
_onResult = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpDownloader::closeFile() {
|
void HttpDownloader::closeFile() {
|
||||||
if(_saveFile){
|
if(_saveFile){
|
||||||
fflush(_saveFile);
|
fflush(_saveFile);
|
||||||
fclose(_saveFile);
|
fclose(_saveFile);
|
||||||
_saveFile = nullptr;
|
_saveFile = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,30 +33,30 @@ namespace mediakit {
|
|||||||
|
|
||||||
class HttpDownloader: public HttpClientImp {
|
class HttpDownloader: public HttpClientImp {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<HttpDownloader> Ptr;
|
typedef std::shared_ptr<HttpDownloader> Ptr;
|
||||||
typedef std::function<void(ErrCode code,const string &errMsg,const string &filePath)> onDownloadResult;
|
typedef std::function<void(ErrCode code,const string &errMsg,const string &filePath)> onDownloadResult;
|
||||||
HttpDownloader();
|
HttpDownloader();
|
||||||
virtual ~HttpDownloader();
|
virtual ~HttpDownloader();
|
||||||
//开始下载文件,默认断点续传方式下载
|
//开始下载文件,默认断点续传方式下载
|
||||||
void startDownload(const string &url,const string &filePath = "",bool bAppend = false, float timeOutSecond = 10 );
|
void startDownload(const string &url,const string &filePath = "",bool bAppend = false, float timeOutSecond = 10 );
|
||||||
void startDownload(const string &url,const onDownloadResult &cb,float timeOutSecond = 10){
|
void startDownload(const string &url,const onDownloadResult &cb,float timeOutSecond = 10){
|
||||||
setOnResult(cb);
|
setOnResult(cb);
|
||||||
startDownload(url,"",false,timeOutSecond);
|
startDownload(url,"",false,timeOutSecond);
|
||||||
}
|
}
|
||||||
void setOnResult(const onDownloadResult &cb){
|
void setOnResult(const onDownloadResult &cb){
|
||||||
_onResult = cb;
|
_onResult = cb;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
int64_t onResponseHeader(const string &status,const HttpHeader &headers) override;
|
int64_t onResponseHeader(const string &status,const HttpHeader &headers) override;
|
||||||
void onResponseBody(const char *buf,int64_t size,int64_t recvedSize,int64_t totalSize) override;
|
void onResponseBody(const char *buf,int64_t size,int64_t recvedSize,int64_t totalSize) override;
|
||||||
void onResponseCompleted() override;
|
void onResponseCompleted() override;
|
||||||
void onDisconnect(const SockException &ex) override;
|
void onDisconnect(const SockException &ex) override;
|
||||||
void closeFile();
|
void closeFile();
|
||||||
private:
|
private:
|
||||||
FILE *_saveFile = nullptr;
|
FILE *_saveFile = nullptr;
|
||||||
string _filePath;
|
string _filePath;
|
||||||
onDownloadResult _onResult;
|
onDownloadResult _onResult;
|
||||||
bool _bDownloadSuccess = false;
|
bool _bDownloadSuccess = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -45,8 +45,8 @@ HttpSession::HttpSession(const Socket::Ptr &pSock) : TcpSession(pSock) {
|
|||||||
TraceP(this);
|
TraceP(this);
|
||||||
GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond);
|
GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond);
|
||||||
pSock->setSendTimeOutSecond(keep_alive_sec);
|
pSock->setSendTimeOutSecond(keep_alive_sec);
|
||||||
//起始接收buffer缓存设置为4K,节省内存
|
//起始接收buffer缓存设置为4K,节省内存
|
||||||
pSock->setReadBuffer(std::make_shared<BufferRaw>(4 * 1024));
|
pSock->setReadBuffer(std::make_shared<BufferRaw>(4 * 1024));
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpSession::~HttpSession() {
|
HttpSession::~HttpSession() {
|
||||||
@ -61,48 +61,48 @@ void HttpSession::Handle_Req_HEAD(int64_t &content_len){
|
|||||||
}
|
}
|
||||||
|
|
||||||
int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) {
|
int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) {
|
||||||
typedef void (HttpSession::*HttpCMDHandle)(int64_t &);
|
typedef void (HttpSession::*HttpCMDHandle)(int64_t &);
|
||||||
static unordered_map<string, HttpCMDHandle> s_func_map;
|
static unordered_map<string, HttpCMDHandle> s_func_map;
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
s_func_map.emplace("GET",&HttpSession::Handle_Req_GET);
|
s_func_map.emplace("GET",&HttpSession::Handle_Req_GET);
|
||||||
s_func_map.emplace("POST",&HttpSession::Handle_Req_POST);
|
s_func_map.emplace("POST",&HttpSession::Handle_Req_POST);
|
||||||
s_func_map.emplace("HEAD",&HttpSession::Handle_Req_HEAD);
|
s_func_map.emplace("HEAD",&HttpSession::Handle_Req_HEAD);
|
||||||
}, nullptr);
|
}, nullptr);
|
||||||
|
|
||||||
_parser.Parse(header);
|
_parser.Parse(header);
|
||||||
urlDecode(_parser);
|
urlDecode(_parser);
|
||||||
string cmd = _parser.Method();
|
string cmd = _parser.Method();
|
||||||
auto it = s_func_map.find(cmd);
|
auto it = s_func_map.find(cmd);
|
||||||
if (it == s_func_map.end()) {
|
if (it == s_func_map.end()) {
|
||||||
WarnL << "不支持该命令:" << cmd;
|
WarnL << "不支持该命令:" << cmd;
|
||||||
sendResponse("405 Not Allowed", true);
|
sendResponse("405 Not Allowed", true);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//跨域
|
//跨域
|
||||||
_origin = _parser["Origin"];
|
_origin = _parser["Origin"];
|
||||||
|
|
||||||
//默认后面数据不是content而是header
|
//默认后面数据不是content而是header
|
||||||
int64_t content_len = 0;
|
int64_t content_len = 0;
|
||||||
auto &fun = it->second;
|
auto &fun = it->second;
|
||||||
try {
|
try {
|
||||||
(this->*fun)(content_len);
|
(this->*fun)(content_len);
|
||||||
}catch (exception &ex){
|
}catch (exception &ex){
|
||||||
shutdown(SockException(Err_shutdown,ex.what()));
|
shutdown(SockException(Err_shutdown,ex.what()));
|
||||||
}
|
}
|
||||||
|
|
||||||
//清空解析器节省内存
|
//清空解析器节省内存
|
||||||
_parser.Clear();
|
_parser.Clear();
|
||||||
//返回content长度
|
//返回content长度
|
||||||
return content_len;
|
return content_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onRecvContent(const char *data,uint64_t len) {
|
void HttpSession::onRecvContent(const char *data,uint64_t len) {
|
||||||
if(_contentCallBack){
|
if(_contentCallBack){
|
||||||
if(!_contentCallBack(data,len)){
|
if(!_contentCallBack(data,len)){
|
||||||
_contentCallBack = nullptr;
|
_contentCallBack = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
||||||
@ -140,25 +140,25 @@ void HttpSession::onManager() {
|
|||||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
||||||
|
|
||||||
if(_ticker.elapsedTime() > keepAliveSec * 1000){
|
if(_ticker.elapsedTime() > keepAliveSec * 1000){
|
||||||
//1分钟超时
|
//1分钟超时
|
||||||
shutdown(SockException(Err_timeout,"session timeouted"));
|
shutdown(SockException(Err_timeout,"session timeouted"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpSession::checkWebSocket(){
|
bool HttpSession::checkWebSocket(){
|
||||||
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
||||||
if(Sec_WebSocket_Key.empty()){
|
if(Sec_WebSocket_Key.empty()){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
|
auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
|
||||||
|
|
||||||
KeyValue headerOut;
|
KeyValue headerOut;
|
||||||
headerOut["Upgrade"] = "websocket";
|
headerOut["Upgrade"] = "websocket";
|
||||||
headerOut["Connection"] = "Upgrade";
|
headerOut["Connection"] = "Upgrade";
|
||||||
headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept;
|
headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept;
|
||||||
if(!_parser["Sec-WebSocket-Protocol"].empty()){
|
if(!_parser["Sec-WebSocket-Protocol"].empty()){
|
||||||
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
|
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto res_cb = [this,headerOut](){
|
auto res_cb = [this,headerOut](){
|
||||||
_flv_over_websocket = true;
|
_flv_over_websocket = true;
|
||||||
@ -177,28 +177,28 @@ bool HttpSession::checkWebSocket(){
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
sendResponse("101 Switching Protocols",false, nullptr,headerOut);
|
sendResponse("101 Switching Protocols",false, nullptr,headerOut);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
||||||
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
|
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
|
||||||
bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||||
auto pos = strrchr(_parser.Url().data(),'.');
|
auto pos = strrchr(_parser.Url().data(),'.');
|
||||||
if(!pos){
|
if(!pos){
|
||||||
//未找到".flv"后缀
|
//未找到".flv"后缀
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(strcasecmp(pos,".flv") != 0){
|
|
||||||
//未找到".flv"后缀
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//这是个.flv的流
|
|
||||||
_mediaInfo.parse(string(RTMP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl());
|
|
||||||
if(_mediaInfo._app.empty() || _mediaInfo._streamid.size() < 5){
|
|
||||||
//url不合法
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if(strcasecmp(pos,".flv") != 0){
|
||||||
|
//未找到".flv"后缀
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//这是个.flv的流
|
||||||
|
_mediaInfo.parse(string(RTMP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl());
|
||||||
|
if(_mediaInfo._app.empty() || _mediaInfo._streamid.size() < 5){
|
||||||
|
//url不合法
|
||||||
|
return false;
|
||||||
|
}
|
||||||
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀
|
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀
|
||||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||||
|
|
||||||
@ -270,21 +270,21 @@ void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::Handle_Req_GET_l(int64_t &content_len, bool sendBody) {
|
void HttpSession::Handle_Req_GET_l(int64_t &content_len, bool sendBody) {
|
||||||
//先看看是否为WebSocket请求
|
//先看看是否为WebSocket请求
|
||||||
if(checkWebSocket()){
|
if(checkWebSocket()){
|
||||||
content_len = -1;
|
content_len = -1;
|
||||||
_contentCallBack = [this](const char *data,uint64_t len){
|
_contentCallBack = [this](const char *data,uint64_t len){
|
||||||
WebSocketSplitter::decode((uint8_t *)data,len);
|
WebSocketSplitter::decode((uint8_t *)data,len);
|
||||||
//_contentCallBack是可持续的,后面还要处理后续数据
|
//_contentCallBack是可持续的,后面还要处理后续数据
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(emitHttpEvent(false)){
|
if(emitHttpEvent(false)){
|
||||||
//拦截http api事件
|
//拦截http api事件
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(checkLiveFlvStream()){
|
if(checkLiveFlvStream()){
|
||||||
//拦截http-flv播放器
|
//拦截http-flv播放器
|
||||||
@ -498,111 +498,111 @@ void HttpSession::sendResponse(const char *pcStatus,
|
|||||||
}
|
}
|
||||||
|
|
||||||
string HttpSession::urlDecode(const string &str){
|
string HttpSession::urlDecode(const string &str){
|
||||||
auto ret = strCoding::UrlDecode(str);
|
auto ret = strCoding::UrlDecode(str);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||||
bool isGb2312 = !strcasecmp(charSet.data(), "gb2312");
|
bool isGb2312 = !strcasecmp(charSet.data(), "gb2312");
|
||||||
if (isGb2312) {
|
if (isGb2312) {
|
||||||
ret = strCoding::UTF8ToGB2312(ret);
|
ret = strCoding::UTF8ToGB2312(ret);
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::urlDecode(Parser &parser){
|
void HttpSession::urlDecode(Parser &parser){
|
||||||
parser.setUrl(urlDecode(parser.Url()));
|
parser.setUrl(urlDecode(parser.Url()));
|
||||||
for(auto &pr : _parser.getUrlArgs()){
|
for(auto &pr : _parser.getUrlArgs()){
|
||||||
const_cast<string &>(pr.second) = urlDecode(pr.second);
|
const_cast<string &>(pr.second) = urlDecode(pr.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpSession::emitHttpEvent(bool doInvoke){
|
bool HttpSession::emitHttpEvent(bool doInvoke){
|
||||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||||
/////////////////////异步回复Invoker///////////////////////////////
|
/////////////////////异步回复Invoker///////////////////////////////
|
||||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||||
HttpResponseInvoker invoker = [weakSelf,bClose](const string &codeOut, const KeyValue &headerOut, const HttpBody::Ptr &body){
|
HttpResponseInvoker invoker = [weakSelf,bClose](const string &codeOut, const KeyValue &headerOut, const HttpBody::Ptr &body){
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strongSelf->async([weakSelf,bClose,codeOut,headerOut,body]() {
|
strongSelf->async([weakSelf,bClose,codeOut,headerOut,body]() {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
//本对象已经销毁
|
//本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strongSelf->sendResponse(codeOut.data(), bClose, nullptr, headerOut, body);
|
strongSelf->sendResponse(codeOut.data(), bClose, nullptr, headerOut, body);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
///////////////////广播HTTP事件///////////////////////////
|
///////////////////广播HTTP事件///////////////////////////
|
||||||
bool consumed = false;//该事件是否被消费
|
bool consumed = false;//该事件是否被消费
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,*this);
|
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,*this);
|
||||||
if(!consumed && doInvoke){
|
if(!consumed && doInvoke){
|
||||||
//该事件无人消费,所以返回404
|
//该事件无人消费,所以返回404
|
||||||
invoker("404 Not Found",KeyValue(), HttpBody::Ptr());
|
invoker("404 Not Found",KeyValue(), HttpBody::Ptr());
|
||||||
}
|
}
|
||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
||||||
GET_CONFIG(uint64_t,maxReqSize,Http::kMaxReqSize);
|
GET_CONFIG(uint64_t,maxReqSize,Http::kMaxReqSize);
|
||||||
|
|
||||||
int64_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
|
int64_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
|
||||||
|
|
||||||
if(totalContentLen == 0){
|
if(totalContentLen == 0){
|
||||||
//content为空
|
//content为空
|
||||||
//emitHttpEvent内部会选择是否关闭连接
|
//emitHttpEvent内部会选择是否关闭连接
|
||||||
emitHttpEvent(true);
|
emitHttpEvent(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//根据Content-Length设置接收缓存大小
|
//根据Content-Length设置接收缓存大小
|
||||||
if(totalContentLen > 0){
|
if(totalContentLen > 0){
|
||||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(MIN(totalContentLen + 1,256 * 1024)));
|
_sock->setReadBuffer(std::make_shared<BufferRaw>(MIN(totalContentLen + 1,256 * 1024)));
|
||||||
}else{
|
}else{
|
||||||
//不定长度的Content-Length
|
//不定长度的Content-Length
|
||||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(totalContentLen > 0 && totalContentLen < maxReqSize ){
|
if(totalContentLen > 0 && totalContentLen < maxReqSize ){
|
||||||
//返回固定长度的content
|
//返回固定长度的content
|
||||||
content_len = totalContentLen;
|
content_len = totalContentLen;
|
||||||
auto parserCopy = _parser;
|
auto parserCopy = _parser;
|
||||||
_contentCallBack = [this,parserCopy](const char *data,uint64_t len){
|
_contentCallBack = [this,parserCopy](const char *data,uint64_t len){
|
||||||
//恢复http头
|
//恢复http头
|
||||||
_parser = parserCopy;
|
_parser = parserCopy;
|
||||||
//设置content
|
//设置content
|
||||||
_parser.setContent(string(data,len));
|
_parser.setContent(string(data,len));
|
||||||
//触发http事件,emitHttpEvent内部会选择是否关闭连接
|
//触发http事件,emitHttpEvent内部会选择是否关闭连接
|
||||||
emitHttpEvent(true);
|
emitHttpEvent(true);
|
||||||
//清空数据,节省内存
|
//清空数据,节省内存
|
||||||
_parser.Clear();
|
_parser.Clear();
|
||||||
//content已经接收完毕
|
//content已经接收完毕
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
}else{
|
}else{
|
||||||
//返回不固定长度的content
|
//返回不固定长度的content
|
||||||
content_len = -1;
|
content_len = -1;
|
||||||
auto parserCopy = _parser;
|
auto parserCopy = _parser;
|
||||||
std::shared_ptr<uint64_t> recvedContentLen = std::make_shared<uint64_t>(0);
|
std::shared_ptr<uint64_t> recvedContentLen = std::make_shared<uint64_t>(0);
|
||||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||||
|
|
||||||
_contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,uint64_t len){
|
_contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,uint64_t len){
|
||||||
*(recvedContentLen) += len;
|
*(recvedContentLen) += len;
|
||||||
|
|
||||||
onRecvUnlimitedContent(parserCopy,data,len,totalContentLen,*(recvedContentLen));
|
onRecvUnlimitedContent(parserCopy,data,len,totalContentLen,*(recvedContentLen));
|
||||||
|
|
||||||
if(*(recvedContentLen) < totalContentLen){
|
if(*(recvedContentLen) < totalContentLen){
|
||||||
//数据还没接收完毕
|
//数据还没接收完毕
|
||||||
//_contentCallBack是可持续的,后面还要处理后续content数据
|
//_contentCallBack是可持续的,后面还要处理后续content数据
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//数据接收完毕
|
//数据接收完毕
|
||||||
if(!bClose){
|
if(!bClose){
|
||||||
//keep-alive类型连接
|
//keep-alive类型连接
|
||||||
//content接收完毕,后续都是http header
|
//content接收完毕,后续都是http header
|
||||||
setContentLen(0);
|
setContentLen(0);
|
||||||
//content已经接收完毕
|
//content已经接收完毕
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -611,9 +611,9 @@ void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
|||||||
shutdown(SockException(Err_shutdown,"recv http content completed"));
|
shutdown(SockException(Err_shutdown,"recv http content completed"));
|
||||||
//content已经接收完毕
|
//content已经接收完毕
|
||||||
return false ;
|
return false ;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
//有后续content数据要处理,暂时不关闭连接
|
//有后续content数据要处理,暂时不关闭连接
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::sendNotFound(bool bClose) {
|
void HttpSession::sendNotFound(bool bClose) {
|
||||||
@ -632,7 +632,7 @@ void HttpSession::setSocketFlags(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onWrite(const Buffer::Ptr &buffer) {
|
void HttpSession::onWrite(const Buffer::Ptr &buffer) {
|
||||||
_ticker.resetTime();
|
_ticker.resetTime();
|
||||||
if(!_flv_over_websocket){
|
if(!_flv_over_websocket){
|
||||||
_ui64TotalBytes += buffer->size();
|
_ui64TotalBytes += buffer->size();
|
||||||
send(buffer);
|
send(buffer);
|
||||||
@ -653,11 +653,11 @@ void HttpSession::onWebSocketEncodeData(const Buffer::Ptr &buffer){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onDetach() {
|
void HttpSession::onDetach() {
|
||||||
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr(){
|
std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr(){
|
||||||
return dynamic_pointer_cast<FlvMuxer>(shared_from_this());
|
return dynamic_pointer_cast<FlvMuxer>(shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -45,51 +45,51 @@ class HttpSession: public TcpSession,
|
|||||||
public HttpRequestSplitter,
|
public HttpRequestSplitter,
|
||||||
public WebSocketSplitter {
|
public WebSocketSplitter {
|
||||||
public:
|
public:
|
||||||
typedef StrCaseMap KeyValue;
|
typedef StrCaseMap KeyValue;
|
||||||
typedef HttpResponseInvokerImp HttpResponseInvoker;
|
typedef HttpResponseInvokerImp HttpResponseInvoker;
|
||||||
friend class AsyncSender;
|
friend class AsyncSender;
|
||||||
/**
|
/**
|
||||||
* @param errMsg 如果为空,则代表鉴权通过,否则为错误提示
|
* @param errMsg 如果为空,则代表鉴权通过,否则为错误提示
|
||||||
* @param accessPath 运行或禁止访问的根目录
|
* @param accessPath 运行或禁止访问的根目录
|
||||||
* @param cookieLifeSecond 鉴权cookie有效期
|
* @param cookieLifeSecond 鉴权cookie有效期
|
||||||
**/
|
**/
|
||||||
typedef std::function<void(const string &errMsg,const string &accessPath, int cookieLifeSecond)> HttpAccessPathInvoker;
|
typedef std::function<void(const string &errMsg,const string &accessPath, int cookieLifeSecond)> HttpAccessPathInvoker;
|
||||||
|
|
||||||
HttpSession(const Socket::Ptr &pSock);
|
HttpSession(const Socket::Ptr &pSock);
|
||||||
virtual ~HttpSession();
|
virtual ~HttpSession();
|
||||||
|
|
||||||
virtual void onRecv(const Buffer::Ptr &) override;
|
virtual void onRecv(const Buffer::Ptr &) override;
|
||||||
virtual void onError(const SockException &err) override;
|
virtual void onError(const SockException &err) override;
|
||||||
virtual void onManager() override;
|
virtual void onManager() override;
|
||||||
static string urlDecode(const string &str);
|
static string urlDecode(const string &str);
|
||||||
protected:
|
protected:
|
||||||
//FlvMuxer override
|
//FlvMuxer override
|
||||||
void onWrite(const Buffer::Ptr &data) override ;
|
void onWrite(const Buffer::Ptr &data) override ;
|
||||||
void onDetach() override;
|
void onDetach() override;
|
||||||
std::shared_ptr<FlvMuxer> getSharedPtr() override;
|
std::shared_ptr<FlvMuxer> getSharedPtr() override;
|
||||||
|
|
||||||
//HttpRequestSplitter override
|
//HttpRequestSplitter override
|
||||||
int64_t onRecvHeader(const char *data,uint64_t len) override;
|
int64_t onRecvHeader(const char *data,uint64_t len) override;
|
||||||
void onRecvContent(const char *data,uint64_t len) override;
|
void onRecvContent(const char *data,uint64_t len) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重载之用于处理不定长度的content
|
* 重载之用于处理不定长度的content
|
||||||
* 这个函数可用于处理大文件上传、http-flv推流
|
* 这个函数可用于处理大文件上传、http-flv推流
|
||||||
* @param header http请求头
|
* @param header http请求头
|
||||||
* @param data content分片数据
|
* @param data content分片数据
|
||||||
* @param len content分片数据大小
|
* @param len content分片数据大小
|
||||||
* @param totalSize content总大小,如果为0则是不限长度content
|
* @param totalSize content总大小,如果为0则是不限长度content
|
||||||
* @param recvedSize 已收数据大小
|
* @param recvedSize 已收数据大小
|
||||||
*/
|
*/
|
||||||
virtual void onRecvUnlimitedContent(const Parser &header,
|
virtual void onRecvUnlimitedContent(const Parser &header,
|
||||||
const char *data,
|
const char *data,
|
||||||
uint64_t len,
|
uint64_t len,
|
||||||
uint64_t totalSize,
|
uint64_t totalSize,
|
||||||
uint64_t recvedSize){
|
uint64_t recvedSize){
|
||||||
shutdown(SockException(Err_shutdown,"http post content is too huge,default closed"));
|
shutdown(SockException(Err_shutdown,"http post content is too huge,default closed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* websocket客户端连接上事件
|
* websocket客户端连接上事件
|
||||||
* @param header http头
|
* @param header http头
|
||||||
* @return true代表允许websocket连接,否则拒绝
|
* @return true代表允许websocket连接,否则拒绝
|
||||||
@ -99,31 +99,31 @@ protected:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//WebSocketSplitter override
|
//WebSocketSplitter override
|
||||||
/**
|
/**
|
||||||
* 发送数据进行websocket协议打包后回调
|
* 发送数据进行websocket协议打包后回调
|
||||||
* @param buffer websocket协议数据
|
* @param buffer websocket协议数据
|
||||||
*/
|
*/
|
||||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override;
|
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override;
|
||||||
private:
|
private:
|
||||||
void Handle_Req_GET(int64_t &content_len);
|
void Handle_Req_GET(int64_t &content_len);
|
||||||
void Handle_Req_GET_l(int64_t &content_len, bool sendBody);
|
void Handle_Req_GET_l(int64_t &content_len, bool sendBody);
|
||||||
void Handle_Req_POST(int64_t &content_len);
|
void Handle_Req_POST(int64_t &content_len);
|
||||||
void Handle_Req_HEAD(int64_t &content_len);
|
void Handle_Req_HEAD(int64_t &content_len);
|
||||||
|
|
||||||
bool checkLiveFlvStream(const function<void()> &cb = nullptr);
|
bool checkLiveFlvStream(const function<void()> &cb = nullptr);
|
||||||
bool checkWebSocket();
|
bool checkWebSocket();
|
||||||
bool emitHttpEvent(bool doInvoke);
|
bool emitHttpEvent(bool doInvoke);
|
||||||
void urlDecode(Parser &parser);
|
void urlDecode(Parser &parser);
|
||||||
void sendNotFound(bool bClose);
|
void sendNotFound(bool bClose);
|
||||||
void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr,
|
void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr,
|
||||||
const HttpSession::KeyValue &header = HttpSession::KeyValue(),
|
const HttpSession::KeyValue &header = HttpSession::KeyValue(),
|
||||||
const HttpBody::Ptr &body = nullptr,bool is_http_flv = false);
|
const HttpBody::Ptr &body = nullptr,bool is_http_flv = false);
|
||||||
|
|
||||||
//设置socket标志
|
//设置socket标志
|
||||||
void setSocketFlags();
|
void setSocketFlags();
|
||||||
private:
|
private:
|
||||||
string _origin;
|
string _origin;
|
||||||
Parser _parser;
|
Parser _parser;
|
||||||
Ticker _ticker;
|
Ticker _ticker;
|
||||||
//消耗的总流量
|
//消耗的总流量
|
||||||
@ -132,8 +132,8 @@ private:
|
|||||||
MediaInfo _mediaInfo;
|
MediaInfo _mediaInfo;
|
||||||
//处理content数据的callback
|
//处理content数据的callback
|
||||||
function<bool (const char *data,uint64_t len) > _contentCallBack;
|
function<bool (const char *data,uint64_t len) > _contentCallBack;
|
||||||
bool _flv_over_websocket = false;
|
bool _flv_over_websocket = false;
|
||||||
bool _is_flv_stream = false;
|
bool _is_flv_stream = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,76 +36,76 @@ namespace mediakit {
|
|||||||
//////////////////////////通用///////////////////////
|
//////////////////////////通用///////////////////////
|
||||||
void UTF8ToUnicode(wchar_t* pOut, const char *pText)
|
void UTF8ToUnicode(wchar_t* pOut, const char *pText)
|
||||||
{
|
{
|
||||||
char* uchar = (char *)pOut;
|
char* uchar = (char *)pOut;
|
||||||
uchar[1] = ((pText[0] & 0x0F) << 4) + ((pText[1] >> 2) & 0x0F);
|
uchar[1] = ((pText[0] & 0x0F) << 4) + ((pText[1] >> 2) & 0x0F);
|
||||||
uchar[0] = ((pText[1] & 0x03) << 6) + (pText[2] & 0x3F);
|
uchar[0] = ((pText[1] & 0x03) << 6) + (pText[2] & 0x3F);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void UnicodeToUTF8(char* pOut, const wchar_t* pText)
|
void UnicodeToUTF8(char* pOut, const wchar_t* pText)
|
||||||
{
|
{
|
||||||
// 注意 WCHAR高低字的顺序,低字节在前,高字节在后
|
// 注意 WCHAR高低字的顺序,低字节在前,高字节在后
|
||||||
const char* pchar = (const char *)pText;
|
const char* pchar = (const char *)pText;
|
||||||
pOut[0] = (0xE0 | ((pchar[1] & 0xF0) >> 4));
|
pOut[0] = (0xE0 | ((pchar[1] & 0xF0) >> 4));
|
||||||
pOut[1] = (0x80 | ((pchar[1] & 0x0F) << 2)) + ((pchar[0] & 0xC0) >> 6);
|
pOut[1] = (0x80 | ((pchar[1] & 0x0F) << 2)) + ((pchar[0] & 0xC0) >> 6);
|
||||||
pOut[2] = (0x80 | (pchar[0] & 0x3F));
|
pOut[2] = (0x80 | (pchar[0] & 0x3F));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char CharToInt(char ch)
|
char CharToInt(char ch)
|
||||||
{
|
{
|
||||||
if (ch >= '0' && ch <= '9')return (char)(ch - '0');
|
if (ch >= '0' && ch <= '9')return (char)(ch - '0');
|
||||||
if (ch >= 'a' && ch <= 'f')return (char)(ch - 'a' + 10);
|
if (ch >= 'a' && ch <= 'f')return (char)(ch - 'a' + 10);
|
||||||
if (ch >= 'A' && ch <= 'F')return (char)(ch - 'A' + 10);
|
if (ch >= 'A' && ch <= 'F')return (char)(ch - 'A' + 10);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
char StrToBin(const char *str)
|
char StrToBin(const char *str)
|
||||||
{
|
{
|
||||||
char tempWord[2];
|
char tempWord[2];
|
||||||
char chn;
|
char chn;
|
||||||
tempWord[0] = CharToInt(str[0]); //make the B to 11 -- 00001011
|
tempWord[0] = CharToInt(str[0]); //make the B to 11 -- 00001011
|
||||||
tempWord[1] = CharToInt(str[1]); //make the 0 to 0 -- 00000000
|
tempWord[1] = CharToInt(str[1]); //make the 0 to 0 -- 00000000
|
||||||
chn = (tempWord[0] << 4) | tempWord[1]; //to change the BO to 10110000
|
chn = (tempWord[0] << 4) | tempWord[1]; //to change the BO to 10110000
|
||||||
return chn;
|
return chn;
|
||||||
}
|
}
|
||||||
|
|
||||||
string strCoding::UrlEncode(const string &str) {
|
string strCoding::UrlEncode(const string &str) {
|
||||||
string out;
|
string out;
|
||||||
size_t len = str.size();
|
size_t len = str.size();
|
||||||
for (size_t i = 0; i < len; ++i) {
|
for (size_t i = 0; i < len; ++i) {
|
||||||
char ch = str[i];
|
char ch = str[i];
|
||||||
if (isalnum((uint8_t)ch)) {
|
if (isalnum((uint8_t)ch)) {
|
||||||
out.push_back(ch);
|
out.push_back(ch);
|
||||||
}else {
|
}else {
|
||||||
char buf[4];
|
char buf[4];
|
||||||
sprintf(buf, "%%%X%X", (uint8_t)ch >> 4,(uint8_t)ch & 0x0F);
|
sprintf(buf, "%%%X%X", (uint8_t)ch >> 4,(uint8_t)ch & 0x0F);
|
||||||
out.append(buf);
|
out.append(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
string strCoding::UrlDecode(const string &str) {
|
string strCoding::UrlDecode(const string &str) {
|
||||||
string output = "";
|
string output = "";
|
||||||
char tmp[2];
|
char tmp[2];
|
||||||
int i = 0, len = str.length();
|
int i = 0, len = str.length();
|
||||||
while (i < len) {
|
while (i < len) {
|
||||||
if (str[i] == '%') {
|
if (str[i] == '%') {
|
||||||
if(i > len - 3){
|
if(i > len - 3){
|
||||||
//防止内存溢出
|
//防止内存溢出
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
tmp[0] = str[i + 1];
|
tmp[0] = str[i + 1];
|
||||||
tmp[1] = str[i + 2];
|
tmp[1] = str[i + 2];
|
||||||
output += StrToBin(tmp);
|
output += StrToBin(tmp);
|
||||||
i = i + 3;
|
i = i + 3;
|
||||||
} else if (str[i] == '+') {
|
} else if (str[i] == '+') {
|
||||||
output += ' ';
|
output += ' ';
|
||||||
i++;
|
i++;
|
||||||
} else {
|
} else {
|
||||||
output += str[i];
|
output += str[i];
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -113,73 +113,73 @@ string strCoding::UrlDecode(const string &str) {
|
|||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
void UnicodeToGB2312(char* pOut, wchar_t uData)
|
void UnicodeToGB2312(char* pOut, wchar_t uData)
|
||||||
{
|
{
|
||||||
WideCharToMultiByte(CP_ACP, NULL, &uData, 1, pOut, sizeof(wchar_t), NULL, NULL);
|
WideCharToMultiByte(CP_ACP, NULL, &uData, 1, pOut, sizeof(wchar_t), NULL, NULL);
|
||||||
}
|
}
|
||||||
void Gb2312ToUnicode(wchar_t* pOut, const char *gbBuffer)
|
void Gb2312ToUnicode(wchar_t* pOut, const char *gbBuffer)
|
||||||
{
|
{
|
||||||
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gbBuffer, 2, pOut, 1);
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gbBuffer, 2, pOut, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
string strCoding::UTF8ToGB2312(const string &str) {
|
string strCoding::UTF8ToGB2312(const string &str) {
|
||||||
auto len = str.size();
|
auto len = str.size();
|
||||||
auto pText = str.data();
|
auto pText = str.data();
|
||||||
char Ctemp[4] = {0};
|
char Ctemp[4] = {0};
|
||||||
char *pOut = new char[len + 1];
|
char *pOut = new char[len + 1];
|
||||||
memset(pOut, 0, len + 1);
|
memset(pOut, 0, len + 1);
|
||||||
|
|
||||||
int i = 0, j = 0;
|
int i = 0, j = 0;
|
||||||
while (i < len)
|
while (i < len)
|
||||||
{
|
{
|
||||||
if (pText[i] >= 0)
|
if (pText[i] >= 0)
|
||||||
{
|
{
|
||||||
pOut[j++] = pText[i++];
|
pOut[j++] = pText[i++];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
wchar_t Wtemp;
|
wchar_t Wtemp;
|
||||||
UTF8ToUnicode(&Wtemp, pText + i);
|
UTF8ToUnicode(&Wtemp, pText + i);
|
||||||
UnicodeToGB2312(Ctemp, Wtemp);
|
UnicodeToGB2312(Ctemp, Wtemp);
|
||||||
pOut[j] = Ctemp[0];
|
pOut[j] = Ctemp[0];
|
||||||
pOut[j + 1] = Ctemp[1];
|
pOut[j + 1] = Ctemp[1];
|
||||||
i += 3;
|
i += 3;
|
||||||
j += 2;
|
j += 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string ret = pOut;
|
string ret = pOut;
|
||||||
delete[] pOut;
|
delete[] pOut;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
string strCoding::GB2312ToUTF8(const string &str) {
|
string strCoding::GB2312ToUTF8(const string &str) {
|
||||||
auto len = str.size();
|
auto len = str.size();
|
||||||
auto pText = str.data();
|
auto pText = str.data();
|
||||||
char buf[4] = { 0 };
|
char buf[4] = { 0 };
|
||||||
int nLength = len * 3;
|
int nLength = len * 3;
|
||||||
char* pOut = new char[nLength];
|
char* pOut = new char[nLength];
|
||||||
memset(pOut, 0, nLength);
|
memset(pOut, 0, nLength);
|
||||||
int i = 0, j = 0;
|
int i = 0, j = 0;
|
||||||
while (i < len)
|
while (i < len)
|
||||||
{
|
{
|
||||||
//如果是英文直接复制就可以
|
//如果是英文直接复制就可以
|
||||||
if (*(pText + i) >= 0)
|
if (*(pText + i) >= 0)
|
||||||
{
|
{
|
||||||
pOut[j++] = pText[i++];
|
pOut[j++] = pText[i++];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
wchar_t pbuffer;
|
wchar_t pbuffer;
|
||||||
Gb2312ToUnicode(&pbuffer, pText + i);
|
Gb2312ToUnicode(&pbuffer, pText + i);
|
||||||
UnicodeToUTF8(buf, &pbuffer);
|
UnicodeToUTF8(buf, &pbuffer);
|
||||||
pOut[j] = buf[0];
|
pOut[j] = buf[0];
|
||||||
pOut[j + 1] = buf[1];
|
pOut[j + 1] = buf[1];
|
||||||
pOut[j + 2] = buf[2];
|
pOut[j + 2] = buf[2];
|
||||||
j += 3;
|
j += 3;
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string ret = pOut;
|
string ret = pOut;
|
||||||
delete[] pOut;
|
delete[] pOut;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
#endif//defined(_WIN32)
|
#endif//defined(_WIN32)
|
||||||
|
|
||||||
|
@ -36,15 +36,15 @@ namespace mediakit {
|
|||||||
|
|
||||||
class strCoding {
|
class strCoding {
|
||||||
public:
|
public:
|
||||||
static string UrlEncode(const string &str); //urlutf8 编码
|
static string UrlEncode(const string &str); //urlutf8 编码
|
||||||
static string UrlDecode(const string &str); //urlutf8解码
|
static string UrlDecode(const string &str); //urlutf8解码
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
static string UTF8ToGB2312(const string &str);//utf_8转为gb2312
|
static string UTF8ToGB2312(const string &str);//utf_8转为gb2312
|
||||||
static string GB2312ToUTF8(const string &str); //gb2312 转utf_8
|
static string GB2312ToUTF8(const string &str); //gb2312 转utf_8
|
||||||
#endif//defined(_WIN32)
|
#endif//defined(_WIN32)
|
||||||
private:
|
private:
|
||||||
strCoding(void);
|
strCoding(void);
|
||||||
virtual ~strCoding(void);
|
virtual ~strCoding(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -42,29 +42,29 @@ MediaPlayer::MediaPlayer(const EventPoller::Ptr &poller) {
|
|||||||
MediaPlayer::~MediaPlayer() {
|
MediaPlayer::~MediaPlayer() {
|
||||||
}
|
}
|
||||||
void MediaPlayer::play(const string &strUrl) {
|
void MediaPlayer::play(const string &strUrl) {
|
||||||
_delegate = PlayerBase::createPlayer(_poller,strUrl);
|
_delegate = PlayerBase::createPlayer(_poller,strUrl);
|
||||||
_delegate->setOnShutdown(_shutdownCB);
|
_delegate->setOnShutdown(_shutdownCB);
|
||||||
_delegate->setOnPlayResult(_playResultCB);
|
_delegate->setOnPlayResult(_playResultCB);
|
||||||
_delegate->setOnResume(_resumeCB);
|
_delegate->setOnResume(_resumeCB);
|
||||||
_delegate->setMediaSouce(_pMediaSrc);
|
_delegate->setMediaSouce(_pMediaSrc);
|
||||||
_delegate->mINI::operator=(*this);
|
_delegate->mINI::operator=(*this);
|
||||||
_delegate->play(strUrl);
|
_delegate->play(strUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventPoller::Ptr MediaPlayer::getPoller(){
|
EventPoller::Ptr MediaPlayer::getPoller(){
|
||||||
return _poller;
|
return _poller;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaPlayer::pause(bool bPause) {
|
void MediaPlayer::pause(bool bPause) {
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
_delegate->pause(bPause);
|
_delegate->pause(bPause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaPlayer::teardown() {
|
void MediaPlayer::teardown() {
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
_delegate->teardown();
|
_delegate->teardown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,16 +39,16 @@ namespace mediakit {
|
|||||||
|
|
||||||
class MediaPlayer : public PlayerImp<PlayerBase,PlayerBase> {
|
class MediaPlayer : public PlayerImp<PlayerBase,PlayerBase> {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<MediaPlayer> Ptr;
|
typedef std::shared_ptr<MediaPlayer> Ptr;
|
||||||
|
|
||||||
MediaPlayer(const EventPoller::Ptr &poller = nullptr);
|
MediaPlayer(const EventPoller::Ptr &poller = nullptr);
|
||||||
virtual ~MediaPlayer();
|
virtual ~MediaPlayer();
|
||||||
void play(const string &strUrl) override;
|
void play(const string &strUrl) override;
|
||||||
void pause(bool bPause) override;
|
void pause(bool bPause) override;
|
||||||
void teardown() override;
|
void teardown() override;
|
||||||
EventPoller::Ptr getPoller();
|
EventPoller::Ptr getPoller();
|
||||||
private:
|
private:
|
||||||
EventPoller::Ptr _poller;
|
EventPoller::Ptr _poller;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -33,92 +33,92 @@ using namespace toolkit;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller,const string &strUrl) {
|
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller,const string &strUrl) {
|
||||||
static auto releasePlayer = [](PlayerBase *ptr){
|
static auto releasePlayer = [](PlayerBase *ptr){
|
||||||
onceToken token(nullptr,[&](){
|
onceToken token(nullptr,[&](){
|
||||||
delete ptr;
|
delete ptr;
|
||||||
});
|
});
|
||||||
ptr->teardown();
|
ptr->teardown();
|
||||||
};
|
};
|
||||||
string prefix = FindField(strUrl.data(), NULL, "://");
|
string prefix = FindField(strUrl.data(), NULL, "://");
|
||||||
|
|
||||||
if (strcasecmp("rtsps",prefix.data()) == 0) {
|
if (strcasecmp("rtsps",prefix.data()) == 0) {
|
||||||
return PlayerBase::Ptr(new TcpClientWithSSL<RtspPlayerImp>(poller),releasePlayer);
|
return PlayerBase::Ptr(new TcpClientWithSSL<RtspPlayerImp>(poller),releasePlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcasecmp("rtsp",prefix.data()) == 0) {
|
if (strcasecmp("rtsp",prefix.data()) == 0) {
|
||||||
return PlayerBase::Ptr(new RtspPlayerImp(poller),releasePlayer);
|
return PlayerBase::Ptr(new RtspPlayerImp(poller),releasePlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcasecmp("rtmps",prefix.data()) == 0) {
|
if (strcasecmp("rtmps",prefix.data()) == 0) {
|
||||||
return PlayerBase::Ptr(new TcpClientWithSSL<RtmpPlayerImp>(poller),releasePlayer);
|
return PlayerBase::Ptr(new TcpClientWithSSL<RtmpPlayerImp>(poller),releasePlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcasecmp("rtmp",prefix.data()) == 0) {
|
if (strcasecmp("rtmp",prefix.data()) == 0) {
|
||||||
return PlayerBase::Ptr(new RtmpPlayerImp(poller),releasePlayer);
|
return PlayerBase::Ptr(new RtmpPlayerImp(poller),releasePlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlayerBase::Ptr(new RtspPlayerImp(poller),releasePlayer);
|
return PlayerBase::Ptr(new RtspPlayerImp(poller),releasePlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerBase::PlayerBase() {
|
PlayerBase::PlayerBase() {
|
||||||
this->mINI::operator[](kTimeoutMS) = 10000;
|
this->mINI::operator[](kTimeoutMS) = 10000;
|
||||||
this->mINI::operator[](kMediaTimeoutMS) = 5000;
|
this->mINI::operator[](kMediaTimeoutMS) = 5000;
|
||||||
this->mINI::operator[](kBeatIntervalMS) = 5000;
|
this->mINI::operator[](kBeatIntervalMS) = 5000;
|
||||||
this->mINI::operator[](kMaxAnalysisMS) = 5000;
|
this->mINI::operator[](kMaxAnalysisMS) = 5000;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////Demuxer//////////////////////////////
|
///////////////////////////Demuxer//////////////////////////////
|
||||||
bool Demuxer::isInited(int analysisMs) {
|
bool Demuxer::isInited(int analysisMs) {
|
||||||
if(analysisMs && _ticker.createdTime() > analysisMs){
|
if(analysisMs && _ticker.createdTime() > analysisMs){
|
||||||
//analysisMs毫秒后强制初始化完毕
|
//analysisMs毫秒后强制初始化完毕
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (_videoTrack && !_videoTrack->ready()) {
|
if (_videoTrack && !_videoTrack->ready()) {
|
||||||
//视频未准备好
|
//视频未准备好
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (_audioTrack && !_audioTrack->ready()) {
|
if (_audioTrack && !_audioTrack->ready()) {
|
||||||
//音频未准备好
|
//音频未准备好
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<Track::Ptr> Demuxer::getTracks(bool trackReady) const {
|
vector<Track::Ptr> Demuxer::getTracks(bool trackReady) const {
|
||||||
vector<Track::Ptr> ret;
|
vector<Track::Ptr> ret;
|
||||||
if(_videoTrack){
|
if(_videoTrack){
|
||||||
if(trackReady){
|
if(trackReady){
|
||||||
if(_videoTrack->ready()){
|
if(_videoTrack->ready()){
|
||||||
ret.emplace_back(_videoTrack);
|
ret.emplace_back(_videoTrack);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
ret.emplace_back(_videoTrack);
|
ret.emplace_back(_videoTrack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(_audioTrack){
|
if(_audioTrack){
|
||||||
if(trackReady){
|
if(trackReady){
|
||||||
if(_audioTrack->ready()){
|
if(_audioTrack->ready()){
|
||||||
ret.emplace_back(_audioTrack);
|
ret.emplace_back(_audioTrack);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
ret.emplace_back(_audioTrack);
|
ret.emplace_back(_audioTrack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return std::move(ret);
|
return std::move(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
float Demuxer::getDuration() const {
|
float Demuxer::getDuration() const {
|
||||||
return _fDuration;
|
return _fDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Demuxer::onAddTrack(const Track::Ptr &track){
|
void Demuxer::onAddTrack(const Track::Ptr &track){
|
||||||
if(_listener){
|
if(_listener){
|
||||||
_listener->onAddTrack(track);
|
_listener->onAddTrack(track);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Demuxer::setTrackListener(Demuxer::Listener *listener) {
|
void Demuxer::setTrackListener(Demuxer::Listener *listener) {
|
||||||
_listener = listener;
|
_listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -43,59 +43,59 @@ namespace mediakit {
|
|||||||
|
|
||||||
class DemuxerBase : public TrackSource{
|
class DemuxerBase : public TrackSource{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<DemuxerBase> Ptr;
|
typedef std::shared_ptr<DemuxerBase> Ptr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取节目总时长,单位秒
|
* 获取节目总时长,单位秒
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
virtual float getDuration() const { return 0;}
|
virtual float getDuration() const { return 0;}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否初始化完毕,完毕后方可调用getTrack方法
|
* 是否初始化完毕,完毕后方可调用getTrack方法
|
||||||
* @param analysisMs 数据流最大分析时间 单位毫秒
|
* @param analysisMs 数据流最大分析时间 单位毫秒
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
virtual bool isInited(int analysisMs) { return true; }
|
virtual bool isInited(int analysisMs) { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class PlayerBase : public DemuxerBase, public mINI{
|
class PlayerBase : public DemuxerBase, public mINI{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<PlayerBase> Ptr;
|
typedef std::shared_ptr<PlayerBase> Ptr;
|
||||||
static Ptr createPlayer(const EventPoller::Ptr &poller,const string &strUrl);
|
static Ptr createPlayer(const EventPoller::Ptr &poller,const string &strUrl);
|
||||||
|
|
||||||
PlayerBase();
|
PlayerBase();
|
||||||
virtual ~PlayerBase(){}
|
virtual ~PlayerBase(){}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始播放
|
* 开始播放
|
||||||
* @param strUrl 视频url,支持rtsp/rtmp
|
* @param strUrl 视频url,支持rtsp/rtmp
|
||||||
*/
|
*/
|
||||||
virtual void play(const string &strUrl) {}
|
virtual void play(const string &strUrl) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂停或恢复
|
* 暂停或恢复
|
||||||
* @param bPause
|
* @param bPause
|
||||||
*/
|
*/
|
||||||
virtual void pause(bool bPause) {}
|
virtual void pause(bool bPause) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 中断播放
|
* 中断播放
|
||||||
*/
|
*/
|
||||||
virtual void teardown() {}
|
virtual void teardown() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置异常中断回调
|
* 设置异常中断回调
|
||||||
* @param cb
|
* @param cb
|
||||||
*/
|
*/
|
||||||
virtual void setOnShutdown( const function<void(const SockException &)> &cb) {}
|
virtual void setOnShutdown( const function<void(const SockException &)> &cb) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置播放结果回调
|
* 设置播放结果回调
|
||||||
* @param cb
|
* @param cb
|
||||||
*/
|
*/
|
||||||
virtual void setOnPlayResult( const function<void(const SockException &ex)> &cb) {}
|
virtual void setOnPlayResult( const function<void(const SockException &ex)> &cb) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置播放恢复回调
|
* 设置播放恢复回调
|
||||||
@ -103,10 +103,10 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void setOnResume( const function<void()> &cb) {}
|
virtual void setOnResume( const function<void()> &cb) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取播放进度,取值 0.0 ~ 1.0
|
* 获取播放进度,取值 0.0 ~ 1.0
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
virtual float getProgress() const { return 0;}
|
virtual float getProgress() const { return 0;}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,7 +126,7 @@ public:
|
|||||||
* @param trackType 音频或视频,TrackInvalid时为总丢包率
|
* @param trackType 音频或视频,TrackInvalid时为总丢包率
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
virtual float getPacketLossRate(TrackType trackType) const {return 0; }
|
virtual float getPacketLossRate(TrackType trackType) const {return 0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有track
|
* 获取所有track
|
||||||
@ -146,24 +146,24 @@ protected:
|
|||||||
template<typename Parent,typename Delegate>
|
template<typename Parent,typename Delegate>
|
||||||
class PlayerImp : public Parent {
|
class PlayerImp : public Parent {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<PlayerImp> Ptr;
|
typedef std::shared_ptr<PlayerImp> Ptr;
|
||||||
|
|
||||||
template<typename ...ArgsType>
|
template<typename ...ArgsType>
|
||||||
PlayerImp(ArgsType &&...args):Parent(std::forward<ArgsType>(args)...){}
|
PlayerImp(ArgsType &&...args):Parent(std::forward<ArgsType>(args)...){}
|
||||||
|
|
||||||
virtual ~PlayerImp(){}
|
virtual ~PlayerImp(){}
|
||||||
void setOnShutdown(const function<void(const SockException &)> &cb) override {
|
void setOnShutdown(const function<void(const SockException &)> &cb) override {
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
_delegate->setOnShutdown(cb);
|
_delegate->setOnShutdown(cb);
|
||||||
}
|
}
|
||||||
_shutdownCB = cb;
|
_shutdownCB = cb;
|
||||||
}
|
}
|
||||||
void setOnPlayResult(const function<void(const SockException &ex)> &cb) override {
|
void setOnPlayResult(const function<void(const SockException &ex)> &cb) override {
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
_delegate->setOnPlayResult(cb);
|
_delegate->setOnPlayResult(cb);
|
||||||
}
|
}
|
||||||
_playResultCB = cb;
|
_playResultCB = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOnResume(const function<void()> &cb) override {
|
void setOnResume(const function<void()> &cb) override {
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
@ -178,12 +178,12 @@ public:
|
|||||||
}
|
}
|
||||||
return Parent::isInited(analysisMs);
|
return Parent::isInited(analysisMs);
|
||||||
}
|
}
|
||||||
float getDuration() const override {
|
float getDuration() const override {
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
return _delegate->getDuration();
|
return _delegate->getDuration();
|
||||||
}
|
}
|
||||||
return Parent::getDuration();
|
return Parent::getDuration();
|
||||||
}
|
}
|
||||||
float getProgress() const override{
|
float getProgress() const override{
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
return _delegate->getProgress();
|
return _delegate->getProgress();
|
||||||
@ -198,95 +198,95 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setMediaSouce(const MediaSource::Ptr & src) override {
|
void setMediaSouce(const MediaSource::Ptr & src) override {
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
_delegate->setMediaSouce(src);
|
_delegate->setMediaSouce(src);
|
||||||
}
|
}
|
||||||
_pMediaSrc = src;
|
_pMediaSrc = src;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override{
|
vector<Track::Ptr> getTracks(bool trackReady = true) const override{
|
||||||
if (_delegate) {
|
if (_delegate) {
|
||||||
return _delegate->getTracks(trackReady);
|
return _delegate->getTracks(trackReady);
|
||||||
}
|
}
|
||||||
return Parent::getTracks(trackReady);
|
return Parent::getTracks(trackReady);
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
void onShutdown(const SockException &ex) override {
|
void onShutdown(const SockException &ex) override {
|
||||||
if (_shutdownCB) {
|
if (_shutdownCB) {
|
||||||
_shutdownCB(ex);
|
_shutdownCB(ex);
|
||||||
_shutdownCB = nullptr;
|
_shutdownCB = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPlayResult(const SockException &ex) override {
|
void onPlayResult(const SockException &ex) override {
|
||||||
if(_playResultCB) {
|
if(_playResultCB) {
|
||||||
_playResultCB(ex);
|
_playResultCB(ex);
|
||||||
_playResultCB = nullptr;
|
_playResultCB = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onResume() override{
|
void onResume() override{
|
||||||
if(_resumeCB){
|
if(_resumeCB){
|
||||||
_resumeCB();
|
_resumeCB();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
function<void(const SockException &ex)> _shutdownCB;
|
function<void(const SockException &ex)> _shutdownCB;
|
||||||
function<void(const SockException &ex)> _playResultCB;
|
function<void(const SockException &ex)> _playResultCB;
|
||||||
function<void()> _resumeCB;
|
function<void()> _resumeCB;
|
||||||
std::shared_ptr<Delegate> _delegate;
|
std::shared_ptr<Delegate> _delegate;
|
||||||
MediaSource::Ptr _pMediaSrc;
|
MediaSource::Ptr _pMediaSrc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class Demuxer : public PlayerBase{
|
class Demuxer : public PlayerBase{
|
||||||
public:
|
public:
|
||||||
class Listener{
|
class Listener{
|
||||||
public:
|
public:
|
||||||
Listener() = default;
|
Listener() = default;
|
||||||
virtual ~Listener() = default;
|
virtual ~Listener() = default;
|
||||||
virtual void onAddTrack(const Track::Ptr &track) = 0;
|
virtual void onAddTrack(const Track::Ptr &track) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Demuxer(){};
|
Demuxer(){};
|
||||||
virtual ~Demuxer(){};
|
virtual ~Demuxer(){};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回是否完成初始化完毕
|
* 返回是否完成初始化完毕
|
||||||
* 在构造RtspDemuxer对象时有些rtsp的sdp不包含sps pps信息
|
* 在构造RtspDemuxer对象时有些rtsp的sdp不包含sps pps信息
|
||||||
* 所以要等待接收到到sps的rtp包后才能完成
|
* 所以要等待接收到到sps的rtp包后才能完成
|
||||||
*
|
*
|
||||||
* 在构造RtmpDemuxer对象时是无法获取sps pps aac_cfg等这些信息,
|
* 在构造RtmpDemuxer对象时是无法获取sps pps aac_cfg等这些信息,
|
||||||
* 所以要调用inputRtmp后才会获取到这些信息,这时才初始化成功
|
* 所以要调用inputRtmp后才会获取到这些信息,这时才初始化成功
|
||||||
* @param analysisMs 数据流最大分析时间 单位毫秒
|
* @param analysisMs 数据流最大分析时间 单位毫秒
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
bool isInited(int analysisMs) override;
|
bool isInited(int analysisMs) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有Track
|
* 获取所有Track
|
||||||
* @return 所有Track
|
* @return 所有Track
|
||||||
*/
|
*/
|
||||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取节目总时长
|
* 获取节目总时长
|
||||||
* @return 节目总时长,单位秒
|
* @return 节目总时长,单位秒
|
||||||
*/
|
*/
|
||||||
float getDuration() const override;
|
float getDuration() const override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置track监听器
|
* 设置track监听器
|
||||||
*/
|
*/
|
||||||
void setTrackListener(Listener *listener);
|
void setTrackListener(Listener *listener);
|
||||||
protected:
|
protected:
|
||||||
void onAddTrack(const Track::Ptr &track);
|
void onAddTrack(const Track::Ptr &track);
|
||||||
protected:
|
protected:
|
||||||
Listener *_listener = nullptr;
|
Listener *_listener = nullptr;
|
||||||
AudioTrack::Ptr _audioTrack;
|
AudioTrack::Ptr _audioTrack;
|
||||||
VideoTrack::Ptr _videoTrack;
|
VideoTrack::Ptr _videoTrack;
|
||||||
Ticker _ticker;
|
Ticker _ticker;
|
||||||
float _fDuration = 0;
|
float _fDuration = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -65,17 +65,17 @@ static uint8_t s_mute_adts[] = {0xff, 0xf1, 0x6c, 0x40, 0x2d, 0x3f, 0xfc, 0x00,
|
|||||||
PlayerProxy::PlayerProxy(const string &strVhost,
|
PlayerProxy::PlayerProxy(const string &strVhost,
|
||||||
const string &strApp,
|
const string &strApp,
|
||||||
const string &strSrc,
|
const string &strSrc,
|
||||||
bool bEnableRtsp,
|
bool bEnableRtsp,
|
||||||
bool bEnableRtmp,
|
bool bEnableRtmp,
|
||||||
bool bEnableHls,
|
bool bEnableHls,
|
||||||
bool bEnableMp4,
|
bool bEnableMp4,
|
||||||
int iRetryCount,
|
int iRetryCount,
|
||||||
const EventPoller::Ptr &poller) : MediaPlayer(poller){
|
const EventPoller::Ptr &poller) : MediaPlayer(poller){
|
||||||
_strVhost = strVhost;
|
_strVhost = strVhost;
|
||||||
_strApp = strApp;
|
_strApp = strApp;
|
||||||
_strSrc = strSrc;
|
_strSrc = strSrc;
|
||||||
_bEnableRtsp = bEnableRtsp;
|
_bEnableRtsp = bEnableRtsp;
|
||||||
_bEnableRtmp = bEnableRtmp;
|
_bEnableRtmp = bEnableRtmp;
|
||||||
_bEnableHls = bEnableHls;
|
_bEnableHls = bEnableHls;
|
||||||
_bEnableMp4 = bEnableMp4;
|
_bEnableMp4 = bEnableMp4;
|
||||||
_iRetryCount = iRetryCount;
|
_iRetryCount = iRetryCount;
|
||||||
@ -90,88 +90,88 @@ void PlayerProxy::setOnClose(const function<void()> &cb){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PlayerProxy::play(const string &strUrlTmp) {
|
void PlayerProxy::play(const string &strUrlTmp) {
|
||||||
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
||||||
std::shared_ptr<int> piFailedCnt(new int(0)); //连续播放失败次数
|
std::shared_ptr<int> piFailedCnt(new int(0)); //连续播放失败次数
|
||||||
setOnPlayResult([weakSelf,strUrlTmp,piFailedCnt](const SockException &err) {
|
setOnPlayResult([weakSelf,strUrlTmp,piFailedCnt](const SockException &err) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(strongSelf->_playCB) {
|
if(strongSelf->_playCB) {
|
||||||
strongSelf->_playCB(err);
|
strongSelf->_playCB(err);
|
||||||
strongSelf->_playCB = nullptr;
|
strongSelf->_playCB = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!err) {
|
if(!err) {
|
||||||
// 播放成功
|
// 播放成功
|
||||||
*piFailedCnt = 0;//连续播放失败次数清0
|
*piFailedCnt = 0;//连续播放失败次数清0
|
||||||
strongSelf->onPlaySuccess();
|
strongSelf->onPlaySuccess();
|
||||||
}else if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
}else if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
||||||
// 播放失败,延时重试播放
|
// 播放失败,延时重试播放
|
||||||
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setOnShutdown([weakSelf,strUrlTmp,piFailedCnt](const SockException &err) {
|
setOnShutdown([weakSelf,strUrlTmp,piFailedCnt](const SockException &err) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(strongSelf->_mediaMuxer) {
|
if(strongSelf->_mediaMuxer) {
|
||||||
auto tracks = strongSelf->getTracks(false);
|
auto tracks = strongSelf->getTracks(false);
|
||||||
for (auto & track : tracks){
|
for (auto & track : tracks){
|
||||||
track->delDelegate(strongSelf->_mediaMuxer.get());
|
track->delDelegate(strongSelf->_mediaMuxer.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
||||||
if (resetWhenRePlay) {
|
if (resetWhenRePlay) {
|
||||||
strongSelf->_mediaMuxer.reset();
|
strongSelf->_mediaMuxer.reset();
|
||||||
} else {
|
} else {
|
||||||
strongSelf->_mediaMuxer->resetTracks();
|
strongSelf->_mediaMuxer->resetTracks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//播放异常中断,延时重试播放
|
//播放异常中断,延时重试播放
|
||||||
if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
||||||
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
MediaPlayer::play(strUrlTmp);
|
MediaPlayer::play(strUrlTmp);
|
||||||
|
|
||||||
MediaSource::Ptr mediaSource;
|
MediaSource::Ptr mediaSource;
|
||||||
if(dynamic_pointer_cast<RtspPlayer>(_delegate)){
|
if(dynamic_pointer_cast<RtspPlayer>(_delegate)){
|
||||||
//rtsp拉流
|
//rtsp拉流
|
||||||
GET_CONFIG(bool,directProxy,Rtsp::kDirectProxy);
|
GET_CONFIG(bool,directProxy,Rtsp::kDirectProxy);
|
||||||
if(directProxy && _bEnableRtsp){
|
if(directProxy && _bEnableRtsp){
|
||||||
mediaSource = std::make_shared<RtspMediaSource>(_strVhost,_strApp,_strSrc);
|
mediaSource = std::make_shared<RtspMediaSource>(_strVhost,_strApp,_strSrc);
|
||||||
}
|
}
|
||||||
} else if(dynamic_pointer_cast<RtmpPlayer>(_delegate)){
|
} else if(dynamic_pointer_cast<RtmpPlayer>(_delegate)){
|
||||||
//rtmp拉流
|
//rtmp拉流
|
||||||
if(_bEnableRtmp){
|
if(_bEnableRtmp){
|
||||||
mediaSource = std::make_shared<RtmpMediaSource>(_strVhost,_strApp,_strSrc);
|
mediaSource = std::make_shared<RtmpMediaSource>(_strVhost,_strApp,_strSrc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(mediaSource){
|
if(mediaSource){
|
||||||
setMediaSouce(mediaSource);
|
setMediaSouce(mediaSource);
|
||||||
mediaSource->setListener(shared_from_this());
|
mediaSource->setListener(shared_from_this());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerProxy::~PlayerProxy() {
|
PlayerProxy::~PlayerProxy() {
|
||||||
_timer.reset();
|
_timer.reset();
|
||||||
}
|
}
|
||||||
void PlayerProxy::rePlay(const string &strUrl,int iFailedCnt){
|
void PlayerProxy::rePlay(const string &strUrl,int iFailedCnt){
|
||||||
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60*1000));
|
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60*1000));
|
||||||
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
||||||
_timer = std::make_shared<Timer>(iDelay / 1000.0f,[weakSelf,strUrl,iFailedCnt]() {
|
_timer = std::make_shared<Timer>(iDelay / 1000.0f,[weakSelf,strUrl,iFailedCnt]() {
|
||||||
//播放失败次数越多,则延时越长
|
//播放失败次数越多,则延时越长
|
||||||
auto strongPlayer = weakSelf.lock();
|
auto strongPlayer = weakSelf.lock();
|
||||||
if(!strongPlayer) {
|
if(!strongPlayer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
WarnL << "重试播放[" << iFailedCnt << "]:" << strUrl;
|
WarnL << "重试播放[" << iFailedCnt << "]:" << strUrl;
|
||||||
strongPlayer->MediaPlayer::play(strUrl);
|
strongPlayer->MediaPlayer::play(strUrl);
|
||||||
return false;
|
return false;
|
||||||
}, getPoller());
|
}, getPoller());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlayerProxy::close(MediaSource &sender,bool force) {
|
bool PlayerProxy::close(MediaSource &sender,bool force) {
|
||||||
@ -179,19 +179,19 @@ bool PlayerProxy::close(MediaSource &sender,bool force) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//通知其停止推流
|
//通知其停止推流
|
||||||
weak_ptr<PlayerProxy> weakSlef = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
|
weak_ptr<PlayerProxy> weakSlef = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
|
||||||
getPoller()->async_first([weakSlef]() {
|
getPoller()->async_first([weakSlef]() {
|
||||||
auto stronSelf = weakSlef.lock();
|
auto stronSelf = weakSlef.lock();
|
||||||
if (stronSelf) {
|
if (stronSelf) {
|
||||||
stronSelf->_mediaMuxer.reset();
|
stronSelf->_mediaMuxer.reset();
|
||||||
stronSelf->setMediaSouce(nullptr);
|
stronSelf->setMediaSouce(nullptr);
|
||||||
stronSelf->teardown();
|
stronSelf->teardown();
|
||||||
if(stronSelf->_onClose){
|
if(stronSelf->_onClose){
|
||||||
stronSelf->_onClose();
|
stronSelf->_onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -208,90 +208,90 @@ int PlayerProxy::totalReaderCount(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
int PlayerProxy::totalReaderCount(MediaSource &sender) {
|
int PlayerProxy::totalReaderCount(MediaSource &sender) {
|
||||||
return totalReaderCount();
|
return totalReaderCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
class MuteAudioMaker : public FrameDispatcher{
|
class MuteAudioMaker : public FrameDispatcher{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<MuteAudioMaker> Ptr;
|
typedef std::shared_ptr<MuteAudioMaker> Ptr;
|
||||||
|
|
||||||
MuteAudioMaker(){};
|
MuteAudioMaker(){};
|
||||||
virtual ~MuteAudioMaker(){}
|
virtual ~MuteAudioMaker(){}
|
||||||
void inputFrame(const Frame::Ptr &frame) override {
|
void inputFrame(const Frame::Ptr &frame) override {
|
||||||
if(frame->getTrackType() == TrackVideo){
|
if(frame->getTrackType() == TrackVideo){
|
||||||
auto iAudioIndex = frame->dts() / MUTE_ADTS_DATA_MS;
|
auto iAudioIndex = frame->dts() / MUTE_ADTS_DATA_MS;
|
||||||
if(_iAudioIndex != iAudioIndex){
|
if(_iAudioIndex != iAudioIndex){
|
||||||
_iAudioIndex = iAudioIndex;
|
_iAudioIndex = iAudioIndex;
|
||||||
auto aacFrame = std::make_shared<AACFrameCacheAble>((char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _iAudioIndex * MUTE_ADTS_DATA_MS);
|
auto aacFrame = std::make_shared<AACFrameCacheAble>((char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _iAudioIndex * MUTE_ADTS_DATA_MS);
|
||||||
FrameDispatcher::inputFrame(aacFrame);
|
FrameDispatcher::inputFrame(aacFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class AACFrameCacheAble : public AACFrameNoCacheAble{
|
class AACFrameCacheAble : public AACFrameNoCacheAble{
|
||||||
public:
|
public:
|
||||||
template <typename ... ARGS>
|
template <typename ... ARGS>
|
||||||
AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward<ARGS>(args)...){};
|
AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward<ARGS>(args)...){};
|
||||||
virtual ~AACFrameCacheAble() = default;
|
virtual ~AACFrameCacheAble() = default;
|
||||||
|
|
||||||
bool cacheAble() const override {
|
bool cacheAble() const override {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
int _iAudioIndex = 0;
|
int _iAudioIndex = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
void PlayerProxy::onPlaySuccess() {
|
void PlayerProxy::onPlaySuccess() {
|
||||||
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
||||||
if (dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc)) {
|
if (dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc)) {
|
||||||
//rtsp拉流代理
|
//rtsp拉流代理
|
||||||
if (resetWhenRePlay || !_mediaMuxer) {
|
if (resetWhenRePlay || !_mediaMuxer) {
|
||||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), false, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), false, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
||||||
}
|
}
|
||||||
} else if (dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc)) {
|
} else if (dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc)) {
|
||||||
//rtmp拉流代理
|
//rtmp拉流代理
|
||||||
if (resetWhenRePlay || !_mediaMuxer) {
|
if (resetWhenRePlay || !_mediaMuxer) {
|
||||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, false, _bEnableHls, _bEnableMp4));
|
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, false, _bEnableHls, _bEnableMp4));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//其他拉流代理
|
//其他拉流代理
|
||||||
if (resetWhenRePlay || !_mediaMuxer) {
|
if (resetWhenRePlay || !_mediaMuxer) {
|
||||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_mediaMuxer->setListener(shared_from_this());
|
_mediaMuxer->setListener(shared_from_this());
|
||||||
|
|
||||||
auto videoTrack = getTrack(TrackVideo,false);
|
auto videoTrack = getTrack(TrackVideo,false);
|
||||||
if(videoTrack){
|
if(videoTrack){
|
||||||
//添加视频
|
//添加视频
|
||||||
_mediaMuxer->addTrack(videoTrack);
|
_mediaMuxer->addTrack(videoTrack);
|
||||||
//视频数据写入_mediaMuxer
|
//视频数据写入_mediaMuxer
|
||||||
videoTrack->addDelegate(_mediaMuxer);
|
videoTrack->addDelegate(_mediaMuxer);
|
||||||
}
|
}
|
||||||
|
|
||||||
//是否添加静音音频
|
//是否添加静音音频
|
||||||
GET_CONFIG(bool,addMuteAudio,General::kAddMuteAudio);
|
GET_CONFIG(bool,addMuteAudio,General::kAddMuteAudio);
|
||||||
|
|
||||||
auto audioTrack = getTrack(TrackAudio, false);
|
auto audioTrack = getTrack(TrackAudio, false);
|
||||||
if(audioTrack){
|
if(audioTrack){
|
||||||
//添加音频
|
//添加音频
|
||||||
_mediaMuxer->addTrack(audioTrack);
|
_mediaMuxer->addTrack(audioTrack);
|
||||||
//音频数据写入_mediaMuxer
|
//音频数据写入_mediaMuxer
|
||||||
audioTrack->addDelegate(_mediaMuxer);
|
audioTrack->addDelegate(_mediaMuxer);
|
||||||
}else if(addMuteAudio && videoTrack){
|
}else if(addMuteAudio && videoTrack){
|
||||||
//没有音频信息,产生一个静音音频
|
//没有音频信息,产生一个静音音频
|
||||||
MuteAudioMaker::Ptr audioMaker = std::make_shared<MuteAudioMaker>();
|
MuteAudioMaker::Ptr audioMaker = std::make_shared<MuteAudioMaker>();
|
||||||
//videoTrack把数据写入MuteAudioMaker
|
//videoTrack把数据写入MuteAudioMaker
|
||||||
videoTrack->addDelegate(audioMaker);
|
videoTrack->addDelegate(audioMaker);
|
||||||
//添加一个静音Track至_mediaMuxer
|
//添加一个静音Track至_mediaMuxer
|
||||||
_mediaMuxer->addTrack(std::make_shared<AACTrack>());
|
_mediaMuxer->addTrack(std::make_shared<AACTrack>());
|
||||||
//MuteAudioMaker生成静音音频然后写入_mediaMuxer;
|
//MuteAudioMaker生成静音音频然后写入_mediaMuxer;
|
||||||
audioMaker->addDelegate(_mediaMuxer);
|
audioMaker->addDelegate(_mediaMuxer);
|
||||||
}
|
}
|
||||||
|
|
||||||
//添加完毕所有track,防止单track情况下最大等待3秒
|
//添加完毕所有track,防止单track情况下最大等待3秒
|
||||||
_mediaMuxer->addTrackCompleted();
|
_mediaMuxer->addTrackCompleted();
|
||||||
|
|
||||||
if(_pMediaSrc){
|
if(_pMediaSrc){
|
||||||
|
@ -39,24 +39,24 @@ using namespace toolkit;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class PlayerProxy :public MediaPlayer,
|
class PlayerProxy :public MediaPlayer,
|
||||||
public std::enable_shared_from_this<PlayerProxy> ,
|
public std::enable_shared_from_this<PlayerProxy> ,
|
||||||
public MediaSourceEvent{
|
public MediaSourceEvent{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<PlayerProxy> Ptr;
|
typedef std::shared_ptr<PlayerProxy> Ptr;
|
||||||
|
|
||||||
//如果iRetryCount<0,则一直重试播放;否则重试iRetryCount次数
|
//如果iRetryCount<0,则一直重试播放;否则重试iRetryCount次数
|
||||||
//默认一直重试
|
//默认一直重试
|
||||||
PlayerProxy(const string &strVhost,
|
PlayerProxy(const string &strVhost,
|
||||||
const string &strApp,
|
const string &strApp,
|
||||||
const string &strSrc,
|
const string &strSrc,
|
||||||
bool bEnableRtsp = true,
|
bool bEnableRtsp = true,
|
||||||
bool bEnableRtmp = true,
|
bool bEnableRtmp = true,
|
||||||
bool bEnableHls = true,
|
bool bEnableHls = true,
|
||||||
bool bEnableMp4 = false,
|
bool bEnableMp4 = false,
|
||||||
int iRetryCount = -1,
|
int iRetryCount = -1,
|
||||||
const EventPoller::Ptr &poller = nullptr);
|
const EventPoller::Ptr &poller = nullptr);
|
||||||
|
|
||||||
virtual ~PlayerProxy();
|
virtual ~PlayerProxy();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置play结果回调,只触发一次;在play执行之前有效
|
* 设置play结果回调,只触发一次;在play执行之前有效
|
||||||
@ -81,19 +81,19 @@ public:
|
|||||||
*/
|
*/
|
||||||
int totalReaderCount() ;
|
int totalReaderCount() ;
|
||||||
private:
|
private:
|
||||||
//MediaSourceEvent override
|
//MediaSourceEvent override
|
||||||
bool close(MediaSource &sender,bool force) override;
|
bool close(MediaSource &sender,bool force) override;
|
||||||
void onNoneReader(MediaSource &sender) override;
|
void onNoneReader(MediaSource &sender) override;
|
||||||
int totalReaderCount(MediaSource &sender) override;
|
int totalReaderCount(MediaSource &sender) override;
|
||||||
void rePlay(const string &strUrl,int iFailedCnt);
|
void rePlay(const string &strUrl,int iFailedCnt);
|
||||||
void onPlaySuccess();
|
void onPlaySuccess();
|
||||||
private:
|
private:
|
||||||
bool _bEnableRtsp;
|
bool _bEnableRtsp;
|
||||||
bool _bEnableRtmp;
|
bool _bEnableRtmp;
|
||||||
bool _bEnableHls;
|
bool _bEnableHls;
|
||||||
bool _bEnableMp4;
|
bool _bEnableMp4;
|
||||||
int _iRetryCount;
|
int _iRetryCount;
|
||||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||||
string _strVhost;
|
string _strVhost;
|
||||||
string _strApp;
|
string _strApp;
|
||||||
string _strSrc;
|
string _strSrc;
|
||||||
|
@ -44,16 +44,16 @@ public:
|
|||||||
virtual ~HlsMediaSource() = default;
|
virtual ~HlsMediaSource() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取媒体源的环形缓冲
|
* 获取媒体源的环形缓冲
|
||||||
*/
|
*/
|
||||||
const RingType::Ptr &getRing() const {
|
const RingType::Ptr &getRing() const {
|
||||||
return _ring;
|
return _ring;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取播放器个数
|
* 获取播放器个数
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
int readerCount() override {
|
int readerCount() override {
|
||||||
return _readerCount.load();
|
return _readerCount.load();
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct mpeg4_avc_t avc = {0};
|
struct mpeg4_avc_t avc = {0};
|
||||||
string sps_pps = string("\x00\x00\x00\x01", 4) + h264_track->getSps() +
|
string sps_pps = string("\x00\x00\x00\x01", 4) + h264_track->getSps() +
|
||||||
string("\x00\x00\x00\x01", 4) + h264_track->getPps();
|
string("\x00\x00\x00\x01", 4) + h264_track->getPps();
|
||||||
h264_annexbtomp4(&avc, sps_pps.data(), sps_pps.size(), NULL, 0, NULL, NULL);
|
h264_annexbtomp4(&avc, sps_pps.data(), sps_pps.size(), NULL, 0, NULL, NULL);
|
||||||
@ -230,7 +230,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct mpeg4_hevc_t hevc = {0};
|
struct mpeg4_hevc_t hevc = {0};
|
||||||
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + h265_track->getVps() +
|
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + h265_track->getVps() +
|
||||||
string("\x00\x00\x00\x01", 4) + h265_track->getSps() +
|
string("\x00\x00\x00\x01", 4) + h265_track->getSps() +
|
||||||
string("\x00\x00\x00\x01", 4) + h265_track->getPps();
|
string("\x00\x00\x00\x01", 4) + h265_track->getPps();
|
||||||
|
@ -39,118 +39,118 @@ namespace mediakit {
|
|||||||
|
|
||||||
#ifdef ENABLE_MP4V2
|
#ifdef ENABLE_MP4V2
|
||||||
MP4Reader::MP4Reader(const string &strVhost,const string &strApp, const string &strId,const string &filePath ) {
|
MP4Reader::MP4Reader(const string &strVhost,const string &strApp, const string &strId,const string &filePath ) {
|
||||||
_poller = WorkThreadPool::Instance().getPoller();
|
_poller = WorkThreadPool::Instance().getPoller();
|
||||||
auto strFileName = filePath;
|
auto strFileName = filePath;
|
||||||
if(strFileName.empty()){
|
if(strFileName.empty()){
|
||||||
GET_CONFIG(string,recordPath,Record::kFilePath);
|
GET_CONFIG(string,recordPath,Record::kFilePath);
|
||||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||||
if(enableVhost){
|
if(enableVhost){
|
||||||
strFileName = strVhost + "/" + strApp + "/" + strId;
|
strFileName = strVhost + "/" + strApp + "/" + strId;
|
||||||
}else{
|
}else{
|
||||||
strFileName = strApp + "/" + strId;
|
strFileName = strApp + "/" + strId;
|
||||||
}
|
}
|
||||||
strFileName = File::absolutePath(strFileName,recordPath);
|
strFileName = File::absolutePath(strFileName,recordPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
_hMP4File = MP4Read(strFileName.data());
|
_hMP4File = MP4Read(strFileName.data());
|
||||||
if(_hMP4File == MP4_INVALID_FILE_HANDLE){
|
if(_hMP4File == MP4_INVALID_FILE_HANDLE){
|
||||||
throw runtime_error(StrPrinter << "打开MP4文件失败:" << strFileName << endl);
|
throw runtime_error(StrPrinter << "打开MP4文件失败:" << strFileName << endl);
|
||||||
}
|
}
|
||||||
_video_trId = MP4FindTrackId(_hMP4File, 0, MP4_VIDEO_TRACK_TYPE, 0);
|
_video_trId = MP4FindTrackId(_hMP4File, 0, MP4_VIDEO_TRACK_TYPE, 0);
|
||||||
if(_video_trId != MP4_INVALID_TRACK_ID){
|
if(_video_trId != MP4_INVALID_TRACK_ID){
|
||||||
if(strcmp(MP4GetTrackMediaDataName(_hMP4File, _video_trId),"avc1") ==0){
|
if(strcmp(MP4GetTrackMediaDataName(_hMP4File, _video_trId),"avc1") ==0){
|
||||||
auto _video_timescale = MP4GetTrackTimeScale(_hMP4File, _video_trId);
|
auto _video_timescale = MP4GetTrackTimeScale(_hMP4File, _video_trId);
|
||||||
auto _video_duration = MP4GetTrackDuration(_hMP4File, _video_trId);
|
auto _video_duration = MP4GetTrackDuration(_hMP4File, _video_trId);
|
||||||
_video_num_samples = MP4GetTrackNumberOfSamples(_hMP4File, _video_trId);
|
_video_num_samples = MP4GetTrackNumberOfSamples(_hMP4File, _video_trId);
|
||||||
_video_sample_max_size = MP4GetTrackMaxSampleSize(_hMP4File, _video_trId);
|
_video_sample_max_size = MP4GetTrackMaxSampleSize(_hMP4File, _video_trId);
|
||||||
_video_width = MP4GetTrackVideoWidth(_hMP4File, _video_trId);
|
_video_width = MP4GetTrackVideoWidth(_hMP4File, _video_trId);
|
||||||
_video_height = MP4GetTrackVideoHeight(_hMP4File, _video_trId);
|
_video_height = MP4GetTrackVideoHeight(_hMP4File, _video_trId);
|
||||||
_video_framerate = MP4GetTrackVideoFrameRate(_hMP4File, _video_trId);
|
_video_framerate = MP4GetTrackVideoFrameRate(_hMP4File, _video_trId);
|
||||||
_pcVideoSample = std::shared_ptr<uint8_t> (new uint8_t[_video_sample_max_size],[](uint8_t *ptr){
|
_pcVideoSample = std::shared_ptr<uint8_t> (new uint8_t[_video_sample_max_size],[](uint8_t *ptr){
|
||||||
delete [] ptr;
|
delete [] ptr;
|
||||||
});
|
});
|
||||||
uint8_t **seqheader;
|
uint8_t **seqheader;
|
||||||
uint8_t **pictheader;
|
uint8_t **pictheader;
|
||||||
uint32_t *pictheadersize;
|
uint32_t *pictheadersize;
|
||||||
uint32_t *seqheadersize;
|
uint32_t *seqheadersize;
|
||||||
uint32_t ix;
|
uint32_t ix;
|
||||||
if(MP4GetTrackH264SeqPictHeaders(_hMP4File, _video_trId, &seqheader, &seqheadersize, &pictheader, &pictheadersize)){
|
if(MP4GetTrackH264SeqPictHeaders(_hMP4File, _video_trId, &seqheader, &seqheadersize, &pictheader, &pictheadersize)){
|
||||||
for (ix = 0; seqheadersize[ix] != 0; ix++) {
|
for (ix = 0; seqheadersize[ix] != 0; ix++) {
|
||||||
_strSps.assign((char *)(seqheader[ix]), seqheadersize[ix]);
|
_strSps.assign((char *)(seqheader[ix]), seqheadersize[ix]);
|
||||||
float framerate;
|
float framerate;
|
||||||
getAVCInfo(_strSps, (int &)_video_width, (int &)_video_height, framerate);
|
getAVCInfo(_strSps, (int &)_video_width, (int &)_video_height, framerate);
|
||||||
_video_framerate = framerate;
|
_video_framerate = framerate;
|
||||||
_strSps = string("\x0\x0\x0\x1",4) + _strSps;
|
_strSps = string("\x0\x0\x0\x1",4) + _strSps;
|
||||||
MP4Free(seqheader[ix]);
|
MP4Free(seqheader[ix]);
|
||||||
}
|
}
|
||||||
MP4Free(seqheader);
|
MP4Free(seqheader);
|
||||||
MP4Free(seqheadersize);
|
MP4Free(seqheadersize);
|
||||||
for (ix = 0; pictheadersize[ix] != 0; ix++) {
|
for (ix = 0; pictheadersize[ix] != 0; ix++) {
|
||||||
_strPps.assign("\x0\x0\x0\x1",4);
|
_strPps.assign("\x0\x0\x0\x1",4);
|
||||||
_strPps.append((char *)(pictheader[ix]), pictheadersize[ix]);
|
_strPps.append((char *)(pictheader[ix]), pictheadersize[ix]);
|
||||||
MP4Free(pictheader[ix]);
|
MP4Free(pictheader[ix]);
|
||||||
}
|
}
|
||||||
MP4Free(pictheader);
|
MP4Free(pictheader);
|
||||||
MP4Free(pictheadersize);
|
MP4Free(pictheadersize);
|
||||||
}
|
}
|
||||||
_video_ms = 1000.0 * _video_duration / _video_timescale;
|
_video_ms = 1000.0 * _video_duration / _video_timescale;
|
||||||
/*InfoL << "\r\n"
|
/*InfoL << "\r\n"
|
||||||
<< _video_ms << "\r\n"
|
<< _video_ms << "\r\n"
|
||||||
<< _video_num_samples << "\r\n"
|
<< _video_num_samples << "\r\n"
|
||||||
<< _video_framerate << "\r\n"
|
<< _video_framerate << "\r\n"
|
||||||
<< _video_width << "\r\n"
|
<< _video_width << "\r\n"
|
||||||
<< _video_height << "\r\n";*/
|
<< _video_height << "\r\n";*/
|
||||||
} else {
|
} else {
|
||||||
//如果不是h264,则忽略
|
//如果不是h264,则忽略
|
||||||
_video_trId = MP4_INVALID_TRACK_ID;
|
_video_trId = MP4_INVALID_TRACK_ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_audio_trId = MP4FindTrackId(_hMP4File, 0, MP4_AUDIO_TRACK_TYPE, 0);
|
_audio_trId = MP4FindTrackId(_hMP4File, 0, MP4_AUDIO_TRACK_TYPE, 0);
|
||||||
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
||||||
if (strcmp(MP4GetTrackMediaDataName(_hMP4File, _audio_trId), "mp4a") == 0) {
|
if (strcmp(MP4GetTrackMediaDataName(_hMP4File, _audio_trId), "mp4a") == 0) {
|
||||||
_audio_sample_rate = MP4GetTrackTimeScale(_hMP4File, _audio_trId);
|
_audio_sample_rate = MP4GetTrackTimeScale(_hMP4File, _audio_trId);
|
||||||
auto _audio_duration = MP4GetTrackDuration(_hMP4File, _audio_trId);
|
auto _audio_duration = MP4GetTrackDuration(_hMP4File, _audio_trId);
|
||||||
_audio_num_samples = MP4GetTrackNumberOfSamples(_hMP4File,_audio_trId);
|
_audio_num_samples = MP4GetTrackNumberOfSamples(_hMP4File,_audio_trId);
|
||||||
_audio_num_channels = MP4GetTrackAudioChannels(_hMP4File, _audio_trId);
|
_audio_num_channels = MP4GetTrackAudioChannels(_hMP4File, _audio_trId);
|
||||||
_audio_sample_max_size = MP4GetTrackMaxSampleSize(_hMP4File,_audio_trId);
|
_audio_sample_max_size = MP4GetTrackMaxSampleSize(_hMP4File,_audio_trId);
|
||||||
uint8_t *ppConfig;
|
uint8_t *ppConfig;
|
||||||
uint32_t pConfigSize;
|
uint32_t pConfigSize;
|
||||||
if(MP4GetTrackESConfiguration(_hMP4File,_audio_trId,&ppConfig,&pConfigSize)){
|
if(MP4GetTrackESConfiguration(_hMP4File,_audio_trId,&ppConfig,&pConfigSize)){
|
||||||
_strAacCfg.assign((char *)ppConfig, pConfigSize);
|
_strAacCfg.assign((char *)ppConfig, pConfigSize);
|
||||||
makeAdtsHeader(_strAacCfg, _adts);
|
makeAdtsHeader(_strAacCfg, _adts);
|
||||||
writeAdtsHeader(_adts,_adts.buffer);
|
writeAdtsHeader(_adts,_adts.buffer);
|
||||||
getAACInfo(_adts, (int &)_audio_sample_rate, (int &)_audio_num_channels);
|
getAACInfo(_adts, (int &)_audio_sample_rate, (int &)_audio_num_channels);
|
||||||
MP4Free(ppConfig);
|
MP4Free(ppConfig);
|
||||||
}
|
}
|
||||||
_audio_ms = 1000.0 * _audio_duration / _audio_sample_rate;
|
_audio_ms = 1000.0 * _audio_duration / _audio_sample_rate;
|
||||||
/*InfoL << "\r\n"
|
/*InfoL << "\r\n"
|
||||||
<< _audio_ms << "\r\n"
|
<< _audio_ms << "\r\n"
|
||||||
<< _audio_num_samples << "\r\n"
|
<< _audio_num_samples << "\r\n"
|
||||||
<< _audio_num_channels << "\r\n"
|
<< _audio_num_channels << "\r\n"
|
||||||
<< _audio_sample_rate << "\r\n";*/
|
<< _audio_sample_rate << "\r\n";*/
|
||||||
}else{
|
}else{
|
||||||
_audio_trId = MP4_INVALID_TRACK_ID;
|
_audio_trId = MP4_INVALID_TRACK_ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(_audio_trId == MP4_INVALID_TRACK_ID && _video_trId == MP4_INVALID_TRACK_ID){
|
if(_audio_trId == MP4_INVALID_TRACK_ID && _video_trId == MP4_INVALID_TRACK_ID){
|
||||||
MP4Close(_hMP4File);
|
MP4Close(_hMP4File);
|
||||||
_hMP4File = MP4_INVALID_FILE_HANDLE;
|
_hMP4File = MP4_INVALID_FILE_HANDLE;
|
||||||
throw runtime_error(StrPrinter << "该MP4文件音视频格式不支持:" << strFileName << endl);
|
throw runtime_error(StrPrinter << "该MP4文件音视频格式不支持:" << strFileName << endl);
|
||||||
}
|
}
|
||||||
|
|
||||||
_iDuration = MAX(_video_ms,_audio_ms);
|
_iDuration = MAX(_video_ms,_audio_ms);
|
||||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(strVhost, strApp, strId, _iDuration / 1000.0, true, true, false, false));
|
_mediaMuxer.reset(new MultiMediaSourceMuxer(strVhost, strApp, strId, _iDuration / 1000.0, true, true, false, false));
|
||||||
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
||||||
AACTrack::Ptr track = std::make_shared<AACTrack>(_strAacCfg);
|
AACTrack::Ptr track = std::make_shared<AACTrack>(_strAacCfg);
|
||||||
_mediaMuxer->addTrack(track);
|
_mediaMuxer->addTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
||||||
H264Track::Ptr track = std::make_shared<H264Track>(_strSps,_strPps);
|
H264Track::Ptr track = std::make_shared<H264Track>(_strSps,_strPps);
|
||||||
_mediaMuxer->addTrack(track);
|
_mediaMuxer->addTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
//添加完毕所有track,防止单track情况下最大等待3秒
|
//添加完毕所有track,防止单track情况下最大等待3秒
|
||||||
_mediaMuxer->addTrackCompleted();
|
_mediaMuxer->addTrackCompleted();
|
||||||
@ -158,34 +158,34 @@ MP4Reader::MP4Reader(const string &strVhost,const string &strApp, const string &
|
|||||||
|
|
||||||
|
|
||||||
MP4Reader::~MP4Reader() {
|
MP4Reader::~MP4Reader() {
|
||||||
if (_hMP4File != MP4_INVALID_FILE_HANDLE) {
|
if (_hMP4File != MP4_INVALID_FILE_HANDLE) {
|
||||||
MP4Close(_hMP4File);
|
MP4Close(_hMP4File);
|
||||||
_hMP4File = MP4_INVALID_FILE_HANDLE;
|
_hMP4File = MP4_INVALID_FILE_HANDLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MP4Reader::startReadMP4() {
|
void MP4Reader::startReadMP4() {
|
||||||
auto strongSelf = shared_from_this();
|
auto strongSelf = shared_from_this();
|
||||||
GET_CONFIG(uint32_t,sampleMS,Record::kSampleMS);
|
GET_CONFIG(uint32_t,sampleMS,Record::kSampleMS);
|
||||||
|
|
||||||
_timer = std::make_shared<Timer>(sampleMS / 1000.0f,[strongSelf](){
|
_timer = std::make_shared<Timer>(sampleMS / 1000.0f,[strongSelf](){
|
||||||
return strongSelf->readSample(0,false);
|
return strongSelf->readSample(0,false);
|
||||||
}, _poller);
|
}, _poller);
|
||||||
|
|
||||||
//先读sampleMS毫秒的数据用于产生MediaSouce
|
//先读sampleMS毫秒的数据用于产生MediaSouce
|
||||||
readSample(sampleMS, false);
|
readSample(sampleMS, false);
|
||||||
_mediaMuxer->setListener(strongSelf);
|
_mediaMuxer->setListener(strongSelf);
|
||||||
}
|
}
|
||||||
bool MP4Reader::seekTo(MediaSource &sender,uint32_t ui32Stamp){
|
bool MP4Reader::seekTo(MediaSource &sender,uint32_t ui32Stamp){
|
||||||
seek(ui32Stamp);
|
seek(ui32Stamp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool MP4Reader::close(MediaSource &sender,bool force){
|
bool MP4Reader::close(MediaSource &sender,bool force){
|
||||||
if(!_mediaMuxer || (!force && _mediaMuxer->totalReaderCount())){
|
if(!_mediaMuxer || (!force && _mediaMuxer->totalReaderCount())){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_timer.reset();
|
_timer.reset();
|
||||||
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -198,145 +198,145 @@ void MP4Reader::onNoneReader(MediaSource &sender) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int MP4Reader::totalReaderCount(MediaSource &sender) {
|
int MP4Reader::totalReaderCount(MediaSource &sender) {
|
||||||
return _mediaMuxer ? _mediaMuxer->totalReaderCount() : sender.readerCount();
|
return _mediaMuxer ? _mediaMuxer->totalReaderCount() : sender.readerCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MP4Reader::readSample(int iTimeInc,bool justSeekSyncFrame) {
|
bool MP4Reader::readSample(int iTimeInc,bool justSeekSyncFrame) {
|
||||||
TimeTicker();
|
TimeTicker();
|
||||||
lock_guard<recursive_mutex> lck(_mtx);
|
lock_guard<recursive_mutex> lck(_mtx);
|
||||||
auto bFlag0 = readVideoSample(iTimeInc,justSeekSyncFrame);//数据没读完
|
auto bFlag0 = readVideoSample(iTimeInc,justSeekSyncFrame);//数据没读完
|
||||||
auto bFlag1 = readAudioSample(iTimeInc,justSeekSyncFrame);//数据没读完
|
auto bFlag1 = readAudioSample(iTimeInc,justSeekSyncFrame);//数据没读完
|
||||||
auto bFlag2 = _mediaMuxer->totalReaderCount() > 0;//读取者大于0
|
auto bFlag2 = _mediaMuxer->totalReaderCount() > 0;//读取者大于0
|
||||||
if((bFlag0 || bFlag1) && bFlag2){
|
if((bFlag0 || bFlag1) && bFlag2){
|
||||||
_alive.resetTime();
|
_alive.resetTime();
|
||||||
}
|
}
|
||||||
//重头开始循环读取
|
//重头开始循环读取
|
||||||
GET_CONFIG(bool,fileRepeat,Record::kFileRepeat);
|
GET_CONFIG(bool,fileRepeat,Record::kFileRepeat);
|
||||||
if (fileRepeat && !bFlag0 && !bFlag1) {
|
if (fileRepeat && !bFlag0 && !bFlag1) {
|
||||||
seek(0);
|
seek(0);
|
||||||
}
|
}
|
||||||
//DebugL << "alive ...";
|
//DebugL << "alive ...";
|
||||||
//3秒延时关闭
|
//3秒延时关闭
|
||||||
return _alive.elapsedTime() < 3 * 1000;
|
return _alive.elapsedTime() < 3 * 1000;
|
||||||
}
|
}
|
||||||
inline bool MP4Reader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) {
|
inline bool MP4Reader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) {
|
||||||
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
||||||
auto iNextSample = getVideoSampleId(iTimeInc);
|
auto iNextSample = getVideoSampleId(iTimeInc);
|
||||||
MP4SampleId iIdx = _video_current;
|
MP4SampleId iIdx = _video_current;
|
||||||
for (; iIdx < iNextSample; iIdx++) {
|
for (; iIdx < iNextSample; iIdx++) {
|
||||||
uint8_t *pBytes = _pcVideoSample.get();
|
uint8_t *pBytes = _pcVideoSample.get();
|
||||||
uint32_t numBytes = _video_sample_max_size;
|
uint32_t numBytes = _video_sample_max_size;
|
||||||
MP4Duration pRenderingOffset;
|
MP4Duration pRenderingOffset;
|
||||||
if(MP4ReadSample(_hMP4File, _video_trId, iIdx + 1, &pBytes, &numBytes,NULL,NULL,&pRenderingOffset,&_bSyncSample)){
|
if(MP4ReadSample(_hMP4File, _video_trId, iIdx + 1, &pBytes, &numBytes,NULL,NULL,&pRenderingOffset,&_bSyncSample)){
|
||||||
if (!justSeekSyncFrame) {
|
if (!justSeekSyncFrame) {
|
||||||
uint32_t dts = (double) _video_ms * iIdx / _video_num_samples;
|
uint32_t dts = (double) _video_ms * iIdx / _video_num_samples;
|
||||||
uint32_t pts = dts + pRenderingOffset / 90;
|
uint32_t pts = dts + pRenderingOffset / 90;
|
||||||
uint32_t iOffset = 0;
|
uint32_t iOffset = 0;
|
||||||
while (iOffset < numBytes) {
|
while (iOffset < numBytes) {
|
||||||
uint32_t iFrameLen;
|
uint32_t iFrameLen;
|
||||||
memcpy(&iFrameLen,pBytes + iOffset,4);
|
memcpy(&iFrameLen,pBytes + iOffset,4);
|
||||||
iFrameLen = ntohl(iFrameLen);
|
iFrameLen = ntohl(iFrameLen);
|
||||||
if(iFrameLen + iOffset + 4> numBytes){
|
if(iFrameLen + iOffset + 4> numBytes){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
memcpy(pBytes + iOffset, "\x0\x0\x0\x1", 4);
|
memcpy(pBytes + iOffset, "\x0\x0\x0\x1", 4);
|
||||||
writeH264(pBytes + iOffset, iFrameLen + 4, dts, pts);
|
writeH264(pBytes + iOffset, iFrameLen + 4, dts, pts);
|
||||||
iOffset += (iFrameLen + 4);
|
iOffset += (iFrameLen + 4);
|
||||||
}
|
}
|
||||||
}else if(_bSyncSample){
|
}else if(_bSyncSample){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
ErrorL << "读取视频失败:" << iIdx + 1;
|
ErrorL << "读取视频失败:" << iIdx + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_video_current = iIdx;
|
_video_current = iIdx;
|
||||||
return _video_current < _video_num_samples;
|
return _video_current < _video_num_samples;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool MP4Reader::readAudioSample(int iTimeInc,bool justSeekSyncFrame) {
|
inline bool MP4Reader::readAudioSample(int iTimeInc,bool justSeekSyncFrame) {
|
||||||
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
||||||
auto iNextSample = getAudioSampleId(iTimeInc);
|
auto iNextSample = getAudioSampleId(iTimeInc);
|
||||||
for (auto i = _audio_current; i < iNextSample; i++) {
|
for (auto i = _audio_current; i < iNextSample; i++) {
|
||||||
uint32_t numBytes = _audio_sample_max_size;
|
uint32_t numBytes = _audio_sample_max_size;
|
||||||
uint8_t *pBytes = _adts.buffer + 7;
|
uint8_t *pBytes = _adts.buffer + 7;
|
||||||
if(MP4ReadSample(_hMP4File, _audio_trId, i + 1, &pBytes, &numBytes)){
|
if(MP4ReadSample(_hMP4File, _audio_trId, i + 1, &pBytes, &numBytes)){
|
||||||
if (!justSeekSyncFrame) {
|
if (!justSeekSyncFrame) {
|
||||||
uint32_t dts = (double) _audio_ms * i / _audio_num_samples;
|
uint32_t dts = (double) _audio_ms * i / _audio_num_samples;
|
||||||
_adts.aac_frame_length = 7 + numBytes;
|
_adts.aac_frame_length = 7 + numBytes;
|
||||||
writeAdtsHeader(_adts, _adts.buffer);
|
writeAdtsHeader(_adts, _adts.buffer);
|
||||||
writeAAC(_adts.buffer, _adts.aac_frame_length, dts);
|
writeAAC(_adts.buffer, _adts.aac_frame_length, dts);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
ErrorL << "读取音频失败:" << i+ 1;
|
ErrorL << "读取音频失败:" << i+ 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_audio_current = iNextSample;
|
_audio_current = iNextSample;
|
||||||
return _audio_current < _audio_num_samples;
|
return _audio_current < _audio_num_samples;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void MP4Reader::writeH264(uint8_t *pucData,int iLen,uint32_t dts,uint32_t pts) {
|
inline void MP4Reader::writeH264(uint8_t *pucData,int iLen,uint32_t dts,uint32_t pts) {
|
||||||
_mediaMuxer->inputFrame(std::make_shared<H264FrameNoCacheAble>((char*)pucData,iLen,dts,pts));
|
_mediaMuxer->inputFrame(std::make_shared<H264FrameNoCacheAble>((char*)pucData,iLen,dts,pts));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void MP4Reader::writeAAC(uint8_t *pucData,int iLen,uint32_t uiStamp) {
|
inline void MP4Reader::writeAAC(uint8_t *pucData,int iLen,uint32_t uiStamp) {
|
||||||
_mediaMuxer->inputFrame(std::make_shared<AACFrameNoCacheAble>((char*)pucData,iLen,uiStamp));
|
_mediaMuxer->inputFrame(std::make_shared<AACFrameNoCacheAble>((char*)pucData,iLen,uiStamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline MP4SampleId MP4Reader::getVideoSampleId(int iTimeInc ) {
|
inline MP4SampleId MP4Reader::getVideoSampleId(int iTimeInc ) {
|
||||||
MP4SampleId video_current = (double)_video_num_samples * (_iSeekTime + _ticker.elapsedTime() + iTimeInc) / _video_ms;
|
MP4SampleId video_current = (double)_video_num_samples * (_iSeekTime + _ticker.elapsedTime() + iTimeInc) / _video_ms;
|
||||||
video_current = MAX(0,MIN(_video_num_samples, video_current));
|
video_current = MAX(0,MIN(_video_num_samples, video_current));
|
||||||
return video_current;
|
return video_current;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline MP4SampleId MP4Reader::getAudioSampleId(int iTimeInc) {
|
inline MP4SampleId MP4Reader::getAudioSampleId(int iTimeInc) {
|
||||||
MP4SampleId audio_current = (double)_audio_num_samples * (_iSeekTime + _ticker.elapsedTime() + iTimeInc) / _audio_ms ;
|
MP4SampleId audio_current = (double)_audio_num_samples * (_iSeekTime + _ticker.elapsedTime() + iTimeInc) / _audio_ms ;
|
||||||
audio_current = MAX(0,MIN(_audio_num_samples,audio_current));
|
audio_current = MAX(0,MIN(_audio_num_samples,audio_current));
|
||||||
return audio_current;
|
return audio_current;
|
||||||
}
|
}
|
||||||
inline void MP4Reader::setSeekTime(uint32_t iSeekTime){
|
inline void MP4Reader::setSeekTime(uint32_t iSeekTime){
|
||||||
_iSeekTime = MAX(0, MIN(iSeekTime,_iDuration));
|
_iSeekTime = MAX(0, MIN(iSeekTime,_iDuration));
|
||||||
_ticker.resetTime();
|
_ticker.resetTime();
|
||||||
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
||||||
_audio_current = getAudioSampleId();
|
_audio_current = getAudioSampleId();
|
||||||
}
|
}
|
||||||
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
||||||
_video_current = getVideoSampleId();
|
_video_current = getVideoSampleId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t MP4Reader::getVideoCurrentTime(){
|
inline uint32_t MP4Reader::getVideoCurrentTime(){
|
||||||
return (double)_video_current * _video_ms /_video_num_samples;
|
return (double)_video_current * _video_ms /_video_num_samples;
|
||||||
}
|
}
|
||||||
void MP4Reader::seek(uint32_t iSeekTime,bool bReStart){
|
void MP4Reader::seek(uint32_t iSeekTime,bool bReStart){
|
||||||
lock_guard<recursive_mutex> lck(_mtx);
|
lock_guard<recursive_mutex> lck(_mtx);
|
||||||
if(iSeekTime == 0 || _video_trId == MP4_INVALID_TRACK_ID){
|
if(iSeekTime == 0 || _video_trId == MP4_INVALID_TRACK_ID){
|
||||||
setSeekTime(iSeekTime);
|
setSeekTime(iSeekTime);
|
||||||
}else{
|
}else{
|
||||||
setSeekTime(iSeekTime - 5000);
|
setSeekTime(iSeekTime - 5000);
|
||||||
//在之后的10秒查找关键帧
|
//在之后的10秒查找关键帧
|
||||||
readVideoSample(10000, true);
|
readVideoSample(10000, true);
|
||||||
if (_bSyncSample) {
|
if (_bSyncSample) {
|
||||||
//找到关键帧
|
//找到关键帧
|
||||||
auto iIdr = _video_current;
|
auto iIdr = _video_current;
|
||||||
setSeekTime(getVideoCurrentTime());
|
setSeekTime(getVideoCurrentTime());
|
||||||
_video_current = iIdr;
|
_video_current = iIdr;
|
||||||
}else{
|
}else{
|
||||||
//未找到关键帧
|
//未找到关键帧
|
||||||
setSeekTime(iSeekTime);
|
setSeekTime(iSeekTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_mediaMuxer->setTimeStamp(_iSeekTime);
|
_mediaMuxer->setTimeStamp(_iSeekTime);
|
||||||
|
|
||||||
if(bReStart){
|
if(bReStart){
|
||||||
_timer.reset();
|
_timer.reset();
|
||||||
startReadMP4();
|
startReadMP4();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //ENABLE_MP4V2
|
#endif //ENABLE_MP4V2
|
||||||
@ -344,26 +344,26 @@ void MP4Reader::seek(uint32_t iSeekTime,bool bReStart){
|
|||||||
|
|
||||||
|
|
||||||
MediaSource::Ptr MP4Reader::onMakeMediaSource(const string &strSchema,
|
MediaSource::Ptr MP4Reader::onMakeMediaSource(const string &strSchema,
|
||||||
const string &strVhost,
|
const string &strVhost,
|
||||||
const string &strApp,
|
const string &strApp,
|
||||||
const string &strId,
|
const string &strId,
|
||||||
const string &filePath,
|
const string &filePath,
|
||||||
bool checkApp ){
|
bool checkApp ){
|
||||||
#ifdef ENABLE_MP4V2
|
#ifdef ENABLE_MP4V2
|
||||||
GET_CONFIG(string,appName,Record::kAppName);
|
GET_CONFIG(string,appName,Record::kAppName);
|
||||||
if (checkApp && strApp != appName) {
|
if (checkApp && strApp != appName) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
MP4Reader::Ptr pReader(new MP4Reader(strVhost,strApp, strId,filePath));
|
MP4Reader::Ptr pReader(new MP4Reader(strVhost,strApp, strId,filePath));
|
||||||
pReader->startReadMP4();
|
pReader->startReadMP4();
|
||||||
return MediaSource::find(strSchema,strVhost,strApp, strId, false);
|
return MediaSource::find(strSchema,strVhost,strApp, strId, false);
|
||||||
} catch (std::exception &ex) {
|
} catch (std::exception &ex) {
|
||||||
WarnL << ex.what();
|
WarnL << ex.what();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
#endif //ENABLE_MP4V2
|
#endif //ENABLE_MP4V2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,91 +39,91 @@ namespace mediakit {
|
|||||||
|
|
||||||
class MP4Reader : public std::enable_shared_from_this<MP4Reader> ,public MediaSourceEvent{
|
class MP4Reader : public std::enable_shared_from_this<MP4Reader> ,public MediaSourceEvent{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<MP4Reader> Ptr;
|
typedef std::shared_ptr<MP4Reader> Ptr;
|
||||||
virtual ~MP4Reader();
|
virtual ~MP4Reader();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流化一个mp4文件,使之转换成RtspMediaSource和RtmpMediaSource
|
* 流化一个mp4文件,使之转换成RtspMediaSource和RtmpMediaSource
|
||||||
* @param strVhost 虚拟主机
|
* @param strVhost 虚拟主机
|
||||||
* @param strApp 应用名
|
* @param strApp 应用名
|
||||||
* @param strId 流id
|
* @param strId 流id
|
||||||
* @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件
|
* @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件
|
||||||
*/
|
*/
|
||||||
MP4Reader(const string &strVhost,const string &strApp, const string &strId,const string &filePath = "");
|
MP4Reader(const string &strVhost,const string &strApp, const string &strId,const string &filePath = "");
|
||||||
/**
|
/**
|
||||||
* 开始流化MP4文件,需要指出的是,MP4Reader对象一经过调用startReadMP4方法,它的强引用会自持有,
|
* 开始流化MP4文件,需要指出的是,MP4Reader对象一经过调用startReadMP4方法,它的强引用会自持有,
|
||||||
* 意思是在文件流化结束之前或中断之前,MP4Reader对象是不会被销毁的(不管有没有被外部对象持有)
|
* 意思是在文件流化结束之前或中断之前,MP4Reader对象是不会被销毁的(不管有没有被外部对象持有)
|
||||||
*/
|
*/
|
||||||
void startReadMP4();
|
void startReadMP4();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动生成MP4Reader对象然后查找相关的MediaSource对象
|
* 自动生成MP4Reader对象然后查找相关的MediaSource对象
|
||||||
* @param strSchema 协议名
|
* @param strSchema 协议名
|
||||||
* @param strVhost 虚拟主机
|
* @param strVhost 虚拟主机
|
||||||
* @param strApp 应用名
|
* @param strApp 应用名
|
||||||
* @param strId 流id
|
* @param strId 流id
|
||||||
* @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件
|
* @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件
|
||||||
* @param checkApp 是否检查app,防止服务器上文件被乱访问
|
* @param checkApp 是否检查app,防止服务器上文件被乱访问
|
||||||
* @return MediaSource
|
* @return MediaSource
|
||||||
*/
|
*/
|
||||||
static MediaSource::Ptr onMakeMediaSource(const string &strSchema,
|
static MediaSource::Ptr onMakeMediaSource(const string &strSchema,
|
||||||
const string &strVhost,
|
const string &strVhost,
|
||||||
const string &strApp,
|
const string &strApp,
|
||||||
const string &strId,
|
const string &strId,
|
||||||
const string &filePath = "",
|
const string &filePath = "",
|
||||||
bool checkApp = true);
|
bool checkApp = true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//MediaSourceEvent override
|
//MediaSourceEvent override
|
||||||
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
||||||
bool close(MediaSource &sender,bool force) override;
|
bool close(MediaSource &sender,bool force) override;
|
||||||
void onNoneReader(MediaSource &sender) override;
|
void onNoneReader(MediaSource &sender) override;
|
||||||
int totalReaderCount(MediaSource &sender) override;
|
int totalReaderCount(MediaSource &sender) override;
|
||||||
#ifdef ENABLE_MP4V2
|
#ifdef ENABLE_MP4V2
|
||||||
void seek(uint32_t iSeekTime,bool bReStart = true);
|
void seek(uint32_t iSeekTime,bool bReStart = true);
|
||||||
inline void setSeekTime(uint32_t iSeekTime);
|
inline void setSeekTime(uint32_t iSeekTime);
|
||||||
inline uint32_t getVideoCurrentTime();
|
inline uint32_t getVideoCurrentTime();
|
||||||
inline MP4SampleId getVideoSampleId(int iTimeInc = 0);
|
inline MP4SampleId getVideoSampleId(int iTimeInc = 0);
|
||||||
inline MP4SampleId getAudioSampleId(int iTimeInc = 0);
|
inline MP4SampleId getAudioSampleId(int iTimeInc = 0);
|
||||||
bool readSample(int iTimeInc, bool justSeekSyncFrame);
|
bool readSample(int iTimeInc, bool justSeekSyncFrame);
|
||||||
inline bool readVideoSample(int iTimeInc,bool justSeekSyncFrame);
|
inline bool readVideoSample(int iTimeInc,bool justSeekSyncFrame);
|
||||||
inline bool readAudioSample(int iTimeInc,bool justSeekSyncFrame);
|
inline bool readAudioSample(int iTimeInc,bool justSeekSyncFrame);
|
||||||
inline void writeH264(uint8_t *pucData,int iLen,uint32_t dts,uint32_t pts);
|
inline void writeH264(uint8_t *pucData,int iLen,uint32_t dts,uint32_t pts);
|
||||||
inline void writeAAC(uint8_t *pucData,int iLen,uint32_t uiStamp);
|
inline void writeAAC(uint8_t *pucData,int iLen,uint32_t uiStamp);
|
||||||
private:
|
private:
|
||||||
MP4FileHandle _hMP4File = MP4_INVALID_FILE_HANDLE;
|
MP4FileHandle _hMP4File = MP4_INVALID_FILE_HANDLE;
|
||||||
MP4TrackId _video_trId = MP4_INVALID_TRACK_ID;
|
MP4TrackId _video_trId = MP4_INVALID_TRACK_ID;
|
||||||
uint32_t _video_ms = 0;
|
uint32_t _video_ms = 0;
|
||||||
uint32_t _video_num_samples = 0;
|
uint32_t _video_num_samples = 0;
|
||||||
uint32_t _video_sample_max_size = 0;
|
uint32_t _video_sample_max_size = 0;
|
||||||
uint32_t _video_width = 0;
|
uint32_t _video_width = 0;
|
||||||
uint32_t _video_height = 0;
|
uint32_t _video_height = 0;
|
||||||
uint32_t _video_framerate = 0;
|
uint32_t _video_framerate = 0;
|
||||||
string _strPps;
|
string _strPps;
|
||||||
string _strSps;
|
string _strSps;
|
||||||
bool _bSyncSample = false;
|
bool _bSyncSample = false;
|
||||||
|
|
||||||
MP4TrackId _audio_trId = MP4_INVALID_TRACK_ID;
|
MP4TrackId _audio_trId = MP4_INVALID_TRACK_ID;
|
||||||
uint32_t _audio_ms = 0;
|
uint32_t _audio_ms = 0;
|
||||||
uint32_t _audio_num_samples = 0;
|
uint32_t _audio_num_samples = 0;
|
||||||
uint32_t _audio_sample_max_size = 0;
|
uint32_t _audio_sample_max_size = 0;
|
||||||
uint32_t _audio_sample_rate = 0;
|
uint32_t _audio_sample_rate = 0;
|
||||||
uint32_t _audio_num_channels = 0;
|
uint32_t _audio_num_channels = 0;
|
||||||
string _strAacCfg;
|
string _strAacCfg;
|
||||||
AACFrame _adts;
|
AACFrame _adts;
|
||||||
|
|
||||||
int _iDuration = 0;
|
int _iDuration = 0;
|
||||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||||
MP4SampleId _video_current = 0;
|
MP4SampleId _video_current = 0;
|
||||||
MP4SampleId _audio_current = 0;
|
MP4SampleId _audio_current = 0;
|
||||||
std::shared_ptr<uint8_t> _pcVideoSample;
|
std::shared_ptr<uint8_t> _pcVideoSample;
|
||||||
|
|
||||||
int _iSeekTime = 0 ;
|
int _iSeekTime = 0 ;
|
||||||
Ticker _ticker;
|
Ticker _ticker;
|
||||||
Ticker _alive;
|
Ticker _alive;
|
||||||
recursive_mutex _mtx;
|
recursive_mutex _mtx;
|
||||||
Timer::Ptr _timer;
|
Timer::Ptr _timer;
|
||||||
EventPoller::Ptr _poller;
|
EventPoller::Ptr _poller;
|
||||||
#endif //ENABLE_MP4V2
|
#endif //ENABLE_MP4V2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,31 +36,31 @@ using namespace toolkit;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
MP4Recorder::MP4Recorder(const string& strPath,
|
MP4Recorder::MP4Recorder(const string& strPath,
|
||||||
const string &strVhost,
|
const string &strVhost,
|
||||||
const string &strApp,
|
const string &strApp,
|
||||||
const string &strStreamId) {
|
const string &strStreamId) {
|
||||||
_strPath = strPath;
|
_strPath = strPath;
|
||||||
/////record 业务逻辑//////
|
/////record 业务逻辑//////
|
||||||
_info.strAppName = strApp;
|
_info.strAppName = strApp;
|
||||||
_info.strStreamId = strStreamId;
|
_info.strStreamId = strStreamId;
|
||||||
_info.strVhost = strVhost;
|
_info.strVhost = strVhost;
|
||||||
_info.strFolder = strPath;
|
_info.strFolder = strPath;
|
||||||
}
|
}
|
||||||
MP4Recorder::~MP4Recorder() {
|
MP4Recorder::~MP4Recorder() {
|
||||||
closeFile();
|
closeFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Recorder::createFile() {
|
void MP4Recorder::createFile() {
|
||||||
closeFile();
|
closeFile();
|
||||||
auto strDate = getTimeStr("%Y-%m-%d");
|
auto strDate = getTimeStr("%Y-%m-%d");
|
||||||
auto strTime = getTimeStr("%H-%M-%S");
|
auto strTime = getTimeStr("%H-%M-%S");
|
||||||
auto strFileTmp = _strPath + strDate + "/." + strTime + ".mp4";
|
auto strFileTmp = _strPath + strDate + "/." + strTime + ".mp4";
|
||||||
auto strFile = _strPath + strDate + "/" + strTime + ".mp4";
|
auto strFile = _strPath + strDate + "/" + strTime + ".mp4";
|
||||||
|
|
||||||
/////record 业务逻辑//////
|
/////record 业务逻辑//////
|
||||||
_info.ui64StartedTime = ::time(NULL);
|
_info.ui64StartedTime = ::time(NULL);
|
||||||
_info.strFileName = strTime + ".mp4";
|
_info.strFileName = strTime + ".mp4";
|
||||||
_info.strFilePath = strFile;
|
_info.strFilePath = strFile;
|
||||||
GET_CONFIG(string,appName,Record::kAppName);
|
GET_CONFIG(string,appName,Record::kAppName);
|
||||||
_info.strUrl = appName + "/"
|
_info.strUrl = appName + "/"
|
||||||
+ _info.strAppName + "/"
|
+ _info.strAppName + "/"
|
||||||
@ -68,78 +68,78 @@ void MP4Recorder::createFile() {
|
|||||||
+ strDate + "/"
|
+ strDate + "/"
|
||||||
+ strTime + ".mp4";
|
+ strTime + ".mp4";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_muxer = std::make_shared<MP4MuxerFile>(strFileTmp.data());
|
_muxer = std::make_shared<MP4MuxerFile>(strFileTmp.data());
|
||||||
for(auto &track :_tracks){
|
for(auto &track :_tracks){
|
||||||
//添加track
|
//添加track
|
||||||
_muxer->addTrack(track);
|
_muxer->addTrack(track);
|
||||||
}
|
}
|
||||||
_strFileTmp = strFileTmp;
|
_strFileTmp = strFileTmp;
|
||||||
_strFile = strFile;
|
_strFile = strFile;
|
||||||
_createFileTicker.resetTime();
|
_createFileTicker.resetTime();
|
||||||
}catch(std::exception &ex) {
|
}catch(std::exception &ex) {
|
||||||
WarnL << ex.what();
|
WarnL << ex.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Recorder::asyncClose() {
|
void MP4Recorder::asyncClose() {
|
||||||
auto muxer = _muxer;
|
auto muxer = _muxer;
|
||||||
auto strFileTmp = _strFileTmp;
|
auto strFileTmp = _strFileTmp;
|
||||||
auto strFile = _strFile;
|
auto strFile = _strFile;
|
||||||
auto info = _info;
|
auto info = _info;
|
||||||
WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() {
|
WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() {
|
||||||
//获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间
|
//获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间
|
||||||
const_cast<MP4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
|
const_cast<MP4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
|
||||||
//关闭mp4非常耗时,所以要放在后台线程执行
|
//关闭mp4非常耗时,所以要放在后台线程执行
|
||||||
const_cast<MP4MuxerFile::Ptr &>(muxer).reset();
|
const_cast<MP4MuxerFile::Ptr &>(muxer).reset();
|
||||||
//临时文件名改成正式文件名,防止mp4未完成时被访问
|
//临时文件名改成正式文件名,防止mp4未完成时被访问
|
||||||
rename(strFileTmp.data(),strFile.data());
|
rename(strFileTmp.data(),strFile.data());
|
||||||
//获取文件大小
|
//获取文件大小
|
||||||
struct stat fileData;
|
struct stat fileData;
|
||||||
stat(strFile.data(), &fileData);
|
stat(strFile.data(), &fileData);
|
||||||
const_cast<MP4Info&>(info).ui64FileSize = fileData.st_size;
|
const_cast<MP4Info&>(info).ui64FileSize = fileData.st_size;
|
||||||
/////record 业务逻辑//////
|
/////record 业务逻辑//////
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4,info);
|
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4,info);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Recorder::closeFile() {
|
void MP4Recorder::closeFile() {
|
||||||
if (_muxer) {
|
if (_muxer) {
|
||||||
asyncClose();
|
asyncClose();
|
||||||
_muxer = nullptr;
|
_muxer = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Recorder::inputFrame(const Frame::Ptr &frame) {
|
void MP4Recorder::inputFrame(const Frame::Ptr &frame) {
|
||||||
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
|
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
|
||||||
if(!_muxer || ((_createFileTicker.elapsedTime() > recordSec * 1000) &&
|
if(!_muxer || ((_createFileTicker.elapsedTime() > recordSec * 1000) &&
|
||||||
(!_haveVideo || (_haveVideo && frame->keyFrame()))) ){
|
(!_haveVideo || (_haveVideo && frame->keyFrame()))) ){
|
||||||
//成立条件
|
//成立条件
|
||||||
//1、_muxer为空
|
//1、_muxer为空
|
||||||
//2、到了切片时间,并且只有音频
|
//2、到了切片时间,并且只有音频
|
||||||
//3、到了切片时间,有视频并且遇到视频的关键帧
|
//3、到了切片时间,有视频并且遇到视频的关键帧
|
||||||
createFile();
|
createFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_muxer){
|
if(_muxer){
|
||||||
//生成mp4文件
|
//生成mp4文件
|
||||||
_muxer->inputFrame(frame);
|
_muxer->inputFrame(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Recorder::addTrack(const Track::Ptr & track){
|
void MP4Recorder::addTrack(const Track::Ptr & track){
|
||||||
//保存所有的track,为创建MP4MuxerFile做准备
|
//保存所有的track,为创建MP4MuxerFile做准备
|
||||||
_tracks.emplace_back(track);
|
_tracks.emplace_back(track);
|
||||||
if(track->getTrackType() == TrackVideo){
|
if(track->getTrackType() == TrackVideo){
|
||||||
_haveVideo = true;
|
_haveVideo = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Recorder::resetTracks() {
|
void MP4Recorder::resetTracks() {
|
||||||
closeFile();
|
closeFile();
|
||||||
_tracks.clear();
|
_tracks.clear();
|
||||||
_haveVideo = false;
|
_haveVideo = false;
|
||||||
_createFileTicker.resetTime();
|
_createFileTicker.resetTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -42,38 +42,38 @@ namespace mediakit {
|
|||||||
|
|
||||||
class MP4Info {
|
class MP4Info {
|
||||||
public:
|
public:
|
||||||
time_t ui64StartedTime; //GMT标准时间,单位秒
|
time_t ui64StartedTime; //GMT标准时间,单位秒
|
||||||
time_t ui64TimeLen;//录像长度,单位秒
|
time_t ui64TimeLen;//录像长度,单位秒
|
||||||
off_t ui64FileSize;//文件大小,单位BYTE
|
off_t ui64FileSize;//文件大小,单位BYTE
|
||||||
string strFilePath;//文件路径
|
string strFilePath;//文件路径
|
||||||
string strFileName;//文件名称
|
string strFileName;//文件名称
|
||||||
string strFolder;//文件夹路径
|
string strFolder;//文件夹路径
|
||||||
string strUrl;//播放路径
|
string strUrl;//播放路径
|
||||||
string strAppName;//应用名称
|
string strAppName;//应用名称
|
||||||
string strStreamId;//流ID
|
string strStreamId;//流ID
|
||||||
string strVhost;//vhost
|
string strVhost;//vhost
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef ENABLE_MP4RECORD
|
#ifdef ENABLE_MP4RECORD
|
||||||
class MP4Recorder : public MediaSinkInterface{
|
class MP4Recorder : public MediaSinkInterface{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<MP4Recorder> Ptr;
|
typedef std::shared_ptr<MP4Recorder> Ptr;
|
||||||
|
|
||||||
MP4Recorder(const string &strPath,
|
MP4Recorder(const string &strPath,
|
||||||
const string &strVhost,
|
const string &strVhost,
|
||||||
const string &strApp,
|
const string &strApp,
|
||||||
const string &strStreamId);
|
const string &strStreamId);
|
||||||
virtual ~MP4Recorder();
|
virtual ~MP4Recorder();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置所有Track
|
* 重置所有Track
|
||||||
*/
|
*/
|
||||||
void resetTracks() override;
|
void resetTracks() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入frame
|
* 输入frame
|
||||||
*/
|
*/
|
||||||
void inputFrame(const Frame::Ptr &frame) override;
|
void inputFrame(const Frame::Ptr &frame) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加ready状态的track
|
* 添加ready状态的track
|
||||||
@ -84,14 +84,14 @@ private:
|
|||||||
void closeFile();
|
void closeFile();
|
||||||
void asyncClose();
|
void asyncClose();
|
||||||
private:
|
private:
|
||||||
string _strPath;
|
string _strPath;
|
||||||
string _strFile;
|
string _strFile;
|
||||||
string _strFileTmp;
|
string _strFileTmp;
|
||||||
Ticker _createFileTicker;
|
Ticker _createFileTicker;
|
||||||
MP4Info _info;
|
MP4Info _info;
|
||||||
bool _haveVideo = false;
|
bool _haveVideo = false;
|
||||||
MP4MuxerFile::Ptr _muxer;
|
MP4MuxerFile::Ptr _muxer;
|
||||||
list<Track::Ptr> _tracks;
|
list<Track::Ptr> _tracks;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif ///ENABLE_MP4RECORD
|
#endif ///ENABLE_MP4RECORD
|
||||||
|
@ -37,32 +37,32 @@ class MediaSinkInterface;
|
|||||||
|
|
||||||
class Recorder{
|
class Recorder{
|
||||||
public:
|
public:
|
||||||
typedef enum {
|
typedef enum {
|
||||||
// 未录制
|
// 未录制
|
||||||
status_not_record = 0,
|
status_not_record = 0,
|
||||||
// 等待MediaSource注册,注册成功后立即开始录制
|
// 等待MediaSource注册,注册成功后立即开始录制
|
||||||
status_wait_record = 1,
|
status_wait_record = 1,
|
||||||
// MediaSource已注册,并且正在录制
|
// MediaSource已注册,并且正在录制
|
||||||
status_recording = 2,
|
status_recording = 2,
|
||||||
} status;
|
} status;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
// 录制hls
|
// 录制hls
|
||||||
type_hls = 0,
|
type_hls = 0,
|
||||||
// 录制MP4
|
// 录制MP4
|
||||||
type_mp4 = 1
|
type_mp4 = 1
|
||||||
} type;
|
} type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取录制文件绝对路径
|
* 获取录制文件绝对路径
|
||||||
* @param type hls还是MP4录制
|
* @param type hls还是MP4录制
|
||||||
* @param vhost 虚拟主机
|
* @param vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream_id 流id
|
* @param stream_id 流id
|
||||||
* @param customized_path 录像文件保存自定义目录,默认为空则自动生成
|
* @param customized_path 录像文件保存自定义目录,默认为空则自动生成
|
||||||
* @return 录制文件绝对路径
|
* @return 录制文件绝对路径
|
||||||
*/
|
*/
|
||||||
static string getRecordPath(type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path = "");
|
static string getRecordPath(type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path = "");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取录制状态
|
* 获取录制状态
|
||||||
@ -72,57 +72,57 @@ public:
|
|||||||
* @param stream_id 流id
|
* @param stream_id 流id
|
||||||
* @return 录制状态
|
* @return 录制状态
|
||||||
*/
|
*/
|
||||||
static status getRecordStatus(type type, const string &vhost, const string &app, const string &stream_id);
|
static status getRecordStatus(type type, const string &vhost, const string &app, const string &stream_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始录制
|
* 开始录制
|
||||||
* @param type hls还是MP4录制
|
* @param type hls还是MP4录制
|
||||||
* @param vhost 虚拟主机
|
* @param vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream_id 流id
|
* @param stream_id 流id
|
||||||
* @param customized_path 录像文件保存自定义目录,默认为空则自动生成
|
* @param customized_path 录像文件保存自定义目录,默认为空则自动生成
|
||||||
* @param waitForRecord 是否等待流注册后再录制,未注册时,置false将返回失败
|
* @param waitForRecord 是否等待流注册后再录制,未注册时,置false将返回失败
|
||||||
* @param continueRecord 流注销时是否继续等待录制还是立即停止录制
|
* @param continueRecord 流注销时是否继续等待录制还是立即停止录制
|
||||||
* @return 0代表成功,负数代表失败
|
* @return 0代表成功,负数代表失败
|
||||||
*/
|
*/
|
||||||
static int startRecord(type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path,bool waitForRecord, bool continueRecord);
|
static int startRecord(type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path,bool waitForRecord, bool continueRecord);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止录制
|
* 停止录制
|
||||||
* @param type hls还是MP4录制
|
* @param type hls还是MP4录制
|
||||||
* @param vhost 虚拟主机
|
* @param vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream_id 流id
|
* @param stream_id 流id
|
||||||
*/
|
*/
|
||||||
static bool stopRecord(type type, const string &vhost, const string &app, const string &stream_id);
|
static bool stopRecord(type type, const string &vhost, const string &app, const string &stream_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止所有录制,一般程序退出时调用
|
* 停止所有录制,一般程序退出时调用
|
||||||
*/
|
*/
|
||||||
static void stopAll();
|
static void stopAll();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取录制对象
|
* 获取录制对象
|
||||||
* @param type hls还是MP4录制
|
* @param type hls还是MP4录制
|
||||||
* @param vhost 虚拟主机
|
* @param vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream_id 流id
|
* @param stream_id 流id
|
||||||
*/
|
*/
|
||||||
static std::shared_ptr<MediaSinkInterface> getRecorder(type type, const string &vhost, const string &app, const string &stream_id);
|
static std::shared_ptr<MediaSinkInterface> getRecorder(type type, const string &vhost, const string &app, const string &stream_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建录制器对象
|
* 创建录制器对象
|
||||||
* @param type hls还是MP4录制
|
* @param type hls还是MP4录制
|
||||||
* @param vhost 虚拟主机
|
* @param vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream_id 流id
|
* @param stream_id 流id
|
||||||
* @param customized_path 录像文件保存自定义目录,默认为空则自动生成
|
* @param customized_path 录像文件保存自定义目录,默认为空则自动生成
|
||||||
* @return 对象指针,可能为nullptr
|
* @return 对象指针,可能为nullptr
|
||||||
*/
|
*/
|
||||||
static std::shared_ptr<MediaSinkInterface> createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path);
|
static std::shared_ptr<MediaSinkInterface> createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path);
|
||||||
private:
|
private:
|
||||||
Recorder() = delete;
|
Recorder() = delete;
|
||||||
~Recorder() = delete;
|
~Recorder() = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -41,27 +41,27 @@ namespace mediakit {
|
|||||||
|
|
||||||
class RtmpDemuxer : public Demuxer{
|
class RtmpDemuxer : public Demuxer{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtmpDemuxer> Ptr;
|
typedef std::shared_ptr<RtmpDemuxer> Ptr;
|
||||||
|
|
||||||
RtmpDemuxer() = default;
|
RtmpDemuxer() = default;
|
||||||
virtual ~RtmpDemuxer() = default;
|
virtual ~RtmpDemuxer() = default;
|
||||||
|
|
||||||
void loadMetaData(const AMFValue &metadata);
|
void loadMetaData(const AMFValue &metadata);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始解复用
|
* 开始解复用
|
||||||
* @param pkt rtmp包
|
* @param pkt rtmp包
|
||||||
* @return true 代表是i帧
|
* @return true 代表是i帧
|
||||||
*/
|
*/
|
||||||
bool inputRtmp(const RtmpPacket::Ptr &pkt);
|
bool inputRtmp(const RtmpPacket::Ptr &pkt);
|
||||||
private:
|
private:
|
||||||
void makeVideoTrack(const AMFValue &val);
|
void makeVideoTrack(const AMFValue &val);
|
||||||
void makeAudioTrack(const AMFValue &val);
|
void makeAudioTrack(const AMFValue &val);
|
||||||
private:
|
private:
|
||||||
bool _tryedGetVideoTrack = false;
|
bool _tryedGetVideoTrack = false;
|
||||||
bool _tryedGetAudioTrack = false;
|
bool _tryedGetAudioTrack = false;
|
||||||
RtmpCodec::Ptr _audioRtmpDecoder;
|
RtmpCodec::Ptr _audioRtmpDecoder;
|
||||||
RtmpCodec::Ptr _videoRtmpDecoder;
|
RtmpCodec::Ptr _videoRtmpDecoder;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -58,163 +58,163 @@ namespace mediakit {
|
|||||||
*/
|
*/
|
||||||
class RtmpMediaSource : public MediaSource, public RingDelegate<RtmpPacket::Ptr> {
|
class RtmpMediaSource : public MediaSource, public RingDelegate<RtmpPacket::Ptr> {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtmpMediaSource> Ptr;
|
typedef std::shared_ptr<RtmpMediaSource> Ptr;
|
||||||
typedef RingBuffer<RtmpPacket::Ptr> RingType;
|
typedef RingBuffer<RtmpPacket::Ptr> RingType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param vhost 虚拟主机名
|
* @param vhost 虚拟主机名
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream_id 流id
|
* @param stream_id 流id
|
||||||
* @param ring_size 可以设置固定的环形缓冲大小,0则自适应
|
* @param ring_size 可以设置固定的环形缓冲大小,0则自适应
|
||||||
*/
|
*/
|
||||||
RtmpMediaSource(const string &vhost,
|
RtmpMediaSource(const string &vhost,
|
||||||
const string &app,
|
const string &app,
|
||||||
const string &stream_id,
|
const string &stream_id,
|
||||||
int ring_size = RTMP_GOP_SIZE) :
|
int ring_size = RTMP_GOP_SIZE) :
|
||||||
MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {
|
MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~RtmpMediaSource() {}
|
virtual ~RtmpMediaSource() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取媒体源的环形缓冲
|
* 获取媒体源的环形缓冲
|
||||||
*/
|
*/
|
||||||
const RingType::Ptr &getRing() const {
|
const RingType::Ptr &getRing() const {
|
||||||
return _ring;
|
return _ring;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取播放器个数
|
* 获取播放器个数
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
int readerCount() override {
|
int readerCount() override {
|
||||||
return _ring ? _ring->readerCount() : 0;
|
return _ring ? _ring->readerCount() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取metadata
|
* 获取metadata
|
||||||
*/
|
*/
|
||||||
const AMFValue &getMetaData() const {
|
const AMFValue &getMetaData() const {
|
||||||
lock_guard<recursive_mutex> lock(_mtx);
|
lock_guard<recursive_mutex> lock(_mtx);
|
||||||
return _metadata;
|
return _metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有的config帧
|
* 获取所有的config帧
|
||||||
*/
|
*/
|
||||||
template<typename FUNC>
|
template<typename FUNC>
|
||||||
void getConfigFrame(const FUNC &f) {
|
void getConfigFrame(const FUNC &f) {
|
||||||
lock_guard<recursive_mutex> lock(_mtx);
|
lock_guard<recursive_mutex> lock(_mtx);
|
||||||
for (auto &pr : _config_frame_map) {
|
for (auto &pr : _config_frame_map) {
|
||||||
f(pr.second);
|
f(pr.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置metadata
|
* 设置metadata
|
||||||
*/
|
*/
|
||||||
virtual void setMetaData(const AMFValue &metadata) {
|
virtual void setMetaData(const AMFValue &metadata) {
|
||||||
lock_guard<recursive_mutex> lock(_mtx);
|
lock_guard<recursive_mutex> lock(_mtx);
|
||||||
_metadata = metadata;
|
_metadata = metadata;
|
||||||
if(_ring){
|
if(_ring){
|
||||||
regist();
|
regist();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入rtmp包
|
* 输入rtmp包
|
||||||
* @param pkt rtmp包
|
* @param pkt rtmp包
|
||||||
* @param key 是否为关键帧
|
* @param key 是否为关键帧
|
||||||
*/
|
*/
|
||||||
void onWrite(const RtmpPacket::Ptr &pkt, bool key = true) override {
|
void onWrite(const RtmpPacket::Ptr &pkt, bool key = true) override {
|
||||||
lock_guard<recursive_mutex> lock(_mtx);
|
lock_guard<recursive_mutex> lock(_mtx);
|
||||||
if(pkt->typeId == MSG_VIDEO){
|
if(pkt->typeId == MSG_VIDEO){
|
||||||
//有视频,那么启用GOP缓存
|
//有视频,那么启用GOP缓存
|
||||||
_have_video = true;
|
_have_video = true;
|
||||||
}
|
}
|
||||||
if (pkt->isCfgFrame()) {
|
if (pkt->isCfgFrame()) {
|
||||||
_config_frame_map[pkt->typeId] = pkt;
|
_config_frame_map[pkt->typeId] = pkt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_ring) {
|
if (!_ring) {
|
||||||
weak_ptr<RtmpMediaSource> weakSelf = dynamic_pointer_cast<RtmpMediaSource>(shared_from_this());
|
weak_ptr<RtmpMediaSource> weakSelf = dynamic_pointer_cast<RtmpMediaSource>(shared_from_this());
|
||||||
auto lam = [weakSelf](const EventPoller::Ptr &, int size, bool) {
|
auto lam = [weakSelf](const EventPoller::Ptr &, int size, bool) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if (!strongSelf) {
|
if (!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strongSelf->onReaderChanged(size);
|
strongSelf->onReaderChanged(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
//rtmp包缓存最大允许512个,如果是纯视频(25fps)大概为20秒数据
|
//rtmp包缓存最大允许512个,如果是纯视频(25fps)大概为20秒数据
|
||||||
//但是这个是GOP缓存的上限值,真实的GOP缓存大小等于两个I帧之间的包数的两倍
|
//但是这个是GOP缓存的上限值,真实的GOP缓存大小等于两个I帧之间的包数的两倍
|
||||||
//而且每次遇到I帧,则会清空GOP缓存,所以真实的GOP缓存远小于此值
|
//而且每次遇到I帧,则会清空GOP缓存,所以真实的GOP缓存远小于此值
|
||||||
_ring = std::make_shared<RingType>(_ring_size,std::move(lam));
|
_ring = std::make_shared<RingType>(_ring_size,std::move(lam));
|
||||||
onReaderChanged(0);
|
onReaderChanged(0);
|
||||||
|
|
||||||
if(_metadata){
|
if(_metadata){
|
||||||
regist();
|
regist();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_track_stamps_map[pkt->typeId] = pkt->timeStamp;
|
_track_stamps_map[pkt->typeId] = pkt->timeStamp;
|
||||||
//不存在视频,为了减少缓存延时,那么关闭GOP缓存
|
//不存在视频,为了减少缓存延时,那么关闭GOP缓存
|
||||||
_ring->write(pkt, _have_video ? pkt->isVideoKeyFrame() : true);
|
_ring->write(pkt, _have_video ? pkt->isVideoKeyFrame() : true);
|
||||||
checkNoneReader();
|
checkNoneReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前时间戳
|
* 获取当前时间戳
|
||||||
*/
|
*/
|
||||||
uint32_t getTimeStamp(TrackType trackType) override {
|
uint32_t getTimeStamp(TrackType trackType) override {
|
||||||
lock_guard<recursive_mutex> lock(_mtx);
|
lock_guard<recursive_mutex> lock(_mtx);
|
||||||
switch (trackType) {
|
switch (trackType) {
|
||||||
case TrackVideo:
|
case TrackVideo:
|
||||||
return _track_stamps_map[MSG_VIDEO];
|
return _track_stamps_map[MSG_VIDEO];
|
||||||
case TrackAudio:
|
case TrackAudio:
|
||||||
return _track_stamps_map[MSG_AUDIO];
|
return _track_stamps_map[MSG_AUDIO];
|
||||||
default:
|
default:
|
||||||
return MAX(_track_stamps_map[MSG_VIDEO], _track_stamps_map[MSG_AUDIO]);
|
return MAX(_track_stamps_map[MSG_VIDEO], _track_stamps_map[MSG_AUDIO]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* 每次增减消费者都会触发该函数
|
* 每次增减消费者都会触发该函数
|
||||||
*/
|
*/
|
||||||
void onReaderChanged(int size) {
|
void onReaderChanged(int size) {
|
||||||
//我们记录最后一次活动时间
|
//我们记录最后一次活动时间
|
||||||
_reader_changed_ticker.resetTime();
|
_reader_changed_ticker.resetTime();
|
||||||
if (size != 0 || totalReaderCount() != 0) {
|
if (size != 0 || totalReaderCount() != 0) {
|
||||||
//还有消费者正在观看该流
|
//还有消费者正在观看该流
|
||||||
_async_emit_none_reader = false;
|
_async_emit_none_reader = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_async_emit_none_reader = true;
|
_async_emit_none_reader = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否无人消费该流,
|
* 检查是否无人消费该流,
|
||||||
* 如果无人消费且超过一定时间会触发onNoneReader事件
|
* 如果无人消费且超过一定时间会触发onNoneReader事件
|
||||||
*/
|
*/
|
||||||
void checkNoneReader() {
|
void checkNoneReader() {
|
||||||
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
||||||
if (_async_emit_none_reader && _reader_changed_ticker.elapsedTime() > stream_none_reader_delay) {
|
if (_async_emit_none_reader && _reader_changed_ticker.elapsedTime() > stream_none_reader_delay) {
|
||||||
_async_emit_none_reader = false;
|
_async_emit_none_reader = false;
|
||||||
onNoneReader();
|
onNoneReader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
int _ring_size;
|
int _ring_size;
|
||||||
bool _async_emit_none_reader = false;
|
bool _async_emit_none_reader = false;
|
||||||
bool _have_video = false;
|
bool _have_video = false;
|
||||||
mutable recursive_mutex _mtx;
|
mutable recursive_mutex _mtx;
|
||||||
Ticker _reader_changed_ticker;
|
Ticker _reader_changed_ticker;
|
||||||
AMFValue _metadata;
|
AMFValue _metadata;
|
||||||
RingBuffer<RtmpPacket::Ptr>::Ptr _ring;
|
RingBuffer<RtmpPacket::Ptr>::Ptr _ring;
|
||||||
unordered_map<int, uint32_t> _track_stamps_map;
|
unordered_map<int, uint32_t> _track_stamps_map;
|
||||||
unordered_map<int, RtmpPacket::Ptr> _config_frame_map;
|
unordered_map<int, RtmpPacket::Ptr> _config_frame_map;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -45,46 +45,46 @@ using namespace toolkit;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
class RtmpMediaSourceImp: public RtmpMediaSource, public Demuxer::Listener , public MultiMediaSourceMuxer::Listener {
|
class RtmpMediaSourceImp: public RtmpMediaSource, public Demuxer::Listener , public MultiMediaSourceMuxer::Listener {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtmpMediaSourceImp> Ptr;
|
typedef std::shared_ptr<RtmpMediaSourceImp> Ptr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param vhost 虚拟主机
|
* @param vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param id 流id
|
* @param id 流id
|
||||||
* @param ringSize 环形缓存大小
|
* @param ringSize 环形缓存大小
|
||||||
*/
|
*/
|
||||||
RtmpMediaSourceImp(const string &vhost, const string &app, const string &id, int ringSize = RTMP_GOP_SIZE) : RtmpMediaSource(vhost, app, id, ringSize) {
|
RtmpMediaSourceImp(const string &vhost, const string &app, const string &id, int ringSize = RTMP_GOP_SIZE) : RtmpMediaSource(vhost, app, id, ringSize) {
|
||||||
_demuxer = std::make_shared<RtmpDemuxer>();
|
_demuxer = std::make_shared<RtmpDemuxer>();
|
||||||
_demuxer->setTrackListener(this);
|
_demuxer->setTrackListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
~RtmpMediaSourceImp() = default;
|
~RtmpMediaSourceImp() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置metadata
|
* 设置metadata
|
||||||
*/
|
*/
|
||||||
void setMetaData(const AMFValue &metadata) override{
|
void setMetaData(const AMFValue &metadata) override{
|
||||||
_demuxer->loadMetaData(metadata);
|
_demuxer->loadMetaData(metadata);
|
||||||
RtmpMediaSource::setMetaData(metadata);
|
RtmpMediaSource::setMetaData(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入rtmp并解析
|
* 输入rtmp并解析
|
||||||
*/
|
*/
|
||||||
void onWrite(const RtmpPacket::Ptr &pkt,bool key_pos = true) override {
|
void onWrite(const RtmpPacket::Ptr &pkt,bool key_pos = true) override {
|
||||||
key_pos = _demuxer->inputRtmp(pkt);
|
key_pos = _demuxer->inputRtmp(pkt);
|
||||||
RtmpMediaSource::onWrite(pkt,key_pos);
|
RtmpMediaSource::onWrite(pkt,key_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置监听器
|
* 设置监听器
|
||||||
* @param listener
|
* @param listener
|
||||||
*/
|
*/
|
||||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) override {
|
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) override {
|
||||||
RtmpMediaSource::setListener(listener);
|
RtmpMediaSource::setListener(listener);
|
||||||
if(_muxer){
|
if(_muxer){
|
||||||
_muxer->setListener(listener);
|
_muxer->setListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,42 +95,42 @@ public:
|
|||||||
return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0);
|
return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置协议转换
|
* 设置协议转换
|
||||||
* @param enableRtsp 是否转换成rtsp
|
* @param enableRtsp 是否转换成rtsp
|
||||||
* @param enableHls 是否转换成hls
|
* @param enableHls 是否转换成hls
|
||||||
* @param enableMP4 是否mp4录制
|
* @param enableMP4 是否mp4录制
|
||||||
*/
|
*/
|
||||||
void setProtocolTranslation(bool enableRtsp, bool enableHls, bool enableMP4) {
|
void setProtocolTranslation(bool enableRtsp, bool enableHls, bool enableMP4) {
|
||||||
//不重复生成rtmp
|
//不重复生成rtmp
|
||||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), enableRtsp, false, enableHls, enableMP4);
|
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), enableRtsp, false, enableHls, enableMP4);
|
||||||
_muxer->setListener(getListener());
|
_muxer->setListener(getListener());
|
||||||
_muxer->setTrackListener(this);
|
_muxer->setTrackListener(this);
|
||||||
for(auto &track : _demuxer->getTracks(false)){
|
for(auto &track : _demuxer->getTracks(false)){
|
||||||
_muxer->addTrack(track);
|
_muxer->addTrack(track);
|
||||||
track->addDelegate(_muxer);
|
track->addDelegate(_muxer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _demuxer触发的添加Track事件
|
* _demuxer触发的添加Track事件
|
||||||
*/
|
*/
|
||||||
void onAddTrack(const Track::Ptr &track) override {
|
void onAddTrack(const Track::Ptr &track) override {
|
||||||
if(_muxer){
|
if(_muxer){
|
||||||
_muxer->addTrack(track);
|
_muxer->addTrack(track);
|
||||||
track->addDelegate(_muxer);
|
track->addDelegate(_muxer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _muxer触发的所有Track就绪的事件
|
* _muxer触发的所有Track就绪的事件
|
||||||
*/
|
*/
|
||||||
void onAllTrackReady() override{
|
void onAllTrackReady() override{
|
||||||
setTrackSource(_muxer);
|
setTrackSource(_muxer);
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
RtmpDemuxer::Ptr _demuxer;
|
RtmpDemuxer::Ptr _demuxer;
|
||||||
MultiMediaSourceMuxer::Ptr _muxer;
|
MultiMediaSourceMuxer::Ptr _muxer;
|
||||||
};
|
};
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
|
||||||
|
@ -38,35 +38,35 @@ RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RtmpPlayer::~RtmpPlayer() {
|
RtmpPlayer::~RtmpPlayer() {
|
||||||
DebugL << endl;
|
DebugL << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPlayer::teardown() {
|
void RtmpPlayer::teardown() {
|
||||||
if (alive()) {
|
if (alive()) {
|
||||||
shutdown(SockException(Err_shutdown,"teardown"));
|
shutdown(SockException(Err_shutdown,"teardown"));
|
||||||
}
|
}
|
||||||
_strApp.clear();
|
_strApp.clear();
|
||||||
_strStream.clear();
|
_strStream.clear();
|
||||||
_strTcUrl.clear();
|
_strTcUrl.clear();
|
||||||
_pBeatTimer.reset();
|
_pBeatTimer.reset();
|
||||||
_pPlayTimer.reset();
|
_pPlayTimer.reset();
|
||||||
_pMediaTimer.reset();
|
_pMediaTimer.reset();
|
||||||
_iSeekTo = 0;
|
_iSeekTo = 0;
|
||||||
RtmpProtocol::reset();
|
RtmpProtocol::reset();
|
||||||
|
|
||||||
CLEAR_ARR(_aiFistStamp);
|
CLEAR_ARR(_aiFistStamp);
|
||||||
CLEAR_ARR(_aiNowStamp);
|
CLEAR_ARR(_aiNowStamp);
|
||||||
|
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||||
_mapOnResultCB.clear();
|
_mapOnResultCB.clear();
|
||||||
lock_guard<recursive_mutex> lck2(_mtxOnStatusCB);
|
lock_guard<recursive_mutex> lck2(_mtxOnStatusCB);
|
||||||
_dqOnStatusCB.clear();
|
_dqOnStatusCB.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPlayer::play(const string &strUrl) {
|
void RtmpPlayer::play(const string &strUrl) {
|
||||||
teardown();
|
teardown();
|
||||||
string strHost = FindField(strUrl.data(), "://", "/");
|
string strHost = FindField(strUrl.data(), "://", "/");
|
||||||
_strApp = FindField(strUrl.data(), (strHost + "/").data(), "/");
|
_strApp = FindField(strUrl.data(), (strHost + "/").data(), "/");
|
||||||
_strStream = FindField(strUrl.data(), (strHost + "/" + _strApp + "/").data(), NULL);
|
_strStream = FindField(strUrl.data(), (strHost + "/" + _strApp + "/").data(), NULL);
|
||||||
_strTcUrl = string("rtmp://") + strHost + "/" + _strApp;
|
_strTcUrl = string("rtmp://") + strHost + "/" + _strApp;
|
||||||
|
|
||||||
@ -74,48 +74,48 @@ void RtmpPlayer::play(const string &strUrl) {
|
|||||||
onPlayResult_l(SockException(Err_other,"rtmp url非法"),false);
|
onPlayResult_l(SockException(Err_other,"rtmp url非法"),false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DebugL << strHost << " " << _strApp << " " << _strStream;
|
DebugL << strHost << " " << _strApp << " " << _strStream;
|
||||||
|
|
||||||
auto iPort = atoi(FindField(strHost.data(), ":", NULL).data());
|
auto iPort = atoi(FindField(strHost.data(), ":", NULL).data());
|
||||||
if (iPort <= 0) {
|
if (iPort <= 0) {
|
||||||
//rtmp 默认端口1935
|
//rtmp 默认端口1935
|
||||||
iPort = 1935;
|
iPort = 1935;
|
||||||
} else {
|
} else {
|
||||||
//服务器域名
|
//服务器域名
|
||||||
strHost = FindField(strHost.data(), NULL, ":");
|
strHost = FindField(strHost.data(), NULL, ":");
|
||||||
}
|
}
|
||||||
if(!(*this)[kNetAdapter].empty()){
|
if(!(*this)[kNetAdapter].empty()){
|
||||||
setNetAdapter((*this)[kNetAdapter]);
|
setNetAdapter((*this)[kNetAdapter]);
|
||||||
}
|
}
|
||||||
|
|
||||||
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||||
float playTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
float playTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
||||||
_pPlayTimer.reset( new Timer(playTimeOutSec, [weakSelf]() {
|
_pPlayTimer.reset( new Timer(playTimeOutSec, [weakSelf]() {
|
||||||
auto strongSelf=weakSelf.lock();
|
auto strongSelf=weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtmp timeout"),false);
|
strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtmp timeout"),false);
|
||||||
return false;
|
return false;
|
||||||
},getPoller()));
|
},getPoller()));
|
||||||
|
|
||||||
_metadata_got = false;
|
_metadata_got = false;
|
||||||
startConnect(strHost, iPort , playTimeOutSec);
|
startConnect(strHost, iPort , playTimeOutSec);
|
||||||
}
|
}
|
||||||
void RtmpPlayer::onErr(const SockException &ex){
|
void RtmpPlayer::onErr(const SockException &ex){
|
||||||
//定时器_pPlayTimer为空后表明握手结束了
|
//定时器_pPlayTimer为空后表明握手结束了
|
||||||
onPlayResult_l(ex, !_pPlayTimer);
|
onPlayResult_l(ex, !_pPlayTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPlayer::onPlayResult_l(const SockException &ex , bool handshakeCompleted) {
|
void RtmpPlayer::onPlayResult_l(const SockException &ex , bool handshakeCompleted) {
|
||||||
WarnL << ex.getErrCode() << " " << ex.what();
|
WarnL << ex.getErrCode() << " " << ex.what();
|
||||||
|
|
||||||
if(!ex){
|
if(!ex){
|
||||||
//播放成功,恢复rtmp接收超时定时器
|
//播放成功,恢复rtmp接收超时定时器
|
||||||
_mediaTicker.resetTime();
|
_mediaTicker.resetTime();
|
||||||
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||||
int timeoutMS = (*this)[kMediaTimeoutMS].as<int>();
|
int timeoutMS = (*this)[kMediaTimeoutMS].as<int>();
|
||||||
//创建rtmp数据接收超时检测定时器
|
//创建rtmp数据接收超时检测定时器
|
||||||
_pMediaTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() {
|
_pMediaTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() {
|
||||||
auto strongSelf=weakSelf.lock();
|
auto strongSelf=weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
@ -130,110 +130,110 @@ void RtmpPlayer::onPlayResult_l(const SockException &ex , bool handshakeComplete
|
|||||||
},getPoller()));
|
},getPoller()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!handshakeCompleted) {
|
if (!handshakeCompleted) {
|
||||||
//开始播放阶段
|
//开始播放阶段
|
||||||
_pPlayTimer.reset();
|
_pPlayTimer.reset();
|
||||||
onPlayResult(ex);
|
onPlayResult(ex);
|
||||||
} else if (ex) {
|
} else if (ex) {
|
||||||
//播放成功后异常断开回调
|
//播放成功后异常断开回调
|
||||||
onShutdown(ex);
|
onShutdown(ex);
|
||||||
} else {
|
} else {
|
||||||
//恢复播放
|
//恢复播放
|
||||||
onResume();
|
onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ex){
|
if(ex){
|
||||||
teardown();
|
teardown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void RtmpPlayer::onConnect(const SockException &err){
|
void RtmpPlayer::onConnect(const SockException &err){
|
||||||
if(err.getErrCode() != Err_success) {
|
if(err.getErrCode() != Err_success) {
|
||||||
onPlayResult_l(err, false);
|
onPlayResult_l(err, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||||
startClientSession([weakSelf](){
|
startClientSession([weakSelf](){
|
||||||
auto strongSelf=weakSelf.lock();
|
auto strongSelf=weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strongSelf->send_connect();
|
strongSelf->send_connect();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void RtmpPlayer::onRecv(const Buffer::Ptr &pBuf){
|
void RtmpPlayer::onRecv(const Buffer::Ptr &pBuf){
|
||||||
try {
|
try {
|
||||||
onParseRtmp(pBuf->data(), pBuf->size());
|
onParseRtmp(pBuf->data(), pBuf->size());
|
||||||
} catch (exception &e) {
|
} catch (exception &e) {
|
||||||
SockException ex(Err_other, e.what());
|
SockException ex(Err_other, e.what());
|
||||||
//定时器_pPlayTimer为空后表明握手结束了
|
//定时器_pPlayTimer为空后表明握手结束了
|
||||||
onPlayResult_l(ex, !_pPlayTimer);
|
onPlayResult_l(ex, !_pPlayTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPlayer::pause(bool bPause) {
|
void RtmpPlayer::pause(bool bPause) {
|
||||||
send_pause(bPause);
|
send_pause(bPause);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void RtmpPlayer::send_connect() {
|
inline void RtmpPlayer::send_connect() {
|
||||||
AMFValue obj(AMF_OBJECT);
|
AMFValue obj(AMF_OBJECT);
|
||||||
obj.set("app", _strApp);
|
obj.set("app", _strApp);
|
||||||
obj.set("tcUrl", _strTcUrl);
|
obj.set("tcUrl", _strTcUrl);
|
||||||
//未使用代理
|
//未使用代理
|
||||||
obj.set("fpad", false);
|
obj.set("fpad", false);
|
||||||
//参考librtmp,什么作用?
|
//参考librtmp,什么作用?
|
||||||
obj.set("capabilities", 15);
|
obj.set("capabilities", 15);
|
||||||
//SUPPORT_VID_CLIENT_SEEK 支持seek
|
//SUPPORT_VID_CLIENT_SEEK 支持seek
|
||||||
obj.set("videoFunction", 1);
|
obj.set("videoFunction", 1);
|
||||||
//只支持aac
|
//只支持aac
|
||||||
obj.set("audioCodecs", (double)(0x0400));
|
obj.set("audioCodecs", (double)(0x0400));
|
||||||
//只支持H264
|
//只支持H264
|
||||||
obj.set("videoCodecs", (double)(0x0080));
|
obj.set("videoCodecs", (double)(0x0080));
|
||||||
sendInvoke("connect", obj);
|
sendInvoke("connect", obj);
|
||||||
addOnResultCB([this](AMFDecoder &dec){
|
addOnResultCB([this](AMFDecoder &dec){
|
||||||
//TraceL << "connect result";
|
//TraceL << "connect result";
|
||||||
dec.load<AMFValue>();
|
dec.load<AMFValue>();
|
||||||
auto val = dec.load<AMFValue>();
|
auto val = dec.load<AMFValue>();
|
||||||
auto level = val["level"].as_string();
|
auto level = val["level"].as_string();
|
||||||
auto code = val["code"].as_string();
|
auto code = val["code"].as_string();
|
||||||
if(level != "status"){
|
if(level != "status"){
|
||||||
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
||||||
}
|
}
|
||||||
send_createStream();
|
send_createStream();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void RtmpPlayer::send_createStream() {
|
inline void RtmpPlayer::send_createStream() {
|
||||||
AMFValue obj(AMF_NULL);
|
AMFValue obj(AMF_NULL);
|
||||||
sendInvoke("createStream", obj);
|
sendInvoke("createStream", obj);
|
||||||
addOnResultCB([this](AMFDecoder &dec){
|
addOnResultCB([this](AMFDecoder &dec){
|
||||||
//TraceL << "createStream result";
|
//TraceL << "createStream result";
|
||||||
dec.load<AMFValue>();
|
dec.load<AMFValue>();
|
||||||
_ui32StreamId = dec.load<int>();
|
_ui32StreamId = dec.load<int>();
|
||||||
send_play();
|
send_play();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void RtmpPlayer::send_play() {
|
inline void RtmpPlayer::send_play() {
|
||||||
AMFEncoder enc;
|
AMFEncoder enc;
|
||||||
enc << "play" << ++_iReqID << nullptr << _strStream << (double)_ui32StreamId;
|
enc << "play" << ++_iReqID << nullptr << _strStream << (double)_ui32StreamId;
|
||||||
sendRequest(MSG_CMD, enc.data());
|
sendRequest(MSG_CMD, enc.data());
|
||||||
auto fun = [this](AMFValue &val){
|
auto fun = [this](AMFValue &val){
|
||||||
//TraceL << "play onStatus";
|
//TraceL << "play onStatus";
|
||||||
auto level = val["level"].as_string();
|
auto level = val["level"].as_string();
|
||||||
auto code = val["code"].as_string();
|
auto code = val["code"].as_string();
|
||||||
if(level != "status"){
|
if(level != "status"){
|
||||||
throw std::runtime_error(StrPrinter <<"play 失败:" << level << " " << code << endl);
|
throw std::runtime_error(StrPrinter <<"play 失败:" << level << " " << code << endl);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
addOnStatusCB(fun);
|
addOnStatusCB(fun);
|
||||||
addOnStatusCB(fun);
|
addOnStatusCB(fun);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void RtmpPlayer::send_pause(bool bPause) {
|
inline void RtmpPlayer::send_pause(bool bPause) {
|
||||||
AMFEncoder enc;
|
AMFEncoder enc;
|
||||||
enc << "pause" << ++_iReqID << nullptr << bPause;
|
enc << "pause" << ++_iReqID << nullptr << bPause;
|
||||||
sendRequest(MSG_CMD, enc.data());
|
sendRequest(MSG_CMD, enc.data());
|
||||||
auto fun = [this,bPause](AMFValue &val){
|
auto fun = [this,bPause](AMFValue &val){
|
||||||
//TraceL << "pause onStatus";
|
//TraceL << "pause onStatus";
|
||||||
auto level = val["level"].as_string();
|
auto level = val["level"].as_string();
|
||||||
auto code = val["code"].as_string();
|
auto code = val["code"].as_string();
|
||||||
@ -250,147 +250,147 @@ inline void RtmpPlayer::send_pause(bool bPause) {
|
|||||||
_pMediaTimer.reset();
|
_pMediaTimer.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
addOnStatusCB(fun);
|
addOnStatusCB(fun);
|
||||||
|
|
||||||
_pBeatTimer.reset();
|
_pBeatTimer.reset();
|
||||||
if(bPause){
|
if(bPause){
|
||||||
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||||
_pBeatTimer.reset(new Timer((*this)[kBeatIntervalMS].as<int>() / 1000.0,[weakSelf](){
|
_pBeatTimer.reset(new Timer((*this)[kBeatIntervalMS].as<int>() / 1000.0,[weakSelf](){
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if (!strongSelf){
|
if (!strongSelf){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint32_t timeStamp = ::time(NULL);
|
uint32_t timeStamp = ::time(NULL);
|
||||||
strongSelf->sendUserControl(CONTROL_PING_REQUEST, timeStamp);
|
strongSelf->sendUserControl(CONTROL_PING_REQUEST, timeStamp);
|
||||||
return true;
|
return true;
|
||||||
},getPoller()));
|
},getPoller()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPlayer::onCmd_result(AMFDecoder &dec){
|
void RtmpPlayer::onCmd_result(AMFDecoder &dec){
|
||||||
auto iReqId = dec.load<int>();
|
auto iReqId = dec.load<int>();
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||||
auto it = _mapOnResultCB.find(iReqId);
|
auto it = _mapOnResultCB.find(iReqId);
|
||||||
if(it != _mapOnResultCB.end()){
|
if(it != _mapOnResultCB.end()){
|
||||||
it->second(dec);
|
it->second(dec);
|
||||||
_mapOnResultCB.erase(it);
|
_mapOnResultCB.erase(it);
|
||||||
}else{
|
}else{
|
||||||
WarnL << "unhandled _result";
|
WarnL << "unhandled _result";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) {
|
void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) {
|
||||||
AMFValue val;
|
AMFValue val;
|
||||||
while(true){
|
while(true){
|
||||||
val = dec.load<AMFValue>();
|
val = dec.load<AMFValue>();
|
||||||
if(val.type() == AMF_OBJECT){
|
if(val.type() == AMF_OBJECT){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(val.type() != AMF_OBJECT){
|
if(val.type() != AMF_OBJECT){
|
||||||
throw std::runtime_error("onStatus:the result object was not found");
|
throw std::runtime_error("onStatus:the result object was not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||||
if(_dqOnStatusCB.size()){
|
if(_dqOnStatusCB.size()){
|
||||||
_dqOnStatusCB.front()(val);
|
_dqOnStatusCB.front()(val);
|
||||||
_dqOnStatusCB.pop_front();
|
_dqOnStatusCB.pop_front();
|
||||||
}else{
|
}else{
|
||||||
auto level = val["level"];
|
auto level = val["level"];
|
||||||
auto code = val["code"].as_string();
|
auto code = val["code"].as_string();
|
||||||
if(level.type() == AMF_STRING){
|
if(level.type() == AMF_STRING){
|
||||||
if(level.as_string() != "status"){
|
if(level.as_string() != "status"){
|
||||||
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//WarnL << "unhandled onStatus:" << code;
|
//WarnL << "unhandled onStatus:" << code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) {
|
void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) {
|
||||||
//TraceL;
|
//TraceL;
|
||||||
auto val = dec.load<AMFValue>();
|
auto val = dec.load<AMFValue>();
|
||||||
if(!onCheckMeta(val)){
|
if(!onCheckMeta(val)){
|
||||||
throw std::runtime_error("onCheckMeta failed");
|
throw std::runtime_error("onCheckMeta failed");
|
||||||
}
|
}
|
||||||
_metadata_got = true;
|
_metadata_got = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPlayer::onStreamDry(uint32_t ui32StreamId) {
|
void RtmpPlayer::onStreamDry(uint32_t ui32StreamId) {
|
||||||
//TraceL << ui32StreamId;
|
//TraceL << ui32StreamId;
|
||||||
onPlayResult_l(SockException(Err_other,"rtmp stream dry"), true);
|
onPlayResult_l(SockException(Err_other,"rtmp stream dry"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPlayer::onMediaData_l(const RtmpPacket::Ptr &packet) {
|
void RtmpPlayer::onMediaData_l(const RtmpPacket::Ptr &packet) {
|
||||||
_mediaTicker.resetTime();
|
_mediaTicker.resetTime();
|
||||||
if(!_pPlayTimer){
|
if(!_pPlayTimer){
|
||||||
//已经触发了onPlayResult事件,直接触发onMediaData事件
|
//已经触发了onPlayResult事件,直接触发onMediaData事件
|
||||||
onMediaData(packet);
|
onMediaData(packet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(packet->isCfgFrame()){
|
if(packet->isCfgFrame()){
|
||||||
//输入配置帧以便初始化完成各个track
|
//输入配置帧以便初始化完成各个track
|
||||||
onMediaData(packet);
|
onMediaData(packet);
|
||||||
}else{
|
}else{
|
||||||
//先触发onPlayResult事件,这个时候解码器才能初始化完毕
|
//先触发onPlayResult事件,这个时候解码器才能初始化完毕
|
||||||
onPlayResult_l(SockException(Err_success,"play rtmp success"), false);
|
onPlayResult_l(SockException(Err_success,"play rtmp success"), false);
|
||||||
//触发onPlayResult事件后,再把帧数据输入到解码器
|
//触发onPlayResult事件后,再把帧数据输入到解码器
|
||||||
onMediaData(packet);
|
onMediaData(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void RtmpPlayer::onRtmpChunk(RtmpPacket &chunkData) {
|
void RtmpPlayer::onRtmpChunk(RtmpPacket &chunkData) {
|
||||||
typedef void (RtmpPlayer::*rtmp_func_ptr)(AMFDecoder &dec);
|
typedef void (RtmpPlayer::*rtmp_func_ptr)(AMFDecoder &dec);
|
||||||
static unordered_map<string, rtmp_func_ptr> s_func_map;
|
static unordered_map<string, rtmp_func_ptr> s_func_map;
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
s_func_map.emplace("_error",&RtmpPlayer::onCmd_result);
|
s_func_map.emplace("_error",&RtmpPlayer::onCmd_result);
|
||||||
s_func_map.emplace("_result",&RtmpPlayer::onCmd_result);
|
s_func_map.emplace("_result",&RtmpPlayer::onCmd_result);
|
||||||
s_func_map.emplace("onStatus",&RtmpPlayer::onCmd_onStatus);
|
s_func_map.emplace("onStatus",&RtmpPlayer::onCmd_onStatus);
|
||||||
s_func_map.emplace("onMetaData",&RtmpPlayer::onCmd_onMetaData);
|
s_func_map.emplace("onMetaData",&RtmpPlayer::onCmd_onMetaData);
|
||||||
}, []() {});
|
}, []() {});
|
||||||
|
|
||||||
switch (chunkData.typeId) {
|
switch (chunkData.typeId) {
|
||||||
case MSG_CMD:
|
case MSG_CMD:
|
||||||
case MSG_CMD3:
|
case MSG_CMD3:
|
||||||
case MSG_DATA:
|
case MSG_DATA:
|
||||||
case MSG_DATA3: {
|
case MSG_DATA3: {
|
||||||
AMFDecoder dec(chunkData.strBuf, 0);
|
AMFDecoder dec(chunkData.strBuf, 0);
|
||||||
std::string type = dec.load<std::string>();
|
std::string type = dec.load<std::string>();
|
||||||
auto it = s_func_map.find(type);
|
auto it = s_func_map.find(type);
|
||||||
if(it != s_func_map.end()){
|
if(it != s_func_map.end()){
|
||||||
auto fun = it->second;
|
auto fun = it->second;
|
||||||
(this->*fun)(dec);
|
(this->*fun)(dec);
|
||||||
}else{
|
}else{
|
||||||
WarnL << "can not support cmd:" << type;
|
WarnL << "can not support cmd:" << type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MSG_AUDIO:
|
case MSG_AUDIO:
|
||||||
case MSG_VIDEO: {
|
case MSG_VIDEO: {
|
||||||
auto idx = chunkData.typeId%2;
|
auto idx = chunkData.typeId%2;
|
||||||
if (_aNowStampTicker[idx].elapsedTime() > 500) {
|
if (_aNowStampTicker[idx].elapsedTime() > 500) {
|
||||||
//计算播放进度时间轴用
|
//计算播放进度时间轴用
|
||||||
_aiNowStamp[idx] = chunkData.timeStamp;
|
_aiNowStamp[idx] = chunkData.timeStamp;
|
||||||
}
|
}
|
||||||
if(!_metadata_got){
|
if(!_metadata_got){
|
||||||
if(!onCheckMeta(TitleMeta().getMetadata())){
|
if(!onCheckMeta(TitleMeta().getMetadata())){
|
||||||
throw std::runtime_error("onCheckMeta failed");
|
throw std::runtime_error("onCheckMeta failed");
|
||||||
}
|
}
|
||||||
_metadata_got = true;
|
_metadata_got = true;
|
||||||
}
|
}
|
||||||
onMediaData_l(std::make_shared<RtmpPacket>(std::move(chunkData)));
|
onMediaData_l(std::make_shared<RtmpPacket>(std::move(chunkData)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t RtmpPlayer::getProgressMilliSecond() const{
|
uint32_t RtmpPlayer::getProgressMilliSecond() const{
|
||||||
uint32_t iTime[2] = {0,0};
|
uint32_t iTime[2] = {0,0};
|
||||||
for(auto i = 0 ;i < 2 ;i++){
|
for(auto i = 0 ;i < 2 ;i++){
|
||||||
iTime[i] = _aiNowStamp[i] - _aiFistStamp[i];
|
iTime[i] = _aiNowStamp[i] - _aiFistStamp[i];
|
||||||
}
|
}
|
||||||
@ -407,7 +407,7 @@ void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){
|
|||||||
//TraceL << "seek result";
|
//TraceL << "seek result";
|
||||||
_aNowStampTicker[0].resetTime();
|
_aNowStampTicker[0].resetTime();
|
||||||
_aNowStampTicker[1].resetTime();
|
_aNowStampTicker[1].resetTime();
|
||||||
int iTimeInc = seekMS - getProgressMilliSecond();
|
int iTimeInc = seekMS - getProgressMilliSecond();
|
||||||
for(auto i = 0 ;i < 2 ;i++){
|
for(auto i = 0 ;i < 2 ;i++){
|
||||||
_aiFistStamp[i] = _aiNowStamp[i] + iTimeInc;
|
_aiFistStamp[i] = _aiNowStamp[i] + iTimeInc;
|
||||||
_aiNowStamp[i] = _aiFistStamp[i];
|
_aiNowStamp[i] = _aiFistStamp[i];
|
||||||
|
@ -47,77 +47,77 @@ namespace mediakit {
|
|||||||
//实现了rtmp播放器协议部分的功能,及数据接收功能
|
//实现了rtmp播放器协议部分的功能,及数据接收功能
|
||||||
class RtmpPlayer:public PlayerBase, public TcpClient, public RtmpProtocol{
|
class RtmpPlayer:public PlayerBase, public TcpClient, public RtmpProtocol{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtmpPlayer> Ptr;
|
typedef std::shared_ptr<RtmpPlayer> Ptr;
|
||||||
RtmpPlayer(const EventPoller::Ptr &poller);
|
RtmpPlayer(const EventPoller::Ptr &poller);
|
||||||
virtual ~RtmpPlayer();
|
virtual ~RtmpPlayer();
|
||||||
|
|
||||||
void play(const string &strUrl) override;
|
void play(const string &strUrl) override;
|
||||||
void pause(bool bPause) override;
|
void pause(bool bPause) override;
|
||||||
void teardown() override;
|
void teardown() override;
|
||||||
protected:
|
protected:
|
||||||
virtual bool onCheckMeta(const AMFValue &val) =0;
|
virtual bool onCheckMeta(const AMFValue &val) =0;
|
||||||
virtual void onMediaData(const RtmpPacket::Ptr &chunkData) =0;
|
virtual void onMediaData(const RtmpPacket::Ptr &chunkData) =0;
|
||||||
uint32_t getProgressMilliSecond() const;
|
uint32_t getProgressMilliSecond() const;
|
||||||
void seekToMilliSecond(uint32_t ms);
|
void seekToMilliSecond(uint32_t ms);
|
||||||
protected:
|
protected:
|
||||||
void onMediaData_l(const RtmpPacket::Ptr &chunkData);
|
void onMediaData_l(const RtmpPacket::Ptr &chunkData);
|
||||||
//在获取config帧后才触发onPlayResult_l(而不是收到play命令回复),所以此时所有track都初始化完毕了
|
//在获取config帧后才触发onPlayResult_l(而不是收到play命令回复),所以此时所有track都初始化完毕了
|
||||||
void onPlayResult_l(const SockException &ex, bool handshakeCompleted);
|
void onPlayResult_l(const SockException &ex, bool handshakeCompleted);
|
||||||
|
|
||||||
//form Tcpclient
|
//form Tcpclient
|
||||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||||
void onConnect(const SockException &err) override;
|
void onConnect(const SockException &err) override;
|
||||||
void onErr(const SockException &ex) override;
|
void onErr(const SockException &ex) override;
|
||||||
//from RtmpProtocol
|
//from RtmpProtocol
|
||||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||||
void onStreamDry(uint32_t ui32StreamId) override;
|
void onStreamDry(uint32_t ui32StreamId) override;
|
||||||
void onSendRawData(const Buffer::Ptr &buffer) override{
|
void onSendRawData(const Buffer::Ptr &buffer) override{
|
||||||
send(buffer);
|
send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FUN>
|
template<typename FUN>
|
||||||
inline void addOnResultCB(const FUN &fun) {
|
inline void addOnResultCB(const FUN &fun) {
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||||
_mapOnResultCB.emplace(_iReqID, fun);
|
_mapOnResultCB.emplace(_iReqID, fun);
|
||||||
}
|
}
|
||||||
template<typename FUN>
|
template<typename FUN>
|
||||||
inline void addOnStatusCB(const FUN &fun) {
|
inline void addOnStatusCB(const FUN &fun) {
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||||
_dqOnStatusCB.emplace_back(fun);
|
_dqOnStatusCB.emplace_back(fun);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onCmd_result(AMFDecoder &dec);
|
void onCmd_result(AMFDecoder &dec);
|
||||||
void onCmd_onStatus(AMFDecoder &dec);
|
void onCmd_onStatus(AMFDecoder &dec);
|
||||||
void onCmd_onMetaData(AMFDecoder &dec);
|
void onCmd_onMetaData(AMFDecoder &dec);
|
||||||
|
|
||||||
inline void send_connect();
|
inline void send_connect();
|
||||||
inline void send_createStream();
|
inline void send_createStream();
|
||||||
inline void send_play();
|
inline void send_play();
|
||||||
inline void send_pause(bool bPause);
|
inline void send_pause(bool bPause);
|
||||||
private:
|
private:
|
||||||
string _strApp;
|
string _strApp;
|
||||||
string _strStream;
|
string _strStream;
|
||||||
string _strTcUrl;
|
string _strTcUrl;
|
||||||
bool _bPaused = false;
|
bool _bPaused = false;
|
||||||
|
|
||||||
unordered_map<int, function<void(AMFDecoder &dec)> > _mapOnResultCB;
|
unordered_map<int, function<void(AMFDecoder &dec)> > _mapOnResultCB;
|
||||||
recursive_mutex _mtxOnResultCB;
|
recursive_mutex _mtxOnResultCB;
|
||||||
deque<function<void(AMFValue &dec)> > _dqOnStatusCB;
|
deque<function<void(AMFValue &dec)> > _dqOnStatusCB;
|
||||||
recursive_mutex _mtxOnStatusCB;
|
recursive_mutex _mtxOnStatusCB;
|
||||||
|
|
||||||
//超时功能实现
|
//超时功能实现
|
||||||
Ticker _mediaTicker;
|
Ticker _mediaTicker;
|
||||||
std::shared_ptr<Timer> _pMediaTimer;
|
std::shared_ptr<Timer> _pMediaTimer;
|
||||||
std::shared_ptr<Timer> _pPlayTimer;
|
std::shared_ptr<Timer> _pPlayTimer;
|
||||||
//心跳定时器
|
//心跳定时器
|
||||||
std::shared_ptr<Timer> _pBeatTimer;
|
std::shared_ptr<Timer> _pBeatTimer;
|
||||||
|
|
||||||
//播放进度控制
|
//播放进度控制
|
||||||
uint32_t _iSeekTo = 0;
|
uint32_t _iSeekTo = 0;
|
||||||
uint32_t _aiFistStamp[2] = { 0, 0 };
|
uint32_t _aiFistStamp[2] = { 0, 0 };
|
||||||
uint32_t _aiNowStamp[2] = { 0, 0 };
|
uint32_t _aiNowStamp[2] = { 0, 0 };
|
||||||
Ticker _aNowStampTicker[2];
|
Ticker _aNowStampTicker[2];
|
||||||
bool _metadata_got = false;
|
bool _metadata_got = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -73,7 +73,7 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void onMediaData(const RtmpPacket::Ptr &chunkData) override {
|
void onMediaData(const RtmpPacket::Ptr &chunkData) override {
|
||||||
if(_pRtmpMediaSrc){
|
if(_pRtmpMediaSrc){
|
||||||
if(!_set_meta_data && !chunkData->isCfgFrame()){
|
if(!_set_meta_data && !chunkData->isCfgFrame()){
|
||||||
_set_meta_data = true;
|
_set_meta_data = true;
|
||||||
_pRtmpMediaSrc->setMetaData(TitleMeta().getMetadata());
|
_pRtmpMediaSrc->setMetaData(TitleMeta().getMetadata());
|
||||||
@ -81,7 +81,7 @@ private:
|
|||||||
_pRtmpMediaSrc->onWrite(chunkData);
|
_pRtmpMediaSrc->onWrite(chunkData);
|
||||||
}
|
}
|
||||||
if(!_delegate){
|
if(!_delegate){
|
||||||
//这个流没有metadata
|
//这个流没有metadata
|
||||||
_delegate.reset(new RtmpDemuxer());
|
_delegate.reset(new RtmpDemuxer());
|
||||||
}
|
}
|
||||||
_delegate->inputRtmp(chunkData);
|
_delegate->inputRtmp(chunkData);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -45,77 +45,77 @@ namespace mediakit {
|
|||||||
|
|
||||||
class RtmpProtocol {
|
class RtmpProtocol {
|
||||||
public:
|
public:
|
||||||
RtmpProtocol();
|
RtmpProtocol();
|
||||||
virtual ~RtmpProtocol();
|
virtual ~RtmpProtocol();
|
||||||
//作为客户端发送c0c1,等待s0s1s2并且回调
|
//作为客户端发送c0c1,等待s0s1s2并且回调
|
||||||
void startClientSession(const function<void()> &cb);
|
void startClientSession(const function<void()> &cb);
|
||||||
void onParseRtmp(const char *pcRawData,int iSize);
|
void onParseRtmp(const char *pcRawData,int iSize);
|
||||||
void reset();
|
void reset();
|
||||||
protected:
|
protected:
|
||||||
virtual void onSendRawData(const Buffer::Ptr &buffer) = 0;
|
virtual void onSendRawData(const Buffer::Ptr &buffer) = 0;
|
||||||
virtual void onRtmpChunk(RtmpPacket &chunkData) = 0;
|
virtual void onRtmpChunk(RtmpPacket &chunkData) = 0;
|
||||||
virtual void onStreamBegin(uint32_t ui32StreamId){
|
virtual void onStreamBegin(uint32_t ui32StreamId){
|
||||||
_ui32StreamId = ui32StreamId;
|
_ui32StreamId = ui32StreamId;
|
||||||
}
|
}
|
||||||
virtual void onStreamEof(uint32_t ui32StreamId){};
|
virtual void onStreamEof(uint32_t ui32StreamId){};
|
||||||
virtual void onStreamDry(uint32_t ui32StreamId){};
|
virtual void onStreamDry(uint32_t ui32StreamId){};
|
||||||
protected:
|
protected:
|
||||||
void sendAcknowledgement(uint32_t ui32Size);
|
void sendAcknowledgement(uint32_t ui32Size);
|
||||||
void sendAcknowledgementSize(uint32_t ui32Size);
|
void sendAcknowledgementSize(uint32_t ui32Size);
|
||||||
void sendPeerBandwidth(uint32_t ui32Size);
|
void sendPeerBandwidth(uint32_t ui32Size);
|
||||||
void sendChunkSize(uint32_t ui32Size);
|
void sendChunkSize(uint32_t ui32Size);
|
||||||
void sendPingRequest(uint32_t ui32TimeStamp = ::time(NULL));
|
void sendPingRequest(uint32_t ui32TimeStamp = ::time(NULL));
|
||||||
void sendPingResponse(uint32_t ui32TimeStamp = ::time(NULL));
|
void sendPingResponse(uint32_t ui32TimeStamp = ::time(NULL));
|
||||||
void sendSetBufferLength(uint32_t ui32StreamId, uint32_t ui32Length);
|
void sendSetBufferLength(uint32_t ui32StreamId, uint32_t ui32Length);
|
||||||
void sendUserControl(uint16_t ui16EventType, uint32_t ui32EventData);
|
void sendUserControl(uint16_t ui16EventType, uint32_t ui32EventData);
|
||||||
void sendUserControl(uint16_t ui16EventType, const string &strEventData);
|
void sendUserControl(uint16_t ui16EventType, const string &strEventData);
|
||||||
|
|
||||||
void sendInvoke(const string &strCmd, const AMFValue &val);
|
void sendInvoke(const string &strCmd, const AMFValue &val);
|
||||||
void sendRequest(int iCmd, const string &str);
|
void sendRequest(int iCmd, const string &str);
|
||||||
void sendResponse(int iType, const string &str);
|
void sendResponse(int iType, const string &str);
|
||||||
void sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId, const std::string &strBuf, uint32_t ui32TimeStamp, int iChunkID);
|
void sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId, const std::string &strBuf, uint32_t ui32TimeStamp, int iChunkID);
|
||||||
void sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId, const Buffer::Ptr &buffer, uint32_t ui32TimeStamp, int iChunkID);
|
void sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId, const Buffer::Ptr &buffer, uint32_t ui32TimeStamp, int iChunkID);
|
||||||
protected:
|
protected:
|
||||||
int _iReqID = 0;
|
int _iReqID = 0;
|
||||||
uint32_t _ui32StreamId = STREAM_CONTROL;
|
uint32_t _ui32StreamId = STREAM_CONTROL;
|
||||||
int _iNowStreamID = 0;
|
int _iNowStreamID = 0;
|
||||||
int _iNowChunkID = 0;
|
int _iNowChunkID = 0;
|
||||||
bool _bDataStarted = false;
|
bool _bDataStarted = false;
|
||||||
inline BufferRaw::Ptr obtainBuffer();
|
inline BufferRaw::Ptr obtainBuffer();
|
||||||
inline BufferRaw::Ptr obtainBuffer(const void *data, int len);
|
inline BufferRaw::Ptr obtainBuffer(const void *data, int len);
|
||||||
//ResourcePool<BufferRaw,MAX_SEND_PKT> _bufferPool;
|
//ResourcePool<BufferRaw,MAX_SEND_PKT> _bufferPool;
|
||||||
private:
|
private:
|
||||||
void handle_S0S1S2(const function<void()> &cb);
|
void handle_S0S1S2(const function<void()> &cb);
|
||||||
void handle_C0C1();
|
void handle_C0C1();
|
||||||
void handle_C1_simple();
|
void handle_C1_simple();
|
||||||
#ifdef ENABLE_OPENSSL
|
#ifdef ENABLE_OPENSSL
|
||||||
void handle_C1_complex();
|
void handle_C1_complex();
|
||||||
string get_C1_digest(const uint8_t *ptr,char **digestPos);
|
string get_C1_digest(const uint8_t *ptr,char **digestPos);
|
||||||
string get_C1_key(const uint8_t *ptr);
|
string get_C1_key(const uint8_t *ptr);
|
||||||
void check_C1_Digest(const string &digest,const string &data);
|
void check_C1_Digest(const string &digest,const string &data);
|
||||||
void send_complex_S0S1S2(int schemeType,const string &digest);
|
void send_complex_S0S1S2(int schemeType,const string &digest);
|
||||||
#endif //ENABLE_OPENSSL
|
#endif //ENABLE_OPENSSL
|
||||||
|
|
||||||
void handle_C2();
|
void handle_C2();
|
||||||
void handle_rtmp();
|
void handle_rtmp();
|
||||||
void handle_rtmpChunk(RtmpPacket &chunkData);
|
void handle_rtmpChunk(RtmpPacket &chunkData);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
////////////ChunkSize////////////
|
////////////ChunkSize////////////
|
||||||
size_t _iChunkLenIn = DEFAULT_CHUNK_LEN;
|
size_t _iChunkLenIn = DEFAULT_CHUNK_LEN;
|
||||||
size_t _iChunkLenOut = DEFAULT_CHUNK_LEN;
|
size_t _iChunkLenOut = DEFAULT_CHUNK_LEN;
|
||||||
////////////Acknowledgement////////////
|
////////////Acknowledgement////////////
|
||||||
uint32_t _ui32ByteSent = 0;
|
uint32_t _ui32ByteSent = 0;
|
||||||
uint32_t _ui32LastSent = 0;
|
uint32_t _ui32LastSent = 0;
|
||||||
uint32_t _ui32WinSize = 0;
|
uint32_t _ui32WinSize = 0;
|
||||||
///////////PeerBandwidth///////////
|
///////////PeerBandwidth///////////
|
||||||
uint32_t _ui32Bandwidth = 2500000;
|
uint32_t _ui32Bandwidth = 2500000;
|
||||||
uint8_t _ui8LimitType = 2;
|
uint8_t _ui8LimitType = 2;
|
||||||
////////////Chunk////////////
|
////////////Chunk////////////
|
||||||
unordered_map<int, RtmpPacket> _mapChunkData;
|
unordered_map<int, RtmpPacket> _mapChunkData;
|
||||||
//////////Rtmp parser//////////
|
//////////Rtmp parser//////////
|
||||||
string _strRcvBuf;
|
string _strRcvBuf;
|
||||||
function<void()> _nextHandle;
|
function<void()> _nextHandle;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -34,55 +34,55 @@ using namespace mediakit::Client;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
RtmpPusher::RtmpPusher(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &src) : TcpClient(poller){
|
RtmpPusher::RtmpPusher(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &src) : TcpClient(poller){
|
||||||
_pMediaSrc=src;
|
_pMediaSrc=src;
|
||||||
}
|
}
|
||||||
|
|
||||||
RtmpPusher::~RtmpPusher() {
|
RtmpPusher::~RtmpPusher() {
|
||||||
teardown();
|
teardown();
|
||||||
DebugL << endl;
|
DebugL << endl;
|
||||||
}
|
}
|
||||||
void RtmpPusher::teardown() {
|
void RtmpPusher::teardown() {
|
||||||
if (alive()) {
|
if (alive()) {
|
||||||
_strApp.clear();
|
_strApp.clear();
|
||||||
_strStream.clear();
|
_strStream.clear();
|
||||||
_strTcUrl.clear();
|
_strTcUrl.clear();
|
||||||
{
|
{
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||||
_mapOnResultCB.clear();
|
_mapOnResultCB.clear();
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||||
_dqOnStatusCB.clear();
|
_dqOnStatusCB.clear();
|
||||||
}
|
}
|
||||||
_pPublishTimer.reset();
|
_pPublishTimer.reset();
|
||||||
reset();
|
reset();
|
||||||
shutdown(SockException(Err_shutdown,"teardown"));
|
shutdown(SockException(Err_shutdown,"teardown"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPusher::onPublishResult(const SockException &ex,bool handshakeCompleted) {
|
void RtmpPusher::onPublishResult(const SockException &ex,bool handshakeCompleted) {
|
||||||
if(!handshakeCompleted){
|
if(!handshakeCompleted){
|
||||||
//播放结果回调
|
//播放结果回调
|
||||||
_pPublishTimer.reset();
|
_pPublishTimer.reset();
|
||||||
if(_onPublished){
|
if(_onPublished){
|
||||||
_onPublished(ex);
|
_onPublished(ex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//播放成功后异常断开回调
|
//播放成功后异常断开回调
|
||||||
if(_onShutdown){
|
if(_onShutdown){
|
||||||
_onShutdown(ex);
|
_onShutdown(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ex){
|
if(ex){
|
||||||
teardown();
|
teardown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPusher::publish(const string &strUrl) {
|
void RtmpPusher::publish(const string &strUrl) {
|
||||||
teardown();
|
teardown();
|
||||||
string strHost = FindField(strUrl.data(), "://", "/");
|
string strHost = FindField(strUrl.data(), "://", "/");
|
||||||
_strApp = FindField(strUrl.data(), (strHost + "/").data(), "/");
|
_strApp = FindField(strUrl.data(), (strHost + "/").data(), "/");
|
||||||
_strStream = FindField(strUrl.data(), (strHost + "/" + _strApp + "/").data(), NULL);
|
_strStream = FindField(strUrl.data(), (strHost + "/" + _strApp + "/").data(), NULL);
|
||||||
_strTcUrl = string("rtmp://") + strHost + "/" + _strApp;
|
_strTcUrl = string("rtmp://") + strHost + "/" + _strApp;
|
||||||
|
|
||||||
@ -90,16 +90,16 @@ void RtmpPusher::publish(const string &strUrl) {
|
|||||||
onPublishResult(SockException(Err_other,"rtmp url非法"),false);
|
onPublishResult(SockException(Err_other,"rtmp url非法"),false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DebugL << strHost << " " << _strApp << " " << _strStream;
|
DebugL << strHost << " " << _strApp << " " << _strStream;
|
||||||
|
|
||||||
auto iPort = atoi(FindField(strHost.data(), ":", NULL).data());
|
auto iPort = atoi(FindField(strHost.data(), ":", NULL).data());
|
||||||
if (iPort <= 0) {
|
if (iPort <= 0) {
|
||||||
//rtmp 默认端口1935
|
//rtmp 默认端口1935
|
||||||
iPort = 1935;
|
iPort = 1935;
|
||||||
} else {
|
} else {
|
||||||
//服务器域名
|
//服务器域名
|
||||||
strHost = FindField(strHost.data(), NULL, ":");
|
strHost = FindField(strHost.data(), NULL, ":");
|
||||||
}
|
}
|
||||||
|
|
||||||
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||||
float publishTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
float publishTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
||||||
@ -116,23 +116,23 @@ void RtmpPusher::publish(const string &strUrl) {
|
|||||||
setNetAdapter((*this)[kNetAdapter]);
|
setNetAdapter((*this)[kNetAdapter]);
|
||||||
}
|
}
|
||||||
|
|
||||||
startConnect(strHost, iPort);
|
startConnect(strHost, iPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPusher::onErr(const SockException &ex){
|
void RtmpPusher::onErr(const SockException &ex){
|
||||||
//定时器_pPublishTimer为空后表明握手结束了
|
//定时器_pPublishTimer为空后表明握手结束了
|
||||||
onPublishResult(ex,!_pPublishTimer);
|
onPublishResult(ex,!_pPublishTimer);
|
||||||
}
|
}
|
||||||
void RtmpPusher::onConnect(const SockException &err){
|
void RtmpPusher::onConnect(const SockException &err){
|
||||||
if(err) {
|
if(err) {
|
||||||
onPublishResult(err,false);
|
onPublishResult(err,false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//推流器不需要多大的接收缓存,节省内存占用
|
//推流器不需要多大的接收缓存,节省内存占用
|
||||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
_sock->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
||||||
|
|
||||||
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||||
startClientSession([weakSelf](){
|
startClientSession([weakSelf](){
|
||||||
auto strongSelf=weakSelf.lock();
|
auto strongSelf=weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return;
|
return;
|
||||||
@ -140,63 +140,63 @@ void RtmpPusher::onConnect(const SockException &err){
|
|||||||
|
|
||||||
strongSelf->sendChunkSize(60000);
|
strongSelf->sendChunkSize(60000);
|
||||||
strongSelf->send_connect();
|
strongSelf->send_connect();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void RtmpPusher::onRecv(const Buffer::Ptr &pBuf){
|
void RtmpPusher::onRecv(const Buffer::Ptr &pBuf){
|
||||||
try {
|
try {
|
||||||
onParseRtmp(pBuf->data(), pBuf->size());
|
onParseRtmp(pBuf->data(), pBuf->size());
|
||||||
} catch (exception &e) {
|
} catch (exception &e) {
|
||||||
SockException ex(Err_other, e.what());
|
SockException ex(Err_other, e.what());
|
||||||
//定时器_pPublishTimer为空后表明握手结束了
|
//定时器_pPublishTimer为空后表明握手结束了
|
||||||
onPublishResult(ex,!_pPublishTimer);
|
onPublishResult(ex,!_pPublishTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline void RtmpPusher::send_connect() {
|
inline void RtmpPusher::send_connect() {
|
||||||
AMFValue obj(AMF_OBJECT);
|
AMFValue obj(AMF_OBJECT);
|
||||||
obj.set("app", _strApp);
|
obj.set("app", _strApp);
|
||||||
obj.set("type", "nonprivate");
|
obj.set("type", "nonprivate");
|
||||||
obj.set("tcUrl", _strTcUrl);
|
obj.set("tcUrl", _strTcUrl);
|
||||||
obj.set("swfUrl", _strTcUrl);
|
obj.set("swfUrl", _strTcUrl);
|
||||||
sendInvoke("connect", obj);
|
sendInvoke("connect", obj);
|
||||||
addOnResultCB([this](AMFDecoder &dec){
|
addOnResultCB([this](AMFDecoder &dec){
|
||||||
//TraceL << "connect result";
|
//TraceL << "connect result";
|
||||||
dec.load<AMFValue>();
|
dec.load<AMFValue>();
|
||||||
auto val = dec.load<AMFValue>();
|
auto val = dec.load<AMFValue>();
|
||||||
auto level = val["level"].as_string();
|
auto level = val["level"].as_string();
|
||||||
auto code = val["code"].as_string();
|
auto code = val["code"].as_string();
|
||||||
if(level != "status"){
|
if(level != "status"){
|
||||||
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
||||||
}
|
}
|
||||||
send_createStream();
|
send_createStream();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void RtmpPusher::send_createStream() {
|
inline void RtmpPusher::send_createStream() {
|
||||||
AMFValue obj(AMF_NULL);
|
AMFValue obj(AMF_NULL);
|
||||||
sendInvoke("createStream", obj);
|
sendInvoke("createStream", obj);
|
||||||
addOnResultCB([this](AMFDecoder &dec){
|
addOnResultCB([this](AMFDecoder &dec){
|
||||||
//TraceL << "createStream result";
|
//TraceL << "createStream result";
|
||||||
dec.load<AMFValue>();
|
dec.load<AMFValue>();
|
||||||
_ui32StreamId = dec.load<int>();
|
_ui32StreamId = dec.load<int>();
|
||||||
send_publish();
|
send_publish();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
inline void RtmpPusher::send_publish() {
|
inline void RtmpPusher::send_publish() {
|
||||||
AMFEncoder enc;
|
AMFEncoder enc;
|
||||||
enc << "publish" << ++_iReqID << nullptr << _strStream << _strApp ;
|
enc << "publish" << ++_iReqID << nullptr << _strStream << _strApp ;
|
||||||
sendRequest(MSG_CMD, enc.data());
|
sendRequest(MSG_CMD, enc.data());
|
||||||
|
|
||||||
addOnStatusCB([this](AMFValue &val) {
|
addOnStatusCB([this](AMFValue &val) {
|
||||||
auto level = val["level"].as_string();
|
auto level = val["level"].as_string();
|
||||||
auto code = val["code"].as_string();
|
auto code = val["code"].as_string();
|
||||||
if(level != "status") {
|
if(level != "status") {
|
||||||
throw std::runtime_error(StrPrinter <<"publish 失败:" << level << " " << code << endl);
|
throw std::runtime_error(StrPrinter <<"publish 失败:" << level << " " << code << endl);
|
||||||
}
|
}
|
||||||
//start send media
|
//start send media
|
||||||
send_metaData();
|
send_metaData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void RtmpPusher::send_metaData(){
|
inline void RtmpPusher::send_metaData(){
|
||||||
@ -216,11 +216,11 @@ inline void RtmpPusher::send_metaData(){
|
|||||||
_pRtmpReader = src->getRing()->attach(getPoller());
|
_pRtmpReader = src->getRing()->attach(getPoller());
|
||||||
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||||
_pRtmpReader->setReadCB([weakSelf](const RtmpPacket::Ptr &pkt){
|
_pRtmpReader->setReadCB([weakSelf](const RtmpPacket::Ptr &pkt){
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strongSelf->sendRtmp(pkt->typeId, strongSelf->_ui32StreamId, pkt, pkt->timeStamp, pkt->chunkId);
|
strongSelf->sendRtmp(pkt->typeId, strongSelf->_ui32StreamId, pkt, pkt->timeStamp, pkt->chunkId);
|
||||||
});
|
});
|
||||||
_pRtmpReader->setDetachCB([weakSelf](){
|
_pRtmpReader->setDetachCB([weakSelf](){
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
@ -229,84 +229,84 @@ inline void RtmpPusher::send_metaData(){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
onPublishResult(SockException(Err_success,"success"), false);
|
onPublishResult(SockException(Err_success,"success"), false);
|
||||||
//提升发送性能
|
//提升发送性能
|
||||||
setSocketFlags();
|
setSocketFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPusher::setSocketFlags(){
|
void RtmpPusher::setSocketFlags(){
|
||||||
GET_CONFIG(bool,ultraLowDelay,General::kUltraLowDelay);
|
GET_CONFIG(bool,ultraLowDelay,General::kUltraLowDelay);
|
||||||
if(!ultraLowDelay) {
|
if(!ultraLowDelay) {
|
||||||
//提高发送性能
|
//提高发送性能
|
||||||
(*this) << SocketFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
(*this) << SocketFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPusher::onCmd_result(AMFDecoder &dec){
|
void RtmpPusher::onCmd_result(AMFDecoder &dec){
|
||||||
auto iReqId = dec.load<int>();
|
auto iReqId = dec.load<int>();
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||||
auto it = _mapOnResultCB.find(iReqId);
|
auto it = _mapOnResultCB.find(iReqId);
|
||||||
if(it != _mapOnResultCB.end()){
|
if(it != _mapOnResultCB.end()){
|
||||||
it->second(dec);
|
it->second(dec);
|
||||||
_mapOnResultCB.erase(it);
|
_mapOnResultCB.erase(it);
|
||||||
}else{
|
}else{
|
||||||
WarnL << "unhandled _result";
|
WarnL << "unhandled _result";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void RtmpPusher::onCmd_onStatus(AMFDecoder &dec) {
|
void RtmpPusher::onCmd_onStatus(AMFDecoder &dec) {
|
||||||
AMFValue val;
|
AMFValue val;
|
||||||
while(true){
|
while(true){
|
||||||
val = dec.load<AMFValue>();
|
val = dec.load<AMFValue>();
|
||||||
if(val.type() == AMF_OBJECT){
|
if(val.type() == AMF_OBJECT){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(val.type() != AMF_OBJECT){
|
if(val.type() != AMF_OBJECT){
|
||||||
throw std::runtime_error("onStatus:the result object was not found");
|
throw std::runtime_error("onStatus:the result object was not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||||
if(_dqOnStatusCB.size()){
|
if(_dqOnStatusCB.size()){
|
||||||
_dqOnStatusCB.front()(val);
|
_dqOnStatusCB.front()(val);
|
||||||
_dqOnStatusCB.pop_front();
|
_dqOnStatusCB.pop_front();
|
||||||
}else{
|
}else{
|
||||||
auto level = val["level"];
|
auto level = val["level"];
|
||||||
auto code = val["code"].as_string();
|
auto code = val["code"].as_string();
|
||||||
if(level.type() == AMF_STRING){
|
if(level.type() == AMF_STRING){
|
||||||
if(level.as_string() != "status"){
|
if(level.as_string() != "status"){
|
||||||
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPusher::onRtmpChunk(RtmpPacket &chunkData) {
|
void RtmpPusher::onRtmpChunk(RtmpPacket &chunkData) {
|
||||||
switch (chunkData.typeId) {
|
switch (chunkData.typeId) {
|
||||||
case MSG_CMD:
|
case MSG_CMD:
|
||||||
case MSG_CMD3: {
|
case MSG_CMD3: {
|
||||||
typedef void (RtmpPusher::*rtmpCMDHandle)(AMFDecoder &dec);
|
typedef void (RtmpPusher::*rtmpCMDHandle)(AMFDecoder &dec);
|
||||||
static unordered_map<string, rtmpCMDHandle> g_mapCmd;
|
static unordered_map<string, rtmpCMDHandle> g_mapCmd;
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
g_mapCmd.emplace("_error",&RtmpPusher::onCmd_result);
|
g_mapCmd.emplace("_error",&RtmpPusher::onCmd_result);
|
||||||
g_mapCmd.emplace("_result",&RtmpPusher::onCmd_result);
|
g_mapCmd.emplace("_result",&RtmpPusher::onCmd_result);
|
||||||
g_mapCmd.emplace("onStatus",&RtmpPusher::onCmd_onStatus);
|
g_mapCmd.emplace("onStatus",&RtmpPusher::onCmd_onStatus);
|
||||||
}, []() {});
|
}, []() {});
|
||||||
|
|
||||||
AMFDecoder dec(chunkData.strBuf, 0);
|
AMFDecoder dec(chunkData.strBuf, 0);
|
||||||
std::string type = dec.load<std::string>();
|
std::string type = dec.load<std::string>();
|
||||||
auto it = g_mapCmd.find(type);
|
auto it = g_mapCmd.find(type);
|
||||||
if(it != g_mapCmd.end()){
|
if(it != g_mapCmd.end()){
|
||||||
auto fun = it->second;
|
auto fun = it->second;
|
||||||
(this->*fun)(dec);
|
(this->*fun)(dec);
|
||||||
}else{
|
}else{
|
||||||
WarnL << "can not support cmd:" << type;
|
WarnL << "can not support cmd:" << type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,66 +36,66 @@ namespace mediakit {
|
|||||||
|
|
||||||
class RtmpPusher: public RtmpProtocol , public TcpClient , public PusherBase{
|
class RtmpPusher: public RtmpProtocol , public TcpClient , public PusherBase{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtmpPusher> Ptr;
|
typedef std::shared_ptr<RtmpPusher> Ptr;
|
||||||
RtmpPusher(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &src);
|
RtmpPusher(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &src);
|
||||||
virtual ~RtmpPusher();
|
virtual ~RtmpPusher();
|
||||||
|
|
||||||
void publish(const string &strUrl) override ;
|
void publish(const string &strUrl) override ;
|
||||||
|
|
||||||
void teardown() override;
|
void teardown() override;
|
||||||
|
|
||||||
void setOnPublished(const Event &cb) override {
|
void setOnPublished(const Event &cb) override {
|
||||||
_onPublished = cb;
|
_onPublished = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOnShutdown(const Event &cb) override{
|
void setOnShutdown(const Event &cb) override{
|
||||||
_onShutdown = cb;
|
_onShutdown = cb;
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
//for Tcpclient override
|
//for Tcpclient override
|
||||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||||
void onConnect(const SockException &err) override;
|
void onConnect(const SockException &err) override;
|
||||||
void onErr(const SockException &ex) override;
|
void onErr(const SockException &ex) override;
|
||||||
|
|
||||||
//for RtmpProtocol override
|
//for RtmpProtocol override
|
||||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||||
void onSendRawData(const Buffer::Ptr &buffer) override{
|
void onSendRawData(const Buffer::Ptr &buffer) override{
|
||||||
send(buffer);
|
send(buffer);
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
void onPublishResult(const SockException &ex,bool handshakeCompleted);
|
void onPublishResult(const SockException &ex,bool handshakeCompleted);
|
||||||
|
|
||||||
template<typename FUN>
|
template<typename FUN>
|
||||||
inline void addOnResultCB(const FUN &fun) {
|
inline void addOnResultCB(const FUN &fun) {
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||||
_mapOnResultCB.emplace(_iReqID, fun);
|
_mapOnResultCB.emplace(_iReqID, fun);
|
||||||
}
|
}
|
||||||
template<typename FUN>
|
template<typename FUN>
|
||||||
inline void addOnStatusCB(const FUN &fun) {
|
inline void addOnStatusCB(const FUN &fun) {
|
||||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||||
_dqOnStatusCB.emplace_back(fun);
|
_dqOnStatusCB.emplace_back(fun);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onCmd_result(AMFDecoder &dec);
|
void onCmd_result(AMFDecoder &dec);
|
||||||
void onCmd_onStatus(AMFDecoder &dec);
|
void onCmd_onStatus(AMFDecoder &dec);
|
||||||
void onCmd_onMetaData(AMFDecoder &dec);
|
void onCmd_onMetaData(AMFDecoder &dec);
|
||||||
|
|
||||||
inline void send_connect();
|
inline void send_connect();
|
||||||
inline void send_createStream();
|
inline void send_createStream();
|
||||||
inline void send_publish();
|
inline void send_publish();
|
||||||
inline void send_metaData();
|
inline void send_metaData();
|
||||||
void setSocketFlags();
|
void setSocketFlags();
|
||||||
private:
|
private:
|
||||||
string _strApp;
|
string _strApp;
|
||||||
string _strStream;
|
string _strStream;
|
||||||
string _strTcUrl;
|
string _strTcUrl;
|
||||||
|
|
||||||
unordered_map<int, function<void(AMFDecoder &dec)> > _mapOnResultCB;
|
unordered_map<int, function<void(AMFDecoder &dec)> > _mapOnResultCB;
|
||||||
recursive_mutex _mtxOnResultCB;
|
recursive_mutex _mtxOnResultCB;
|
||||||
deque<function<void(AMFValue &dec)> > _dqOnStatusCB;
|
deque<function<void(AMFValue &dec)> > _dqOnStatusCB;
|
||||||
recursive_mutex _mtxOnStatusCB;
|
recursive_mutex _mtxOnStatusCB;
|
||||||
//超时功能实现
|
//超时功能实现
|
||||||
std::shared_ptr<Timer> _pPublishTimer;
|
std::shared_ptr<Timer> _pPublishTimer;
|
||||||
//源
|
//源
|
||||||
std::weak_ptr<RtmpMediaSource> _pMediaSrc;
|
std::weak_ptr<RtmpMediaSource> _pMediaSrc;
|
||||||
RtmpMediaSource::RingType::RingReader::Ptr _pRtmpReader;
|
RtmpMediaSource::RingType::RingReader::Ptr _pRtmpReader;
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
RtmpSession::RtmpSession(const Socket::Ptr &pSock) : TcpSession(pSock) {
|
RtmpSession::RtmpSession(const Socket::Ptr &pSock) : TcpSession(pSock) {
|
||||||
DebugP(this);
|
DebugP(this);
|
||||||
GET_CONFIG(uint32_t,keep_alive_sec,Rtmp::kKeepAliveSecond);
|
GET_CONFIG(uint32_t,keep_alive_sec,Rtmp::kKeepAliveSecond);
|
||||||
pSock->setSendTimeOutSecond(keep_alive_sec);
|
pSock->setSendTimeOutSecond(keep_alive_sec);
|
||||||
//起始接收buffer缓存设置为4K,节省内存
|
//起始接收buffer缓存设置为4K,节省内存
|
||||||
@ -65,42 +65,42 @@ void RtmpSession::onManager() {
|
|||||||
GET_CONFIG(uint32_t,handshake_sec,Rtmp::kHandshakeSecond);
|
GET_CONFIG(uint32_t,handshake_sec,Rtmp::kHandshakeSecond);
|
||||||
GET_CONFIG(uint32_t,keep_alive_sec,Rtmp::kKeepAliveSecond);
|
GET_CONFIG(uint32_t,keep_alive_sec,Rtmp::kKeepAliveSecond);
|
||||||
|
|
||||||
if (_ticker.createdTime() > handshake_sec * 1000) {
|
if (_ticker.createdTime() > handshake_sec * 1000) {
|
||||||
if (!_pRingReader && !_pPublisherSrc) {
|
if (!_pRingReader && !_pPublisherSrc) {
|
||||||
shutdown(SockException(Err_timeout,"illegal connection"));
|
shutdown(SockException(Err_timeout,"illegal connection"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_pPublisherSrc) {
|
if (_pPublisherSrc) {
|
||||||
//publisher
|
//publisher
|
||||||
if (_ticker.elapsedTime() > keep_alive_sec * 1000) {
|
if (_ticker.elapsedTime() > keep_alive_sec * 1000) {
|
||||||
shutdown(SockException(Err_timeout,"recv data from rtmp pusher timeout"));
|
shutdown(SockException(Err_timeout,"recv data from rtmp pusher timeout"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onRecv(const Buffer::Ptr &pBuf) {
|
void RtmpSession::onRecv(const Buffer::Ptr &pBuf) {
|
||||||
_ticker.resetTime();
|
_ticker.resetTime();
|
||||||
try {
|
try {
|
||||||
_ui64TotalBytes += pBuf->size();
|
_ui64TotalBytes += pBuf->size();
|
||||||
onParseRtmp(pBuf->data(), pBuf->size());
|
onParseRtmp(pBuf->data(), pBuf->size());
|
||||||
} catch (exception &e) {
|
} catch (exception &e) {
|
||||||
shutdown(SockException(Err_shutdown, e.what()));
|
shutdown(SockException(Err_shutdown, e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onCmd_connect(AMFDecoder &dec) {
|
void RtmpSession::onCmd_connect(AMFDecoder &dec) {
|
||||||
auto params = dec.load<AMFValue>();
|
auto params = dec.load<AMFValue>();
|
||||||
double amfVer = 0;
|
double amfVer = 0;
|
||||||
AMFValue objectEncoding = params["objectEncoding"];
|
AMFValue objectEncoding = params["objectEncoding"];
|
||||||
if(objectEncoding){
|
if(objectEncoding){
|
||||||
amfVer = objectEncoding.as_number();
|
amfVer = objectEncoding.as_number();
|
||||||
}
|
}
|
||||||
///////////set chunk size////////////////
|
///////////set chunk size////////////////
|
||||||
sendChunkSize(60000);
|
sendChunkSize(60000);
|
||||||
////////////window Acknowledgement size/////
|
////////////window Acknowledgement size/////
|
||||||
sendAcknowledgementSize(5000000);
|
sendAcknowledgementSize(5000000);
|
||||||
///////////set peerBandwidth////////////////
|
///////////set peerBandwidth////////////////
|
||||||
sendPeerBandwidth(5000000);
|
sendPeerBandwidth(5000000);
|
||||||
|
|
||||||
_mediaInfo._app = params["app"].as_string();
|
_mediaInfo._app = params["app"].as_string();
|
||||||
_strTcUrl = params["tcUrl"].as_string();
|
_strTcUrl = params["tcUrl"].as_string();
|
||||||
@ -108,27 +108,27 @@ void RtmpSession::onCmd_connect(AMFDecoder &dec) {
|
|||||||
//defaultVhost:默认vhost
|
//defaultVhost:默认vhost
|
||||||
_strTcUrl = string(RTMP_SCHEMA) + "://" + DEFAULT_VHOST + "/" + _mediaInfo._app;
|
_strTcUrl = string(RTMP_SCHEMA) + "://" + DEFAULT_VHOST + "/" + _mediaInfo._app;
|
||||||
}
|
}
|
||||||
bool ok = true; //(app == APP_NAME);
|
bool ok = true; //(app == APP_NAME);
|
||||||
AMFValue version(AMF_OBJECT);
|
AMFValue version(AMF_OBJECT);
|
||||||
version.set("fmsVer", "FMS/3,0,1,123");
|
version.set("fmsVer", "FMS/3,0,1,123");
|
||||||
version.set("capabilities", 31.0);
|
version.set("capabilities", 31.0);
|
||||||
AMFValue status(AMF_OBJECT);
|
AMFValue status(AMF_OBJECT);
|
||||||
status.set("level", ok ? "status" : "error");
|
status.set("level", ok ? "status" : "error");
|
||||||
status.set("code", ok ? "NetConnection.Connect.Success" : "NetConnection.Connect.InvalidApp");
|
status.set("code", ok ? "NetConnection.Connect.Success" : "NetConnection.Connect.InvalidApp");
|
||||||
status.set("description", ok ? "Connection succeeded." : "InvalidApp.");
|
status.set("description", ok ? "Connection succeeded." : "InvalidApp.");
|
||||||
status.set("objectEncoding", amfVer);
|
status.set("objectEncoding", amfVer);
|
||||||
sendReply(ok ? "_result" : "_error", version, status);
|
sendReply(ok ? "_result" : "_error", version, status);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
throw std::runtime_error("Unsupported application: " + _mediaInfo._app);
|
throw std::runtime_error("Unsupported application: " + _mediaInfo._app);
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFEncoder invoke;
|
AMFEncoder invoke;
|
||||||
invoke << "onBWDone" << 0.0 << nullptr;
|
invoke << "onBWDone" << 0.0 << nullptr;
|
||||||
sendResponse(MSG_CMD, invoke.data());
|
sendResponse(MSG_CMD, invoke.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onCmd_createStream(AMFDecoder &dec) {
|
void RtmpSession::onCmd_createStream(AMFDecoder &dec) {
|
||||||
sendReply("_result", nullptr, double(STREAM_MEDIA));
|
sendReply("_result", nullptr, double(STREAM_MEDIA));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onCmd_publish(AMFDecoder &dec) {
|
void RtmpSession::onCmd_publish(AMFDecoder &dec) {
|
||||||
@ -140,7 +140,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) {
|
|||||||
DebugP(strongSelf.get()) << "publish 回复时间:" << pTicker->elapsedTime() << "ms";
|
DebugP(strongSelf.get()) << "publish 回复时间:" << pTicker->elapsedTime() << "ms";
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
dec.load<AMFValue>();/* NULL */
|
dec.load<AMFValue>();/* NULL */
|
||||||
_mediaInfo.parse(_strTcUrl + "/" + getStreamId(dec.load<std::string>()));
|
_mediaInfo.parse(_strTcUrl + "/" + getStreamId(dec.load<std::string>()));
|
||||||
_mediaInfo._schema = RTMP_SCHEMA;
|
_mediaInfo._schema = RTMP_SCHEMA;
|
||||||
|
|
||||||
@ -203,12 +203,12 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onCmd_deleteStream(AMFDecoder &dec) {
|
void RtmpSession::onCmd_deleteStream(AMFDecoder &dec) {
|
||||||
AMFValue status(AMF_OBJECT);
|
AMFValue status(AMF_OBJECT);
|
||||||
status.set("level", "status");
|
status.set("level", "status");
|
||||||
status.set("code", "NetStream.Unpublish.Success");
|
status.set("code", "NetStream.Unpublish.Success");
|
||||||
status.set("description", "Stop publishing.");
|
status.set("description", "Stop publishing.");
|
||||||
sendReply("onStatus", nullptr, status);
|
sendReply("onStatus", nullptr, status);
|
||||||
throw std::runtime_error(StrPrinter << "Stop publishing" << endl);
|
throw std::runtime_error(StrPrinter << "Stop publishing" << endl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -355,7 +355,7 @@ void RtmpSession::doPlay(AMFDecoder &dec){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void RtmpSession::onCmd_play2(AMFDecoder &dec) {
|
void RtmpSession::onCmd_play2(AMFDecoder &dec) {
|
||||||
doPlay(dec);
|
doPlay(dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
string RtmpSession::getStreamId(const string &str){
|
string RtmpSession::getStreamId(const string &str){
|
||||||
@ -390,49 +390,49 @@ string RtmpSession::getStreamId(const string &str){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onCmd_play(AMFDecoder &dec) {
|
void RtmpSession::onCmd_play(AMFDecoder &dec) {
|
||||||
dec.load<AMFValue>();/* NULL */
|
dec.load<AMFValue>();/* NULL */
|
||||||
_mediaInfo.parse(_strTcUrl + "/" + getStreamId(dec.load<std::string>()));
|
_mediaInfo.parse(_strTcUrl + "/" + getStreamId(dec.load<std::string>()));
|
||||||
_mediaInfo._schema = RTMP_SCHEMA;
|
_mediaInfo._schema = RTMP_SCHEMA;
|
||||||
doPlay(dec);
|
doPlay(dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onCmd_pause(AMFDecoder &dec) {
|
void RtmpSession::onCmd_pause(AMFDecoder &dec) {
|
||||||
dec.load<AMFValue>();/* NULL */
|
dec.load<AMFValue>();/* NULL */
|
||||||
bool paused = dec.load<bool>();
|
bool paused = dec.load<bool>();
|
||||||
TraceP(this) << paused;
|
TraceP(this) << paused;
|
||||||
AMFValue status(AMF_OBJECT);
|
AMFValue status(AMF_OBJECT);
|
||||||
status.set("level", "status");
|
status.set("level", "status");
|
||||||
status.set("code", paused ? "NetStream.Pause.Notify" : "NetStream.Unpause.Notify");
|
status.set("code", paused ? "NetStream.Pause.Notify" : "NetStream.Unpause.Notify");
|
||||||
status.set("description", paused ? "Paused stream." : "Unpaused stream.");
|
status.set("description", paused ? "Paused stream." : "Unpaused stream.");
|
||||||
sendReply("onStatus", nullptr, status);
|
sendReply("onStatus", nullptr, status);
|
||||||
//streamBegin
|
//streamBegin
|
||||||
sendUserControl(paused ? CONTROL_STREAM_EOF : CONTROL_STREAM_BEGIN,
|
sendUserControl(paused ? CONTROL_STREAM_EOF : CONTROL_STREAM_BEGIN,
|
||||||
STREAM_MEDIA);
|
STREAM_MEDIA);
|
||||||
if (!_pRingReader) {
|
if (!_pRingReader) {
|
||||||
throw std::runtime_error("Rtmp not started yet!");
|
throw std::runtime_error("Rtmp not started yet!");
|
||||||
}
|
}
|
||||||
if (paused) {
|
if (paused) {
|
||||||
_pRingReader->setReadCB(nullptr);
|
_pRingReader->setReadCB(nullptr);
|
||||||
} else {
|
} else {
|
||||||
weak_ptr<RtmpSession> weakSelf = dynamic_pointer_cast<RtmpSession>(shared_from_this());
|
weak_ptr<RtmpSession> weakSelf = dynamic_pointer_cast<RtmpSession>(shared_from_this());
|
||||||
_pRingReader->setReadCB([weakSelf](const RtmpPacket::Ptr &pkt) {
|
_pRingReader->setReadCB([weakSelf](const RtmpPacket::Ptr &pkt) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strongSelf->onSendMedia(pkt);
|
strongSelf->onSendMedia(pkt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::setMetaData(AMFDecoder &dec) {
|
void RtmpSession::setMetaData(AMFDecoder &dec) {
|
||||||
if (!_pPublisherSrc) {
|
if (!_pPublisherSrc) {
|
||||||
throw std::runtime_error("not a publisher");
|
throw std::runtime_error("not a publisher");
|
||||||
}
|
}
|
||||||
std::string type = dec.load<std::string>();
|
std::string type = dec.load<std::string>();
|
||||||
if (type != "onMetaData") {
|
if (type != "onMetaData") {
|
||||||
throw std::runtime_error("can only set metadata");
|
throw std::runtime_error("can only set metadata");
|
||||||
}
|
}
|
||||||
auto metadata = dec.load<AMFValue>();
|
auto metadata = dec.load<AMFValue>();
|
||||||
// dumpMetadata(metadata);
|
// dumpMetadata(metadata);
|
||||||
_pPublisherSrc->setMetaData(metadata);
|
_pPublisherSrc->setMetaData(metadata);
|
||||||
@ -453,42 +453,42 @@ void RtmpSession::onProcessCmd(AMFDecoder &dec) {
|
|||||||
s_cmd_functions.emplace("pause",&RtmpSession::onCmd_pause);}, []() {});
|
s_cmd_functions.emplace("pause",&RtmpSession::onCmd_pause);}, []() {});
|
||||||
|
|
||||||
std::string method = dec.load<std::string>();
|
std::string method = dec.load<std::string>();
|
||||||
auto it = s_cmd_functions.find(method);
|
auto it = s_cmd_functions.find(method);
|
||||||
if (it == s_cmd_functions.end()) {
|
if (it == s_cmd_functions.end()) {
|
||||||
// TraceP(this) << "can not support cmd:" << method;
|
// TraceP(this) << "can not support cmd:" << method;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_dNowReqID = dec.load<double>();
|
_dNowReqID = dec.load<double>();
|
||||||
auto fun = it->second;
|
auto fun = it->second;
|
||||||
(this->*fun)(dec);
|
(this->*fun)(dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) {
|
void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) {
|
||||||
switch (chunkData.typeId) {
|
switch (chunkData.typeId) {
|
||||||
case MSG_CMD:
|
case MSG_CMD:
|
||||||
case MSG_CMD3: {
|
case MSG_CMD3: {
|
||||||
AMFDecoder dec(chunkData.strBuf, chunkData.typeId == MSG_CMD3 ? 1 : 0);
|
AMFDecoder dec(chunkData.strBuf, chunkData.typeId == MSG_CMD3 ? 1 : 0);
|
||||||
onProcessCmd(dec);
|
onProcessCmd(dec);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_DATA:
|
case MSG_DATA:
|
||||||
case MSG_DATA3: {
|
case MSG_DATA3: {
|
||||||
AMFDecoder dec(chunkData.strBuf, chunkData.typeId == MSG_CMD3 ? 1 : 0);
|
AMFDecoder dec(chunkData.strBuf, chunkData.typeId == MSG_CMD3 ? 1 : 0);
|
||||||
std::string type = dec.load<std::string>();
|
std::string type = dec.load<std::string>();
|
||||||
if (type == "@setDataFrame") {
|
if (type == "@setDataFrame") {
|
||||||
setMetaData(dec);
|
setMetaData(dec);
|
||||||
}else{
|
}else{
|
||||||
TraceP(this) << "unknown notify:" << type;
|
TraceP(this) << "unknown notify:" << type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MSG_AUDIO:
|
case MSG_AUDIO:
|
||||||
case MSG_VIDEO: {
|
case MSG_VIDEO: {
|
||||||
if (!_pPublisherSrc) {
|
if (!_pPublisherSrc) {
|
||||||
throw std::runtime_error("Not a rtmp publisher!");
|
throw std::runtime_error("Not a rtmp publisher!");
|
||||||
}
|
}
|
||||||
GET_CONFIG(bool,rtmp_modify_stamp,Rtmp::kModifyStamp);
|
GET_CONFIG(bool,rtmp_modify_stamp,Rtmp::kModifyStamp);
|
||||||
if(rtmp_modify_stamp){
|
if(rtmp_modify_stamp){
|
||||||
int64_t dts_out;
|
int64_t dts_out;
|
||||||
_stamp[chunkData.typeId % 2].revise(chunkData.timeStamp, chunkData.timeStamp, dts_out, dts_out, true);
|
_stamp[chunkData.typeId % 2].revise(chunkData.timeStamp, chunkData.timeStamp, dts_out, dts_out, true);
|
||||||
@ -500,12 +500,12 @@ void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) {
|
|||||||
_pPublisherSrc->setMetaData(TitleMeta().getMetadata());
|
_pPublisherSrc->setMetaData(TitleMeta().getMetadata());
|
||||||
}
|
}
|
||||||
_pPublisherSrc->onWrite(std::make_shared<RtmpPacket>(std::move(chunkData)));
|
_pPublisherSrc->onWrite(std::make_shared<RtmpPacket>(std::move(chunkData)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
WarnP(this) << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
WarnP(this) << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onCmd_seek(AMFDecoder &dec) {
|
void RtmpSession::onCmd_seek(AMFDecoder &dec) {
|
||||||
@ -518,12 +518,12 @@ void RtmpSession::onCmd_seek(AMFDecoder &dec) {
|
|||||||
_stamp[1].setPlayBack();
|
_stamp[1].setPlayBack();
|
||||||
stongSrc->seekTo(milliSeconds);
|
stongSrc->seekTo(milliSeconds);
|
||||||
}
|
}
|
||||||
AMFValue status(AMF_OBJECT);
|
AMFValue status(AMF_OBJECT);
|
||||||
AMFEncoder invoke;
|
AMFEncoder invoke;
|
||||||
status.set("level", "status");
|
status.set("level", "status");
|
||||||
status.set("code", "NetStream.Seek.Notify");
|
status.set("code", "NetStream.Seek.Notify");
|
||||||
status.set("description", "Seeking.");
|
status.set("description", "Seeking.");
|
||||||
sendReply("onStatus", nullptr, status);
|
sendReply("onStatus", nullptr, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::onSendMedia(const RtmpPacket::Ptr &pkt) {
|
void RtmpSession::onSendMedia(const RtmpPacket::Ptr &pkt) {
|
||||||
|
@ -45,65 +45,65 @@ namespace mediakit {
|
|||||||
|
|
||||||
class RtmpSession: public TcpSession ,public RtmpProtocol , public MediaSourceEvent{
|
class RtmpSession: public TcpSession ,public RtmpProtocol , public MediaSourceEvent{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtmpSession> Ptr;
|
typedef std::shared_ptr<RtmpSession> Ptr;
|
||||||
RtmpSession(const Socket::Ptr &_sock);
|
RtmpSession(const Socket::Ptr &_sock);
|
||||||
virtual ~RtmpSession();
|
virtual ~RtmpSession();
|
||||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||||
void onError(const SockException &err) override;
|
void onError(const SockException &err) override;
|
||||||
void onManager() override;
|
void onManager() override;
|
||||||
private:
|
private:
|
||||||
void onProcessCmd(AMFDecoder &dec);
|
void onProcessCmd(AMFDecoder &dec);
|
||||||
void onCmd_connect(AMFDecoder &dec);
|
void onCmd_connect(AMFDecoder &dec);
|
||||||
void onCmd_createStream(AMFDecoder &dec);
|
void onCmd_createStream(AMFDecoder &dec);
|
||||||
|
|
||||||
void onCmd_publish(AMFDecoder &dec);
|
void onCmd_publish(AMFDecoder &dec);
|
||||||
void onCmd_deleteStream(AMFDecoder &dec);
|
void onCmd_deleteStream(AMFDecoder &dec);
|
||||||
|
|
||||||
void onCmd_play(AMFDecoder &dec);
|
void onCmd_play(AMFDecoder &dec);
|
||||||
void onCmd_play2(AMFDecoder &dec);
|
void onCmd_play2(AMFDecoder &dec);
|
||||||
void doPlay(AMFDecoder &dec);
|
void doPlay(AMFDecoder &dec);
|
||||||
void doPlayResponse(const string &err,const std::function<void(bool)> &cb);
|
void doPlayResponse(const string &err,const std::function<void(bool)> &cb);
|
||||||
void sendPlayResponse(const string &err,const RtmpMediaSource::Ptr &src);
|
void sendPlayResponse(const string &err,const RtmpMediaSource::Ptr &src);
|
||||||
|
|
||||||
void onCmd_seek(AMFDecoder &dec);
|
void onCmd_seek(AMFDecoder &dec);
|
||||||
void onCmd_pause(AMFDecoder &dec);
|
void onCmd_pause(AMFDecoder &dec);
|
||||||
void setMetaData(AMFDecoder &dec);
|
void setMetaData(AMFDecoder &dec);
|
||||||
|
|
||||||
void onSendMedia(const RtmpPacket::Ptr &pkt);
|
void onSendMedia(const RtmpPacket::Ptr &pkt);
|
||||||
void onSendRawData(const Buffer::Ptr &buffer) override{
|
void onSendRawData(const Buffer::Ptr &buffer) override{
|
||||||
_ui64TotalBytes += buffer->size();
|
_ui64TotalBytes += buffer->size();
|
||||||
send(buffer);
|
send(buffer);
|
||||||
}
|
}
|
||||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||||
|
|
||||||
template<typename first, typename second>
|
template<typename first, typename second>
|
||||||
inline void sendReply(const char *str, const first &reply, const second &status) {
|
inline void sendReply(const char *str, const first &reply, const second &status) {
|
||||||
AMFEncoder invoke;
|
AMFEncoder invoke;
|
||||||
invoke << str << _dNowReqID << reply << status;
|
invoke << str << _dNowReqID << reply << status;
|
||||||
sendResponse(MSG_CMD, invoke.data());
|
sendResponse(MSG_CMD, invoke.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
//MediaSourceEvent override
|
//MediaSourceEvent override
|
||||||
bool close(MediaSource &sender,bool force) override ;
|
bool close(MediaSource &sender,bool force) override ;
|
||||||
void onNoneReader(MediaSource &sender) override;
|
void onNoneReader(MediaSource &sender) override;
|
||||||
int totalReaderCount(MediaSource &sender) override;
|
int totalReaderCount(MediaSource &sender) override;
|
||||||
|
|
||||||
void setSocketFlags();
|
void setSocketFlags();
|
||||||
string getStreamId(const string &str);
|
string getStreamId(const string &str);
|
||||||
void dumpMetadata(const AMFValue &metadata);
|
void dumpMetadata(const AMFValue &metadata);
|
||||||
private:
|
private:
|
||||||
std::string _strTcUrl;
|
std::string _strTcUrl;
|
||||||
MediaInfo _mediaInfo;
|
MediaInfo _mediaInfo;
|
||||||
double _dNowReqID = 0;
|
double _dNowReqID = 0;
|
||||||
bool _set_meta_data = false;
|
bool _set_meta_data = false;
|
||||||
Ticker _ticker;//数据接收时间
|
Ticker _ticker;//数据接收时间
|
||||||
RingBuffer<RtmpPacket::Ptr>::RingReader::Ptr _pRingReader;
|
RingBuffer<RtmpPacket::Ptr>::RingReader::Ptr _pRingReader;
|
||||||
std::shared_ptr<RtmpMediaSourceImp> _pPublisherSrc;
|
std::shared_ptr<RtmpMediaSourceImp> _pPublisherSrc;
|
||||||
std::weak_ptr<RtmpMediaSource> _pPlayerSrc;
|
std::weak_ptr<RtmpMediaSource> _pPlayerSrc;
|
||||||
//时间戳修整器
|
//时间戳修整器
|
||||||
Stamp _stamp[2];
|
Stamp _stamp[2];
|
||||||
//消耗的总流量
|
//消耗的总流量
|
||||||
uint64_t _ui64TotalBytes = 0;
|
uint64_t _ui64TotalBytes = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
794
src/Rtmp/amf.cpp
794
src/Rtmp/amf.cpp
@ -35,130 +35,130 @@ using namespace toolkit;
|
|||||||
|
|
||||||
/////////////////////AMFValue/////////////////////////////
|
/////////////////////AMFValue/////////////////////////////
|
||||||
inline void AMFValue::destroy() {
|
inline void AMFValue::destroy() {
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case AMF_STRING:
|
case AMF_STRING:
|
||||||
if (_value.string) {
|
if (_value.string) {
|
||||||
delete _value.string;
|
delete _value.string;
|
||||||
_value.string = nullptr;
|
_value.string = nullptr;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AMF_OBJECT:
|
case AMF_OBJECT:
|
||||||
case AMF_ECMA_ARRAY:
|
case AMF_ECMA_ARRAY:
|
||||||
if (_value.object) {
|
if (_value.object) {
|
||||||
delete _value.object;
|
delete _value.object;
|
||||||
_value.object = nullptr;
|
_value.object = nullptr;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AMF_STRICT_ARRAY:
|
case AMF_STRICT_ARRAY:
|
||||||
if (_value.array) {
|
if (_value.array) {
|
||||||
delete _value.array;
|
delete _value.array;
|
||||||
_value.array = nullptr;
|
_value.array = nullptr;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inline void AMFValue::init() {
|
inline void AMFValue::init() {
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case AMF_OBJECT:
|
case AMF_OBJECT:
|
||||||
case AMF_ECMA_ARRAY:
|
case AMF_ECMA_ARRAY:
|
||||||
_value.object = new mapType;
|
_value.object = new mapType;
|
||||||
break;
|
break;
|
||||||
case AMF_STRING:
|
case AMF_STRING:
|
||||||
_value.string = new std::string;
|
_value.string = new std::string;
|
||||||
break;
|
break;
|
||||||
case AMF_STRICT_ARRAY:
|
case AMF_STRICT_ARRAY:
|
||||||
_value.array = new arrayType;
|
_value.array = new arrayType;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
AMFValue::AMFValue(AMFType type) :
|
AMFValue::AMFValue(AMFType type) :
|
||||||
_type(type) {
|
_type(type) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AMFValue::~AMFValue() {
|
AMFValue::~AMFValue() {
|
||||||
destroy();
|
destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue::AMFValue(const char *s) :
|
AMFValue::AMFValue(const char *s) :
|
||||||
_type(AMF_STRING) {
|
_type(AMF_STRING) {
|
||||||
init();
|
init();
|
||||||
*_value.string = s;
|
*_value.string = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AMFValue::AMFValue(const std::string &s) :
|
AMFValue::AMFValue(const std::string &s) :
|
||||||
_type(AMF_STRING) {
|
_type(AMF_STRING) {
|
||||||
init();
|
init();
|
||||||
*_value.string = s;
|
*_value.string = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue::AMFValue(double n) :
|
AMFValue::AMFValue(double n) :
|
||||||
_type(AMF_NUMBER) {
|
_type(AMF_NUMBER) {
|
||||||
init();
|
init();
|
||||||
_value.number = n;
|
_value.number = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue::AMFValue(int i) :
|
AMFValue::AMFValue(int i) :
|
||||||
_type(AMF_INTEGER) {
|
_type(AMF_INTEGER) {
|
||||||
init();
|
init();
|
||||||
_value.integer = i;
|
_value.integer = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue::AMFValue(bool b) :
|
AMFValue::AMFValue(bool b) :
|
||||||
_type(AMF_BOOLEAN) {
|
_type(AMF_BOOLEAN) {
|
||||||
init();
|
init();
|
||||||
_value.boolean = b;
|
_value.boolean = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue::AMFValue(const AMFValue &from) :
|
AMFValue::AMFValue(const AMFValue &from) :
|
||||||
_type(AMF_NULL) {
|
_type(AMF_NULL) {
|
||||||
*this = from;
|
*this = from;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue::AMFValue(AMFValue &&from) {
|
AMFValue::AMFValue(AMFValue &&from) {
|
||||||
*this = std::forward<AMFValue>(from);
|
*this = std::forward<AMFValue>(from);
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue& AMFValue::operator =(const AMFValue &from) {
|
AMFValue& AMFValue::operator =(const AMFValue &from) {
|
||||||
return *this = const_cast<AMFValue &&>(from);
|
return *this = const_cast<AMFValue &&>(from);
|
||||||
|
|
||||||
}
|
}
|
||||||
AMFValue& AMFValue::operator =(AMFValue &&from) {
|
AMFValue& AMFValue::operator =(AMFValue &&from) {
|
||||||
destroy();
|
destroy();
|
||||||
_type = from._type;
|
_type = from._type;
|
||||||
init();
|
init();
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case AMF_STRING:
|
case AMF_STRING:
|
||||||
*_value.string = (*from._value.string);
|
*_value.string = (*from._value.string);
|
||||||
break;
|
break;
|
||||||
case AMF_OBJECT:
|
case AMF_OBJECT:
|
||||||
case AMF_ECMA_ARRAY:
|
case AMF_ECMA_ARRAY:
|
||||||
*_value.object = (*from._value.object);
|
*_value.object = (*from._value.object);
|
||||||
break;
|
break;
|
||||||
case AMF_STRICT_ARRAY:
|
case AMF_STRICT_ARRAY:
|
||||||
*_value.array = (*from._value.array);
|
*_value.array = (*from._value.array);
|
||||||
break;
|
break;
|
||||||
case AMF_NUMBER:
|
case AMF_NUMBER:
|
||||||
_value.number = from._value.number;
|
_value.number = from._value.number;
|
||||||
break;
|
break;
|
||||||
case AMF_INTEGER:
|
case AMF_INTEGER:
|
||||||
_value.integer = from._value.integer;
|
_value.integer = from._value.integer;
|
||||||
break;
|
break;
|
||||||
case AMF_BOOLEAN:
|
case AMF_BOOLEAN:
|
||||||
_value.boolean = from._value.boolean;
|
_value.boolean = from._value.boolean;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,156 +305,156 @@ const AMFValue::arrayType &AMFValue::getArr() const {
|
|||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
AMF0_NUMBER,
|
AMF0_NUMBER,
|
||||||
AMF0_BOOLEAN,
|
AMF0_BOOLEAN,
|
||||||
AMF0_STRING,
|
AMF0_STRING,
|
||||||
AMF0_OBJECT,
|
AMF0_OBJECT,
|
||||||
AMF0_MOVIECLIP,
|
AMF0_MOVIECLIP,
|
||||||
AMF0_NULL,
|
AMF0_NULL,
|
||||||
AMF0_UNDEFINED,
|
AMF0_UNDEFINED,
|
||||||
AMF0_REFERENCE,
|
AMF0_REFERENCE,
|
||||||
AMF0_ECMA_ARRAY,
|
AMF0_ECMA_ARRAY,
|
||||||
AMF0_OBJECT_END,
|
AMF0_OBJECT_END,
|
||||||
AMF0_STRICT_ARRAY,
|
AMF0_STRICT_ARRAY,
|
||||||
AMF0_DATE,
|
AMF0_DATE,
|
||||||
AMF0_LONG_STRING,
|
AMF0_LONG_STRING,
|
||||||
AMF0_UNSUPPORTED,
|
AMF0_UNSUPPORTED,
|
||||||
AMF0_RECORD_SET,
|
AMF0_RECORD_SET,
|
||||||
AMF0_XML_OBJECT,
|
AMF0_XML_OBJECT,
|
||||||
AMF0_TYPED_OBJECT,
|
AMF0_TYPED_OBJECT,
|
||||||
AMF0_SWITCH_AMF3,
|
AMF0_SWITCH_AMF3,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
AMF3_UNDEFINED,
|
AMF3_UNDEFINED,
|
||||||
AMF3_NULL,
|
AMF3_NULL,
|
||||||
AMF3_FALSE,
|
AMF3_FALSE,
|
||||||
AMF3_TRUE,
|
AMF3_TRUE,
|
||||||
AMF3_INTEGER,
|
AMF3_INTEGER,
|
||||||
AMF3_NUMBER,
|
AMF3_NUMBER,
|
||||||
AMF3_STRING,
|
AMF3_STRING,
|
||||||
AMF3_LEGACY_XML,
|
AMF3_LEGACY_XML,
|
||||||
AMF3_DATE,
|
AMF3_DATE,
|
||||||
AMF3_ARRAY,
|
AMF3_ARRAY,
|
||||||
AMF3_OBJECT,
|
AMF3_OBJECT,
|
||||||
AMF3_XML,
|
AMF3_XML,
|
||||||
AMF3_BYTE_ARRAY,
|
AMF3_BYTE_ARRAY,
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////Encoder//////////////////////////////////////////
|
////////////////////////////////Encoder//////////////////////////////////////////
|
||||||
AMFEncoder & AMFEncoder::operator <<(const char *s) {
|
AMFEncoder & AMFEncoder::operator <<(const char *s) {
|
||||||
if (s) {
|
if (s) {
|
||||||
buf += char(AMF0_STRING);
|
buf += char(AMF0_STRING);
|
||||||
uint16_t str_len = htons(strlen(s));
|
uint16_t str_len = htons(strlen(s));
|
||||||
buf.append((char *) &str_len, 2);
|
buf.append((char *) &str_len, 2);
|
||||||
buf += s;
|
buf += s;
|
||||||
} else {
|
} else {
|
||||||
buf += char(AMF0_NULL);
|
buf += char(AMF0_NULL);
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
AMFEncoder & AMFEncoder::operator <<(const std::string &s) {
|
AMFEncoder & AMFEncoder::operator <<(const std::string &s) {
|
||||||
if (!s.empty()) {
|
if (!s.empty()) {
|
||||||
buf += char(AMF0_STRING);
|
buf += char(AMF0_STRING);
|
||||||
uint16_t str_len = htons(s.size());
|
uint16_t str_len = htons(s.size());
|
||||||
buf.append((char *) &str_len, 2);
|
buf.append((char *) &str_len, 2);
|
||||||
buf += s;
|
buf += s;
|
||||||
} else {
|
} else {
|
||||||
buf += char(AMF0_NULL);
|
buf += char(AMF0_NULL);
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
AMFEncoder & AMFEncoder::operator <<(std::nullptr_t) {
|
AMFEncoder & AMFEncoder::operator <<(std::nullptr_t) {
|
||||||
buf += char(AMF0_NULL);
|
buf += char(AMF0_NULL);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
AMFEncoder & AMFEncoder::write_undefined() {
|
AMFEncoder & AMFEncoder::write_undefined() {
|
||||||
buf += char(AMF0_UNDEFINED);
|
buf += char(AMF0_UNDEFINED);
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
}
|
}
|
||||||
AMFEncoder & AMFEncoder::operator <<(const int n){
|
AMFEncoder & AMFEncoder::operator <<(const int n){
|
||||||
return (*this) << (double)n;
|
return (*this) << (double)n;
|
||||||
}
|
}
|
||||||
AMFEncoder & AMFEncoder::operator <<(const double n) {
|
AMFEncoder & AMFEncoder::operator <<(const double n) {
|
||||||
buf += char(AMF0_NUMBER);
|
buf += char(AMF0_NUMBER);
|
||||||
uint64_t encoded = 0;
|
uint64_t encoded = 0;
|
||||||
memcpy(&encoded, &n, 8);
|
memcpy(&encoded, &n, 8);
|
||||||
uint32_t val = htonl(encoded >> 32);
|
uint32_t val = htonl(encoded >> 32);
|
||||||
buf.append((char *) &val, 4);
|
buf.append((char *) &val, 4);
|
||||||
val = htonl(encoded);
|
val = htonl(encoded);
|
||||||
buf.append((char *) &val, 4);
|
buf.append((char *) &val, 4);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFEncoder & AMFEncoder::operator <<(const bool b) {
|
AMFEncoder & AMFEncoder::operator <<(const bool b) {
|
||||||
buf += char(AMF0_BOOLEAN);
|
buf += char(AMF0_BOOLEAN);
|
||||||
buf += char(b);
|
buf += char(b);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFEncoder & AMFEncoder::operator <<(const AMFValue& value) {
|
AMFEncoder & AMFEncoder::operator <<(const AMFValue& value) {
|
||||||
switch ((int) value.type()) {
|
switch ((int) value.type()) {
|
||||||
case AMF_STRING:
|
case AMF_STRING:
|
||||||
*this << value.as_string();
|
*this << value.as_string();
|
||||||
break;
|
break;
|
||||||
case AMF_NUMBER:
|
case AMF_NUMBER:
|
||||||
*this << value.as_number();
|
*this << value.as_number();
|
||||||
break;
|
break;
|
||||||
case AMF_INTEGER:
|
case AMF_INTEGER:
|
||||||
*this << value.as_integer();
|
*this << value.as_integer();
|
||||||
break;
|
break;
|
||||||
case AMF_BOOLEAN:
|
case AMF_BOOLEAN:
|
||||||
*this << value.as_boolean();
|
*this << value.as_boolean();
|
||||||
break;
|
break;
|
||||||
case AMF_OBJECT: {
|
case AMF_OBJECT: {
|
||||||
buf += char(AMF0_OBJECT);
|
buf += char(AMF0_OBJECT);
|
||||||
for (auto &pr : value.getMap()) {
|
for (auto &pr : value.getMap()) {
|
||||||
write_key(pr.first);
|
write_key(pr.first);
|
||||||
*this << pr.second;
|
*this << pr.second;
|
||||||
}
|
}
|
||||||
write_key("");
|
write_key("");
|
||||||
buf += char(AMF0_OBJECT_END);
|
buf += char(AMF0_OBJECT_END);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AMF_ECMA_ARRAY: {
|
case AMF_ECMA_ARRAY: {
|
||||||
buf += char(AMF0_ECMA_ARRAY);
|
buf += char(AMF0_ECMA_ARRAY);
|
||||||
uint32_t sz = htonl(value.getMap().size());
|
uint32_t sz = htonl(value.getMap().size());
|
||||||
buf.append((char *) &sz, 4);
|
buf.append((char *) &sz, 4);
|
||||||
for (auto &pr : value.getMap()) {
|
for (auto &pr : value.getMap()) {
|
||||||
write_key(pr.first);
|
write_key(pr.first);
|
||||||
*this << pr.second;
|
*this << pr.second;
|
||||||
}
|
}
|
||||||
write_key("");
|
write_key("");
|
||||||
buf += char(AMF0_OBJECT_END);
|
buf += char(AMF0_OBJECT_END);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AMF_NULL:
|
case AMF_NULL:
|
||||||
*this << nullptr;
|
*this << nullptr;
|
||||||
break;
|
break;
|
||||||
case AMF_UNDEFINED:
|
case AMF_UNDEFINED:
|
||||||
this->write_undefined();
|
this->write_undefined();
|
||||||
break;
|
break;
|
||||||
case AMF_STRICT_ARRAY: {
|
case AMF_STRICT_ARRAY: {
|
||||||
buf += char(AMF0_STRICT_ARRAY);
|
buf += char(AMF0_STRICT_ARRAY);
|
||||||
uint32_t sz = htonl(value.getArr().size());
|
uint32_t sz = htonl(value.getArr().size());
|
||||||
buf.append((char *) &sz, 4);
|
buf.append((char *) &sz, 4);
|
||||||
for (auto &val : value.getArr()) {
|
for (auto &val : value.getArr()) {
|
||||||
*this << val;
|
*this << val;
|
||||||
}
|
}
|
||||||
//write_key("");
|
//write_key("");
|
||||||
//buf += char(AMF0_OBJECT_END);
|
//buf += char(AMF0_OBJECT_END);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AMFEncoder::write_key(const std::string& s) {
|
void AMFEncoder::write_key(const std::string& s) {
|
||||||
uint16_t str_len = htons(s.size());
|
uint16_t str_len = htons(s.size());
|
||||||
buf.append((char *) &str_len, 2);
|
buf.append((char *) &str_len, 2);
|
||||||
buf += s;
|
buf += s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AMFEncoder::clear() {
|
void AMFEncoder::clear() {
|
||||||
@ -468,237 +468,237 @@ const std::string& AMFEncoder::data() const {
|
|||||||
//////////////////Decoder//////////////////
|
//////////////////Decoder//////////////////
|
||||||
|
|
||||||
uint8_t AMFDecoder::front() {
|
uint8_t AMFDecoder::front() {
|
||||||
if (pos >= buf.size()) {
|
if (pos >= buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
return uint8_t(buf[pos]);
|
return uint8_t(buf[pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t AMFDecoder::pop_front() {
|
uint8_t AMFDecoder::pop_front() {
|
||||||
if (version == 0 && front() == AMF0_SWITCH_AMF3) {
|
if (version == 0 && front() == AMF0_SWITCH_AMF3) {
|
||||||
InfoL << "entering AMF3 mode";
|
InfoL << "entering AMF3 mode";
|
||||||
pos++;
|
pos++;
|
||||||
version = 3;
|
version = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pos >= buf.size()) {
|
if (pos >= buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
return uint8_t(buf[pos++]);
|
return uint8_t(buf[pos++]);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
double AMFDecoder::load<double>() {
|
double AMFDecoder::load<double>() {
|
||||||
if (pop_front() != AMF0_NUMBER) {
|
if (pop_front() != AMF0_NUMBER) {
|
||||||
throw std::runtime_error("Expected a number");
|
throw std::runtime_error("Expected a number");
|
||||||
}
|
}
|
||||||
if (pos + 8 > buf.size()) {
|
if (pos + 8 > buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
uint64_t val = ((uint64_t) load_be32(&buf[pos]) << 32)
|
uint64_t val = ((uint64_t) load_be32(&buf[pos]) << 32)
|
||||||
| load_be32(&buf[pos + 4]);
|
| load_be32(&buf[pos + 4]);
|
||||||
double n = 0;
|
double n = 0;
|
||||||
memcpy(&n, &val, 8);
|
memcpy(&n, &val, 8);
|
||||||
pos += 8;
|
pos += 8;
|
||||||
return n;
|
return n;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
bool AMFDecoder::load<bool>() {
|
bool AMFDecoder::load<bool>() {
|
||||||
if (pop_front() != AMF0_BOOLEAN) {
|
if (pop_front() != AMF0_BOOLEAN) {
|
||||||
throw std::runtime_error("Expected a boolean");
|
throw std::runtime_error("Expected a boolean");
|
||||||
}
|
}
|
||||||
return pop_front() != 0;
|
return pop_front() != 0;
|
||||||
}
|
}
|
||||||
template<>
|
template<>
|
||||||
unsigned int AMFDecoder::load<unsigned int>() {
|
unsigned int AMFDecoder::load<unsigned int>() {
|
||||||
unsigned int value = 0;
|
unsigned int value = 0;
|
||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
uint8_t b = pop_front();
|
uint8_t b = pop_front();
|
||||||
if (i == 3) {
|
if (i == 3) {
|
||||||
/* use all bits from 4th byte */
|
/* use all bits from 4th byte */
|
||||||
value = (value << 8) | b;
|
value = (value << 8) | b;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
value = (value << 7) | (b & 0x7f);
|
value = (value << 7) | (b & 0x7f);
|
||||||
if ((b & 0x80) == 0)
|
if ((b & 0x80) == 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
int AMFDecoder::load<int>() {
|
int AMFDecoder::load<int>() {
|
||||||
if (version == 3) {
|
if (version == 3) {
|
||||||
return load<unsigned int>();
|
return load<unsigned int>();
|
||||||
} else {
|
} else {
|
||||||
return load<double>();
|
return load<double>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::string AMFDecoder::load<std::string>() {
|
std::string AMFDecoder::load<std::string>() {
|
||||||
size_t str_len = 0;
|
size_t str_len = 0;
|
||||||
uint8_t type = pop_front();
|
uint8_t type = pop_front();
|
||||||
if (version == 3) {
|
if (version == 3) {
|
||||||
if (type != AMF3_STRING) {
|
if (type != AMF3_STRING) {
|
||||||
throw std::runtime_error("Expected a string");
|
throw std::runtime_error("Expected a string");
|
||||||
}
|
}
|
||||||
str_len = load<unsigned int>() / 2;
|
str_len = load<unsigned int>() / 2;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (type != AMF0_STRING) {
|
if (type != AMF0_STRING) {
|
||||||
throw std::runtime_error("Expected a string");
|
throw std::runtime_error("Expected a string");
|
||||||
}
|
}
|
||||||
if (pos + 2 > buf.size()) {
|
if (pos + 2 > buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
str_len = load_be16(&buf[pos]);
|
str_len = load_be16(&buf[pos]);
|
||||||
pos += 2;
|
pos += 2;
|
||||||
}
|
}
|
||||||
if (pos + str_len > buf.size()) {
|
if (pos + str_len > buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
std::string s(buf, pos, str_len);
|
std::string s(buf, pos, str_len);
|
||||||
pos += str_len;
|
pos += str_len;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
AMFValue AMFDecoder::load<AMFValue>() {
|
AMFValue AMFDecoder::load<AMFValue>() {
|
||||||
uint8_t type = front();
|
uint8_t type = front();
|
||||||
if (version == 3) {
|
if (version == 3) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AMF3_STRING:
|
case AMF3_STRING:
|
||||||
return load<std::string>();
|
return load<std::string>();
|
||||||
case AMF3_NUMBER:
|
case AMF3_NUMBER:
|
||||||
return load<double>();
|
return load<double>();
|
||||||
case AMF3_INTEGER:
|
case AMF3_INTEGER:
|
||||||
return load<int>();
|
return load<int>();
|
||||||
case AMF3_FALSE:
|
case AMF3_FALSE:
|
||||||
pos++;
|
pos++;
|
||||||
return false;
|
return false;
|
||||||
case AMF3_TRUE:
|
case AMF3_TRUE:
|
||||||
pos++;
|
pos++;
|
||||||
return true;
|
return true;
|
||||||
case AMF3_OBJECT:
|
case AMF3_OBJECT:
|
||||||
return load_object();
|
return load_object();
|
||||||
case AMF3_ARRAY:
|
case AMF3_ARRAY:
|
||||||
return load_ecma();
|
return load_ecma();
|
||||||
case AMF3_NULL:
|
case AMF3_NULL:
|
||||||
pos++;
|
pos++;
|
||||||
return AMF_NULL;
|
return AMF_NULL;
|
||||||
case AMF3_UNDEFINED:
|
case AMF3_UNDEFINED:
|
||||||
pos++;
|
pos++;
|
||||||
return AMF_UNDEFINED;
|
return AMF_UNDEFINED;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
StrPrinter << "Unsupported AMF3 type:" << (int) type << endl);
|
StrPrinter << "Unsupported AMF3 type:" << (int) type << endl);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AMF0_STRING:
|
case AMF0_STRING:
|
||||||
return load<std::string>();
|
return load<std::string>();
|
||||||
case AMF0_NUMBER:
|
case AMF0_NUMBER:
|
||||||
return load<double>();
|
return load<double>();
|
||||||
case AMF0_BOOLEAN:
|
case AMF0_BOOLEAN:
|
||||||
return load<bool>();
|
return load<bool>();
|
||||||
case AMF0_OBJECT:
|
case AMF0_OBJECT:
|
||||||
return load_object();
|
return load_object();
|
||||||
case AMF0_ECMA_ARRAY:
|
case AMF0_ECMA_ARRAY:
|
||||||
return load_ecma();
|
return load_ecma();
|
||||||
case AMF0_NULL:
|
case AMF0_NULL:
|
||||||
pos++;
|
pos++;
|
||||||
return AMF_NULL;
|
return AMF_NULL;
|
||||||
case AMF0_UNDEFINED:
|
case AMF0_UNDEFINED:
|
||||||
pos++;
|
pos++;
|
||||||
return AMF_UNDEFINED;
|
return AMF_UNDEFINED;
|
||||||
case AMF0_STRICT_ARRAY:
|
case AMF0_STRICT_ARRAY:
|
||||||
return load_arr();
|
return load_arr();
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
StrPrinter << "Unsupported AMF type:" << (int) type << endl);
|
StrPrinter << "Unsupported AMF type:" << (int) type << endl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string AMFDecoder::load_key() {
|
std::string AMFDecoder::load_key() {
|
||||||
if (pos + 2 > buf.size()) {
|
if (pos + 2 > buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
size_t str_len = load_be16(&buf[pos]);
|
size_t str_len = load_be16(&buf[pos]);
|
||||||
pos += 2;
|
pos += 2;
|
||||||
if (pos + str_len > buf.size()) {
|
if (pos + str_len > buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
std::string s(buf, pos, str_len);
|
std::string s(buf, pos, str_len);
|
||||||
pos += str_len;
|
pos += str_len;
|
||||||
return s;
|
return s;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue AMFDecoder::load_object() {
|
AMFValue AMFDecoder::load_object() {
|
||||||
AMFValue object(AMF_OBJECT);
|
AMFValue object(AMF_OBJECT);
|
||||||
if (pop_front() != AMF0_OBJECT) {
|
if (pop_front() != AMF0_OBJECT) {
|
||||||
throw std::runtime_error("Expected an object");
|
throw std::runtime_error("Expected an object");
|
||||||
}
|
}
|
||||||
while (1) {
|
while (1) {
|
||||||
std::string key = load_key();
|
std::string key = load_key();
|
||||||
if (key.empty())
|
if (key.empty())
|
||||||
break;
|
break;
|
||||||
AMFValue value = load<AMFValue>();
|
AMFValue value = load<AMFValue>();
|
||||||
object.set(key, value);
|
object.set(key, value);
|
||||||
}
|
}
|
||||||
if (pop_front() != AMF0_OBJECT_END) {
|
if (pop_front() != AMF0_OBJECT_END) {
|
||||||
throw std::runtime_error("expected object end");
|
throw std::runtime_error("expected object end");
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFValue AMFDecoder::load_ecma() {
|
AMFValue AMFDecoder::load_ecma() {
|
||||||
/* ECMA array is the same as object, with 4 extra zero bytes */
|
/* ECMA array is the same as object, with 4 extra zero bytes */
|
||||||
AMFValue object(AMF_ECMA_ARRAY);
|
AMFValue object(AMF_ECMA_ARRAY);
|
||||||
if (pop_front() != AMF0_ECMA_ARRAY) {
|
if (pop_front() != AMF0_ECMA_ARRAY) {
|
||||||
throw std::runtime_error("Expected an ECMA array");
|
throw std::runtime_error("Expected an ECMA array");
|
||||||
}
|
}
|
||||||
if (pos + 4 > buf.size()) {
|
if (pos + 4 > buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
pos += 4;
|
pos += 4;
|
||||||
while (1) {
|
while (1) {
|
||||||
std::string key = load_key();
|
std::string key = load_key();
|
||||||
if (key.empty())
|
if (key.empty())
|
||||||
break;
|
break;
|
||||||
AMFValue value = load<AMFValue>();
|
AMFValue value = load<AMFValue>();
|
||||||
object.set(key, value);
|
object.set(key, value);
|
||||||
}
|
}
|
||||||
if (pop_front() != AMF0_OBJECT_END) {
|
if (pop_front() != AMF0_OBJECT_END) {
|
||||||
throw std::runtime_error("expected object end");
|
throw std::runtime_error("expected object end");
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
AMFValue AMFDecoder::load_arr() {
|
AMFValue AMFDecoder::load_arr() {
|
||||||
/* ECMA array is the same as object, with 4 extra zero bytes */
|
/* ECMA array is the same as object, with 4 extra zero bytes */
|
||||||
AMFValue object(AMF_STRICT_ARRAY);
|
AMFValue object(AMF_STRICT_ARRAY);
|
||||||
if (pop_front() != AMF0_STRICT_ARRAY) {
|
if (pop_front() != AMF0_STRICT_ARRAY) {
|
||||||
throw std::runtime_error("Expected an STRICT array");
|
throw std::runtime_error("Expected an STRICT array");
|
||||||
}
|
}
|
||||||
if (pos + 4 > buf.size()) {
|
if (pos + 4 > buf.size()) {
|
||||||
throw std::runtime_error("Not enough data");
|
throw std::runtime_error("Not enough data");
|
||||||
}
|
}
|
||||||
int arrSize = load_be32(&buf[pos]);
|
int arrSize = load_be32(&buf[pos]);
|
||||||
pos += 4;
|
pos += 4;
|
||||||
while (arrSize--) {
|
while (arrSize--) {
|
||||||
AMFValue value = load<AMFValue>();
|
AMFValue value = load<AMFValue>();
|
||||||
object.add(value);
|
object.add(value);
|
||||||
}
|
}
|
||||||
/*pos += 2;
|
/*pos += 2;
|
||||||
if (pop_front() != AMF0_OBJECT_END) {
|
if (pop_front() != AMF0_OBJECT_END) {
|
||||||
throw std::runtime_error("expected object end");
|
throw std::runtime_error("expected object end");
|
||||||
}*/
|
}*/
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
AMFDecoder::AMFDecoder(const std::string &buf_in, size_t pos_in, int version_in) :
|
AMFDecoder::AMFDecoder(const std::string &buf_in, size_t pos_in, int version_in) :
|
||||||
|
126
src/Rtmp/amf.h
126
src/Rtmp/amf.h
@ -36,15 +36,15 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
enum AMFType {
|
enum AMFType {
|
||||||
AMF_NUMBER,
|
AMF_NUMBER,
|
||||||
AMF_INTEGER,
|
AMF_INTEGER,
|
||||||
AMF_BOOLEAN,
|
AMF_BOOLEAN,
|
||||||
AMF_STRING,
|
AMF_STRING,
|
||||||
AMF_OBJECT,
|
AMF_OBJECT,
|
||||||
AMF_NULL,
|
AMF_NULL,
|
||||||
AMF_UNDEFINED,
|
AMF_UNDEFINED,
|
||||||
AMF_ECMA_ARRAY,
|
AMF_ECMA_ARRAY,
|
||||||
AMF_STRICT_ARRAY,
|
AMF_STRICT_ARRAY,
|
||||||
};
|
};
|
||||||
|
|
||||||
class AMFValue;
|
class AMFValue;
|
||||||
@ -55,79 +55,79 @@ public:
|
|||||||
typedef std::map<std::string, AMFValue> mapType;
|
typedef std::map<std::string, AMFValue> mapType;
|
||||||
typedef std::vector<AMFValue> arrayType;
|
typedef std::vector<AMFValue> arrayType;
|
||||||
|
|
||||||
AMFValue(AMFType type = AMF_NULL);
|
AMFValue(AMFType type = AMF_NULL);
|
||||||
AMFValue(const char *s);
|
AMFValue(const char *s);
|
||||||
AMFValue(const std::string &s);
|
AMFValue(const std::string &s);
|
||||||
AMFValue(double n);
|
AMFValue(double n);
|
||||||
AMFValue(int i);
|
AMFValue(int i);
|
||||||
AMFValue(bool b);
|
AMFValue(bool b);
|
||||||
AMFValue(const AMFValue &from);
|
AMFValue(const AMFValue &from);
|
||||||
AMFValue(AMFValue &&from);
|
AMFValue(AMFValue &&from);
|
||||||
AMFValue &operator =(const AMFValue &from);
|
AMFValue &operator =(const AMFValue &from);
|
||||||
AMFValue &operator =(AMFValue &&from);
|
AMFValue &operator =(AMFValue &&from);
|
||||||
~AMFValue();
|
~AMFValue();
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
AMFType type() const ;
|
AMFType type() const ;
|
||||||
const std::string &as_string() const;
|
const std::string &as_string() const;
|
||||||
double as_number() const;
|
double as_number() const;
|
||||||
int as_integer() const;
|
int as_integer() const;
|
||||||
bool as_boolean() const;
|
bool as_boolean() const;
|
||||||
string to_string() const;
|
string to_string() const;
|
||||||
const AMFValue &operator[](const char *str) const;
|
const AMFValue &operator[](const char *str) const;
|
||||||
void object_for_each(const function<void(const string &key, const AMFValue &val)> &fun) const ;
|
void object_for_each(const function<void(const string &key, const AMFValue &val)> &fun) const ;
|
||||||
operator bool() const;
|
operator bool() const;
|
||||||
void set(const std::string &s, const AMFValue &val);
|
void set(const std::string &s, const AMFValue &val);
|
||||||
void add(const AMFValue &val);
|
void add(const AMFValue &val);
|
||||||
private:
|
private:
|
||||||
const mapType &getMap() const;
|
const mapType &getMap() const;
|
||||||
const arrayType &getArr() const;
|
const arrayType &getArr() const;
|
||||||
void destroy();
|
void destroy();
|
||||||
void init();
|
void init();
|
||||||
private:
|
private:
|
||||||
AMFType _type;
|
AMFType _type;
|
||||||
union {
|
union {
|
||||||
std::string *string;
|
std::string *string;
|
||||||
double number;
|
double number;
|
||||||
int integer;
|
int integer;
|
||||||
bool boolean;
|
bool boolean;
|
||||||
mapType *object;
|
mapType *object;
|
||||||
arrayType *array;
|
arrayType *array;
|
||||||
} _value;
|
} _value;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AMFDecoder {
|
class AMFDecoder {
|
||||||
public:
|
public:
|
||||||
AMFDecoder(const std::string &buf, size_t pos, int version = 0);
|
AMFDecoder(const std::string &buf, size_t pos, int version = 0);
|
||||||
template<typename TP>
|
template<typename TP>
|
||||||
TP load();
|
TP load();
|
||||||
private:
|
private:
|
||||||
std::string load_key();
|
std::string load_key();
|
||||||
AMFValue load_object();
|
AMFValue load_object();
|
||||||
AMFValue load_ecma();
|
AMFValue load_ecma();
|
||||||
AMFValue load_arr();
|
AMFValue load_arr();
|
||||||
uint8_t front();
|
uint8_t front();
|
||||||
uint8_t pop_front();
|
uint8_t pop_front();
|
||||||
private:
|
private:
|
||||||
const std::string &buf;
|
const std::string &buf;
|
||||||
size_t pos;
|
size_t pos;
|
||||||
int version;
|
int version;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AMFEncoder {
|
class AMFEncoder {
|
||||||
public:
|
public:
|
||||||
AMFEncoder & operator <<(const char *s);
|
AMFEncoder & operator <<(const char *s);
|
||||||
AMFEncoder & operator <<(const std::string &s);
|
AMFEncoder & operator <<(const std::string &s);
|
||||||
AMFEncoder & operator <<(std::nullptr_t);
|
AMFEncoder & operator <<(std::nullptr_t);
|
||||||
AMFEncoder & operator <<(const int n);
|
AMFEncoder & operator <<(const int n);
|
||||||
AMFEncoder & operator <<(const double n);
|
AMFEncoder & operator <<(const double n);
|
||||||
AMFEncoder & operator <<(const bool b);
|
AMFEncoder & operator <<(const bool b);
|
||||||
AMFEncoder & operator <<(const AMFValue &value);
|
AMFEncoder & operator <<(const AMFValue &value);
|
||||||
const std::string& data() const ;
|
const std::string& data() const ;
|
||||||
void clear() ;
|
void clear() ;
|
||||||
private:
|
private:
|
||||||
void write_key(const std::string &s);
|
void write_key(const std::string &s);
|
||||||
AMFEncoder &write_undefined();
|
AMFEncoder &write_undefined();
|
||||||
private:
|
private:
|
||||||
std::string buf;
|
std::string buf;
|
||||||
};
|
};
|
||||||
|
@ -38,54 +38,54 @@ using namespace toolkit;
|
|||||||
*/
|
*/
|
||||||
uint32_t load_be32(const void *p)
|
uint32_t load_be32(const void *p)
|
||||||
{
|
{
|
||||||
uint32_t val;
|
uint32_t val;
|
||||||
memcpy(&val, p, sizeof val);
|
memcpy(&val, p, sizeof val);
|
||||||
return ntohl(val);
|
return ntohl(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t load_be16(const void *p)
|
uint16_t load_be16(const void *p)
|
||||||
{
|
{
|
||||||
uint16_t val;
|
uint16_t val;
|
||||||
memcpy(&val, p, sizeof val);
|
memcpy(&val, p, sizeof val);
|
||||||
return ntohs(val);
|
return ntohs(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t load_le32(const void *p)
|
uint32_t load_le32(const void *p)
|
||||||
{
|
{
|
||||||
const uint8_t *data = (const uint8_t *) p;
|
const uint8_t *data = (const uint8_t *) p;
|
||||||
return data[0] | ((uint32_t) data[1] << 8) |
|
return data[0] | ((uint32_t) data[1] << 8) |
|
||||||
((uint32_t) data[2] << 16) | ((uint32_t) data[3] << 24);
|
((uint32_t) data[2] << 16) | ((uint32_t) data[3] << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t load_be24(const void *p)
|
uint32_t load_be24(const void *p)
|
||||||
{
|
{
|
||||||
const uint8_t *data = (const uint8_t *) p;
|
const uint8_t *data = (const uint8_t *) p;
|
||||||
return data[2] | ((uint32_t) data[1] << 8) | ((uint32_t) data[0] << 16);
|
return data[2] | ((uint32_t) data[1] << 8) | ((uint32_t) data[0] << 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_be24(void *p, uint32_t val)
|
void set_be24(void *p, uint32_t val)
|
||||||
{
|
{
|
||||||
uint8_t *data = (uint8_t *) p;
|
uint8_t *data = (uint8_t *) p;
|
||||||
data[0] = val >> 16;
|
data[0] = val >> 16;
|
||||||
data[1] = val >> 8;
|
data[1] = val >> 8;
|
||||||
data[2] = val;
|
data[2] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_le32(void *p, uint32_t val)
|
void set_le32(void *p, uint32_t val)
|
||||||
{
|
{
|
||||||
uint8_t *data = (uint8_t *) p;
|
uint8_t *data = (uint8_t *) p;
|
||||||
data[0] = val;
|
data[0] = val;
|
||||||
data[1] = val >> 8;
|
data[1] = val >> 8;
|
||||||
data[2] = val >> 16;
|
data[2] = val >> 16;
|
||||||
data[3] = val >> 24;
|
data[3] = val >> 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_be32(void *p, uint32_t val)
|
void set_be32(void *p, uint32_t val)
|
||||||
{
|
{
|
||||||
uint8_t *data = (uint8_t *) p;
|
uint8_t *data = (uint8_t *) p;
|
||||||
data[3] = val;
|
data[3] = val;
|
||||||
data[2] = val >> 8;
|
data[2] = val >> 8;
|
||||||
data[1] = val >> 16;
|
data[1] = val >> 16;
|
||||||
data[0] = val >> 24;
|
data[0] = val >> 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,47 +37,47 @@ using namespace toolkit;
|
|||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
MultiCastAddressMaker &MultiCastAddressMaker::Instance() {
|
MultiCastAddressMaker &MultiCastAddressMaker::Instance() {
|
||||||
static MultiCastAddressMaker instance;
|
static MultiCastAddressMaker instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t addressToInt(const string &ip){
|
static uint32_t addressToInt(const string &ip){
|
||||||
struct in_addr addr;
|
struct in_addr addr;
|
||||||
bzero(&addr,sizeof(addr));
|
bzero(&addr,sizeof(addr));
|
||||||
addr.s_addr = inet_addr(ip.data());
|
addr.s_addr = inet_addr(ip.data());
|
||||||
return (uint32_t)ntohl((uint32_t &)addr.s_addr);
|
return (uint32_t)ntohl((uint32_t &)addr.s_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<uint32_t> MultiCastAddressMaker::obtain(uint32_t iTry) {
|
std::shared_ptr<uint32_t> MultiCastAddressMaker::obtain(uint32_t iTry) {
|
||||||
lock_guard<recursive_mutex> lck(_mtx);
|
lock_guard<recursive_mutex> lck(_mtx);
|
||||||
GET_CONFIG(string,addrMinStr,MultiCast::kAddrMin);
|
GET_CONFIG(string,addrMinStr,MultiCast::kAddrMin);
|
||||||
GET_CONFIG(string,addrMaxStr,MultiCast::kAddrMax);
|
GET_CONFIG(string,addrMaxStr,MultiCast::kAddrMax);
|
||||||
uint32_t addrMin = addressToInt(addrMinStr);
|
uint32_t addrMin = addressToInt(addrMinStr);
|
||||||
uint32_t addrMax = addressToInt(addrMaxStr);
|
uint32_t addrMax = addressToInt(addrMaxStr);
|
||||||
|
|
||||||
if(_iAddr > addrMax || _iAddr == 0){
|
if(_iAddr > addrMax || _iAddr == 0){
|
||||||
_iAddr = addrMin;
|
_iAddr = addrMin;
|
||||||
}
|
}
|
||||||
auto iGotAddr = _iAddr++;
|
auto iGotAddr = _iAddr++;
|
||||||
if(_setBadAddr.find(iGotAddr) != _setBadAddr.end()){
|
if(_setBadAddr.find(iGotAddr) != _setBadAddr.end()){
|
||||||
//已经分配过了
|
//已经分配过了
|
||||||
if(iTry){
|
if(iTry){
|
||||||
return obtain(--iTry);
|
return obtain(--iTry);
|
||||||
}
|
}
|
||||||
//分配完了,应该不可能到这里
|
//分配完了,应该不可能到这里
|
||||||
ErrorL;
|
ErrorL;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
_setBadAddr.emplace(iGotAddr);
|
_setBadAddr.emplace(iGotAddr);
|
||||||
std::shared_ptr<uint32_t> ret(new uint32_t(iGotAddr),[](uint32_t *ptr){
|
std::shared_ptr<uint32_t> ret(new uint32_t(iGotAddr),[](uint32_t *ptr){
|
||||||
MultiCastAddressMaker::Instance().release(*ptr);
|
MultiCastAddressMaker::Instance().release(*ptr);
|
||||||
delete ptr;
|
delete ptr;
|
||||||
});
|
});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
void MultiCastAddressMaker::release(uint32_t iAddr){
|
void MultiCastAddressMaker::release(uint32_t iAddr){
|
||||||
lock_guard<recursive_mutex> lck(_mtx);
|
lock_guard<recursive_mutex> lck(_mtx);
|
||||||
_setBadAddr.erase(iAddr);
|
_setBadAddr.erase(iAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -85,106 +85,106 @@ recursive_mutex RtpMultiCaster::g_mtx;
|
|||||||
unordered_map<string, weak_ptr<RtpMultiCaster> > RtpMultiCaster::g_mapBroadCaster;
|
unordered_map<string, weak_ptr<RtpMultiCaster> > RtpMultiCaster::g_mapBroadCaster;
|
||||||
|
|
||||||
void RtpMultiCaster::setDetachCB(void* listener, const onDetach& cb) {
|
void RtpMultiCaster::setDetachCB(void* listener, const onDetach& cb) {
|
||||||
lock_guard<recursive_mutex> lck(_mtx);
|
lock_guard<recursive_mutex> lck(_mtx);
|
||||||
if(cb){
|
if(cb){
|
||||||
_mapDetach.emplace(listener,cb);
|
_mapDetach.emplace(listener,cb);
|
||||||
}else{
|
}else{
|
||||||
_mapDetach.erase(listener);
|
_mapDetach.erase(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RtpMultiCaster::~RtpMultiCaster() {
|
RtpMultiCaster::~RtpMultiCaster() {
|
||||||
_pReader->setReadCB(nullptr);
|
_pReader->setReadCB(nullptr);
|
||||||
_pReader->setDetachCB(nullptr);
|
_pReader->setDetachCB(nullptr);
|
||||||
DebugL;
|
DebugL;
|
||||||
}
|
}
|
||||||
RtpMultiCaster::RtpMultiCaster(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream) {
|
RtpMultiCaster::RtpMultiCaster(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream) {
|
||||||
auto src = dynamic_pointer_cast<RtspMediaSource>(MediaSource::find(RTSP_SCHEMA,strVhost,strApp, strStream));
|
auto src = dynamic_pointer_cast<RtspMediaSource>(MediaSource::find(RTSP_SCHEMA,strVhost,strApp, strStream));
|
||||||
if(!src){
|
if(!src){
|
||||||
auto strErr = StrPrinter << "未找到媒体源:" << strVhost << " " << strApp << " " << strStream << endl;
|
auto strErr = StrPrinter << "未找到媒体源:" << strVhost << " " << strApp << " " << strStream << endl;
|
||||||
throw std::runtime_error(strErr);
|
throw std::runtime_error(strErr);
|
||||||
}
|
}
|
||||||
_multiAddr = MultiCastAddressMaker::Instance().obtain();
|
_multiAddr = MultiCastAddressMaker::Instance().obtain();
|
||||||
for(auto i = 0; i < 2; i++){
|
for(auto i = 0; i < 2; i++){
|
||||||
_apUdpSock[i].reset(new Socket(poller));
|
_apUdpSock[i].reset(new Socket(poller));
|
||||||
if(!_apUdpSock[i]->bindUdpSock(0, strLocalIp.data())){
|
if(!_apUdpSock[i]->bindUdpSock(0, strLocalIp.data())){
|
||||||
auto strErr = StrPrinter << "绑定UDP端口失败:" << strLocalIp << endl;
|
auto strErr = StrPrinter << "绑定UDP端口失败:" << strLocalIp << endl;
|
||||||
throw std::runtime_error(strErr);
|
throw std::runtime_error(strErr);
|
||||||
}
|
}
|
||||||
auto fd = _apUdpSock[i]->rawFD();
|
auto fd = _apUdpSock[i]->rawFD();
|
||||||
GET_CONFIG(uint32_t,udpTTL,MultiCast::kUdpTTL);
|
GET_CONFIG(uint32_t,udpTTL,MultiCast::kUdpTTL);
|
||||||
|
|
||||||
SockUtil::setMultiTTL(fd, udpTTL);
|
SockUtil::setMultiTTL(fd, udpTTL);
|
||||||
SockUtil::setMultiLOOP(fd, false);
|
SockUtil::setMultiLOOP(fd, false);
|
||||||
SockUtil::setMultiIF(fd, strLocalIp.data());
|
SockUtil::setMultiIF(fd, strLocalIp.data());
|
||||||
|
|
||||||
struct sockaddr_in &peerAddr = _aPeerUdpAddr[i];
|
struct sockaddr_in &peerAddr = _aPeerUdpAddr[i];
|
||||||
peerAddr.sin_family = AF_INET;
|
peerAddr.sin_family = AF_INET;
|
||||||
peerAddr.sin_port = htons(_apUdpSock[i]->get_local_port());
|
peerAddr.sin_port = htons(_apUdpSock[i]->get_local_port());
|
||||||
peerAddr.sin_addr.s_addr = htonl(*_multiAddr);
|
peerAddr.sin_addr.s_addr = htonl(*_multiAddr);
|
||||||
bzero(&(peerAddr.sin_zero), sizeof peerAddr.sin_zero);
|
bzero(&(peerAddr.sin_zero), sizeof peerAddr.sin_zero);
|
||||||
_apUdpSock[i]->setSendPeerAddr((struct sockaddr *)&peerAddr);
|
_apUdpSock[i]->setSendPeerAddr((struct sockaddr *)&peerAddr);
|
||||||
}
|
}
|
||||||
_pReader = src->getRing()->attach(poller);
|
_pReader = src->getRing()->attach(poller);
|
||||||
_pReader->setReadCB([this](const RtpPacket::Ptr &pkt){
|
_pReader->setReadCB([this](const RtpPacket::Ptr &pkt){
|
||||||
int i = (int)(pkt->type);
|
int i = (int)(pkt->type);
|
||||||
auto &pSock = _apUdpSock[i];
|
auto &pSock = _apUdpSock[i];
|
||||||
auto &peerAddr = _aPeerUdpAddr[i];
|
auto &peerAddr = _aPeerUdpAddr[i];
|
||||||
BufferRtp::Ptr buffer(new BufferRtp(pkt,4));
|
BufferRtp::Ptr buffer(new BufferRtp(pkt,4));
|
||||||
pSock->send(buffer);
|
pSock->send(buffer);
|
||||||
});
|
});
|
||||||
_pReader->setDetachCB([this](){
|
_pReader->setDetachCB([this](){
|
||||||
unordered_map<void * , onDetach > _mapDetach_copy;
|
unordered_map<void * , onDetach > _mapDetach_copy;
|
||||||
{
|
{
|
||||||
lock_guard<recursive_mutex> lck(_mtx);
|
lock_guard<recursive_mutex> lck(_mtx);
|
||||||
_mapDetach_copy = std::move(_mapDetach);
|
_mapDetach_copy = std::move(_mapDetach);
|
||||||
}
|
}
|
||||||
for(auto &pr : _mapDetach_copy){
|
for(auto &pr : _mapDetach_copy){
|
||||||
pr.second();
|
pr.second();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
DebugL << MultiCastAddressMaker::toString(*_multiAddr) << " "
|
DebugL << MultiCastAddressMaker::toString(*_multiAddr) << " "
|
||||||
<< _apUdpSock[0]->get_local_port() << " "
|
<< _apUdpSock[0]->get_local_port() << " "
|
||||||
<< _apUdpSock[1]->get_local_port() << " "
|
<< _apUdpSock[1]->get_local_port() << " "
|
||||||
<< strVhost << " "
|
<< strVhost << " "
|
||||||
<< strApp << " " << strStream;
|
<< strApp << " " << strStream;
|
||||||
}
|
}
|
||||||
uint16_t RtpMultiCaster::getPort(TrackType trackType){
|
uint16_t RtpMultiCaster::getPort(TrackType trackType){
|
||||||
return _apUdpSock[trackType]->get_local_port();
|
return _apUdpSock[trackType]->get_local_port();
|
||||||
}
|
}
|
||||||
string RtpMultiCaster::getIP(){
|
string RtpMultiCaster::getIP(){
|
||||||
return inet_ntoa(_aPeerUdpAddr[0].sin_addr);
|
return inet_ntoa(_aPeerUdpAddr[0].sin_addr);
|
||||||
}
|
}
|
||||||
RtpMultiCaster::Ptr RtpMultiCaster::make(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream){
|
RtpMultiCaster::Ptr RtpMultiCaster::make(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream){
|
||||||
try{
|
try{
|
||||||
auto ret = Ptr(new RtpMultiCaster(poller,strLocalIp,strVhost,strApp,strStream),[poller](RtpMultiCaster *ptr){
|
auto ret = Ptr(new RtpMultiCaster(poller,strLocalIp,strVhost,strApp,strStream),[poller](RtpMultiCaster *ptr){
|
||||||
poller->async([ptr]() {
|
poller->async([ptr]() {
|
||||||
delete ptr;
|
delete ptr;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
lock_guard<recursive_mutex> lck(g_mtx);
|
lock_guard<recursive_mutex> lck(g_mtx);
|
||||||
string strKey = StrPrinter << strLocalIp << " " << strVhost << " " << strApp << " " << strStream << endl;
|
string strKey = StrPrinter << strLocalIp << " " << strVhost << " " << strApp << " " << strStream << endl;
|
||||||
weak_ptr<RtpMultiCaster> weakPtr = ret;
|
weak_ptr<RtpMultiCaster> weakPtr = ret;
|
||||||
g_mapBroadCaster.emplace(strKey,weakPtr);
|
g_mapBroadCaster.emplace(strKey,weakPtr);
|
||||||
return ret;
|
return ret;
|
||||||
}catch (std::exception &ex) {
|
}catch (std::exception &ex) {
|
||||||
WarnL << ex.what();
|
WarnL << ex.what();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RtpMultiCaster::Ptr RtpMultiCaster::get(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream) {
|
RtpMultiCaster::Ptr RtpMultiCaster::get(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream) {
|
||||||
string strKey = StrPrinter << strLocalIp << " " << strVhost << " " << strApp << " " << strStream << endl;
|
string strKey = StrPrinter << strLocalIp << " " << strVhost << " " << strApp << " " << strStream << endl;
|
||||||
lock_guard<recursive_mutex> lck(g_mtx);
|
lock_guard<recursive_mutex> lck(g_mtx);
|
||||||
auto it = g_mapBroadCaster.find(strKey);
|
auto it = g_mapBroadCaster.find(strKey);
|
||||||
if (it == g_mapBroadCaster.end()) {
|
if (it == g_mapBroadCaster.end()) {
|
||||||
return make(poller,strLocalIp,strVhost,strApp, strStream);
|
return make(poller,strLocalIp,strVhost,strApp, strStream);
|
||||||
}
|
}
|
||||||
auto ret = it->second.lock();
|
auto ret = it->second.lock();
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
g_mapBroadCaster.erase(it);
|
g_mapBroadCaster.erase(it);
|
||||||
return make(poller,strLocalIp,strVhost,strApp, strStream);
|
return make(poller,strLocalIp,strVhost,strApp, strStream);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,48 +45,48 @@ namespace mediakit{
|
|||||||
class MultiCastAddressMaker
|
class MultiCastAddressMaker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static MultiCastAddressMaker &Instance();
|
static MultiCastAddressMaker &Instance();
|
||||||
|
|
||||||
static bool isMultiCastAddress(uint32_t iAddr){
|
static bool isMultiCastAddress(uint32_t iAddr){
|
||||||
static uint32_t addrMin = mINI::Instance()[MultiCast::kAddrMin].as<uint32_t>();
|
static uint32_t addrMin = mINI::Instance()[MultiCast::kAddrMin].as<uint32_t>();
|
||||||
static uint32_t addrMax = mINI::Instance()[MultiCast::kAddrMax].as<uint32_t>();
|
static uint32_t addrMax = mINI::Instance()[MultiCast::kAddrMax].as<uint32_t>();
|
||||||
return iAddr >= addrMin && iAddr <= addrMax;
|
return iAddr >= addrMin && iAddr <= addrMax;
|
||||||
}
|
}
|
||||||
static string toString(uint32_t iAddr){
|
static string toString(uint32_t iAddr){
|
||||||
iAddr = htonl(iAddr);
|
iAddr = htonl(iAddr);
|
||||||
return ::inet_ntoa((struct in_addr &)(iAddr));
|
return ::inet_ntoa((struct in_addr &)(iAddr));
|
||||||
}
|
}
|
||||||
virtual ~MultiCastAddressMaker(){}
|
virtual ~MultiCastAddressMaker(){}
|
||||||
std::shared_ptr<uint32_t> obtain(uint32_t iTry = 10);
|
std::shared_ptr<uint32_t> obtain(uint32_t iTry = 10);
|
||||||
private:
|
private:
|
||||||
MultiCastAddressMaker(){};
|
MultiCastAddressMaker(){};
|
||||||
void release(uint32_t iAddr);
|
void release(uint32_t iAddr);
|
||||||
uint32_t _iAddr = 0;
|
uint32_t _iAddr = 0;
|
||||||
recursive_mutex _mtx;
|
recursive_mutex _mtx;
|
||||||
unordered_set<uint32_t> _setBadAddr;
|
unordered_set<uint32_t> _setBadAddr;
|
||||||
};
|
};
|
||||||
class RtpMultiCaster {
|
class RtpMultiCaster {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtpMultiCaster> Ptr;
|
typedef std::shared_ptr<RtpMultiCaster> Ptr;
|
||||||
typedef function<void()> onDetach;
|
typedef function<void()> onDetach;
|
||||||
virtual ~RtpMultiCaster();
|
virtual ~RtpMultiCaster();
|
||||||
static Ptr get(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream);
|
static Ptr get(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream);
|
||||||
void setDetachCB(void *listener,const onDetach &cb);
|
void setDetachCB(void *listener,const onDetach &cb);
|
||||||
uint16_t getPort(TrackType trackType);
|
uint16_t getPort(TrackType trackType);
|
||||||
string getIP();
|
string getIP();
|
||||||
private:
|
private:
|
||||||
static recursive_mutex g_mtx;
|
static recursive_mutex g_mtx;
|
||||||
static unordered_map<string , weak_ptr<RtpMultiCaster> > g_mapBroadCaster;
|
static unordered_map<string , weak_ptr<RtpMultiCaster> > g_mapBroadCaster;
|
||||||
static Ptr make(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream);
|
static Ptr make(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream);
|
||||||
|
|
||||||
std::shared_ptr<uint32_t> _multiAddr;
|
std::shared_ptr<uint32_t> _multiAddr;
|
||||||
recursive_mutex _mtx;
|
recursive_mutex _mtx;
|
||||||
unordered_map<void * , onDetach > _mapDetach;
|
unordered_map<void * , onDetach > _mapDetach;
|
||||||
RtspMediaSource::RingType::RingReader::Ptr _pReader;
|
RtspMediaSource::RingType::RingReader::Ptr _pReader;
|
||||||
Socket::Ptr _apUdpSock[2];
|
Socket::Ptr _apUdpSock[2];
|
||||||
struct sockaddr_in _aPeerUdpAddr[2];
|
struct sockaddr_in _aPeerUdpAddr[2];
|
||||||
|
|
||||||
RtpMultiCaster(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream);
|
RtpMultiCaster(const EventPoller::Ptr &poller,const string &strLocalIp,const string &strVhost,const string &strApp,const string &strStream);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,9 +28,9 @@
|
|||||||
#include "RtpReceiver.h"
|
#include "RtpReceiver.h"
|
||||||
|
|
||||||
#define POP_HEAD(trackidx) \
|
#define POP_HEAD(trackidx) \
|
||||||
auto it = _rtp_sort_cache_map[trackidx].begin(); \
|
auto it = _rtp_sort_cache_map[trackidx].begin(); \
|
||||||
onRtpSorted(it->second, trackidx); \
|
onRtpSorted(it->second, trackidx); \
|
||||||
_rtp_sort_cache_map[trackidx].erase(it);
|
_rtp_sort_cache_map[trackidx].erase(it);
|
||||||
|
|
||||||
#define AV_RB16(x) \
|
#define AV_RB16(x) \
|
||||||
((((const uint8_t*)(x))[0] << 8) | \
|
((((const uint8_t*)(x))[0] << 8) | \
|
||||||
|
@ -31,176 +31,176 @@
|
|||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
static void getAttrSdp(const map<string, string> &attr, _StrPrinter &printer){
|
static void getAttrSdp(const map<string, string> &attr, _StrPrinter &printer){
|
||||||
const map<string, string>::value_type *ptr = nullptr;
|
const map<string, string>::value_type *ptr = nullptr;
|
||||||
for(auto &pr : attr){
|
for(auto &pr : attr){
|
||||||
if(pr.first == "control"){
|
if(pr.first == "control"){
|
||||||
ptr = ≺
|
ptr = ≺
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(pr.second.empty()){
|
if(pr.second.empty()){
|
||||||
printer << "a=" << pr.first << "\r\n";
|
printer << "a=" << pr.first << "\r\n";
|
||||||
}else{
|
}else{
|
||||||
printer << "a=" << pr.first << ":" << pr.second << "\r\n";
|
printer << "a=" << pr.first << ":" << pr.second << "\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(ptr){
|
if(ptr){
|
||||||
printer << "a=" << ptr->first << ":" << ptr->second << "\r\n";
|
printer << "a=" << ptr->first << ":" << ptr->second << "\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string SdpTrack::toString() const {
|
string SdpTrack::toString() const {
|
||||||
_StrPrinter _printer;
|
_StrPrinter _printer;
|
||||||
switch (_type){
|
switch (_type){
|
||||||
case TrackTitle:{
|
case TrackTitle:{
|
||||||
_printer << "v=" << 0 << "\r\n";
|
_printer << "v=" << 0 << "\r\n";
|
||||||
if(!_o.empty()){
|
if(!_o.empty()){
|
||||||
_printer << "o="<< _o << "\r\n";
|
_printer << "o="<< _o << "\r\n";
|
||||||
}
|
}
|
||||||
if(!_c.empty()){
|
if(!_c.empty()){
|
||||||
_printer << "c=" << _c << "\r\n";
|
_printer << "c=" << _c << "\r\n";
|
||||||
}
|
}
|
||||||
if(!_t.empty()){
|
if(!_t.empty()){
|
||||||
_printer << "t=" << _t << "\r\n";
|
_printer << "t=" << _t << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
_printer << "s=RTSP Session, streamed by the ZLMediaKit\r\n";
|
_printer << "s=RTSP Session, streamed by the ZLMediaKit\r\n";
|
||||||
_printer << "i=ZLMediaKit Live Stream\r\n";
|
_printer << "i=ZLMediaKit Live Stream\r\n";
|
||||||
getAttrSdp(_attr,_printer);
|
getAttrSdp(_attr,_printer);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TrackAudio:
|
case TrackAudio:
|
||||||
case TrackVideo:{
|
case TrackVideo:{
|
||||||
if(_type == TrackAudio){
|
if(_type == TrackAudio){
|
||||||
_printer << "m=audio 0 RTP/AVP " << _pt << "\r\n";
|
_printer << "m=audio 0 RTP/AVP " << _pt << "\r\n";
|
||||||
}else{
|
}else{
|
||||||
_printer << "m=video 0 RTP/AVP " << _pt << "\r\n";
|
_printer << "m=video 0 RTP/AVP " << _pt << "\r\n";
|
||||||
}
|
}
|
||||||
if(!_b.empty()){
|
if(!_b.empty()){
|
||||||
_printer << "b=" <<_b << "\r\n";
|
_printer << "b=" <<_b << "\r\n";
|
||||||
}
|
}
|
||||||
getAttrSdp(_attr,_printer);
|
getAttrSdp(_attr,_printer);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return _printer;
|
return _printer;
|
||||||
}
|
}
|
||||||
|
|
||||||
static TrackType toTrackType(const string &str) {
|
static TrackType toTrackType(const string &str) {
|
||||||
if (str == "") {
|
if (str == "") {
|
||||||
return TrackTitle;
|
return TrackTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str == "video") {
|
if (str == "video") {
|
||||||
return TrackVideo;
|
return TrackVideo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str == "audio") {
|
if (str == "audio") {
|
||||||
return TrackAudio;
|
return TrackAudio;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TrackInvalid;
|
return TrackInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdpParser::load(const string &sdp) {
|
void SdpParser::load(const string &sdp) {
|
||||||
{
|
{
|
||||||
_track_vec.clear();
|
_track_vec.clear();
|
||||||
string key;
|
string key;
|
||||||
SdpTrack::Ptr track = std::make_shared<SdpTrack>();
|
SdpTrack::Ptr track = std::make_shared<SdpTrack>();
|
||||||
|
|
||||||
auto lines = split(sdp, "\n");
|
auto lines = split(sdp, "\n");
|
||||||
for (auto &line : lines) {
|
for (auto &line : lines) {
|
||||||
trim(line);
|
trim(line);
|
||||||
if (line.size() < 2 || line[1] != '=') {
|
if (line.size() < 2 || line[1] != '=') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
char opt = line[0];
|
char opt = line[0];
|
||||||
string opt_val = line.substr(2);
|
string opt_val = line.substr(2);
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'o':
|
case 'o':
|
||||||
track->_o = opt_val;
|
track->_o = opt_val;
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
track->_s = opt_val;
|
track->_s = opt_val;
|
||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
track->_i = opt_val;
|
track->_i = opt_val;
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
track->_c = opt_val;
|
track->_c = opt_val;
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
track->_t = opt_val;
|
track->_t = opt_val;
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
track->_b = opt_val;
|
track->_b = opt_val;
|
||||||
break;
|
break;
|
||||||
case 'm': {
|
case 'm': {
|
||||||
track->_type = toTrackType(key);
|
track->_type = toTrackType(key);
|
||||||
_track_vec.emplace_back(track);
|
_track_vec.emplace_back(track);
|
||||||
track = std::make_shared<SdpTrack>();
|
track = std::make_shared<SdpTrack>();
|
||||||
key = FindField(opt_val.data(), nullptr, " ");
|
key = FindField(opt_val.data(), nullptr, " ");
|
||||||
track->_m = opt_val;
|
track->_m = opt_val;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'a': {
|
case 'a': {
|
||||||
string attr = FindField(opt_val.data(), nullptr, ":");
|
string attr = FindField(opt_val.data(), nullptr, ":");
|
||||||
if (attr.empty()) {
|
if (attr.empty()) {
|
||||||
track->_attr[opt_val] = "";
|
track->_attr[opt_val] = "";
|
||||||
} else {
|
} else {
|
||||||
track->_attr[attr] = FindField(opt_val.data(), ":", nullptr);
|
track->_attr[attr] = FindField(opt_val.data(), ":", nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
track->_other[opt] = opt_val;
|
track->_other[opt] = opt_val;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
track->_type = toTrackType(key);
|
track->_type = toTrackType(key);
|
||||||
_track_vec.emplace_back(track);
|
_track_vec.emplace_back(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &track_ptr : _track_vec) {
|
for (auto &track_ptr : _track_vec) {
|
||||||
auto &track = *track_ptr;
|
auto &track = *track_ptr;
|
||||||
auto it = track._attr.find("range");
|
auto it = track._attr.find("range");
|
||||||
if (it != track._attr.end()) {
|
if (it != track._attr.end()) {
|
||||||
char name[16] = {0}, start[16] = {0}, end[16] = {0};
|
char name[16] = {0}, start[16] = {0}, end[16] = {0};
|
||||||
int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end);
|
int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end);
|
||||||
if (3 == ret || 2 == ret) {
|
if (3 == ret || 2 == ret) {
|
||||||
if (strcmp(start, "now") == 0) {
|
if (strcmp(start, "now") == 0) {
|
||||||
strcpy(start, "0");
|
strcpy(start, "0");
|
||||||
}
|
}
|
||||||
track._start = atof(start);
|
track._start = atof(start);
|
||||||
track._end = atof(end);
|
track._end = atof(end);
|
||||||
track._duration = track._end - track._start;
|
track._duration = track._end - track._start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it = track._attr.find("rtpmap");
|
it = track._attr.find("rtpmap");
|
||||||
if(it != track._attr.end()){
|
if(it != track._attr.end()){
|
||||||
auto rtpmap = it->second;
|
auto rtpmap = it->second;
|
||||||
int pt, samplerate;
|
int pt, samplerate;
|
||||||
char codec[16] = {0};
|
char codec[16] = {0};
|
||||||
if (3 == sscanf(rtpmap.data(), "%d %15[^/]/%d", &pt, codec, &samplerate)) {
|
if (3 == sscanf(rtpmap.data(), "%d %15[^/]/%d", &pt, codec, &samplerate)) {
|
||||||
track._pt = pt;
|
track._pt = pt;
|
||||||
track._codec = codec;
|
track._codec = codec;
|
||||||
track._samplerate = samplerate;
|
track._samplerate = samplerate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it = track._attr.find("fmtp");
|
it = track._attr.find("fmtp");
|
||||||
if(it != track._attr.end()) {
|
if(it != track._attr.end()) {
|
||||||
track._fmtp = it->second;
|
track._fmtp = it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
it = track._attr.find("control");
|
it = track._attr.find("control");
|
||||||
if(it != track._attr.end()) {
|
if(it != track._attr.end()) {
|
||||||
track._control = it->second;
|
track._control = it->second;
|
||||||
auto surffix = string("/") + track._control;
|
auto surffix = string("/") + track._control;
|
||||||
track._control_surffix = surffix.substr(1 + surffix.rfind('/'));
|
track._control_surffix = surffix.substr(1 + surffix.rfind('/'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SdpParser::available() const {
|
bool SdpParser::available() const {
|
||||||
@ -208,112 +208,112 @@ bool SdpParser::available() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SdpTrack::Ptr SdpParser::getTrack(TrackType type) const {
|
SdpTrack::Ptr SdpParser::getTrack(TrackType type) const {
|
||||||
for (auto &track : _track_vec){
|
for (auto &track : _track_vec){
|
||||||
if(track->_type == type){
|
if(track->_type == type){
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<SdpTrack::Ptr> SdpParser::getAvailableTrack() const {
|
vector<SdpTrack::Ptr> SdpParser::getAvailableTrack() const {
|
||||||
vector<SdpTrack::Ptr> ret;
|
vector<SdpTrack::Ptr> ret;
|
||||||
bool audio_added = false;
|
bool audio_added = false;
|
||||||
bool video_added = false;
|
bool video_added = false;
|
||||||
for (auto &track : _track_vec){
|
for (auto &track : _track_vec){
|
||||||
if(track->_type == TrackAudio ){
|
if(track->_type == TrackAudio ){
|
||||||
if(!audio_added){
|
if(!audio_added){
|
||||||
ret.emplace_back(track);
|
ret.emplace_back(track);
|
||||||
audio_added = true;
|
audio_added = true;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(track->_type == TrackVideo ){
|
if(track->_type == TrackVideo ){
|
||||||
if(!video_added){
|
if(!video_added){
|
||||||
ret.emplace_back(track);
|
ret.emplace_back(track);
|
||||||
video_added = true;
|
video_added = true;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return std::move(ret);
|
return std::move(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
string SdpParser::toString() const {
|
string SdpParser::toString() const {
|
||||||
string title,audio,video;
|
string title,audio,video;
|
||||||
for(auto &track : _track_vec){
|
for(auto &track : _track_vec){
|
||||||
switch (track->_type){
|
switch (track->_type){
|
||||||
case TrackTitle:{
|
case TrackTitle:{
|
||||||
title = track->toString();
|
title = track->toString();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TrackVideo:{
|
case TrackVideo:{
|
||||||
video = track->toString();
|
video = track->toString();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TrackAudio:{
|
case TrackAudio:{
|
||||||
audio = track->toString();
|
audio = track->toString();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return title + video + audio;
|
return title + video + audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtspUrl::parse(const string &strUrl) {
|
bool RtspUrl::parse(const string &strUrl) {
|
||||||
auto schema = FindField(strUrl.data(), nullptr, "://");
|
auto schema = FindField(strUrl.data(), nullptr, "://");
|
||||||
bool isSSL = strcasecmp(schema.data(), "rtsps") == 0;
|
bool isSSL = strcasecmp(schema.data(), "rtsps") == 0;
|
||||||
//查找"://"与"/"之间的字符串,用于提取用户名密码
|
//查找"://"与"/"之间的字符串,用于提取用户名密码
|
||||||
auto middle_url = FindField(strUrl.data(), "://", "/");
|
auto middle_url = FindField(strUrl.data(), "://", "/");
|
||||||
if (middle_url.empty()) {
|
if (middle_url.empty()) {
|
||||||
middle_url = FindField(strUrl.data(), "://", nullptr);
|
middle_url = FindField(strUrl.data(), "://", nullptr);
|
||||||
}
|
}
|
||||||
auto pos = middle_url.rfind('@');
|
auto pos = middle_url.rfind('@');
|
||||||
if (pos == string::npos) {
|
if (pos == string::npos) {
|
||||||
//并没有用户名密码
|
//并没有用户名密码
|
||||||
return setup(isSSL, strUrl, "", "");
|
return setup(isSSL, strUrl, "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
//包含用户名密码
|
//包含用户名密码
|
||||||
auto user_pwd = middle_url.substr(0, pos);
|
auto user_pwd = middle_url.substr(0, pos);
|
||||||
auto suffix = strUrl.substr(schema.size() + 3 + pos + 1);
|
auto suffix = strUrl.substr(schema.size() + 3 + pos + 1);
|
||||||
auto url = StrPrinter << "rtsp://" << suffix << endl;
|
auto url = StrPrinter << "rtsp://" << suffix << endl;
|
||||||
if (user_pwd.find(":") == string::npos) {
|
if (user_pwd.find(":") == string::npos) {
|
||||||
return setup(isSSL, url, user_pwd, "");
|
return setup(isSSL, url, user_pwd, "");
|
||||||
}
|
}
|
||||||
auto user = FindField(user_pwd.data(), nullptr, ":");
|
auto user = FindField(user_pwd.data(), nullptr, ":");
|
||||||
auto pwd = FindField(user_pwd.data(), ":", nullptr);
|
auto pwd = FindField(user_pwd.data(), ":", nullptr);
|
||||||
return setup(isSSL, url, user, pwd);
|
return setup(isSSL, url, user, pwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtspUrl::setup(bool isSSL, const string &strUrl, const string &strUser, const string &strPwd) {
|
bool RtspUrl::setup(bool isSSL, const string &strUrl, const string &strUser, const string &strPwd) {
|
||||||
auto ip = FindField(strUrl.data(), "://", "/");
|
auto ip = FindField(strUrl.data(), "://", "/");
|
||||||
if (ip.empty()) {
|
if (ip.empty()) {
|
||||||
ip = split(FindField(strUrl.data(), "://", NULL), "?")[0];
|
ip = split(FindField(strUrl.data(), "://", NULL), "?")[0];
|
||||||
}
|
}
|
||||||
auto port = atoi(FindField(ip.data(), ":", NULL).data());
|
auto port = atoi(FindField(ip.data(), ":", NULL).data());
|
||||||
if (port <= 0 || port > UINT16_MAX) {
|
if (port <= 0 || port > UINT16_MAX) {
|
||||||
//rtsp 默认端口554
|
//rtsp 默认端口554
|
||||||
port = isSSL ? 322 : 554;
|
port = isSSL ? 322 : 554;
|
||||||
} else {
|
} else {
|
||||||
//服务器域名
|
//服务器域名
|
||||||
ip = FindField(ip.data(), NULL, ":");
|
ip = FindField(ip.data(), NULL, ":");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ip.empty()) {
|
if (ip.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_url = std::move(strUrl);
|
_url = std::move(strUrl);
|
||||||
_user = std::move(strUser);
|
_user = std::move(strUser);
|
||||||
_passwd = std::move(strPwd);
|
_passwd = std::move(strPwd);
|
||||||
_host = std::move(ip);
|
_host = std::move(ip);
|
||||||
_port = port;
|
_port = port;
|
||||||
_is_ssl = isSSL;
|
_is_ssl = isSSL;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
288
src/Rtsp/Rtsp.h
288
src/Rtsp/Rtsp.h
@ -42,87 +42,87 @@ namespace mediakit {
|
|||||||
|
|
||||||
namespace Rtsp {
|
namespace Rtsp {
|
||||||
typedef enum {
|
typedef enum {
|
||||||
RTP_Invalid = -1,
|
RTP_Invalid = -1,
|
||||||
RTP_TCP = 0,
|
RTP_TCP = 0,
|
||||||
RTP_UDP = 1,
|
RTP_UDP = 1,
|
||||||
RTP_MULTICAST = 2,
|
RTP_MULTICAST = 2,
|
||||||
} eRtpType;
|
} eRtpType;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RtpPacket : public BufferRaw{
|
class RtpPacket : public BufferRaw{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtpPacket> Ptr;
|
typedef std::shared_ptr<RtpPacket> Ptr;
|
||||||
uint8_t interleaved;
|
uint8_t interleaved;
|
||||||
uint8_t PT;
|
uint8_t PT;
|
||||||
bool mark;
|
bool mark;
|
||||||
//时间戳,单位毫秒
|
//时间戳,单位毫秒
|
||||||
uint32_t timeStamp;
|
uint32_t timeStamp;
|
||||||
uint16_t sequence;
|
uint16_t sequence;
|
||||||
uint32_t ssrc;
|
uint32_t ssrc;
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
TrackType type;
|
TrackType type;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RtcpCounter {
|
class RtcpCounter {
|
||||||
public:
|
public:
|
||||||
uint32_t pktCnt = 0;
|
uint32_t pktCnt = 0;
|
||||||
uint32_t octCount = 0;
|
uint32_t octCount = 0;
|
||||||
//网络字节序
|
//网络字节序
|
||||||
uint32_t timeStamp = 0;
|
uint32_t timeStamp = 0;
|
||||||
uint32_t lastTimeStamp = 0;
|
uint32_t lastTimeStamp = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SdpTrack {
|
class SdpTrack {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<SdpTrack> Ptr;
|
typedef std::shared_ptr<SdpTrack> Ptr;
|
||||||
|
|
||||||
string _m;
|
string _m;
|
||||||
string _o;
|
string _o;
|
||||||
string _s;
|
string _s;
|
||||||
string _i;
|
string _i;
|
||||||
string _c;
|
string _c;
|
||||||
string _t;
|
string _t;
|
||||||
string _b;
|
string _b;
|
||||||
|
|
||||||
float _duration = 0;
|
float _duration = 0;
|
||||||
float _start = 0;
|
float _start = 0;
|
||||||
float _end = 0;
|
float _end = 0;
|
||||||
|
|
||||||
map<char, string> _other;
|
map<char, string> _other;
|
||||||
map<string, string> _attr;
|
map<string, string> _attr;
|
||||||
|
|
||||||
string toString() const;
|
string toString() const;
|
||||||
public:
|
public:
|
||||||
int _pt;
|
int _pt;
|
||||||
string _codec;
|
string _codec;
|
||||||
int _samplerate;
|
int _samplerate;
|
||||||
string _fmtp;
|
string _fmtp;
|
||||||
string _control;
|
string _control;
|
||||||
string _control_surffix;
|
string _control_surffix;
|
||||||
TrackType _type;
|
TrackType _type;
|
||||||
public:
|
public:
|
||||||
uint8_t _interleaved = 0;
|
uint8_t _interleaved = 0;
|
||||||
bool _inited = false;
|
bool _inited = false;
|
||||||
uint32_t _ssrc = 0;
|
uint32_t _ssrc = 0;
|
||||||
uint16_t _seq = 0;
|
uint16_t _seq = 0;
|
||||||
//时间戳,单位毫秒
|
//时间戳,单位毫秒
|
||||||
uint32_t _time_stamp = 0;
|
uint32_t _time_stamp = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SdpParser {
|
class SdpParser {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<SdpParser> Ptr;
|
typedef std::shared_ptr<SdpParser> Ptr;
|
||||||
|
|
||||||
SdpParser() {}
|
SdpParser() {}
|
||||||
SdpParser(const string &sdp) { load(sdp); }
|
SdpParser(const string &sdp) { load(sdp); }
|
||||||
~SdpParser() {}
|
~SdpParser() {}
|
||||||
void load(const string &sdp);
|
void load(const string &sdp);
|
||||||
bool available() const;
|
bool available() const;
|
||||||
SdpTrack::Ptr getTrack(TrackType type) const;
|
SdpTrack::Ptr getTrack(TrackType type) const;
|
||||||
vector<SdpTrack::Ptr> getAvailableTrack() const;
|
vector<SdpTrack::Ptr> getAvailableTrack() const;
|
||||||
string toString() const ;
|
string toString() const ;
|
||||||
private:
|
private:
|
||||||
vector<SdpTrack::Ptr> _track_vec;
|
vector<SdpTrack::Ptr> _track_vec;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,18 +130,18 @@ private:
|
|||||||
*/
|
*/
|
||||||
class RtspUrl{
|
class RtspUrl{
|
||||||
public:
|
public:
|
||||||
string _url;
|
string _url;
|
||||||
string _user;
|
string _user;
|
||||||
string _passwd;
|
string _passwd;
|
||||||
string _host;
|
string _host;
|
||||||
uint16_t _port;
|
uint16_t _port;
|
||||||
bool _is_ssl;
|
bool _is_ssl;
|
||||||
public:
|
public:
|
||||||
RtspUrl() = default;
|
RtspUrl() = default;
|
||||||
~RtspUrl() = default;
|
~RtspUrl() = default;
|
||||||
bool parse(const string &url);
|
bool parse(const string &url);
|
||||||
private:
|
private:
|
||||||
bool setup(bool,const string &, const string &, const string &);
|
bool setup(bool,const string &, const string &, const string &);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,44 +149,44 @@ private:
|
|||||||
*/
|
*/
|
||||||
class Sdp : public CodecInfo{
|
class Sdp : public CodecInfo{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<Sdp> Ptr;
|
typedef std::shared_ptr<Sdp> Ptr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造sdp
|
* 构造sdp
|
||||||
* @param sample_rate 采样率
|
* @param sample_rate 采样率
|
||||||
* @param playload_type pt类型
|
* @param playload_type pt类型
|
||||||
*/
|
*/
|
||||||
Sdp(uint32_t sample_rate, uint8_t playload_type){
|
Sdp(uint32_t sample_rate, uint8_t playload_type){
|
||||||
_sample_rate = sample_rate;
|
_sample_rate = sample_rate;
|
||||||
_playload_type = playload_type;
|
_playload_type = playload_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~Sdp(){}
|
virtual ~Sdp(){}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取sdp字符串
|
* 获取sdp字符串
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
virtual string getSdp() const = 0;
|
virtual string getSdp() const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取pt
|
* 获取pt
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
uint8_t getPlayloadType() const{
|
uint8_t getPlayloadType() const{
|
||||||
return _playload_type;
|
return _playload_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取采样率
|
* 获取采样率
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
uint32_t getSampleRate() const{
|
uint32_t getSampleRate() const{
|
||||||
return _sample_rate;
|
return _sample_rate;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
uint8_t _playload_type;
|
uint8_t _playload_type;
|
||||||
uint32_t _sample_rate;
|
uint32_t _sample_rate;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,56 +195,56 @@ private:
|
|||||||
class TitleSdp : public Sdp{
|
class TitleSdp : public Sdp{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造title类型sdp
|
* 构造title类型sdp
|
||||||
* @param dur_sec rtsp点播时长,0代表直播,单位秒
|
* @param dur_sec rtsp点播时长,0代表直播,单位秒
|
||||||
* @param header 自定义sdp描述
|
* @param header 自定义sdp描述
|
||||||
* @param version sdp版本
|
* @param version sdp版本
|
||||||
*/
|
*/
|
||||||
TitleSdp(float dur_sec = 0,
|
TitleSdp(float dur_sec = 0,
|
||||||
const map<string,string> &header = map<string,string>(),
|
const map<string,string> &header = map<string,string>(),
|
||||||
int version = 0) : Sdp(0,0){
|
int version = 0) : Sdp(0,0){
|
||||||
_printer << "v=" << version << "\r\n";
|
_printer << "v=" << version << "\r\n";
|
||||||
|
|
||||||
if(!header.empty()){
|
if(!header.empty()){
|
||||||
for (auto &pr : header){
|
for (auto &pr : header){
|
||||||
_printer << pr.first << "=" << pr.second << "\r\n";
|
_printer << pr.first << "=" << pr.second << "\r\n";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_printer << "o=- 1383190487994921 1 IN IP4 0.0.0.0\r\n";
|
_printer << "o=- 1383190487994921 1 IN IP4 0.0.0.0\r\n";
|
||||||
_printer << "s=RTSP Session, streamed by the ZLMediaKit\r\n";
|
_printer << "s=RTSP Session, streamed by the ZLMediaKit\r\n";
|
||||||
_printer << "i=ZLMediaKit Live Stream\r\n";
|
_printer << "i=ZLMediaKit Live Stream\r\n";
|
||||||
_printer << "c=IN IP4 0.0.0.0\r\n";
|
_printer << "c=IN IP4 0.0.0.0\r\n";
|
||||||
_printer << "t=0 0\r\n";
|
_printer << "t=0 0\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dur_sec <= 0){
|
if(dur_sec <= 0){
|
||||||
_printer << "a=range:npt=0-\r\n";
|
_printer << "a=range:npt=0-\r\n";
|
||||||
}else{
|
}else{
|
||||||
_printer << "a=range:npt=0-" << dur_sec << "\r\n";
|
_printer << "a=range:npt=0-" << dur_sec << "\r\n";
|
||||||
}
|
}
|
||||||
_printer << "a=control:*\r\n";
|
_printer << "a=control:*\r\n";
|
||||||
}
|
}
|
||||||
string getSdp() const override {
|
string getSdp() const override {
|
||||||
return _printer;
|
return _printer;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 返回音频或视频类型
|
* 返回音频或视频类型
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
TrackType getTrackType() const override {
|
TrackType getTrackType() const override {
|
||||||
return TrackTitle;
|
return TrackTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回编码器id
|
* 返回编码器id
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
CodecId getCodecId() const override{
|
CodecId getCodecId() const override{
|
||||||
return CodecInvalid;
|
return CodecInvalid;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
_StrPrinter _printer;
|
_StrPrinter _printer;
|
||||||
};
|
};
|
||||||
|
|
||||||
} //namespace mediakit
|
} //namespace mediakit
|
||||||
|
@ -35,83 +35,83 @@ using namespace std;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
void RtspDemuxer::loadSdp(const string &sdp){
|
void RtspDemuxer::loadSdp(const string &sdp){
|
||||||
loadSdp(SdpParser(sdp));
|
loadSdp(SdpParser(sdp));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspDemuxer::loadSdp(const SdpParser &attr) {
|
void RtspDemuxer::loadSdp(const SdpParser &attr) {
|
||||||
auto tracks = attr.getAvailableTrack();
|
auto tracks = attr.getAvailableTrack();
|
||||||
for (auto &track : tracks){
|
for (auto &track : tracks){
|
||||||
switch (track->_type) {
|
switch (track->_type) {
|
||||||
case TrackVideo: {
|
case TrackVideo: {
|
||||||
makeVideoTrack(track);
|
makeVideoTrack(track);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TrackAudio: {
|
case TrackAudio: {
|
||||||
makeAudioTrack(track);
|
makeAudioTrack(track);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto titleTrack = attr.getTrack(TrackTitle);
|
auto titleTrack = attr.getTrack(TrackTitle);
|
||||||
if(titleTrack){
|
if(titleTrack){
|
||||||
_fDuration = titleTrack->_duration;
|
_fDuration = titleTrack->_duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool RtspDemuxer::inputRtp(const RtpPacket::Ptr & rtp) {
|
bool RtspDemuxer::inputRtp(const RtpPacket::Ptr & rtp) {
|
||||||
switch (rtp->type) {
|
switch (rtp->type) {
|
||||||
case TrackVideo:{
|
case TrackVideo:{
|
||||||
if(_videoRtpDecoder){
|
if(_videoRtpDecoder){
|
||||||
return _videoRtpDecoder->inputRtp(rtp, true);
|
return _videoRtpDecoder->inputRtp(rtp, true);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case TrackAudio:{
|
case TrackAudio:{
|
||||||
if(_audioRtpDecoder){
|
if(_audioRtpDecoder){
|
||||||
_audioRtpDecoder->inputRtp(rtp, false);
|
_audioRtpDecoder->inputRtp(rtp, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void RtspDemuxer::makeAudioTrack(const SdpTrack::Ptr &audio) {
|
void RtspDemuxer::makeAudioTrack(const SdpTrack::Ptr &audio) {
|
||||||
//生成Track对象
|
//生成Track对象
|
||||||
_audioTrack = dynamic_pointer_cast<AudioTrack>(Factory::getTrackBySdp(audio));
|
_audioTrack = dynamic_pointer_cast<AudioTrack>(Factory::getTrackBySdp(audio));
|
||||||
if(_audioTrack){
|
if(_audioTrack){
|
||||||
//生成RtpCodec对象以便解码rtp
|
//生成RtpCodec对象以便解码rtp
|
||||||
_audioRtpDecoder = Factory::getRtpDecoderByTrack(_audioTrack);
|
_audioRtpDecoder = Factory::getRtpDecoderByTrack(_audioTrack);
|
||||||
if(_audioRtpDecoder){
|
if(_audioRtpDecoder){
|
||||||
//设置rtp解码器代理,生成的frame写入该Track
|
//设置rtp解码器代理,生成的frame写入该Track
|
||||||
_audioRtpDecoder->addDelegate(_audioTrack);
|
_audioRtpDecoder->addDelegate(_audioTrack);
|
||||||
onAddTrack(_audioTrack);
|
onAddTrack(_audioTrack);
|
||||||
} else{
|
} else{
|
||||||
//找不到相应的rtp解码器,该track无效
|
//找不到相应的rtp解码器,该track无效
|
||||||
_audioTrack.reset();
|
_audioTrack.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspDemuxer::makeVideoTrack(const SdpTrack::Ptr &video) {
|
void RtspDemuxer::makeVideoTrack(const SdpTrack::Ptr &video) {
|
||||||
//生成Track对象
|
//生成Track对象
|
||||||
_videoTrack = dynamic_pointer_cast<VideoTrack>(Factory::getTrackBySdp(video));
|
_videoTrack = dynamic_pointer_cast<VideoTrack>(Factory::getTrackBySdp(video));
|
||||||
if(_videoTrack){
|
if(_videoTrack){
|
||||||
//生成RtpCodec对象以便解码rtp
|
//生成RtpCodec对象以便解码rtp
|
||||||
_videoRtpDecoder = Factory::getRtpDecoderByTrack(_videoTrack);
|
_videoRtpDecoder = Factory::getRtpDecoderByTrack(_videoTrack);
|
||||||
if(_videoRtpDecoder){
|
if(_videoRtpDecoder){
|
||||||
//设置rtp解码器代理,生成的frame写入该Track
|
//设置rtp解码器代理,生成的frame写入该Track
|
||||||
_videoRtpDecoder->addDelegate(_videoTrack);
|
_videoRtpDecoder->addDelegate(_videoTrack);
|
||||||
onAddTrack(_videoTrack);
|
onAddTrack(_videoTrack);
|
||||||
}else{
|
}else{
|
||||||
//找不到相应的rtp解码器,该track无效
|
//找不到相应的rtp解码器,该track无效
|
||||||
_videoTrack.reset();
|
_videoTrack.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -39,8 +39,8 @@ namespace mediakit {
|
|||||||
|
|
||||||
class RtspDemuxer : public Demuxer{
|
class RtspDemuxer : public Demuxer{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtspDemuxer> Ptr;
|
typedef std::shared_ptr<RtspDemuxer> Ptr;
|
||||||
RtspDemuxer() = default;
|
RtspDemuxer() = default;
|
||||||
virtual ~RtspDemuxer() = default;
|
virtual ~RtspDemuxer() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,19 +48,19 @@ public:
|
|||||||
*/
|
*/
|
||||||
void loadSdp(const string &sdp);
|
void loadSdp(const string &sdp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始解复用
|
* 开始解复用
|
||||||
* @param rtp rtp包
|
* @param rtp rtp包
|
||||||
* @return true 代表是i帧第一个rtp包
|
* @return true 代表是i帧第一个rtp包
|
||||||
*/
|
*/
|
||||||
bool inputRtp(const RtpPacket::Ptr &rtp);
|
bool inputRtp(const RtpPacket::Ptr &rtp);
|
||||||
private:
|
private:
|
||||||
void makeAudioTrack(const SdpTrack::Ptr &audio);
|
void makeAudioTrack(const SdpTrack::Ptr &audio);
|
||||||
void makeVideoTrack(const SdpTrack::Ptr &video);
|
void makeVideoTrack(const SdpTrack::Ptr &video);
|
||||||
void loadSdp(const SdpParser &parser);
|
void loadSdp(const SdpParser &parser);
|
||||||
private:
|
private:
|
||||||
RtpCodec::Ptr _audioRtpDecoder;
|
RtpCodec::Ptr _audioRtpDecoder;
|
||||||
RtpCodec::Ptr _videoRtpDecoder;
|
RtpCodec::Ptr _videoRtpDecoder;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -56,177 +56,177 @@ namespace mediakit {
|
|||||||
*/
|
*/
|
||||||
class RtspMediaSource : public MediaSource, public RingDelegate<RtpPacket::Ptr> {
|
class RtspMediaSource : public MediaSource, public RingDelegate<RtpPacket::Ptr> {
|
||||||
public:
|
public:
|
||||||
typedef ResourcePool<RtpPacket> PoolType;
|
typedef ResourcePool<RtpPacket> PoolType;
|
||||||
typedef std::shared_ptr<RtspMediaSource> Ptr;
|
typedef std::shared_ptr<RtspMediaSource> Ptr;
|
||||||
typedef RingBuffer<RtpPacket::Ptr> RingType;
|
typedef RingBuffer<RtpPacket::Ptr> RingType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param vhost 虚拟主机名
|
* @param vhost 虚拟主机名
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream_id 流id
|
* @param stream_id 流id
|
||||||
* @param ring_size 可以设置固定的环形缓冲大小,0则自适应
|
* @param ring_size 可以设置固定的环形缓冲大小,0则自适应
|
||||||
*/
|
*/
|
||||||
RtspMediaSource(const string &vhost,
|
RtspMediaSource(const string &vhost,
|
||||||
const string &app,
|
const string &app,
|
||||||
const string &stream_id,
|
const string &stream_id,
|
||||||
int ring_size = RTP_GOP_SIZE) :
|
int ring_size = RTP_GOP_SIZE) :
|
||||||
MediaSource(RTSP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {}
|
MediaSource(RTSP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {}
|
||||||
|
|
||||||
virtual ~RtspMediaSource() {}
|
virtual ~RtspMediaSource() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取媒体源的环形缓冲
|
* 获取媒体源的环形缓冲
|
||||||
*/
|
*/
|
||||||
const RingType::Ptr &getRing() const {
|
const RingType::Ptr &getRing() const {
|
||||||
return _ring;
|
return _ring;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取播放器个数
|
* 获取播放器个数
|
||||||
*/
|
*/
|
||||||
int readerCount() override {
|
int readerCount() override {
|
||||||
return _ring ? _ring->readerCount() : 0;
|
return _ring ? _ring->readerCount() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取该源的sdp
|
* 获取该源的sdp
|
||||||
*/
|
*/
|
||||||
const string &getSdp() const {
|
const string &getSdp() const {
|
||||||
return _sdp;
|
return _sdp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取相应轨道的ssrc
|
* 获取相应轨道的ssrc
|
||||||
*/
|
*/
|
||||||
virtual uint32_t getSsrc(TrackType trackType) {
|
virtual uint32_t getSsrc(TrackType trackType) {
|
||||||
auto track = _sdp_parser.getTrack(trackType);
|
auto track = _sdp_parser.getTrack(trackType);
|
||||||
if (!track) {
|
if (!track) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return track->_ssrc;
|
return track->_ssrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取相应轨道的seqence
|
* 获取相应轨道的seqence
|
||||||
*/
|
*/
|
||||||
virtual uint16_t getSeqence(TrackType trackType) {
|
virtual uint16_t getSeqence(TrackType trackType) {
|
||||||
auto track = _sdp_parser.getTrack(trackType);
|
auto track = _sdp_parser.getTrack(trackType);
|
||||||
if (!track) {
|
if (!track) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return track->_seq;
|
return track->_seq;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取相应轨道的时间戳,单位毫秒
|
* 获取相应轨道的时间戳,单位毫秒
|
||||||
*/
|
*/
|
||||||
uint32_t getTimeStamp(TrackType trackType) override {
|
uint32_t getTimeStamp(TrackType trackType) override {
|
||||||
auto track = _sdp_parser.getTrack(trackType);
|
auto track = _sdp_parser.getTrack(trackType);
|
||||||
if (track) {
|
if (track) {
|
||||||
return track->_time_stamp;
|
return track->_time_stamp;
|
||||||
}
|
}
|
||||||
auto tracks = _sdp_parser.getAvailableTrack();
|
auto tracks = _sdp_parser.getAvailableTrack();
|
||||||
switch (tracks.size()) {
|
switch (tracks.size()) {
|
||||||
case 0:
|
case 0:
|
||||||
return 0;
|
return 0;
|
||||||
case 1:
|
case 1:
|
||||||
return tracks[0]->_time_stamp;
|
return tracks[0]->_time_stamp;
|
||||||
default:
|
default:
|
||||||
return MAX(tracks[0]->_time_stamp, tracks[1]->_time_stamp);
|
return MAX(tracks[0]->_time_stamp, tracks[1]->_time_stamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新时间戳
|
* 更新时间戳
|
||||||
*/
|
*/
|
||||||
void setTimeStamp(uint32_t uiStamp) override {
|
void setTimeStamp(uint32_t uiStamp) override {
|
||||||
auto tracks = _sdp_parser.getAvailableTrack();
|
auto tracks = _sdp_parser.getAvailableTrack();
|
||||||
for (auto &track : tracks) {
|
for (auto &track : tracks) {
|
||||||
track->_time_stamp = uiStamp;
|
track->_time_stamp = uiStamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置sdp
|
* 设置sdp
|
||||||
*/
|
*/
|
||||||
virtual void setSdp(const string &sdp) {
|
virtual void setSdp(const string &sdp) {
|
||||||
_sdp = sdp;
|
_sdp = sdp;
|
||||||
_sdp_parser.load(sdp);
|
_sdp_parser.load(sdp);
|
||||||
_have_video = (bool)_sdp_parser.getTrack(TrackVideo);
|
_have_video = (bool)_sdp_parser.getTrack(TrackVideo);
|
||||||
if (_ring) {
|
if (_ring) {
|
||||||
regist();
|
regist();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入rtp
|
* 输入rtp
|
||||||
* @param rtp rtp包
|
* @param rtp rtp包
|
||||||
* @param keyPos 该包是否为关键帧的第一个包
|
* @param keyPos 该包是否为关键帧的第一个包
|
||||||
*/
|
*/
|
||||||
void onWrite(const RtpPacket::Ptr &rtp, bool keyPos) override {
|
void onWrite(const RtpPacket::Ptr &rtp, bool keyPos) override {
|
||||||
auto track = _sdp_parser.getTrack(rtp->type);
|
auto track = _sdp_parser.getTrack(rtp->type);
|
||||||
if (track) {
|
if (track) {
|
||||||
track->_seq = rtp->sequence;
|
track->_seq = rtp->sequence;
|
||||||
track->_time_stamp = rtp->timeStamp;
|
track->_time_stamp = rtp->timeStamp;
|
||||||
track->_ssrc = rtp->ssrc;
|
track->_ssrc = rtp->ssrc;
|
||||||
}
|
}
|
||||||
if (!_ring) {
|
if (!_ring) {
|
||||||
weak_ptr<RtspMediaSource> weakSelf = dynamic_pointer_cast<RtspMediaSource>(shared_from_this());
|
weak_ptr<RtspMediaSource> weakSelf = dynamic_pointer_cast<RtspMediaSource>(shared_from_this());
|
||||||
auto lam = [weakSelf](const EventPoller::Ptr &, int size, bool) {
|
auto lam = [weakSelf](const EventPoller::Ptr &, int size, bool) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if (!strongSelf) {
|
if (!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strongSelf->onReaderChanged(size);
|
strongSelf->onReaderChanged(size);
|
||||||
};
|
};
|
||||||
//rtp包缓存最大允许2048个,大概最多3MB数据
|
//rtp包缓存最大允许2048个,大概最多3MB数据
|
||||||
//但是这个是GOP缓存的上限值,真实的GOP缓存大小等于两个I帧之间的包数的两倍
|
//但是这个是GOP缓存的上限值,真实的GOP缓存大小等于两个I帧之间的包数的两倍
|
||||||
//而且每次遇到I帧,则会清空GOP缓存,所以真实的GOP缓存远小于此值
|
//而且每次遇到I帧,则会清空GOP缓存,所以真实的GOP缓存远小于此值
|
||||||
_ring = std::make_shared<RingType>(_ring_size, std::move(lam));
|
_ring = std::make_shared<RingType>(_ring_size, std::move(lam));
|
||||||
onReaderChanged(0);
|
onReaderChanged(0);
|
||||||
if (!_sdp.empty()) {
|
if (!_sdp.empty()) {
|
||||||
regist();
|
regist();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//不存在视频,为了减少缓存延时,那么关闭GOP缓存
|
//不存在视频,为了减少缓存延时,那么关闭GOP缓存
|
||||||
_ring->write(rtp, _have_video ? keyPos : true);
|
_ring->write(rtp, _have_video ? keyPos : true);
|
||||||
checkNoneReader();
|
checkNoneReader();
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* 每次增减消费者都会触发该函数
|
* 每次增减消费者都会触发该函数
|
||||||
*/
|
*/
|
||||||
void onReaderChanged(int size) {
|
void onReaderChanged(int size) {
|
||||||
//我们记录最后一次活动时间
|
//我们记录最后一次活动时间
|
||||||
_reader_changed_ticker.resetTime();
|
_reader_changed_ticker.resetTime();
|
||||||
if (size != 0 || totalReaderCount() != 0) {
|
if (size != 0 || totalReaderCount() != 0) {
|
||||||
//还有消费者正在观看该流
|
//还有消费者正在观看该流
|
||||||
_async_emit_none_reader = false;
|
_async_emit_none_reader = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_async_emit_none_reader = true;
|
_async_emit_none_reader = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否无人消费该流,
|
* 检查是否无人消费该流,
|
||||||
* 如果无人消费且超过一定时间会触发onNoneReader事件
|
* 如果无人消费且超过一定时间会触发onNoneReader事件
|
||||||
*/
|
*/
|
||||||
void checkNoneReader() {
|
void checkNoneReader() {
|
||||||
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
||||||
if (_async_emit_none_reader && _reader_changed_ticker.elapsedTime() > stream_none_reader_delay) {
|
if (_async_emit_none_reader && _reader_changed_ticker.elapsedTime() > stream_none_reader_delay) {
|
||||||
_async_emit_none_reader = false;
|
_async_emit_none_reader = false;
|
||||||
onNoneReader();
|
onNoneReader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
int _ring_size;
|
int _ring_size;
|
||||||
bool _async_emit_none_reader = false;
|
bool _async_emit_none_reader = false;
|
||||||
bool _have_video = false;
|
bool _have_video = false;
|
||||||
Ticker _reader_changed_ticker;
|
Ticker _reader_changed_ticker;
|
||||||
SdpParser _sdp_parser;
|
SdpParser _sdp_parser;
|
||||||
string _sdp;
|
string _sdp;
|
||||||
RingType::Ptr _ring;
|
RingType::Ptr _ring;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -39,12 +39,12 @@ public:
|
|||||||
typedef std::shared_ptr<RtspMediaSourceImp> Ptr;
|
typedef std::shared_ptr<RtspMediaSourceImp> Ptr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param vhost 虚拟主机
|
* @param vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param id 流id
|
* @param id 流id
|
||||||
* @param ringSize 环形缓存大小
|
* @param ringSize 环形缓存大小
|
||||||
*/
|
*/
|
||||||
RtspMediaSourceImp(const string &vhost, const string &app, const string &id, int ringSize = RTP_GOP_SIZE) : RtspMediaSource(vhost, app, id,ringSize) {
|
RtspMediaSourceImp(const string &vhost, const string &app, const string &id, int ringSize = RTP_GOP_SIZE) : RtspMediaSource(vhost, app, id,ringSize) {
|
||||||
_demuxer = std::make_shared<RtspDemuxer>();
|
_demuxer = std::make_shared<RtspDemuxer>();
|
||||||
_demuxer->setTrackListener(this);
|
_demuxer->setTrackListener(this);
|
||||||
@ -69,9 +69,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置监听器
|
* 设置监听器
|
||||||
* @param listener
|
* @param listener
|
||||||
*/
|
*/
|
||||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) override {
|
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) override {
|
||||||
RtspMediaSource::setListener(listener);
|
RtspMediaSource::setListener(listener);
|
||||||
if(_muxer){
|
if(_muxer){
|
||||||
@ -87,11 +87,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置协议转换
|
* 设置协议转换
|
||||||
* @param enableRtmp 是否转换成rtmp
|
* @param enableRtmp 是否转换成rtmp
|
||||||
* @param enableHls 是否转换成hls
|
* @param enableHls 是否转换成hls
|
||||||
* @param enableMP4 是否mp4录制
|
* @param enableMP4 是否mp4录制
|
||||||
*/
|
*/
|
||||||
void setProtocolTranslation(bool enableRtmp,bool enableHls,bool enableMP4){
|
void setProtocolTranslation(bool enableRtmp,bool enableHls,bool enableMP4){
|
||||||
//不重复生成rtsp
|
//不重复生成rtsp
|
||||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), false, enableRtmp, enableHls, enableMP4);
|
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), false, enableRtmp, enableHls, enableMP4);
|
||||||
@ -104,8 +104,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _demuxer触发的添加Track事件
|
* _demuxer触发的添加Track事件
|
||||||
*/
|
*/
|
||||||
void onAddTrack(const Track::Ptr &track) override {
|
void onAddTrack(const Track::Ptr &track) override {
|
||||||
if(_muxer){
|
if(_muxer){
|
||||||
_muxer->addTrack(track);
|
_muxer->addTrack(track);
|
||||||
|
@ -43,29 +43,29 @@ using namespace mediakit::Client;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
enum PlayType {
|
enum PlayType {
|
||||||
type_play = 0,
|
type_play = 0,
|
||||||
type_pause,
|
type_pause,
|
||||||
type_seek
|
type_seek
|
||||||
};
|
};
|
||||||
|
|
||||||
RtspPlayer::RtspPlayer(const EventPoller::Ptr &poller) : TcpClient(poller){
|
RtspPlayer::RtspPlayer(const EventPoller::Ptr &poller) : TcpClient(poller){
|
||||||
RtpReceiver::setPoolSize(64);
|
RtpReceiver::setPoolSize(64);
|
||||||
}
|
}
|
||||||
RtspPlayer::~RtspPlayer(void) {
|
RtspPlayer::~RtspPlayer(void) {
|
||||||
DebugL << endl;
|
DebugL << endl;
|
||||||
}
|
}
|
||||||
void RtspPlayer::teardown(){
|
void RtspPlayer::teardown(){
|
||||||
if (alive()) {
|
if (alive()) {
|
||||||
sendRtspRequest("TEARDOWN" ,_strContentBase);
|
sendRtspRequest("TEARDOWN" ,_strContentBase);
|
||||||
shutdown(SockException(Err_shutdown,"teardown"));
|
shutdown(SockException(Err_shutdown,"teardown"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_rtspMd5Nonce.clear();
|
_rtspMd5Nonce.clear();
|
||||||
_rtspRealm.clear();
|
_rtspRealm.clear();
|
||||||
_aTrackInfo.clear();
|
_aTrackInfo.clear();
|
||||||
_strSession.clear();
|
_strSession.clear();
|
||||||
_strContentBase.clear();
|
_strContentBase.clear();
|
||||||
RtpReceiver::clear();
|
RtpReceiver::clear();
|
||||||
|
|
||||||
CLEAR_ARR(_apRtpSock);
|
CLEAR_ARR(_apRtpSock);
|
||||||
CLEAR_ARR(_apRtcpSock);
|
CLEAR_ARR(_apRtcpSock);
|
||||||
@ -74,65 +74,65 @@ void RtspPlayer::teardown(){
|
|||||||
CLEAR_ARR(_aui64RtpRecv)
|
CLEAR_ARR(_aui64RtpRecv)
|
||||||
CLEAR_ARR(_aui16NowSeq)
|
CLEAR_ARR(_aui16NowSeq)
|
||||||
|
|
||||||
_pPlayTimer.reset();
|
_pPlayTimer.reset();
|
||||||
_pRtpTimer.reset();
|
_pRtpTimer.reset();
|
||||||
_uiCseq = 1;
|
_uiCseq = 1;
|
||||||
_onHandshake = nullptr;
|
_onHandshake = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::play(const string &strUrl){
|
void RtspPlayer::play(const string &strUrl){
|
||||||
RtspUrl url;
|
RtspUrl url;
|
||||||
if(!url.parse(strUrl)){
|
if(!url.parse(strUrl)){
|
||||||
onPlayResult_l(SockException(Err_other,StrPrinter << "illegal rtsp url:" << strUrl),false);
|
onPlayResult_l(SockException(Err_other,StrPrinter << "illegal rtsp url:" << strUrl),false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown();
|
teardown();
|
||||||
|
|
||||||
if (url._user.size()) {
|
if (url._user.size()) {
|
||||||
(*this)[kRtspUser] = url._user;
|
(*this)[kRtspUser] = url._user;
|
||||||
}
|
}
|
||||||
if (url._passwd.size()) {
|
if (url._passwd.size()) {
|
||||||
(*this)[kRtspPwd] = url._passwd;
|
(*this)[kRtspPwd] = url._passwd;
|
||||||
(*this)[kRtspPwdIsMD5] = false;
|
(*this)[kRtspPwdIsMD5] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_strUrl = url._url;
|
_strUrl = url._url;
|
||||||
_eType = (Rtsp::eRtpType)(int)(*this)[kRtpType];
|
_eType = (Rtsp::eRtpType)(int)(*this)[kRtpType];
|
||||||
DebugL << url._url << " " << (url._user.size() ? url._user : "null") << " " << (url._passwd.size() ? url._passwd : "null") << " " << _eType;
|
DebugL << url._url << " " << (url._user.size() ? url._user : "null") << " " << (url._passwd.size() ? url._passwd : "null") << " " << _eType;
|
||||||
|
|
||||||
weak_ptr<RtspPlayer> weakSelf = dynamic_pointer_cast<RtspPlayer>(shared_from_this());
|
weak_ptr<RtspPlayer> weakSelf = dynamic_pointer_cast<RtspPlayer>(shared_from_this());
|
||||||
float playTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
float playTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
||||||
_pPlayTimer.reset( new Timer(playTimeOutSec, [weakSelf]() {
|
_pPlayTimer.reset( new Timer(playTimeOutSec, [weakSelf]() {
|
||||||
auto strongSelf=weakSelf.lock();
|
auto strongSelf=weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout"),false);
|
strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout"),false);
|
||||||
return false;
|
return false;
|
||||||
},getPoller()));
|
},getPoller()));
|
||||||
|
|
||||||
if(!(*this)[kNetAdapter].empty()){
|
if(!(*this)[kNetAdapter].empty()){
|
||||||
setNetAdapter((*this)[kNetAdapter]);
|
setNetAdapter((*this)[kNetAdapter]);
|
||||||
}
|
}
|
||||||
startConnect(url._host, url._port, playTimeOutSec);
|
startConnect(url._host, url._port, playTimeOutSec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::onConnect(const SockException &err){
|
void RtspPlayer::onConnect(const SockException &err){
|
||||||
if(err.getErrCode() != Err_success) {
|
if(err.getErrCode() != Err_success) {
|
||||||
onPlayResult_l(err,false);
|
onPlayResult_l(err,false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendDescribe();
|
sendDescribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::onRecv(const Buffer::Ptr& pBuf) {
|
void RtspPlayer::onRecv(const Buffer::Ptr& pBuf) {
|
||||||
input(pBuf->data(),pBuf->size());
|
input(pBuf->data(),pBuf->size());
|
||||||
}
|
}
|
||||||
void RtspPlayer::onErr(const SockException &ex) {
|
void RtspPlayer::onErr(const SockException &ex) {
|
||||||
//定时器_pPlayTimer为空后表明握手结束了
|
//定时器_pPlayTimer为空后表明握手结束了
|
||||||
onPlayResult_l(ex,!_pPlayTimer);
|
onPlayResult_l(ex,!_pPlayTimer);
|
||||||
}
|
}
|
||||||
// from live555
|
// from live555
|
||||||
bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) {
|
bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) {
|
||||||
@ -167,25 +167,25 @@ bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void RtspPlayer::handleResDESCRIBE(const Parser& parser) {
|
void RtspPlayer::handleResDESCRIBE(const Parser& parser) {
|
||||||
string authInfo = parser["WWW-Authenticate"];
|
string authInfo = parser["WWW-Authenticate"];
|
||||||
//发送DESCRIBE命令后的回复
|
//发送DESCRIBE命令后的回复
|
||||||
if ((parser.Url() == "401") && handleAuthenticationFailure(authInfo)) {
|
if ((parser.Url() == "401") && handleAuthenticationFailure(authInfo)) {
|
||||||
sendDescribe();
|
sendDescribe();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(parser.Url() == "302" || parser.Url() == "301"){
|
if(parser.Url() == "302" || parser.Url() == "301"){
|
||||||
auto newUrl = parser["Location"];
|
auto newUrl = parser["Location"];
|
||||||
if(newUrl.empty()){
|
if(newUrl.empty()){
|
||||||
throw std::runtime_error("未找到Location字段(跳转url)");
|
throw std::runtime_error("未找到Location字段(跳转url)");
|
||||||
}
|
}
|
||||||
play(newUrl);
|
play(newUrl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (parser.Url() != "200") {
|
if (parser.Url() != "200") {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
StrPrinter << "DESCRIBE:" << parser.Url() << " " << parser.Tail() << endl);
|
StrPrinter << "DESCRIBE:" << parser.Url() << " " << parser.Tail() << endl);
|
||||||
}
|
}
|
||||||
_strContentBase = parser["Content-Base"];
|
_strContentBase = parser["Content-Base"];
|
||||||
|
|
||||||
if(_strContentBase.empty()){
|
if(_strContentBase.empty()){
|
||||||
_strContentBase = _strUrl;
|
_strContentBase = _strUrl;
|
||||||
@ -194,58 +194,58 @@ void RtspPlayer::handleResDESCRIBE(const Parser& parser) {
|
|||||||
_strContentBase.pop_back();
|
_strContentBase.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
SdpParser sdpParser(parser.Content());
|
SdpParser sdpParser(parser.Content());
|
||||||
//解析sdp
|
//解析sdp
|
||||||
_aTrackInfo = sdpParser.getAvailableTrack();
|
_aTrackInfo = sdpParser.getAvailableTrack();
|
||||||
auto title = sdpParser.getTrack(TrackTitle);
|
auto title = sdpParser.getTrack(TrackTitle);
|
||||||
bool isPlayback = false;
|
bool isPlayback = false;
|
||||||
if(title && title->_duration ){
|
if(title && title->_duration ){
|
||||||
isPlayback = true;
|
isPlayback = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto &stamp : _stamp){
|
for(auto &stamp : _stamp){
|
||||||
stamp.setPlayBack(isPlayback);
|
stamp.setPlayBack(isPlayback);
|
||||||
stamp.setRelativeStamp(0);
|
stamp.setRelativeStamp(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_aTrackInfo.empty()) {
|
if (_aTrackInfo.empty()) {
|
||||||
throw std::runtime_error("无有效的Sdp Track");
|
throw std::runtime_error("无有效的Sdp Track");
|
||||||
}
|
}
|
||||||
if (!onCheckSDP(sdpParser.toString())) {
|
if (!onCheckSDP(sdpParser.toString())) {
|
||||||
throw std::runtime_error("onCheckSDP faied");
|
throw std::runtime_error("onCheckSDP faied");
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSetup(0);
|
sendSetup(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
//有必要的情况下创建udp端口
|
//有必要的情况下创建udp端口
|
||||||
void RtspPlayer::createUdpSockIfNecessary(int track_idx){
|
void RtspPlayer::createUdpSockIfNecessary(int track_idx){
|
||||||
auto &rtpSockRef = _apRtpSock[track_idx];
|
auto &rtpSockRef = _apRtpSock[track_idx];
|
||||||
auto &rtcpSockRef = _apRtcpSock[track_idx];
|
auto &rtcpSockRef = _apRtcpSock[track_idx];
|
||||||
if(!rtpSockRef){
|
if(!rtpSockRef){
|
||||||
rtpSockRef.reset(new Socket(getPoller()));
|
rtpSockRef.reset(new Socket(getPoller()));
|
||||||
//rtp随机端口
|
//rtp随机端口
|
||||||
if (!rtpSockRef->bindUdpSock(0, get_local_ip().data())) {
|
if (!rtpSockRef->bindUdpSock(0, get_local_ip().data())) {
|
||||||
rtpSockRef.reset();
|
rtpSockRef.reset();
|
||||||
throw std::runtime_error("open rtp sock failed");
|
throw std::runtime_error("open rtp sock failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!rtcpSockRef){
|
if(!rtcpSockRef){
|
||||||
rtcpSockRef.reset(new Socket(getPoller()));
|
rtcpSockRef.reset(new Socket(getPoller()));
|
||||||
//rtcp端口为rtp端口+1,目的是为了兼容某些服务器,其实更推荐随机端口
|
//rtcp端口为rtp端口+1,目的是为了兼容某些服务器,其实更推荐随机端口
|
||||||
if (!rtcpSockRef->bindUdpSock(rtpSockRef->get_local_port() + 1, get_local_ip().data())) {
|
if (!rtcpSockRef->bindUdpSock(rtpSockRef->get_local_port() + 1, get_local_ip().data())) {
|
||||||
rtcpSockRef.reset();
|
rtcpSockRef.reset();
|
||||||
throw std::runtime_error("open rtcp sock failed");
|
throw std::runtime_error("open rtcp sock failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(rtpSockRef->get_local_port() % 2 != 0){
|
if(rtpSockRef->get_local_port() % 2 != 0){
|
||||||
//如果rtp端口不是偶数,那么与rtcp端口互换,目的是兼容一些要求严格的服务器
|
//如果rtp端口不是偶数,那么与rtcp端口互换,目的是兼容一些要求严格的服务器
|
||||||
Socket::Ptr tmp = rtpSockRef;
|
Socket::Ptr tmp = rtpSockRef;
|
||||||
rtpSockRef = rtcpSockRef;
|
rtpSockRef = rtcpSockRef;
|
||||||
rtcpSockRef = tmp;
|
rtcpSockRef = tmp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -253,91 +253,91 @@ void RtspPlayer::createUdpSockIfNecessary(int track_idx){
|
|||||||
void RtspPlayer::sendSetup(unsigned int trackIndex) {
|
void RtspPlayer::sendSetup(unsigned int trackIndex) {
|
||||||
_onHandshake = std::bind(&RtspPlayer::handleResSETUP,this, placeholders::_1,trackIndex);
|
_onHandshake = std::bind(&RtspPlayer::handleResSETUP,this, placeholders::_1,trackIndex);
|
||||||
auto &track = _aTrackInfo[trackIndex];
|
auto &track = _aTrackInfo[trackIndex];
|
||||||
auto baseUrl = _strContentBase + "/" + track->_control_surffix;
|
auto baseUrl = _strContentBase + "/" + track->_control_surffix;
|
||||||
switch (_eType) {
|
switch (_eType) {
|
||||||
case Rtsp::RTP_TCP: {
|
case Rtsp::RTP_TCP: {
|
||||||
sendRtspRequest("SETUP",baseUrl,{"Transport",StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1});
|
sendRtspRequest("SETUP",baseUrl,{"Transport",StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Rtsp::RTP_MULTICAST: {
|
case Rtsp::RTP_MULTICAST: {
|
||||||
sendRtspRequest("SETUP",baseUrl,{"Transport","Transport: RTP/AVP;multicast"});
|
sendRtspRequest("SETUP",baseUrl,{"Transport","Transport: RTP/AVP;multicast"});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Rtsp::RTP_UDP: {
|
case Rtsp::RTP_UDP: {
|
||||||
createUdpSockIfNecessary(trackIndex);
|
createUdpSockIfNecessary(trackIndex);
|
||||||
sendRtspRequest("SETUP",baseUrl,{"Transport",
|
sendRtspRequest("SETUP",baseUrl,{"Transport",
|
||||||
StrPrinter << "RTP/AVP;unicast;client_port="
|
StrPrinter << "RTP/AVP;unicast;client_port="
|
||||||
<< _apRtpSock[trackIndex]->get_local_port() << "-"
|
<< _apRtpSock[trackIndex]->get_local_port() << "-"
|
||||||
<< _apRtcpSock[trackIndex]->get_local_port()});
|
<< _apRtcpSock[trackIndex]->get_local_port()});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int uiTrackIndex) {
|
void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int uiTrackIndex) {
|
||||||
if (parser.Url() != "200") {
|
if (parser.Url() != "200") {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
StrPrinter << "SETUP:" << parser.Url() << " " << parser.Tail() << endl);
|
StrPrinter << "SETUP:" << parser.Url() << " " << parser.Tail() << endl);
|
||||||
}
|
}
|
||||||
if (uiTrackIndex == 0) {
|
if (uiTrackIndex == 0) {
|
||||||
_strSession = parser["Session"];
|
_strSession = parser["Session"];
|
||||||
_strSession.append(";");
|
_strSession.append(";");
|
||||||
_strSession = FindField(_strSession.data(), nullptr, ";");
|
_strSession = FindField(_strSession.data(), nullptr, ";");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto strTransport = parser["Transport"];
|
auto strTransport = parser["Transport"];
|
||||||
if(strTransport.find("TCP") != string::npos || strTransport.find("interleaved") != string::npos){
|
if(strTransport.find("TCP") != string::npos || strTransport.find("interleaved") != string::npos){
|
||||||
_eType = Rtsp::RTP_TCP;
|
_eType = Rtsp::RTP_TCP;
|
||||||
}else if(strTransport.find("multicast") != string::npos){
|
}else if(strTransport.find("multicast") != string::npos){
|
||||||
_eType = Rtsp::RTP_MULTICAST;
|
_eType = Rtsp::RTP_MULTICAST;
|
||||||
}else{
|
}else{
|
||||||
_eType = Rtsp::RTP_UDP;
|
_eType = Rtsp::RTP_UDP;
|
||||||
}
|
}
|
||||||
|
|
||||||
RtspSplitter::enableRecvRtp(_eType == Rtsp::RTP_TCP);
|
RtspSplitter::enableRecvRtp(_eType == Rtsp::RTP_TCP);
|
||||||
|
|
||||||
if(_eType == Rtsp::RTP_TCP) {
|
if(_eType == Rtsp::RTP_TCP) {
|
||||||
string interleaved = FindField( FindField((strTransport + ";").data(), "interleaved=", ";").data(), NULL, "-");
|
string interleaved = FindField( FindField((strTransport + ";").data(), "interleaved=", ";").data(), NULL, "-");
|
||||||
_aTrackInfo[uiTrackIndex]->_interleaved = atoi(interleaved.data());
|
_aTrackInfo[uiTrackIndex]->_interleaved = atoi(interleaved.data());
|
||||||
}else{
|
}else{
|
||||||
const char *strPos = (_eType == Rtsp::RTP_MULTICAST ? "port=" : "server_port=") ;
|
const char *strPos = (_eType == Rtsp::RTP_MULTICAST ? "port=" : "server_port=") ;
|
||||||
auto port_str = FindField((strTransport + ";").data(), strPos, ";");
|
auto port_str = FindField((strTransport + ";").data(), strPos, ";");
|
||||||
uint16_t rtp_port = atoi(FindField(port_str.data(), NULL, "-").data());
|
uint16_t rtp_port = atoi(FindField(port_str.data(), NULL, "-").data());
|
||||||
uint16_t rtcp_port = atoi(FindField(port_str.data(), "-",NULL).data());
|
uint16_t rtcp_port = atoi(FindField(port_str.data(), "-",NULL).data());
|
||||||
auto &pRtpSockRef = _apRtpSock[uiTrackIndex];
|
auto &pRtpSockRef = _apRtpSock[uiTrackIndex];
|
||||||
auto &pRtcpSockRef = _apRtcpSock[uiTrackIndex];
|
auto &pRtcpSockRef = _apRtcpSock[uiTrackIndex];
|
||||||
|
|
||||||
if (_eType == Rtsp::RTP_MULTICAST) {
|
if (_eType == Rtsp::RTP_MULTICAST) {
|
||||||
//udp组播
|
//udp组播
|
||||||
auto multiAddr = FindField((strTransport + ";").data(), "destination=", ";");
|
auto multiAddr = FindField((strTransport + ";").data(), "destination=", ";");
|
||||||
pRtpSockRef.reset(new Socket(getPoller()));
|
pRtpSockRef.reset(new Socket(getPoller()));
|
||||||
if (!pRtpSockRef->bindUdpSock(rtp_port, multiAddr.data())) {
|
if (!pRtpSockRef->bindUdpSock(rtp_port, multiAddr.data())) {
|
||||||
pRtpSockRef.reset();
|
pRtpSockRef.reset();
|
||||||
throw std::runtime_error("open udp sock err");
|
throw std::runtime_error("open udp sock err");
|
||||||
}
|
}
|
||||||
auto fd = pRtpSockRef->rawFD();
|
auto fd = pRtpSockRef->rawFD();
|
||||||
if (-1 == SockUtil::joinMultiAddrFilter(fd, multiAddr.data(), get_peer_ip().data(),get_local_ip().data())) {
|
if (-1 == SockUtil::joinMultiAddrFilter(fd, multiAddr.data(), get_peer_ip().data(),get_local_ip().data())) {
|
||||||
SockUtil::joinMultiAddr(fd, multiAddr.data(),get_local_ip().data());
|
SockUtil::joinMultiAddr(fd, multiAddr.data(),get_local_ip().data());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
createUdpSockIfNecessary(uiTrackIndex);
|
createUdpSockIfNecessary(uiTrackIndex);
|
||||||
//udp单播
|
//udp单播
|
||||||
struct sockaddr_in rtpto;
|
struct sockaddr_in rtpto;
|
||||||
rtpto.sin_port = ntohs(rtp_port);
|
rtpto.sin_port = ntohs(rtp_port);
|
||||||
rtpto.sin_family = AF_INET;
|
rtpto.sin_family = AF_INET;
|
||||||
rtpto.sin_addr.s_addr = inet_addr(get_peer_ip().data());
|
rtpto.sin_addr.s_addr = inet_addr(get_peer_ip().data());
|
||||||
pRtpSockRef->setSendPeerAddr((struct sockaddr *)&(rtpto));
|
pRtpSockRef->setSendPeerAddr((struct sockaddr *)&(rtpto));
|
||||||
//发送rtp打洞包
|
//发送rtp打洞包
|
||||||
pRtpSockRef->send("\xce\xfa\xed\xfe", 4);
|
pRtpSockRef->send("\xce\xfa\xed\xfe", 4);
|
||||||
|
|
||||||
//设置rtcp发送目标,为后续发送rtcp做准备
|
//设置rtcp发送目标,为后续发送rtcp做准备
|
||||||
rtpto.sin_port = ntohs(rtcp_port);
|
rtpto.sin_port = ntohs(rtcp_port);
|
||||||
rtpto.sin_family = AF_INET;
|
rtpto.sin_family = AF_INET;
|
||||||
rtpto.sin_addr.s_addr = inet_addr(get_peer_ip().data());
|
rtpto.sin_addr.s_addr = inet_addr(get_peer_ip().data());
|
||||||
pRtcpSockRef->setSendPeerAddr((struct sockaddr *)&(rtpto));
|
pRtcpSockRef->setSendPeerAddr((struct sockaddr *)&(rtpto));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto srcIP = inet_addr(get_peer_ip().data());
|
auto srcIP = inet_addr(get_peer_ip().data());
|
||||||
weak_ptr<RtspPlayer> weakSelf = dynamic_pointer_cast<RtspPlayer>(shared_from_this());
|
weak_ptr<RtspPlayer> weakSelf = dynamic_pointer_cast<RtspPlayer>(shared_from_this());
|
||||||
@ -368,98 +368,98 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int uiTrackIndex)
|
|||||||
strongSelf->onRtcpPacket(uiTrackIndex, strongSelf->_aTrackInfo[uiTrackIndex], (unsigned char *) buf->data(), buf->size());
|
strongSelf->onRtcpPacket(uiTrackIndex, strongSelf->_aTrackInfo[uiTrackIndex], (unsigned char *) buf->data(), buf->size());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiTrackIndex < _aTrackInfo.size() - 1) {
|
if (uiTrackIndex < _aTrackInfo.size() - 1) {
|
||||||
//需要继续发送SETUP命令
|
//需要继续发送SETUP命令
|
||||||
sendSetup(uiTrackIndex + 1);
|
sendSetup(uiTrackIndex + 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//所有setup命令发送完毕
|
//所有setup命令发送完毕
|
||||||
//发送play命令
|
//发送play命令
|
||||||
sendPause(type_play, 0);
|
sendPause(type_play, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::sendDescribe() {
|
void RtspPlayer::sendDescribe() {
|
||||||
//发送DESCRIBE命令后处理函数:handleResDESCRIBE
|
//发送DESCRIBE命令后处理函数:handleResDESCRIBE
|
||||||
_onHandshake = std::bind(&RtspPlayer::handleResDESCRIBE,this, placeholders::_1);
|
_onHandshake = std::bind(&RtspPlayer::handleResDESCRIBE,this, placeholders::_1);
|
||||||
sendRtspRequest("DESCRIBE",_strUrl,{"Accept","application/sdp"});
|
sendRtspRequest("DESCRIBE",_strUrl,{"Accept","application/sdp"});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::sendPause(int type , uint32_t seekMS){
|
void RtspPlayer::sendPause(int type , uint32_t seekMS){
|
||||||
_onHandshake = std::bind(&RtspPlayer::handleResPAUSE,this, placeholders::_1,type);
|
_onHandshake = std::bind(&RtspPlayer::handleResPAUSE,this, placeholders::_1,type);
|
||||||
//开启或暂停rtsp
|
//开启或暂停rtsp
|
||||||
switch (type){
|
switch (type){
|
||||||
case type_pause:
|
case type_pause:
|
||||||
sendRtspRequest("PAUSE", _strContentBase);
|
sendRtspRequest("PAUSE", _strContentBase);
|
||||||
break;
|
break;
|
||||||
case type_play:
|
case type_play:
|
||||||
sendRtspRequest("PLAY", _strContentBase);
|
sendRtspRequest("PLAY", _strContentBase);
|
||||||
break;
|
break;
|
||||||
case type_seek:
|
case type_seek:
|
||||||
sendRtspRequest("PLAY", _strContentBase, {"Range",StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-"});
|
sendRtspRequest("PLAY", _strContentBase, {"Range",StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-"});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
WarnL << "unknown type : " << type;
|
WarnL << "unknown type : " << type;
|
||||||
_onHandshake = nullptr;
|
_onHandshake = nullptr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void RtspPlayer::pause(bool bPause) {
|
void RtspPlayer::pause(bool bPause) {
|
||||||
sendPause(bPause ? type_pause : type_seek, getProgressMilliSecond());
|
sendPause(bPause ? type_pause : type_seek, getProgressMilliSecond());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::handleResPAUSE(const Parser& parser,int type) {
|
void RtspPlayer::handleResPAUSE(const Parser& parser,int type) {
|
||||||
if (parser.Url() != "200") {
|
if (parser.Url() != "200") {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case type_pause:
|
case type_pause:
|
||||||
WarnL << "Pause failed:" << parser.Url() << " " << parser.Tail() << endl;
|
WarnL << "Pause failed:" << parser.Url() << " " << parser.Tail() << endl;
|
||||||
break;
|
break;
|
||||||
case type_play:
|
case type_play:
|
||||||
WarnL << "Play failed:" << parser.Url() << " " << parser.Tail() << endl;
|
WarnL << "Play failed:" << parser.Url() << " " << parser.Tail() << endl;
|
||||||
break;
|
break;
|
||||||
case type_seek:
|
case type_seek:
|
||||||
WarnL << "Seek failed:" << parser.Url() << " " << parser.Tail() << endl;
|
WarnL << "Seek failed:" << parser.Url() << " " << parser.Tail() << endl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == type_pause) {
|
if (type == type_pause) {
|
||||||
//暂停成功!
|
//暂停成功!
|
||||||
_pRtpTimer.reset();
|
_pRtpTimer.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//play或seek成功
|
//play或seek成功
|
||||||
uint32_t iSeekTo = 0;
|
uint32_t iSeekTo = 0;
|
||||||
//修正时间轴
|
//修正时间轴
|
||||||
auto strRange = parser["Range"];
|
auto strRange = parser["Range"];
|
||||||
if (strRange.size()) {
|
if (strRange.size()) {
|
||||||
auto strStart = FindField(strRange.data(), "npt=", "-");
|
auto strStart = FindField(strRange.data(), "npt=", "-");
|
||||||
if (strStart == "now") {
|
if (strStart == "now") {
|
||||||
strStart = "0";
|
strStart = "0";
|
||||||
}
|
}
|
||||||
iSeekTo = 1000 * atof(strStart.data());
|
iSeekTo = 1000 * atof(strStart.data());
|
||||||
DebugL << "seekTo(ms):" << iSeekTo;
|
DebugL << "seekTo(ms):" << iSeekTo;
|
||||||
}
|
}
|
||||||
//设置相对时间戳
|
//设置相对时间戳
|
||||||
_stamp[0].setRelativeStamp(iSeekTo);
|
_stamp[0].setRelativeStamp(iSeekTo);
|
||||||
_stamp[1].setRelativeStamp(iSeekTo);
|
_stamp[1].setRelativeStamp(iSeekTo);
|
||||||
onPlayResult_l(SockException(Err_success, type == type_seek ? "resum rtsp success" : "rtsp play success"), type == type_seek);
|
onPlayResult_l(SockException(Err_success, type == type_seek ? "resum rtsp success" : "rtsp play success"), type == type_seek);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::onWholeRtspPacket(Parser &parser) {
|
void RtspPlayer::onWholeRtspPacket(Parser &parser) {
|
||||||
try {
|
try {
|
||||||
decltype(_onHandshake) fun;
|
decltype(_onHandshake) fun;
|
||||||
_onHandshake.swap(fun);
|
_onHandshake.swap(fun);
|
||||||
if(fun){
|
if(fun){
|
||||||
fun(parser);
|
fun(parser);
|
||||||
}
|
}
|
||||||
parser.Clear();
|
parser.Clear();
|
||||||
} catch (std::exception &err) {
|
} catch (std::exception &err) {
|
||||||
//定时器_pPlayTimer为空后表明握手结束了
|
//定时器_pPlayTimer为空后表明握手结束了
|
||||||
onPlayResult_l(SockException(Err_other, err.what()),!_pPlayTimer);
|
onPlayResult_l(SockException(Err_other, err.what()),!_pPlayTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,39 +610,39 @@ void RtspPlayer::sendReceiverReport(bool overTcp,int iTrackIndex){
|
|||||||
|
|
||||||
|
|
||||||
void RtspPlayer::onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx){
|
void RtspPlayer::onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx){
|
||||||
//统计丢包率
|
//统计丢包率
|
||||||
if (_aui16FirstSeq[trackidx] == 0 || rtppt->sequence < _aui16FirstSeq[trackidx]) {
|
if (_aui16FirstSeq[trackidx] == 0 || rtppt->sequence < _aui16FirstSeq[trackidx]) {
|
||||||
_aui16FirstSeq[trackidx] = rtppt->sequence;
|
_aui16FirstSeq[trackidx] = rtppt->sequence;
|
||||||
_aui64RtpRecv[trackidx] = 0;
|
_aui64RtpRecv[trackidx] = 0;
|
||||||
}
|
}
|
||||||
_aui64RtpRecv[trackidx] ++;
|
_aui64RtpRecv[trackidx] ++;
|
||||||
_aui16NowSeq[trackidx] = rtppt->sequence;
|
_aui16NowSeq[trackidx] = rtppt->sequence;
|
||||||
|
|
||||||
//计算相对时间戳
|
//计算相对时间戳
|
||||||
int64_t dts_out;
|
int64_t dts_out;
|
||||||
_stamp[trackidx].revise(rtppt->timeStamp,rtppt->timeStamp,dts_out,dts_out);
|
_stamp[trackidx].revise(rtppt->timeStamp,rtppt->timeStamp,dts_out,dts_out);
|
||||||
rtppt->timeStamp = dts_out;
|
rtppt->timeStamp = dts_out;
|
||||||
onRecvRTP_l(rtppt,_aTrackInfo[trackidx]);
|
onRecvRTP_l(rtppt,_aTrackInfo[trackidx]);
|
||||||
}
|
}
|
||||||
float RtspPlayer::getPacketLossRate(TrackType type) const{
|
float RtspPlayer::getPacketLossRate(TrackType type) const{
|
||||||
int iTrackIdx = getTrackIndexByTrackType(type);
|
int iTrackIdx = getTrackIndexByTrackType(type);
|
||||||
if(iTrackIdx == -1){
|
if(iTrackIdx == -1){
|
||||||
uint64_t totalRecv = 0;
|
uint64_t totalRecv = 0;
|
||||||
uint64_t totalSend = 0;
|
uint64_t totalSend = 0;
|
||||||
for (unsigned int i = 0; i < _aTrackInfo.size(); i++) {
|
for (unsigned int i = 0; i < _aTrackInfo.size(); i++) {
|
||||||
totalRecv += _aui64RtpRecv[i];
|
totalRecv += _aui64RtpRecv[i];
|
||||||
totalSend += (_aui16NowSeq[i] - _aui16FirstSeq[i] + 1);
|
totalSend += (_aui16NowSeq[i] - _aui16FirstSeq[i] + 1);
|
||||||
}
|
}
|
||||||
if(totalSend == 0){
|
if(totalSend == 0){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 1.0 - (double)totalRecv / totalSend;
|
return 1.0 - (double)totalRecv / totalSend;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_aui16NowSeq[iTrackIdx] - _aui16FirstSeq[iTrackIdx] + 1 == 0){
|
if(_aui16NowSeq[iTrackIdx] - _aui16FirstSeq[iTrackIdx] + 1 == 0){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 1.0 - (double)_aui64RtpRecv[iTrackIdx] / (_aui16NowSeq[iTrackIdx] - _aui16FirstSeq[iTrackIdx] + 1);
|
return 1.0 - (double)_aui64RtpRecv[iTrackIdx] / (_aui16NowSeq[iTrackIdx] - _aui16FirstSeq[iTrackIdx] + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t RtspPlayer::getProgressMilliSecond() const{
|
uint32_t RtspPlayer::getProgressMilliSecond() const{
|
||||||
@ -653,70 +653,70 @@ void RtspPlayer::seekToMilliSecond(uint32_t ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std::initializer_list<string> &header) {
|
void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std::initializer_list<string> &header) {
|
||||||
string key;
|
string key;
|
||||||
StrCaseMap header_map;
|
StrCaseMap header_map;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(auto &val : header){
|
for(auto &val : header){
|
||||||
if(++i % 2 == 0){
|
if(++i % 2 == 0){
|
||||||
header_map.emplace(key,val);
|
header_map.emplace(key,val);
|
||||||
}else{
|
}else{
|
||||||
key = val;
|
key = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendRtspRequest(cmd,url,header_map);
|
sendRtspRequest(cmd,url,header_map);
|
||||||
}
|
}
|
||||||
void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrCaseMap &header_const) {
|
void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrCaseMap &header_const) {
|
||||||
auto header = header_const;
|
auto header = header_const;
|
||||||
header.emplace("CSeq",StrPrinter << _uiCseq++);
|
header.emplace("CSeq",StrPrinter << _uiCseq++);
|
||||||
header.emplace("User-Agent",SERVER_NAME "(build in " __DATE__ " " __TIME__ ")");
|
header.emplace("User-Agent",SERVER_NAME "(build in " __DATE__ " " __TIME__ ")");
|
||||||
|
|
||||||
if(!_strSession.empty()){
|
if(!_strSession.empty()){
|
||||||
header.emplace("Session",_strSession);
|
header.emplace("Session",_strSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_rtspRealm.empty() && !(*this)[kRtspUser].empty()){
|
if(!_rtspRealm.empty() && !(*this)[kRtspUser].empty()){
|
||||||
if(!_rtspMd5Nonce.empty()){
|
if(!_rtspMd5Nonce.empty()){
|
||||||
//MD5认证
|
//MD5认证
|
||||||
/*
|
/*
|
||||||
response计算方法如下:
|
response计算方法如下:
|
||||||
RTSP客户端应该使用username + password并计算response如下:
|
RTSP客户端应该使用username + password并计算response如下:
|
||||||
(1)当password为MD5编码,则
|
(1)当password为MD5编码,则
|
||||||
response = md5( password:nonce:md5(public_method:url) );
|
response = md5( password:nonce:md5(public_method:url) );
|
||||||
(2)当password为ANSI字符串,则
|
(2)当password为ANSI字符串,则
|
||||||
response= md5( md5(username:realm:password):nonce:md5(public_method:url) );
|
response= md5( md5(username:realm:password):nonce:md5(public_method:url) );
|
||||||
*/
|
*/
|
||||||
string encrypted_pwd = (*this)[kRtspPwd];
|
string encrypted_pwd = (*this)[kRtspPwd];
|
||||||
if(!(*this)[kRtspPwdIsMD5].as<bool>()){
|
if(!(*this)[kRtspPwdIsMD5].as<bool>()){
|
||||||
encrypted_pwd = MD5((*this)[kRtspUser]+ ":" + _rtspRealm + ":" + encrypted_pwd).hexdigest();
|
encrypted_pwd = MD5((*this)[kRtspUser]+ ":" + _rtspRealm + ":" + encrypted_pwd).hexdigest();
|
||||||
}
|
}
|
||||||
auto response = MD5( encrypted_pwd + ":" + _rtspMd5Nonce + ":" + MD5(cmd + ":" + url).hexdigest()).hexdigest();
|
auto response = MD5( encrypted_pwd + ":" + _rtspMd5Nonce + ":" + MD5(cmd + ":" + url).hexdigest()).hexdigest();
|
||||||
_StrPrinter printer;
|
_StrPrinter printer;
|
||||||
printer << "Digest ";
|
printer << "Digest ";
|
||||||
printer << "username=\"" << (*this)[kRtspUser] << "\", ";
|
printer << "username=\"" << (*this)[kRtspUser] << "\", ";
|
||||||
printer << "realm=\"" << _rtspRealm << "\", ";
|
printer << "realm=\"" << _rtspRealm << "\", ";
|
||||||
printer << "nonce=\"" << _rtspMd5Nonce << "\", ";
|
printer << "nonce=\"" << _rtspMd5Nonce << "\", ";
|
||||||
printer << "uri=\"" << url << "\", ";
|
printer << "uri=\"" << url << "\", ";
|
||||||
printer << "response=\"" << response << "\"";
|
printer << "response=\"" << response << "\"";
|
||||||
header.emplace("Authorization",printer);
|
header.emplace("Authorization",printer);
|
||||||
}else if(!(*this)[kRtspPwdIsMD5].as<bool>()){
|
}else if(!(*this)[kRtspPwdIsMD5].as<bool>()){
|
||||||
//base64认证
|
//base64认证
|
||||||
string authStr = StrPrinter << (*this)[kRtspUser] << ":" << (*this)[kRtspPwd];
|
string authStr = StrPrinter << (*this)[kRtspUser] << ":" << (*this)[kRtspPwd];
|
||||||
char authStrBase64[1024] = {0};
|
char authStrBase64[1024] = {0};
|
||||||
av_base64_encode(authStrBase64,sizeof(authStrBase64),(uint8_t *)authStr.data(),authStr.size());
|
av_base64_encode(authStrBase64,sizeof(authStrBase64),(uint8_t *)authStr.data(),authStr.size());
|
||||||
header.emplace("Authorization",StrPrinter << "Basic " << authStrBase64 );
|
header.emplace("Authorization",StrPrinter << "Basic " << authStrBase64 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_StrPrinter printer;
|
_StrPrinter printer;
|
||||||
printer << cmd << " " << url << " RTSP/1.0\r\n";
|
printer << cmd << " " << url << " RTSP/1.0\r\n";
|
||||||
for (auto &pr : header){
|
for (auto &pr : header){
|
||||||
printer << pr.first << ": " << pr.second << "\r\n";
|
printer << pr.first << ": " << pr.second << "\r\n";
|
||||||
}
|
}
|
||||||
send(printer << "\r\n");
|
send(printer << "\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspPlayer::onRecvRTP_l(const RtpPacket::Ptr &pkt, const SdpTrack::Ptr &track) {
|
void RtspPlayer::onRecvRTP_l(const RtpPacket::Ptr &pkt, const SdpTrack::Ptr &track) {
|
||||||
_rtpTicker.resetTime();
|
_rtpTicker.resetTime();
|
||||||
onRecvRTP(pkt,track);
|
onRecvRTP(pkt,track);
|
||||||
|
|
||||||
int iTrackIndex = getTrackIndexByInterleaved(pkt->interleaved);
|
int iTrackIndex = getTrackIndexByInterleaved(pkt->interleaved);
|
||||||
@ -740,22 +740,22 @@ void RtspPlayer::onRecvRTP_l(const RtpPacket::Ptr &pkt, const SdpTrack::Ptr &tra
|
|||||||
|
|
||||||
}
|
}
|
||||||
void RtspPlayer::onPlayResult_l(const SockException &ex , bool handshakeCompleted) {
|
void RtspPlayer::onPlayResult_l(const SockException &ex , bool handshakeCompleted) {
|
||||||
WarnL << ex.getErrCode() << " " << ex.what();
|
WarnL << ex.getErrCode() << " " << ex.what();
|
||||||
|
|
||||||
if(!ex){
|
if(!ex){
|
||||||
//播放成功,恢复rtp接收超时定时器
|
//播放成功,恢复rtp接收超时定时器
|
||||||
_rtpTicker.resetTime();
|
_rtpTicker.resetTime();
|
||||||
weak_ptr<RtspPlayer> weakSelf = dynamic_pointer_cast<RtspPlayer>(shared_from_this());
|
weak_ptr<RtspPlayer> weakSelf = dynamic_pointer_cast<RtspPlayer>(shared_from_this());
|
||||||
int timeoutMS = (*this)[kMediaTimeoutMS].as<int>();
|
int timeoutMS = (*this)[kMediaTimeoutMS].as<int>();
|
||||||
//创建rtp数据接收超时检测定时器
|
//创建rtp数据接收超时检测定时器
|
||||||
_pRtpTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() {
|
_pRtpTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() {
|
||||||
auto strongSelf=weakSelf.lock();
|
auto strongSelf=weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(strongSelf->_rtpTicker.elapsedTime()> timeoutMS) {
|
if(strongSelf->_rtpTicker.elapsedTime()> timeoutMS) {
|
||||||
//接收rtp媒体数据包超时
|
//接收rtp媒体数据包超时
|
||||||
strongSelf->onPlayResult_l(SockException(Err_timeout,"receive rtp timeout"), true);
|
strongSelf->onPlayResult_l(SockException(Err_timeout,"receive rtp timeout"), true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -780,27 +780,27 @@ void RtspPlayer::onPlayResult_l(const SockException &ex , bool handshakeComplete
|
|||||||
}
|
}
|
||||||
|
|
||||||
int RtspPlayer::getTrackIndexByInterleaved(int interleaved) const{
|
int RtspPlayer::getTrackIndexByInterleaved(int interleaved) const{
|
||||||
for (unsigned int i = 0; i < _aTrackInfo.size(); i++) {
|
for (unsigned int i = 0; i < _aTrackInfo.size(); i++) {
|
||||||
if (_aTrackInfo[i]->_interleaved == interleaved) {
|
if (_aTrackInfo[i]->_interleaved == interleaved) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(_aTrackInfo.size() == 1){
|
if(_aTrackInfo.size() == 1){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RtspPlayer::getTrackIndexByTrackType(TrackType trackType) const {
|
int RtspPlayer::getTrackIndexByTrackType(TrackType trackType) const {
|
||||||
for (unsigned int i = 0; i < _aTrackInfo.size(); i++) {
|
for (unsigned int i = 0; i < _aTrackInfo.size(); i++) {
|
||||||
if (_aTrackInfo[i]->_type == trackType) {
|
if (_aTrackInfo[i]->_type == trackType) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(_aTrackInfo.size() == 1){
|
if(_aTrackInfo.size() == 1){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -50,18 +50,18 @@ namespace mediakit {
|
|||||||
//实现了rtsp播放器协议部分的功能,及数据接收功能
|
//实现了rtsp播放器协议部分的功能,及数据接收功能
|
||||||
class RtspPlayer: public PlayerBase,public TcpClient, public RtspSplitter, public RtpReceiver {
|
class RtspPlayer: public PlayerBase,public TcpClient, public RtspSplitter, public RtpReceiver {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtspPlayer> Ptr;
|
typedef std::shared_ptr<RtspPlayer> Ptr;
|
||||||
|
|
||||||
RtspPlayer(const EventPoller::Ptr &poller) ;
|
RtspPlayer(const EventPoller::Ptr &poller) ;
|
||||||
virtual ~RtspPlayer(void);
|
virtual ~RtspPlayer(void);
|
||||||
void play(const string &strUrl) override;
|
void play(const string &strUrl) override;
|
||||||
void pause(bool bPause) override;
|
void pause(bool bPause) override;
|
||||||
void teardown() override;
|
void teardown() override;
|
||||||
float getPacketLossRate(TrackType type) const override;
|
float getPacketLossRate(TrackType type) const override;
|
||||||
protected:
|
protected:
|
||||||
//派生类回调函数
|
//派生类回调函数
|
||||||
virtual bool onCheckSDP(const string &strSdp) = 0;
|
virtual bool onCheckSDP(const string &strSdp) = 0;
|
||||||
virtual void onRecvRTP(const RtpPacket::Ptr &pRtppt, const SdpTrack::Ptr &track) = 0;
|
virtual void onRecvRTP(const RtpPacket::Ptr &pRtppt, const SdpTrack::Ptr &track) = 0;
|
||||||
uint32_t getProgressMilliSecond() const;
|
uint32_t getProgressMilliSecond() const;
|
||||||
void seekToMilliSecond(uint32_t ms);
|
void seekToMilliSecond(uint32_t ms);
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ protected:
|
|||||||
* @param rtppt rtp数据包
|
* @param rtppt rtp数据包
|
||||||
* @param trackidx track索引
|
* @param trackidx track索引
|
||||||
*/
|
*/
|
||||||
void onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx) override;
|
void onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx) override;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,61 +95,61 @@ protected:
|
|||||||
*/
|
*/
|
||||||
virtual void onRtcpPacket(int iTrackidx, SdpTrack::Ptr &track, unsigned char *pucData, unsigned int uiLen);
|
virtual void onRtcpPacket(int iTrackidx, SdpTrack::Ptr &track, unsigned char *pucData, unsigned int uiLen);
|
||||||
|
|
||||||
/////////////TcpClient override/////////////
|
/////////////TcpClient override/////////////
|
||||||
void onConnect(const SockException &err) override;
|
void onConnect(const SockException &err) override;
|
||||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||||
void onErr(const SockException &ex) override;
|
void onErr(const SockException &ex) override;
|
||||||
private:
|
private:
|
||||||
void onRecvRTP_l(const RtpPacket::Ptr &pRtppt, const SdpTrack::Ptr &track);
|
void onRecvRTP_l(const RtpPacket::Ptr &pRtppt, const SdpTrack::Ptr &track);
|
||||||
void onPlayResult_l(const SockException &ex , bool handshakeCompleted);
|
void onPlayResult_l(const SockException &ex , bool handshakeCompleted);
|
||||||
|
|
||||||
int getTrackIndexByInterleaved(int interleaved) const;
|
int getTrackIndexByInterleaved(int interleaved) const;
|
||||||
int getTrackIndexByTrackType(TrackType trackType) const;
|
int getTrackIndexByTrackType(TrackType trackType) const;
|
||||||
|
|
||||||
void handleResSETUP(const Parser &parser, unsigned int uiTrackIndex);
|
void handleResSETUP(const Parser &parser, unsigned int uiTrackIndex);
|
||||||
void handleResDESCRIBE(const Parser &parser);
|
void handleResDESCRIBE(const Parser &parser);
|
||||||
bool handleAuthenticationFailure(const string &wwwAuthenticateParamsStr);
|
bool handleAuthenticationFailure(const string &wwwAuthenticateParamsStr);
|
||||||
void handleResPAUSE(const Parser &parser, int type);
|
void handleResPAUSE(const Parser &parser, int type);
|
||||||
|
|
||||||
//发送SETUP命令
|
//发送SETUP命令
|
||||||
void sendSetup(unsigned int uiTrackIndex);
|
void sendSetup(unsigned int uiTrackIndex);
|
||||||
void sendPause(int type , uint32_t ms);
|
void sendPause(int type , uint32_t ms);
|
||||||
void sendDescribe();
|
void sendDescribe();
|
||||||
|
|
||||||
void sendRtspRequest(const string &cmd, const string &url ,const StrCaseMap &header = StrCaseMap());
|
void sendRtspRequest(const string &cmd, const string &url ,const StrCaseMap &header = StrCaseMap());
|
||||||
void sendRtspRequest(const string &cmd, const string &url ,const std::initializer_list<string> &header);
|
void sendRtspRequest(const string &cmd, const string &url ,const std::initializer_list<string> &header);
|
||||||
void sendReceiverReport(bool overTcp,int iTrackIndex);
|
void sendReceiverReport(bool overTcp,int iTrackIndex);
|
||||||
void createUdpSockIfNecessary(int track_idx);
|
void createUdpSockIfNecessary(int track_idx);
|
||||||
private:
|
private:
|
||||||
string _strUrl;
|
string _strUrl;
|
||||||
vector<SdpTrack::Ptr> _aTrackInfo;
|
vector<SdpTrack::Ptr> _aTrackInfo;
|
||||||
function<void(const Parser&)> _onHandshake;
|
function<void(const Parser&)> _onHandshake;
|
||||||
Socket::Ptr _apRtpSock[2]; //RTP端口,trackid idx 为数组下标
|
Socket::Ptr _apRtpSock[2]; //RTP端口,trackid idx 为数组下标
|
||||||
Socket::Ptr _apRtcpSock[2];//RTCP端口,trackid idx 为数组下标
|
Socket::Ptr _apRtcpSock[2];//RTCP端口,trackid idx 为数组下标
|
||||||
|
|
||||||
//rtsp鉴权相关
|
//rtsp鉴权相关
|
||||||
string _rtspMd5Nonce;
|
string _rtspMd5Nonce;
|
||||||
string _rtspRealm;
|
string _rtspRealm;
|
||||||
//rtsp info
|
//rtsp info
|
||||||
string _strSession;
|
string _strSession;
|
||||||
unsigned int _uiCseq = 1;
|
unsigned int _uiCseq = 1;
|
||||||
string _strContentBase;
|
string _strContentBase;
|
||||||
Rtsp::eRtpType _eType = Rtsp::RTP_TCP;
|
Rtsp::eRtpType _eType = Rtsp::RTP_TCP;
|
||||||
|
|
||||||
/* 丢包率统计需要用到的参数 */
|
/* 丢包率统计需要用到的参数 */
|
||||||
uint16_t _aui16FirstSeq[2] = { 0 , 0};
|
uint16_t _aui16FirstSeq[2] = { 0 , 0};
|
||||||
uint16_t _aui16NowSeq[2] = { 0 , 0 };
|
uint16_t _aui16NowSeq[2] = { 0 , 0 };
|
||||||
uint64_t _aui64RtpRecv[2] = { 0 , 0};
|
uint64_t _aui64RtpRecv[2] = { 0 , 0};
|
||||||
|
|
||||||
//超时功能实现
|
//超时功能实现
|
||||||
Ticker _rtpTicker;
|
Ticker _rtpTicker;
|
||||||
std::shared_ptr<Timer> _pPlayTimer;
|
std::shared_ptr<Timer> _pPlayTimer;
|
||||||
std::shared_ptr<Timer> _pRtpTimer;
|
std::shared_ptr<Timer> _pRtpTimer;
|
||||||
|
|
||||||
//时间戳
|
//时间戳
|
||||||
Stamp _stamp[2];
|
Stamp _stamp[2];
|
||||||
|
|
||||||
//rtcp相关
|
//rtcp相关
|
||||||
RtcpCounter _aRtcpCnt[2]; //rtcp统计,trackid idx 为数组下标
|
RtcpCounter _aRtcpCnt[2]; //rtcp统计,trackid idx 为数组下标
|
||||||
Ticker _aRtcpTicker[2]; //rtcp发送时间,trackid idx 为数组下标
|
Ticker _aRtcpTicker[2]; //rtcp发送时间,trackid idx 为数组下标
|
||||||
};
|
};
|
||||||
|
@ -43,9 +43,9 @@ namespace mediakit {
|
|||||||
|
|
||||||
class RtspPlayerImp: public PlayerImp<RtspPlayer,RtspDemuxer> {
|
class RtspPlayerImp: public PlayerImp<RtspPlayer,RtspDemuxer> {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtspPlayerImp> Ptr;
|
typedef std::shared_ptr<RtspPlayerImp> Ptr;
|
||||||
RtspPlayerImp(const EventPoller::Ptr &poller) : PlayerImp<RtspPlayer,RtspDemuxer>(poller){}
|
RtspPlayerImp(const EventPoller::Ptr &poller) : PlayerImp<RtspPlayer,RtspDemuxer>(poller){}
|
||||||
virtual ~RtspPlayerImp(){
|
virtual ~RtspPlayerImp(){
|
||||||
DebugL<<endl;
|
DebugL<<endl;
|
||||||
};
|
};
|
||||||
float getProgress() const override{
|
float getProgress() const override{
|
||||||
@ -60,17 +60,17 @@ public:
|
|||||||
seekToMilliSecond(fProgress * getDuration() * 1000);
|
seekToMilliSecond(fProgress * getDuration() * 1000);
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
//派生类回调函数
|
//派生类回调函数
|
||||||
bool onCheckSDP(const string &sdp) override {
|
bool onCheckSDP(const string &sdp) override {
|
||||||
_pRtspMediaSrc = dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc);
|
_pRtspMediaSrc = dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc);
|
||||||
if(_pRtspMediaSrc){
|
if(_pRtspMediaSrc){
|
||||||
_pRtspMediaSrc->setSdp(sdp);
|
_pRtspMediaSrc->setSdp(sdp);
|
||||||
}
|
}
|
||||||
_delegate.reset(new RtspDemuxer);
|
_delegate.reset(new RtspDemuxer);
|
||||||
_delegate->loadSdp(sdp);
|
_delegate->loadSdp(sdp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void onRecvRTP(const RtpPacket::Ptr &rtp, const SdpTrack::Ptr &track) override {
|
void onRecvRTP(const RtpPacket::Ptr &rtp, const SdpTrack::Ptr &track) override {
|
||||||
if(_pRtspMediaSrc){
|
if(_pRtspMediaSrc){
|
||||||
// rtsp直接代理是无法判断该rtp是否是I帧,所以GOP缓存基本是无效的
|
// rtsp直接代理是无法判断该rtp是否是I帧,所以GOP缓存基本是无效的
|
||||||
// 为了减少内存使用,那么我们设置为一直关键帧以便清空GOP缓存
|
// 为了减少内存使用,那么我们设置为一直关键帧以便清空GOP缓存
|
||||||
@ -99,7 +99,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
RtspMediaSource::Ptr _pRtspMediaSrc;
|
RtspMediaSource::Ptr _pRtspMediaSrc;
|
||||||
int _maxAnalysisMS = 0;
|
int _maxAnalysisMS = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -69,20 +69,20 @@ private:
|
|||||||
|
|
||||||
class RtspSession: public TcpSession, public RtspSplitter, public RtpReceiver , public MediaSourceEvent{
|
class RtspSession: public TcpSession, public RtspSplitter, public RtpReceiver , public MediaSourceEvent{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtspSession> Ptr;
|
typedef std::shared_ptr<RtspSession> Ptr;
|
||||||
typedef std::function<void(const string &realm)> onGetRealm;
|
typedef std::function<void(const string &realm)> onGetRealm;
|
||||||
//encrypted为true是则表明是md5加密的密码,否则是明文密码
|
//encrypted为true是则表明是md5加密的密码,否则是明文密码
|
||||||
//在请求明文密码时如果提供md5密码者则会导致认证失败
|
//在请求明文密码时如果提供md5密码者则会导致认证失败
|
||||||
typedef std::function<void(bool encrypted,const string &pwd_or_md5)> onAuth;
|
typedef std::function<void(bool encrypted,const string &pwd_or_md5)> onAuth;
|
||||||
|
|
||||||
RtspSession(const Socket::Ptr &pSock);
|
RtspSession(const Socket::Ptr &pSock);
|
||||||
virtual ~RtspSession();
|
virtual ~RtspSession();
|
||||||
////TcpSession override////
|
////TcpSession override////
|
||||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||||
void onError(const SockException &err) override;
|
void onError(const SockException &err) override;
|
||||||
void onManager() override;
|
void onManager() override;
|
||||||
protected:
|
protected:
|
||||||
//RtspSplitter override
|
//RtspSplitter override
|
||||||
/**
|
/**
|
||||||
* 收到完整的rtsp包回调,包括sdp等content数据
|
* 收到完整的rtsp包回调,包括sdp等content数据
|
||||||
* @param parser rtsp包
|
* @param parser rtsp包
|
||||||
@ -103,14 +103,14 @@ protected:
|
|||||||
*/
|
*/
|
||||||
int64_t getContentLength(Parser &parser) override;
|
int64_t getContentLength(Parser &parser) override;
|
||||||
|
|
||||||
//RtpReceiver override
|
//RtpReceiver override
|
||||||
void onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx) override;
|
void onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx) override;
|
||||||
//MediaSourceEvent override
|
//MediaSourceEvent override
|
||||||
bool close(MediaSource &sender,bool force) override ;
|
bool close(MediaSource &sender,bool force) override ;
|
||||||
void onNoneReader(MediaSource &sender) override;
|
void onNoneReader(MediaSource &sender) override;
|
||||||
int totalReaderCount(MediaSource &sender) override;
|
int totalReaderCount(MediaSource &sender) override;
|
||||||
|
|
||||||
//TcpSession override
|
//TcpSession override
|
||||||
int send(const Buffer::Ptr &pkt) override;
|
int send(const Buffer::Ptr &pkt) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,121 +122,121 @@ protected:
|
|||||||
*/
|
*/
|
||||||
virtual void onRtcpPacket(int iTrackidx, SdpTrack::Ptr &track, unsigned char *pucData, unsigned int uiLen);
|
virtual void onRtcpPacket(int iTrackidx, SdpTrack::Ptr &track, unsigned char *pucData, unsigned int uiLen);
|
||||||
private:
|
private:
|
||||||
//处理options方法,获取服务器能力
|
//处理options方法,获取服务器能力
|
||||||
void handleReq_Options(const Parser &parser);
|
void handleReq_Options(const Parser &parser);
|
||||||
//处理describe方法,请求服务器rtsp sdp信息
|
//处理describe方法,请求服务器rtsp sdp信息
|
||||||
void handleReq_Describe(const Parser &parser);
|
void handleReq_Describe(const Parser &parser);
|
||||||
//处理ANNOUNCE方法,请求推流,附带sdp
|
//处理ANNOUNCE方法,请求推流,附带sdp
|
||||||
void handleReq_ANNOUNCE(const Parser &parser);
|
void handleReq_ANNOUNCE(const Parser &parser);
|
||||||
//处理record方法,开始推流
|
//处理record方法,开始推流
|
||||||
void handleReq_RECORD(const Parser &parser);
|
void handleReq_RECORD(const Parser &parser);
|
||||||
//处理setup方法,播放和推流协商rtp传输方式用
|
//处理setup方法,播放和推流协商rtp传输方式用
|
||||||
void handleReq_Setup(const Parser &parser);
|
void handleReq_Setup(const Parser &parser);
|
||||||
//处理play方法,开始或恢复播放
|
//处理play方法,开始或恢复播放
|
||||||
void handleReq_Play(const Parser &parser);
|
void handleReq_Play(const Parser &parser);
|
||||||
//处理pause方法,暂停播放
|
//处理pause方法,暂停播放
|
||||||
void handleReq_Pause(const Parser &parser);
|
void handleReq_Pause(const Parser &parser);
|
||||||
//处理teardown方法,结束播放
|
//处理teardown方法,结束播放
|
||||||
void handleReq_Teardown(const Parser &parser);
|
void handleReq_Teardown(const Parser &parser);
|
||||||
//处理Get方法,rtp over http才用到
|
//处理Get方法,rtp over http才用到
|
||||||
void handleReq_Get(const Parser &parser);
|
void handleReq_Get(const Parser &parser);
|
||||||
//处理Post方法,rtp over http才用到
|
//处理Post方法,rtp over http才用到
|
||||||
void handleReq_Post(const Parser &parser);
|
void handleReq_Post(const Parser &parser);
|
||||||
//处理SET_PARAMETER、GET_PARAMETER方法,一般用于心跳
|
//处理SET_PARAMETER、GET_PARAMETER方法,一般用于心跳
|
||||||
void handleReq_SET_PARAMETER(const Parser &parser);
|
void handleReq_SET_PARAMETER(const Parser &parser);
|
||||||
|
|
||||||
//rtsp资源未找到
|
//rtsp资源未找到
|
||||||
void inline send_StreamNotFound();
|
void inline send_StreamNotFound();
|
||||||
//不支持的传输模式
|
//不支持的传输模式
|
||||||
void inline send_UnsupportedTransport();
|
void inline send_UnsupportedTransport();
|
||||||
//会话id错误
|
//会话id错误
|
||||||
void inline send_SessionNotFound();
|
void inline send_SessionNotFound();
|
||||||
//一般rtsp服务器打开端口失败时触发
|
//一般rtsp服务器打开端口失败时触发
|
||||||
void inline send_NotAcceptable();
|
void inline send_NotAcceptable();
|
||||||
//ssrc转字符串
|
//ssrc转字符串
|
||||||
inline string printSSRC(uint32_t ui32Ssrc);
|
inline string printSSRC(uint32_t ui32Ssrc);
|
||||||
|
|
||||||
//获取track下标
|
//获取track下标
|
||||||
inline int getTrackIndexByTrackType(TrackType type);
|
inline int getTrackIndexByTrackType(TrackType type);
|
||||||
inline int getTrackIndexByControlSuffix(const string &controlSuffix);
|
inline int getTrackIndexByControlSuffix(const string &controlSuffix);
|
||||||
inline int getTrackIndexByInterleaved(int interleaved);
|
inline int getTrackIndexByInterleaved(int interleaved);
|
||||||
|
|
||||||
//一般用于接收udp打洞包,也用于rtsp推流
|
//一般用于接收udp打洞包,也用于rtsp推流
|
||||||
inline void onRcvPeerUdpData(int intervaled, const Buffer::Ptr &pBuf, const struct sockaddr &addr);
|
inline void onRcvPeerUdpData(int intervaled, const Buffer::Ptr &pBuf, const struct sockaddr &addr);
|
||||||
//配合onRcvPeerUdpData使用
|
//配合onRcvPeerUdpData使用
|
||||||
inline void startListenPeerUdpData(int iTrackIdx);
|
inline void startListenPeerUdpData(int iTrackIdx);
|
||||||
|
|
||||||
////rtsp专有认证相关////
|
////rtsp专有认证相关////
|
||||||
//认证成功
|
//认证成功
|
||||||
void onAuthSuccess();
|
void onAuthSuccess();
|
||||||
//认证失败
|
//认证失败
|
||||||
void onAuthFailed(const string &realm,const string &why,bool close = true);
|
void onAuthFailed(const string &realm,const string &why,bool close = true);
|
||||||
//开始走rtsp专有认证流程
|
//开始走rtsp专有认证流程
|
||||||
void onAuthUser(const string &realm,const string &authorization);
|
void onAuthUser(const string &realm,const string &authorization);
|
||||||
//校验base64方式的认证加密
|
//校验base64方式的认证加密
|
||||||
void onAuthBasic(const string &realm,const string &strBase64);
|
void onAuthBasic(const string &realm,const string &strBase64);
|
||||||
//校验md5方式的认证加密
|
//校验md5方式的认证加密
|
||||||
void onAuthDigest(const string &realm,const string &strMd5);
|
void onAuthDigest(const string &realm,const string &strMd5);
|
||||||
|
|
||||||
//发送rtp给客户端
|
//发送rtp给客户端
|
||||||
void sendRtpPacket(const RtpPacket::Ptr &pkt);
|
void sendRtpPacket(const RtpPacket::Ptr &pkt);
|
||||||
//回复客户端
|
//回复客户端
|
||||||
bool sendRtspResponse(const string &res_code,const std::initializer_list<string> &header, const string &sdp = "" , const char *protocol = "RTSP/1.0");
|
bool sendRtspResponse(const string &res_code,const std::initializer_list<string> &header, const string &sdp = "" , const char *protocol = "RTSP/1.0");
|
||||||
bool sendRtspResponse(const string &res_code,const StrCaseMap &header = StrCaseMap(), const string &sdp = "",const char *protocol = "RTSP/1.0");
|
bool sendRtspResponse(const string &res_code,const StrCaseMap &header = StrCaseMap(), const string &sdp = "",const char *protocol = "RTSP/1.0");
|
||||||
//服务器发送rtcp
|
//服务器发送rtcp
|
||||||
void sendSenderReport(bool overTcp,int iTrackIndex);
|
void sendSenderReport(bool overTcp,int iTrackIndex);
|
||||||
//设置socket标志
|
//设置socket标志
|
||||||
void setSocketFlags();
|
void setSocketFlags();
|
||||||
private:
|
private:
|
||||||
//用于判断客户端是否超时
|
//用于判断客户端是否超时
|
||||||
Ticker _ticker;
|
Ticker _ticker;
|
||||||
//收到的seq,回复时一致
|
//收到的seq,回复时一致
|
||||||
int _iCseq = 0;
|
int _iCseq = 0;
|
||||||
//ContentBase
|
//ContentBase
|
||||||
string _strContentBase;
|
string _strContentBase;
|
||||||
//Session号
|
//Session号
|
||||||
string _strSession;
|
string _strSession;
|
||||||
//是否第一次播放,第一次播放需要鉴权,第二次播放属于暂停恢复
|
//是否第一次播放,第一次播放需要鉴权,第二次播放属于暂停恢复
|
||||||
bool _bFirstPlay = true;
|
bool _bFirstPlay = true;
|
||||||
//url解析后保存的相关信息
|
//url解析后保存的相关信息
|
||||||
MediaInfo _mediaInfo;
|
MediaInfo _mediaInfo;
|
||||||
//rtsp播放器绑定的直播源
|
//rtsp播放器绑定的直播源
|
||||||
std::weak_ptr<RtspMediaSource> _pMediaSrc;
|
std::weak_ptr<RtspMediaSource> _pMediaSrc;
|
||||||
//直播源读取器
|
//直播源读取器
|
||||||
RingBuffer<RtpPacket::Ptr>::RingReader::Ptr _pRtpReader;
|
RingBuffer<RtpPacket::Ptr>::RingReader::Ptr _pRtpReader;
|
||||||
//推流或拉流客户端采用的rtp传输方式
|
//推流或拉流客户端采用的rtp传输方式
|
||||||
Rtsp::eRtpType _rtpType = Rtsp::RTP_Invalid;
|
Rtsp::eRtpType _rtpType = Rtsp::RTP_Invalid;
|
||||||
//sdp里面有效的track,包含音频或视频
|
//sdp里面有效的track,包含音频或视频
|
||||||
vector<SdpTrack::Ptr> _aTrackInfo;
|
vector<SdpTrack::Ptr> _aTrackInfo;
|
||||||
////////RTP over udp////////
|
////////RTP over udp////////
|
||||||
//RTP端口,trackid idx 为数组下标
|
//RTP端口,trackid idx 为数组下标
|
||||||
Socket::Ptr _apRtpSock[2];
|
Socket::Ptr _apRtpSock[2];
|
||||||
//RTCP端口,trackid idx 为数组下标
|
//RTCP端口,trackid idx 为数组下标
|
||||||
Socket::Ptr _apRtcpSock[2];
|
Socket::Ptr _apRtcpSock[2];
|
||||||
//标记是否收到播放的udp打洞包,收到播放的udp打洞包后才能知道其外网udp端口号
|
//标记是否收到播放的udp打洞包,收到播放的udp打洞包后才能知道其外网udp端口号
|
||||||
unordered_set<int> _udpSockConnected;
|
unordered_set<int> _udpSockConnected;
|
||||||
////////RTP over udp_multicast////////
|
////////RTP over udp_multicast////////
|
||||||
//共享的rtp组播对象
|
//共享的rtp组播对象
|
||||||
RtpMultiCaster::Ptr _multicaster;
|
RtpMultiCaster::Ptr _multicaster;
|
||||||
|
|
||||||
//登录认证
|
//登录认证
|
||||||
string _strNonce;
|
string _strNonce;
|
||||||
//消耗的总流量
|
//消耗的总流量
|
||||||
uint64_t _ui64TotalBytes = 0;
|
uint64_t _ui64TotalBytes = 0;
|
||||||
|
|
||||||
//RTSP over HTTP
|
//RTSP over HTTP
|
||||||
//quicktime 请求rtsp会产生两次tcp连接,
|
//quicktime 请求rtsp会产生两次tcp连接,
|
||||||
//一次发送 get 一次发送post,需要通过x-sessioncookie关联起来
|
//一次发送 get 一次发送post,需要通过x-sessioncookie关联起来
|
||||||
string _http_x_sessioncookie;
|
string _http_x_sessioncookie;
|
||||||
function<void(const Buffer::Ptr &pBuf)> _onRecv;
|
function<void(const Buffer::Ptr &pBuf)> _onRecv;
|
||||||
//是否开始发送rtp
|
//是否开始发送rtp
|
||||||
bool _enableSendRtp;
|
bool _enableSendRtp;
|
||||||
//rtsp推流相关
|
//rtsp推流相关
|
||||||
RtspMediaSourceImp::Ptr _pushSrc;
|
RtspMediaSourceImp::Ptr _pushSrc;
|
||||||
//rtcp统计,trackid idx 为数组下标
|
//rtcp统计,trackid idx 为数组下标
|
||||||
RtcpCounter _aRtcpCnt[2];
|
RtcpCounter _aRtcpCnt[2];
|
||||||
//rtcp发送时间,trackid idx 为数组下标
|
//rtcp发送时间,trackid idx 为数组下标
|
||||||
Ticker _aRtcpTicker[2];
|
Ticker _aRtcpTicker[2];
|
||||||
//时间戳修整器
|
//时间戳修整器
|
||||||
Stamp _stamp[2];
|
Stamp _stamp[2];
|
||||||
};
|
};
|
||||||
|
@ -38,76 +38,76 @@ UDPServer::UDPServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UDPServer::~UDPServer() {
|
UDPServer::~UDPServer() {
|
||||||
InfoL;
|
InfoL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket::Ptr UDPServer::getSock(const EventPoller::Ptr &poller,const char* strLocalIp, int intervaled,uint16_t iLocalPort) {
|
Socket::Ptr UDPServer::getSock(const EventPoller::Ptr &poller,const char* strLocalIp, int intervaled,uint16_t iLocalPort) {
|
||||||
lock_guard<mutex> lck(_mtxUpdSock);
|
lock_guard<mutex> lck(_mtxUpdSock);
|
||||||
string strKey = StrPrinter << strLocalIp << ":" << intervaled << endl;
|
string strKey = StrPrinter << strLocalIp << ":" << intervaled << endl;
|
||||||
auto it = _mapUpdSock.find(strKey);
|
auto it = _mapUpdSock.find(strKey);
|
||||||
if (it == _mapUpdSock.end()) {
|
if (it == _mapUpdSock.end()) {
|
||||||
Socket::Ptr pSock(new Socket(poller));
|
Socket::Ptr pSock(new Socket(poller));
|
||||||
//InfoL<<localIp;
|
//InfoL<<localIp;
|
||||||
if (!pSock->bindUdpSock(iLocalPort, strLocalIp)) {
|
if (!pSock->bindUdpSock(iLocalPort, strLocalIp)) {
|
||||||
//分配失败
|
//分配失败
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
pSock->setOnRead(bind(&UDPServer::onRcvData, this, intervaled, placeholders::_1,placeholders::_2));
|
pSock->setOnRead(bind(&UDPServer::onRcvData, this, intervaled, placeholders::_1,placeholders::_2));
|
||||||
pSock->setOnErr(bind(&UDPServer::onErr, this, strKey, placeholders::_1));
|
pSock->setOnErr(bind(&UDPServer::onErr, this, strKey, placeholders::_1));
|
||||||
_mapUpdSock[strKey] = pSock;
|
_mapUpdSock[strKey] = pSock;
|
||||||
DebugL << strLocalIp << " " << pSock->get_local_port() << " " << intervaled;
|
DebugL << strLocalIp << " " << pSock->get_local_port() << " " << intervaled;
|
||||||
return pSock;
|
return pSock;
|
||||||
}
|
}
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDPServer::listenPeer(const char* strPeerIp, void* pSelf, const onRecvData& cb) {
|
void UDPServer::listenPeer(const char* strPeerIp, void* pSelf, const onRecvData& cb) {
|
||||||
lock_guard<mutex> lck(_mtxDataHandler);
|
lock_guard<mutex> lck(_mtxDataHandler);
|
||||||
auto &mapRef = _mapDataHandler[strPeerIp];
|
auto &mapRef = _mapDataHandler[strPeerIp];
|
||||||
mapRef.emplace(pSelf, cb);
|
mapRef.emplace(pSelf, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDPServer::stopListenPeer(const char* strPeerIp, void* pSelf) {
|
void UDPServer::stopListenPeer(const char* strPeerIp, void* pSelf) {
|
||||||
lock_guard<mutex> lck(_mtxDataHandler);
|
lock_guard<mutex> lck(_mtxDataHandler);
|
||||||
auto it0 = _mapDataHandler.find(strPeerIp);
|
auto it0 = _mapDataHandler.find(strPeerIp);
|
||||||
if (it0 == _mapDataHandler.end()) {
|
if (it0 == _mapDataHandler.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto &mapRef = it0->second;
|
auto &mapRef = it0->second;
|
||||||
auto it1 = mapRef.find(pSelf);
|
auto it1 = mapRef.find(pSelf);
|
||||||
if (it1 != mapRef.end()) {
|
if (it1 != mapRef.end()) {
|
||||||
mapRef.erase(it1);
|
mapRef.erase(it1);
|
||||||
}
|
}
|
||||||
if (mapRef.size() == 0) {
|
if (mapRef.size() == 0) {
|
||||||
_mapDataHandler.erase(it0);
|
_mapDataHandler.erase(it0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
void UDPServer::onErr(const string& strKey, const SockException& err) {
|
void UDPServer::onErr(const string& strKey, const SockException& err) {
|
||||||
WarnL << err.what();
|
WarnL << err.what();
|
||||||
lock_guard<mutex> lck(_mtxUpdSock);
|
lock_guard<mutex> lck(_mtxUpdSock);
|
||||||
_mapUpdSock.erase(strKey);
|
_mapUpdSock.erase(strKey);
|
||||||
}
|
}
|
||||||
void UDPServer::onRcvData(int intervaled, const Buffer::Ptr &pBuf, struct sockaddr* pPeerAddr) {
|
void UDPServer::onRcvData(int intervaled, const Buffer::Ptr &pBuf, struct sockaddr* pPeerAddr) {
|
||||||
//TraceL << trackIndex;
|
//TraceL << trackIndex;
|
||||||
struct sockaddr_in *in = (struct sockaddr_in *) pPeerAddr;
|
struct sockaddr_in *in = (struct sockaddr_in *) pPeerAddr;
|
||||||
string peerIp = inet_ntoa(in->sin_addr);
|
string peerIp = inet_ntoa(in->sin_addr);
|
||||||
lock_guard<mutex> lck(_mtxDataHandler);
|
lock_guard<mutex> lck(_mtxDataHandler);
|
||||||
auto it0 = _mapDataHandler.find(peerIp);
|
auto it0 = _mapDataHandler.find(peerIp);
|
||||||
if (it0 == _mapDataHandler.end()) {
|
if (it0 == _mapDataHandler.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto &mapRef = it0->second;
|
auto &mapRef = it0->second;
|
||||||
for (auto it1 = mapRef.begin(); it1 != mapRef.end(); ++it1) {
|
for (auto it1 = mapRef.begin(); it1 != mapRef.end(); ++it1) {
|
||||||
onRecvData &funRef = it1->second;
|
onRecvData &funRef = it1->second;
|
||||||
if (!funRef(intervaled, pBuf, pPeerAddr)) {
|
if (!funRef(intervaled, pBuf, pPeerAddr)) {
|
||||||
it1 = mapRef.erase(it1);
|
it1 = mapRef.erase(it1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mapRef.size() == 0) {
|
if (mapRef.size() == 0) {
|
||||||
_mapDataHandler.erase(it0);
|
_mapDataHandler.erase(it0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -43,21 +43,21 @@ namespace mediakit {
|
|||||||
|
|
||||||
class UDPServer : public std::enable_shared_from_this<UDPServer> {
|
class UDPServer : public std::enable_shared_from_this<UDPServer> {
|
||||||
public:
|
public:
|
||||||
typedef function< bool(int intervaled, const Buffer::Ptr &buffer, struct sockaddr *peer_addr)> onRecvData;
|
typedef function< bool(int intervaled, const Buffer::Ptr &buffer, struct sockaddr *peer_addr)> onRecvData;
|
||||||
~UDPServer();
|
~UDPServer();
|
||||||
static UDPServer &Instance();
|
static UDPServer &Instance();
|
||||||
Socket::Ptr getSock(const EventPoller::Ptr &poller,const char *strLocalIp, int intervaled,uint16_t iLocalPort = 0);
|
Socket::Ptr getSock(const EventPoller::Ptr &poller,const char *strLocalIp, int intervaled,uint16_t iLocalPort = 0);
|
||||||
void listenPeer(const char *strPeerIp, void *pSelf, const onRecvData &cb);
|
void listenPeer(const char *strPeerIp, void *pSelf, const onRecvData &cb);
|
||||||
void stopListenPeer(const char *strPeerIp, void *pSelf);
|
void stopListenPeer(const char *strPeerIp, void *pSelf);
|
||||||
private:
|
private:
|
||||||
UDPServer();
|
UDPServer();
|
||||||
void onRcvData(int intervaled, const Buffer::Ptr &pBuf,struct sockaddr *pPeerAddr);
|
void onRcvData(int intervaled, const Buffer::Ptr &pBuf,struct sockaddr *pPeerAddr);
|
||||||
void onErr(const string &strKey,const SockException &err);
|
void onErr(const string &strKey,const SockException &err);
|
||||||
unordered_map<string, Socket::Ptr> _mapUpdSock;
|
unordered_map<string, Socket::Ptr> _mapUpdSock;
|
||||||
mutex _mtxUpdSock;
|
mutex _mtxUpdSock;
|
||||||
|
|
||||||
unordered_map<string, unordered_map<void *, onRecvData> > _mapDataHandler;
|
unordered_map<string, unordered_map<void *, onRecvData> > _mapDataHandler;
|
||||||
mutex _mtxDataHandler;
|
mutex _mtxDataHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -48,29 +48,29 @@ ShellSession::~ShellSession() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShellSession::onRecv(const Buffer::Ptr&buf) {
|
void ShellSession::onRecv(const Buffer::Ptr&buf) {
|
||||||
//DebugL << hexdump(buf->data(), buf->size());
|
//DebugL << hexdump(buf->data(), buf->size());
|
||||||
GET_CONFIG(uint32_t,maxReqSize,Shell::kMaxReqSize);
|
GET_CONFIG(uint32_t,maxReqSize,Shell::kMaxReqSize);
|
||||||
if (_strRecvBuf.size() + buf->size() >= maxReqSize) {
|
if (_strRecvBuf.size() + buf->size() >= maxReqSize) {
|
||||||
shutdown(SockException(Err_other,"recv buffer overflow"));
|
shutdown(SockException(Err_other,"recv buffer overflow"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_beatTicker.resetTime();
|
_beatTicker.resetTime();
|
||||||
_strRecvBuf.append(buf->data(), buf->size());
|
_strRecvBuf.append(buf->data(), buf->size());
|
||||||
if (_strRecvBuf.find("\xff\xf4\xff\0xfd\x06") != std::string::npos) {
|
if (_strRecvBuf.find("\xff\xf4\xff\0xfd\x06") != std::string::npos) {
|
||||||
send("\033[0m\r\n Bye bye!\r\n");
|
send("\033[0m\r\n Bye bye!\r\n");
|
||||||
shutdown(SockException(Err_other,"received Ctrl+C"));
|
shutdown(SockException(Err_other,"received Ctrl+C"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
size_t index;
|
size_t index;
|
||||||
string line;
|
string line;
|
||||||
while ((index = _strRecvBuf.find("\r\n")) != std::string::npos) {
|
while ((index = _strRecvBuf.find("\r\n")) != std::string::npos) {
|
||||||
line = _strRecvBuf.substr(0, index);
|
line = _strRecvBuf.substr(0, index);
|
||||||
_strRecvBuf.erase(0, index + 2);
|
_strRecvBuf.erase(0, index + 2);
|
||||||
if (!onCommandLine(line)) {
|
if (!onCommandLine(line)) {
|
||||||
shutdown(SockException(Err_other,"exit cmd"));
|
shutdown(SockException(Err_other,"exit cmd"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShellSession::onError(const SockException &err){
|
void ShellSession::onError(const SockException &err){
|
||||||
@ -78,19 +78,19 @@ void ShellSession::onError(const SockException &err){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShellSession::onManager() {
|
void ShellSession::onManager() {
|
||||||
if (_beatTicker.elapsedTime() > 1000 * 60 * 5) {
|
if (_beatTicker.elapsedTime() > 1000 * 60 * 5) {
|
||||||
//5 miniutes for alive
|
//5 miniutes for alive
|
||||||
shutdown(SockException(Err_timeout,"session timeout"));
|
shutdown(SockException(Err_timeout,"session timeout"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool ShellSession::onCommandLine(const string& line) {
|
inline bool ShellSession::onCommandLine(const string& line) {
|
||||||
auto loginInterceptor = _loginInterceptor;
|
auto loginInterceptor = _loginInterceptor;
|
||||||
if (loginInterceptor) {
|
if (loginInterceptor) {
|
||||||
bool ret = loginInterceptor(line);
|
bool ret = loginInterceptor(line);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
std::shared_ptr<stringstream> ss(new stringstream);
|
std::shared_ptr<stringstream> ss(new stringstream);
|
||||||
CMDRegister::Instance()(line,ss);
|
CMDRegister::Instance()(line,ss);
|
||||||
@ -102,21 +102,21 @@ inline bool ShellSession::onCommandLine(const string& line) {
|
|||||||
send("\r\n");
|
send("\r\n");
|
||||||
}
|
}
|
||||||
printShellPrefix();
|
printShellPrefix();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void ShellSession::pleaseInputUser() {
|
inline void ShellSession::pleaseInputUser() {
|
||||||
send("\033[0m");
|
send("\033[0m");
|
||||||
send(StrPrinter << SERVER_NAME << " login: " << endl);
|
send(StrPrinter << SERVER_NAME << " login: " << endl);
|
||||||
_loginInterceptor = [this](const string &user_name) {
|
_loginInterceptor = [this](const string &user_name) {
|
||||||
_strUserName=user_name;
|
_strUserName=user_name;
|
||||||
pleaseInputPasswd();
|
pleaseInputPasswd();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
inline void ShellSession::pleaseInputPasswd() {
|
inline void ShellSession::pleaseInputPasswd() {
|
||||||
send("Password: \033[8m");
|
send("Password: \033[8m");
|
||||||
_loginInterceptor = [this](const string &passwd) {
|
_loginInterceptor = [this](const string &passwd) {
|
||||||
auto onAuth = [this](const string &errMessage){
|
auto onAuth = [this](const string &errMessage){
|
||||||
if(!errMessage.empty()){
|
if(!errMessage.empty()){
|
||||||
//鉴权失败
|
//鉴权失败
|
||||||
@ -157,12 +157,12 @@ inline void ShellSession::pleaseInputPasswd() {
|
|||||||
//如果无人监听shell登录事件,那么默认shell无法登录
|
//如果无人监听shell登录事件,那么默认shell无法登录
|
||||||
onAuth("please listen kBroadcastShellLogin event");
|
onAuth("please listen kBroadcastShellLogin event");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void ShellSession::printShellPrefix() {
|
inline void ShellSession::printShellPrefix() {
|
||||||
send(StrPrinter << _strUserName << "@" << SERVER_NAME << "# " << endl);
|
send(StrPrinter << _strUserName << "@" << SERVER_NAME << "# " << endl);
|
||||||
}
|
}
|
||||||
|
|
||||||
}/* namespace mediakit */
|
}/* namespace mediakit */
|
||||||
|
@ -37,23 +37,23 @@ namespace mediakit {
|
|||||||
|
|
||||||
class ShellSession: public TcpSession {
|
class ShellSession: public TcpSession {
|
||||||
public:
|
public:
|
||||||
ShellSession(const Socket::Ptr &_sock);
|
ShellSession(const Socket::Ptr &_sock);
|
||||||
virtual ~ShellSession();
|
virtual ~ShellSession();
|
||||||
|
|
||||||
void onRecv(const Buffer::Ptr &) override;
|
void onRecv(const Buffer::Ptr &) override;
|
||||||
void onError(const SockException &err) override;
|
void onError(const SockException &err) override;
|
||||||
void onManager() override;
|
void onManager() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline bool onCommandLine(const string &);
|
inline bool onCommandLine(const string &);
|
||||||
inline void pleaseInputUser();
|
inline void pleaseInputUser();
|
||||||
inline void pleaseInputPasswd();
|
inline void pleaseInputPasswd();
|
||||||
inline void printShellPrefix();
|
inline void printShellPrefix();
|
||||||
|
|
||||||
function<bool(const string &)> _loginInterceptor;
|
function<bool(const string &)> _loginInterceptor;
|
||||||
string _strRecvBuf;
|
string _strRecvBuf;
|
||||||
Ticker _beatTicker;
|
Ticker _beatTicker;
|
||||||
string _strUserName;
|
string _strUserName;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -32,232 +32,232 @@ namespace mediakit {
|
|||||||
#define HK_APP_NAME "live"
|
#define HK_APP_NAME "live"
|
||||||
|
|
||||||
DeviceHK::DeviceHK() {
|
DeviceHK::DeviceHK() {
|
||||||
InfoL << endl;
|
InfoL << endl;
|
||||||
static onceToken token( []() {
|
static onceToken token( []() {
|
||||||
NET_DVR_Init();
|
NET_DVR_Init();
|
||||||
NET_DVR_SetDVRMessageCallBack_V31([](LONG lCommand,NET_DVR_ALARMER *pAlarmer,char *pAlarmInfo,DWORD dwBufLen,void* pUser){
|
NET_DVR_SetDVRMessageCallBack_V31([](LONG lCommand,NET_DVR_ALARMER *pAlarmer,char *pAlarmInfo,DWORD dwBufLen,void* pUser){
|
||||||
WarnL<<pAlarmInfo;
|
WarnL<<pAlarmInfo;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
},NULL);
|
},NULL);
|
||||||
}, []() {
|
}, []() {
|
||||||
NET_DVR_Cleanup();
|
NET_DVR_Cleanup();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceHK::~DeviceHK() {
|
DeviceHK::~DeviceHK() {
|
||||||
InfoL << endl;
|
InfoL << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceHK::connectDevice(const connectInfo &info, const connectCB& cb, int iTimeOut) {
|
void DeviceHK::connectDevice(const connectInfo &info, const connectCB& cb, int iTimeOut) {
|
||||||
NET_DVR_USER_LOGIN_INFO loginInfo;
|
NET_DVR_USER_LOGIN_INFO loginInfo;
|
||||||
NET_DVR_DEVICEINFO_V40 loginResult;
|
NET_DVR_DEVICEINFO_V40 loginResult;
|
||||||
|
|
||||||
//login info
|
//login info
|
||||||
strcpy(loginInfo.sDeviceAddress, info.strDevIp.c_str());
|
strcpy(loginInfo.sDeviceAddress, info.strDevIp.c_str());
|
||||||
loginInfo.wPort = info.ui16DevPort;
|
loginInfo.wPort = info.ui16DevPort;
|
||||||
strcpy(loginInfo.sUserName, info.strUserName.c_str());
|
strcpy(loginInfo.sUserName, info.strUserName.c_str());
|
||||||
strcpy(loginInfo.sPassword, info.strPwd.c_str());
|
strcpy(loginInfo.sPassword, info.strPwd.c_str());
|
||||||
|
|
||||||
//callback info
|
//callback info
|
||||||
typedef function< void(LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo)> hkLoginCB;
|
typedef function< void(LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo)> hkLoginCB;
|
||||||
loginInfo.bUseAsynLogin = TRUE;
|
loginInfo.bUseAsynLogin = TRUE;
|
||||||
weak_ptr<Device> weakSelf = shared_from_this();
|
weak_ptr<Device> weakSelf = shared_from_this();
|
||||||
loginInfo.pUser = new hkLoginCB([weakSelf,cb](LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo ) {
|
loginInfo.pUser = new hkLoginCB([weakSelf,cb](LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo ) {
|
||||||
//TraceL<<lUserID<<" "<<dwResult<<" "<<lpDeviceInfo->sSerialNumber;
|
//TraceL<<lUserID<<" "<<dwResult<<" "<<lpDeviceInfo->sSerialNumber;
|
||||||
connectResult result;
|
connectResult result;
|
||||||
if(dwResult==TRUE) {
|
if(dwResult==TRUE) {
|
||||||
result.strDevName=(char *)(lpDeviceInfo->sSerialNumber);
|
result.strDevName=(char *)(lpDeviceInfo->sSerialNumber);
|
||||||
result.ui16ChnStart=lpDeviceInfo->byStartChan;
|
result.ui16ChnStart=lpDeviceInfo->byStartChan;
|
||||||
result.ui16ChnCount=lpDeviceInfo->byChanNum;
|
result.ui16ChnCount=lpDeviceInfo->byChanNum;
|
||||||
auto _strongSelf=weakSelf.lock();
|
auto _strongSelf=weakSelf.lock();
|
||||||
if(_strongSelf) {
|
if(_strongSelf) {
|
||||||
auto strongSelf=dynamic_pointer_cast<DeviceHK>(_strongSelf);
|
auto strongSelf=dynamic_pointer_cast<DeviceHK>(_strongSelf);
|
||||||
strongSelf->onConnected(lUserID,lpDeviceInfo);
|
strongSelf->onConnected(lUserID,lpDeviceInfo);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
WarnL<<"connect deviceHK failed:"<<NET_DVR_GetLastError();
|
WarnL<<"connect deviceHK failed:"<<NET_DVR_GetLastError();
|
||||||
}
|
}
|
||||||
cb(dwResult==TRUE,result);
|
cb(dwResult==TRUE,result);
|
||||||
});
|
});
|
||||||
loginInfo.cbLoginResult = [](LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo , void* pUser) {
|
loginInfo.cbLoginResult = [](LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo , void* pUser) {
|
||||||
auto *fun=static_cast<hkLoginCB *>(pUser);
|
auto *fun=static_cast<hkLoginCB *>(pUser);
|
||||||
(*fun)(lUserID,dwResult,lpDeviceInfo);
|
(*fun)(lUserID,dwResult,lpDeviceInfo);
|
||||||
delete fun;
|
delete fun;
|
||||||
};
|
};
|
||||||
NET_DVR_SetConnectTime(iTimeOut * 1000, 3);
|
NET_DVR_SetConnectTime(iTimeOut * 1000, 3);
|
||||||
NET_DVR_Login_V40(&loginInfo, &loginResult);
|
NET_DVR_Login_V40(&loginInfo, &loginResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceHK::disconnect(const relustCB& cb) {
|
void DeviceHK::disconnect(const relustCB& cb) {
|
||||||
m_mapChannels.clear();
|
m_mapChannels.clear();
|
||||||
if (m_i64LoginId >= 0) {
|
if (m_i64LoginId >= 0) {
|
||||||
NET_DVR_Logout(m_i64LoginId);
|
NET_DVR_Logout(m_i64LoginId);
|
||||||
m_i64LoginId = -1;
|
m_i64LoginId = -1;
|
||||||
Device::onDisconnected(true);
|
Device::onDisconnected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceHK::addChannel(int iChn, bool bMainStream) {
|
void DeviceHK::addChannel(int iChn, bool bMainStream) {
|
||||||
DevChannel::Ptr channel( new DevChannelHK(m_i64LoginId, (char *) m_deviceInfo.sSerialNumber, iChn, bMainStream));
|
DevChannel::Ptr channel( new DevChannelHK(m_i64LoginId, (char *) m_deviceInfo.sSerialNumber, iChn, bMainStream));
|
||||||
m_mapChannels[iChn] = channel;
|
m_mapChannels[iChn] = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceHK::delChannel(int chn) {
|
void DeviceHK::delChannel(int chn) {
|
||||||
m_mapChannels.erase(chn);
|
m_mapChannels.erase(chn);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceHK::onConnected(LONG lUserID, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo) {
|
void DeviceHK::onConnected(LONG lUserID, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo) {
|
||||||
m_i64LoginId = lUserID;
|
m_i64LoginId = lUserID;
|
||||||
m_deviceInfo = *lpDeviceInfo;
|
m_deviceInfo = *lpDeviceInfo;
|
||||||
Device::onConnected();
|
Device::onConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceHK::addAllChannel(bool bMainStream) {
|
void DeviceHK::addAllChannel(bool bMainStream) {
|
||||||
InfoL << endl;
|
InfoL << endl;
|
||||||
for (int i = 0; i < m_deviceInfo.byChanNum; i++) {
|
for (int i = 0; i < m_deviceInfo.byChanNum; i++) {
|
||||||
addChannel(m_deviceInfo.byStartChan + i, bMainStream);
|
addChannel(m_deviceInfo.byStartChan + i, bMainStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DevChannelHK::DevChannelHK(int64_t i64LoginId, const char* pcDevName, int iChn, bool bMainStream) :
|
DevChannelHK::DevChannelHK(int64_t i64LoginId, const char* pcDevName, int iChn, bool bMainStream) :
|
||||||
DevChannel(HK_APP_NAME,(StrPrinter<<MD5(pcDevName).hexdigest()<<"_"<<iChn<<endl).data()),
|
DevChannel(HK_APP_NAME,(StrPrinter<<MD5(pcDevName).hexdigest()<<"_"<<iChn<<endl).data()),
|
||||||
m_i64LoginId(i64LoginId) {
|
m_i64LoginId(i64LoginId) {
|
||||||
InfoL << endl;
|
InfoL << endl;
|
||||||
NET_DVR_PREVIEWINFO previewInfo;
|
NET_DVR_PREVIEWINFO previewInfo;
|
||||||
previewInfo.lChannel = iChn; //通道号
|
previewInfo.lChannel = iChn; //通道号
|
||||||
previewInfo.dwStreamType = bMainStream ? 0 : 1; // 码流类型,0-主码流,1-子码流,2-码流3,3-码流4 等以此类推
|
previewInfo.dwStreamType = bMainStream ? 0 : 1; // 码流类型,0-主码流,1-子码流,2-码流3,3-码流4 等以此类推
|
||||||
previewInfo.dwLinkMode = 1; // 0:TCP方式,1:UDP方式,2:多播方式,3 - RTP方式,4-RTP/RTSP,5-RSTP/HTTP
|
previewInfo.dwLinkMode = 1; // 0:TCP方式,1:UDP方式,2:多播方式,3 - RTP方式,4-RTP/RTSP,5-RSTP/HTTP
|
||||||
previewInfo.hPlayWnd = 0; //播放窗口的句柄,为NULL表示不播放图象
|
previewInfo.hPlayWnd = 0; //播放窗口的句柄,为NULL表示不播放图象
|
||||||
previewInfo.byProtoType = 0; //应用层取流协议,0-私有协议,1-RTSP协议
|
previewInfo.byProtoType = 0; //应用层取流协议,0-私有协议,1-RTSP协议
|
||||||
previewInfo.dwDisplayBufNum = 1; //播放库播放缓冲区最大缓冲帧数,范围1-50,置0时默认为1
|
previewInfo.dwDisplayBufNum = 1; //播放库播放缓冲区最大缓冲帧数,范围1-50,置0时默认为1
|
||||||
previewInfo.bBlocked = 0;
|
previewInfo.bBlocked = 0;
|
||||||
m_i64PreviewHandle = NET_DVR_RealPlay_V40(m_i64LoginId, &previewInfo,
|
m_i64PreviewHandle = NET_DVR_RealPlay_V40(m_i64LoginId, &previewInfo,
|
||||||
[](LONG lPlayHandle,DWORD dwDataType,BYTE *pBuffer,DWORD dwBufSize,void* pUser) {
|
[](LONG lPlayHandle,DWORD dwDataType,BYTE *pBuffer,DWORD dwBufSize,void* pUser) {
|
||||||
DevChannelHK *self=reinterpret_cast<DevChannelHK *>(pUser);
|
DevChannelHK *self=reinterpret_cast<DevChannelHK *>(pUser);
|
||||||
if(self->m_i64PreviewHandle!=(int64_t)lPlayHandle) {
|
if(self->m_i64PreviewHandle!=(int64_t)lPlayHandle) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self->onPreview(dwDataType,pBuffer,dwBufSize);
|
self->onPreview(dwDataType,pBuffer,dwBufSize);
|
||||||
}, this);
|
}, this);
|
||||||
if (m_i64PreviewHandle == -1) {
|
if (m_i64PreviewHandle == -1) {
|
||||||
throw std::runtime_error( StrPrinter << "设备[" << pcDevName << "/" << iChn << "]开始实时预览失败:"
|
throw std::runtime_error( StrPrinter << "设备[" << pcDevName << "/" << iChn << "]开始实时预览失败:"
|
||||||
<< NET_DVR_GetLastError() << endl);
|
<< NET_DVR_GetLastError() << endl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DevChannelHK::~DevChannelHK() {
|
DevChannelHK::~DevChannelHK() {
|
||||||
InfoL << endl;
|
InfoL << endl;
|
||||||
if (m_i64PreviewHandle >= 0) {
|
if (m_i64PreviewHandle >= 0) {
|
||||||
NET_DVR_StopRealPlay(m_i64PreviewHandle);
|
NET_DVR_StopRealPlay(m_i64PreviewHandle);
|
||||||
m_i64PreviewHandle = -1;
|
m_i64PreviewHandle = -1;
|
||||||
}
|
}
|
||||||
if (m_iPlayHandle >= 0) {
|
if (m_iPlayHandle >= 0) {
|
||||||
PlayM4_StopSoundShare(m_iPlayHandle);
|
PlayM4_StopSoundShare(m_iPlayHandle);
|
||||||
PlayM4_Stop(m_iPlayHandle);
|
PlayM4_Stop(m_iPlayHandle);
|
||||||
m_iPlayHandle = -1;
|
m_iPlayHandle = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevChannelHK::onPreview(DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize) {
|
void DevChannelHK::onPreview(DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize) {
|
||||||
//TimeTicker1(-1);
|
//TimeTicker1(-1);
|
||||||
switch (dwDataType) {
|
switch (dwDataType) {
|
||||||
case NET_DVR_SYSHEAD: { //系统头数据
|
case NET_DVR_SYSHEAD: { //系统头数据
|
||||||
if (!PlayM4_GetPort(&m_iPlayHandle)) { //获取播放库未使用的通道号
|
if (!PlayM4_GetPort(&m_iPlayHandle)) { //获取播放库未使用的通道号
|
||||||
WarnL << "PlayM4_GetPort:" << NET_DVR_GetLastError();
|
WarnL << "PlayM4_GetPort:" << NET_DVR_GetLastError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (dwBufSize > 0) {
|
if (dwBufSize > 0) {
|
||||||
if (!PlayM4_SetStreamOpenMode(m_iPlayHandle, STREAME_REALTIME)) { //设置实时流播放模式
|
if (!PlayM4_SetStreamOpenMode(m_iPlayHandle, STREAME_REALTIME)) { //设置实时流播放模式
|
||||||
WarnL << "PlayM4_SetStreamOpenMode:" << NET_DVR_GetLastError();
|
WarnL << "PlayM4_SetStreamOpenMode:" << NET_DVR_GetLastError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!PlayM4_OpenStream(m_iPlayHandle, pBuffer, dwBufSize,
|
if (!PlayM4_OpenStream(m_iPlayHandle, pBuffer, dwBufSize,
|
||||||
1024 * 1024)) { //打开流接口
|
1024 * 1024)) { //打开流接口
|
||||||
WarnL << "PlayM4_OpenStream:" << NET_DVR_GetLastError();
|
WarnL << "PlayM4_OpenStream:" << NET_DVR_GetLastError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayM4_SetDecCallBackMend(m_iPlayHandle,
|
PlayM4_SetDecCallBackMend(m_iPlayHandle,
|
||||||
[](int nPort,char * pBuf,int nSize,FRAME_INFO * pFrameInfo, void* nUser,int nReserved2) {
|
[](int nPort,char * pBuf,int nSize,FRAME_INFO * pFrameInfo, void* nUser,int nReserved2) {
|
||||||
DevChannelHK *chn=reinterpret_cast<DevChannelHK *>(nUser);
|
DevChannelHK *chn=reinterpret_cast<DevChannelHK *>(nUser);
|
||||||
if(chn->m_iPlayHandle!=nPort) {
|
if(chn->m_iPlayHandle!=nPort) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chn->onGetDecData(pBuf,nSize,pFrameInfo);
|
chn->onGetDecData(pBuf,nSize,pFrameInfo);
|
||||||
}, this);
|
}, this);
|
||||||
if (!PlayM4_Play(m_iPlayHandle, 0)) { //播放开始
|
if (!PlayM4_Play(m_iPlayHandle, 0)) { //播放开始
|
||||||
WarnL << "PlayM4_Play:" << NET_DVR_GetLastError();
|
WarnL << "PlayM4_Play:" << NET_DVR_GetLastError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
InfoL << "设置解码器成功!" << endl;
|
InfoL << "设置解码器成功!" << endl;
|
||||||
//打开音频解码, 需要码流是复合流
|
//打开音频解码, 需要码流是复合流
|
||||||
if (!PlayM4_PlaySoundShare(m_iPlayHandle)) {
|
if (!PlayM4_PlaySoundShare(m_iPlayHandle)) {
|
||||||
WarnL << "PlayM4_PlaySound:" << NET_DVR_GetLastError();
|
WarnL << "PlayM4_PlaySound:" << NET_DVR_GetLastError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NET_DVR_STREAMDATA: { //流数据(包括复合流或音视频分开的视频流数据)
|
case NET_DVR_STREAMDATA: { //流数据(包括复合流或音视频分开的视频流数据)
|
||||||
if (dwBufSize > 0 && m_iPlayHandle != -1) {
|
if (dwBufSize > 0 && m_iPlayHandle != -1) {
|
||||||
if (!PlayM4_InputData(m_iPlayHandle, pBuffer, dwBufSize)) {
|
if (!PlayM4_InputData(m_iPlayHandle, pBuffer, dwBufSize)) {
|
||||||
WarnL << "PlayM4_InputData:" << NET_DVR_GetLastError();
|
WarnL << "PlayM4_InputData:" << NET_DVR_GetLastError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NET_DVR_AUDIOSTREAMDATA: { //音频数据
|
case NET_DVR_AUDIOSTREAMDATA: { //音频数据
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NET_DVR_PRIVATE_DATA: { //私有数据,包括智能信息
|
case NET_DVR_PRIVATE_DATA: { //私有数据,包括智能信息
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevChannelHK::onGetDecData(char* pBuf, int nSize, FRAME_INFO* pFrameInfo) {
|
void DevChannelHK::onGetDecData(char* pBuf, int nSize, FRAME_INFO* pFrameInfo) {
|
||||||
//InfoL << pFrameInfo->nType;
|
//InfoL << pFrameInfo->nType;
|
||||||
switch (pFrameInfo->nType) {
|
switch (pFrameInfo->nType) {
|
||||||
case T_YV12: {
|
case T_YV12: {
|
||||||
if (!m_bVideoSeted) {
|
if (!m_bVideoSeted) {
|
||||||
m_bVideoSeted = true;
|
m_bVideoSeted = true;
|
||||||
VideoInfo video;
|
VideoInfo video;
|
||||||
video.iWidth = pFrameInfo->nWidth;
|
video.iWidth = pFrameInfo->nWidth;
|
||||||
video.iHeight = pFrameInfo->nHeight;
|
video.iHeight = pFrameInfo->nHeight;
|
||||||
video.iFrameRate = pFrameInfo->nFrameRate;
|
video.iFrameRate = pFrameInfo->nFrameRate;
|
||||||
initVideo(video);
|
initVideo(video);
|
||||||
}
|
}
|
||||||
char *yuv[3];
|
char *yuv[3];
|
||||||
int yuv_len[3];
|
int yuv_len[3];
|
||||||
yuv_len[0] = pFrameInfo->nWidth;
|
yuv_len[0] = pFrameInfo->nWidth;
|
||||||
yuv_len[1] = pFrameInfo->nWidth / 2;
|
yuv_len[1] = pFrameInfo->nWidth / 2;
|
||||||
yuv_len[2] = pFrameInfo->nWidth / 2;
|
yuv_len[2] = pFrameInfo->nWidth / 2;
|
||||||
int dwOffset_Y = pFrameInfo->nWidth * pFrameInfo->nHeight;
|
int dwOffset_Y = pFrameInfo->nWidth * pFrameInfo->nHeight;
|
||||||
yuv[0] = pBuf;
|
yuv[0] = pBuf;
|
||||||
yuv[2] = yuv[0] + dwOffset_Y;
|
yuv[2] = yuv[0] + dwOffset_Y;
|
||||||
yuv[1] = yuv[2] + dwOffset_Y / 4;
|
yuv[1] = yuv[2] + dwOffset_Y / 4;
|
||||||
inputYUV(yuv, yuv_len, pFrameInfo->nStamp);
|
inputYUV(yuv, yuv_len, pFrameInfo->nStamp);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_AUDIO16: {
|
case T_AUDIO16: {
|
||||||
if (!m_bAudioSeted) {
|
if (!m_bAudioSeted) {
|
||||||
m_bAudioSeted = true;
|
m_bAudioSeted = true;
|
||||||
AudioInfo audio;
|
AudioInfo audio;
|
||||||
audio.iChannel = pFrameInfo->nWidth;
|
audio.iChannel = pFrameInfo->nWidth;
|
||||||
audio.iSampleBit = pFrameInfo->nHeight;
|
audio.iSampleBit = pFrameInfo->nHeight;
|
||||||
audio.iSampleRate = pFrameInfo->nFrameRate;
|
audio.iSampleRate = pFrameInfo->nFrameRate;
|
||||||
initAudio(audio);
|
initAudio(audio);
|
||||||
}
|
}
|
||||||
inputPCM(pBuf, nSize, pFrameInfo->nStamp);
|
inputPCM(pBuf, nSize, pFrameInfo->nStamp);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -41,26 +41,26 @@ namespace mediakit {
|
|||||||
|
|
||||||
class connectInfo {
|
class connectInfo {
|
||||||
public:
|
public:
|
||||||
connectInfo(const char *_strDevIp,
|
connectInfo(const char *_strDevIp,
|
||||||
uint16_t _ui16DevPort,
|
uint16_t _ui16DevPort,
|
||||||
const char *_strUserName,
|
const char *_strUserName,
|
||||||
const char *_strPwd) {
|
const char *_strPwd) {
|
||||||
strDevIp = _strDevIp;
|
strDevIp = _strDevIp;
|
||||||
ui16DevPort = _ui16DevPort;
|
ui16DevPort = _ui16DevPort;
|
||||||
strUserName = _strUserName;
|
strUserName = _strUserName;
|
||||||
strPwd = _strPwd;
|
strPwd = _strPwd;
|
||||||
}
|
}
|
||||||
string strDevIp;
|
string strDevIp;
|
||||||
uint16_t ui16DevPort;
|
uint16_t ui16DevPort;
|
||||||
string strUserName;
|
string strUserName;
|
||||||
string strPwd;
|
string strPwd;
|
||||||
};
|
};
|
||||||
|
|
||||||
class connectResult {
|
class connectResult {
|
||||||
public:
|
public:
|
||||||
string strDevName;
|
string strDevName;
|
||||||
uint16_t ui16ChnStart;
|
uint16_t ui16ChnStart;
|
||||||
uint16_t ui16ChnCount;
|
uint16_t ui16ChnCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef function<void(bool success, const connectResult &)> connectCB;
|
typedef function<void(bool success, const connectResult &)> connectCB;
|
||||||
@ -68,28 +68,28 @@ typedef function<void(bool success)> relustCB;
|
|||||||
|
|
||||||
class Device: public enable_shared_from_this<Device> {
|
class Device: public enable_shared_from_this<Device> {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<Device> Ptr;
|
typedef std::shared_ptr<Device> Ptr;
|
||||||
Device() {
|
Device() {
|
||||||
}
|
}
|
||||||
virtual ~Device(){ disconnect([](bool bSuccess){
|
virtual ~Device(){ disconnect([](bool bSuccess){
|
||||||
});};
|
});};
|
||||||
|
|
||||||
virtual void connectDevice(const connectInfo &info, const connectCB &cb, int iTimeOut = 3)=0;
|
virtual void connectDevice(const connectInfo &info, const connectCB &cb, int iTimeOut = 3)=0;
|
||||||
|
|
||||||
virtual void disconnect(const relustCB &cb) {
|
virtual void disconnect(const relustCB &cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void addChannel(int iChnIndex, bool bMainStream = true)=0;
|
virtual void addChannel(int iChnIndex, bool bMainStream = true)=0;
|
||||||
|
|
||||||
virtual void delChannel(int iChnIndex)=0;
|
virtual void delChannel(int iChnIndex)=0;
|
||||||
|
|
||||||
virtual void addAllChannel(bool bMainStream = true)=0;
|
virtual void addAllChannel(bool bMainStream = true)=0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onConnected() {
|
void onConnected() {
|
||||||
}
|
}
|
||||||
void onDisconnected(bool bSelfDisconnect) {
|
void onDisconnected(bool bSelfDisconnect) {
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,36 +97,36 @@ protected:
|
|||||||
class DevChannelHK;
|
class DevChannelHK;
|
||||||
class DeviceHK: public Device {
|
class DeviceHK: public Device {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<DeviceHK> Ptr;
|
typedef std::shared_ptr<DeviceHK> Ptr;
|
||||||
DeviceHK();
|
DeviceHK();
|
||||||
virtual ~DeviceHK();
|
virtual ~DeviceHK();
|
||||||
|
|
||||||
void connectDevice(const connectInfo &info, const connectCB &cb, int iTimeOut = 3) override;
|
void connectDevice(const connectInfo &info, const connectCB &cb, int iTimeOut = 3) override;
|
||||||
void disconnect(const relustCB &cb) override;
|
void disconnect(const relustCB &cb) override;
|
||||||
|
|
||||||
void addChannel(int iChnIndex, bool bMainStream = true) override;
|
void addChannel(int iChnIndex, bool bMainStream = true) override;
|
||||||
void delChannel(int iChnIndex) override;
|
void delChannel(int iChnIndex) override;
|
||||||
void addAllChannel(bool bMainStream = true) override;
|
void addAllChannel(bool bMainStream = true) override;
|
||||||
private:
|
private:
|
||||||
map<int, std::shared_ptr<DevChannel> > m_mapChannels;
|
map<int, std::shared_ptr<DevChannel> > m_mapChannels;
|
||||||
int64_t m_i64LoginId = -1;
|
int64_t m_i64LoginId = -1;
|
||||||
NET_DVR_DEVICEINFO_V30 m_deviceInfo;
|
NET_DVR_DEVICEINFO_V30 m_deviceInfo;
|
||||||
void onConnected(LONG lUserID, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo);
|
void onConnected(LONG lUserID, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
class DevChannelHK: public DevChannel {
|
class DevChannelHK: public DevChannel {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<DevChannel> Ptr;
|
typedef std::shared_ptr<DevChannel> Ptr;
|
||||||
DevChannelHK(int64_t i64LoginId, const char *pcDevName, int iChn, bool bMainStream = true);
|
DevChannelHK(int64_t i64LoginId, const char *pcDevName, int iChn, bool bMainStream = true);
|
||||||
virtual ~DevChannelHK();
|
virtual ~DevChannelHK();
|
||||||
protected:
|
protected:
|
||||||
int64_t m_i64LoginId = -1;
|
int64_t m_i64LoginId = -1;
|
||||||
int64_t m_i64PreviewHandle = -1;
|
int64_t m_i64PreviewHandle = -1;
|
||||||
int m_iPlayHandle = -1;
|
int m_iPlayHandle = -1;
|
||||||
void onPreview(DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize);
|
void onPreview(DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize);
|
||||||
void onGetDecData(char * pBuf, int nSize, FRAME_INFO * pFrameInfo);
|
void onGetDecData(char * pBuf, int nSize, FRAME_INFO * pFrameInfo);
|
||||||
bool m_bVideoSeted = false;
|
bool m_bVideoSeted = false;
|
||||||
bool m_bAudioSeted = false;
|
bool m_bAudioSeted = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -46,51 +46,51 @@ namespace mediakit {
|
|||||||
class H264Decoder
|
class H264Decoder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
H264Decoder(void){
|
H264Decoder(void){
|
||||||
avcodec_register_all();
|
avcodec_register_all();
|
||||||
AVCodec *pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
AVCodec *pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||||
if (!pCodec) {
|
if (!pCodec) {
|
||||||
throw std::runtime_error("未找到H264解码器");
|
throw std::runtime_error("未找到H264解码器");
|
||||||
}
|
}
|
||||||
m_pContext.reset(avcodec_alloc_context3(pCodec), [](AVCodecContext *pCtx) {
|
m_pContext.reset(avcodec_alloc_context3(pCodec), [](AVCodecContext *pCtx) {
|
||||||
avcodec_close(pCtx);
|
avcodec_close(pCtx);
|
||||||
avcodec_free_context(&pCtx);
|
avcodec_free_context(&pCtx);
|
||||||
});
|
});
|
||||||
if (!m_pContext) {
|
if (!m_pContext) {
|
||||||
throw std::runtime_error("创建解码器失败");
|
throw std::runtime_error("创建解码器失败");
|
||||||
}
|
}
|
||||||
if (pCodec->capabilities & AV_CODEC_CAP_TRUNCATED) {
|
if (pCodec->capabilities & AV_CODEC_CAP_TRUNCATED) {
|
||||||
/* we do not send complete frames */
|
/* we do not send complete frames */
|
||||||
m_pContext->flags |= AV_CODEC_FLAG_TRUNCATED;
|
m_pContext->flags |= AV_CODEC_FLAG_TRUNCATED;
|
||||||
}
|
}
|
||||||
if(avcodec_open2(m_pContext.get(), pCodec, NULL)< 0){
|
if(avcodec_open2(m_pContext.get(), pCodec, NULL)< 0){
|
||||||
throw std::runtime_error("打开编码器失败");
|
throw std::runtime_error("打开编码器失败");
|
||||||
}
|
}
|
||||||
m_pFrame.reset(av_frame_alloc(),[](AVFrame *pFrame){
|
m_pFrame.reset(av_frame_alloc(),[](AVFrame *pFrame){
|
||||||
av_frame_free(&pFrame);
|
av_frame_free(&pFrame);
|
||||||
});
|
});
|
||||||
if (!m_pFrame) {
|
if (!m_pFrame) {
|
||||||
throw std::runtime_error("创建帧缓存失败");
|
throw std::runtime_error("创建帧缓存失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
virtual ~H264Decoder(void){}
|
virtual ~H264Decoder(void){}
|
||||||
bool inputVideo(unsigned char* data,unsigned int dataSize,uint32_t ui32Stamp,AVFrame **ppFrame){
|
bool inputVideo(unsigned char* data,unsigned int dataSize,uint32_t ui32Stamp,AVFrame **ppFrame){
|
||||||
AVPacket pkt;
|
AVPacket pkt;
|
||||||
av_init_packet(&pkt);
|
av_init_packet(&pkt);
|
||||||
pkt.data = data;
|
pkt.data = data;
|
||||||
pkt.size = dataSize;
|
pkt.size = dataSize;
|
||||||
pkt.dts = ui32Stamp;
|
pkt.dts = ui32Stamp;
|
||||||
int iGotPicture ;
|
int iGotPicture ;
|
||||||
auto iLen = avcodec_decode_video2(m_pContext.get(), m_pFrame.get(), &iGotPicture, &pkt);
|
auto iLen = avcodec_decode_video2(m_pContext.get(), m_pFrame.get(), &iGotPicture, &pkt);
|
||||||
if (!iGotPicture || iLen < 0) {
|
if (!iGotPicture || iLen < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
*ppFrame = m_pFrame.get();
|
*ppFrame = m_pFrame.get();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<AVCodecContext> m_pContext;
|
std::shared_ptr<AVCodecContext> m_pContext;
|
||||||
std::shared_ptr<AVFrame> m_pFrame;
|
std::shared_ptr<AVFrame> m_pFrame;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,13 +65,13 @@ public:
|
|||||||
SDL_PushEvent(&event);
|
SDL_PushEvent(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void runLoop(){
|
void runLoop(){
|
||||||
bool flag = true;
|
bool flag = true;
|
||||||
std::function<bool ()> task;
|
std::function<bool ()> task;
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while(flag){
|
while(flag){
|
||||||
SDL_WaitEvent(&event);
|
SDL_WaitEvent(&event);
|
||||||
switch (event.type){
|
switch (event.type){
|
||||||
case REFRESH_EVENT:{
|
case REFRESH_EVENT:{
|
||||||
{
|
{
|
||||||
lock_guard<mutex> lck(_mtxTask);
|
lock_guard<mutex> lck(_mtxTask);
|
||||||
@ -91,17 +91,17 @@ public:
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void shutdown(){
|
void shutdown(){
|
||||||
doTask([](){return false;});
|
doTask([](){return false;});
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
SDLDisplayerHelper(){
|
SDLDisplayerHelper(){
|
||||||
};
|
};
|
||||||
~SDLDisplayerHelper(){
|
~SDLDisplayerHelper(){
|
||||||
shutdown();
|
shutdown();
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
std::deque<std::function<bool ()> > _taskList;
|
std::deque<std::function<bool ()> > _taskList;
|
||||||
@ -112,7 +112,7 @@ private:
|
|||||||
|
|
||||||
class YuvDisplayer {
|
class YuvDisplayer {
|
||||||
public:
|
public:
|
||||||
YuvDisplayer(void *hwnd = nullptr,const char *title = "untitled"){
|
YuvDisplayer(void *hwnd = nullptr,const char *title = "untitled"){
|
||||||
|
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) == -1) {
|
if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) == -1) {
|
||||||
@ -131,39 +131,39 @@ public:
|
|||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
_title = title;
|
_title = title;
|
||||||
_hwnd = hwnd;
|
_hwnd = hwnd;
|
||||||
}
|
}
|
||||||
virtual ~YuvDisplayer(){
|
virtual ~YuvDisplayer(){
|
||||||
if (_texture) {
|
if (_texture) {
|
||||||
SDL_DestroyTexture(_texture);
|
SDL_DestroyTexture(_texture);
|
||||||
_texture = nullptr;
|
_texture = nullptr;
|
||||||
}
|
}
|
||||||
if (_render) {
|
if (_render) {
|
||||||
SDL_DestroyRenderer(_render);
|
SDL_DestroyRenderer(_render);
|
||||||
_render = nullptr;
|
_render = nullptr;
|
||||||
}
|
}
|
||||||
if (_win) {
|
if (_win) {
|
||||||
SDL_DestroyWindow(_win);
|
SDL_DestroyWindow(_win);
|
||||||
_win = nullptr;
|
_win = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool displayYUV(AVFrame *pFrame){
|
bool displayYUV(AVFrame *pFrame){
|
||||||
if (!_win) {
|
if (!_win) {
|
||||||
if (_hwnd) {
|
if (_hwnd) {
|
||||||
_win = SDL_CreateWindowFrom(_hwnd);
|
_win = SDL_CreateWindowFrom(_hwnd);
|
||||||
}else {
|
}else {
|
||||||
_win = SDL_CreateWindow(_title.data(),
|
_win = SDL_CreateWindow(_title.data(),
|
||||||
SDL_WINDOWPOS_UNDEFINED,
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
SDL_WINDOWPOS_UNDEFINED,
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
pFrame->width,
|
pFrame->width,
|
||||||
pFrame->height,
|
pFrame->height,
|
||||||
SDL_WINDOW_OPENGL);
|
SDL_WINDOW_OPENGL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_win && ! _render){
|
if (_win && ! _render){
|
||||||
#if 0
|
#if 0
|
||||||
SDL_RENDERER_SOFTWARE = 0x00000001, /**< The renderer is a software fallback */
|
SDL_RENDERER_SOFTWARE = 0x00000001, /**< The renderer is a software fallback */
|
||||||
SDL_RENDERER_ACCELERATED = 0x00000002, /**< The renderer uses hardware
|
SDL_RENDERER_ACCELERATED = 0x00000002, /**< The renderer uses hardware
|
||||||
acceleration */
|
acceleration */
|
||||||
SDL_RENDERER_PRESENTVSYNC = 0x00000004, /**< Present is synchronized
|
SDL_RENDERER_PRESENTVSYNC = 0x00000004, /**< Present is synchronized
|
||||||
@ -172,34 +172,34 @@ public:
|
|||||||
rendering to texture */
|
rendering to texture */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_render = SDL_CreateRenderer(_win, -1, SDL_RENDERER_ACCELERATED);
|
_render = SDL_CreateRenderer(_win, -1, SDL_RENDERER_ACCELERATED);
|
||||||
}
|
}
|
||||||
if (_render && !_texture) {
|
if (_render && !_texture) {
|
||||||
_texture = SDL_CreateTexture(_render, SDL_PIXELFORMAT_IYUV,
|
_texture = SDL_CreateTexture(_render, SDL_PIXELFORMAT_IYUV,
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
pFrame->width,
|
pFrame->width,
|
||||||
pFrame->height);
|
pFrame->height);
|
||||||
}
|
}
|
||||||
if (_texture) {
|
if (_texture) {
|
||||||
SDL_UpdateYUVTexture(_texture, nullptr,
|
SDL_UpdateYUVTexture(_texture, nullptr,
|
||||||
pFrame->data[0], pFrame->linesize[0],
|
pFrame->data[0], pFrame->linesize[0],
|
||||||
pFrame->data[1], pFrame->linesize[1],
|
pFrame->data[1], pFrame->linesize[1],
|
||||||
pFrame->data[2], pFrame->linesize[2]);
|
pFrame->data[2], pFrame->linesize[2]);
|
||||||
|
|
||||||
//SDL_UpdateTexture(_texture, nullptr, pFrame->data[0], pFrame->linesize[0]);
|
//SDL_UpdateTexture(_texture, nullptr, pFrame->data[0], pFrame->linesize[0]);
|
||||||
SDL_RenderClear(_render);
|
SDL_RenderClear(_render);
|
||||||
SDL_RenderCopy(_render, _texture, nullptr, nullptr);
|
SDL_RenderCopy(_render, _texture, nullptr, nullptr);
|
||||||
SDL_RenderPresent(_render);
|
SDL_RenderPresent(_render);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
string _title;
|
string _title;
|
||||||
SDL_Window *_win = nullptr;
|
SDL_Window *_win = nullptr;
|
||||||
SDL_Renderer *_render = nullptr;
|
SDL_Renderer *_render = nullptr;
|
||||||
SDL_Texture *_texture = nullptr;
|
SDL_Texture *_texture = nullptr;
|
||||||
void *_hwnd = nullptr;
|
void *_hwnd = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* YUVDISPLAYER_H_ */
|
#endif /* YUVDISPLAYER_H_ */
|
@ -126,6 +126,7 @@ void process_file(const char *file,bool rm_bom){
|
|||||||
InfoL << (rm_bom ? "删除" : "添加") << "bom:" << file;
|
InfoL << (rm_bom ? "删除" : "添加") << "bom:" << file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 这个程序是为了统一添加或删除utf-8 bom头
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
CMD_main cmd_main;
|
CMD_main cmd_main;
|
||||||
try {
|
try {
|
||||||
@ -148,7 +149,7 @@ int main(int argc, char *argv[]) {
|
|||||||
bool no_filter = filter_set.find("*") != filter_set.end();
|
bool no_filter = filter_set.find("*") != filter_set.end();
|
||||||
//设置日志
|
//设置日志
|
||||||
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
||||||
|
path = File::absolutePath(path, "");
|
||||||
for_each_file(path.data(),[&](const char *path){
|
for_each_file(path.data(),[&](const char *path){
|
||||||
if(!no_filter){
|
if(!no_filter){
|
||||||
//开启了过滤器
|
//开启了过滤器
|
||||||
|
@ -1,18 +1,9 @@
|
|||||||
#include <stdlib.h>
|
#include <memory.h>
|
||||||
#include <memory.h>
|
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
|
||||||
|
|
||||||
#include <dirent.h>
|
|
||||||
|
|
||||||
#endif //!defined(_WIN32)
|
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include "Util/CMD.h"
|
#include "Util/CMD.h"
|
||||||
#include "Util/util.h"
|
#include "Util/util.h"
|
||||||
#include "Util/logger.h"
|
#include "Util/logger.h"
|
||||||
#include "Util/File.h"
|
#include "Util/File.h"
|
||||||
#include "Util/uv_errno.h"
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
@ -41,17 +32,40 @@ public:
|
|||||||
virtual ~CMD_main() {}
|
virtual ~CMD_main() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
vector<string> split(const string& s, const char *delim) {
|
||||||
|
vector<string> ret;
|
||||||
|
int last = 0;
|
||||||
|
int index = s.find(delim, last);
|
||||||
|
while (index != string::npos) {
|
||||||
|
if (index - last >= 0) {
|
||||||
|
ret.push_back(s.substr(last, index - last));
|
||||||
|
}
|
||||||
|
last = index + strlen(delim);
|
||||||
|
index = s.find(delim, last);
|
||||||
|
}
|
||||||
|
if (!s.size() || s.size() - last >= 0) {
|
||||||
|
ret.push_back(s.substr(last));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
void process_file(const char *file) {
|
void process_file(const char *file) {
|
||||||
auto str = File::loadFile(file);
|
auto str = File::loadFile(file);
|
||||||
if (str.empty()) {
|
if (str.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto lines = split(str, "\n");
|
auto lines = ::split(str, "\n");
|
||||||
deque<string> lines_copy;
|
deque<string> lines_copy;
|
||||||
for (auto &line : lines) {
|
for (auto &line : lines) {
|
||||||
|
if(line.empty()){
|
||||||
|
lines_copy.push_back("");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
string line_copy;
|
string line_copy;
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
|
int i = 0;
|
||||||
for (auto &ch : line) {
|
for (auto &ch : line) {
|
||||||
|
++i;
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '\t' :
|
case '\t' :
|
||||||
line_copy.append(" ");
|
line_copy.append(" ");
|
||||||
@ -65,6 +79,7 @@ void process_file(const char *file) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (flag) {
|
if (flag) {
|
||||||
|
line_copy.append(line.substr(i));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,9 +90,13 @@ void process_file(const char *file) {
|
|||||||
str.append(line);
|
str.append(line);
|
||||||
str.push_back('\n');
|
str.push_back('\n');
|
||||||
}
|
}
|
||||||
|
if(!lines_copy.empty()){
|
||||||
|
str.pop_back();
|
||||||
|
}
|
||||||
File::saveFile(str, file);
|
File::saveFile(str, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 这个程序是为了统一替换tab为4个空格
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
CMD_main cmd_main;
|
CMD_main cmd_main;
|
||||||
try {
|
try {
|
||||||
@ -87,10 +106,9 @@ int main(int argc, char *argv[]) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rm_bom = cmd_main.hasKey("rm");
|
|
||||||
string path = cmd_main["in"];
|
string path = cmd_main["in"];
|
||||||
string filter = cmd_main["filter"];
|
string filter = cmd_main["filter"];
|
||||||
auto vec = split(filter, ",");
|
auto vec = ::split(filter, ",");
|
||||||
|
|
||||||
set<string> filter_set;
|
set<string> filter_set;
|
||||||
for (auto ext : vec) {
|
for (auto ext : vec) {
|
||||||
@ -100,6 +118,8 @@ int main(int argc, char *argv[]) {
|
|||||||
bool no_filter = filter_set.find("*") != filter_set.end();
|
bool no_filter = filter_set.find("*") != filter_set.end();
|
||||||
//设置日志
|
//设置日志
|
||||||
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
||||||
|
path = File::absolutePath(path, "");
|
||||||
|
DebugL << path;
|
||||||
File::scanDir(path, [&](const string &path, bool isDir) {
|
File::scanDir(path, [&](const string &path, bool isDir) {
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -50,89 +50,89 @@ const char kPort[] = HTTP_FIELD"port";
|
|||||||
#define HTTPS_PORT 443
|
#define HTTPS_PORT 443
|
||||||
extern const char kSSLPort[] = HTTP_FIELD"sslport";
|
extern const char kSSLPort[] = HTTP_FIELD"sslport";
|
||||||
onceToken token1([](){
|
onceToken token1([](){
|
||||||
mINI::Instance()[kPort] = HTTP_PORT;
|
mINI::Instance()[kPort] = HTTP_PORT;
|
||||||
mINI::Instance()[kSSLPort] = HTTPS_PORT;
|
mINI::Instance()[kSSLPort] = HTTPS_PORT;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
}//namespace Http
|
}//namespace Http
|
||||||
} // namespace mediakit
|
} // namespace mediakit
|
||||||
|
|
||||||
void initEventListener(){
|
void initEventListener(){
|
||||||
static onceToken s_token([](){
|
static onceToken s_token([](){
|
||||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpRequest,[](BroadcastHttpRequestArgs){
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpRequest,[](BroadcastHttpRequestArgs){
|
||||||
//const Parser &parser,HttpSession::HttpResponseInvoker &invoker,bool &consumed
|
//const Parser &parser,HttpSession::HttpResponseInvoker &invoker,bool &consumed
|
||||||
if(strstr(parser.Url().data(),"/api/") != parser.Url().data()){
|
if(strstr(parser.Url().data(),"/api/") != parser.Url().data()){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//url以"/api/起始,说明是http api"
|
//url以"/api/起始,说明是http api"
|
||||||
consumed = true;//该http请求已被消费
|
consumed = true;//该http请求已被消费
|
||||||
|
|
||||||
_StrPrinter printer;
|
_StrPrinter printer;
|
||||||
////////////////method////////////////////
|
////////////////method////////////////////
|
||||||
printer << "\r\nmethod:\r\n\t" << parser.Method();
|
printer << "\r\nmethod:\r\n\t" << parser.Method();
|
||||||
////////////////url/////////////////
|
////////////////url/////////////////
|
||||||
printer << "\r\nurl:\r\n\t" << parser.Url();
|
printer << "\r\nurl:\r\n\t" << parser.Url();
|
||||||
////////////////protocol/////////////////
|
////////////////protocol/////////////////
|
||||||
printer << "\r\nprotocol:\r\n\t" << parser.Tail();
|
printer << "\r\nprotocol:\r\n\t" << parser.Tail();
|
||||||
///////////////args//////////////////
|
///////////////args//////////////////
|
||||||
printer << "\r\nargs:\r\n";
|
printer << "\r\nargs:\r\n";
|
||||||
for(auto &pr : parser.getUrlArgs()){
|
for(auto &pr : parser.getUrlArgs()){
|
||||||
printer << "\t" << pr.first << " : " << pr.second << "\r\n";
|
printer << "\t" << pr.first << " : " << pr.second << "\r\n";
|
||||||
}
|
}
|
||||||
///////////////header//////////////////
|
///////////////header//////////////////
|
||||||
printer << "\r\nheader:\r\n";
|
printer << "\r\nheader:\r\n";
|
||||||
for(auto &pr : parser.getValues()){
|
for(auto &pr : parser.getValues()){
|
||||||
printer << "\t" << pr.first << " : " << pr.second << "\r\n";
|
printer << "\t" << pr.first << " : " << pr.second << "\r\n";
|
||||||
}
|
}
|
||||||
////////////////content/////////////////
|
////////////////content/////////////////
|
||||||
printer << "\r\ncontent:\r\n" << parser.Content();
|
printer << "\r\ncontent:\r\n" << parser.Content();
|
||||||
auto contentOut = printer << endl;
|
auto contentOut = printer << endl;
|
||||||
|
|
||||||
////////////////我们测算异步回复,当然你也可以同步回复/////////////////
|
////////////////我们测算异步回复,当然你也可以同步回复/////////////////
|
||||||
EventPollerPool::Instance().getPoller()->async([invoker,contentOut](){
|
EventPollerPool::Instance().getPoller()->async([invoker,contentOut](){
|
||||||
HttpSession::KeyValue headerOut;
|
HttpSession::KeyValue headerOut;
|
||||||
//你可以自定义header,如果跟默认header重名,则会覆盖之
|
//你可以自定义header,如果跟默认header重名,则会覆盖之
|
||||||
//默认header有:Server,Connection,Date,Content-Type,Content-Length
|
//默认header有:Server,Connection,Date,Content-Type,Content-Length
|
||||||
//请勿覆盖Connection、Content-Length键
|
//请勿覆盖Connection、Content-Length键
|
||||||
//键名覆盖时不区分大小写
|
//键名覆盖时不区分大小写
|
||||||
headerOut["TestHeader"] = "HeaderValue";
|
headerOut["TestHeader"] = "HeaderValue";
|
||||||
invoker("200 OK",headerOut,contentOut);
|
invoker("200 OK",headerOut,contentOut);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, nullptr);
|
}, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc,char *argv[]){
|
int main(int argc,char *argv[]){
|
||||||
//设置退出信号处理函数
|
//设置退出信号处理函数
|
||||||
static semaphore sem;
|
static semaphore sem;
|
||||||
signal(SIGINT, [](int) { sem.post(); });// 设置退出信号
|
signal(SIGINT, [](int) { sem.post(); });// 设置退出信号
|
||||||
|
|
||||||
//设置日志
|
//设置日志
|
||||||
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
||||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||||
|
|
||||||
//加载配置文件,如果配置文件不存在就创建一个
|
//加载配置文件,如果配置文件不存在就创建一个
|
||||||
loadIniConfig();
|
loadIniConfig();
|
||||||
initEventListener();
|
initEventListener();
|
||||||
|
|
||||||
//加载证书,证书包含公钥和私钥
|
//加载证书,证书包含公钥和私钥
|
||||||
SSL_Initor::Instance().loadCertificate((exeDir() + "ssl.p12").data());
|
SSL_Initor::Instance().loadCertificate((exeDir() + "ssl.p12").data());
|
||||||
//信任某个自签名证书
|
//信任某个自签名证书
|
||||||
SSL_Initor::Instance().trustCertificate((exeDir() + "ssl.p12").data());
|
SSL_Initor::Instance().trustCertificate((exeDir() + "ssl.p12").data());
|
||||||
//不忽略无效证书证书(例如自签名或过期证书)
|
//不忽略无效证书证书(例如自签名或过期证书)
|
||||||
SSL_Initor::Instance().ignoreInvalidCertificate(false);
|
SSL_Initor::Instance().ignoreInvalidCertificate(false);
|
||||||
|
|
||||||
|
|
||||||
//开启http服务器
|
//开启http服务器
|
||||||
TcpServer::Ptr httpSrv(new TcpServer());
|
TcpServer::Ptr httpSrv(new TcpServer());
|
||||||
httpSrv->start<HttpSession>(mINI::Instance()[Http::kPort]);//默认80
|
httpSrv->start<HttpSession>(mINI::Instance()[Http::kPort]);//默认80
|
||||||
|
|
||||||
//如果支持ssl,还可以开启https服务器
|
//如果支持ssl,还可以开启https服务器
|
||||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||||
httpsSrv->start<HttpsSession>(mINI::Instance()[Http::kSSLPort]);//默认443
|
httpsSrv->start<HttpsSession>(mINI::Instance()[Http::kSSLPort]);//默认443
|
||||||
|
|
||||||
InfoL << "你可以在浏览器输入:http://127.0.0.1/api/my_api?key0=val0&key1=参数1" << endl;
|
InfoL << "你可以在浏览器输入:http://127.0.0.1/api/my_api?key0=val0&key1=参数1" << endl;
|
||||||
|
|
||||||
sem.wait();
|
sem.wait();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,92 +48,92 @@ Timer::Ptr g_timer;
|
|||||||
//推流失败或断开延迟2秒后重试推流
|
//推流失败或断开延迟2秒后重试推流
|
||||||
void rePushDelay(const EventPoller::Ptr &poller,
|
void rePushDelay(const EventPoller::Ptr &poller,
|
||||||
const string &schema,
|
const string &schema,
|
||||||
const string &vhost,
|
const string &vhost,
|
||||||
const string &app,
|
const string &app,
|
||||||
const string &stream,
|
const string &stream,
|
||||||
const string &filePath,
|
const string &filePath,
|
||||||
const string &url) ;
|
const string &url) ;
|
||||||
|
|
||||||
//创建推流器并开始推流
|
//创建推流器并开始推流
|
||||||
void createPusher(const EventPoller::Ptr &poller,
|
void createPusher(const EventPoller::Ptr &poller,
|
||||||
const string &schema,
|
const string &schema,
|
||||||
const string &vhost,
|
const string &vhost,
|
||||||
const string &app,
|
const string &app,
|
||||||
const string &stream,
|
const string &stream,
|
||||||
const string &filePath,
|
const string &filePath,
|
||||||
const string &url) {
|
const string &url) {
|
||||||
//不限制APP名,并且指定文件绝对路径
|
//不限制APP名,并且指定文件绝对路径
|
||||||
auto src = MP4Reader::onMakeMediaSource(schema,vhost,app,stream,filePath, false);
|
auto src = MP4Reader::onMakeMediaSource(schema,vhost,app,stream,filePath, false);
|
||||||
if(!src){
|
if(!src){
|
||||||
//文件不存在
|
//文件不存在
|
||||||
WarnL << "MP4文件不存在:" << filePath;
|
WarnL << "MP4文件不存在:" << filePath;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//创建推流器并绑定一个MediaSource
|
//创建推流器并绑定一个MediaSource
|
||||||
pusher.reset(new MediaPusher(src,poller));
|
pusher.reset(new MediaPusher(src,poller));
|
||||||
//可以指定rtsp推流方式,支持tcp和udp方式,默认tcp
|
//可以指定rtsp推流方式,支持tcp和udp方式,默认tcp
|
||||||
// (*pusher)[Client::kRtpType] = Rtsp::RTP_UDP;
|
// (*pusher)[Client::kRtpType] = Rtsp::RTP_UDP;
|
||||||
|
|
||||||
//设置推流中断处理逻辑
|
//设置推流中断处理逻辑
|
||||||
pusher->setOnShutdown([poller,schema,vhost,app,stream,filePath, url](const SockException &ex) {
|
pusher->setOnShutdown([poller,schema,vhost,app,stream,filePath, url](const SockException &ex) {
|
||||||
WarnL << "Server connection is closed:" << ex.getErrCode() << " " << ex.what();
|
WarnL << "Server connection is closed:" << ex.getErrCode() << " " << ex.what();
|
||||||
//重新推流
|
//重新推流
|
||||||
rePushDelay(poller,schema,vhost,app, stream,filePath, url);
|
rePushDelay(poller,schema,vhost,app, stream,filePath, url);
|
||||||
});
|
});
|
||||||
//设置发布结果处理逻辑
|
//设置发布结果处理逻辑
|
||||||
pusher->setOnPublished([poller,schema,vhost,app,stream,filePath, url](const SockException &ex) {
|
pusher->setOnPublished([poller,schema,vhost,app,stream,filePath, url](const SockException &ex) {
|
||||||
if (ex) {
|
if (ex) {
|
||||||
WarnL << "Publish fail:" << ex.getErrCode() << " " << ex.what();
|
WarnL << "Publish fail:" << ex.getErrCode() << " " << ex.what();
|
||||||
//如果发布失败,就重试
|
//如果发布失败,就重试
|
||||||
rePushDelay(poller,schema,vhost,app, stream, filePath ,url);
|
rePushDelay(poller,schema,vhost,app, stream, filePath ,url);
|
||||||
}else {
|
}else {
|
||||||
InfoL << "Publish success,Please play with player:" << url;
|
InfoL << "Publish success,Please play with player:" << url;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
pusher->publish(url);
|
pusher->publish(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
//推流失败或断开延迟2秒后重试推流
|
//推流失败或断开延迟2秒后重试推流
|
||||||
void rePushDelay(const EventPoller::Ptr &poller,
|
void rePushDelay(const EventPoller::Ptr &poller,
|
||||||
const string &schema,
|
const string &schema,
|
||||||
const string &vhost,
|
const string &vhost,
|
||||||
const string &app,
|
const string &app,
|
||||||
const string &stream,
|
const string &stream,
|
||||||
const string &filePath,
|
const string &filePath,
|
||||||
const string &url) {
|
const string &url) {
|
||||||
g_timer = std::make_shared<Timer>(2,[poller,schema,vhost,app, stream, filePath,url]() {
|
g_timer = std::make_shared<Timer>(2,[poller,schema,vhost,app, stream, filePath,url]() {
|
||||||
InfoL << "Re-Publishing...";
|
InfoL << "Re-Publishing...";
|
||||||
//重新推流
|
//重新推流
|
||||||
createPusher(poller,schema,vhost,app, stream, filePath,url);
|
createPusher(poller,schema,vhost,app, stream, filePath,url);
|
||||||
//此任务不重复
|
//此任务不重复
|
||||||
return false;
|
return false;
|
||||||
}, poller);
|
}, poller);
|
||||||
}
|
}
|
||||||
|
|
||||||
//这里才是真正执行main函数,你可以把函数名(domain)改成main,然后就可以输入自定义url了
|
//这里才是真正执行main函数,你可以把函数名(domain)改成main,然后就可以输入自定义url了
|
||||||
int domain(const string & filePath,const string & pushUrl){
|
int domain(const string & filePath,const string & pushUrl){
|
||||||
//设置日志
|
//设置日志
|
||||||
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
||||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||||
auto poller = EventPollerPool::Instance().getPoller();
|
auto poller = EventPollerPool::Instance().getPoller();
|
||||||
//vhost/app/stream可以随便自己填,现在不限制app应用名了
|
//vhost/app/stream可以随便自己填,现在不限制app应用名了
|
||||||
createPusher(poller,FindField(pushUrl.data(), nullptr,"://"),DEFAULT_VHOST,"live","stream",filePath,pushUrl);
|
createPusher(poller,FindField(pushUrl.data(), nullptr,"://"),DEFAULT_VHOST,"live","stream",filePath,pushUrl);
|
||||||
|
|
||||||
//设置退出信号处理函数
|
//设置退出信号处理函数
|
||||||
static semaphore sem;
|
static semaphore sem;
|
||||||
signal(SIGINT, [](int) { sem.post(); });// 设置退出信号
|
signal(SIGINT, [](int) { sem.post(); });// 设置退出信号
|
||||||
sem.wait();
|
sem.wait();
|
||||||
pusher.reset();
|
pusher.reset();
|
||||||
g_timer.reset();
|
g_timer.reset();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc,char *argv[]){
|
int main(int argc,char *argv[]){
|
||||||
//可以使用test_server生成的mp4文件
|
//可以使用test_server生成的mp4文件
|
||||||
//文件使用绝对路径,推流url支持rtsp和rtmp
|
//文件使用绝对路径,推流url支持rtsp和rtmp
|
||||||
return domain("/Users/xzl/Desktop/bear-1280x720-long.mp4","rtsp://127.0.0.1/live/rtsp_push");
|
return domain("/Users/xzl/Desktop/bear-1280x720-long.mp4","rtsp://127.0.0.1/live/rtsp_push");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,6 +357,6 @@ int main(int argc,char *argv[]) {
|
|||||||
Recorder::stopAll();
|
Recorder::stopAll();
|
||||||
lock_guard<mutex> lck(s_mtxFlvRecorder);
|
lock_guard<mutex> lck(s_mtxFlvRecorder);
|
||||||
s_mapFlvRecorder.clear();
|
s_mapFlvRecorder.clear();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user