diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c index 0cc702ad62..de09486dd7 100644 --- a/libavformat/rtmpproto.c +++ b/libavformat/rtmpproto.c @@ -97,6 +97,9 @@ typedef struct RTMPContext { uint32_t bytes_read; ///< number of bytes read from server uint32_t last_bytes_read; ///< number of bytes read last reported to server int skip_bytes; ///< number of bytes to skip from the input FLV stream in the next write call + int has_audio; ///< presence of audio data + int has_video; ///< presence of video data + int received_metadata; ///< Indicates if we have received metadata about the streams uint8_t flv_header[RTMP_HEADER]; ///< partial incoming flv packet header int flv_header_bytes; ///< number of initialized bytes in flv_header int nb_invokes; ///< keeps track of invoke messages @@ -2109,6 +2112,12 @@ static int append_flv_data(RTMPContext *rt, RTMPPacket *pkt, int skip) const int size = pkt->size - skip; uint32_t ts = pkt->timestamp; + if (pkt->type == RTMP_PT_AUDIO) { + rt->has_audio = 1; + } else if (pkt->type == RTMP_PT_VIDEO) { + rt->has_video = 1; + } + old_flv_size = update_offset(rt, size + 15); if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) { @@ -2141,6 +2150,38 @@ static int handle_notify(URLContext *s, RTMPPacket *pkt) &stringlen)) return AVERROR_INVALIDDATA; + if (!strcmp(commandbuffer, "onMetaData")) { + // metadata properties should be stored in a mixed array + if (bytestream2_get_byte(&gbc) == AMF_DATA_TYPE_MIXEDARRAY) { + // We have found a metaData Array so flv can determine the streams + // from this. + rt->received_metadata = 1; + // skip 32-bit max array index + bytestream2_skip(&gbc, 4); + while (bytestream2_get_bytes_left(&gbc) > 3) { + if (ff_amf_get_string(&gbc, statusmsg, sizeof(statusmsg), + &stringlen)) + return AVERROR_INVALIDDATA; + // We do not care about the content of the property (yet). + stringlen = ff_amf_tag_size(gbc.buffer, gbc.buffer_end); + if (stringlen < 0) + return AVERROR_INVALIDDATA; + bytestream2_skip(&gbc, stringlen); + + // The presence of the following properties indicates that the + // respective streams are present. + if (!strcmp(statusmsg, "videocodecid")) { + rt->has_video = 1; + } + if (!strcmp(statusmsg, "audiocodecid")) { + rt->has_audio = 1; + } + } + if (bytestream2_get_be24(&gbc) != AMF_END_OF_OBJECT) + return AVERROR_INVALIDDATA; + } + } + // Skip the @setDataFrame string and validate it is a notification if (!strcmp(commandbuffer, "@setDataFrame")) { skip = gbc.buffer - pkt->data; @@ -2571,6 +2612,9 @@ reconnect: rt->client_report_size = 1048576; rt->bytes_read = 0; + rt->has_audio = 0; + rt->has_video = 0; + rt->received_metadata = 0; rt->last_bytes_read = 0; rt->server_bw = 2500000; @@ -2610,7 +2654,27 @@ reconnect: if ((err = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) return err; rt->flv_off = 0; - memcpy(rt->flv_data, "FLV\1\5\0\0\0\011\0\0\0\0", rt->flv_size); + memcpy(rt->flv_data, "FLV\1\0\0\0\0\011\0\0\0\0", rt->flv_size); + + // Read packets until we reach the first A/V packet or read metadata. + // If there was a metadata package in front of the A/V packets, we can + // build the FLV header from this. If we do not receive any metadata, + // the FLV decoder will allocate the needed streams when their first + // audio or video packet arrives. + while (!rt->has_audio && !rt->has_video && !rt->received_metadata) { + if ((ret = get_packet(s, 0)) < 0) + return ret; + } + + // Either after we have read the metadata or (if there is none) the + // first packet of an A/V stream, we have a better knowledge about the + // streams, so set the FLV header accordingly. + if (rt->has_audio) { + rt->flv_data[4] |= FLV_HEADER_FLAG_HASAUDIO; + } + if (rt->has_video) { + rt->flv_data[4] |= FLV_HEADER_FLAG_HASVIDEO; + } } else { rt->flv_size = 0; rt->flv_data = NULL;