On Tue, 5 Jun 2012, Samuel Pitoiset wrote:
+/* protocol handler context */
+typedef struct RTMP_HTTPContext {
+ URLContext *stream; ///< HTTP stream
+ char host[256]; ///< hostname of the server
+ int port; ///< port to connect (default is 80)
+ char client_id[64]; ///< client ID used for all requests
except the first one
+ int seq; ///< sequence ID used for all requests
+ ClientState state; ///< current state
+ uint8_t *out_data; ///< output buffer
+ int out_size; ///< current output buffer size
+} RTMP_HTTPContext;
+
+static int rtmp_http_send_cmd(URLContext *h, const char *cmd,
+ const uint8_t *buf, int size)
+{
+ RTMP_HTTPContext *rt = h->priv_data;
+ char uri[2048];
+ uint8_t c;
+ int ret;
+
+ ff_url_join(uri, sizeof(uri), "http", NULL, rt->host, rt->port,
+ "/%s/%s/%d", cmd, rt->client_id, rt->seq++);
+
+ av_opt_set_bin(rt->stream->priv_data, "post_data", buf, size, 0);
+
+ /* send a new request to the server */
+ if ((ret = ff_http_do_new_request(rt->stream, uri)) < 0)
+ return ret;
+
+ /* free output buffer */
+ av_freep(&rt->out_data);
+ rt->out_size = 0;
This isn't good design. If the function takes any buffer as parameter, one
wouldn't expect it to explicitly modify the out_data/out_size parameters.
That is, either don't change them here, or don't take any buffer/size
parameters at all and just use out_data/out_size explicitly as the data to
send, too.
+ /* read the first byte which contains the polling interval */
+ if ((ret = ffurl_read(rt->stream, &c, 1)) < 0)
+ return ret;
+
+ /* client is now waiting for a server reply */
+ rt->state = STATE_WAITING;
+
+ return ret;
+}
+
+static int rtmp_http_close(URLContext *h)
+{
+ RTMP_HTTPContext *rt = h->priv_data;
+ int ret = 0;
+
+ if (rt->state > STATE_OPENED)
+ ret = rtmp_http_send_cmd(h, "close", "", 1);
+
+ av_freep(&rt->out_data);
+ ffurl_close(rt->stream);
+
+ return ret;
+}
+
+static int rtmp_http_open(URLContext *h, const char *uri, int flags)
+{
+ RTMP_HTTPContext *rt = h->priv_data;
+ char headers[1024], url[1024];
+ int ret;
+
+ av_url_split(NULL, 0, NULL, 0, rt->host, sizeof(rt->host), &rt->port,
+ NULL, 0, uri);
+
+ rt->state = STATE_START;
+ if (rt->port < 0)
+ rt->port = RTMPT_DEFAULT_PORT;
+
+ /* This is the first request that is sent to the server in order to
+ * register a client on the server and start a new session. The server
+ * replies with a unique id (usually a number) that is used by the client
+ * for all future requests.
+ * Note: the reply doesn't contain a value for the polling interval.
+ * A successful connect resets the consecutive index that is used
+ * in the URLs. */
+ ff_url_join(url, sizeof(url), "http", NULL, rt->host, rt->port, "/open/1");
+
+ /* alloc the http context */
+ if ((ret = ffurl_alloc(&rt->stream, url, AVIO_FLAG_READ_WRITE, NULL)) < 0)
+ goto fail;
+
+ /* set options */
+ snprintf(headers, sizeof(headers),
+ "Cache-Control: no-cache\r\n"
+ "Content-type: application/x-fcs\r\n");
+ av_opt_set(rt->stream->priv_data, "headers", headers, 0);
+ av_opt_set(rt->stream->priv_data, "multiple_requests", "1", 0);
+ av_opt_set_bin(rt->stream->priv_data, "post_data", "", 1, 0);
+
+ /* open the http context */
+ if ((ret = ffurl_connect(rt->stream, NULL)) < 0)
+ goto fail;
+
+ /* read the server reply which contains a unique ID */
+ for (;;) {
+ ret = ffurl_read(rt->stream, rt->client_id, sizeof(rt->client_id));
+ if (ret == AVERROR_EOF)
+ break;
+ if (ret < 0)
+ goto fail;
+ rt->client_id[ret - 1] = '\0';
+ }
This isn't right. ffurl_read could easily return one single char at a time
here, then this wouldn't work. (To test this, try modifying tcp_read in
tcp.c, set size to 1 here - this should work with all calling code.)
Also, the blind [ret - 1] = '\0' isn't too nice IMO (although librtmp does
things similiarly). Ideally I'd check the received buffer from the end and
trim out all whitespace characters, so if the buffer would happen to
contain \r\n it would work just as well.
+ /* client has now opened the http tunneling connection */
+ rt->state = STATE_OPENED;
+
+ return 0;
+
+fail:
+ rtmp_http_close(h);
+ return ret;
+}
+
+static int rtmp_http_read(URLContext *h, uint8_t *buf, int size)
+{
+ RTMP_HTTPContext *rt = h->priv_data;
+ int orig_size = size;
+ int ret, off = 0;
+
+ while (size > 0) {
+ ret = ffurl_read(rt->stream, buf + off, size);
+ if (ret < 0 && ret != AVERROR_EOF)
+ return ret;
This will make things much slower and add more latency than required.
You're only returning once you have the full 'size' amount of data. If the
stream is an audio-only 8 kHz stream, and size is 32 KB (as it ofte is
with AVIOContext), it might require 8 seconds worth of data to fill the 32
KB buffer. So the caller will not get a single byte of data until you have
filled the buffer, giving you a minimum latency of 8 seconds, and really
choppy behaviour.
If the caller wants more data, the caller will call read again, otherwise
it is ok to return as soon as you have at least 1 byte of data to return.
+
+ if (ret == AVERROR_EOF) {
+ /* client is now ready to send new requests */
+ rt->state = STATE_READY;
+
+ /* When the client has reached end of file for the last request,
+ * we have to send a new request if we have buffered data.
+ * Otherwise, we have to send an idle POST. */
+ if (rt->out_size > 0) {
+ if ((ret = rtmp_http_send_cmd(h, "send", rt->out_data,
+ rt->out_size)) < 0)
+ return ret;
+ } else {
+ if ((ret == rtmp_http_send_cmd(h, "idle", "", 1)) < 0)
+ return ret;
+ }
+ } else {
+ off += ret;
+ size -= ret;
+ }
+ }
+
+ return orig_size;
+}
+
+static int rtmp_http_write(URLContext *h, const uint8_t *buf, int size)
+{
+ RTMP_HTTPContext *rt = h->priv_data;
+ void *ptr;
+
+ ptr = av_realloc(rt->out_data, rt->out_size + size);
+ if (!ptr)
+ return AVERROR(ENOMEM);
+ rt->out_data = ptr;
+
You could save some reallocation work here by reusing the same buffer -
when clearing it, you could just set the size back to 0 but keep the old
buffer, and keep track of the allocation size in a separate variable. The
downside of this is that if the buffer ever grows excessively big, it
won't be shrunk.
+ memcpy(rt->out_data + rt->out_size, buf, size);
+ rt->out_size += size;
+
+ return size;
+}
+
+static int rtmp_http_shutdown(URLContext *h, int flags)
+{
+ RTMP_HTTPContext *rt = h->priv_data;
+ int ret;
+
+ if (rt->state == STATE_WAITING) {
+ /* Do not send a new request until the client is waiting
+ * for a server reply of the latest request. */
+ return 0;
+ }
+
+ if ((ret = rtmp_http_send_cmd(h, "send", rt->out_data, rt->out_size)) < 0)
+ return ret;
Given the way the code is written right now, is there any time except at
startup when this actually will be used? I think not - once you enter
STATE_WAITING, you'll only switch out of it in rtmp_http_read once you hit
EOF, and in both cases there, you will switch back into STATE_WAITING
(unless an error occurs).
This isn't (necessarily) an issue in itself, it's just a think which might
(or might not) allow you to simplify things further.
+
+ return ret;
+}
+
+URLProtocol ff_rtmphttp_protocol = {
+ .name = "rtmphttp",
+ .url_open = rtmp_http_open,
+ .url_read = rtmp_http_read,
+ .url_write = rtmp_http_write,
+ .url_close = rtmp_http_close,
+ .url_shutdown = rtmp_http_shutdown,
+ .priv_data_size = sizeof(RTMP_HTTPContext),
+ .flags = URL_PROTOCOL_FLAG_NETWORK,
+};
diff --git a/libavformat/rtmppkt.c b/libavformat/rtmppkt.c
index ed8e6b2..dd719e3 100644
--- a/libavformat/rtmppkt.c
+++ b/libavformat/rtmppkt.c
@@ -163,8 +163,8 @@ int ff_rtmp_packet_read(URLContext *h, RTMPPacket *p,
return size;
}
-int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt,
- int chunk_size, RTMPPacket *prev_pkt)
+int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt, int chunk_size,
+ RTMPPacket *prev_pkt, int http_tunneling)
{
uint8_t pkt_hdr[16], *p = pkt_hdr;
int mode = RTMP_PS_TWELVEBYTES;
@@ -237,6 +237,14 @@ int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt,
size++;
}
}
+
+ if (http_tunneling) {
+ /* When rtmpt is used we have to signal the URLContext that we are done
+ * writing the stream in order to send the packet in one chunk. */
+ if ((ret = ffurl_shutdown(h, AVIO_FLAG_WRITE)) < 0)
+ return ret;
+ }
+
return size;
}
I think we can make do without this change, with a bit of reasoning: In
most (all?) cases, the shutdown call will actually not be able to flush
the data, since we're still waiting for the end of the current http
request, so it will actually not be sent at the next read() call. So as
long as we do read calls regularly, we really won't need to flush it
explicitly like this almost at all. This avoids having to change this
function and all the callers of it, shrinking the patch quite a bit.
Also, a bit more of reasoning to why I think this should be ok: This code
will mostly be blocking waiting for data to read. At startup during the
handshake, the client sends a few packets (which are buffered and not sent
yet), then the client reads a reply and waits until it receives a packet
indicating the desired state change. Even if the data is buffered for
quite some time, we will read all data from the server until the client
receives the right packets, so it will flush the data at some point.
So in principle, I think you could actually skip every single
ffurl_shutdown() call here, and things would work just as well as before.
There's one exception to this reasoning, though, which will require larger
fixes within rtmpproto.c (actually this could be seen as a bug in the
current code). When writing data to the server (as in, publishing a live
stream), only rtmp_write is called, never rtmp_read. Given the way this
function is implemented right now, we never ever read from the handle
again after the initial setup.
First, the issue with the code for plain rtmp - if the server has any
message to send to us, we won't read it and act upon it. If the server
would require us to e.g. reply to some ping message regularly, we wouldn't
know about it.
For rtmpt, an initial (but insufficient) solution would be the following:
Buffer the initial byte of the response. That is, in rtmp_http_send_cmd,
after you've read the polling interval byte, you read another byte. If
this succeeds, you store it and return it immediately at the next
rtmp_http_read call. If it fails with EOF, you switch state back to the
right one.
This allows you to continuously send data, as long as the server just
replies with a 1-byte response (the polling byte) each time.
But if the server response contains more data than this, we will never be
able to send any more data until we've read the rest of the response -
since we're still in the state that we've got more data to read from the
current request until we can send the next one, and we can't send any more
data until someone actually consumes the pending input data.
I don't have a good solution off-hand for this problem right now, I'll try
to think about how to solve it cleanly, given the current urlprotocol
architecture (in other words - the minimal changes to the urlprotocol
system needed for solving this properly).
+URLProtocol ff_rtmpt_protocol = {
+ .name = "rtmpt",
+ .url_open = rtmp_open,
+ .url_read = rtmp_read,
+ .url_write = rtmp_write,
+ .url_close = rtmp_close,
+ .priv_data_size = sizeof(RTMPContext),
+ .flags = URL_PROTOCOL_FLAG_NETWORK,
+ .priv_data_class = &rtmp_class,
+};
IIRC (Anton please correct me if I misremember) you have to have a
separate, unique class for each protocol (or was that only for
demuxers/muxers?), so you need to create a rtmpt_class for this one.
// Martin
_______________________________________________
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel