RTMP Client Project in C#
(by Lou Brown)
The Brief
This is a guide for C# developers who want to connect to an RTMP server and download streamed content. I will start by describing the RTMP protocol and the quickest way to implement an RTMP client, then a description of a simple application that will connect to an RTMP server and download a file to your disk, and finally a list of classes and functions available in the RtmpClient library.
The libraries for this project can be found in RtmpClient.zip, which includes the RtmpClient library (BroccoliProducts.RtmpClient), and a low level library (BroccoliProducts.LowLevel) that the RtmpClient library uses.
| Current Build: |
1.0.0.6 (7th December, 2009) |
| System Requirements: |
Windows XP |
The code for the sample project can be found in "Program.cs", a single C# file that compiles as a console application. I have compiled and run this file on XP with VS2005. If you want to run this program, make sure you include a reference to the BroccoliProducts.RtmpClient library.
What is RTMP?
RTMP (Real Time Messaging Protocol) is the dominant protocol for streaming audio and video content over the Internet. If you watch web-tv or listen to Internet radio, the chances are the data is being transmitted as RTMP messages (or one of its variants), from an RTMP server, to an RTMP client embedded in your browser. The most widely used RTMP client is Adobe's Flash Player.
RTMP streamed data in three separate channels, or sub-streams - audio data, video data and meta-data. For a film, the audio-data is the soundtrack, the video-data is the images. For audio streaming, such as mp3 files, there is no video data. The meta-data describes the format of the video and audio data, for example, the dimensions of the video (e.g. 640x480), or the duration of the audio track. The main body of meta-data arrives when streaming starts.
RTMP is a complex protocol. It is owned by Adobe, and Adobe's official RTMP specification is not complete. The majority of usable information available on the workings of RTMP has been discovered by software developers' reverse engineering raw network port data. If your application requirement is to grab streamed RTMP data from a media server, a freely available RTMP client library will save you time, even if only for the initial stages of your application project.
The RTMP Client Library
The RTMP Client Library (BroccoliProducts.RtmpClient) provides a simple interface to an RTMP Server, with enough functionality to connect, disconnect, invoke commands, open and delete streams, and control the flow of the stream (pause, seek etc...). There are also classes to help you roll-your-own RTMP messages, and a feature for pre-processing incoming RTMP messages.
Program.cs
Our brief is to connect to an RTMP server, and download a stream of content to our hard disk. The code for this application is available here Program.cs.
At the top of the Program.cs file are examples of connection parameters that connect to local servers running on your system as well as remote servers globally available on the Internet.
Here is a table showing the programmatic steps required to download a stream of RTMP data.
| A |
Open the target files. Two files will be needed - one for the audio data, and one for the video data. If an MP3 is being streamed, the video data will be empty.
|
| B |
Create an RtmpClient object. The RtmpClient constructor takes one parameters, a reference to the main window of your application. Since out application runs in a console, which is windowless, this parameter can be null.
|
| C |
Call an RtmpClient function to open an RtmpConnection object. The RtmpConnection encapsulates a connection to the RTMP server. Three parameters are required to create the connection - the host address, the host port (1935 for RTMP), and the protocol, which is RTMP.
There are several variants of RTMP, for example, RTMPT which is RTMP tunneled through HTTP, or RTMPE which is encrypted RTMP.
|
| D |
Set up the behavior parameters for our RtmpConnection.
Some RTMP servers combine messages into one large message to save on processing overhead. Enable the automatic chopping up of aggregate messages to break these messages up into their sub-messages.
During the connection handshake, the RTMP sends a SetPeerBW messages, and expects a WindowsAck message in return. Enable AutoRespondToSetPeerBW to ensure this done automatically.
Every so often the RTMP server sends out a Ping message to see if the client is still alive, and the server expects a Pong response within a timeframe. Enable AutoPingPong to automatically send the Pongs back.
During connection handshake, the server sends out a ReceiveAckWindow, which is the number of bytes the client can receive from the server after which the server expects an acknowledgement. This value is usually 250,000 bytes, but can be much less. If no acknowledgement is received, the server stops sending. Enable AutoAckReceivedData to automatically send these acknowledgments.
|
| E |
Call an RtmpConnection function to connect to the streaming application we are interested in. There are ten parameters for this function, defining the path of the stream, and the capabilities of the client. The AppName and the TcUrl are the most important. The audio and video capabilities can be set to ALL. The other parameters can often be left empty.
The RTMP connection parameters consist of application names, combined with relative locations.
Look at the sets of connection parameters at the top of the Program.cs file for examples of these values.
|
| F |
Call an RtmpConnection function to create a stream. This returns an RtmpStream object.
An RTMP connection can theoretically support multiple streams, but in reality only the more sophisticated RTMP servers can do this.
|
| G |
Enable the RtmpStream's collecting buffers for audio and video data.
By enabling the CollectAudioDataFlag and CollectVideoDataFlag, the audio and video data that is received by a stream is added to two internal thread-safe buffers.
The amount of data in these buffers can be tested at any time using the AvailableAudioDataSize and AvailableVideoDataSize variables.
When data has been collected into the buffers, an external application can grab the data and write it to a file. This is what our application is doing in the _WriteAudioAndVideoDataToFiles function.
|
| H |
Register for pre-processing on our RtmpStream.
The pre-processing callback allows us to look at RTMP messages as they arrive for our stream, before anything else is done with them. We can intercept meta-data messages and display them for the user.
Another method of storing our audio and video data would have been to intercept audio and video messages in a pre-processing callback function and write the message payload directly to the files. However in our application we use the RtmpStream's thread-safe buffers because they use less processor time.
Another use of pre-processing is to examine the messages sent from the RTMP server that indicate when the stream is paused, stopped, streaming and complete, and then update the application interface.
The pre-processor function is called from inside a thread. Any processing done in the pre-processing callback function should be minimal, and must not interact with any form controls. If you want to receive events from the RtmpClient, use the StatusChanged event of the RtmpClient, which is described below.
|
| I |
Play the stream.
Playing the stream initiates a flow of audio and video data from the RTMP server, and some meta-data.
|
| J |
Take the incoming audio and video data and write it to the target files. Keep doing this until the streaming stops.
|
| K |
Clean up the Rtmp session. Delete the stream, close the connection, and destruct the RtmpClient object.
|
| L |
Flush and close the target files.
|
| M |
There is a good change the audio data will consist of MP3 packets, so attempt to convert this data into a valid MP3 files by calling one of the static helper functions of the RtmpClient, RebuildMP3File.
|
Additional Functionality
The following is a chart of the public functions and properties of each of the main Rtmp classes.
| RtmpClient |
|
| |
RtmpClient |
Construct a new RtmpClient object.
|
| |
CreateConnection |
Create a new RtmpConnection object which encapsulates a connection to an RTMP server.
|
| |
StatusChanged |
An event to notify of changes to client, connection or stream. This event and its arguments are detailed below. |
| |
RebuildMP3File |
A static helper function that converts a file of MP3 frames downloaded from an RtmpStream into a valid MP3 file that can be played with an MP3 player. |
| |
|
|
| RtmpConnection |
|
| |
AutoRespondToSetPeerBW |
A boolean property for enabling automatic responses to the handshake message SetPeerBW. |
| |
AutoChopUpAggregateMessages |
A boolean property for enabling the chopping up of aggregate messages as soon as they are received. |
| |
AutoPingPong |
A boolean property for enabling automatic ping message handling. |
| |
AutoAckReceivedData |
A boolean property for enabling automatic data acknowledgment messages. |
| |
Timestamp |
A UInt32 property for getting the current client-side timestamp. Used when creating your own messages. |
| |
ChunkSize |
A UInt32 property for getting the current chunk size. Used when creating your own messages. |
| |
Close |
Close the current connection. Closes all streams for this connection. |
| |
IsValid |
Returns null if the stream is valid, otherwise returns a string description. |
| |
SendMessageAndWait |
Send a message and wait for a response. The response expected will have the same message target as the sent message, will have a string value for the first parameter (for example "_result" or "_onStatus"), and will be the same message type (for example, an Amf0 Command). |
| |
SendMessage |
Send a message and return immediately. |
| |
GetNextTransactionId |
Get the next transaction id, and increment the next transaction Id counter (2 - 0x7FFFFF). |
| |
GetNextAmfStreamId |
Get the next Amf Stream id. Amf stream ids are rotated on the client-side (3 - 28). |
| |
ConnectToApplication |
Connect to a streaming application on the server. |
| |
CreateStream |
Create a new RtmpStream object. |
| |
|
|
| RtmpStream |
|
| |
StreamId |
A UInt32 property for getting the stream id that was assigned to the stream by the RTMP server. |
| |
State |
An eState property for getting the current state. Values include Idle, Streaming, Reset, Paused, Stopped and Closed. Once a stream is closed, it cannot be re-opened. |
| |
AvailableAudioDataSize |
A UInt32 property for getting the amount of data collected in the audio data buffer. |
| |
AvailableVideoDataSize |
A UInt32 property for getting the amount of data collected in the video data buffer. |
| |
CollectAudioDataFlag |
A boolean property to enable the collecting of audio data in the audio data buffer. |
| |
CollectVideoDataFlag |
A boolean property to enable the collecting of video data in the video data buffer. |
| |
ForwardMetaDataFlag |
A boolean property which when enabled, sends a StatusChanged event when meta-data is received for this stream. |
| |
IsValid |
Returns null if the stream is valid, otherwise returns a string description. |
| |
Play |
Sends the play command for the stream. |
| |
Pause |
Pause or resume streaming. |
| |
Seek |
Seek to a time offset within the stream. |
| |
Close |
Close the stream using the RTMP function "deleteStream". |
| |
SetBufferSize |
Set the buffer size in milliseconds. |
| |
ClearAudioCollector |
Clear the buffer of collected audio data. |
| |
ClearVideoCollector |
Clear the buffer of collected video data. |
| |
GetCollectedAudioData |
Grab collected audio data into the buffer provided. |
| |
GetCollectedVideoData |
Grab collected video data into the buffer provided. |
| |
RegisterForPreProcessing |
Register a function as the pre-processing callback function for this stream. |
| |
StreamNameFormatter |
A static helper function to help format stream names. |
RTMP Messages
As message data is received it is translated into Rtmp messages.
Each RTMP message type has an associated class derived from RtmpMessage. Each message class has one or more properties for accessing the parameters of the message. Some of the message classes also have static helper functions to assist building commonly used messages.
| RtmpMessage |
This is an abstract top-level class. All of the message classes are based on this class. |
| |
AmfStreamId |
The Amf stream id for the message. |
| |
Timestamp |
The server-side timestamp. |
| |
MsgTarget |
The message target, which is either zero or a stream id. |
| |
MsgType |
An eMsgType property for getting the RTMP type of the message. |
| |
ToBuffer |
Creates a buffer out of the message that can be sent over a socket connection. |
| |
ToString |
Creates a string description of the message. |
| |
|
|
| RtmpMessage_AudioData |
The audio-data message. |
| |
Data |
A byte-array property containing the audio-data payload of the message. |
| |
SetData |
Set the audio-data payload. |
| |
|
|
| RtmpMessage_CommandAmf0 |
An AMF0 command message. |
| |
Parameters |
A ParameterList property for getting a reference to the list of parameters. |
| |
Build_NetConnection_Connect |
A static helper function for building a "connect" message. |
| |
Build_NetConnection_CreateStream |
A static helper function for building a "createStream" message. |
| |
Build_NetStream_Play |
A static helper function for building a "play" message. |
| |
Build_NetStream_Pause |
A static helper function for building a "pause" message. |
| |
Build_NetStream_Seek |
A static helper function for building a "seek" message. |
| |
Build_NetStream_DeleteStream |
A static helper function for building a "deleteStream" message. |
| |
|
|
| RtmpMessage_Control |
A control-code message. Control messages are used for a variety of functions, each with their own parameter set. |
| |
ControlCode |
Set or get the ControlCode for the message, including STREAM_BEGIN, STREAM_EOF, STREAM_DRY, SET_BUFFER_SIZE,
STREAM_IS_RECORDED,
PING_REQUEST,
PING_RESPONSE,
SWFV_REQUEST,
SWFV_RESPONSE,
UNKNOWN1 and
UNKNOWN2. |
| |
StreamId |
The stream-id parameter for the stream control code messages. |
| |
PingTime |
The ping-time data in a ping request and returned in a ping response (a pong). |
| |
SetBufferLength |
Get and set the SetBufferLength parameter in milliseconds. |
| |
ResponseBuffer |
The response buffer for the SWFV_REQUEST and SWFV_RESPONSE control messages. |
| |
Build_SetBufferSize |
A static helper function for building a SET_BUFFER_SIZE message. |
| |
Build_Pong |
A static helper function for building a PING_RESPONSE message. |
| |
|
|
| RtmpMessage_DataAck |
A acknowledgement of received data. |
| |
Value |
Get or set a UInt32 value representing the amount of bytes received since the last DataAck message was sent. |
| |
Build |
A static helper function for building a DataAck message. |
| |
|
|
| RtmpMessage_MetaDataAmf0 |
Meta-data that is streamed with the audio and video data. |
| |
Parameters |
A ParameterList property for getting a reference to the list of parameters. |
| |
|
|
| RtmpMessage_SetChunkSize |
A chunk-size value, sent from the RTMP server, specifying how message data will be chopped up. |
| |
Value |
A UInt32 property for getting and setting the new chunk size. |
| |
|
|
| RtmpMessage_SetPeerBW |
A SET_PEER_BW message. |
| |
eLimitType |
An eLimitValue property, including HARD, SOFT and DYNAMIC. |
| |
Value |
A UInt32 property for getting and setting the bandwidth value. |
| |
|
|
| RtmpMessage_VideoData |
The video-data message. |
| |
Data |
A byte array property containing the video-data payload of the message. |
| |
SetData |
Set the video-data payload. |
| |
|
|
| RtmpMessage_WindowAckSize |
A specification of the Ack data size. |
| |
Value |
A UInt32 property for getting and setting the acknowledgement widow size in bytes. |
| |
Build |
A static helper function for building a Window-Ack-Size message. |
There is one further message class, and this one does not correspond to an RTMP message type.
| RtmpMessage_Raw |
An Rtmp message with header and payload. |
| |
Payload |
A byte array property for accessing the payload buffer. |
| |
SetPayload |
Set the payload buffer. |
Messages that are not translated into one of the RTMP message type classes are encapsulated in as a raw message, RtmpMessage_Raw.
Reasons why a message would not be translated include the following:
|
The message had a corrupt payload.
|
 |
