Browse Source

修改读写buffer大小

change read/write buffer size
完善apidoc
complete apidoc
更改录像文件格式,由mp4改为hls格式
change the recorded file format from mp4 to hls
pull/132/head
macbookpro 6 years ago
parent
commit
4a830c8f9f
  1. 11
      easydarwin.ini
  2. 2
      routers/routers.go
  3. 72
      routers/streams.go
  4. 2
      rtsp/rtsp-client.go
  5. 25
      rtsp/rtsp-server.go
  6. 2
      rtsp/rtsp-session.go

11
easydarwin.ini

@ -5,8 +5,9 @@ default_password=admin
[rtsp] [rtsp]
port=554 port=554
timeout=28800 timeout=28800; rtsp 超时时间,包括RTSP建立连接与数据收发。
gop_cache_enable=1 gop_cache_enable=1; 是否使能gop cache。如果使能,服务器会缓存最后一个I帧以及其后的非I帧,以提高播放速度。但是可能在高并发的情况下带来内存压力。
save_stream_to_mp4=0 save_stream_to_local=1; 是否使能推送的同事进行本地存储,使能后则可以进行录像查询与回放。
ffmpeg_path=/Users/jiaozebo/Downloads/ffmpeg-20180719-9cb3d8f-macos64-shared/bin/ffmpeg ffmpeg_path=~/Downloads/ffmpeg-20180719-9cb3d8f-macos64-shared/bin/ffmpeg;easydarwin使用ffmpeg工具来进行存储。这里表示ffmpeg的可执行程序的路径
mp4_dir_path=/Users/jiaozebo/Downloads/EasyDarwinGoMP4 m3u8_dir_path=~/Downloads/EasyDarwinGoM3u8;本地存储所将要保存的根目录。如果不存在,程序会尝试创建该目录。
ts_duration_second=600;切片文件时长。本地存储时,将以该时间段为标准来生成ts文件,单位秒

2
routers/routers.go

@ -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 { if len(mp4Path) != 0 {
Router.Use(static.Serve("/record", static.LocalFile(mp4Path, true))) Router.Use(static.Serve("/record", static.LocalFile(mp4Path, true)))
} }

72
routers/streams.go

