mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 10:40:05 +08:00
hls/mp4录制不再能随时开启或关闭
This commit is contained in:
parent
a581f78d12
commit
cefb2a7cc4
@ -60,47 +60,6 @@ API_EXPORT void API_CALL mk_flv_recorder_release(mk_flv_recorder ctx);
|
|||||||
*/
|
*/
|
||||||
API_EXPORT int API_CALL mk_flv_recorder_start(mk_flv_recorder ctx, const char *vhost, const char *app, const char *stream, const char *file_path);
|
API_EXPORT int API_CALL mk_flv_recorder_start(mk_flv_recorder ctx, const char *vhost, const char *app, const char *stream, const char *file_path);
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////hls/mp4录制/////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取录制状态
|
|
||||||
* @param type 0:hls,1:MP4
|
|
||||||
* @param vhost 虚拟主机
|
|
||||||
* @param app 应用名
|
|
||||||
* @param stream 流id
|
|
||||||
* @return 录制状态,0:未录制,1:等待MediaSource注册,注册成功后立即开始录制,2:MediaSource已注册,并且正在录制
|
|
||||||
*/
|
|
||||||
API_EXPORT int API_CALL mk_recorder_status(int type, const char *vhost, const char *app, const char *stream);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始录制
|
|
||||||
* @param type 0:hls,1:MP4
|
|
||||||
* @param vhost 虚拟主机
|
|
||||||
* @param app 应用名
|
|
||||||
* @param stream 流id
|
|
||||||
* @param customized_path 录像文件保存自定义目录,默认为空或null则自动生成
|
|
||||||
* @param wait_for_record 是否等待流注册后再录制,未注册时,置false将返回失败
|
|
||||||
* @param continue_record 流注销时是否继续等待录制还是立即停止录制
|
|
||||||
* @return 0代表成功,负数代表失败
|
|
||||||
*/
|
|
||||||
API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const char *app, const char *stream, const char *customized_path, int wait_for_record, int continue_record);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止录制
|
|
||||||
* @param type 0:hls,1:MP4
|
|
||||||
* @param vhost 虚拟主机
|
|
||||||
* @param app 应用名
|
|
||||||
* @param stream 流id
|
|
||||||
* @return 1:成功,0:失败
|
|
||||||
*/
|
|
||||||
API_EXPORT int API_CALL mk_recorder_stop(int type, const char *vhost, const char *app, const char *stream);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止所有录制,一般程序退出时调用
|
|
||||||
*/
|
|
||||||
API_EXPORT void API_CALL mk_recorder_stop_all();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -49,24 +49,4 @@ API_EXPORT int API_CALL mk_flv_recorder_start(mk_flv_recorder ctx, const char *v
|
|||||||
WarnL << ex.what();
|
WarnL << ex.what();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////hls/mp4录制/////////////////////////////////////////////
|
|
||||||
API_EXPORT int API_CALL mk_recorder_status(int type, const char *vhost, const char *app, const char *stream){
|
|
||||||
assert(vhost && app && stream);
|
|
||||||
return Recorder::getRecordStatus((Recorder::type)type,vhost,app,stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const char *app, const char *stream,const char *customized_path,int wait_for_record, int continue_record){
|
|
||||||
assert(vhost && app && stream);
|
|
||||||
return Recorder::startRecord((Recorder::type)type,vhost,app,stream,customized_path ? customized_path : "",wait_for_record,continue_record);
|
|
||||||
}
|
|
||||||
|
|
||||||
API_EXPORT int API_CALL mk_recorder_stop(int type, const char *vhost, const char *app, const char *stream){
|
|
||||||
assert(vhost && app && stream);
|
|
||||||
return Recorder::stopRecord((Recorder::type)type,vhost,app,stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
API_EXPORT void API_CALL mk_recorder_stop_all(){
|
|
||||||
Recorder::stopAll();
|
|
||||||
}
|
}
|
@ -737,43 +737,6 @@ void installWebApi() {
|
|||||||
});
|
});
|
||||||
#endif//ENABLE_RTPPROXY
|
#endif//ENABLE_RTPPROXY
|
||||||
|
|
||||||
// 开始录制hls或MP4
|
|
||||||
api_regist1("/index/api/startRecord",[](API_ARGS1){
|
|
||||||
CHECK_SECRET();
|
|
||||||
CHECK_ARGS("type","vhost","app","stream","wait_for_record","continue_record");
|
|
||||||
|
|
||||||
int result = Recorder::startRecord((Recorder::type)allArgs["type"].as<int>(),
|
|
||||||
allArgs["vhost"],
|
|
||||||
allArgs["app"],
|
|
||||||
allArgs["stream"],
|
|
||||||
allArgs["customized_path"],
|
|
||||||
allArgs["wait_for_record"],
|
|
||||||
allArgs["continue_record"]);
|
|
||||||
val["result"] = result;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 停止录制hls或MP4
|
|
||||||
api_regist1("/index/api/stopRecord",[](API_ARGS1){
|
|
||||||
CHECK_SECRET();
|
|
||||||
CHECK_ARGS("type","vhost","app","stream");
|
|
||||||
int result = Recorder::stopRecord((Recorder::type)allArgs["type"].as<int>(),
|
|
||||||
allArgs["vhost"],
|
|
||||||
allArgs["app"],
|
|
||||||
allArgs["stream"]);
|
|
||||||
val["result"] = result;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取hls或MP4录制状态
|
|
||||||
api_regist1("/index/api/getRecordStatus",[](API_ARGS1){
|
|
||||||
CHECK_SECRET();
|
|
||||||
CHECK_ARGS("type","vhost","app","stream");
|
|
||||||
auto status = Recorder::getRecordStatus((Recorder::type)allArgs["type"].as<int>(),
|
|
||||||
allArgs["vhost"],
|
|
||||||
allArgs["app"],
|
|
||||||
allArgs["stream"]);
|
|
||||||
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){
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||||
@ -354,7 +354,6 @@ int start_main(int argc,char *argv[]) {
|
|||||||
}
|
}
|
||||||
unInstallWebApi();
|
unInstallWebApi();
|
||||||
unInstallWebHook();
|
unInstallWebHook();
|
||||||
Recorder::stopAll();
|
|
||||||
//休眠1秒再退出,防止资源释放顺序错误
|
//休眠1秒再退出,防止资源释放顺序错误
|
||||||
InfoL << "程序退出中,请等待...";
|
InfoL << "程序退出中,请等待...";
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
@ -78,7 +78,6 @@ vector<Track::Ptr> MediaSource::getTracks(bool trackReady) const {
|
|||||||
|
|
||||||
void MediaSource::setTrackSource(const std::weak_ptr<TrackSource> &track_src) {
|
void MediaSource::setTrackSource(const std::weak_ptr<TrackSource> &track_src) {
|
||||||
_track_source = track_src;
|
_track_source = track_src;
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaResetTracks, *this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||||
|
@ -57,20 +57,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(enable_hls){
|
if(enable_hls){
|
||||||
Recorder::startRecord(Recorder::type_hls,vhost, app, stream, "", true, false);
|
_hls = Recorder::createRecorder(Recorder::type_hls,vhost, app, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(enable_mp4){
|
if(enable_mp4){
|
||||||
Recorder::startRecord(Recorder::type_mp4,vhost, app, stream, "", true, false);
|
_mp4 = Recorder::createRecorder(Recorder::type_mp4,vhost, app, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
_get_hls_media_source = [vhost,app,stream](){
|
|
||||||
auto recorder = dynamic_pointer_cast<HlsRecorder>(Recorder::getRecorder(Recorder::type_hls,vhost,app,stream));
|
|
||||||
if(recorder){
|
|
||||||
return recorder->getMediaSource();
|
|
||||||
}
|
|
||||||
return MediaSource::Ptr();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
virtual ~MultiMediaSourceMuxer(){}
|
virtual ~MultiMediaSourceMuxer(){}
|
||||||
|
|
||||||
@ -84,6 +76,12 @@ public:
|
|||||||
if(_rtsp){
|
if(_rtsp){
|
||||||
_rtsp->resetTracks();
|
_rtsp->resetTracks();
|
||||||
}
|
}
|
||||||
|
if(_hls){
|
||||||
|
_hls->resetTracks();
|
||||||
|
}
|
||||||
|
if(_mp4){
|
||||||
|
_mp4->resetTracks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,7 +97,7 @@ public:
|
|||||||
_rtsp->setListener(listener);
|
_rtsp->setListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto hls_src = _get_hls_media_source();
|
auto hls_src = getHlsMediaSource();
|
||||||
if(hls_src){
|
if(hls_src){
|
||||||
hls_src->setListener(listener);
|
hls_src->setListener(listener);
|
||||||
}
|
}
|
||||||
@ -110,7 +108,7 @@ public:
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
int totalReaderCount() const{
|
int totalReaderCount() const{
|
||||||
auto hls_src = _get_hls_media_source();
|
auto hls_src = getHlsMediaSource();
|
||||||
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0);
|
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +116,6 @@ public:
|
|||||||
if(_rtmp){
|
if(_rtmp){
|
||||||
_rtmp->setTimeStamp(stamp);
|
_rtmp->setTimeStamp(stamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_rtsp){
|
if(_rtsp){
|
||||||
_rtsp->setTimeStamp(stamp);
|
_rtsp->setTimeStamp(stamp);
|
||||||
}
|
}
|
||||||
@ -139,6 +136,12 @@ protected:
|
|||||||
if(_rtsp){
|
if(_rtsp){
|
||||||
_rtsp->addTrack(track);
|
_rtsp->addTrack(track);
|
||||||
}
|
}
|
||||||
|
if(_hls){
|
||||||
|
_hls->addTrack(track);
|
||||||
|
}
|
||||||
|
if(_mp4){
|
||||||
|
_mp4->addTrack(track);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,6 +155,12 @@ protected:
|
|||||||
if(_rtsp) {
|
if(_rtsp) {
|
||||||
_rtsp->inputFrame(frame);
|
_rtsp->inputFrame(frame);
|
||||||
}
|
}
|
||||||
|
if(_hls){
|
||||||
|
_hls->inputFrame(frame);
|
||||||
|
}
|
||||||
|
if(_mp4){
|
||||||
|
_mp4->inputFrame(frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -167,7 +176,7 @@ protected:
|
|||||||
_rtsp->onAllTrackReady();
|
_rtsp->onAllTrackReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto hls_src = _get_hls_media_source();
|
auto hls_src = getHlsMediaSource();
|
||||||
if(hls_src){
|
if(hls_src){
|
||||||
hls_src->setTrackSource(shared_from_this());
|
hls_src->setTrackSource(shared_from_this());
|
||||||
}
|
}
|
||||||
@ -176,11 +185,20 @@ protected:
|
|||||||
_listener->onAllTrackReady();
|
_listener->onAllTrackReady();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediaSource::Ptr getHlsMediaSource() const{
|
||||||
|
auto recorder = dynamic_pointer_cast<HlsRecorder>(_hls);
|
||||||
|
if(recorder){
|
||||||
|
return recorder->getMediaSource();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
RtmpMediaSourceMuxer::Ptr _rtmp;
|
RtmpMediaSourceMuxer::Ptr _rtmp;
|
||||||
RtspMediaSourceMuxer::Ptr _rtsp;
|
RtspMediaSourceMuxer::Ptr _rtsp;
|
||||||
|
MediaSinkInterface::Ptr _hls;
|
||||||
|
MediaSinkInterface::Ptr _mp4;
|
||||||
Listener *_listener = nullptr;
|
Listener *_listener = nullptr;
|
||||||
function<MediaSource::Ptr ()> _get_hls_media_source;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +55,6 @@ bool loadIniConfig(const char *ini_path){
|
|||||||
////////////广播名称///////////
|
////////////广播名称///////////
|
||||||
namespace Broadcast {
|
namespace Broadcast {
|
||||||
const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
|
const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
|
||||||
const string kBroadcastMediaResetTracks = "kBroadcastMediaResetTracks";
|
|
||||||
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
|
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
|
||||||
const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
|
const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
|
||||||
const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
|
const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
|
||||||
|
@ -72,10 +72,6 @@ namespace Broadcast {
|
|||||||
extern const string kBroadcastMediaChanged;
|
extern const string kBroadcastMediaChanged;
|
||||||
#define BroadcastMediaChangedArgs const bool &bRegist, MediaSource &sender
|
#define BroadcastMediaChangedArgs const bool &bRegist, MediaSource &sender
|
||||||
|
|
||||||
//MediaSource重置Track事件
|
|
||||||
extern const string kBroadcastMediaResetTracks;
|
|
||||||
#define BroadcastMediaResetTracksArgs MediaSource &sender
|
|
||||||
|
|
||||||
//录制mp4文件成功后广播
|
//录制mp4文件成功后广播
|
||||||
extern const string kBroadcastRecordMP4;
|
extern const string kBroadcastRecordMP4;
|
||||||
#define BroadcastRecordMP4Args const MP4Info &info
|
#define BroadcastRecordMP4Args const MP4Info &info
|
||||||
|
@ -70,286 +70,6 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
class RecorderHelper {
|
|
||||||
public:
|
|
||||||
typedef std::shared_ptr<RecorderHelper> Ptr;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建函数
|
|
||||||
* @param bContinueRecord false表明hls录制从头开始录制(意味着hls临时文件在媒体反注册时会被删除)
|
|
||||||
*/
|
|
||||||
RecorderHelper(const MediaSinkInterface::Ptr &recorder, bool bContinueRecord) {
|
|
||||||
_recorder = recorder;
|
|
||||||
_continueRecord = bContinueRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
~RecorderHelper() {
|
|
||||||
resetTracks();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 附则于track上
|
|
||||||
void attachTracks(vector<Track::Ptr> &&tracks, const string &schema){
|
|
||||||
if(isTracksSame(tracks)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resetTracks();
|
|
||||||
_tracks = std::move(tracks);
|
|
||||||
_schema = schema;
|
|
||||||
for (auto &track : _tracks) {
|
|
||||||
_recorder->addTrack(track);
|
|
||||||
track->addDelegate(_recorder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 判断新的tracks是否与之前的一致
|
|
||||||
bool isTracksSame(const vector<Track::Ptr> &tracks){
|
|
||||||
if(tracks.size() != _tracks.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int i = 0;
|
|
||||||
for(auto &track : tracks){
|
|
||||||
if(track != _tracks[i++]){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置所有track
|
|
||||||
void resetTracks(){
|
|
||||||
if(_tracks.empty()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (auto &track : _tracks) {
|
|
||||||
track->delDelegate(_recorder.get());
|
|
||||||
}
|
|
||||||
_tracks.clear();
|
|
||||||
_recorder->resetTracks();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回false表明hls录制从头开始录制(意味着hls临时文件在媒体反注册时会被删除)
|
|
||||||
bool continueRecord(){
|
|
||||||
return _continueRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isRecording() {
|
|
||||||
return !_tracks.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
const string &getSchema() const{
|
|
||||||
return _schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MediaSinkInterface::Ptr& getRecorder() const{
|
|
||||||
return _recorder;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
MediaSinkInterface::Ptr _recorder;
|
|
||||||
vector<Track::Ptr> _tracks;
|
|
||||||
bool _continueRecord;
|
|
||||||
string _schema;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template<Recorder::type type>
|
|
||||||
class MediaSourceWatcher {
|
|
||||||
public:
|
|
||||||
static MediaSourceWatcher& Instance(){
|
|
||||||
static MediaSourceWatcher instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Recorder::status getRecordStatus(const string &vhost, const string &app, const string &stream_id) {
|
|
||||||
return getRecordStatus_l(getRecorderKey(vhost, app, stream_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaSinkInterface::Ptr getRecorder(const string &vhost, const string &app, const string &stream_id) const{
|
|
||||||
auto key = getRecorderKey(vhost, app, stream_id);
|
|
||||||
lock_guard<decltype(_recorder_mtx)> lck(_recorder_mtx);
|
|
||||||
auto it = _recorder_map.find(key);
|
|
||||||
if (it == _recorder_map.end()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return it->second->getRecorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
int startRecord(const string &vhost, const string &app, const string &stream_id, const string &customized_path, bool waitForRecord, bool continueRecord) {
|
|
||||||
auto key = getRecorderKey(vhost, app, stream_id);
|
|
||||||
lock_guard<decltype(_recorder_mtx)> lck(_recorder_mtx);
|
|
||||||
if (getRecordStatus_l(key) != Recorder::status_not_record) {
|
|
||||||
// 已经在录制了
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto src = findMediaSource(vhost, app, stream_id);
|
|
||||||
if (!waitForRecord && !src) {
|
|
||||||
// 暂时无法开启录制
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto recorder = Recorder::createRecorder(type, vhost, app, stream_id, customized_path);
|
|
||||||
if (!recorder) {
|
|
||||||
// 创建录制器失败
|
|
||||||
WarnL << "不支持该录制类型:" << type;
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
auto helper = std::make_shared<RecorderHelper>(recorder, continueRecord);
|
|
||||||
if(src){
|
|
||||||
auto tracks = src->getTracks(needTrackReady());
|
|
||||||
if(tracks.size()){
|
|
||||||
helper->attachTracks(std::move(tracks),src->getSchema());
|
|
||||||
}
|
|
||||||
auto hls_recorder = dynamic_pointer_cast<HlsRecorder>(recorder);
|
|
||||||
if(hls_recorder){
|
|
||||||
hls_recorder->getMediaSource()->setListener(src->getListener());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_recorder_map[key] = std::move(helper);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool stopRecord(const string &vhost, const string &app, const string &stream_id) {
|
|
||||||
lock_guard<decltype(_recorder_mtx)> lck(_recorder_mtx);
|
|
||||||
return _recorder_map.erase(getRecorderKey(vhost, app, stream_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopAll(){
|
|
||||||
lock_guard<decltype(_recorder_mtx)> lck(_recorder_mtx);
|
|
||||||
_recorder_map.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
MediaSourceWatcher(){
|
|
||||||
//保存NoticeCenter的强引用,防止在MediaSourceWatcher单例释放前释放NoticeCenter单例
|
|
||||||
_notice_center = NoticeCenter::Instance().shared_from_this();
|
|
||||||
_notice_center->addListener(this,Broadcast::kBroadcastMediaChanged,[this](BroadcastMediaChangedArgs){
|
|
||||||
if(!bRegist){
|
|
||||||
removeRecorder(sender);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_notice_center->addListener(this,Broadcast::kBroadcastMediaResetTracks,[this](BroadcastMediaResetTracksArgs){
|
|
||||||
addRecorder(sender);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
~MediaSourceWatcher(){
|
|
||||||
_notice_center->delListener(this,Broadcast::kBroadcastMediaChanged);
|
|
||||||
_notice_center->delListener(this,Broadcast::kBroadcastMediaResetTracks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addRecorder(MediaSource &sender){
|
|
||||||
auto tracks = sender.getTracks(needTrackReady());
|
|
||||||
auto key = getRecorderKey(sender.getVhost(),sender.getApp(),sender.getId());
|
|
||||||
lock_guard<decltype(_recorder_mtx)> lck(_recorder_mtx);
|
|
||||||
auto it = _recorder_map.find(key);
|
|
||||||
if(it == _recorder_map.end()){
|
|
||||||
// 录像记录不存在
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!it->second->isRecording() || it->second->getSchema() == sender.getSchema()){
|
|
||||||
// 绑定的协议一致或者并未正在录制则替换tracks
|
|
||||||
if (!tracks.empty()) {
|
|
||||||
it->second->attachTracks(std::move(tracks),sender.getSchema());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeRecorder(MediaSource &sender){
|
|
||||||
auto key = getRecorderKey(sender.getVhost(),sender.getApp(),sender.getId());
|
|
||||||
lock_guard<decltype(_recorder_mtx)> lck(_recorder_mtx);
|
|
||||||
auto it = _recorder_map.find(key);
|
|
||||||
if(it == _recorder_map.end() || it->second->getSchema() != sender.getSchema()){
|
|
||||||
// 录像记录不存在或绑定的协议不一致
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(it->second->continueRecord()){
|
|
||||||
// 如果可以继续录制,那么只重置tracks,不删除对象
|
|
||||||
it->second->resetTracks();
|
|
||||||
}else{
|
|
||||||
// 删除对象(意味着可能删除hls临时文件)
|
|
||||||
_recorder_map.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Recorder::status getRecordStatus_l(const string &key) {
|
|
||||||
auto it = _recorder_map.find(key);
|
|
||||||
if (it == _recorder_map.end()) {
|
|
||||||
return Recorder::status_not_record;
|
|
||||||
}
|
|
||||||
return it->second->isRecording() ? Recorder::status_recording : Recorder::status_wait_record;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找MediaSource以便录制
|
|
||||||
MediaSource::Ptr findMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
|
||||||
bool need_ready = needTrackReady();
|
|
||||||
auto src = MediaSource::find(RTMP_SCHEMA, vhost, app, stream_id);
|
|
||||||
if (src) {
|
|
||||||
auto ret = src->getTracks(need_ready);
|
|
||||||
if (!ret.empty()) {
|
|
||||||
return std::move(src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
src = MediaSource::find(RTSP_SCHEMA, vhost, app, stream_id);
|
|
||||||
if (src) {
|
|
||||||
auto ret = src->getTracks(need_ready);
|
|
||||||
if (!ret.empty()) {
|
|
||||||
return std::move(src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
string getRecorderKey(const string &vhost, const string &app, const string &stream_id) const{
|
|
||||||
return vhost + "/" + app + "/" + stream_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 有些录制类型不需要track就绪即可录制
|
|
||||||
*/
|
|
||||||
bool needTrackReady(){
|
|
||||||
switch (type){
|
|
||||||
case Recorder::type_hls:
|
|
||||||
return false;
|
|
||||||
case Recorder::type_mp4:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
mutable recursive_mutex _recorder_mtx;
|
|
||||||
NoticeCenter::Ptr _notice_center;
|
|
||||||
unordered_map<string, RecorderHelper::Ptr> _recorder_map;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Recorder::status Recorder::getRecordStatus(Recorder::type type, const string &vhost, const string &app, const string &stream_id) {
|
|
||||||
switch (type){
|
|
||||||
case type_mp4:
|
|
||||||
return MediaSourceWatcher<type_mp4>::Instance().getRecordStatus(vhost,app,stream_id);
|
|
||||||
case type_hls:
|
|
||||||
return MediaSourceWatcher<type_hls>::Instance().getRecordStatus(vhost,app,stream_id);
|
|
||||||
}
|
|
||||||
return status_not_record;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<MediaSinkInterface> Recorder::getRecorder(type type, const string &vhost, const string &app, const string &stream_id){
|
|
||||||
switch (type){
|
|
||||||
case type_mp4:
|
|
||||||
return MediaSourceWatcher<type_mp4>::Instance().getRecorder(vhost,app,stream_id);
|
|
||||||
case type_hls:
|
|
||||||
return MediaSourceWatcher<type_hls>::Instance().getRecorder(vhost,app,stream_id);
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path){
|
std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path){
|
||||||
auto path = Recorder::getRecordPath(type, vhost, app, stream_id);
|
auto path = Recorder::getRecordPath(type, vhost, app, stream_id);
|
||||||
@ -375,30 +95,4 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Recorder::startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path, bool waitForRecord, bool continueRecord) {
|
|
||||||
switch (type){
|
|
||||||
case type_mp4:
|
|
||||||
return MediaSourceWatcher<type_mp4>::Instance().startRecord(vhost,app,stream_id,customized_path,waitForRecord,continueRecord);
|
|
||||||
case type_hls:
|
|
||||||
return MediaSourceWatcher<type_hls>::Instance().startRecord(vhost,app,stream_id,customized_path,waitForRecord,continueRecord);
|
|
||||||
}
|
|
||||||
WarnL << "unknown record type: " << type;
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Recorder::stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id) {
|
|
||||||
switch (type){
|
|
||||||
case type_mp4:
|
|
||||||
return MediaSourceWatcher<type_mp4>::Instance().stopRecord(vhost,app,stream_id);
|
|
||||||
case type_hls:
|
|
||||||
return MediaSourceWatcher<type_hls>::Instance().stopRecord(vhost,app,stream_id);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Recorder::stopAll() {
|
|
||||||
MediaSourceWatcher<type_hls>::Instance().stopAll();
|
|
||||||
MediaSourceWatcher<type_mp4>::Instance().stopAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -26,26 +26,14 @@
|
|||||||
|
|
||||||
#ifndef SRC_MEDIAFILE_RECORDER_H_
|
#ifndef SRC_MEDIAFILE_RECORDER_H_
|
||||||
#define SRC_MEDIAFILE_RECORDER_H_
|
#define SRC_MEDIAFILE_RECORDER_H_
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class MediaSinkInterface;
|
class MediaSinkInterface;
|
||||||
|
|
||||||
class Recorder{
|
class Recorder{
|
||||||
public:
|
public:
|
||||||
typedef enum {
|
|
||||||
// 未录制
|
|
||||||
status_not_record = 0,
|
|
||||||
// 等待MediaSource注册,注册成功后立即开始录制
|
|
||||||
status_wait_record = 1,
|
|
||||||
// MediaSource已注册,并且正在录制
|
|
||||||
status_recording = 2,
|
|
||||||
} status;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
// 录制hls
|
// 录制hls
|
||||||
type_hls = 0,
|
type_hls = 0,
|
||||||
@ -64,52 +52,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
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 = "");
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取录制状态
|
|
||||||
* @param type hls还是MP4录制
|
|
||||||
* @param vhost 虚拟主机
|
|
||||||
* @param app 应用名
|
|
||||||
* @param stream_id 流id
|
|
||||||
* @return 录制状态
|
|
||||||
*/
|
|
||||||
static status getRecordStatus(type type, const string &vhost, const string &app, const string &stream_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始录制
|
|
||||||
* @param type hls还是MP4录制
|
|
||||||
* @param vhost 虚拟主机
|
|
||||||
* @param app 应用名
|
|
||||||
* @param stream_id 流id
|
|
||||||
* @param customized_path 录像文件保存自定义目录,默认为空则自动生成
|
|
||||||
* @param waitForRecord 是否等待流注册后再录制,未注册时,置false将返回失败
|
|
||||||
* @param continueRecord 流注销时是否继续等待录制还是立即停止录制
|
|
||||||
* @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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止录制
|
|
||||||
* @param type hls还是MP4录制
|
|
||||||
* @param vhost 虚拟主机
|
|
||||||
* @param app 应用名
|
|
||||||
* @param stream_id 流id
|
|
||||||
*/
|
|
||||||
static bool stopRecord(type type, const string &vhost, const string &app, const string &stream_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止所有录制,一般程序退出时调用
|
|
||||||
*/
|
|
||||||
static void stopAll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取录制对象
|
|
||||||
* @param type hls还是MP4录制
|
|
||||||
* @param vhost 虚拟主机
|
|
||||||
* @param app 应用名
|
|
||||||
* @param stream_id 流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录制
|
||||||
@ -119,12 +61,11 @@ public:
|
|||||||
* @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 */
|
||||||
|
|
||||||
#endif /* SRC_MEDIAFILE_RECORDER_H_ */
|
#endif /* SRC_MEDIAFILE_RECORDER_H_ */
|
||||||
|
@ -354,7 +354,6 @@ int main(int argc,char *argv[]) {
|
|||||||
signal(SIGHUP, [](int) { loadIniConfig(); });
|
signal(SIGHUP, [](int) { loadIniConfig(); });
|
||||||
sem.wait();
|
sem.wait();
|
||||||
|
|
||||||
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