From ffe803579b0bd3645d7f911d8aa0da777c64363d Mon Sep 17 00:00:00 2001 From: qydysky <32743305+qydysky@users.noreply.github.com> Date: Wed, 25 Jan 2023 00:39:00 +0800 Subject: [PATCH] =?utf8?q?Add=20=E6=94=AF=E6=8C=81hevc=E6=A0=BC=E5=BC=8F?= =?utf8?q?=E4=BF=9D=E5=AD=98=20Impove=20=E7=A7=BB=E9=99=A4=E7=9B=B4?= =?utf8?q?=E6=92=AD=E6=B5=81=E7=B1=BB=E5=9E=8B=E6=95=85=E9=9A=9C=E5=88=87?= =?utf8?q?=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- CV/Var.go | 104 ++++++--- F/api.go | 412 +++++++++++------------------------- Json/NEPTUNE_IS_MY_WAIFU.go | 21 +- Json/getRoomPlayInfo.go | 21 +- Json/streamType.go | 22 ++ Reply/F.go | 2 +- Reply/flvDecode.go | 4 + Reply/fmp4Decode_test.go | 4 +- Reply/stream.go | 29 +-- demo/config/config_K_v.json | 3 +- 10 files changed, 243 insertions(+), 379 deletions(-) create mode 100644 Json/streamType.go diff --git a/CV/Var.go b/CV/Var.go index 3c6649f..0032dd7 100644 --- a/CV/Var.go +++ b/CV/Var.go @@ -19,38 +19,40 @@ import ( ) type Common struct { - Uid int //client uid - Live []LiveQn //直播流链接 - Live_qn int //当前直播流质量 - Live_want_qn int //期望直播流质量 - Roomid int //房间ID - Cookie syncmap.Map //Cookie - Title string //直播标题 - Uname string //主播名 - UpUid int //主播uid - Rev float64 //营收 - Renqi int //人气 - Watched int //观看人数 - OnlineNum int //在线人数 - GuardNum int //舰长数 - ParentAreaID int //父分区 - AreaID int //子分区 - Locked bool //直播间封禁 - Note string //分区排行 - Live_Start_Time time.Time //直播开始时间 - Liveing bool //是否在直播 - Wearing_FansMedal int //当前佩戴的粉丝牌 - Token string //弹幕钥 - WSURL []string //弹幕链接 - LIVE_BUVID bool //cookies含LIVE_BUVID - Stream_url []string //直播Web服务 - Proxy string //全局代理 - AcceptQn map[int]string //允许的直播流质量 - Qn map[int]string //全部直播流质量 - K_v syncmap.Map //配置文件 - Log *log.Log_interface //日志 - Danmu_Main_mq *mq.Msgq //消息 - ReqPool *idpool.Idpool //请求池 + Uid int //client uid + Live []LiveQn //直播流链接 + Live_qn int //当前直播流质量 + Live_want_qn int //期望直播流质量 + Roomid int //房间ID + Cookie syncmap.Map //Cookie + Title string //直播标题 + Uname string //主播名 + UpUid int //主播uid + Rev float64 //营收 + Renqi int //人气 + Watched int //观看人数 + OnlineNum int //在线人数 + GuardNum int //舰长数 + ParentAreaID int //父分区 + AreaID int //子分区 + Locked bool //直播间封禁 + Note string //分区排行 + Live_Start_Time time.Time //直播开始时间 + Liveing bool //是否在直播 + Wearing_FansMedal int //当前佩戴的粉丝牌 + Token string //弹幕钥 + WSURL []string //弹幕链接 + LIVE_BUVID bool //cookies含LIVE_BUVID + Stream_url []string //直播Web服务 + Proxy string //全局代理 + AcceptQn map[int]string //允许的直播流质量 + Qn map[int]string //全部直播流质量 + StreamType StreamType //当前直播流类型 + AllStreamType map[string]StreamType //直播流类型 + K_v syncmap.Map //配置文件 + Log *log.Log_interface //日志 + Danmu_Main_mq *mq.Msgq //消息 + ReqPool *idpool.Idpool //请求池 } type LiveQn struct { @@ -59,7 +61,36 @@ type LiveQn struct { Expires int //流到期时间 } +type StreamType struct { + Protocol_name string + Format_name string + Codec_name string +} + func (t *Common) Init() Common { + t.AllStreamType = map[string]StreamType{ + `fmp4`: { + Protocol_name: "http_hls", + Format_name: "fmp4", + Codec_name: "avc", + }, + `flv`: { + Protocol_name: "http_stream", + Format_name: "flv", + Codec_name: "avc", + }, + `fmp4H`: { + Protocol_name: "http_hls", + Format_name: "fmp4", + Codec_name: "hevc", + }, + `flvH`: { + Protocol_name: "http_stream", + Format_name: "flv", + Codec_name: "hevc", + }, + } + t.Qn = map[int]string{ // no change 20000: "4K", 10000: "原画", @@ -116,6 +147,15 @@ func (t *Common) Init() Common { t.Proxy = val.(string) } + // 配置直播流类型 + if val, exist := t.K_v.Load("直播流类型"); exist { + if st, ok := t.AllStreamType[val.(string)]; ok { + t.StreamType = st + } else { + panic("未找到设定类型" + val.(string)) + } + } + { v, _ := t.K_v.LoadV("日志文件输出").(string) t.Log = log.New(log.Config{ diff --git a/F/api.go b/F/api.go index b9f711a..5b7257c 100644 --- a/F/api.go +++ b/F/api.go @@ -303,85 +303,7 @@ func (c *GetFunc) Html() (missKey []string) { } //当前直播流 - { - type Stream_name struct { - Protocol_name string - Format_name string - Codec_name string - } - var name_map = map[string]Stream_name{ - `flv`: { - Protocol_name: "http_stream", - Format_name: "flv", - Codec_name: "avc", - }, - `hls`: { - Protocol_name: "http_hls", - Format_name: "fmp4", - Codec_name: "avc", - }, - } - - want_type := name_map[`hls`] - if v, ok := c.K_v.LoadV(`直播流类型`).(string); ok { - if v, ok := name_map[v]; ok { - want_type = v - } else { - apilog.L(`I: `, `未找到`, v, `,默认hls`) - } - } else { - apilog.L(`T: `, `默认flv`) - } - - for _, v := range j.RoomInitRes.Data.PlayurlInfo.Playurl.Stream { - if v.ProtocolName != want_type.Protocol_name { - continue - } - - for _, v := range v.Format { - if v.FormatName != want_type.Format_name { - continue - } - - for _, v := range v.Codec { - if v.CodecName != want_type.Codec_name { - continue - } - - //当前直播流质量 - c.Live_qn = v.CurrentQn - if c.Live_want_qn == 0 { - c.Live_want_qn = v.CurrentQn - } - //允许的清晰度 - { - var tmp = make(map[int]string) - for _, v := range v.AcceptQn { - if s, ok := c.Qn[v]; ok { - tmp[v] = s - } - } - c.AcceptQn = tmp - } - //直播流链接 - c.Live = []cv.LiveQn{} - for _, v1 := range v.URLInfo { - item := cv.LiveQn{ - Url: v1.Host + v.BaseURL + v1.Extra, - } - - if query, e := url.ParseQuery(v1.Extra); e == nil { - if expires, e := strconv.Atoi(query.Get("expires")); e == nil { - item.Expires = expires - } - } - - c.Live = append(c.Live, item) - } - } - } - } - } + c.configStreamType(j.RoomInitRes.Data.PlayurlInfo.Playurl.Stream) } //Roominfores @@ -415,6 +337,127 @@ func (c *GetFunc) Html() (missKey []string) { return } +// 配置直播流 +func (c *GetFunc) configStreamType(sts []J.StreamType) { + defer apilog.Base_add(`configStreamType`).L(`T: `, fmt.Sprintf("使用直播流 %s %s %s", c.Qn[c.Live_qn], c.StreamType.Format_name, c.StreamType.Codec_name)) + + if v, ok := c.Common.K_v.LoadV(`直播流类型`).(string); ok { + if st, ok := c.AllStreamType[v]; ok { + c.StreamType = st + } + } + + // 查找配置类型是否存在 + for _, v := range sts { + if v.ProtocolName != c.StreamType.Protocol_name { + continue + } + + for _, v := range v.Format { + if v.FormatName != c.StreamType.Format_name { + continue + } + + for _, v := range v.Codec { + if v.CodecName != c.StreamType.Codec_name { + continue + } + + //当前直播流质量 + c.Live_qn = v.CurrentQn + if c.Live_want_qn == 0 { + c.Live_want_qn = v.CurrentQn + } + //允许的清晰度 + { + var tmp = make(map[int]string) + for _, v := range v.AcceptQn { + if s, ok := c.Qn[v]; ok { + tmp[v] = s + } + } + c.AcceptQn = tmp + } + //直播流链接 + c.Live = []cv.LiveQn{} + for _, v1 := range v.URLInfo { + item := cv.LiveQn{ + Url: v1.Host + v.BaseURL + v1.Extra, + } + + if query, e := url.ParseQuery(v1.Extra); e == nil { + if expires, e := strconv.Atoi(query.Get("expires")); e == nil { + item.Expires = expires + } + } + + c.Live = append(c.Live, item) + } + + return + } + } + } + + apilog.Base_add(`configStreamType`).L(`W: `, "未找到配置的直播流类型,使用默认flv、fmp4") + + // 默认使用flv、fmp4 + for _, streamType := range []cv.StreamType{ + c.AllStreamType[`flv`], + c.AllStreamType[`fmp4`], + } { + + for _, v := range sts { + if v.ProtocolName != streamType.Protocol_name { + continue + } + + for _, v := range v.Format { + if v.FormatName != streamType.Format_name { + continue + } + + for _, v := range v.Codec { + if v.CodecName != streamType.Codec_name { + continue + } + + //当前直播流质量 + c.Live_qn = v.CurrentQn + if c.Live_want_qn == 0 { + c.Live_want_qn = v.CurrentQn + } + //允许的清晰度 + { + var tmp = make(map[int]string) + for _, v := range v.AcceptQn { + if s, ok := c.Qn[v]; ok { + tmp[v] = s + } + } + c.AcceptQn = tmp + } + //直播流链接 + c.Live = []cv.LiveQn{} + for _, v1 := range v.URLInfo { + item := cv.LiveQn{ + Url: v1.Host + v.BaseURL + v1.Extra, + } + + if query, e := url.ParseQuery(v1.Extra); e == nil { + if expires, e := strconv.Atoi(query.Get("expires")); e == nil { + item.Expires = expires + } + } + + c.Live = append(c.Live, item) + } + } + } + } + } +} + func (c *GetFunc) missRoomId() (missKey []string) { apilog.Base_add(`missRoomId`).L(`E: `, `missRoomId`) return @@ -567,114 +610,7 @@ func (c *GetFunc) getRoomPlayInfo() (missKey []string) { } //当前直播流 - { - type Stream_name struct { - Protocol_name string - Format_name string - Codec_name string - } - - //所有支持的格式 - var name_map = map[string]Stream_name{ - `flv`: { - Protocol_name: "http_stream", - Format_name: "flv", - Codec_name: "avc", - }, - `hls`: { - Protocol_name: "http_hls", - Format_name: "fmp4", - Codec_name: "avc", - }, - } - - // 默认使用hls - want_type := name_map[`hls`] - - //从配置文件选取格式 - if v, ok := c.K_v.LoadV(`直播流类型`).(string); ok { - if v, ok := name_map[v]; ok { - want_type = v - } else { - apilog.L(`I: `, `未找到`, v, `,默认hls`) - } - } else { - apilog.L(`T: `, `默认hls`) - } - - no_found_type := true - for { - //返回的所有支持的格式 - for _, v := range j.Data.PlayurlInfo.Playurl.Stream { - //选取配置中的格式 - if v.ProtocolName != want_type.Protocol_name { - continue - } - - for _, v := range v.Format { - //选取配置中的格式 - if v.FormatName != want_type.Format_name { - continue - } - - no_found_type = false - - for _, v := range v.Codec { - //选取配置中的格式 - if v.CodecName != want_type.Codec_name { - continue - } - - //当前直播流质量 - c.Live_qn = v.CurrentQn - if c.Live_want_qn == 0 { - c.Live_want_qn = v.CurrentQn - } - //允许的清晰度 - { - var tmp = make(map[int]string) - for _, v := range v.AcceptQn { - if s, ok := c.Qn[v]; ok { - tmp[v] = s - } - } - c.AcceptQn = tmp - } - //直播流链接 - c.Live = []cv.LiveQn{} - for _, v1 := range v.URLInfo { - item := cv.LiveQn{ - Url: v1.Host + v.BaseURL + v1.Extra, - } - - if query, e := url.ParseQuery(v1.Extra); e == nil { - if expires, e := strconv.Atoi(query.Get("expires")); e == nil { - item.Expires = expires - } - } - - c.Live = append(c.Live, item) - } - - //找到配置格式,跳出 - break - } - } - } - if no_found_type { - if want_type.Protocol_name == "http_stream" { - apilog.L(`I: `, `不支持flv,使用hls`) - want_type = name_map[`hls`] - } else { - apilog.L(`I: `, `不支持hls,使用flv`) - want_type = name_map[`flv`] - } - no_found_type = false - } else { - break - } - } - } + c.configStreamType(j.Data.PlayurlInfo.Playurl.Stream) } return } @@ -768,107 +704,7 @@ func (c *GetFunc) getRoomPlayInfoByQn() (missKey []string) { } //当前直播流 - { - type Stream_name struct { - Protocol_name string - Format_name string - Codec_name string - } - var name_map = map[string]Stream_name{ - `flv`: { - Protocol_name: "http_stream", - Format_name: "flv", - Codec_name: "avc", - }, - `hls`: { - Protocol_name: "http_hls", - Format_name: "fmp4", - Codec_name: "avc", - }, - } - - want_type := name_map[`hls`] - if v, ok := c.K_v.LoadV(`直播流类型`).(string); ok { - if v, ok := name_map[v]; ok { - want_type = v - } else { - apilog.L(`I: `, `未找到`, v, `,默认hls`) - } - } else { - apilog.L(`T: `, `默认hls`) - } - - no_found_type := true - for { - for _, v := range j.Data.PlayurlInfo.Playurl.Stream { - if v.ProtocolName != want_type.Protocol_name { - continue - } - - for _, v := range v.Format { - if v.FormatName != want_type.Format_name { - continue - } - - for _, v := range v.Codec { - if v.CodecName != want_type.Codec_name { - continue - } - - no_found_type = false - - //当前直播流质量 - c.Live_qn = v.CurrentQn - if c.Live_want_qn == 0 { - c.Live_want_qn = v.CurrentQn - } - //允许的清晰度 - { - var tmp = make(map[int]string) - for _, v := range v.AcceptQn { - if s, ok := c.Qn[v]; ok { - tmp[v] = s - } - } - c.AcceptQn = tmp - } - //直播流链接 - c.Live = []cv.LiveQn{} - for _, v1 := range v.URLInfo { - item := cv.LiveQn{ - Url: v1.Host + v.BaseURL + v1.Extra, - } - - if query, e := url.ParseQuery(v1.Extra); e == nil { - if expires, e := strconv.Atoi(query.Get("expires")); e == nil { - item.Expires = expires - } - } - - c.Live = append(c.Live, item) - } - } - } - } - if no_found_type { - if want_type.Protocol_name == "http_stream" { - apilog.L(`I: `, `不支持flv,使用hls`) - want_type = name_map[`hls`] - } else { - apilog.L(`I: `, `不支持hls,使用flv`) - want_type = name_map[`flv`] - } - no_found_type = false - } else { - break - } - } - if s, ok := c.Qn[c.Live_qn]; !ok { - apilog.L(`W: `, `未知清晰度`, c.Live_qn) - } else { - apilog.L(`I: `, s) - } - } + c.configStreamType(j.Data.PlayurlInfo.Playurl.Stream) } return } diff --git a/Json/NEPTUNE_IS_MY_WAIFU.go b/Json/NEPTUNE_IS_MY_WAIFU.go index 8b1520f..944cda7 100644 --- a/Json/NEPTUNE_IS_MY_WAIFU.go +++ b/Json/NEPTUNE_IS_MY_WAIFU.go @@ -30,26 +30,7 @@ type NEPTUNE_IS_MY_WAIFU struct { HdrDesc string `json:"hdr_desc"` AttrDesc interface{} `json:"attr_desc"` } `json:"g_qn_desc"` - Stream []struct { - ProtocolName string `json:"protocol_name"` - Format []struct { - FormatName string `json:"format_name"` - Codec []struct { - CodecName string `json:"codec_name"` - CurrentQn int `json:"current_qn"` - AcceptQn []int `json:"accept_qn"` - BaseURL string `json:"base_url"` - URLInfo []struct { - Host string `json:"host"` - Extra string `json:"extra"` - StreamTTL int `json:"stream_ttl"` - } `json:"url_info"` - HdrQn interface{} `json:"hdr_qn"` - DolbyType int `json:"dolby_type"` - AttrName string `json:"attr_name"` - } `json:"codec"` - } `json:"format"` - } `json:"stream"` + Stream []StreamType `json:"stream"` P2PData struct { P2P bool `json:"p2p"` P2PType int `json:"p2p_type"` diff --git a/Json/getRoomPlayInfo.go b/Json/getRoomPlayInfo.go index 9a03b97..6b52388 100644 --- a/Json/getRoomPlayInfo.go +++ b/Json/getRoomPlayInfo.go @@ -29,26 +29,7 @@ type GetRoomPlayInfo struct { HdrDesc string `json:"hdr_desc"` AttrDesc interface{} `json:"attr_desc"` } `json:"g_qn_desc"` - Stream []struct { - ProtocolName string `json:"protocol_name"` - Format []struct { - FormatName string `json:"format_name"` - Codec []struct { - CodecName string `json:"codec_name"` - CurrentQn int `json:"current_qn"` - AcceptQn []int `json:"accept_qn"` - BaseURL string `json:"base_url"` - URLInfo []struct { - Host string `json:"host"` - Extra string `json:"extra"` - StreamTTL int `json:"stream_ttl"` - } `json:"url_info"` - HdrQn interface{} `json:"hdr_qn"` - DolbyType int `json:"dolby_type"` - AttrName string `json:"attr_name"` - } `json:"codec"` - } `json:"format"` - } `json:"stream"` + Stream []StreamType `json:"stream"` P2PData struct { P2P bool `json:"p2p"` P2PType int `json:"p2p_type"` diff --git a/Json/streamType.go b/Json/streamType.go new file mode 100644 index 0000000..aa32cad --- /dev/null +++ b/Json/streamType.go @@ -0,0 +1,22 @@ +package part + +type StreamType struct { + ProtocolName string `json:"protocol_name"` + Format []struct { + FormatName string `json:"format_name"` + Codec []struct { + CodecName string `json:"codec_name"` + CurrentQn int `json:"current_qn"` + AcceptQn []int `json:"accept_qn"` + BaseURL string `json:"base_url"` + URLInfo []struct { + Host string `json:"host"` + Extra string `json:"extra"` + StreamTTL int `json:"stream_ttl"` + } `json:"url_info"` + HdrQn interface{} `json:"hdr_qn"` + DolbyType int `json:"dolby_type"` + AttrName string `json:"attr_name"` + } `json:"codec"` + } `json:"format"` +} diff --git a/Reply/F.go b/Reply/F.go index 6d1af2b..51cb420 100644 --- a/Reply/F.go +++ b/Reply/F.go @@ -258,7 +258,7 @@ func dtos(t time.Duration) string { return fmt.Sprintf("%d:%02d:%02d.%02d", int(math.Floor(t.Hours())), M, S, Ns) } -// hls +// fmp4 // https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming var streamO = new(sync.Map) diff --git a/Reply/flvDecode.go b/Reply/flvDecode.go index 7ce8788..29feb2e 100644 --- a/Reply/flvDecode.go +++ b/Reply/flvDecode.go @@ -422,6 +422,10 @@ var ( func Seach_stream_tag(buf []byte) (front_buf []byte, keyframe [][]byte, last_avilable_offset int, err error) { //get flv header(9byte) + FirstTagSize(4byte) if header_offset := bytes.Index(buf, flv_header_sign); header_offset != -1 { + if header_offset+flv_header_size+previou_tag_size > len(buf) { + err = errors.New(`no found available tag`) + return + } front_buf = buf[header_offset : header_offset+flv_header_size+previou_tag_size] last_avilable_offset = header_offset + flv_header_size + previou_tag_size } diff --git a/Reply/fmp4Decode_test.go b/Reply/fmp4Decode_test.go index 6b88783..ff2b9d7 100644 --- a/Reply/fmp4Decode_test.go +++ b/Reply/fmp4Decode_test.go @@ -1,13 +1,13 @@ package reply import ( - _ "embed" + // _ "embed" "testing" F "github.com/qydysky/bili_danmu/F" ) -//go:embed 32320131.m4s +// go:embed 32320131.m4s var buf []byte func Test_deal(t *testing.T) { diff --git a/Reply/stream.go b/Reply/stream.go index ddd72f2..a18ca68 100644 --- a/Reply/stream.go +++ b/Reply/stream.go @@ -1101,21 +1101,22 @@ func (t *M4SStream) Start() bool { t.log.L(`E: `, "saveStream:", err) } + // Deprecated: 默认总是获取到可用流 // 直播流类型故障切换 - if v, ok := t.common.K_v.LoadV(`直播流类型故障切换`).(bool); v && ok { - if err != nil && err.Error() == "未能找到可用流服务器" { - if v, ok := t.common.K_v.LoadV(`直播流类型`).(string); ok { - switch v { - case "hls": - t.common.K_v.Store(`直播流类型`, `flv`) - case "flv": - t.common.K_v.Store(`直播流类型`, `hls`) - default: - t.log.L(`E: `, `未知的流类型:`+v) - } - } - } - } + // if v, ok := t.common.K_v.LoadV(`直播流类型故障切换`).(bool); v && ok { + // if err != nil && err.Error() == "未能找到可用流服务器" { + // if v, ok := t.common.K_v.LoadV(`直播流类型`).(string); ok { + // switch v { + // case "fmp4": + // t.common.K_v.Store(`直播流类型`, `flv`) + // case "flv": + // t.common.K_v.Store(`直播流类型`, `hls`) + // default: + // t.log.L(`E: `, `未知的流类型:`+v) + // } + // } + // } + // } } diff --git a/demo/config/config_K_v.json b/demo/config/config_K_v.json index 095841b..21b20df 100644 --- a/demo/config/config_K_v.json +++ b/demo/config/config_K_v.json @@ -54,9 +54,8 @@ "调用obs": false, "直播流清晰度-help": "清晰度可选-1:不保存 0:默认 20000:4K 10000:原画 400:蓝光 250:超清 150:高清 80:流畅,无提供所选清晰度时,使用低一档清晰度", "直播流清晰度": 10000, - "直播流类型-help": "flv or hls", + "直播流类型-help": "flv,fmp4,flvH,fmp4H,带H后缀的为Hevc格式编码", "直播流类型": "flv", - "直播流类型故障切换": true, "直播流保存位置": "./live", "直播hls流故障转移-help":"true:hls服务器故障时,使用其他", "直播hls流故障转移": true, -- 2.39.2