In (non-live) streams with no metadata, the duration of a stream can
be retrieved by calling the RTMP function getStreamLength with the
playpath. The server will return a positive duration upon the request if
the duration is known, otherwise either no response or a duration of 0
will be returned.
---
 libavformat/rtmpproto.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c
index 4aaa420..ccc64b2 100644
--- a/libavformat/rtmpproto.c
+++ b/libavformat/rtmpproto.c
@@ -123,6 +123,7 @@ typedef struct RTMPContext {
     int           listen;                     ///< listen mode flag
     int           listen_timeout;             ///< listen timeout to wait for 
new connections
     int           nb_streamid;                ///< The next stream id to 
return on createStream calls
+    double        duration;                   ///< Duration of the stream in 
seconds as returned by the server (only valid if non-zero)
     char          username[50];
     char          password[50];
     char          auth_params[500];
@@ -679,6 +680,30 @@ static int gen_delete_stream(URLContext *s, RTMPContext 
*rt)
 }
 
 /**
+ * Generate 'getStreamLength' call and send it to the server. If the server
+ * knows the duration of the selected stream, it will reply with the duration
+ * in seconds.
+ */
+static int gen_get_stream_length(URLContext *s, RTMPContext *rt)
+{
+    RTMPPacket pkt;
+    uint8_t *p;
+    int ret;
+
+    if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
+                                     0, 31 + strlen(rt->playpath))) < 0)
+        return ret;
+
+    p = pkt.data;
+    ff_amf_write_string(&p, "getStreamLength");
+    ff_amf_write_number(&p, ++rt->nb_invokes);
+    ff_amf_write_null(&p);
+    ff_amf_write_string(&p, rt->playpath);
+
+    return rtmp_send_packet(rt, &pkt, 1);
+}
+
+/**
  * Generate client buffer time and send it to the server.
  */
 static int gen_buffer_time(URLContext *s, RTMPContext *rt)
@@ -2005,11 +2030,18 @@ static int handle_invoke_result(URLContext *s, 
RTMPPacket *pkt)
             if ((ret = gen_publish(s, rt)) < 0)
                 goto fail;
         } else {
+            if (rt->live != -1) {
+                if ((ret = gen_get_stream_length(s, rt)) < 0)
+                    goto fail;
+            }
             if ((ret = gen_play(s, rt)) < 0)
                 goto fail;
             if ((ret = gen_buffer_time(s, rt)) < 0)
                 goto fail;
         }
+    } else if (!strcmp(tracked_method, "getStreamLength")) {
+        // If the server does not know the duration of the stream, it will 
report 0 here.
+        rt->duration = av_int2double(AV_RB64(pkt->data + 21));
     }
 
 fail:
@@ -2617,6 +2649,7 @@ reconnect:
     rt->received_metadata = 0;
     rt->last_bytes_read = 0;
     rt->server_bw = 2500000;
+    rt->duration = 0;
 
     av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n",
            proto, path, rt->app, rt->playpath);
@@ -2675,6 +2708,54 @@ reconnect:
         if (rt->has_video) {
             rt->flv_data[4] |= FLV_HEADER_FLAG_HASVIDEO;
         }
+
+        // If we received the first packet of an A/V stream and no metadata but
+        // the server returned a valid duration, create a fake metadata packet
+        // to inform the FLV decoder about the duration.
+        if (!rt->received_metadata && rt->duration > 0) {
+            // We need to insert the metdata packet directly after the FLV
+            // header, i.e. we need to move all other already read data by the
+            // size of our fake metadata packet.
+
+            uint8_t* p;
+            // Keep old flv_data pointer
+            uint8_t* old_flv_data = rt->flv_data;
+            // Allocate a new flv_data pointer with enough space for the 
additional package
+            rt->flv_data = av_malloc(rt->flv_size + 55);
+            // Copy FLV header
+            memcpy(rt->flv_data, old_flv_data, 13);
+            // Copy remaining packets
+            memcpy(rt->flv_data + 13 + 55, old_flv_data + 13, rt->flv_size - 
13);
+            // Increase the size by the injected packet
+            rt->flv_size += 55;
+
+            p = rt->flv_data + 13;
+            bytestream_put_byte(&p, 18); // tag type META
+            bytestream_put_be24(&p, 40); // size of data part (sum of all 
parts below)
+            bytestream_put_be24(&p, 0);  // timestamp
+            bytestream_put_be32(&p, 0);  // reserved
+
+            // first event name as a string
+            bytestream_put_byte(&p, AMF_DATA_TYPE_STRING);
+            // "onMetaData" as AMF string
+            bytestream_put_be16(&p, 10);
+            bytestream_put_buffer(&p, "onMetaData", 10);
+
+            // mixed array (hash) with size and string/type/data tuples
+            bytestream_put_byte(&p, AMF_DATA_TYPE_MIXEDARRAY);
+            bytestream_put_be32(&p, 1); // metadata_count
+
+            // "duration" as AMF string
+            bytestream_put_be16(&p, 8);
+            bytestream_put_buffer(&p, "duration", 8);
+            bytestream_put_byte(&p, AMF_DATA_TYPE_NUMBER);
+            bytestream_put_be64(&p, av_double2int(rt->duration));
+
+            // Finalise object
+            bytestream_put_be16(&p, 0); // Empty string
+            bytestream_put_byte(&p, AMF_END_OF_OBJECT);
+            bytestream_put_be32(&p, 40); // size of data part (sum of all 
parts below)
+        }
     } else {
         rt->flv_size = 0;
         rt->flv_data = NULL;
-- 
2.1.1

_______________________________________________
libav-devel mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to