@ -19,6 +19,20 @@ import (
"github.com/penggy/EasyGoLib/utils" "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) { func (h *APIHandler) StreamStart(c *gin.Context) {
type Form struct { type Form struct {
URL string `form:"url" binding:"required"` URL string `form:"url" binding:"required"`
@ -58,6 +72,13 @@ func (h *APIHandler) StreamStart(c *gin.Context) {
c.IndentedJSON(200, pusher.ID()) 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) { func (h *APIHandler) StreamStop(c *gin.Context) {
type Form struct { type Form struct {
ID string `form:"id" binding:"required"` 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)) 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) { 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() form := utils.NewPageForm()
if err := c.Bind(form); err != nil { if err := c.Bind(form); err != nil {
log.Printf("record folder bind err:%v", err) 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) { func (h *APIHandler) RecordFiles(c *gin.Context) {
type Form struct { type Form struct {
utils.PageForm utils.PageForm
@ -134,7 +196,7 @@ func (h *APIHandler) RecordFiles(c *gin.Context) {
} }
files := make([]interface{}, 0) 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 != "" { if mp4Path != "" {
ffmpeg_path := utils.Conf().Section("rtsp").Key("ffmpeg_path").MustString("") ffmpeg_path := utils.Conf().Section("rtsp").Key("ffmpeg_path").MustString("")
ffmpeg_folder, executable := filepath.Split(ffmpeg_path) ffmpeg_folder, executable := filepath.Split(ffmpeg_path)
@ -162,6 +224,9 @@ func (h *APIHandler) RecordFiles(c *gin.Context) {
if info.Name() == ".DS_Store" { if info.Name() == ".DS_Store" {
return nil return nil
} }
if !strings.HasSuffix(info.Name(), ".m3u8") {
return filepath.SkipDir
}
cmd := exec.Command(ffprobe, "-i", path) cmd := exec.Command(ffprobe, "-i", path)
cmdOutput := &bytes.Buffer{} cmdOutput := &bytes.Buffer{}
//cmd.Stdout = cmdOutput //cmd.Stdout = cmdOutput
@ -185,8 +250,9 @@ func (h *APIHandler) RecordFiles(c *gin.Context) {
millis, _ := strconv.Atoi(result[5]) millis, _ := strconv.Atoi(result[5])
duration += time.Duration(millis) * time.Millisecond duration += time.Duration(millis) * time.Millisecond
} }
*files = append(*files, map[string]interface{}{ *files = append(*files, map[string]interface{}{
"name": info.Name(), "path": path[len(mp4Path):],
"durationMillis": duration / time.Millisecond, "durationMillis": duration / time.Millisecond,
"duration": durationStr}) "duration": durationStr})
return nil return nil

2
rtsp/rtsp-client.go

@ -119,7 +119,7 @@ func (client *RTSPClient) Start(timeout time.Duration) error {
} }
client.Conn = conn 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{ timeoutConn := RichConn{
conn, conn,

25
rtsp/rtsp-server.go

@ -2,16 +2,16 @@ package rtsp
import ( import (
"fmt" "fmt"
"github.com/penggy/EasyGoLib/utils"
"log" "log"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"strconv"
"sync" "sync"
"syscall" "syscall"
"time" "time"
"github.com/penggy/EasyGoLib/utils"
) )
type Server struct { type Server struct {
@ -49,14 +49,15 @@ func (server *Server) Start() (err error) {
return 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("") 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 SaveStreamToLocal := false
if (len(ffmpeg) > 0) && localRecord > 0 && len(mp4Path) > 0 { if (len(ffmpeg) > 0) && localRecord > 0 && len(m3u8_dir_path) > 0 {
err := utils.EnsureDir(mp4Path) err := utils.EnsureDir(m3u8_dir_path)
if err != nil { 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 { } else {
SaveStreamToLocal = true SaveStreamToLocal = true
} }
@ -75,14 +76,16 @@ func (server *Server) Start() (err error) {
case pusher, addChnOk = <-server.addPusherCh: case pusher, addChnOk = <-server.addPusherCh:
if SaveStreamToLocal { if SaveStreamToLocal {
if addChnOk { if addChnOk {
dir := path.Join(mp4Path, pusher.Path()) dir := path.Join(m3u8_dir_path, pusher.Path(), time.Now().Format("20060102150405"))
err := utils.EnsureDir(dir) err := utils.EnsureDir(dir)
if err != nil { if err != nil {
log.Printf("EnsureDir:[%s] err:%v.", dir, err) log.Printf("EnsureDir:[%s] err:%v.", dir, err)
continue continue
} }
path := path.Join(dir, fmt.Sprintf("%s.mp4", time.Now().Format("20060102150405"))) path := path.Join(dir, fmt.Sprintf("out.m3u8"))
cmd := exec.Command(ffmpeg, "-i", pusher.URL(), "-c:v", "copy", "-c:a", "copy", path) // 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.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err = cmd.Start() err = cmd.Start()
@ -90,7 +93,7 @@ func (server *Server) Start() (err error) {
log.Printf("Start ffmpeg err:%v", err) log.Printf("Start ffmpeg err:%v", err)
} }
pusher2ffmpegMap[pusher] = cmd 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 { } else {
log.Printf("addPusherChan closed") log.Printf("addPusherChan closed")
} }

2
rtsp/rtsp-session.go

@ -128,7 +128,7 @@ func (session *Session) String() string {
} }
func NewSession(server *Server, conn net.Conn) *Session { 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) timeoutMillis := utils.Conf().Section("rtsp").Key("timeout").MustInt(0)
timeoutTCPConn := &RichConn{conn, time.Duration(timeoutMillis) * time.Millisecond} timeoutTCPConn := &RichConn{conn, time.Duration(timeoutMillis) * time.Millisecond}

Loading…
Cancel
Save