diff --git a/trunk/3rdparty/srs-bench/README.md b/trunk/3rdparty/srs-bench/README.md index 26c636018..7c795a331 100644 --- a/trunk/3rdparty/srs-bench/README.md +++ b/trunk/3rdparty/srs-bench/README.md @@ -215,7 +215,7 @@ make && ./objs/srs_gb28181_test -test.v * `-srs-stream`,GB的user,即流名称,一般会加上随机的后缀。默认值:`3402000000` * `-srs-timeout`,每个Case的超时时间,毫秒。默认值:`11000`,即11秒。 * `-srs-publish-audio`,推流时,使用的音频文件。默认值:`avatar.aac` -* `-srs-publish-video`,推流时,使用的视频文件。默认值:`avatar.h264` +* `-srs-publish-video`,推流时,使用的视频文件,注意:扩展名`.h264`表明编码格式为`AVC`,`.h265`表明编码格式为`HEVC`。默认值:`avatar.h264` * `-srs-publish-video-fps`,推流时,视频文件的FPS。默认值:`25` 其他不常用参数: diff --git a/trunk/3rdparty/srs-bench/avatar.h265 b/trunk/3rdparty/srs-bench/avatar.h265 new file mode 100644 index 000000000..da72cf579 Binary files /dev/null and b/trunk/3rdparty/srs-bench/avatar.h265 differ diff --git a/trunk/3rdparty/srs-bench/gb28181/gb_test.go b/trunk/3rdparty/srs-bench/gb28181/gb_test.go index 1414b7f71..ead8d02cf 100644 --- a/trunk/3rdparty/srs-bench/gb28181/gb_test.go +++ b/trunk/3rdparty/srs-bench/gb28181/gb_test.go @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) 2022 Winlin +// # Copyright (c) 2022 Winlin // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in @@ -47,6 +47,36 @@ func TestGbPublishRegularly(t *testing.T) { return nil } + t.ingester.conf.psConfig.video = "avatar.h264" + if err := t.Run(ctx); err != nil { + return err + } + + return nil + }() + if err := filterTestError(ctx.Err(), err); err != nil { + t.Errorf("err %+v", err) + } +} + +func TestGbPublishRegularlyH265(t *testing.T) { + ctx := logger.WithContext(context.Background()) + ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond) + defer cancel() + + err := func() error { + t := NewGBTestPublisher() + defer t.Close() + + var nnPackets int + t.ingester.onSendPacket = func(pack *PSPackStream) error { + if nnPackets += 1; nnPackets > 10 { + cancel() + } + return nil + } + + t.ingester.conf.psConfig.video = "avatar.h265" if err := t.Run(ctx); err != nil { return err } diff --git a/trunk/3rdparty/srs-bench/gb28181/h265reader.go b/trunk/3rdparty/srs-bench/gb28181/h265reader.go new file mode 100644 index 000000000..9073c5c32 --- /dev/null +++ b/trunk/3rdparty/srs-bench/gb28181/h265reader.go @@ -0,0 +1,226 @@ +// Package h265reader implements a H265 Annex-B Reader +package gb28181 + +import ( + "bytes" + "errors" + "io" +) + +type NalUnitType uint8 + +// Enums for NalUnitTypes +const ( + NaluTypeSliceTrailN NalUnitType = 0 // 0x0 + NaluTypeSliceTrailR NalUnitType = 1 // 0x01 + NaluTypeSliceTsaN NalUnitType = 2 // 0x02 + NaluTypeSliceTsaR NalUnitType = 3 // 0x03 + NaluTypeSliceStsaN NalUnitType = 4 // 0x04 + NaluTypeSliceStsaR NalUnitType = 5 // 0x05 + NaluTypeSliceRadlN NalUnitType = 6 // 0x06 + NaluTypeSliceRadlR NalUnitType = 7 // 0x07 + NaluTypeSliceRaslN NalUnitType = 8 // 0x06 + NaluTypeSliceRaslR NalUnitType = 9 // 0x09 + + NaluTypeSliceBlaWlp NalUnitType = 16 // 0x10 + NaluTypeSliceBlaWradl NalUnitType = 17 // 0x11 + NaluTypeSliceBlaNlp NalUnitType = 18 // 0x12 + NaluTypeSliceIdr NalUnitType = 19 // 0x13 + NaluTypeSliceIdrNlp NalUnitType = 20 // 0x14 + NaluTypeSliceCranut NalUnitType = 21 // 0x15 + NaluTypeSliceRsvIrapVcl22 NalUnitType = 22 // 0x16 + NaluTypeSliceRsvIrapVcl23 NalUnitType = 23 // 0x17 + + NaluTypeVps NalUnitType = 32 // 0x20 + NaluTypeSps NalUnitType = 33 // 0x21 + NaluTypePps NalUnitType = 34 // 0x22 + NaluTypeAud NalUnitType = 35 // 0x23 + NaluTypeSei NalUnitType = 39 // 0x27 + NaluTypeSeiSuffix NalUnitType = 40 // 0x28 + + NaluTypeUnspecified NalUnitType = 48 // 0x30 +) + +// H265Reader reads data from stream and constructs h265 nal units +type H265Reader struct { + stream io.Reader + nalBuffer []byte + countOfConsecutiveZeroBytes int + nalPrefixParsed bool + readBuffer []byte +} + +var ( + errNilReader = errors.New("stream is nil") + errDataIsNotH265Stream = errors.New("data is not a H265 bitstream") +) + +// NewReader creates new H265Reader +func NewReader(in io.Reader) (*H265Reader, error) { + if in == nil { + return nil, errNilReader + } + + reader := &H265Reader{ + stream: in, + nalBuffer: make([]byte, 0), + nalPrefixParsed: false, + readBuffer: make([]byte, 0), + } + + return reader, nil +} + +// NAL H.265 Network Abstraction Layer +type NAL struct { + PictureOrderCount uint32 + + // NAL header + ForbiddenZeroBit bool + UnitType NalUnitType + NuhLayerId uint8 + NuhTemporalIdPlus1 uint8 + + Data []byte // header byte + rbsp +} + +func (reader *H265Reader) read(numToRead int) (data []byte) { + for len(reader.readBuffer) < numToRead { + buf := make([]byte, 4096) + n, err := reader.stream.Read(buf) + if n == 0 || err != nil { + break + } + buf = buf[0:n] + reader.readBuffer = append(reader.readBuffer, buf...) + } + var numShouldRead int + if numToRead <= len(reader.readBuffer) { + numShouldRead = numToRead + } else { + numShouldRead = len(reader.readBuffer) + } + data = reader.readBuffer[0:numShouldRead] + reader.readBuffer = reader.readBuffer[numShouldRead:] + return data +} + +func (reader *H265Reader) bitStreamStartsWithH265Prefix() (prefixLength int, e error) { + nalPrefix3Bytes := []byte{0, 0, 1} + nalPrefix4Bytes := []byte{0, 0, 0, 1} + + prefixBuffer := reader.read(4) + + n := len(prefixBuffer) + + if n == 0 { + return 0, io.EOF + } + + if n < 3 { + return 0, errDataIsNotH265Stream + } + + nalPrefix3BytesFound := bytes.Equal(nalPrefix3Bytes, prefixBuffer[:3]) + if n == 3 { + if nalPrefix3BytesFound { + return 0, io.EOF + } + return 0, errDataIsNotH265Stream + } + + // n == 4 + if nalPrefix3BytesFound { + reader.nalBuffer = append(reader.nalBuffer, prefixBuffer[3]) + return 3, nil + } + + nalPrefix4BytesFound := bytes.Equal(nalPrefix4Bytes, prefixBuffer) + if nalPrefix4BytesFound { + return 4, nil + } + return 0, errDataIsNotH265Stream +} + +// NextNAL reads from stream and returns then next NAL, +// and an error if there is incomplete frame data. +// Returns all nil values when no more NALs are available. +func (reader *H265Reader) NextNAL() (*NAL, error) { + if !reader.nalPrefixParsed { + _, err := reader.bitStreamStartsWithH265Prefix() + if err != nil { + return nil, err + } + + reader.nalPrefixParsed = true + } + + for { + buffer := reader.read(1) + n := len(buffer) + + if n != 1 { + break + } + readByte := buffer[0] + nalFound := reader.processByte(readByte) + if nalFound { + nal := newNal(reader.nalBuffer) + nal.parseHeader() + if nal.UnitType == NaluTypeSeiSuffix || nal.UnitType == NaluTypeSei { + reader.nalBuffer = nil + continue + } else { + break + } + } + + reader.nalBuffer = append(reader.nalBuffer, readByte) + } + + if len(reader.nalBuffer) == 0 { + return nil, io.EOF + } + + nal := newNal(reader.nalBuffer) + reader.nalBuffer = nil + nal.parseHeader() + + return nal, nil +} + +func (reader *H265Reader) processByte(readByte byte) (nalFound bool) { + nalFound = false + + switch readByte { + case 0: + reader.countOfConsecutiveZeroBytes++ + case 1: + if reader.countOfConsecutiveZeroBytes >= 2 { + countOfConsecutiveZeroBytesInPrefix := 2 + if reader.countOfConsecutiveZeroBytes > 2 { + countOfConsecutiveZeroBytesInPrefix = 3 + } + nalUnitLength := len(reader.nalBuffer) - countOfConsecutiveZeroBytesInPrefix + reader.nalBuffer = reader.nalBuffer[0:nalUnitLength] + reader.countOfConsecutiveZeroBytes = 0 + nalFound = true + } else { + reader.countOfConsecutiveZeroBytes = 0 + } + default: + reader.countOfConsecutiveZeroBytes = 0 + } + + return nalFound +} + +func newNal(data []byte) *NAL { + return &NAL{PictureOrderCount: 0, ForbiddenZeroBit: false, UnitType: NaluTypeUnspecified, Data: data} +} + +func (h *NAL) parseHeader() { + firstByte := h.Data[0] + h.ForbiddenZeroBit = (((firstByte & 0x80) >> 7) == 1) // 0x80 = 0b10000000 + h.UnitType = NalUnitType((firstByte & 0x7E) >> 1) // 0x1F = 0b01111110 +} diff --git a/trunk/3rdparty/srs-bench/gb28181/ingester.go b/trunk/3rdparty/srs-bench/gb28181/ingester.go index 5ba496012..1fb2e3d04 100644 --- a/trunk/3rdparty/srs-bench/gb28181/ingester.go +++ b/trunk/3rdparty/srs-bench/gb28181/ingester.go @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) 2022 Winlin +// # Copyright (c) 2022 Winlin // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in @@ -26,8 +26,10 @@ import ( "github.com/ossrs/go-oryx-lib/errors" "github.com/ossrs/go-oryx-lib/logger" "github.com/pion/webrtc/v3/pkg/media/h264reader" + "github.com/yapingcat/gomedia/mpeg2" "io" "os" + "path" "strconv" "strings" "sync" @@ -293,9 +295,16 @@ func (v *PSIngester) Ingest(ctx context.Context) error { } defer f.Close() - h264, err := h264reader.NewReader(videoFile) + fileSuffix := path.Ext(v.conf.psConfig.video) + var h264 *h264reader.H264Reader + var h265 *H265Reader + if fileSuffix == ".h265" { + h265, err = NewReader(videoFile) + } else { + h264, err = h264reader.NewReader(videoFile) + } if err != nil { - return errors.Wrapf(err, "Open h264 %v", v.conf.psConfig.video) + return errors.Wrapf(err, "Open %v", v.conf.psConfig.video) } audio, err := NewAACReader(f) @@ -328,47 +337,13 @@ func (v *PSIngester) Ingest(ctx context.Context) error { // One pack should only contains one video frame. if !pack.hasVideo { - var sps, pps *h264reader.NAL - var videoFrames []*h264reader.NAL - for ctx.Err() == nil { - frame, err := h264.NextNAL() - if err == io.EOF { - return io.EOF - } - if err != nil { - return errors.Wrapf(err, "Read h264") - } - - videoFrames = append(videoFrames, frame) - logger.If(ctx, "NALU %v PictureOrderCount=%v, ForbiddenZeroBit=%v, RefIdc=%v, %v bytes", - frame.UnitType.String(), frame.PictureOrderCount, frame.ForbiddenZeroBit, frame.RefIdc, len(frame.Data)) - - if frame.UnitType == h264reader.NalUnitTypeSPS { - sps = frame - } else if frame.UnitType == h264reader.NalUnitTypePPS { - pps = frame - } else { - break - } - } - - // We convert the video sample rate to be based over 1024, that is 1024 samples means one video frame. - avcSamples += 1024 - videoDTS = uint64(v.conf.clockRate*avcSamples) / uint64(videoSampleRate) - - if sps != nil || pps != nil { - err = pack.WriteHeader(videoDTS) + if fileSuffix == ".h265" { + err = v.writeH265(ctx, pack, h265, videoSampleRate, &avcSamples, &videoDTS) } else { - err = pack.WritePackHeader(videoDTS) + err = v.writeH264(ctx, pack, h264, videoSampleRate, &avcSamples, &videoDTS) } if err != nil { - return errors.Wrap(err, "pack header") - } - - for _, frame := range videoFrames { - if err = pack.WriteVideo(frame.Data, videoDTS); err != nil { - return errors.Wrapf(err, "write video %v", len(frame.Data)) - } + return errors.Wrap(err, "WriteVideo") } } @@ -416,3 +391,101 @@ func (v *PSIngester) Ingest(ctx context.Context) error { return nil } + +func (v *PSIngester) writeH264(ctx context.Context, pack *PSPackStream, h264 *h264reader.H264Reader, + videoSampleRate int, avcSamples, videoDTS *uint64) error { + var sps, pps *h264reader.NAL + var videoFrames []*h264reader.NAL + for ctx.Err() == nil { + frame, err := h264.NextNAL() + if err == io.EOF { + return io.EOF + } + if err != nil { + return errors.Wrapf(err, "Read h264") + } + + videoFrames = append(videoFrames, frame) + logger.If(ctx, "NALU %v PictureOrderCount=%v, ForbiddenZeroBit=%v, RefIdc=%v, %v bytes", + frame.UnitType.String(), frame.PictureOrderCount, frame.ForbiddenZeroBit, frame.RefIdc, len(frame.Data)) + + if frame.UnitType == h264reader.NalUnitTypeSPS { + sps = frame + } else if frame.UnitType == h264reader.NalUnitTypePPS { + pps = frame + } else { + break + } + } + + // We convert the video sample rate to be based over 1024, that is 1024 samples means one video frame. + *avcSamples += 1024 + *videoDTS = uint64(v.conf.clockRate*(*avcSamples)) / uint64(videoSampleRate) + + var err error + if sps != nil || pps != nil { + err = pack.WriteHeader(mpeg2.PS_STREAM_H264, *videoDTS) + } else { + err = pack.WritePackHeader(*videoDTS) + } + if err != nil { + return errors.Wrap(err, "pack header") + } + + for _, frame := range videoFrames { + if err = pack.WriteVideo(frame.Data, *videoDTS); err != nil { + return errors.Wrapf(err, "write video %v", len(frame.Data)) + } + } + return nil +} + +func (v *PSIngester) writeH265(ctx context.Context, pack *PSPackStream, h265 *H265Reader, + videoSampleRate int, avcSamples, videoDTS *uint64) error { + var vps, sps, pps *NAL + var videoFrames []*NAL + for ctx.Err() == nil { + frame, err := h265.NextNAL() + if err == io.EOF { + return io.EOF + } + if err != nil { + return errors.Wrapf(err, "Read h265") + } + + videoFrames = append(videoFrames, frame) + logger.If(ctx, "NALU %v PictureOrderCount=%v, ForbiddenZeroBit=%v, %v bytes", + frame.UnitType, frame.PictureOrderCount, frame.ForbiddenZeroBit, len(frame.Data)) + + if frame.UnitType == NaluTypeVps { + vps = frame + } else if frame.UnitType == NaluTypeSps { + sps = frame + } else if frame.UnitType == NaluTypePps { + pps = frame + } else { + break + } + } + + // We convert the video sample rate to be based over 1024, that is 1024 samples means one video frame. + *avcSamples += 1024 + *videoDTS = uint64(v.conf.clockRate*(*avcSamples)) / uint64(videoSampleRate) + + var err error + if vps != nil || sps != nil || pps != nil { + err = pack.WriteHeader(mpeg2.PS_STREAM_H265, *videoDTS) + } else { + err = pack.WritePackHeader(*videoDTS) + } + if err != nil { + return errors.Wrap(err, "pack header") + } + + for _, frame := range videoFrames { + if err = pack.WriteVideo(frame.Data, *videoDTS); err != nil { + return errors.Wrapf(err, "write video %v", len(frame.Data)) + } + } + return nil +} diff --git a/trunk/3rdparty/srs-bench/gb28181/ps.go b/trunk/3rdparty/srs-bench/gb28181/ps.go index c2d9fd76b..07deeee82 100644 --- a/trunk/3rdparty/srs-bench/gb28181/ps.go +++ b/trunk/3rdparty/srs-bench/gb28181/ps.go @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) 2022 Winlin +// # Copyright (c) 2022 Winlin // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in @@ -162,14 +162,14 @@ func NewPSPackStream(pt uint8) *PSPackStream { return &PSPackStream{ideaPesLength: 1400, pt: pt} } -func (v *PSPackStream) WriteHeader(dts uint64) error { +func (v *PSPackStream) WriteHeader(videoCodec mpeg2.PS_STREAM_TYPE, dts uint64) error { if err := v.WritePackHeader(dts); err != nil { return err } if err := v.WriteSystemHeader(dts); err != nil { return err } - if err := v.WriteProgramStreamMap(dts); err != nil { + if err := v.WriteProgramStreamMap(videoCodec, dts); err != nil { return err } return nil @@ -215,18 +215,19 @@ func (v *PSPackStream) WriteSystemHeader(dts uint64) error { return nil } -func (v *PSPackStream) WriteProgramStreamMap(dts uint64) error { +func (v *PSPackStream) WriteProgramStreamMap(videoCodec mpeg2.PS_STREAM_TYPE, dts uint64) error { w := codec.NewBitStreamWriter(1500) psm := &mpeg2.Program_stream_map{ Stream_map: []*mpeg2.Elementary_stream_elem{ // SrsTsPESStreamIdVideoCommon = 0xe0 - mpeg2.NewElementary_stream_elem(uint8(mpeg2.PS_STREAM_H264), 0xe0), + mpeg2.NewElementary_stream_elem(uint8(videoCodec), 0xe0), // SrsTsPESStreamIdAudioCommon = 0xc0 mpeg2.NewElementary_stream_elem(uint8(mpeg2.PS_STREAM_AAC), 0xc0), }, } + psm.Current_next_indicator = 1 psm.Encode(w) v.packets = append(v.packets, NewPSPacket(PSPacketTypeProgramStramMap, w.Bits(), dts, v.pt)) diff --git a/trunk/3rdparty/srs-bench/gb28181/util.go b/trunk/3rdparty/srs-bench/gb28181/util.go index 42bf37cb7..18bc84c99 100644 --- a/trunk/3rdparty/srs-bench/gb28181/util.go +++ b/trunk/3rdparty/srs-bench/gb28181/util.go @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) 2022 Winlin +// # Copyright (c) 2022 Winlin // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in @@ -65,7 +65,7 @@ func prepareTest() (err error) { srsMediaTimeout = flag.Int("srs-media-timeout", 2100, "PS media disconnect timeout in ms") srsReinviteTimeout = flag.Int("srs-reinvite-timeout", 1200, "When disconnect, SIP re-invite timeout in ms") srsPublishAudio = flag.String("srs-publish-audio", "avatar.aac", "The audio file for publisher.") - srsPublishVideo = flag.String("srs-publish-video", "avatar.h264", "The video file for publisher.") + srsPublishVideo = flag.String("srs-publish-video", "avatar.h264", "The video file for publisher. Note that *.h264 is for AVC, *.h265 is for HEVC.") srsPublishVideoFps = flag.Int("srs-publish-video-fps", 25, "The video fps for publisher.") // Should parse it first. diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 76eded386..e8da97b34 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -8,6 +8,7 @@ The changelog for SRS. ## SRS 6.0 Changelog +* v6.0, 2023-02-25, Merge [#3416](https://github.com/ossrs/srs/pull/3416): GB: Support HEVC for regression test and load tool for GB. v6.0.29 (#3416) * v6.0, 2023-02-25, Merge [#3424](https://github.com/ossrs/srs/pull/3424): API: Add service_id for http_hooks, which identify the process. v6.0.28 (#3424) * v6.0, 2023-02-22, Compatible with legacy RTMP URL. v6.0.27 * v6.0, 2023-02-16, Merge [#3411](https://github.com/ossrs/srs/pull/3411): HEVC: Fix nalu vec duplicate when h265 vps/sps/pps demux. v6.0.26 (#3411) diff --git a/trunk/src/core/srs_core_version6.hpp b/trunk/src/core/srs_core_version6.hpp index 6aacadcd6..089adec66 100644 --- a/trunk/src/core/srs_core_version6.hpp +++ b/trunk/src/core/srs_core_version6.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 6 #define VERSION_MINOR 0 -#define VERSION_REVISION 28 +#define VERSION_REVISION 29 #endif