mirror of https://github.com/EasyDarwin/EasyDarwin
修改读写buffer大小
change read/write buffer size 完善apidoc complete apidoc 更改录像文件格式,由mp4改为hls格式 change the recorded file format from mp4 to hlspull/132/head
parent
02ee38a14c
commit
4a830c8f9f
|
@ -5,8 +5,9 @@ default_password=admin
|
|||
|
||||
[rtsp]
|
||||
port=554
|
||||
timeout=28800
|
||||
gop_cache_enable=1
|
||||
save_stream_to_mp4=0
|
||||
ffmpeg_path=/Users/jiaozebo/Downloads/ffmpeg-20180719-9cb3d8f-macos64-shared/bin/ffmpeg
|
||||
mp4_dir_path=/Users/jiaozebo/Downloads/EasyDarwinGoMP4
|
||||
timeout=28800; rtsp 超时时间,包括RTSP建立连接与数据收发。
|
||||
gop_cache_enable=1; 是否使能gop cache。如果使能,服务器会缓存最后一个I帧以及其后的非I帧,以提高播放速度。但是可能在高并发的情况下带来内存压力。
|
||||
save_stream_to_local=1; 是否使能推送的同事进行本地存储,使能后则可以进行录像查询与回放。
|
||||
ffmpeg_path=~/Downloads/ffmpeg-20180719-9cb3d8f-macos64-shared/bin/ffmpeg;easydarwin使用ffmpeg工具来进行存储。这里表示ffmpeg的可执行程序的路径
|
||||
m3u8_dir_path=~/Downloads/EasyDarwinGoM3u8;本地存储所将要保存的根目录。如果不存在,程序会尝试创建该目录。
|
||||
ts_duration_second=600;切片文件时长。本地存储时,将以该时间段为标准来生成ts文件,单位秒
|
|
@ -145,7 +145,7 @@ func Init() (err error) {
|
|||
|
||||
{
|
||||
|
||||
mp4Path := utils.Conf().Section("rtsp").Key("mp4_dir_path").MustString("")
|
||||
mp4Path := utils.Conf().Section("rtsp").Key("m3u8_dir_path").MustString("")
|
||||
if len(mp4Path) != 0 {
|
||||
Router.Use(static.Serve("/record", static.LocalFile(mp4Path, true)))
|
||||
}
|
||||
|
|
|
@ -19,6 +19,20 @@ import (
|
|||
"github.com/penggy/EasyGoLib/utils"
|
||||
)
|
||||
|
||||
/**
|
||||
* @apiDefine pull 拉流转推
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {get} /api/vi/stream/start 启动拉流
|
||||
* @apiGroup pull
|
||||
* @apiName StreamStart
|
||||
* @apiParam {String} url RTSP源地址
|
||||
* @apiParam {String} [customPath] 转推时的推送PATH
|
||||
* @apiParam {Number} [IdleTimeout] 拉流时的超时时间
|
||||
* @apiParam {Number} [HeartbeatInterval] 拉流时的心跳间隔,毫秒为单位。如果心跳间隔不为0,那拉流时会向源地址以该间隔发送OPTION请求用来心跳保活
|
||||
* @apiSuccess (200) {String} ID 拉流的ID。后续可以通过该ID来停止拉流
|
||||
*/
|
||||
func (h *APIHandler) StreamStart(c *gin.Context) {
|
||||
type Form struct {
|
||||
URL string `form:"url" binding:"required"`
|
||||
|
@ -58,6 +72,13 @@ func (h *APIHandler) StreamStart(c *gin.Context) {
|
|||
c.IndentedJSON(200, pusher.ID())
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /api/vi/stream/stop 停止拉流
|
||||
* @apiGroup pull
|
||||
* @apiName StreamStop
|
||||
* @apiParam {String} id 拉流的ID
|
||||
* @apiUse simpleSuccess
|
||||
*/
|
||||
func (h *APIHandler) StreamStop(c *gin.Context) {
|
||||
type Form struct {
|
||||
ID string `form:"id" binding:"required"`
|
||||
|
@ -80,8 +101,33 @@ func (h *APIHandler) StreamStop(c *gin.Context) {
|
|||
c.AbortWithStatusJSON(http.StatusBadRequest, fmt.Sprintf("Pusher[%s] not found", form.ID))
|
||||
}
|
||||
|
||||
/**
|
||||
* @apiDefine record 录像 Record
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine fileInfo
|
||||
* @apiSuccess (200) {String} duration 格式化好的录像时长
|
||||
* @apiSuccess (200) {Number} durationMillis 录像时长,毫秒为单位
|
||||
* @apiSuccess (200) {String} path 录像文件的相对路径,其绝对路径为:http[s]://host:port/record/[path]。
|
||||
* @apiSuccess (200) {String} folder 录像文件夹,录像文件夹以推流路径命名。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @api {get} /api/vi/record/folders 获取所有录像文件夹
|
||||
* @apiGroup record
|
||||
* @apiName RecordFolders
|
||||
* @apiParam {Number} [start] 分页开始,从零开始
|
||||
* @apiParam {Number} [limit] 分页大小
|
||||
* @apiParam {String} [sort] 排序字段
|
||||
* @apiParam {String=ascending,descending} [order] 排序顺序
|
||||
* @apiParam {String} [q] 查询参数
|
||||
* @apiSuccess (200) {Number} total 总数
|
||||
* @apiSuccess (200) {Array} rows 文件夹列表
|
||||
* @apiSuccess (200) {String} rows.folder 录像文件夹名称
|
||||
*/
|
||||
func (h *APIHandler) RecordFolders(c *gin.Context) {
|
||||
mp4Path := utils.Conf().Section("rtsp").Key("mp4_dir_path").MustString("")
|
||||
mp4Path := utils.Conf().Section("rtsp").Key("m3u8_dir_path").MustString("")
|
||||
form := utils.NewPageForm()
|
||||
if err := c.Bind(form); err != nil {
|
||||
log.Printf("record folder bind err:%v", err)
|
||||
|
@ -118,6 +164,22 @@ func (h *APIHandler) RecordFolders(c *gin.Context) {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /api/vi/record/files 获取所有录像文件
|
||||
* @apiGroup record
|
||||
* @apiName RecordFiles
|
||||
* @apiParam {Number} folder 录像文件所在的文件夹
|
||||
* @apiParam {Number} [start] 分页开始,从零开始
|
||||
* @apiParam {Number} [limit] 分页大小
|
||||
* @apiParam {String} [sort] 排序字段
|
||||
* @apiParam {String=ascending,descending} [order] 排序顺序
|
||||
* @apiParam {String} [q] 查询参数
|
||||
* @apiSuccess (200) {Number} total 总数
|
||||
* @apiSuccess (200) {Array} rows 文件列表
|
||||
* @apiSuccess (200) {String} rows.duration 格式化好的录像时长
|
||||
* @apiSuccess (200) {Number} rows.durationMillis 录像时长,毫秒为单位
|
||||
* @apiSuccess (200) {String} rows.path 录像文件的相对路径,录像文件为m3u8格式,将其放到video标签中便可直接播放。其绝对路径为:http[s]://host:port/record/[path]。
|
||||
*/
|
||||
func (h *APIHandler) RecordFiles(c *gin.Context) {
|
||||
type Form struct {
|
||||
utils.PageForm
|
||||
|
@ -134,7 +196,7 @@ func (h *APIHandler) RecordFiles(c *gin.Context) {
|
|||
}
|
||||
|
||||
files := make([]interface{}, 0)
|
||||
mp4Path := utils.Conf().Section("rtsp").Key("mp4_dir_path").MustString("")
|
||||
mp4Path := utils.Conf().Section("rtsp").Key("m3u8_dir_path").MustString("")
|
||||
if mp4Path != "" {
|
||||
ffmpeg_path := utils.Conf().Section("rtsp").Key("ffmpeg_path").MustString("")
|
||||
ffmpeg_folder, executable := filepath.Split(ffmpeg_path)
|
||||
|
@ -162,6 +224,9 @@ func (h *APIHandler) RecordFiles(c *gin.Context) {
|
|||
if info.Name() == ".DS_Store" {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(info.Name(), ".m3u8") {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
cmd := exec.Command(ffprobe, "-i", path)
|
||||
cmdOutput := &bytes.Buffer{}
|
||||
//cmd.Stdout = cmdOutput
|
||||
|
@ -185,8 +250,9 @@ func (h *APIHandler) RecordFiles(c *gin.Context) {
|
|||
millis, _ := strconv.Atoi(result[5])
|
||||
duration += time.Duration(millis) * time.Millisecond
|
||||
}
|
||||
|
||||
*files = append(*files, map[string]interface{}{
|
||||
"name": info.Name(),
|
||||
"path": path[len(mp4Path):],
|
||||
"durationMillis": duration / time.Millisecond,
|
||||
"duration": durationStr})
|
||||
return nil
|
||||
|
|
|
@ -119,7 +119,7 @@ func (client *RTSPClient) Start(timeout time.Duration) error {
|
|||
}
|
||||
client.Conn = conn
|
||||
|
||||
networkBuffer := utils.Conf().Section("rtsp").Key("network_buffer").MustInt(1048576)
|
||||
networkBuffer := utils.Conf().Section("rtsp").Key("network_buffer").MustInt(204800)
|
||||
|
||||
timeoutConn := RichConn{
|
||||
conn,
|
||||
|
|
|
@ -2,16 +2,16 @@ package rtsp
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/penggy/EasyGoLib/utils"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/penggy/EasyGoLib/utils"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
@ -49,14 +49,15 @@ func (server *Server) Start() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
localRecord := utils.Conf().Section("rtsp").Key("save_stream_to_mp4").MustInt(0)
|
||||
localRecord := utils.Conf().Section("rtsp").Key("save_stream_to_local").MustInt(0)
|
||||
ffmpeg := utils.Conf().Section("rtsp").Key("ffmpeg_path").MustString("")
|
||||
mp4Path := utils.Conf().Section("rtsp").Key("mp4_dir_path").MustString("")
|
||||
m3u8_dir_path := utils.Conf().Section("rtsp").Key("m3u8_dir_path").MustString("")
|
||||
ts_duration_second := utils.Conf().Section("rtsp").Key("ts_duration_second").MustInt(10 * 60)
|
||||
SaveStreamToLocal := false
|
||||
if (len(ffmpeg) > 0) && localRecord > 0 && len(mp4Path) > 0 {
|
||||
err := utils.EnsureDir(mp4Path)
|
||||
if (len(ffmpeg) > 0) && localRecord > 0 && len(m3u8_dir_path) > 0 {
|
||||
err := utils.EnsureDir(m3u8_dir_path)
|
||||
if err != nil {
|
||||
log.Printf("Create mp4_dir_path[%s] err:%v.", mp4Path, err)
|
||||
log.Printf("Create m3u8_dir_path[%s] err:%v.", m3u8_dir_path, err)
|
||||
} else {
|
||||
SaveStreamToLocal = true
|
||||
}
|
||||
|
@ -75,14 +76,16 @@ func (server *Server) Start() (err error) {
|
|||
case pusher, addChnOk = <-server.addPusherCh:
|
||||
if SaveStreamToLocal {
|
||||
if addChnOk {
|
||||
dir := path.Join(mp4Path, pusher.Path())
|
||||
dir := path.Join(m3u8_dir_path, pusher.Path(), time.Now().Format("20060102150405"))
|
||||
err := utils.EnsureDir(dir)
|
||||
if err != nil {
|
||||
log.Printf("EnsureDir:[%s] err:%v.", dir, err)
|
||||
continue
|
||||
}
|
||||
path := path.Join(dir, fmt.Sprintf("%s.mp4", time.Now().Format("20060102150405")))
|
||||
cmd := exec.Command(ffmpeg, "-i", pusher.URL(), "-c:v", "copy", "-c:a", "copy", path)
|
||||
path := path.Join(dir, fmt.Sprintf("out.m3u8"))
|
||||
// ffmpeg -i ~/Downloads/720p.mp4 -s 640x360 -g 15 -c:a aac -hls_time 5 -hls_list_size 0 record.m3u8
|
||||
|
||||
cmd := exec.Command(ffmpeg, "-fflags", "genpts", "-rtsp_transport", "tcp", "-i", pusher.URL(), "-c:v", "copy", "-hls_time", strconv.Itoa(ts_duration_second), "-hls_list_size", "0", path)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Start()
|
||||
|
@ -90,7 +93,7 @@ func (server *Server) Start() (err error) {
|
|||
log.Printf("Start ffmpeg err:%v", err)
|
||||
}
|
||||
pusher2ffmpegMap[pusher] = cmd
|
||||
log.Printf("add ffmpeg to pull stream from pusher[%v]", pusher)
|
||||
log.Printf("add ffmpeg [%v] to pull stream from pusher[%v]", cmd, pusher)
|
||||
} else {
|
||||
log.Printf("addPusherChan closed")
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ func (session *Session) String() string {
|
|||
}
|
||||
|
||||
func NewSession(server *Server, conn net.Conn) *Session {
|
||||
networkBuffer := utils.Conf().Section("rtsp").Key("network_buffer").MustInt(1048576)
|
||||
networkBuffer := utils.Conf().Section("rtsp").Key("network_buffer").MustInt(204800)
|
||||
timeoutMillis := utils.Conf().Section("rtsp").Key("timeout").MustInt(0)
|
||||
timeoutTCPConn := &RichConn{conn, time.Duration(timeoutMillis) * time.Millisecond}
|
||||
|
||||
|
|
Loading…
Reference in New Issue