The messages caused the RtmpClient library to throw an exception during translation.
|
 |
The message is of a type not currently supported by the BroccoliProducts.RtmpClient library, including Meta Data AMF3, AMF3 Commands, Shared Object AMF0, and Abort.
|
 |
The message contained parameters not currently supported by the BroccoliProducts.RtmpClient, including MovieClip, Referencer, LongString, Unsupported, Recordset, XmlDocuments and TypedObject. |
When a message cannot be translated, a StatusChanged event is fired with the CannotTranslateMsg flag, and a reference to the message.
By combining StatusChanged event monitoring and message pre-processing, and using the RtmpMessage class to create your own message, any message handling can be achieved by an external application, even if not directly supported by the RtmpClient library.
StatusChanged Events
StatusChanged events can be accessed using the RtmpClient::StatusChanged event handler. The event argument for the StatusChanged event is of type RtmpEventArgs. The members of this class are detailed as follows:
Hint |
A property of type RtmpEventArgs::eHint, describing the status change that raised the event. A table of possible values for the Hint is detailed below.
Note that eHint is an attributes flag, and can combine more than one value.
|
Stream |
A reference to the RtmpStream object for this event.
|
Msg |
A reference to the RtmpMessage for this event. |
Possible values for RtmpEventArgs::eHint are as follows:
| ConnectionOpened |
A new connection has been created.
|
| ConnectionClosed |
An existing connection has been closed.
|
| StreamOpened |
A new stream has been opened. The Stream property of the RtmpEventArgs will have a reference to the new stream.
|
| StreamClosed |
An existing stream has been closed. The Stream property of the RtmpEventArgs will have a reference to the closed stream.
|
| CannotTranslateMsg |
A message has been received from the RTMP server that cannot be translated. The Message property of the RtmpEventArgs will have a reference to a raw message class.
|
| AudioDataAvailable |
Some audio data has been stored in the stream's audio data collector. This event is only sent when the collector was previously empty. The Stream property of the RtmpEventArgs will have a reference to the stream on which the data was received.
|
| VideoDataAvailable |
See the description for AudioDataAvailable.
|
| StreamBegin |
Streaming has begun. The Stream property of the RtmpEventArgs will have a reference to the stream on which the data was received.
|
| StreamPaused |
A stream has been paused. The Stream property of the RtmpEventArgs will have a reference to the stream on which the data was received.
|
| StreamReset |
A stream has been reset. The Stream property of the RtmpEventArgs will have a reference to the stream on which the data was received.
|
| StreamStopped |
A stream has stopped. The Stream property of the RtmpEventArgs will have a reference to the stream on which the data was received.
|
| IncomingMetaData |
A meta-data message has been received. The Stream property of the RtmpEventArgs will have a reference to the stream on which the data was received.
|
| ParserTripped |
During translation of a received RTMP message, the RtmpClient library code fell over, and there can be no more translation of messages. This is a fatal error.
|
| PingsExchanged |
A ping request was received, and a response was sent.
|
| ReceivedDataAck |
A DataAck message was sent to acknowledge received data. |
Creating Your Own RTMP Messages
There will be some messages that the RtmpClient library does not support. You can create these message yourself using the message classes derived from RtmpMessage.
There are two ways to create your own RTMP messages - create an instance of the whichever message class correspondents to a message type, for example, a Command(AMF0) message could be created using the RtmpMessage_CommandAmf0 class. Or create a raw message, and format the binary content yourself, which is a lot harder.
The following is an example of creating a "play" command message, starting with a new RtmpMessage_CommandAmf0 object.
The ComandAmf0 class has a property "Parameters", which is a reference to a list of parameters.
The "Amf0" refers to the formatting of the parameters of the message.
// create a message object
RtmpMessage_CommandAmf0 cmd = new RtmpMessage_CommandAmf0(
m_rtmpConnection.GetNextAmfStreamId(),
m_rtmpConnection.Timestamp,
1 // target is stream-id 1
);
// add parameter - string command for playing a stream
cmd.Parameters.Add( new RtmpParameter_String(null,"play") );
// add parameter - transaction id (must be 0)
cmd.Parameters.Add( new RtmpParameter_Number(null,0) );
// add parameter -- command object, can be null
cmd.Parameters.Add( new RtmpParameter_Null(null) );
// add parameter -- stream name
cmd.Parameters.Add( new RtmpParameter_String(null,"mp3:song01.mp3") );
// add parameter - start position (ms)
cmd.Parameters.Add( new RtmpParameter_Number(null,0) );
// add parameter - duration (-1 for all of it)
cmd.Parameters.Add( new RtmpParameter_Number(null,-1) );
// add parameter - reset (do not define true or false)
cmd.Parameters.Add( new RtmpParameter_Undefined(null) ); |
To send this message, pass the message to the connection's SendMessage function.
More Information
Here are some links for further reading:
Wikipedia - RTMP
The Adobe RTMP Specification
The Adobe AMF0 Specification
The Adobe AMF3 Specification
NOTEBOOK SUPPORT
If you have any questions or suggestions, or have found an error, or would like more explanation of one
of the items in the Software Developers Notebook please contact us by email.
Software notebook support: enquiries@broccoliproducts.com
Return to the home page.
|