diff options
-rw-r--r-- | Changelog | 1 | ||||
-rw-r--r-- | libavformat/rtp_asf.c | 198 | ||||
-rw-r--r-- | libavformat/rtsp.h | 4 |
3 files changed, 202 insertions, 1 deletions
@@ -28,6 +28,7 @@ version <next>: - DivX (XSUB) subtitle encoder - nonfree libamr support for AMR-NB/WB decoding/encoding removed - Experimental AAC encoder +- RTP depacketization of ASF and RTSP from WMS servers diff --git a/libavformat/rtp_asf.c b/libavformat/rtp_asf.c index b64f470..33a4a31 100644 --- a/libavformat/rtp_asf.c +++ b/libavformat/rtp_asf.c @@ -27,11 +27,73 @@ #include <libavutil/base64.h> #include <libavutil/avstring.h> +#include <libavutil/intreadwrite.h> #include "rtp.h" #include "rtp_asf.h" #include "rtsp.h" #include "asf.h" +/** + * From MSDN 2.2.1.4, we learn that ASF data packets over RTP should not + * contain any padding. Unfortunately, the header min/max_pktsize are not + * updated (thus making min_pktsize invalid). Here, we "fix" these faulty + * min_pktsize values in the ASF file header. + * @return 0 on success, <0 on failure (currently -1). + */ +static int +rtp_asf_fix_header(uint8_t *buf, int len) +{ + uint8_t *p = buf, *end = buf + len; + + if (len < sizeof(ff_asf_guid) * 2 + 22 || + memcmp(p, ff_asf_header, sizeof(ff_asf_guid))) { + return -1; + } + p += sizeof(ff_asf_guid) + 14; + do { + uint64_t chunksize = AV_RL64(p + sizeof(ff_asf_guid)); + if (memcmp(p, ff_asf_file_header, sizeof(ff_asf_guid))) { + if (chunksize > end - p) + return -1; + p += chunksize; + continue; + } + + /* skip most of the file header, to min_pktsize */ + p += 6 * 8 + 3 * 4 + sizeof(ff_asf_guid) * 2; + if (p + 8 <= end && AV_RL32(p) == AV_RL32(p + 4)) { + /* and set that to zero */ + AV_WL32(p, 0); + return 0; + } + break; + } while (end - p >= sizeof(ff_asf_guid) + 8); + + return -1; +} + +/** + * The following code is basically a buffered ByteIOContext, + * with the added benefit of returning -EAGAIN (instead of 0) + * on packet boundaries, such that the ASF demuxer can return + * safely and resume business at the next packet. + */ +static int +packetizer_read(void *opaque, uint8_t *buf, int buf_size) +{ + return AVERROR(EAGAIN); +} + +static void +init_packetizer(ByteIOContext *pb, uint8_t *buf, int len) +{ + init_put_byte(pb, buf, len, 0, NULL, packetizer_read, NULL, NULL); + + /* this "fills" the buffer with its current content */ + pb->pos = len; + pb->buf_end = buf + len; +} + void ff_wms_parse_sdp_a_line(AVFormatContext *s, const char *p) { if (av_strstart(p, "pgmpu:data:application/vnd.ms.wms-hdr.asfv1;base64,", &p)) { @@ -41,12 +103,16 @@ void ff_wms_parse_sdp_a_line(AVFormatContext *s, const char *p) char *buf = av_mallocz(len); av_base64_decode(buf, p, len); - init_put_byte(&pb, buf, len, 0, NULL, NULL, NULL, NULL); + if (rtp_asf_fix_header(buf, len) < 0) + av_log(s, AV_LOG_ERROR, + "Failed to fix invalid RTSP-MS/ASF min_pktsize\n"); + init_packetizer(&pb, buf, len); if (rt->asf_ctx) { av_close_input_stream(rt->asf_ctx); rt->asf_ctx = NULL; } av_open_input_stream(&rt->asf_ctx, &pb, "", &asf_demuxer, NULL); + rt->asf_pb_pos = url_ftell(&pb); av_free(buf); rt->asf_ctx->pb = NULL; } @@ -79,12 +145,142 @@ asfrtp_parse_sdp_line (AVFormatContext *s, int stream_index, return 0; } +struct PayloadContext { + ByteIOContext *pktbuf, pb; + char *buf; +}; + +/** + * @return 0 when a packet was written into /p pkt, and no more data is left; + * 1 when a packet was written into /p pkt, and more packets might be left; + * <0 when not enough data was provided to return a full packet, or on error. + */ +static int +asfrtp_parse_packet (AVFormatContext *s, PayloadContext *asf, AVStream *st, + AVPacket *pkt, uint32_t *timestamp, + const uint8_t *buf, int len, int flags) +{ + ByteIOContext *pb = &asf->pb; + int res, mflags, len_off; + RTSPState *rt = s->priv_data; + + if (!rt->asf_ctx) + return -1; + + if (len > 0) { + int off, out_len; + + if (len < 4) + return -1; + + init_put_byte(pb, buf, len, 0, NULL, NULL, NULL, NULL); + mflags = get_byte(pb); + if (mflags & 0x80) + flags |= RTP_FLAG_KEY; + len_off = get_be24(pb); + if (mflags & 0x20) /**< relative timestamp */ + url_fskip(pb, 4); + if (mflags & 0x10) /**< has duration */ + url_fskip(pb, 4); + if (mflags & 0x8) /**< has location ID */ + url_fskip(pb, 4); + off = url_ftell(pb); + + av_freep(&asf->buf); + if (!(mflags & 0x40)) { + /** + * If 0x40 is not set, the len_off field specifies an offset of this + * packet's payload data in the complete (reassembled) ASF packet. + * This is used to spread one ASF packet over multiple RTP packets. + */ + if (asf->pktbuf && len_off != url_ftell(asf->pktbuf)) { + uint8_t *p; + url_close_dyn_buf(asf->pktbuf, &p); + asf->pktbuf = NULL; + av_free(p); + } + if (!len_off && !asf->pktbuf && + !(res = url_open_dyn_packet_buf(&asf->pktbuf, rt->asf_ctx->packet_size))) + return res; + if (!asf->pktbuf) + return AVERROR(EIO); + + put_buffer(asf->pktbuf, buf + off, len - off); + if (!(flags & RTP_FLAG_MARKER)) + return -1; + out_len = url_close_dyn_buf(asf->pktbuf, &asf->buf); + asf->pktbuf = NULL; + } else { + /** + * If 0x40 is set, the len_off field specifies the length of the + * next ASF packet that can be read from this payload data alone. + * This is commonly the same as the payload size, but could be + * less in case of packet splitting (i.e. multiple ASF packets in + * one RTP packet). + */ + if (len_off != len) { + av_log_missing_feature(s, + "RTSP-MS packet splitting", 1); + return -1; + } + asf->buf = av_malloc(len - off); + out_len = len - off; + memcpy(asf->buf, buf + off, len - off); + } + + init_packetizer(pb, asf->buf, out_len); + pb->pos += rt->asf_pb_pos; + pb->eof_reached = 0; + rt->asf_ctx->pb = pb; + } + + for (;;) { + int i; + + res = av_read_packet(rt->asf_ctx, pkt); + rt->asf_pb_pos = url_ftell(pb); + if (res != 0) + break; + for (i = 0; i < s->nb_streams; i++) { + if (s->streams[i]->id == rt->asf_ctx->streams[pkt->stream_index]->id) { + pkt->stream_index = i; + return 1; // FIXME: return 0 if last packet + } + } + av_free_packet(pkt); + } + + return res == 1 ? -1 : res; +} + +static PayloadContext * +asfrtp_new_context (void) +{ + return av_mallocz(sizeof(PayloadContext)); +} + +static void +asfrtp_free_context (PayloadContext *asf) +{ + if (asf->pktbuf) { + uint8_t *p = NULL; + url_close_dyn_buf(asf->pktbuf, &p); + asf->pktbuf = NULL; + av_free(p); + } + av_freep(&asf->buf); + av_free(asf); +} + #define RTP_ASF_HANDLER(n, s, t) \ RTPDynamicProtocolHandler ff_ms_rtp_ ## n ## _handler = { \ s, \ t, \ CODEC_ID_NONE, \ asfrtp_parse_sdp_line, \ + asfrtp_new_context, \ + asfrtp_free_context, \ + asfrtp_parse_packet, \ }; RTP_ASF_HANDLER(asf_pfv, "x-asf-pf", CODEC_TYPE_VIDEO); diff --git a/libavformat/rtsp.h b/libavformat/rtsp.h index 37e7ead..0772f74 100644 --- a/libavformat/rtsp.h +++ b/libavformat/rtsp.h @@ -249,6 +249,10 @@ typedef struct RTSPState { //@{ /** ASF demuxer context for the embedded ASF stream from WMS servers */ AVFormatContext *asf_ctx; + + /** cache for position of the asf demuxer, since we load a new + * data packet in the bytecontext for each incoming RTSP packet. */ + uint64_t asf_pb_pos; //@} } RTSPState; |