public Task ProcessMessageAsync(RtmpMessage message) { BinaryPrimitives.TryReadInt32BigEndian(message.Payload.Span, out var value); switch (message.Message.MessageType) { case RtmpMessageType.SetChunkSize: SetChunkSize(value); break; case RtmpMessageType.SetPeerBandwidth: SetPeerBandwidth(value); break; case RtmpMessageType.WindowAcknowledgementSize: SetWindowAcknowledgementSize(value); break; case RtmpMessageType.Acknowledgement: break; default: throw new Exception($"Unknown control command {message.Message.MessageType}"); } return(Task.CompletedTask); }
public async Task ProcessMessageAsync(RtmpMessage message) { var command = new AmfDataMessage(); command.Decode(message.Payload.Span); await HandleDataCommandAsync(command, message); }
private bool AssembleMessage(RtmpMessageEntry messageEntry, RtmpChunk chunk, out RtmpMessage message) { message = null; var currentMessage = messageEntry.Message; if (currentMessage == null) { var payload = _memoryPool.Rent(chunk.Message.Length); currentMessage = new RtmpMessage(chunk.Header, chunk.Message, payload); messageEntry.Message = currentMessage; } var memory = currentMessage.Payload.Slice(messageEntry.BytesRead, chunk.PayloadLength); var reader = new SequenceReader <byte>(chunk.Payload); reader.TryCopyTo(memory.Span); messageEntry.BytesRead += chunk.PayloadLength; // if message is complete .. return it. if (messageEntry.BytesRemaining == 0) { message = messageEntry.Message; _logger.LogInformation($"Parsed message :{message.Header}: Message: {message.Message}"); messageEntry.BytesRead = 0; messageEntry.Message = null; return(true); } return(false); }
private async Task HandlePublishStreamAsync(RtmpMessage message, AmfCommandMessage command) { var streamName = command.AdditionalArguments[0] as string; var streamPath = $"/{_application}/{streamName}"; var streamId = (int)message.Message.StreamId; var stream = new RtmpStream(streamId, streamName, streamPath); string code, level, description; if (!_context.TryAddPublishser(streamPath, _session.Id)) { _logger.LogError($@"[rtmp publish] Already has a stream. id ={_session.Id} streamPath ={streamPath} streamId ={streamId}"); level = "error"; code = "NetStream.Publish.BadName"; description = "Stream already publishing"; } else if (_session.PublishStream != null) { level = "error"; description = $"{streamPath} is already published."; code = "NetStream.Publish.BadConnection"; } else { _session.PublishStream = stream; level = "status"; description = $"{streamPath} is now published."; code = "NetStream.Publish.Start"; } await SendStatusMessageAsync(stream.Id, level, code, description); await _session.SendStartToAllPlayersAsync(); }
public Task DispatchMessageAsync(RtmpMessage message) { switch (message.Message.MessageType) { case RtmpMessageType.SetChunkSize: case RtmpMessageType.Acknowledgement: case RtmpMessageType.WindowAcknowledgementSize: case RtmpMessageType.SetPeerBandwidth: return(ProcessProtocolMessageAsync(message)); case RtmpMessageType.CommandAMF0: case RtmpMessageType.CommandAMF3: return(ProcessCommandMessageAsync(message)); case RtmpMessageType.DataAMF0: case RtmpMessageType.DataAMF3: return(ProcessDataMessageAsync(message)); case RtmpMessageType.Audio: case RtmpMessageType.Video: return(ProcessMediaMessageAsync(message)); case RtmpMessageType.UserCtrlMessage: return(ProcessUserControlMessageAsync(message)); default: throw new Exception($"Unknown message of type: {message.Message.MessageType}"); } }
public async Task ProcessMessageAsync(RtmpMessage message) { var command = new AmfCommandMessage(); command.Decode(message.Payload.Span); _logger.LogInformation($"Procesing command:{command.Name} for session {_session.Id}s"); await ProcessCommandAsync(command, message); }
public async Task SendProtocolControlMessageAsync(RtmpMessageType messageType, int data) { var header = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Protocol); var messageHeader = new RtmpMessageHeader(0, 4, messageType, 0); var body = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(data)); var message = new RtmpMessage(header, messageHeader, body); await _session.SendMessageAsync(message); }
public async Task SendDataCommandAsync(AmfDataMessage data) { var body = new byte[data.GetLength()]; data.Encode(body.AsSpan()); var header = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Data); var messageHeader = new RtmpMessageHeader(0, body.Length, RtmpMessageType.DataAMF0, 0); var message = new RtmpMessage(header, messageHeader, body); await _session.SendMessageAsync(message); }
public async Task SendCommandMessageAsync(int streamId, AmfCommandMessage command) { var body = new byte[command.GetLength()]; command.Encode(body.AsSpan()); var header = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Invoke); var messageHeader = new RtmpMessageHeader(0, body.Length, RtmpMessageType.CommandAMF0, streamId); var message = new RtmpMessage(header, messageHeader, body); await _session.SendMessageAsync(message); }
protected override async Task ProcessCommandAsync(AmfCommandMessage command, RtmpMessage message) { switch (command.Name) { case "connect": await HandleConnectAsync(command); break; case "createStream": await HandleCreateStreamAsync(command); break; case "closeStream": await HandleCloseStreamAsync(command); break; case "deleteStream": await HandleDeleteStreamAsync(command); break; case "releaseStream": // nothting to do. break; case "FCPublish": case "FCUnpublish": break; case "publish": await HandlePublishStreamAsync(message, command); break; case "play": await HandlePlayAsync(message, command); break; case "pause": await HandlePauseAsync(message, command); break; case "getStreamLength": break; default: throw new InvalidOperationException($"Unknown command {command.Name} {command.CommandObject} "); } }
private async Task SendUserControlMessageAsync(UserControlMessageType type, int data) { var header = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Protocol); var messageHeader = new RtmpMessageHeader(0, 6, RtmpMessageType.UserCtrlMessage, 0); var payload = new byte[6]; BinaryPrimitives.WriteInt16BigEndian(payload.AsSpan(), (short)type); BinaryPrimitives.WriteInt32BigEndian(payload.AsSpan().Slice(2), data); var message = new RtmpMessage(header, messageHeader, payload); await _session.SendMessageAsync(message); }
private async Task SendMessageAsync(RtmpMessage message) { var chunkPayloadLength = Math.Min(message.Payload.Length, _session.OutgoingChunkLength); using var buffer = _memoryPool.Rent(chunkPayloadLength + RtmpChunk.MaxHeaderLength); foreach (var chunk in ChunkMessage(message)) { var memory = buffer.Memory.Slice(0, chunk.Length); chunk.Write(memory.Span); await _stream.WriteAsync(memory); } }
public async Task SendResultAsync(object data) { var chunkHeader = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Invoke); var response = new AmfCommandMessage { Name = "_result", CommandObject = data }; var length = response.GetLength(); var memory = new byte[length]; response.Encode(memory); var messageHeader = new RtmpMessageHeader(0, length, RtmpMessageType.CommandAMF0, 0); var message = new RtmpMessage(chunkHeader, messageHeader, memory); await _session.SendMessageAsync(message); }
public async Task ProcessMessageAsync(RtmpMessage message) { if (message.Message.MessageType == RtmpMessageType.Audio) { ProcessAudio(message); } else if (message.Message.MessageType == RtmpMessageType.Video) { ProcessVideo(message); } if (_session.IsPublishing) { await _session.SendToAllPalyersAsync(message); } }
/// <summary> /// Send a mesage to the other side of this session. /// </summary> /// <param name="message">The Rtmp message to send.</param> /// <returns></returns> public override async Task SendMessageAsync(RtmpMessage message) { if (IsPlaying) { if (!_keyFrameSent && message.Message.MessageType == RtmpMessageType.Video) { var frame_type = (message.Payload.Span[0] >> 4) & 0x0f; _keyFrameSent = frame_type == 1; } if (!_keyFrameSent && (message.Message.MessageType == RtmpMessageType.Audio || message.Message.MessageType == RtmpMessageType.Video)) { //return; } } await base.SendMessageAsync(message); }
public IEnumerable <RtmpChunk> ChunkMessage(RtmpMessage message) { var length = message.Message.Length; var memory = message.Payload; var offset = 0; while (length != 0) { var chunkHeader = new RtmpChunkHeader( offset == 0 ? RtmpChunkHeaderType.Type0 : RtmpChunkHeaderType.Type2, message.Header.StreamId); var chunkLength = Math.Min(_session.OutgoingChunkLength, length); var chunk = new RtmpChunk(chunkHeader, message.Message, memory.Slice(offset, chunkLength)); yield return(chunk); length -= chunkLength; offset += chunkLength; } }
public async Task OnStartPlayAsync(ServerSession publisher, RtmpStream stream) { if (publisher.MetaData.Length != 0) { var payload = publisher.MetaData; var header = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Data); var messageHeader = new RtmpMessageHeader(0, payload.Length, RtmpMessageType.DataAMF0, stream.Id); var message = new RtmpMessage(header, messageHeader, payload); await _session.SendMessageAsync(message); } if (publisher.AudioCodec?.CodecId == RtmpConstants.AacAudio) { var payload = publisher.AudioCodec.AacSequenceHeader; var header = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Audio); var messageHeader = new RtmpMessageHeader(0, payload.Length, RtmpMessageType.Audio, stream.Id); var message = new RtmpMessage(header, messageHeader, payload); await _session.SendMessageAsync(message); } if (publisher.VideoCodec?.CodecId == RtmpConstants.H264Video || publisher.VideoCodec?.CodecId == RtmpConstants.H265Video) { var payload = publisher.VideoCodec.AvcSequenceHeader; var header = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Video); var messageHeader = new RtmpMessageHeader(0, payload.Length, RtmpMessageType.Video, stream.Id); var message = new RtmpMessage(header, messageHeader, payload); await _session.SendMessageAsync(message); } if (publisher.RtmpGopCacheQueue != null) { foreach (var message in publisher.RtmpGopCacheQueue) { var modifiedMessage = new RtmpMessage( message.Header, message.Message.Translate(stream.Id), message.Payload); await _session.SendMessageAsync(modifiedMessage); } } _logger.LogInformation($@"[rtmp play] Join stream. id ={_session.Id} streamPath ={stream.Path} streamId ={stream.Id} "); }
private async Task HandleSetDataFrame(AmfDataMessage command) { _logger.LogInformation("Metadata found {0}", command.AdditionalArguments); var dataObj = (dynamic)command.AdditionalArguments[1]; var metadata = new AmfDataMessage { Name = "onMetaData", AdditionalArguments = { dataObj } }; _session.MetaData = new byte[metadata.GetLength()]; metadata.Encode(_session.MetaData.Span); if (((IDictionary <string, object>)dataObj).ContainsKey("audiocodecid")) { _session.AudioCodec = new AudioCodecInfo { CodecId = dataObj.audiocodecid is double?(int)dataObj.audiocodecid : 0, Channels = dataObj.stereo ? 2 : 1, Samplerate = (int)dataObj.audiosamplerate, Bitrate = (int)dataObj.audiodatarate }; } if (((IDictionary <string, object>)dataObj).ContainsKey("videocodecid")) { _session.VideoCodec = new VideoCodecInfo { CodecId = dataObj.videocodecid is double?(int)dataObj.videocodecid : 0, Width = (int)dataObj.width, Height = (int)dataObj.height, Framerate = (int)dataObj.framerate, Bitrate = (int)dataObj.videodatarate }; } var header = new RtmpChunkHeader(RtmpChunkHeaderType.Type0, RtmpConstants.RtmpChannel_Data); var messsageHeader = new RtmpMessageHeader(0, _session.MetaData.Length, RtmpMessageType.DataAMF0, 0); var message = new RtmpMessage(header, messsageHeader, _session.MetaData); await _session.SendToAllPalyersAsync(message); }
public async Task ProcessMessageAsync(RtmpMessage message) { var command = new AmfDataMessage(); command.Decode(message.Payload.Span); switch (command.Name) { case "@setDataFrame": await HandleSetDataFrame(command); break; case "onMetaData": await HandleMetadtataAsync(message, command); break; default: throw new InvalidOperationException($"Unknown command {command.Name} {command.AdditionalArguments} "); } }
public async Task SendToAllPalyersAsync(RtmpMessage message) { var players = _context.GetPlayers(PublishStream.Path); foreach (var player in players) { var stream = player.PlayStream; var header = message.Header; var messageHeader = message.Message.Translate(stream.Id); var playerMessage = new RtmpMessage(header, messageHeader, message.Payload); try { await player.SendMessageAsync(playerMessage); } catch (Exception ex) { //remove failing player. _logger.LogError(ex, $"Failed to send to player {player.Id}"); _context.RemovePlayer(PublishStream.Path, player.Id); } } }
private void ProcessVideo(RtmpMessage message) { var payload = message.Payload.Span; var frame_type = (payload[0] >> 4) & 0x0f; var codec_id = payload[0] & 0x0f; var video = _session.VideoCodec; if (video == null) { video = new VideoCodecInfo { CodecId = codec_id, }; _logger.LogInformation( $@"[rtmp publish] Handle video. id ={_session.Id} streamPath ={_session.PublishStream.Path} frame_type ={frame_type} codec_id ={codec_id} codec_name ={video.CodecName} {video.Width} x {video.Height}"); } if (codec_id == RtmpConstants.H264Video || codec_id == RtmpConstants.H265Video) { //cache avc sequence header if (frame_type == 1 && payload[1] == 0) { video.AvcSequenceHeader = new byte[payload.Length]; payload.CopyTo(video.AvcSequenceHeader.Span); dynamic info = AVUtil.ReadAVCSpecificConfig(payload); video.Width = info.width; video.Height = info.height; video.ProfileName = AVUtil.GetAVCProfileName(info); video.Level = info.level; } } }
private async Task HandlePlayAsync(RtmpMessage message, AmfCommandMessage command) { var streamName = command.AdditionalArguments[0] as string; var streamPath = $"/{_application}/{streamName.Split("?")[0]}"; var streamId = message.Message.StreamId; var stream = new RtmpStream(streamId, streamName, streamPath); if (_session.IsPlaying) { _logger.LogInformation($@"[rtmp play] NetConnection is playing.id ={_session.Id} streamPath ={streamPath} streamId ={streamId} "); await SendStatusMessageAsync(streamId, "error", "NetStream.Play.BadConnection", "Connection already playing"); return; } else { _session.PlayStream = stream; await RespondPlayAsync(stream); } if (_context.TryGetPublishser(streamPath, out var publisher)) { await OnStartPlayAsync(publisher, stream); _context.AddPlayer(streamPath, _session.Id); } else { _logger.LogInformation($@"[rtmp play] Stream not found.id ={_session.Id} streamPath ={streamPath} streamId ={streamId}"); _context.AddIdlePlayer(streamPath, _session.Id); } }
protected abstract Task ProcessCommandAsync(AmfCommandMessage command, RtmpMessage message);
public async Task ProcessMessageAsync(RtmpMessage message) { await _channel.Writer.WriteAsync(message); }
protected abstract Task ProcessMediaMessageAsync(RtmpMessage message);
protected abstract Task ProcessCommandMessageAsync(RtmpMessage message);
protected virtual async Task ProcessUserControlMessageAsync(RtmpMessage message) { await _eventProcessor.ProcessMessageAsync(message); }
protected virtual async Task ProcessProtocolMessageAsync(RtmpMessage message) { await _controlMessageProcessor.ProcessMessageAsync(message); }
protected virtual Task HandleDataCommandAsync(AmfDataMessage command, RtmpMessage message) { throw new NotImplementedException($"Do not know how to process {command.Name}"); }
/// <summary> /// Send a mesage to the other side of this session. /// </summary> /// <param name="message">The Rtmp message to send.</param> /// <returns></returns> public virtual async Task SendMessageAsync(RtmpMessage message) { await _writer.ProcessMessageAsync(message); }