Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
ZLMediaKit
概览
Overview
Details
Activity
Cycle Analytics
版本库
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
问题
0
Issues
0
列表
Board
标记
里程碑
合并请求
0
Merge Requests
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
Snippets
成员
Collapse sidebar
Close sidebar
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
张翔宇
ZLMediaKit
Commits
38c2c465
Commit
38c2c465
authored
Apr 03, 2019
by
xiongziliang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
完善mp4推流
parent
ea4f9a0c
显示空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
92 行增加
和
34 行删除
+92
-34
src/MediaFile/MediaReader.cpp
+13
-7
src/MediaFile/MediaReader.h
+41
-4
tests/test_pusherMp4.cpp
+38
-23
没有找到文件。
src/MediaFile/MediaReader.cpp
查看文件 @
38c2c465
...
@@ -33,10 +33,12 @@ using namespace toolkit;
...
@@ -33,10 +33,12 @@ using namespace toolkit;
namespace
mediakit
{
namespace
mediakit
{
#ifdef ENABLE_MP4V2
#ifdef ENABLE_MP4V2
MediaReader
::
MediaReader
(
const
string
&
strVhost
,
const
string
&
strApp
,
const
string
&
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
);
GET_CONFIG_AND_REGISTER
(
string
,
recordPath
,
Record
::
kFilePath
);
strFileName
=
recordPath
+
"/"
+
strVhost
+
"/"
+
strApp
+
"/"
+
strId
;
auto
strFileName
=
recordPath
+
"/"
+
strVhost
+
"/"
+
strApp
+
"/"
+
strId
;
}
_hMP4File
=
MP4Read
(
strFileName
.
data
());
_hMP4File
=
MP4Read
(
strFileName
.
data
());
if
(
_hMP4File
==
MP4_INVALID_FILE_HANDLE
){
if
(
_hMP4File
==
MP4_INVALID_FILE_HANDLE
){
...
@@ -302,15 +304,19 @@ void MediaReader::seek(uint32_t iSeekTime,bool bReStart){
...
@@ -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
#ifdef ENABLE_MP4V2
GET_CONFIG_AND_REGISTER
(
string
,
appName
,
Record
::
kAppName
);
GET_CONFIG_AND_REGISTER
(
string
,
appName
,
Record
::
kAppName
);
if
(
checkApp
&&
strApp
!=
appName
)
{
if
(
strApp
!=
appName
)
{
return
nullptr
;
return
nullptr
;
}
}
try
{
try
{
MediaReader
::
Ptr
pReader
(
new
MediaReader
(
strVhost
,
strApp
,
strId
));
MediaReader
::
Ptr
pReader
(
new
MediaReader
(
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
)
{
...
...
src/MediaFile/MediaReader.h
查看文件 @
38c2c465
...
@@ -41,19 +41,56 @@ namespace mediakit {
...
@@ -41,19 +41,56 @@ namespace mediakit {
class
MediaReader
:
public
std
::
enable_shared_from_this
<
MediaReader
>
,
public
MediaSourceEvent
{
class
MediaReader
:
public
std
::
enable_shared_from_this
<
MediaReader
>
,
public
MediaSourceEvent
{
public
:
public
:
typedef
std
::
shared_ptr
<
MediaReader
>
Ptr
;
typedef
std
::
shared_ptr
<
MediaReader
>
Ptr
;
MediaReader
(
const
string
&
strVhost
,
const
string
&
strApp
,
const
string
&
strId
);
virtual
~
MediaReader
();
virtual
~
MediaReader
();
static
MediaSource
::
Ptr
onMakeMediaSource
(
const
string
&
strSchema
,
const
string
&
strVhost
,
const
string
&
strApp
,
const
string
&
strId
);
public
:
/**
* 流化一个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
;
bool
seekTo
(
uint32_t
ui32Stamp
)
override
;
/**
* 关闭MediaReader的流化进程,会触发该对象放弃自持有
* @return
*/
bool
close
()
override
;
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
#ifdef ENABLE_MP4V2
private
:
private
:
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
();
void
startReadMP4
();
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
);
...
...
tests/test_pusherMp4.cpp
查看文件 @
38c2c465
...
@@ -43,32 +43,46 @@ using namespace mediakit;
...
@@ -43,32 +43,46 @@ using namespace mediakit;
MediaPusher
::
Ptr
pusher
;
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
)
{
void
createPusher
(
const
string
&
schema
,
auto
src
=
MediaReader
::
onMakeMediaSource
(
schema
,
vhost
,
app
,
stream
);
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
){
if
(
!
src
){
//文件不存在
//文件不存在
WarnL
<<
"MP4
file not exited!"
;
WarnL
<<
"MP4
文件不存在:"
<<
filePath
;
return
;
return
;
}
}
//创建推流器并绑定一个MediaSource
//创建推流器并绑定一个MediaSource
pusher
.
reset
(
new
MediaPusher
(
src
));
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
();
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
)
{
if
(
ex
)
{
WarnL
<<
"Publish fail:"
<<
ex
.
getErrCode
()
<<
" "
<<
ex
.
what
();
WarnL
<<
"Publish fail:"
<<
ex
.
getErrCode
()
<<
" "
<<
ex
.
what
();
//如果发布失败,就重试
//如果发布失败,就重试
rePushDelay
(
schema
,
vhost
,
app
,
stream
,
url
);
rePushDelay
(
schema
,
vhost
,
app
,
stream
,
filePath
,
url
);
}
else
{
}
else
{
InfoL
<<
"Publish success,Please play with player:"
<<
url
;
InfoL
<<
"Publish success,Please play with player:"
<<
url
;
}
}
...
@@ -78,11 +92,16 @@ void createPusher(const string &schema,const string &vhost,const string &app, co
...
@@ -78,11 +92,16 @@ void createPusher(const string &schema,const string &vhost,const string &app, co
Timer
::
Ptr
g_timer
;
Timer
::
Ptr
g_timer
;
//推流失败或断开延迟2秒后重试推流
//推流失败或断开延迟2秒后重试推流
void
rePushDelay
(
const
string
&
schema
,
const
string
&
vhost
,
const
string
&
app
,
const
string
&
stream
,
const
string
&
url
)
{
void
rePushDelay
(
const
string
&
schema
,
g_timer
=
std
::
make_shared
<
Timer
>
(
2
,[
schema
,
vhost
,
app
,
stream
,
url
]()
{
const
string
&
vhost
,
const
string
&
app
,
const
string
&
stream
,
const
string
&
filePath
,
const
string
&
url
)
{
g_timer
=
std
::
make_shared
<
Timer
>
(
2
,[
schema
,
vhost
,
app
,
stream
,
filePath
,
url
]()
{
InfoL
<<
"Re-Publishing..."
;
InfoL
<<
"Re-Publishing..."
;
//重新推流
//重新推流
createPusher
(
schema
,
vhost
,
app
,
stream
,
url
);
createPusher
(
schema
,
vhost
,
app
,
stream
,
filePath
,
url
);
//此任务不重复
//此任务不重复
return
false
;
return
false
;
},
nullptr
);
},
nullptr
);
...
@@ -90,19 +109,15 @@ void rePushDelay(const string &schema,const string &vhost,const string &app, con
...
@@ -90,19 +109,15 @@ void rePushDelay(const string &schema,const string &vhost,const string &app, con
//这里才是真正执行main函数,你可以把函数名(domain)改成main,然后就可以输入自定义url了
//这里才是真正执行main函数,你可以把函数名(domain)改成main,然后就可以输入自定义url了
int
domain
(
const
string
&
filePath
,
const
string
&
pushUrl
){
int
domain
(
const
string
&
filePath
,
const
string
&
pushUrl
){
//设置退出信号处理函数
static
semaphore
sem
;
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
>
());
//vhost/app/stream可以随便自己填,现在不限制app应用名了
createPusher
(
FindField
(
pushUrl
.
data
(),
nullptr
,
"://"
),
DEFAULT_VHOST
,
"live"
,
"stream"
,
filePath
,
pushUrl
);
//录像应用名称默认为record
//设置退出信号处理函数
string
appName
=
mINI
::
Instance
()[
Record
::
kAppName
];
static
semaphore
sem
;
//app必须record,filePath(流id)为相对于httpRoot/record的路径,否则MediaReader会找到不该文件
signal
(
SIGINT
,
[](
int
)
{
sem
.
post
();
});
// 设置退出信号
//限制app为record是为了防止服务器上的文件被肆意访问
createPusher
(
FindField
(
pushUrl
.
data
(),
nullptr
,
"://"
),
DEFAULT_VHOST
,
appName
,
filePath
,
pushUrl
);
sem
.
wait
();
sem
.
wait
();
pusher
.
reset
();
pusher
.
reset
();
g_timer
.
reset
();
g_timer
.
reset
();
...
@@ -112,9 +127,9 @@ int domain(const string & filePath,const string & pushUrl){
...
@@ -112,9 +127,9 @@ int domain(const string & filePath,const string & pushUrl){
int
main
(
int
argc
,
char
*
argv
[]){
int
main
(
int
argc
,
char
*
argv
[]){
//MP4文件需要放置在 httpRoot/record目录下,文件负载必须为h264+aac
//可以使用test_server生成的mp4文件
//可以使用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"
);
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论