diff --git a/src/MediaFile/MediaReader.cpp b/src/MediaFile/MediaReader.cpp index 19a86536..a1612b3c 100644 --- a/src/MediaFile/MediaReader.cpp +++ b/src/MediaFile/MediaReader.cpp @@ -33,10 +33,12 @@ using namespace toolkit; namespace mediakit { #ifdef ENABLE_MP4V2 -MediaReader::MediaReader(const string &strVhost,const string &strApp, const string &strId) { - GET_CONFIG_AND_REGISTER(string,recordPath,Record::kFilePath); - - auto strFileName = recordPath + "/" + strVhost + "/" + strApp + "/" + strId; +MediaReader::MediaReader(const string &strVhost,const string &strApp, const string &strId,const string &filePath ) { + auto strFileName = filePath; + if(strFileName.empty()){ + GET_CONFIG_AND_REGISTER(string,recordPath,Record::kFilePath); + strFileName = recordPath + "/" + strVhost + "/" + strApp + "/" + strId; + } _hMP4File = MP4Read(strFileName.data()); if(_hMP4File == MP4_INVALID_FILE_HANDLE){ @@ -302,15 +304,19 @@ void MediaReader::seek(uint32_t iSeekTime,bool bReStart){ -MediaSource::Ptr MediaReader::onMakeMediaSource(const string &strSchema,const string &strVhost,const string &strApp, const string &strId){ +MediaSource::Ptr MediaReader::onMakeMediaSource(const string &strSchema, + const string &strVhost, + const string &strApp, + const string &strId, + const string &filePath, + bool checkApp ){ #ifdef ENABLE_MP4V2 GET_CONFIG_AND_REGISTER(string,appName,Record::kAppName); - - if (strApp != appName) { + if (checkApp && strApp != appName) { return nullptr; } try { - MediaReader::Ptr pReader(new MediaReader(strVhost,strApp, strId)); + MediaReader::Ptr pReader(new MediaReader(strVhost,strApp, strId,filePath)); pReader->startReadMP4(); return MediaSource::find(strSchema,strVhost,strApp, strId, false); } catch (std::exception &ex) { diff --git a/src/MediaFile/MediaReader.h b/src/MediaFile/MediaReader.h index 7f7d7bb3..d6db5e61 100644 --- a/src/MediaFile/MediaReader.h +++ b/src/MediaFile/MediaReader.h @@ -41,19 +41,56 @@ namespace mediakit { class MediaReader : public std::enable_shared_from_this ,public MediaSourceEvent{ public: typedef std::shared_ptr Ptr; - MediaReader(const string &strVhost,const string &strApp, const string &strId); virtual ~MediaReader(); - static MediaSource::Ptr onMakeMediaSource(const string &strSchema,const string &strVhost,const string &strApp, const string &strId); -public: - bool seekTo(uint32_t ui32Stamp) override; - bool close() override; + /** + * 流化一个mp4文件,使之转换成RtspMediaSource和RtmpMediaSource + * @param strVhost 虚拟主机 + * @param strApp 应用名 + * @param strId 流id + * @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件 + */ + MediaReader(const string &strVhost,const string &strApp, const string &strId,const string &filePath = ""); + /** + * 开始流化MP4文件,需要指出的是,MediaReader对象一经过调用startReadMP4方法,它的强引用会自持有, + * 意思是在文件流化结束之前或中断之前,MediaReader对象是不会被销毁的(不管有没有被外部对象持有) + */ + void startReadMP4(); + + /** + * 设置时移偏移量 + * @param ui32Stamp 偏移量,单位毫秒 + * @return + */ + bool seekTo(uint32_t ui32Stamp) override; + + /** + * 关闭MediaReader的流化进程,会触发该对象放弃自持有 + * @return + */ + bool close() override; + + /** + * 自动生成MediaReader对象然后查找相关的MediaSource对象 + * @param strSchema 协议名 + * @param strVhost 虚拟主机 + * @param strApp 应用名 + * @param strId 流id + * @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件 + * @param checkApp 是否检查app,防止服务器上文件被乱访问 + * @return MediaSource + */ + static MediaSource::Ptr onMakeMediaSource(const string &strSchema, + const string &strVhost, + const string &strApp, + const string &strId, + const string &filePath = "", + bool checkApp = true); #ifdef ENABLE_MP4V2 private: void seek(uint32_t iSeekTime,bool bReStart = true); inline void setSeekTime(uint32_t iSeekTime); inline uint32_t getVideoCurrentTime(); - void startReadMP4(); inline MP4SampleId getVideoSampleId(int iTimeInc = 0); inline MP4SampleId getAudioSampleId(int iTimeInc = 0); bool readSample(int iTimeInc, bool justSeekSyncFrame); diff --git a/tests/test_pusherMp4.cpp b/tests/test_pusherMp4.cpp index d67ac45a..537d8ee5 100644 --- a/tests/test_pusherMp4.cpp +++ b/tests/test_pusherMp4.cpp @@ -43,32 +43,46 @@ using namespace mediakit; MediaPusher::Ptr pusher; //声明函数 -void rePushDelay(const string &schema,const string &vhost,const string &app, const string &stream, const string &url); - +//推流失败或断开延迟2秒后重试推流 +void rePushDelay(const string &schema, + const string &vhost, + const string &app, + const string &stream, + const string &filePath, + const string &url) ; //创建推流器并开始推流 -void createPusher(const string &schema,const string &vhost,const string &app, const string &stream, const string &url) { - auto src = MediaReader::onMakeMediaSource(schema,vhost,app,stream); +void createPusher(const string &schema, + const string &vhost, + const string &app, + const string &stream, + const string &filePath, + const string &url) { + //不限制APP名,并且指定文件绝对路径 + auto src = MediaReader::onMakeMediaSource(schema,vhost,app,stream,filePath, false); if(!src){ //文件不存在 - WarnL << "MP4 file not exited!"; + WarnL << "MP4文件不存在:" << filePath; return; } //创建推流器并绑定一个MediaSource pusher.reset(new MediaPusher(src)); + //可以指定rtsp推流方式,支持tcp和udp方式,默认tcp +// (*pusher)[Client::kRtpType] = Rtsp::RTP_UDP; + //设置推流中断处理逻辑 - pusher->setOnShutdown([schema,vhost,app,stream, url](const SockException &ex) { + pusher->setOnShutdown([schema,vhost,app,stream,filePath, url](const SockException &ex) { WarnL << "Server connection is closed:" << ex.getErrCode() << " " << ex.what(); //重新推流 - rePushDelay(schema,vhost,app, stream, url); + rePushDelay(schema,vhost,app, stream,filePath, url); }); //设置发布结果处理逻辑 - pusher->setOnPublished([schema,vhost,app,stream, url](const SockException &ex) { + pusher->setOnPublished([schema,vhost,app,stream,filePath, url](const SockException &ex) { if (ex) { WarnL << "Publish fail:" << ex.getErrCode() << " " << ex.what(); //如果发布失败,就重试 - rePushDelay(schema,vhost,app, stream, url); + rePushDelay(schema,vhost,app, stream, filePath ,url); }else { InfoL << "Publish success,Please play with player:" << url; } @@ -78,11 +92,16 @@ void createPusher(const string &schema,const string &vhost,const string &app, co Timer::Ptr g_timer; //推流失败或断开延迟2秒后重试推流 -void rePushDelay(const string &schema,const string &vhost,const string &app, const string &stream, const string &url) { - g_timer = std::make_shared(2,[schema,vhost,app, stream, url]() { +void rePushDelay(const string &schema, + const string &vhost, + const string &app, + const string &stream, + const string &filePath, + const string &url) { + g_timer = std::make_shared(2,[schema,vhost,app, stream, filePath,url]() { InfoL << "Re-Publishing..."; //重新推流 - createPusher(schema,vhost,app, stream, url); + createPusher(schema,vhost,app, stream, filePath,url); //此任务不重复 return false; }, nullptr); @@ -90,19 +109,15 @@ void rePushDelay(const string &schema,const string &vhost,const string &app, con //这里才是真正执行main函数,你可以把函数名(domain)改成main,然后就可以输入自定义url了 int domain(const string & filePath,const string & pushUrl){ - //设置退出信号处理函数 - static semaphore sem; - signal(SIGINT, [](int) { sem.post(); });// 设置退出信号 - //设置日志 Logger::Instance().add(std::make_shared()); Logger::Instance().setWriter(std::make_shared()); + //vhost/app/stream可以随便自己填,现在不限制app应用名了 + createPusher(FindField(pushUrl.data(), nullptr,"://"),DEFAULT_VHOST,"live","stream",filePath,pushUrl); - //录像应用名称默认为record - string appName = mINI::Instance()[Record::kAppName]; - //app必须record,filePath(流id)为相对于httpRoot/record的路径,否则MediaReader会找到不该文件 - //限制app为record是为了防止服务器上的文件被肆意访问 - createPusher(FindField(pushUrl.data(), nullptr,"://"),DEFAULT_VHOST,appName,filePath,pushUrl); + //设置退出信号处理函数 + static semaphore sem; + signal(SIGINT, [](int) { sem.post(); });// 设置退出信号 sem.wait(); pusher.reset(); g_timer.reset(); @@ -112,9 +127,9 @@ int domain(const string & filePath,const string & pushUrl){ int main(int argc,char *argv[]){ - //MP4文件需要放置在 httpRoot/record目录下,文件负载必须为h264+aac //可以使用test_server生成的mp4文件 - return domain("app/stream/2017-09-30/12-55-38.mp4","rtsp://127.0.0.1/live/rtsp_push"); + //文件使用绝对路径,推流url支持rtsp和rtmp + return domain("/Users/xzl/Desktop/bear-1280x720-long.mp4","rtsp://127.0.0.1/live/rtsp_push"); }