diff --git a/README.md b/README.md index ad412e9a5254..ad312362baa2 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Live streams can be published to the server with: |[WebRTC servers](#webrtc-servers)|WHEP|AV1, VP9, VP8, [H265](#supported-browsers), H264|Opus, G722, G711 (PCMA, PCMU)| |[RTSP clients](#rtsp-clients)|UDP, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM and any RTP-compatible codec| |[RTSP cameras and servers](#rtsp-cameras-and-servers)|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM and any RTP-compatible codec| -|[RTMP clients](#rtmp-clients)|RTMP, RTMPS, Enhanced RTMP|AV1, VP9, H265, H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G711 (PCMA, PCMU), LPCM| +|[RTMP clients](#rtmp-clients)|RTMP, RTMPS, Enhanced RTMP|AV1, VP9, H265, H264|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G711 (PCMA, PCMU), LPCM| |[RTMP cameras and servers](#rtmp-cameras-and-servers)|RTMP, RTMPS, Enhanced RTMP|AV1, VP9, H265, H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G711 (PCMA, PCMU), LPCM| |[HLS cameras and servers](#hls-cameras-and-servers)|Low-Latency HLS, MP4-based HLS, legacy HLS|AV1, VP9, [H265](#supported-browsers-1), H264|Opus, MPEG-4 Audio (AAC)| |[UDP/MPEG-TS](#udpmpeg-ts)|Unicast, broadcast, multicast|H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3| @@ -2438,9 +2438,10 @@ All the code in this repository is released under the [MIT License](LICENSE). Co |----|----| |[RTSP / RTP / RTCP specifications](https://github.com/bluenviron/gortsplib#specifications)|RTSP| |[HLS specifications](https://github.com/bluenviron/gohlslib#specifications)|HLS| -|[RTMP](https://rtmp.veriskope.com/pdf/rtmp_specification_1.0.pdf)|RTMP| -|[Enhanced RTMP v1](https://veovera.org/docs/enhanced/enhanced-rtmp-v1.pdf)|RTMP| -|[Action Message Format](https://rtmp.veriskope.com/pdf/amf0-file-format-specification.pdf)|RTMP| +|[Action Message Format - AMF 0](https://veovera.org/docs/legacy/amf0-file-format-spec.pdf)|RTMP| +|[FLV](https://veovera.org/docs/legacy/video-file-format-v10-1-spec.pdf)|RTMP| +|[RTMP](https://veovera.org/docs/legacy/rtmp-v1-0-spec.pdf)|RTMP| +|[Enhanced RTMP v2](https://veovera.org/docs/enhanced/enhanced-rtmp-v2.pdf)|RTMP| |[WebRTC: Real-Time Communication in Browsers](https://www.w3.org/TR/webrtc/)|WebRTC| |[RFC8835, Transports for WebRTC](https://datatracker.ietf.org/doc/html/rfc8835)|WebRTC| |[RFC7742, WebRTC Video Processing and Codec Requirements](https://datatracker.ietf.org/doc/html/rfc7742)|WebRTC| diff --git a/internal/protocols/rtmp/message/extended_mpeg2ts_sequence_start.go b/internal/protocols/rtmp/message/extended_mpeg2ts_sequence_start.go deleted file mode 100644 index f599c3265649..000000000000 --- a/internal/protocols/rtmp/message/extended_mpeg2ts_sequence_start.go +++ /dev/null @@ -1,26 +0,0 @@ -package message - -import ( - "fmt" - - "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" -) - -// ExtendedMPEG2TSSequenceStart is a MPEG2-TS sequence start extended message. -type ExtendedMPEG2TSSequenceStart struct { - FourCC FourCC -} - -func (m *ExtendedMPEG2TSSequenceStart) unmarshal(raw *rawmessage.Message) error { - if len(raw.Body) < 5 { - return fmt.Errorf("invalid body size") - } - - m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4]) - - return fmt.Errorf("ExtendedMPEG2TSSequenceStart is not implemented yet") -} - -func (m ExtendedMPEG2TSSequenceStart) marshal() (*rawmessage.Message, error) { - return nil, fmt.Errorf("TODO") -} diff --git a/internal/protocols/rtmp/message/message.go b/internal/protocols/rtmp/message/message.go index 4d34d0334863..15c280889348 100644 --- a/internal/protocols/rtmp/message/message.go +++ b/internal/protocols/rtmp/message/message.go @@ -18,19 +18,15 @@ const ( TypeSetChunkSize Type = 1 TypeAbortMessage Type = 2 TypeAcknowledge Type = 3 + TypeUserControl Type = 4 TypeSetWindowAckSize Type = 5 TypeSetPeerBandwidth Type = 6 - - TypeUserControl Type = 4 - - TypeCommandAMF3 Type = 17 - TypeCommandAMF0 Type = 20 - - TypeDataAMF3 Type = 15 - TypeDataAMF0 Type = 18 - - TypeAudio Type = 8 - TypeVideo Type = 9 + TypeAudio Type = 8 + TypeVideo Type = 9 + TypeDataAMF3 Type = 15 + TypeDataAMF0 Type = 18 + TypeCommandAMF3 Type = 17 + TypeCommandAMF0 Type = 20 ) // UserControlType is a user control type. @@ -47,27 +43,44 @@ const ( UserControlTypePingResponse UserControlType = 7 ) -// ExtendedType is a message extended type. -type ExtendedType uint8 +// AudioExType is an audio message extended type. +type AudioExType uint8 -// message extended types. +// video message extended types. const ( - ExtendedTypeSequenceStart ExtendedType = 0 - ExtendedTypeCodedFrames ExtendedType = 1 - ExtendedTypeSequenceEnd ExtendedType = 2 - ExtendedTypeFramesX ExtendedType = 3 - ExtendedTypeMetadata ExtendedType = 4 - ExtendedTypeMPEG2TSSequenceStart ExtendedType = 5 + AudioExTypeSequenceStart AudioExType = 0 + AudioExTypeCodedFrames AudioExType = 1 + AudioExTypeSequenceEnd AudioExType = 2 + AudioExTypeMultichannelConfig AudioExType = 4 + AudioExTypeMultitrack AudioExType = 5 ) -// FourCC is an identifier of a video codec. +// VideoExType is a video message extended type. +type VideoExType uint8 + +// video message extended types. +const ( + VideoExTypeSequenceStart VideoExType = 0 + VideoExTypeCodedFrames VideoExType = 1 + VideoExTypeSequenceEnd VideoExType = 2 + VideoExTypeFramesX VideoExType = 3 + VideoExTypeMetadata VideoExType = 4 + VideoExTypeMPEG2TSSequenceStart VideoExType = 5 + VideoExTypeMultitrack VideoExType = 6 +) + +// FourCC is an identifier of a Extended-RTMP codec. type FourCC uint32 -// video codec identifiers. +// codec identifiers. var ( + // video FourCCAV1 FourCC = 'a'<<24 | 'v'<<16 | '0'<<8 | '1' FourCCVP9 FourCC = 'v'<<24 | 'p'<<16 | '0'<<8 | '9' FourCCHEVC FourCC = 'h'<<24 | 'v'<<16 | 'c'<<8 | '1' + + // audio + FourCCOpus FourCC = 'O'<<24 | 'p'<<16 | 'u'<<8 | 's' ) // Message is a message. diff --git a/internal/protocols/rtmp/message/acknowledge.go b/internal/protocols/rtmp/message/msg_acknowledge.go similarity index 100% rename from internal/protocols/rtmp/message/acknowledge.go rename to internal/protocols/rtmp/message/msg_acknowledge.go diff --git a/internal/protocols/rtmp/message/audio.go b/internal/protocols/rtmp/message/msg_audio.go similarity index 100% rename from internal/protocols/rtmp/message/audio.go rename to internal/protocols/rtmp/message/msg_audio.go diff --git a/internal/protocols/rtmp/message/msg_audio_ex_coded_frames.go b/internal/protocols/rtmp/message/msg_audio_ex_coded_frames.go new file mode 100644 index 000000000000..28fd8607c840 --- /dev/null +++ b/internal/protocols/rtmp/message/msg_audio_ex_coded_frames.go @@ -0,0 +1,54 @@ +package message + +import ( + "fmt" + "time" + + "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" +) + +// AudioExCodedFrames is a CodedFrames extended message. +type AudioExCodedFrames struct { + ChunkStreamID byte + DTS time.Duration + MessageStreamID uint32 + FourCC FourCC + Payload []byte +} + +func (m *AudioExCodedFrames) unmarshal(raw *rawmessage.Message) error { + if len(raw.Body) < 6 { + return fmt.Errorf("not enough bytes") + } + + m.ChunkStreamID = raw.ChunkStreamID + m.DTS = raw.Timestamp + m.MessageStreamID = raw.MessageStreamID + m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4]) + m.Payload = raw.Body[5:] + + return nil +} + +func (m AudioExCodedFrames) marshalBodySize() int { + return 5 + len(m.Payload) +} + +func (m AudioExCodedFrames) marshal() (*rawmessage.Message, error) { + body := make([]byte, m.marshalBodySize()) + + body[0] = (9 << 4) | byte(AudioExTypeCodedFrames) + body[1] = uint8(m.FourCC >> 24) + body[2] = uint8(m.FourCC >> 16) + body[3] = uint8(m.FourCC >> 8) + body[4] = uint8(m.FourCC) + copy(body[5:], m.Payload) + + return &rawmessage.Message{ + ChunkStreamID: m.ChunkStreamID, + Timestamp: m.DTS, + Type: uint8(TypeAudio), + MessageStreamID: m.MessageStreamID, + Body: body, + }, nil +} diff --git a/internal/protocols/rtmp/message/msg_audio_ex_multichannel_config.go b/internal/protocols/rtmp/message/msg_audio_ex_multichannel_config.go new file mode 100644 index 000000000000..5cbd51d626a2 --- /dev/null +++ b/internal/protocols/rtmp/message/msg_audio_ex_multichannel_config.go @@ -0,0 +1,69 @@ +package message + +import ( + "fmt" + + "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" +) + +// AudioExChannelOrder is an audio channel order. +type AudioExChannelOrder uint8 + +// audio channel orders. +const ( + AudioExChannelOrderUnspecified AudioExChannelOrder = 0 + AudioExChannelOrderNative AudioExChannelOrder = 1 + AudioExChannelOrderCustom AudioExChannelOrder = 2 +) + +// AudioExMultichannelConfig is a sequence start extended message. +type AudioExMultichannelConfig struct { + ChunkStreamID byte + MessageStreamID uint32 + FourCC FourCC + AudioChannelOrder AudioExChannelOrder + ChannelCount uint8 + AudioChannelMapping uint8 // only for AudioChannelOrder == AudioExChannelOrderCustom + AudioChannelFlags uint32 // only for AudioChannelOrder == AudioExChannelOrderNative +} + +func (m *AudioExMultichannelConfig) unmarshal(raw *rawmessage.Message) error { + if len(raw.Body) < 7 { + return fmt.Errorf("not enough bytes") + } + + m.ChunkStreamID = raw.ChunkStreamID + m.MessageStreamID = raw.MessageStreamID + m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4]) + m.AudioChannelOrder = AudioExChannelOrder(raw.Body[5]) + m.ChannelCount = raw.Body[6] + + switch m.AudioChannelOrder { + case AudioExChannelOrderCustom: + if len(raw.Body) != 8 { + return fmt.Errorf("invalid AudioExMultichannelConfig size") + } + m.AudioChannelMapping = raw.Body[7] + + case AudioExChannelOrderNative: + if len(raw.Body) != 11 { + return fmt.Errorf("invalid AudioExMultichannelConfig size") + } + m.AudioChannelFlags = uint32(raw.Body[7])<<24 | uint32(raw.Body[8])<<16 | + uint32(raw.Body[9])<<8 | uint32(raw.Body[10]) + + case AudioExChannelOrderUnspecified: + if len(raw.Body) != 7 { + return fmt.Errorf("invalid AudioExMultichannelConfig size") + } + + default: + return fmt.Errorf("invalid AudioChannelOrder") + } + + return nil +} + +func (m AudioExMultichannelConfig) marshal() (*rawmessage.Message, error) { + return nil, fmt.Errorf("TODO") +} diff --git a/internal/protocols/rtmp/message/msg_audio_ex_sequence_start.go b/internal/protocols/rtmp/message/msg_audio_ex_sequence_start.go new file mode 100644 index 000000000000..dccf560e1fe8 --- /dev/null +++ b/internal/protocols/rtmp/message/msg_audio_ex_sequence_start.go @@ -0,0 +1,32 @@ +package message + +import ( + "fmt" + + "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" +) + +// AudioExSequenceStart is a sequence start extended message. +type AudioExSequenceStart struct { + ChunkStreamID byte + MessageStreamID uint32 + FourCC FourCC + Config []byte +} + +func (m *AudioExSequenceStart) unmarshal(raw *rawmessage.Message) error { + if len(raw.Body) < 6 { + return fmt.Errorf("not enough bytes") + } + + m.ChunkStreamID = raw.ChunkStreamID + m.MessageStreamID = raw.MessageStreamID + m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4]) + m.Config = raw.Body[5:] + + return nil +} + +func (m AudioExSequenceStart) marshal() (*rawmessage.Message, error) { + return nil, fmt.Errorf("TODO") +} diff --git a/internal/protocols/rtmp/message/command_amf0.go b/internal/protocols/rtmp/message/msg_command_amf0.go similarity index 100% rename from internal/protocols/rtmp/message/command_amf0.go rename to internal/protocols/rtmp/message/msg_command_amf0.go diff --git a/internal/protocols/rtmp/message/command_amf0_test.go b/internal/protocols/rtmp/message/msg_command_amf0_test.go similarity index 100% rename from internal/protocols/rtmp/message/command_amf0_test.go rename to internal/protocols/rtmp/message/msg_command_amf0_test.go diff --git a/internal/protocols/rtmp/message/data_amf0.go b/internal/protocols/rtmp/message/msg_data_amf0.go similarity index 100% rename from internal/protocols/rtmp/message/data_amf0.go rename to internal/protocols/rtmp/message/msg_data_amf0.go diff --git a/internal/protocols/rtmp/message/set_chunk_size.go b/internal/protocols/rtmp/message/msg_set_chunk_size.go similarity index 100% rename from internal/protocols/rtmp/message/set_chunk_size.go rename to internal/protocols/rtmp/message/msg_set_chunk_size.go diff --git a/internal/protocols/rtmp/message/set_peer_bandwidth.go b/internal/protocols/rtmp/message/msg_set_peer_bandwidth.go similarity index 100% rename from internal/protocols/rtmp/message/set_peer_bandwidth.go rename to internal/protocols/rtmp/message/msg_set_peer_bandwidth.go diff --git a/internal/protocols/rtmp/message/set_window_ack_size.go b/internal/protocols/rtmp/message/msg_set_window_ack_size.go similarity index 100% rename from internal/protocols/rtmp/message/set_window_ack_size.go rename to internal/protocols/rtmp/message/msg_set_window_ack_size.go diff --git a/internal/protocols/rtmp/message/user_control_ping_request.go b/internal/protocols/rtmp/message/msg_user_control_ping_request.go similarity index 100% rename from internal/protocols/rtmp/message/user_control_ping_request.go rename to internal/protocols/rtmp/message/msg_user_control_ping_request.go diff --git a/internal/protocols/rtmp/message/user_control_ping_response.go b/internal/protocols/rtmp/message/msg_user_control_ping_response.go similarity index 100% rename from internal/protocols/rtmp/message/user_control_ping_response.go rename to internal/protocols/rtmp/message/msg_user_control_ping_response.go diff --git a/internal/protocols/rtmp/message/user_control_set_buffer_length.go b/internal/protocols/rtmp/message/msg_user_control_set_buffer_length.go similarity index 100% rename from internal/protocols/rtmp/message/user_control_set_buffer_length.go rename to internal/protocols/rtmp/message/msg_user_control_set_buffer_length.go diff --git a/internal/protocols/rtmp/message/user_control_stream_begin.go b/internal/protocols/rtmp/message/msg_user_control_stream_begin.go similarity index 100% rename from internal/protocols/rtmp/message/user_control_stream_begin.go rename to internal/protocols/rtmp/message/msg_user_control_stream_begin.go diff --git a/internal/protocols/rtmp/message/user_control_stream_dry.go b/internal/protocols/rtmp/message/msg_user_control_stream_dry.go similarity index 100% rename from internal/protocols/rtmp/message/user_control_stream_dry.go rename to internal/protocols/rtmp/message/msg_user_control_stream_dry.go diff --git a/internal/protocols/rtmp/message/user_control_stream_eof.go b/internal/protocols/rtmp/message/msg_user_control_stream_eof.go similarity index 100% rename from internal/protocols/rtmp/message/user_control_stream_eof.go rename to internal/protocols/rtmp/message/msg_user_control_stream_eof.go diff --git a/internal/protocols/rtmp/message/user_control_stream_is_recorded.go b/internal/protocols/rtmp/message/msg_user_control_stream_is_recorded.go similarity index 100% rename from internal/protocols/rtmp/message/user_control_stream_is_recorded.go rename to internal/protocols/rtmp/message/msg_user_control_stream_is_recorded.go diff --git a/internal/protocols/rtmp/message/video.go b/internal/protocols/rtmp/message/msg_video.go similarity index 100% rename from internal/protocols/rtmp/message/video.go rename to internal/protocols/rtmp/message/msg_video.go diff --git a/internal/protocols/rtmp/message/extended_coded_frames.go b/internal/protocols/rtmp/message/msg_video_ex_coded_frames.go similarity index 81% rename from internal/protocols/rtmp/message/extended_coded_frames.go rename to internal/protocols/rtmp/message/msg_video_ex_coded_frames.go index 5759f4b8ea21..1e4e071143e1 100644 --- a/internal/protocols/rtmp/message/extended_coded_frames.go +++ b/internal/protocols/rtmp/message/msg_video_ex_coded_frames.go @@ -7,8 +7,8 @@ import ( "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" ) -// ExtendedCodedFrames is a CodedFrames extended message. -type ExtendedCodedFrames struct { +// VideoExCodedFrames is a CodedFrames extended message. +type VideoExCodedFrames struct { ChunkStreamID byte DTS time.Duration MessageStreamID uint32 @@ -17,7 +17,7 @@ type ExtendedCodedFrames struct { Payload []byte } -func (m *ExtendedCodedFrames) unmarshal(raw *rawmessage.Message) error { +func (m *VideoExCodedFrames) unmarshal(raw *rawmessage.Message) error { if len(raw.Body) < 8 { return fmt.Errorf("not enough bytes") } @@ -37,7 +37,7 @@ func (m *ExtendedCodedFrames) unmarshal(raw *rawmessage.Message) error { return nil } -func (m ExtendedCodedFrames) marshalBodySize() int { +func (m VideoExCodedFrames) marshalBodySize() int { var l int if m.FourCC == FourCCHEVC { l = 8 + len(m.Payload) @@ -47,10 +47,10 @@ func (m ExtendedCodedFrames) marshalBodySize() int { return l } -func (m ExtendedCodedFrames) marshal() (*rawmessage.Message, error) { +func (m VideoExCodedFrames) marshal() (*rawmessage.Message, error) { body := make([]byte, m.marshalBodySize()) - body[0] = 0b10000000 | byte(ExtendedTypeCodedFrames) + body[0] = 0b10000000 | byte(VideoExTypeCodedFrames) body[1] = uint8(m.FourCC >> 24) body[2] = uint8(m.FourCC >> 16) body[3] = uint8(m.FourCC >> 8) diff --git a/internal/protocols/rtmp/message/extended_frames_x.go b/internal/protocols/rtmp/message/msg_video_ex_frames_x.go similarity index 75% rename from internal/protocols/rtmp/message/extended_frames_x.go rename to internal/protocols/rtmp/message/msg_video_ex_frames_x.go index a85ea764e3cb..3443c7778e9d 100644 --- a/internal/protocols/rtmp/message/extended_frames_x.go +++ b/internal/protocols/rtmp/message/msg_video_ex_frames_x.go @@ -7,8 +7,8 @@ import ( "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" ) -// ExtendedFramesX is a FramesX extended message. -type ExtendedFramesX struct { +// VideoExFramesX is a FramesX extended message. +type VideoExFramesX struct { ChunkStreamID byte DTS time.Duration MessageStreamID uint32 @@ -16,7 +16,7 @@ type ExtendedFramesX struct { Payload []byte } -func (m *ExtendedFramesX) unmarshal(raw *rawmessage.Message) error { +func (m *VideoExFramesX) unmarshal(raw *rawmessage.Message) error { if len(raw.Body) < 6 { return fmt.Errorf("not enough bytes") } @@ -30,14 +30,14 @@ func (m *ExtendedFramesX) unmarshal(raw *rawmessage.Message) error { return nil } -func (m ExtendedFramesX) marshalBodySize() int { +func (m VideoExFramesX) marshalBodySize() int { return 5 + len(m.Payload) } -func (m ExtendedFramesX) marshal() (*rawmessage.Message, error) { +func (m VideoExFramesX) marshal() (*rawmessage.Message, error) { body := make([]byte, m.marshalBodySize()) - body[0] = 0b10000000 | byte(ExtendedTypeFramesX) + body[0] = 0b10000000 | byte(VideoExTypeFramesX) body[1] = uint8(m.FourCC >> 24) body[2] = uint8(m.FourCC >> 16) body[3] = uint8(m.FourCC >> 8) diff --git a/internal/protocols/rtmp/message/extended_metadata.go b/internal/protocols/rtmp/message/msg_video_ex_metadata.go similarity index 79% rename from internal/protocols/rtmp/message/extended_metadata.go rename to internal/protocols/rtmp/message/msg_video_ex_metadata.go index 8a8ee5172c5a..5a79c200ba1c 100644 --- a/internal/protocols/rtmp/message/extended_metadata.go +++ b/internal/protocols/rtmp/message/msg_video_ex_metadata.go @@ -8,8 +8,8 @@ import ( "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" ) -// ExtendedMetadata is a metadata extended message. -type ExtendedMetadata struct { +// VideoExMetadata is a metadata extended message. +type VideoExMetadata struct { ChunkStreamID byte DTS time.Duration MessageStreamID uint32 @@ -17,7 +17,7 @@ type ExtendedMetadata struct { Payload amf0.Data } -func (m *ExtendedMetadata) unmarshal(raw *rawmessage.Message) error { +func (m *VideoExMetadata) unmarshal(raw *rawmessage.Message) error { if len(raw.Body) < 6 { return fmt.Errorf("invalid body size") } @@ -36,7 +36,7 @@ func (m *ExtendedMetadata) unmarshal(raw *rawmessage.Message) error { return nil } -func (m ExtendedMetadata) marshalBodySize() (int, error) { +func (m VideoExMetadata) marshalBodySize() (int, error) { ms, err := m.Payload.MarshalSize() if err != nil { return 0, err @@ -44,14 +44,14 @@ func (m ExtendedMetadata) marshalBodySize() (int, error) { return 5 + ms, nil } -func (m ExtendedMetadata) marshal() (*rawmessage.Message, error) { +func (m VideoExMetadata) marshal() (*rawmessage.Message, error) { mbs, err := m.marshalBodySize() if err != nil { return nil, err } body := make([]byte, mbs) - body[0] = 0b10000000 | byte(ExtendedTypeMetadata) + body[0] = 0b10000000 | byte(VideoExTypeMetadata) body[1] = uint8(m.FourCC >> 24) body[2] = uint8(m.FourCC >> 16) body[3] = uint8(m.FourCC >> 8) diff --git a/internal/protocols/rtmp/message/extended_sequence_end.go b/internal/protocols/rtmp/message/msg_video_ex_sequence_end.go similarity index 59% rename from internal/protocols/rtmp/message/extended_sequence_end.go rename to internal/protocols/rtmp/message/msg_video_ex_sequence_end.go index b595225d8677..7de3ac550936 100644 --- a/internal/protocols/rtmp/message/extended_sequence_end.go +++ b/internal/protocols/rtmp/message/msg_video_ex_sequence_end.go @@ -6,12 +6,12 @@ import ( "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" ) -// ExtendedSequenceEnd is a sequence end extended message. -type ExtendedSequenceEnd struct { +// VideoExSequenceEnd is a sequence end extended message. +type VideoExSequenceEnd struct { FourCC FourCC } -func (m *ExtendedSequenceEnd) unmarshal(raw *rawmessage.Message) error { +func (m *VideoExSequenceEnd) unmarshal(raw *rawmessage.Message) error { if len(raw.Body) < 5 { return fmt.Errorf("invalid body size") } @@ -21,6 +21,6 @@ func (m *ExtendedSequenceEnd) unmarshal(raw *rawmessage.Message) error { return nil } -func (m ExtendedSequenceEnd) marshal() (*rawmessage.Message, error) { +func (m VideoExSequenceEnd) marshal() (*rawmessage.Message, error) { return nil, fmt.Errorf("TODO") } diff --git a/internal/protocols/rtmp/message/extended_sequence_start.go b/internal/protocols/rtmp/message/msg_video_ex_sequence_start.go similarity index 71% rename from internal/protocols/rtmp/message/extended_sequence_start.go rename to internal/protocols/rtmp/message/msg_video_ex_sequence_start.go index 59804c6b6942..afe32f3a4430 100644 --- a/internal/protocols/rtmp/message/extended_sequence_start.go +++ b/internal/protocols/rtmp/message/msg_video_ex_sequence_start.go @@ -6,15 +6,15 @@ import ( "github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage" ) -// ExtendedSequenceStart is a sequence start extended message. -type ExtendedSequenceStart struct { +// VideoExSequenceStart is a sequence start extended message. +type VideoExSequenceStart struct { ChunkStreamID byte MessageStreamID uint32 FourCC FourCC Config []byte } -func (m *ExtendedSequenceStart) unmarshal(raw *rawmessage.Message) error { +func (m *VideoExSequenceStart) unmarshal(raw *rawmessage.Message) error { if len(raw.Body) < 6 { return fmt.Errorf("not enough bytes") } @@ -27,14 +27,14 @@ func (m *ExtendedSequenceStart) unmarshal(raw *rawmessage.Message) error { return nil } -func (m ExtendedSequenceStart) marshalBodySize() int { +func (m VideoExSequenceStart) marshalBodySize() int { return 5 + len(m.Config) } -func (m ExtendedSequenceStart) marshal() (*rawmessage.Message, error) { +func (m VideoExSequenceStart) marshal() (*rawmessage.Message, error) { body := make([]byte, m.marshalBodySize()) - body[0] = 0b10000000 | byte(ExtendedTypeSequenceStart) + body[0] = 0b10000000 | byte(VideoExTypeSequenceStart) body[1] = uint8(m.FourCC >> 24) body[2] = uint8(m.FourCC >> 16) body[3] = uint8(m.FourCC >> 8) diff --git a/internal/protocols/rtmp/message/reader.go b/internal/protocols/rtmp/message/reader.go index b4a34b63adba..3aba9e512fc0 100644 --- a/internal/protocols/rtmp/message/reader.go +++ b/internal/protocols/rtmp/message/reader.go @@ -62,6 +62,36 @@ func allocateMessage(raw *rawmessage.Message) (Message, error) { return &DataAMF0{}, nil case TypeAudio: + if len(raw.Body) < 5 { + return nil, fmt.Errorf("not enough bytes") + } + + if (raw.Body[0] >> 4) == 9 { + fourCC := FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4]) + + switch fourCC { + case FourCCOpus: + default: + return nil, fmt.Errorf("invalid fourCC: %v", fourCC) + } + + extendedType := AudioExType(raw.Body[0] & 0x0F) + + switch extendedType { + case AudioExTypeSequenceStart: + return &AudioExSequenceStart{}, nil + + case AudioExTypeMultichannelConfig: + return &AudioExMultichannelConfig{}, nil + + case AudioExTypeCodedFrames: + return &AudioExCodedFrames{}, nil + + default: + return nil, fmt.Errorf("unsupported audio extended type: %v", extendedType) + } + } + return &Audio{}, nil case TypeVideo: @@ -78,29 +108,26 @@ func allocateMessage(raw *rawmessage.Message) (Message, error) { return nil, fmt.Errorf("invalid fourCC: %v", fourCC) } - extendedType := ExtendedType(raw.Body[0] & 0x0F) + extendedType := VideoExType(raw.Body[0] & 0x0F) switch extendedType { - case ExtendedTypeSequenceStart: - return &ExtendedSequenceStart{}, nil - - case ExtendedTypeCodedFrames: - return &ExtendedCodedFrames{}, nil + case VideoExTypeSequenceStart: + return &VideoExSequenceStart{}, nil - case ExtendedTypeSequenceEnd: - return &ExtendedSequenceEnd{}, nil + case VideoExTypeCodedFrames: + return &VideoExCodedFrames{}, nil - case ExtendedTypeFramesX: - return &ExtendedFramesX{}, nil + case VideoExTypeSequenceEnd: + return &VideoExSequenceEnd{}, nil - case ExtendedTypeMetadata: - return &ExtendedMetadata{}, nil + case VideoExTypeFramesX: + return &VideoExFramesX{}, nil - case ExtendedTypeMPEG2TSSequenceStart: - return &ExtendedMPEG2TSSequenceStart{}, nil + case VideoExTypeMetadata: + return &VideoExMetadata{}, nil default: - return nil, fmt.Errorf("invalid extended type: %v", extendedType) + return nil, fmt.Errorf("unsupported video extended type: %v", extendedType) } } return &Video{}, nil diff --git a/internal/protocols/rtmp/message/reader_test.go b/internal/protocols/rtmp/message/reader_test.go index b2f8239ddefd..5a86ac90acad 100644 --- a/internal/protocols/rtmp/message/reader_test.go +++ b/internal/protocols/rtmp/message/reader_test.go @@ -22,8 +22,8 @@ var readWriterCases = []struct { Value: 45953968, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x3, - 0x0, 0x0, 0x0, 0x0, 0x2, 0xbd, 0x33, 0xb0, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x02, 0xbd, 0x33, 0xb0, }, }, { @@ -39,8 +39,9 @@ var readWriterCases = []struct { Payload: []byte{0x01, 0x02, 0x03, 0x04}, }, []byte{ - 0x7, 0x5b, 0xc3, 0x6e, 0x0, 0x0, 0x5, 0x8, 0x0, 0x45, 0x31, 0xf, 0x2f, - 0x01, 0x02, 0x03, 0x04, + 0x07, 0x5b, 0xc3, 0x6e, 0x00, 0x00, 0x05, 0x08, + 0x00, 0x45, 0x31, 0x0f, 0x2f, 0x01, 0x02, 0x03, + 0x04, }, }, { @@ -57,8 +58,8 @@ var readWriterCases = []struct { Payload: []byte{0x5A, 0xC0, 0x77, 0x40}, }, []byte{ - 0x7, 0x5b, 0xc3, 0x6e, 0x0, 0x0, 0x6, 0x8, - 0x0, 0x45, 0x31, 0xf, 0xaf, 0x1, 0x5a, 0xc0, + 0x07, 0x5b, 0xc3, 0x6e, 0x00, 0x00, 0x06, 0x08, + 0x00, 0x45, 0x31, 0x0f, 0xaf, 0x01, 0x5a, 0xc0, 0x77, 0x40, }, }, @@ -78,14 +79,14 @@ var readWriterCases = []struct { }, }, []byte{ - 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0x14, - 0x0, 0x5, 0x44, 0x9b, 0x2, 0x0, 0xc, 0x69, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x14, + 0x00, 0x05, 0x44, 0x9b, 0x02, 0x00, 0x0c, 0x69, 0x38, 0x79, 0x79, 0x74, 0x68, 0x72, 0x65, 0x72, - 0x67, 0x72, 0x65, 0x0, 0x40, 0xeb, 0x91, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x6b, - 0x31, 0x2, 0x0, 0x2, 0x76, 0x31, 0x0, 0x2, - 0x6b, 0x32, 0x2, 0x0, 0x2, 0x76, 0x32, 0x0, - 0x0, 0x9, 0x5, + 0x67, 0x72, 0x65, 0x00, 0x40, 0xeb, 0x91, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x6b, + 0x31, 0x02, 0x00, 0x02, 0x76, 0x31, 0x00, 0x02, + 0x6b, 0x32, 0x02, 0x00, 0x02, 0x76, 0x32, 0x00, + 0x00, 0x09, 0x05, }, }, { @@ -100,9 +101,9 @@ var readWriterCases = []struct { }, }, []byte{ - 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0x12, - 0x0, 0x5, 0x44, 0x9b, 0x0, 0x40, 0x6d, 0x40, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x6, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x12, + 0x00, 0x05, 0x44, 0x9b, 0x00, 0x40, 0x6d, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x05, }, }, @@ -112,8 +113,8 @@ var readWriterCases = []struct { Value: 10000, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x27, 0x10, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x10, }, }, { @@ -122,8 +123,8 @@ var readWriterCases = []struct { Value: 10000, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x27, 0x10, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x10, }, }, { @@ -132,8 +133,8 @@ var readWriterCases = []struct { Value: 10000, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x27, 0x10, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x10, }, }, { @@ -142,8 +143,8 @@ var readWriterCases = []struct { ServerTime: 569834435, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x4, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x21, 0xf6, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x21, 0xf6, 0xfb, 0xc3, }, }, @@ -153,8 +154,8 @@ var readWriterCases = []struct { ServerTime: 569834435, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x4, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x21, 0xf6, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x21, 0xf6, 0xfb, 0xc3, }, }, @@ -165,9 +166,9 @@ var readWriterCases = []struct { BufferLength: 235345, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x4, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, - 0x8a, 0xce, 0x0, 0x3, 0x97, 0x51, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x8a, 0xce, 0x00, 0x03, 0x97, 0x51, }, }, { @@ -176,8 +177,8 @@ var readWriterCases = []struct { StreamID: 35534, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x4, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xce, }, }, @@ -187,8 +188,8 @@ var readWriterCases = []struct { StreamID: 35534, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x4, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x8a, 0xce, }, }, @@ -198,8 +199,8 @@ var readWriterCases = []struct { StreamID: 35534, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x4, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x8a, 0xce, }, }, @@ -209,8 +210,8 @@ var readWriterCases = []struct { StreamID: 35534, }, []byte{ - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x4, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x8a, 0xce, }, }, @@ -234,7 +235,7 @@ var readWriterCases = []struct { }, { "extended sequence start", - &ExtendedSequenceStart{ + &VideoExSequenceStart{ ChunkStreamID: 4, MessageStreamID: 0x1000000, FourCC: FourCCHEVC, @@ -248,7 +249,7 @@ var readWriterCases = []struct { }, { "extended coded frames", - &ExtendedCodedFrames{ + &VideoExCodedFrames{ ChunkStreamID: 4, DTS: 15100 * time.Millisecond, MessageStreamID: 0x1000000, @@ -264,7 +265,7 @@ var readWriterCases = []struct { }, { "extended frames x", - &ExtendedFramesX{ + &VideoExFramesX{ ChunkStreamID: 4, DTS: 15100 * time.Millisecond, MessageStreamID: 0x1000000, @@ -279,7 +280,7 @@ var readWriterCases = []struct { }, { "extended metadata", - &ExtendedMetadata{ + &VideoExMetadata{ ChunkStreamID: 0x6, DTS: 0, MessageStreamID: 0x1000000, diff --git a/internal/protocols/rtmp/reader.go b/internal/protocols/rtmp/reader.go index b111780b8058..3dfdf2216a26 100644 --- a/internal/protocols/rtmp/reader.go +++ b/internal/protocols/rtmp/reader.go @@ -31,6 +31,9 @@ type OnDataVP9Func func(pts time.Duration, frame []byte) // OnDataH26xFunc is the prototype of the callback passed to OnDataH26x(). type OnDataH26xFunc func(pts time.Duration, au [][]byte) +// OnDataOpusFunc is the prototype of the callback passed to OnDataOpus(). +type OnDataOpusFunc func(pts time.Duration, packet []byte) + // OnDataMPEG4AudioFunc is the prototype of the callback passed to OnDataMPEG4Audio(). type OnDataMPEG4AudioFunc func(pts time.Duration, au []byte) @@ -87,17 +90,12 @@ func hasAudio(md amf0.Object, audioTrack *format.Format) (bool, error) { case 0: return false, nil - case message.CodecMPEG4Audio, message.CodecLPCM: - return true, nil - case message.CodecMPEG1Audio: *audioTrack = &format.MPEG1Audio{} return true, nil - case message.CodecPCMA: - return true, nil - - case message.CodecPCMU: + case message.CodecMPEG4Audio, message.CodecLPCM, + message.CodecPCMA, message.CodecPCMU, float64(message.FourCCOpus): return true, nil } @@ -263,7 +261,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma return videoTrack, nil, nil } - case *message.ExtendedSequenceStart: + case *message.VideoExSequenceStart: if !hasVideo { return nil, nil, fmt.Errorf("unexpected video packet") } @@ -308,7 +306,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma PayloadTyp: 96, } - default: // VP9 + case message.FourCCVP9: var vpcc mp4.VpcC _, err = mp4.Unmarshal(bytes.NewReader(msg.Config), uint64(len(msg.Config)), &vpcc, mp4.Context{}) if err != nil { @@ -383,6 +381,20 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma } } } + + case *message.AudioExSequenceStart: + if !hasAudio { + return nil, nil, fmt.Errorf("unexpected audio packet") + } + + if audioTrack == nil { + if msg.FourCC == message.FourCCOpus { + audioTrack = &format.Opus{ + PayloadTyp: 96, + ChannelCount: 2, // TODO + } + } + } } } } @@ -467,7 +479,7 @@ type Reader struct { videoTrack format.Format audioTrack format.Format onDataVideo func(message.Message) error - onDataAudio func(*message.Audio) error + onDataAudio func(message.Message) error } // NewReader allocates a Reader. @@ -542,7 +554,7 @@ func (r *Reader) Tracks() (format.Format, format.Format) { // OnDataAV1 sets a callback that is called when AV1 data is received. func (r *Reader) OnDataAV1(cb OnDataAV1Func) { r.onDataVideo = func(msg message.Message) error { - if msg, ok := msg.(*message.ExtendedCodedFrames); ok { + if msg, ok := msg.(*message.VideoExCodedFrames); ok { tu, err := av1.BitstreamUnmarshal(msg.Payload, true) if err != nil { return fmt.Errorf("unable to decode bitstream: %w", err) @@ -557,7 +569,7 @@ func (r *Reader) OnDataAV1(cb OnDataAV1Func) { // OnDataVP9 sets a callback that is called when VP9 data is received. func (r *Reader) OnDataVP9(cb OnDataVP9Func) { r.onDataVideo = func(msg message.Message) error { - if msg, ok := msg.(*message.ExtendedCodedFrames); ok { + if msg, ok := msg.(*message.VideoExCodedFrames); ok { cb(msg.DTS, msg.Payload) } return nil @@ -579,7 +591,7 @@ func (r *Reader) OnDataH265(cb OnDataH26xFunc) { cb(msg.DTS+msg.PTSDelta, au) - case *message.ExtendedFramesX: + case *message.VideoExFramesX: au, err := h264.AVCCUnmarshal(msg.Payload) if err != nil { if errors.Is(err, h264.ErrAVCCNoNALUs) { @@ -590,7 +602,7 @@ func (r *Reader) OnDataH265(cb OnDataH26xFunc) { cb(msg.DTS, au) - case *message.ExtendedCodedFrames: + case *message.VideoExCodedFrames: au, err := h264.AVCCUnmarshal(msg.Payload) if err != nil { if errors.Is(err, h264.ErrAVCCNoNALUs) { @@ -642,11 +654,23 @@ func (r *Reader) OnDataH264(cb OnDataH26xFunc) { } } +// OnDataOpus sets a callback that is called when Opus data is received. +func (r *Reader) OnDataOpus(cb OnDataOpusFunc) { + r.onDataAudio = func(msg message.Message) error { + if msg, ok := msg.(*message.AudioExCodedFrames); ok { + cb(msg.DTS, msg.Payload) + } + return nil + } +} + // OnDataMPEG4Audio sets a callback that is called when MPEG-4 Audio data is received. func (r *Reader) OnDataMPEG4Audio(cb OnDataMPEG4AudioFunc) { - r.onDataAudio = func(msg *message.Audio) error { - if msg.AACType == message.AudioAACTypeAU { - cb(msg.DTS, msg.Payload) + r.onDataAudio = func(msg message.Message) error { + if msg, ok := msg.(*message.Audio); ok { + if msg.AACType == message.AudioAACTypeAU { + cb(msg.DTS, msg.Payload) + } } return nil } @@ -654,16 +678,20 @@ func (r *Reader) OnDataMPEG4Audio(cb OnDataMPEG4AudioFunc) { // OnDataMPEG1Audio sets a callback that is called when MPEG-1 Audio data is received. func (r *Reader) OnDataMPEG1Audio(cb OnDataMPEG1AudioFunc) { - r.onDataAudio = func(msg *message.Audio) error { - cb(msg.DTS, msg.Payload) + r.onDataAudio = func(msg message.Message) error { + if msg, ok := msg.(*message.Audio); ok { + cb(msg.DTS, msg.Payload) + } return nil } } // OnDataG711 sets a callback that is called when G711 data is received. func (r *Reader) OnDataG711(cb OnDataG711Func) { - r.onDataAudio = func(msg *message.Audio) error { - cb(msg.DTS, msg.Payload) + r.onDataAudio = func(msg message.Message) error { + if msg, ok := msg.(*message.Audio); ok { + cb(msg.DTS, msg.Payload) + } return nil } } @@ -673,23 +701,27 @@ func (r *Reader) OnDataLPCM(cb OnDataLPCMFunc) { bitDepth := r.audioTrack.(*format.LPCM).BitDepth if bitDepth == 16 { - r.onDataAudio = func(msg *message.Audio) error { - le := len(msg.Payload) - if le%2 != 0 { - return fmt.Errorf("invalid payload length: %d", le) - } + r.onDataAudio = func(msg message.Message) error { + if msg, ok := msg.(*message.Audio); ok { + le := len(msg.Payload) + if le%2 != 0 { + return fmt.Errorf("invalid payload length: %d", le) + } - // convert from little endian to big endian - for i := 0; i < le; i += 2 { - msg.Payload[i], msg.Payload[i+1] = msg.Payload[i+1], msg.Payload[i] - } + // convert from little endian to big endian + for i := 0; i < le; i += 2 { + msg.Payload[i], msg.Payload[i+1] = msg.Payload[i+1], msg.Payload[i] + } - cb(msg.DTS, msg.Payload) + cb(msg.DTS, msg.Payload) + } return nil } } else { - r.onDataAudio = func(msg *message.Audio) error { - cb(msg.DTS, msg.Payload) + r.onDataAudio = func(msg message.Message) error { + if msg, ok := msg.(*message.Audio); ok { + cb(msg.DTS, msg.Payload) + } return nil } } @@ -703,14 +735,14 @@ func (r *Reader) Read() error { } switch msg := msg.(type) { - case *message.Video, *message.ExtendedFramesX, *message.ExtendedCodedFrames: + case *message.Video, *message.VideoExFramesX, *message.VideoExCodedFrames: if r.onDataVideo == nil { return fmt.Errorf("received a video packet, but track is not set up") } return r.onDataVideo(msg) - case *message.Audio: + case *message.Audio, *message.AudioExCodedFrames: if r.onDataAudio == nil { return fmt.Errorf("received an audio packet, but track is not set up") } diff --git a/internal/protocols/rtmp/reader_test.go b/internal/protocols/rtmp/reader_test.go index f5a2e9a5c5d8..655fecb21c49 100644 --- a/internal/protocols/rtmp/reader_test.go +++ b/internal/protocols/rtmp/reader_test.go @@ -579,7 +579,7 @@ func TestReadTracks(t *testing.T) { }, }, }, - &message.ExtendedSequenceStart{ + &message.VideoExSequenceStart{ ChunkStreamID: 4, MessageStreamID: 0x1000000, FourCC: message.FourCCHEVC, @@ -628,7 +628,7 @@ func TestReadTracks(t *testing.T) { }, }, }, - &message.ExtendedSequenceStart{ + &message.VideoExSequenceStart{ ChunkStreamID: 4, MessageStreamID: 0x1000000, FourCC: message.FourCCHEVC, @@ -690,7 +690,7 @@ func TestReadTracks(t *testing.T) { }, }, }, - &message.ExtendedSequenceStart{ + &message.VideoExSequenceStart{ ChunkStreamID: 6, MessageStreamID: 0x1000000, FourCC: message.FourCCAV1, diff --git a/internal/protocols/rtmp/to_stream.go b/internal/protocols/rtmp/to_stream.go index 4b8f8b0ce930..0a5e956bc794 100644 --- a/internal/protocols/rtmp/to_stream.go +++ b/internal/protocols/rtmp/to_stream.go @@ -95,6 +95,17 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { medias = append(medias, medi) switch audioFormat.(type) { + case *format.Opus: + r.OnDataOpus(func(pts time.Duration, packet []byte) { + (*stream).WriteUnit(medi, audioFormat, &unit.Opus{ + Base: unit.Base{ + NTP: time.Now(), + PTS: durationToTimestamp(pts, audioFormat.ClockRate()), + }, + Packets: [][]byte{packet}, + }) + }) + case *format.MPEG4Audio: r.OnDataMPEG4Audio(func(pts time.Duration, au []byte) { (*stream).WriteUnit(medi, audioFormat, &unit.MPEG4Audio{