From 9d03cb9fc5ddf914920ab0dbe13f19a34c754966 Mon Sep 17 00:00:00 2001 From: Diego Biurrun Date: Mon, 13 Feb 2012 20:03:30 +0100 Subject: swscale: Remove some write-only variables related to alpha handling. --- libswscale/swscale_internal.h | 1 - libswscale/x86/swscale_mmx.c | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/libswscale/swscale_internal.h b/libswscale/swscale_internal.h index bc36826ea2..5e93629d54 100644 --- a/libswscale/swscale_internal.h +++ b/libswscale/swscale_internal.h @@ -378,7 +378,6 @@ typedef struct SwsContext { DECLARE_ALIGNED(8, uint64_t, u_temp); DECLARE_ALIGNED(8, uint64_t, v_temp); DECLARE_ALIGNED(8, uint64_t, y_temp); - int32_t alpMmxFilter[4 * MAX_FILTER_SIZE]; // alignment of these values is not necessary, but merely here // to maintain the same offset across x8632 and x86-64. Once we // use proper offset macros in the asm, they can be removed. diff --git a/libswscale/x86/swscale_mmx.c b/libswscale/x86/swscale_mmx.c index 764472e95e..c112cb8a6d 100644 --- a/libswscale/x86/swscale_mmx.c +++ b/libswscale/x86/swscale_mmx.c @@ -90,7 +90,6 @@ void updateMMXDitherTables(SwsContext *c, int dstY, int lumBufIndex, int chrBufI const int flags= c->flags; int16_t **lumPixBuf= c->lumPixBuf; int16_t **chrUPixBuf= c->chrUPixBuf; - int16_t **alpPixBuf= c->alpPixBuf; const int vLumBufSize= c->vLumBufSize; const int vChrBufSize= c->vChrBufSize; int16_t *vLumFilterPos= c->vLumFilterPos; @@ -99,7 +98,6 @@ void updateMMXDitherTables(SwsContext *c, int dstY, int lumBufIndex, int chrBufI int16_t *vChrFilter= c->vChrFilter; int32_t *lumMmxFilter= c->lumMmxFilter; int32_t *chrMmxFilter= c->chrMmxFilter; - int32_t av_unused *alpMmxFilter= c->alpMmxFilter; const int vLumFilterSize= c->vLumFilterSize; const int vChrFilterSize= c->vChrFilterSize; const int chrDstY= dstY>>c->chrDstVSubSample; @@ -115,7 +113,6 @@ void updateMMXDitherTables(SwsContext *c, int dstY, int lumBufIndex, int chrBufI if (dstY < dstH - 2) { const int16_t **lumSrcPtr= (const int16_t **) lumPixBuf + lumBufIndex + firstLumSrcY - lastInLumBuf + vLumBufSize; const int16_t **chrUSrcPtr= (const int16_t **) chrUPixBuf + chrBufIndex + firstChrSrcY - lastInChrBuf + vChrBufSize; - const int16_t **alpSrcPtr= (CONFIG_SWSCALE_ALPHA && alpPixBuf) ? (const int16_t **) alpPixBuf + lumBufIndex + firstLumSrcY - lastInLumBuf + vLumBufSize : NULL; int i; if (flags & SWS_ACCURATE_RND) { int s= APCK_SIZE / 8; @@ -125,12 +122,6 @@ void updateMMXDitherTables(SwsContext *c, int dstY, int lumBufIndex, int chrBufI lumMmxFilter[s*i+APCK_COEF/4 ]= lumMmxFilter[s*i+APCK_COEF/4+1]= vLumFilter[dstY*vLumFilterSize + i ] + (vLumFilterSize>1 ? vLumFilter[dstY*vLumFilterSize + i + 1]<<16 : 0); - if (CONFIG_SWSCALE_ALPHA && alpPixBuf) { - *(const void**)&alpMmxFilter[s*i ]= alpSrcPtr[i ]; - *(const void**)&alpMmxFilter[s*i+APCK_PTR2/4 ]= alpSrcPtr[i+(vLumFilterSize>1)]; - alpMmxFilter[s*i+APCK_COEF/4 ]= - alpMmxFilter[s*i+APCK_COEF/4+1]= lumMmxFilter[s*i+APCK_COEF/4 ]; - } } for (i=0; i Date: Tue, 14 Feb 2012 03:48:42 +0000 Subject: smjpegdec: implement seeking Signed-off-by: Paul B Mahol Signed-off-by: Diego Biurrun --- libavformat/smjpegdec.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libavformat/smjpegdec.c b/libavformat/smjpegdec.c index 05a92839da..7764c0f34d 100644 --- a/libavformat/smjpegdec.c +++ b/libavformat/smjpegdec.c @@ -136,9 +136,11 @@ static int smjpeg_read_packet(AVFormatContext *s, AVPacket *pkt) { SMJPEGContext *sc = s->priv_data; uint32_t dtype, ret, size, timestamp; + int64_t pos; if (s->pb->eof_reached) return AVERROR_EOF; + pos = avio_tell(s->pb); dtype = avio_rl32(s->pb); switch (dtype) { case SMJPEG_SNDD: @@ -147,6 +149,7 @@ static int smjpeg_read_packet(AVFormatContext *s, AVPacket *pkt) ret = av_get_packet(s->pb, pkt, size); pkt->stream_index = sc->audio_stream_index; pkt->pts = timestamp; + pkt->pos = pos; break; case SMJPEG_VIDD: timestamp = avio_rb32(s->pb); @@ -154,6 +157,7 @@ static int smjpeg_read_packet(AVFormatContext *s, AVPacket *pkt) ret = av_get_packet(s->pb, pkt, size); pkt->stream_index = sc->video_stream_index; pkt->pts = timestamp; + pkt->pos = pos; break; case SMJPEG_DONE: ret = AVERROR_EOF; @@ -174,4 +178,5 @@ AVInputFormat ff_smjpeg_demuxer = { .read_header = smjpeg_read_header, .read_packet = smjpeg_read_packet, .extensions = "mjpg", + .flags = AVFMT_GENERIC_INDEX, }; -- cgit v1.2.3 From df6050188c5bfa4dd7eed62a662506187810249b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 14 Feb 2012 11:10:52 +0200 Subject: movenc: Use defines instead of hardcoded numbers for RTCP types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Storsjö --- libavformat/movenchint.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libavformat/movenchint.c b/libavformat/movenchint.c index c2025c0af9..f3eb0f20d5 100644 --- a/libavformat/movenchint.c +++ b/libavformat/movenchint.c @@ -24,6 +24,7 @@ #include "internal.h" #include "rtpenc_chain.h" #include "avio_internal.h" +#include "rtp.h" int ff_mov_init_hinting(AVFormatContext *s, int index, int src_index) { @@ -332,7 +333,7 @@ static int write_hint_packets(AVIOContext *out, const uint8_t *data, size -= 4; if (packet_len > size || packet_len <= 12) break; - if (data[1] >= 200 && data[1] <= 204) { + if (data[1] >= RTCP_SR && data[1] <= RTCP_APP) { /* RTCP packet, just skip */ data += packet_len; size -= packet_len; -- cgit v1.2.3 From c3b57d6e76cd3df87652639405505b7de5746ca8 Mon Sep 17 00:00:00 2001 From: Diego Biurrun Date: Mon, 13 Feb 2012 19:37:25 +0100 Subject: librtmp: Add "lib" prefix to librtmp URLProtocol declarations. This allows easily differentiating between both implementations within the build system and combining the native implementation for plain RTMP with librtmp for the RTMPE, RTMPS, RTMPT, RTMPTE protocol variants. --- configure | 10 ++++++---- libavformat/Makefile | 8 ++------ libavformat/allformats.c | 15 ++++++++------- libavformat/librtmp.c | 10 +++++----- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/configure b/configure index d816389fe0..5c0e56c4c0 100755 --- a/configure +++ b/configure @@ -1501,13 +1501,15 @@ httpproxy_protocol_select="tcp_protocol" http_protocol_deps="network" http_protocol_select="tcp_protocol" https_protocol_select="tls_protocol" +librtmp_protocol_deps="librtmp" +librtmpe_protocol_deps="librtmp" +librtmps_protocol_deps="librtmp" +librtmpt_protocol_deps="librtmp" +librtmpte_protocol_deps="librtmp" mmsh_protocol_select="http_protocol" mmst_protocol_deps="network" +rtmp_protocol_deps="!librtmp_protocol" rtmp_protocol_select="tcp_protocol" -rtmpe_protocol_deps="librtmp" -rtmps_protocol_deps="librtmp" -rtmpt_protocol_deps="librtmp" -rtmpte_protocol_deps="librtmp" rtp_protocol_select="udp_protocol" tcp_protocol_deps="network" tls_protocol_deps_any="openssl gnutls" diff --git a/libavformat/Makefile b/libavformat/Makefile index 0d6cb91e49..a465b2f44e 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -323,6 +323,7 @@ OBJS-$(CONFIG_YUV4MPEGPIPE_DEMUXER) += yuv4mpeg.o # external libraries OBJS-$(CONFIG_LIBNUT_DEMUXER) += libnut.o OBJS-$(CONFIG_LIBNUT_MUXER) += libnut.o +OBJS-$(CONFIG_LIBRTMP) += librtmp.o # protocols I/O OBJS+= avio.o aviobuf.o @@ -339,12 +340,7 @@ OBJS-$(CONFIG_MMSH_PROTOCOL) += mmsh.o mms.o asf.o OBJS-$(CONFIG_MMST_PROTOCOL) += mmst.o mms.o asf.o OBJS-$(CONFIG_MD5_PROTOCOL) += md5proto.o OBJS-$(CONFIG_PIPE_PROTOCOL) += file.o - -# external or internal rtmp -RTMP-OBJS-$(CONFIG_LIBRTMP) = librtmp.o -RTMP-OBJS-$(!CONFIG_LIBRTMP) = rtmpproto.o rtmppkt.o -OBJS-$(CONFIG_RTMP_PROTOCOL) += $(RTMP-OBJS-yes) - +OBJS-$(CONFIG_RTMP_PROTOCOL) += rtmpproto.o rtmppkt.o OBJS-$(CONFIG_RTP_PROTOCOL) += rtpproto.o OBJS-$(CONFIG_TCP_PROTOCOL) += tcp.o OBJS-$(CONFIG_TLS_PROTOCOL) += tls.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 9a1f94d13a..9a559936f8 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -237,9 +237,6 @@ void av_register_all(void) REGISTER_DEMUXER (YOP, yop); REGISTER_MUXDEMUX (YUV4MPEGPIPE, yuv4mpegpipe); - /* external libraries */ - REGISTER_MUXDEMUX (LIBNUT, libnut); - /* protocols */ REGISTER_PROTOCOL (APPLEHTTP, applehttp); REGISTER_PROTOCOL (CONCAT, concat); @@ -254,12 +251,16 @@ void av_register_all(void) REGISTER_PROTOCOL (MD5, md5); REGISTER_PROTOCOL (PIPE, pipe); REGISTER_PROTOCOL (RTMP, rtmp); - REGISTER_PROTOCOL (RTMPE, rtmpe); - REGISTER_PROTOCOL (RTMPS, rtmps); - REGISTER_PROTOCOL (RTMPT, rtmpt); - REGISTER_PROTOCOL (RTMPTE, rtmpte); REGISTER_PROTOCOL (RTP, rtp); REGISTER_PROTOCOL (TCP, tcp); REGISTER_PROTOCOL (TLS, tls); REGISTER_PROTOCOL (UDP, udp); + + /* external libraries */ + REGISTER_MUXDEMUX (LIBNUT, libnut); + REGISTER_PROTOCOL (LIBRTMP, librtmp); + REGISTER_PROTOCOL (LIBRTMPE, librtmpe); + REGISTER_PROTOCOL (LIBRTMPS, librtmps); + REGISTER_PROTOCOL (LIBRTMPT, librtmpt); + REGISTER_PROTOCOL (LIBRTMPTE, librtmpte); } diff --git a/libavformat/librtmp.c b/libavformat/librtmp.c index 2d028b05e2..8883bbc65f 100644 --- a/libavformat/librtmp.c +++ b/libavformat/librtmp.c @@ -152,7 +152,7 @@ static int rtmp_get_file_handle(URLContext *s) return RTMP_Socket(r); } -URLProtocol ff_rtmp_protocol = { +URLProtocol ff_librtmp_protocol = { .name = "rtmp", .url_open = rtmp_open, .url_read = rtmp_read, @@ -165,7 +165,7 @@ URLProtocol ff_rtmp_protocol = { .flags = URL_PROTOCOL_FLAG_NETWORK, }; -URLProtocol ff_rtmpt_protocol = { +URLProtocol ff_librtmpt_protocol = { .name = "rtmpt", .url_open = rtmp_open, .url_read = rtmp_read, @@ -178,7 +178,7 @@ URLProtocol ff_rtmpt_protocol = { .flags = URL_PROTOCOL_FLAG_NETWORK, }; -URLProtocol ff_rtmpe_protocol = { +URLProtocol ff_librtmpe_protocol = { .name = "rtmpe", .url_open = rtmp_open, .url_read = rtmp_read, @@ -191,7 +191,7 @@ URLProtocol ff_rtmpe_protocol = { .flags = URL_PROTOCOL_FLAG_NETWORK, }; -URLProtocol ff_rtmpte_protocol = { +URLProtocol ff_librtmpte_protocol = { .name = "rtmpte", .url_open = rtmp_open, .url_read = rtmp_read, @@ -204,7 +204,7 @@ URLProtocol ff_rtmpte_protocol = { .flags = URL_PROTOCOL_FLAG_NETWORK, }; -URLProtocol ff_rtmps_protocol = { +URLProtocol ff_librtmps_protocol = { .name = "rtmps", .url_open = rtmp_open, .url_read = rtmp_read, -- cgit v1.2.3 From bf61ef2316b6b6eac815fe5ada98d2ed41086164 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 9 Feb 2012 21:19:16 +0100 Subject: rv34: use uint16_t for RV34DecContext.deblock_coefs It is used as bitfield with 16 entries. --- libavcodec/rv34.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavcodec/rv34.h b/libavcodec/rv34.h index d86b009705..76232145c5 100644 --- a/libavcodec/rv34.h +++ b/libavcodec/rv34.h @@ -110,7 +110,7 @@ typedef struct RV34DecContext{ uint16_t *cbp_luma; ///< CBP values for luma subblocks uint8_t *cbp_chroma; ///< CBP values for chroma subblocks - int *deblock_coefs; ///< deblock coefficients for each macroblock + uint16_t *deblock_coefs; ///< deblock coefficients for each macroblock /** 8x8 block available flags (for MV prediction) */ DECLARE_ALIGNED(8, uint32_t, avail_cache)[3*4]; -- cgit v1.2.3 From 29330721b0e8514f9f8b4d54be75a662a2b79e44 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Feb 2012 21:14:19 +0100 Subject: rv34: use AVERROR return values in ff_rv34_decode_frame() Also adds an error message. --- libavcodec/rv34.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libavcodec/rv34.c b/libavcodec/rv34.c index e6af0793d3..7683a0ffdf 100644 --- a/libavcodec/rv34.c +++ b/libavcodec/rv34.c @@ -1652,15 +1652,19 @@ int ff_rv34_decode_frame(AVCodecContext *avctx, if(get_slice_offset(avctx, slices_hdr, 0) < 0 || get_slice_offset(avctx, slices_hdr, 0) > buf_size){ av_log(avctx, AV_LOG_ERROR, "Slice offset is invalid\n"); - return -1; + return AVERROR_INVALIDDATA; } init_get_bits(&s->gb, buf+get_slice_offset(avctx, slices_hdr, 0), (buf_size-get_slice_offset(avctx, slices_hdr, 0))*8); if(r->parse_slice_header(r, &r->s.gb, &si) < 0 || si.start){ av_log(avctx, AV_LOG_ERROR, "First slice header is incorrect\n"); - return -1; + return AVERROR_INVALIDDATA; + } + if ((!s->last_picture_ptr || !s->last_picture_ptr->f.data[0]) && + si.type == AV_PICTURE_TYPE_B) { + av_log(avctx, AV_LOG_ERROR, "Invalid decoder state: B-frame without " + "reference data.\n"); + return AVERROR_INVALIDDATA; } - if ((!s->last_picture_ptr || !s->last_picture_ptr->f.data[0]) && si.type == AV_PICTURE_TYPE_B) - return -1; if( (avctx->skip_frame >= AVDISCARD_NONREF && si.type==AV_PICTURE_TYPE_B) || (avctx->skip_frame >= AVDISCARD_NONKEY && si.type!=AV_PICTURE_TYPE_I) || avctx->skip_frame >= AVDISCARD_ALL) -- cgit v1.2.3 From b54b40b33cd77fb135ae678c7726a1f8636e4b3e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Feb 2012 22:12:54 +0100 Subject: rv40: prevent undefined signed overflow in rv40_loop_filter() --- libavcodec/rv40.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libavcodec/rv40.c b/libavcodec/rv40.c index c55a07a7d0..04cc4bcfa9 100644 --- a/libavcodec/rv40.c +++ b/libavcodec/rv40.c @@ -357,7 +357,7 @@ static void rv40_loop_filter(RV34DecContext *r, int row) * in addition to the coded ones because because they lie at the edge of * 8x8 block with different enough motion vectors */ - int mvmasks[4]; + unsigned mvmasks[4]; mb_pos = row * s->mb_stride; for(mb_x = 0; mb_x < s->mb_width; mb_x++, mb_pos++){ @@ -373,7 +373,8 @@ static void rv40_loop_filter(RV34DecContext *r, int row) int c_v_deblock[2], c_h_deblock[2]; int clip_left; int avail[4]; - int y_to_deblock, c_to_deblock[2]; + unsigned y_to_deblock; + int c_to_deblock[2]; q = s->current_picture_ptr->f.qscale_table[mb_pos]; alpha = rv40_alpha_tab[q]; -- cgit v1.2.3 From 2bd730010da24d035639586bb13862abe36cc1b8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Feb 2012 21:10:48 +0100 Subject: rv34: handle size changes during frame multithreading Factors all context dynamic memory handling to its own functions. Fixes bug 220. --- libavcodec/rv34.c | 146 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 82 insertions(+), 64 deletions(-) diff --git a/libavcodec/rv34.c b/libavcodec/rv34.c index 7683a0ffdf..3e55bd115c 100644 --- a/libavcodec/rv34.c +++ b/libavcodec/rv34.c @@ -711,8 +711,7 @@ static inline void rv34_mc(RV34DecContext *r, const int block_type, if (HAVE_THREADS && (s->avctx->active_thread_type & FF_THREAD_FRAME)) { /* wait for the referenced mb row to be finished */ - int mb_row = FFMIN(s->mb_height - 1, - s->mb_y + ((yoff + my + 5 + 8 * height) >> 4)); + int mb_row = s->mb_y + ((yoff + my + 5 + 8 * height) >> 4); AVFrame *f = dir ? &s->next_picture_ptr->f : &s->last_picture_ptr->f; ff_thread_await_progress(f, mb_row, 0); } @@ -1361,6 +1360,53 @@ static int check_slice_end(RV34DecContext *r, MpegEncContext *s) return 0; } + +static void rv34_decoder_free(RV34DecContext *r) +{ + av_freep(&r->intra_types_hist); + r->intra_types = NULL; + av_freep(&r->tmp_b_block_base); + av_freep(&r->mb_type); + av_freep(&r->cbp_luma); + av_freep(&r->cbp_chroma); + av_freep(&r->deblock_coefs); +} + + +static int rv34_decoder_alloc(RV34DecContext *r) +{ + r->intra_types_stride = r->s.mb_width * 4 + 4; + + r->cbp_chroma = av_malloc(r->s.mb_stride * r->s.mb_height * + sizeof(*r->cbp_chroma)); + r->cbp_luma = av_malloc(r->s.mb_stride * r->s.mb_height * + sizeof(*r->cbp_luma)); + r->deblock_coefs = av_malloc(r->s.mb_stride * r->s.mb_height * + sizeof(*r->deblock_coefs)); + r->intra_types_hist = av_malloc(r->intra_types_stride * 4 * 2 * + sizeof(*r->intra_types_hist)); + r->mb_type = av_mallocz(r->s.mb_stride * r->s.mb_height * + sizeof(*r->mb_type)); + + if (!(r->cbp_chroma && r->cbp_luma && r->deblock_coefs && + r->intra_types_hist && r->mb_type)) { + rv34_decoder_free(r); + return AVERROR(ENOMEM); + } + + r->intra_types = r->intra_types_hist + r->intra_types_stride * 4; + + return 0; +} + + +static int rv34_decoder_realloc(RV34DecContext *r) +{ + rv34_decoder_free(r); + return rv34_decoder_alloc(r); +} + + static int rv34_decode_slice(RV34DecContext *r, int end, const uint8_t* buf, int buf_size) { MpegEncContext *s = &r->s; @@ -1376,22 +1422,19 @@ static int rv34_decode_slice(RV34DecContext *r, int end, const uint8_t* buf, int } if ((s->mb_x == 0 && s->mb_y == 0) || s->current_picture_ptr==NULL) { - if(s->width != r->si.width || s->height != r->si.height){ - av_log(s->avctx, AV_LOG_DEBUG, "Changing dimensions to %dx%d\n", r->si.width,r->si.height); + if (s->width != r->si.width || s->height != r->si.height) { + int err; + + av_log(s->avctx, AV_LOG_WARNING, "Changing dimensions to %dx%d\n", + r->si.width, r->si.height); MPV_common_end(s); s->width = r->si.width; s->height = r->si.height; avcodec_set_dimensions(s->avctx, s->width, s->height); - if(MPV_common_init(s) < 0) - return -1; - r->intra_types_stride = s->mb_width*4 + 4; - r->intra_types_hist = av_realloc(r->intra_types_hist, r->intra_types_stride * 4 * 2 * sizeof(*r->intra_types_hist)); - r->intra_types = r->intra_types_hist + r->intra_types_stride * 4; - r->mb_type = av_realloc(r->mb_type, r->s.mb_stride * r->s.mb_height * sizeof(*r->mb_type)); - r->cbp_luma = av_realloc(r->cbp_luma, r->s.mb_stride * r->s.mb_height * sizeof(*r->cbp_luma)); - r->cbp_chroma = av_realloc(r->cbp_chroma, r->s.mb_stride * r->s.mb_height * sizeof(*r->cbp_chroma)); - r->deblock_coefs = av_realloc(r->deblock_coefs, r->s.mb_stride * r->s.mb_height * sizeof(*r->deblock_coefs)); - av_freep(&r->tmp_b_block_base); + if ((err = MPV_common_init(s)) < 0) + return err; + if ((err = rv34_decoder_realloc(r)) < 0) + return err; } s->pict_type = r->si.type ? r->si.type : AV_PICTURE_TYPE_I; if(MPV_frame_start(s, s->avctx) < 0) @@ -1496,6 +1539,7 @@ av_cold int ff_rv34_decode_init(AVCodecContext *avctx) { RV34DecContext *r = avctx->priv_data; MpegEncContext *s = &r->s; + int ret; MPV_decode_defaults(s); s->avctx = avctx; @@ -1512,8 +1556,8 @@ av_cold int ff_rv34_decode_init(AVCodecContext *avctx) avctx->has_b_frames = 1; s->low_delay = 0; - if (MPV_common_init(s) < 0) - return -1; + if ((ret = MPV_common_init(s)) < 0) + return ret; ff_h264_pred_init(&r->h, CODEC_ID_RV40, 8, 1); @@ -1526,15 +1570,8 @@ av_cold int ff_rv34_decode_init(AVCodecContext *avctx) ff_rv40dsp_init(&r->rdsp, &r->s.dsp); #endif - r->intra_types_stride = 4*s->mb_stride + 4; - r->intra_types_hist = av_malloc(r->intra_types_stride * 4 * 2 * sizeof(*r->intra_types_hist)); - r->intra_types = r->intra_types_hist + r->intra_types_stride * 4; - - r->mb_type = av_mallocz(r->s.mb_stride * r->s.mb_height * sizeof(*r->mb_type)); - - r->cbp_luma = av_malloc(r->s.mb_stride * r->s.mb_height * sizeof(*r->cbp_luma)); - r->cbp_chroma = av_malloc(r->s.mb_stride * r->s.mb_height * sizeof(*r->cbp_chroma)); - r->deblock_coefs = av_malloc(r->s.mb_stride * r->s.mb_height * sizeof(*r->deblock_coefs)); + if ((ret = rv34_decoder_alloc(r)) < 0) + return ret; if(!intra_vlcs[0].cbppattern[0].bits) rv34_init_tables(); @@ -1544,40 +1581,17 @@ av_cold int ff_rv34_decode_init(AVCodecContext *avctx) int ff_rv34_decode_init_thread_copy(AVCodecContext *avctx) { + int err; RV34DecContext *r = avctx->priv_data; r->s.avctx = avctx; if (avctx->internal->is_copy) { - r->cbp_chroma = av_malloc(r->s.mb_stride * r->s.mb_height * - sizeof(*r->cbp_chroma)); - r->cbp_luma = av_malloc(r->s.mb_stride * r->s.mb_height * - sizeof(*r->cbp_luma)); - r->deblock_coefs = av_malloc(r->s.mb_stride * r->s.mb_height * - sizeof(*r->deblock_coefs)); - r->intra_types_hist = av_malloc(r->intra_types_stride * 4 * 2 * - sizeof(*r->intra_types_hist)); - r->mb_type = av_malloc(r->s.mb_stride * r->s.mb_height * - sizeof(*r->mb_type)); - - if (!(r->cbp_chroma && r->cbp_luma && r->deblock_coefs && - r->intra_types_hist && r->mb_type)) { - av_freep(&r->cbp_chroma); - av_freep(&r->cbp_luma); - av_freep(&r->deblock_coefs); - av_freep(&r->intra_types_hist); - av_freep(&r->mb_type); - r->intra_types = NULL; - return AVERROR(ENOMEM); - } - - r->intra_types = r->intra_types_hist + r->intra_types_stride * 4; r->tmp_b_block_base = NULL; - - memset(r->mb_type, 0, r->s.mb_stride * r->s.mb_height * - sizeof(*r->mb_type)); - - MPV_common_init(&r->s); + if ((err = MPV_common_init(&r->s)) < 0) + return err; + if ((err = rv34_decoder_alloc(r)) < 0) + return err; } return 0; } @@ -1591,6 +1605,16 @@ int ff_rv34_decode_update_thread_context(AVCodecContext *dst, const AVCodecConte if (dst == src || !s1->context_initialized) return 0; + if (s->height != s1->height || s->width != s1->width) { + MPV_common_end(s); + s->height = s1->height; + s->width = s1->width; + if ((err = MPV_common_init(s)) < 0) + return err; + if ((err = rv34_decoder_realloc(r)) < 0) + return err; + } + if ((err = ff_mpeg_update_thread_context(dst, src))) return err; @@ -1712,11 +1736,12 @@ int ff_rv34_decode_frame(AVCodecContext *avctx, if(last && s->current_picture_ptr){ if(r->loop_filter) r->loop_filter(r, s->mb_height - 1); - if (HAVE_THREADS && (s->avctx->active_thread_type & FF_THREAD_FRAME)) - ff_thread_report_progress(&s->current_picture_ptr->f, - s->mb_height - 1, 0); ff_er_frame_end(s); MPV_frame_end(s); + + if (HAVE_THREADS && (s->avctx->active_thread_type & FF_THREAD_FRAME)) + ff_thread_report_progress(&s->current_picture_ptr->f, INT_MAX, 0); + if (s->pict_type == AV_PICTURE_TYPE_B || s->low_delay) { *pict = *(AVFrame*)s->current_picture_ptr; } else if (s->last_picture_ptr != NULL) { @@ -1737,14 +1762,7 @@ av_cold int ff_rv34_decode_end(AVCodecContext *avctx) RV34DecContext *r = avctx->priv_data; MPV_common_end(&r->s); - - av_freep(&r->intra_types_hist); - r->intra_types = NULL; - av_freep(&r->tmp_b_block_base); - av_freep(&r->mb_type); - av_freep(&r->cbp_luma); - av_freep(&r->cbp_chroma); - av_freep(&r->deblock_coefs); + rv34_decoder_free(r); return 0; } -- cgit v1.2.3 From ae5a9355744f048e14c9242142f78f7023fa357f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Mon, 23 Jan 2012 13:06:07 +0200 Subject: avconv: Don't split out inline sequence headers when stream copying VC1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is required when stream copying VC1 in ismv - there's one global header in the moov atom, but keyframes have a separate sequence header prepended. Signed-off-by: Martin Storsjö --- avconv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/avconv.c b/avconv.c index 92ffd0fd0d..862e325ad9 100644 --- a/avconv.c +++ b/avconv.c @@ -1795,6 +1795,7 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p if ( ost->st->codec->codec_id != CODEC_ID_H264 && ost->st->codec->codec_id != CODEC_ID_MPEG1VIDEO && ost->st->codec->codec_id != CODEC_ID_MPEG2VIDEO + && ost->st->codec->codec_id != CODEC_ID_VC1 ) { if (av_parser_change(ist->st->parser, ost->st->codec, &opkt.data, &opkt.size, pkt->data, pkt->size, pkt->flags & AV_PKT_FLAG_KEY)) opkt.destruct = av_destruct_packet; -- cgit v1.2.3 From 4ace130ee60163d88e998e717ec903e85d4775d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Sat, 21 Jan 2012 02:16:34 +0200 Subject: movenc: Support muxing VC1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Storsjö --- libavformat/movenc.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++- libavformat/movenc.h | 9 +++ 2 files changed, 175 insertions(+), 1 deletion(-) diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1c68a93f57..4193ec5e64 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -30,6 +30,7 @@ #include "avc.h" #include "libavcodec/get_bits.h" #include "libavcodec/put_bits.h" +#include "libavcodec/vc1.h" #include "internal.h" #include "libavutil/avstring.h" #include "libavutil/intfloat.h" @@ -421,6 +422,98 @@ static int mov_write_wave_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_dvc1_structs(MOVTrack *track, uint8_t *buf) +{ + uint8_t *unescaped; + const uint8_t *start, *next, *end = track->vos_data + track->vos_len; + int unescaped_size, seq_found = 0; + int level = 0, interlace = 0; + int packet_seq = track->vc1_info.packet_seq; + int packet_entry = track->vc1_info.packet_entry; + int slices = track->vc1_info.slices; + PutBitContext pbc; + + if (track->start_dts == AV_NOPTS_VALUE) { + /* No packets written yet, vc1_info isn't authoritative yet. */ + /* Assume inline sequence and entry headers. This will be + * overwritten at the end if the file is seekable. */ + packet_seq = packet_entry = 1; + } + + unescaped = av_mallocz(track->vos_len + FF_INPUT_BUFFER_PADDING_SIZE); + if (!unescaped) + return AVERROR(ENOMEM); + start = find_next_marker(track->vos_data, end); + for (next = start; next < end; start = next) { + GetBitContext gb; + int size; + next = find_next_marker(start + 4, end); + size = next - start - 4; + if (size <= 0) + continue; + unescaped_size = vc1_unescape_buffer(start + 4, size, unescaped); + init_get_bits(&gb, unescaped, 8 * unescaped_size); + if (AV_RB32(start) == VC1_CODE_SEQHDR) { + int profile = get_bits(&gb, 2); + if (profile != PROFILE_ADVANCED) { + av_free(unescaped); + return AVERROR(ENOSYS); + } + seq_found = 1; + level = get_bits(&gb, 3); + /* chromaformat, frmrtq_postproc, bitrtq_postproc, postprocflag, + * width, height */ + skip_bits_long(&gb, 2 + 3 + 5 + 1 + 2*12); + skip_bits(&gb, 1); /* broadcast */ + interlace = get_bits1(&gb); + skip_bits(&gb, 4); /* tfcntrflag, finterpflag, reserved, psf */ + } + } + if (!seq_found) { + av_free(unescaped); + return AVERROR(ENOSYS); + } + + init_put_bits(&pbc, buf, 7); + /* VC1DecSpecStruc */ + put_bits(&pbc, 4, 12); /* profile - advanced */ + put_bits(&pbc, 3, level); + put_bits(&pbc, 1, 0); /* reserved */ + /* VC1AdvDecSpecStruc */ + put_bits(&pbc, 3, level); + put_bits(&pbc, 1, 0); /* cbr */ + put_bits(&pbc, 6, 0); /* reserved */ + put_bits(&pbc, 1, !interlace); /* no interlace */ + put_bits(&pbc, 1, !packet_seq); /* no multiple seq */ + put_bits(&pbc, 1, !packet_entry); /* no multiple entry */ + put_bits(&pbc, 1, !slices); /* no slice code */ + put_bits(&pbc, 1, 0); /* no bframe */ + put_bits(&pbc, 1, 0); /* reserved */ + put_bits32(&pbc, track->enc->time_base.den); /* framerate */ + flush_put_bits(&pbc); + + av_free(unescaped); + + return 0; +} + +static int mov_write_dvc1_tag(AVIOContext *pb, MOVTrack *track) +{ + uint8_t buf[7] = { 0 }; + int ret; + + if ((ret = mov_write_dvc1_structs(track, buf)) < 0) + return ret; + + avio_wb32(pb, track->vos_len + 8 + sizeof(buf)); + ffio_wfourcc(pb, "dvc1"); + track->vc1_info.struct_offset = avio_tell(pb); + avio_write(pb, buf, sizeof(buf)); + avio_write(pb, track->vos_data, track->vos_len); + + return 0; +} + static int mov_write_glbl_tag(AVIOContext *pb, MOVTrack *track) { avio_wb32(pb, track->vos_len + 8); @@ -625,6 +718,7 @@ static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track) else if (track->enc->codec_id == CODEC_ID_AC3) tag = MKTAG('a','c','-','3'); else if (track->enc->codec_id == CODEC_ID_DIRAC) tag = MKTAG('d','r','a','c'); else if (track->enc->codec_id == CODEC_ID_MOV_TEXT) tag = MKTAG('t','x','3','g'); + else if (track->enc->codec_id == CODEC_ID_VC1) tag = MKTAG('v','c','-','1'); else if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) tag = MKTAG('m','p','4','v'); else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) tag = MKTAG('m','p','4','a'); @@ -911,6 +1005,8 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) mov_write_uuid_tag_ipod(pb); } else if (track->enc->field_order != AV_FIELD_UNKNOWN) mov_write_fiel_tag(pb, track); + else if (track->enc->codec_id == CODEC_ID_VC1 && track->vos_len > 0) + mov_write_dvc1_tag(pb, track); else if (track->vos_len > 0) mov_write_glbl_tag(pb, track); @@ -2502,6 +2598,63 @@ static int mov_parse_mpeg2_frame(AVPacket *pkt, uint32_t *flags) return 0; } +static void mov_parse_vc1_frame(AVPacket *pkt, MOVTrack *trk, int fragment) +{ + const uint8_t *start, *next, *end = pkt->data + pkt->size; + int seq = 0, entry = 0; + int key = pkt->flags & AV_PKT_FLAG_KEY; + start = find_next_marker(pkt->data, end); + for (next = start; next < end; start = next) { + next = find_next_marker(start + 4, end); + switch (AV_RB32(start)) { + case VC1_CODE_SEQHDR: + seq = 1; + break; + case VC1_CODE_ENTRYPOINT: + entry = 1; + break; + case VC1_CODE_SLICE: + trk->vc1_info.slices = 1; + break; + } + } + if (!trk->entry && !fragment) { + /* First packet in first fragment */ + trk->vc1_info.first_packet_seq = seq; + trk->vc1_info.first_packet_entry = entry; + } else if ((seq && !trk->vc1_info.packet_seq) || + (entry && !trk->vc1_info.packet_entry)) { + int i; + for (i = 0; i < trk->entry; i++) + trk->cluster[i].flags &= ~MOV_SYNC_SAMPLE; + trk->has_keyframes = 0; + if (seq) + trk->vc1_info.packet_seq = 1; + if (entry) + trk->vc1_info.packet_entry = 1; + if (!fragment) { + /* First fragment */ + if ((!seq || trk->vc1_info.first_packet_seq) && + (!entry || trk->vc1_info.first_packet_entry)) { + /* First packet had the same headers as this one, readd the + * sync sample flag. */ + trk->cluster[0].flags |= MOV_SYNC_SAMPLE; + trk->has_keyframes = 1; + } + } + } + if (trk->vc1_info.packet_seq && trk->vc1_info.packet_entry) + key = seq && entry; + else if (trk->vc1_info.packet_seq) + key = seq; + else if (trk->vc1_info.packet_entry) + key = entry; + if (key) { + trk->cluster[trk->entry].flags |= MOV_SYNC_SAMPLE; + trk->has_keyframes++; + } +} + static int mov_flush_fragment(AVFormatContext *s) { MOVMuxContext *mov = s->priv_data; @@ -2725,7 +2878,9 @@ static int mov_write_packet_internal(AVFormatContext *s, AVPacket *pkt) trk->flags |= MOV_TRACK_CTTS; trk->cluster[trk->entry].cts = pkt->pts - pkt->dts; trk->cluster[trk->entry].flags = 0; - if (pkt->flags & AV_PKT_FLAG_KEY) { + if (enc->codec_id == CODEC_ID_VC1) { + mov_parse_vc1_frame(pkt, trk, mov->fragments); + } else if (pkt->flags & AV_PKT_FLAG_KEY) { if (mov->mode == MODE_MOV && enc->codec_id == CODEC_ID_MPEG2VIDEO && trk->entry > 0) { // force sync sample for the first key frame mov_parse_mpeg2_frame(pkt, &trk->cluster[trk->entry].flags); @@ -3033,6 +3188,16 @@ static int mov_write_trailer(AVFormatContext *s) for (i=0; inb_streams; i++) { if (mov->tracks[i].tag == MKTAG('r','t','p',' ')) ff_mov_close_hinting(&mov->tracks[i]); + if (mov->flags & FF_MOV_FLAG_FRAGMENT && + mov->tracks[i].vc1_info.struct_offset && s->pb->seekable) { + int64_t off = avio_tell(pb); + uint8_t buf[7]; + if (mov_write_dvc1_structs(&mov->tracks[i], buf) >= 0) { + avio_seek(pb, mov->tracks[i].vc1_info.struct_offset, SEEK_SET); + avio_write(pb, buf, 7); + avio_seek(pb, off, SEEK_SET); + } + } av_freep(&mov->tracks[i].cluster); av_freep(&mov->tracks[i].frag_info); diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 5fc71fb99d..b77fc80b2f 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -120,6 +120,15 @@ typedef struct MOVIndex { int nb_frag_info; MOVFragmentInfo *frag_info; + + struct { + int64_t struct_offset; + int first_packet_seq; + int first_packet_entry; + int packet_seq; + int packet_entry; + int slices; + } vc1_info; } MOVTrack; typedef struct MOVMuxContext { -- cgit v1.2.3 From 2af3dc8698707f800f83f5fc890571a6a119866e Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Sat, 11 Feb 2012 21:30:30 +0000 Subject: ttadec: CRC checking Signed-off-by: Paul B Mahol Signed-off-by: Justin Ruggles --- libavcodec/tta.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/libavcodec/tta.c b/libavcodec/tta.c index 28d0e9b7e9..688c88d2f9 100644 --- a/libavcodec/tta.c +++ b/libavcodec/tta.c @@ -32,6 +32,7 @@ #include #include "avcodec.h" #include "get_bits.h" +#include "libavutil/crc.h" #define FORMAT_SIMPLE 1 #define FORMAT_ENCRYPTED 2 @@ -58,6 +59,7 @@ typedef struct TTAContext { AVCodecContext *avctx; AVFrame frame; GetBitContext gb; + const AVCRC *crc_table; int format, channels, bps, data_length; int frame_length, last_frame_length, total_frames; @@ -188,6 +190,20 @@ static int tta_get_unary(GetBitContext *gb) return ret; } +static int tta_check_crc(TTAContext *s, const uint8_t *buf, int buf_size) +{ + uint32_t crc, CRC; + + CRC = AV_RL32(buf + buf_size); + crc = av_crc(s->crc_table, 0xFFFFFFFFU, buf, buf_size); + if (CRC != (crc ^ 0xFFFFFFFFU)) { + av_log(s->avctx, AV_LOG_ERROR, "CRC error\n"); + return AVERROR_INVALIDDATA; + } + + return 0; +} + static av_cold int tta_decode_init(AVCodecContext * avctx) { TTAContext *s = avctx->priv_data; @@ -201,6 +217,12 @@ static av_cold int tta_decode_init(AVCodecContext * avctx) init_get_bits(&s->gb, avctx->extradata, avctx->extradata_size * 8); if (show_bits_long(&s->gb, 32) == AV_RL32("TTA1")) { + if (avctx->err_recognition & AV_EF_CRCCHECK) { + s->crc_table = av_crc_get_table(AV_CRC_32_IEEE_LE); + if (tta_check_crc(s, avctx->extradata, 18)) + return AVERROR_INVALIDDATA; + } + /* signature */ skip_bits_long(&s->gb, 32); @@ -260,6 +282,12 @@ static av_cold int tta_decode_init(AVCodecContext * avctx) s->data_length, s->frame_length, s->last_frame_length, s->total_frames); // FIXME: seek table + if (get_bits_left(&s->gb) < 32 * s->total_frames + 32) + av_log(avctx, AV_LOG_WARNING, "Seek table missing or too small\n"); + else if (avctx->err_recognition & AV_EF_CRCCHECK) { + if (tta_check_crc(s, avctx->extradata + 22, s->total_frames * 4)) + return AVERROR_INVALIDDATA; + } skip_bits_long(&s->gb, 32 * s->total_frames); skip_bits_long(&s->gb, 32); // CRC32 of seektable @@ -299,6 +327,11 @@ static int tta_decode_frame(AVCodecContext *avctx, void *data, int cur_chan = 0, framelen = s->frame_length; int32_t *p; + if (avctx->err_recognition & AV_EF_CRCCHECK) { + if (buf_size < 4 || tta_check_crc(s, buf, buf_size - 4)) + return AVERROR_INVALIDDATA; + } + init_get_bits(&s->gb, buf, buf_size*8); // FIXME: seeking -- cgit v1.2.3 From 18d1d5886bb78e4d0e11a2a0193fda765e05805d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 14 Feb 2012 15:02:30 +0100 Subject: rv30: check block type validity Prevents crashes with the fuzzed samples from bugs 88, 89 and 125 after "golomb: avoid infinite loop on all-zero input". --- libavcodec/rv30.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavcodec/rv30.c b/libavcodec/rv30.c index 4828e982b7..1bb223cd7b 100644 --- a/libavcodec/rv30.c +++ b/libavcodec/rv30.c @@ -103,7 +103,7 @@ static int rv30_decode_mb_info(RV34DecContext *r) GetBitContext *gb = &s->gb; int code = svq3_get_ue_golomb(gb); - if(code > 11){ + if (code < 0 || code > 11) { av_log(s->avctx, AV_LOG_ERROR, "Incorrect MB type code\n"); return -1; } -- cgit v1.2.3 From 08bddfcde5ebf47eec6b108e0909c70da6529331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 7 Feb 2012 16:15:26 +0200 Subject: rtpdec: Support H263 in RFC 2190 format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is different from the "modern" RTP payload formats for H263 as defined by RFC 4629, 2429 and 3555. According to the newer RFCs, this old one is to be considered deprecated and only be used for interoperating with legacy systems. Signed-off-by: Martin Storsjö --- libavformat/Makefile | 1 + libavformat/rtpdec.c | 1 + libavformat/rtpdec_formats.h | 1 + libavformat/rtpdec_h263_rfc2190.c | 184 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 libavformat/rtpdec_h263_rfc2190.c diff --git a/libavformat/Makefile b/libavformat/Makefile index a465b2f44e..cacfaf8560 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -254,6 +254,7 @@ OBJS-$(CONFIG_RTPDEC) += rdt.o \ rtpdec_asf.o \ rtpdec_g726.o \ rtpdec_h263.o \ + rtpdec_h263_rfc2190.o \ rtpdec_h264.o \ rtpdec_latm.o \ rtpdec_mpeg4.o \ diff --git a/libavformat/rtpdec.c b/libavformat/rtpdec.c index 3442c9b2b1..668755ba41 100644 --- a/libavformat/rtpdec.c +++ b/libavformat/rtpdec.c @@ -66,6 +66,7 @@ void av_register_rtp_dynamic_payload_handlers(void) ff_register_dynamic_payload_handler(&ff_amr_wb_dynamic_handler); ff_register_dynamic_payload_handler(&ff_h263_1998_dynamic_handler); ff_register_dynamic_payload_handler(&ff_h263_2000_dynamic_handler); + ff_register_dynamic_payload_handler(&ff_h263_rfc2190_dynamic_handler); ff_register_dynamic_payload_handler(&ff_h264_dynamic_handler); ff_register_dynamic_payload_handler(&ff_vorbis_dynamic_handler); ff_register_dynamic_payload_handler(&ff_theora_dynamic_handler); diff --git a/libavformat/rtpdec_formats.h b/libavformat/rtpdec_formats.h index 3074565db7..7f18ca4104 100644 --- a/libavformat/rtpdec_formats.h +++ b/libavformat/rtpdec_formats.h @@ -39,6 +39,7 @@ extern RTPDynamicProtocolHandler ff_g726_32_dynamic_handler; extern RTPDynamicProtocolHandler ff_g726_40_dynamic_handler; extern RTPDynamicProtocolHandler ff_h263_1998_dynamic_handler; extern RTPDynamicProtocolHandler ff_h263_2000_dynamic_handler; +extern RTPDynamicProtocolHandler ff_h263_rfc2190_dynamic_handler; extern RTPDynamicProtocolHandler ff_h264_dynamic_handler; extern RTPDynamicProtocolHandler ff_mp4a_latm_dynamic_handler; extern RTPDynamicProtocolHandler ff_mp4v_es_dynamic_handler; diff --git a/libavformat/rtpdec_h263_rfc2190.c b/libavformat/rtpdec_h263_rfc2190.c new file mode 100644 index 0000000000..baec6a427c --- /dev/null +++ b/libavformat/rtpdec_h263_rfc2190.c @@ -0,0 +1,184 @@ +/* + * RTP H.263 Depacketizer, RFC 2190 + * Copyright (c) 2012 Martin Storsjo + * Based on the GStreamer H.263 Depayloder: + * Copyright 2005 Wim Taymans + * Copyright 2007 Edward Hervey + * Copyright 2007 Nokia Corporation + * Copyright 2007 Collabora Ltd, Philippe Kalaf + * Copyright 2010 Mark Nauwelaerts + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "rtpdec_formats.h" +#include "libavutil/intreadwrite.h" +#include "libavcodec/get_bits.h" + +struct PayloadContext { + AVIOContext *buf; + uint8_t endbyte; + int endbyte_bits; + uint32_t timestamp; +}; + +static PayloadContext *h263_new_context(void) +{ + return av_mallocz(sizeof(PayloadContext)); +} + +static void h263_free_context(PayloadContext *data) +{ + if (!data) + return; + if (data->buf) { + uint8_t *p; + avio_close_dyn_buf(data->buf, &p); + av_free(p); + } + av_free(data); +} + +static int h263_handle_packet(AVFormatContext *ctx, PayloadContext *data, + AVStream *st, AVPacket *pkt, uint32_t *timestamp, + const uint8_t *buf, int len, int flags) +{ + int f, p, i, sbit, ebit; /* Corresponding to header fields in the RFC */ + int header_size; + + if (data->buf && data->timestamp != *timestamp) { + /* Dropping old buffered, unfinished data */ + uint8_t *p; + avio_close_dyn_buf(data->buf, &p); + av_free(p); + data->buf = NULL; + } + + if (len < 4) { + av_log(ctx, AV_LOG_ERROR, "Too short H.263 RTP packet: %d\n", len); + return AVERROR_INVALIDDATA; + } + + f = buf[0] & 0x80; + p = buf[0] & 0x40; + if (!f) { + /* Mode A */ + header_size = 4; + i = buf[1] & 0x10; + } else if (!p) { + /* Mode B */ + header_size = 8; + if (len < header_size) { + av_log(ctx, AV_LOG_ERROR, + "Too short H.263 RTP packet: %d bytes, %d header bytes\n", + len, header_size); + return AVERROR_INVALIDDATA; + } + i = buf[4] & 0x80; + } else { + /* Mode C */ + header_size = 12; + if (len < header_size) { + av_log(ctx, AV_LOG_ERROR, + "Too short H.263 RTP packet: %d bytes, %d header bytes\n", + len, header_size); + return AVERROR_INVALIDDATA; + } + i = buf[4] & 0x80; + } + sbit = (buf[0] >> 3) & 0x7; + ebit = buf[0] & 0x7; + + buf += header_size; + len -= header_size; + + if (!data->buf) { + /* Check the picture start code, only start buffering a new frame + * if this is correct */ + if (!f && len > 4 && AV_RB32(buf) >> 10 == 0x20) { + int ret = avio_open_dyn_buf(&data->buf); + if (ret < 0) + return ret; + data->timestamp = *timestamp; + } else { + /* Frame not started yet, skipping */ + return AVERROR(EAGAIN); + } + } + + if (data->endbyte_bits || sbit) { + if (data->endbyte_bits == sbit) { + data->endbyte |= buf[0] & (0xff >> sbit); + data->endbyte_bits = 0; + buf++; + len--; + avio_w8(data->buf, data->endbyte); + } else { + /* Start/end skip bits not matching - missed packets? */ + GetBitContext gb; + init_get_bits(&gb, buf, len*8 - ebit); + skip_bits(&gb, sbit); + if (data->endbyte_bits) { + data->endbyte |= get_bits(&gb, 8 - data->endbyte_bits); + avio_w8(data->buf, data->endbyte); + } + while (get_bits_left(&gb) >= 8) + avio_w8(data->buf, get_bits(&gb, 8)); + data->endbyte_bits = get_bits_left(&gb); + if (data->endbyte_bits) + data->endbyte = get_bits(&gb, data->endbyte_bits) << + (8 - data->endbyte_bits); + ebit = 0; + len = 0; + } + } + if (ebit) { + if (len > 0) + avio_write(data->buf, buf, len - 1); + data->endbyte_bits = 8 - ebit; + data->endbyte = buf[len - 1] & (0xff << ebit); + } else { + avio_write(data->buf, buf, len); + } + + if (!(flags & RTP_FLAG_MARKER)) + return AVERROR(EAGAIN); + + if (data->endbyte_bits) + avio_w8(data->buf, data->endbyte); + data->endbyte_bits = 0; + + av_init_packet(pkt); + pkt->size = avio_close_dyn_buf(data->buf, &pkt->data); + pkt->destruct = av_destruct_packet; + pkt->stream_index = st->index; + if (!i) + pkt->flags |= AV_PKT_FLAG_KEY; + data->buf = NULL; + + return 0; +} + +RTPDynamicProtocolHandler ff_h263_rfc2190_dynamic_handler = { + .codec_type = AVMEDIA_TYPE_VIDEO, + .codec_id = CODEC_ID_H263, + .parse_packet = h263_handle_packet, + .alloc = h263_new_context, + .free = h263_free_context, + .static_payload_id = 34, +}; -- cgit v1.2.3 From 2772258a986fb2da3ca5c6eb423a5024b9e9df27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 14 Feb 2012 11:47:31 +0200 Subject: libavformat: Rename the applehttp demuxer to hls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When this demuxer was created, there didn't seem to be any consensus of a common short name for this protocol. Now the consensus seems to be to call it hls. Signed-off-by: Martin Storsjö --- libavformat/Makefile | 2 +- libavformat/allformats.c | 2 +- libavformat/applehttp.c | 711 ----------------------------------------------- libavformat/hls.c | 711 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 713 insertions(+), 713 deletions(-) delete mode 100644 libavformat/applehttp.c create mode 100644 libavformat/hls.c diff --git a/libavformat/Makefile b/libavformat/Makefile index cacfaf8560..a54a8f569a 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -33,7 +33,6 @@ OBJS-$(CONFIG_AMR_MUXER) += amr.o OBJS-$(CONFIG_ANM_DEMUXER) += anm.o OBJS-$(CONFIG_APC_DEMUXER) += apc.o OBJS-$(CONFIG_APE_DEMUXER) += ape.o apetag.o -OBJS-$(CONFIG_APPLEHTTP_DEMUXER) += applehttp.o OBJS-$(CONFIG_ASF_DEMUXER) += asfdec.o asf.o asfcrypt.o \ avlanguage.o OBJS-$(CONFIG_ASF_MUXER) += asfenc.o asf.o @@ -103,6 +102,7 @@ OBJS-$(CONFIG_H263_DEMUXER) += h263dec.o rawdec.o OBJS-$(CONFIG_H263_MUXER) += rawenc.o OBJS-$(CONFIG_H264_DEMUXER) += h264dec.o rawdec.o OBJS-$(CONFIG_H264_MUXER) += rawenc.o +OBJS-$(CONFIG_HLS_DEMUXER) += hls.o OBJS-$(CONFIG_IDCIN_DEMUXER) += idcin.o OBJS-$(CONFIG_IFF_DEMUXER) += iff.o OBJS-$(CONFIG_IMAGE2_DEMUXER) += img2.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 9a559936f8..af6ad97e92 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -59,7 +59,6 @@ void av_register_all(void) REGISTER_DEMUXER (ANM, anm); REGISTER_DEMUXER (APC, apc); REGISTER_DEMUXER (APE, ape); - REGISTER_DEMUXER (APPLEHTTP, applehttp); REGISTER_MUXDEMUX (ASF, asf); REGISTER_MUXDEMUX (ASS, ass); REGISTER_MUXER (ASF_STREAM, asf_stream); @@ -104,6 +103,7 @@ void av_register_all(void) REGISTER_MUXDEMUX (H261, h261); REGISTER_MUXDEMUX (H263, h263); REGISTER_MUXDEMUX (H264, h264); + REGISTER_DEMUXER (HLS, hls); REGISTER_DEMUXER (IDCIN, idcin); REGISTER_DEMUXER (IFF, iff); REGISTER_MUXDEMUX (IMAGE2, image2); diff --git a/libavformat/applehttp.c b/libavformat/applehttp.c deleted file mode 100644 index 32a51fede1..0000000000 --- a/libavformat/applehttp.c +++ /dev/null @@ -1,711 +0,0 @@ -/* - * Apple HTTP Live Streaming demuxer - * Copyright (c) 2010 Martin Storsjo - * - * This file is part of Libav. - * - * Libav is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * Libav is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -/** - * @file - * Apple HTTP Live Streaming demuxer - * http://tools.ietf.org/html/draft-pantos-http-live-streaming - */ - -#include "libavutil/avstring.h" -#include "libavutil/intreadwrite.h" -#include "libavutil/mathematics.h" -#include "libavutil/opt.h" -#include "libavutil/dict.h" -#include "avformat.h" -#include "internal.h" -#include -#include "avio_internal.h" -#include "url.h" - -#define INITIAL_BUFFER_SIZE 32768 - -/* - * An apple http stream consists of a playlist with media segment files, - * played sequentially. There may be several playlists with the same - * video content, in different bandwidth variants, that are played in - * parallel (preferrably only one bandwidth variant at a time). In this case, - * the user supplied the url to a main playlist that only lists the variant - * playlists. - * - * If the main playlist doesn't point at any variants, we still create - * one anonymous toplevel variant for this, to maintain the structure. - */ - -enum KeyType { - KEY_NONE, - KEY_AES_128, -}; - -struct segment { - int duration; - char url[MAX_URL_SIZE]; - char key[MAX_URL_SIZE]; - enum KeyType key_type; - uint8_t iv[16]; -}; - -/* - * Each variant has its own demuxer. If it currently is active, - * it has an open AVIOContext too, and potentially an AVPacket - * containing the next packet from this stream. - */ -struct variant { - int bandwidth; - char url[MAX_URL_SIZE]; - AVIOContext pb; - uint8_t* read_buffer; - URLContext *input; - AVFormatContext *parent; - int index; - AVFormatContext *ctx; - AVPacket pkt; - int stream_offset; - - int finished; - int target_duration; - int start_seq_no; - int n_segments; - struct segment **segments; - int needed, cur_needed; - int cur_seq_no; - int64_t last_load_time; - - char key_url[MAX_URL_SIZE]; - uint8_t key[16]; -}; - -typedef struct AppleHTTPContext { - int n_variants; - struct variant **variants; - int cur_seq_no; - int end_of_segment; - int first_packet; - int64_t first_timestamp; - AVIOInterruptCB *interrupt_callback; -} AppleHTTPContext; - -static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) -{ - int len = ff_get_line(s, buf, maxlen); - while (len > 0 && isspace(buf[len - 1])) - buf[--len] = '\0'; - return len; -} - -static void free_segment_list(struct variant *var) -{ - int i; - for (i = 0; i < var->n_segments; i++) - av_free(var->segments[i]); - av_freep(&var->segments); - var->n_segments = 0; -} - -static void free_variant_list(AppleHTTPContext *c) -{ - int i; - for (i = 0; i < c->n_variants; i++) { - struct variant *var = c->variants[i]; - free_segment_list(var); - av_free_packet(&var->pkt); - av_free(var->pb.buffer); - if (var->input) - ffurl_close(var->input); - if (var->ctx) { - var->ctx->pb = NULL; - avformat_close_input(&var->ctx); - } - av_free(var); - } - av_freep(&c->variants); - c->n_variants = 0; -} - -/* - * Used to reset a statically allocated AVPacket to a clean slate, - * containing no data. - */ -static void reset_packet(AVPacket *pkt) -{ - av_init_packet(pkt); - pkt->data = NULL; -} - -static struct variant *new_variant(AppleHTTPContext *c, int bandwidth, - const char *url, const char *base) -{ - struct variant *var = av_mallocz(sizeof(struct variant)); - if (!var) - return NULL; - reset_packet(&var->pkt); - var->bandwidth = bandwidth; - ff_make_absolute_url(var->url, sizeof(var->url), base, url); - dynarray_add(&c->variants, &c->n_variants, var); - return var; -} - -struct variant_info { - char bandwidth[20]; -}; - -static void handle_variant_args(struct variant_info *info, const char *key, - int key_len, char **dest, int *dest_len) -{ - if (!strncmp(key, "BANDWIDTH=", key_len)) { - *dest = info->bandwidth; - *dest_len = sizeof(info->bandwidth); - } -} - -struct key_info { - char uri[MAX_URL_SIZE]; - char method[10]; - char iv[35]; -}; - -static void handle_key_args(struct key_info *info, const char *key, - int key_len, char **dest, int *dest_len) -{ - if (!strncmp(key, "METHOD=", key_len)) { - *dest = info->method; - *dest_len = sizeof(info->method); - } else if (!strncmp(key, "URI=", key_len)) { - *dest = info->uri; - *dest_len = sizeof(info->uri); - } else if (!strncmp(key, "IV=", key_len)) { - *dest = info->iv; - *dest_len = sizeof(info->iv); - } -} - -static int parse_playlist(AppleHTTPContext *c, const char *url, - struct variant *var, AVIOContext *in) -{ - int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; - enum KeyType key_type = KEY_NONE; - uint8_t iv[16] = ""; - int has_iv = 0; - char key[MAX_URL_SIZE] = ""; - char line[1024]; - const char *ptr; - int close_in = 0; - - if (!in) { - close_in = 1; - if ((ret = avio_open2(&in, url, AVIO_FLAG_READ, - c->interrupt_callback, NULL)) < 0) - return ret; - } - - read_chomp_line(in, line, sizeof(line)); - if (strcmp(line, "#EXTM3U")) { - ret = AVERROR_INVALIDDATA; - goto fail; - } - - if (var) { - free_segment_list(var); - var->finished = 0; - } - while (!in->eof_reached) { - read_chomp_line(in, line, sizeof(line)); - if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { - struct variant_info info = {{0}}; - is_variant = 1; - ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, - &info); - bandwidth = atoi(info.bandwidth); - } else if (av_strstart(line, "#EXT-X-KEY:", &ptr)) { - struct key_info info = {{0}}; - ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args, - &info); - key_type = KEY_NONE; - has_iv = 0; - if (!strcmp(info.method, "AES-128")) - key_type = KEY_AES_128; - if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) { - ff_hex_to_data(iv, info.iv + 2); - has_iv = 1; - } - av_strlcpy(key, info.uri, sizeof(key)); - } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { - if (!var) { - var = new_variant(c, 0, url, NULL); - if (!var) { - ret = AVERROR(ENOMEM); - goto fail; - } - } - var->target_duration = atoi(ptr); - } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { - if (!var) { - var = new_variant(c, 0, url, NULL); - if (!var) { - ret = AVERROR(ENOMEM); - goto fail; - } - } - var->start_seq_no = atoi(ptr); - } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { - if (var) - var->finished = 1; - } else if (av_strstart(line, "#EXTINF:", &ptr)) { - is_segment = 1; - duration = atoi(ptr); - } else if (av_strstart(line, "#", NULL)) { - continue; - } else if (line[0]) { - if (is_variant) { - if (!new_variant(c, bandwidth, line, url)) { - ret = AVERROR(ENOMEM); - goto fail; - } - is_variant = 0; - bandwidth = 0; - } - if (is_segment) { - struct segment *seg; - if (!var) { - var = new_variant(c, 0, url, NULL); - if (!var) { - ret = AVERROR(ENOMEM); - goto fail; - } - } - seg = av_malloc(sizeof(struct segment)); - if (!seg) { - ret = AVERROR(ENOMEM); - goto fail; - } - seg->duration = duration; - seg->key_type = key_type; - if (has_iv) { - memcpy(seg->iv, iv, sizeof(iv)); - } else { - int seq = var->start_seq_no + var->n_segments; - memset(seg->iv, 0, sizeof(seg->iv)); - AV_WB32(seg->iv + 12, seq); - } - ff_make_absolute_url(seg->key, sizeof(seg->key), url, key); - ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); - dynarray_add(&var->segments, &var->n_segments, seg); - is_segment = 0; - } - } - } - if (var) - var->last_load_time = av_gettime(); - -fail: - if (close_in) - avio_close(in); - return ret; -} - -static int open_input(struct variant *var) -{ - struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no]; - if (seg->key_type == KEY_NONE) { - return ffurl_open(&var->input, seg->url, AVIO_FLAG_READ, - &var->parent->interrupt_callback, NULL); - } else if (seg->key_type == KEY_AES_128) { - char iv[33], key[33], url[MAX_URL_SIZE]; - int ret; - if (strcmp(seg->key, var->key_url)) { - URLContext *uc; - if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ, - &var->parent->interrupt_callback, NULL) == 0) { - if (ffurl_read_complete(uc, var->key, sizeof(var->key)) - != sizeof(var->key)) { - av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n", - seg->key); - } - ffurl_close(uc); - } else { - av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n", - seg->key); - } - av_strlcpy(var->key_url, seg->key, sizeof(var->key_url)); - } - ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); - ff_data_to_hex(key, var->key, sizeof(var->key), 0); - iv[32] = key[32] = '\0'; - if (strstr(seg->url, "://")) - snprintf(url, sizeof(url), "crypto+%s", seg->url); - else - snprintf(url, sizeof(url), "crypto:%s", seg->url); - if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ, - &var->parent->interrupt_callback)) < 0) - return ret; - av_opt_set(var->input->priv_data, "key", key, 0); - av_opt_set(var->input->priv_data, "iv", iv, 0); - if ((ret = ffurl_connect(var->input, NULL)) < 0) { - ffurl_close(var->input); - var->input = NULL; - return ret; - } - return 0; - } - return AVERROR(ENOSYS); -} - -static int read_data(void *opaque, uint8_t *buf, int buf_size) -{ - struct variant *v = opaque; - AppleHTTPContext *c = v->parent->priv_data; - int ret, i; - -restart: - if (!v->input) { - /* If this is a live stream and the reload interval has elapsed since - * the last playlist reload, reload the variant playlists now. */ - int64_t reload_interval = v->n_segments > 0 ? - v->segments[v->n_segments - 1]->duration : - v->target_duration; - reload_interval *= 1000000; - -reload: - if (!v->finished && - av_gettime() - v->last_load_time >= reload_interval) { - if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) - return ret; - /* If we need to reload the playlist again below (if - * there's still no more segments), switch to a reload - * interval of half the target duration. */ - reload_interval = v->target_duration * 500000; - } - if (v->cur_seq_no < v->start_seq_no) { - av_log(NULL, AV_LOG_WARNING, - "skipping %d segments ahead, expired from playlists\n", - v->start_seq_no - v->cur_seq_no); - v->cur_seq_no = v->start_seq_no; - } - if (v->cur_seq_no >= v->start_seq_no + v->n_segments) { - if (v->finished) - return AVERROR_EOF; - while (av_gettime() - v->last_load_time < reload_interval) { - if (ff_check_interrupt(c->interrupt_callback)) - return AVERROR_EXIT; - usleep(100*1000); - } - /* Enough time has elapsed since the last reload */ - goto reload; - } - - ret = open_input(v); - if (ret < 0) - return ret; - } - ret = ffurl_read(v->input, buf, buf_size); - if (ret > 0) - return ret; - if (ret < 0 && ret != AVERROR_EOF) - return ret; - ffurl_close(v->input); - v->input = NULL; - v->cur_seq_no++; - - c->end_of_segment = 1; - c->cur_seq_no = v->cur_seq_no; - - if (v->ctx && v->ctx->nb_streams) { - v->needed = 0; - for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; - i++) { - if (v->parent->streams[i]->discard < AVDISCARD_ALL) - v->needed = 1; - } - } - if (!v->needed) { - av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n", - v->index); - return AVERROR_EOF; - } - goto restart; -} - -static int applehttp_read_header(AVFormatContext *s) -{ - AppleHTTPContext *c = s->priv_data; - int ret = 0, i, j, stream_offset = 0; - - c->interrupt_callback = &s->interrupt_callback; - - if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0) - goto fail; - - if (c->n_variants == 0) { - av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); - ret = AVERROR_EOF; - goto fail; - } - /* If the playlist only contained variants, parse each individual - * variant playlist. */ - if (c->n_variants > 1 || c->variants[0]->n_segments == 0) { - for (i = 0; i < c->n_variants; i++) { - struct variant *v = c->variants[i]; - if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) - goto fail; - } - } - - if (c->variants[0]->n_segments == 0) { - av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); - ret = AVERROR_EOF; - goto fail; - } - - /* If this isn't a live stream, calculate the total duration of the - * stream. */ - if (c->variants[0]->finished) { - int64_t duration = 0; - for (i = 0; i < c->variants[0]->n_segments; i++) - duration += c->variants[0]->segments[i]->duration; - s->duration = duration * AV_TIME_BASE; - } - - /* Open the demuxer for each variant */ - for (i = 0; i < c->n_variants; i++) { - struct variant *v = c->variants[i]; - AVInputFormat *in_fmt = NULL; - char bitrate_str[20]; - if (v->n_segments == 0) - continue; - - if (!(v->ctx = avformat_alloc_context())) { - ret = AVERROR(ENOMEM); - goto fail; - } - - v->index = i; - v->needed = 1; - v->parent = s; - - /* If this is a live stream with more than 3 segments, start at the - * third last segment. */ - v->cur_seq_no = v->start_seq_no; - if (!v->finished && v->n_segments > 3) - v->cur_seq_no = v->start_seq_no + v->n_segments - 3; - - v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); - ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v, - read_data, NULL, NULL); - v->pb.seekable = 0; - ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url, - NULL, 0, 0); - if (ret < 0) { - /* Free the ctx - it isn't initialized properly at this point, - * so avformat_close_input shouldn't be called. If - * avformat_open_input fails below, it frees and zeros the - * context, so it doesn't need any special treatment like this. */ - avformat_free_context(v->ctx); - v->ctx = NULL; - goto fail; - } - v->ctx->pb = &v->pb; - ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL); - if (ret < 0) - goto fail; - v->stream_offset = stream_offset; - snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth); - /* Create new AVStreams for each stream in this variant */ - for (j = 0; j < v->ctx->nb_streams; j++) { - AVStream *st = avformat_new_stream(s, NULL); - if (!st) { - ret = AVERROR(ENOMEM); - goto fail; - } - st->id = i; - avcodec_copy_context(st->codec, v->ctx->streams[j]->codec); - if (v->bandwidth) - av_dict_set(&st->metadata, "variant_bitrate", bitrate_str, - 0); - } - stream_offset += v->ctx->nb_streams; - } - - c->first_packet = 1; - c->first_timestamp = AV_NOPTS_VALUE; - - return 0; -fail: - free_variant_list(c); - return ret; -} - -static int recheck_discard_flags(AVFormatContext *s, int first) -{ - AppleHTTPContext *c = s->priv_data; - int i, changed = 0; - - /* Check if any new streams are needed */ - for (i = 0; i < c->n_variants; i++) - c->variants[i]->cur_needed = 0;; - - for (i = 0; i < s->nb_streams; i++) { - AVStream *st = s->streams[i]; - struct variant *var = c->variants[s->streams[i]->id]; - if (st->discard < AVDISCARD_ALL) - var->cur_needed = 1; - } - for (i = 0; i < c->n_variants; i++) { - struct variant *v = c->variants[i]; - if (v->cur_needed && !v->needed) { - v->needed = 1; - changed = 1; - v->cur_seq_no = c->cur_seq_no; - v->pb.eof_reached = 0; - av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i); - } else if (first && !v->cur_needed && v->needed) { - if (v->input) - ffurl_close(v->input); - v->input = NULL; - v->needed = 0; - changed = 1; - av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i); - } - } - return changed; -} - -static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt) -{ - AppleHTTPContext *c = s->priv_data; - int ret, i, minvariant = -1; - - if (c->first_packet) { - recheck_discard_flags(s, 1); - c->first_packet = 0; - } - -start: - c->end_of_segment = 0; - for (i = 0; i < c->n_variants; i++) { - struct variant *var = c->variants[i]; - /* Make sure we've got one buffered packet from each open variant - * stream */ - if (var->needed && !var->pkt.data) { - ret = av_read_frame(var->ctx, &var->pkt); - if (ret < 0) { - if (!var->pb.eof_reached) - return ret; - reset_packet(&var->pkt); - } else { - if (c->first_timestamp == AV_NOPTS_VALUE) - c->first_timestamp = var->pkt.dts; - } - } - /* Check if this stream has the packet with the lowest dts */ - if (var->pkt.data) { - if (minvariant < 0 || - var->pkt.dts < c->variants[minvariant]->pkt.dts) - minvariant = i; - } - } - if (c->end_of_segment) { - if (recheck_discard_flags(s, 0)) - goto start; - } - /* If we got a packet, return it */ - if (minvariant >= 0) { - *pkt = c->variants[minvariant]->pkt; - pkt->stream_index += c->variants[minvariant]->stream_offset; - reset_packet(&c->variants[minvariant]->pkt); - return 0; - } - return AVERROR_EOF; -} - -static int applehttp_close(AVFormatContext *s) -{ - AppleHTTPContext *c = s->priv_data; - - free_variant_list(c); - return 0; -} - -static int applehttp_read_seek(AVFormatContext *s, int stream_index, - int64_t timestamp, int flags) -{ - AppleHTTPContext *c = s->priv_data; - int i, j, ret; - - if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) - return AVERROR(ENOSYS); - - timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ? - s->streams[stream_index]->time_base.den : - AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ? - AV_ROUND_DOWN : AV_ROUND_UP); - ret = AVERROR(EIO); - for (i = 0; i < c->n_variants; i++) { - /* Reset reading */ - struct variant *var = c->variants[i]; - int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? 0 : - av_rescale_rnd(c->first_timestamp, 1, - stream_index >= 0 ? s->streams[stream_index]->time_base.den : AV_TIME_BASE, - flags & AVSEEK_FLAG_BACKWARD ? AV_ROUND_DOWN : AV_ROUND_UP); - if (var->input) { - ffurl_close(var->input); - var->input = NULL; - } - av_free_packet(&var->pkt); - reset_packet(&var->pkt); - var->pb.eof_reached = 0; - - /* Locate the segment that contains the target timestamp */ - for (j = 0; j < var->n_segments; j++) { - if (timestamp >= pos && - timestamp < pos + var->segments[j]->duration) { - var->cur_seq_no = var->start_seq_no + j; - ret = 0; - break; - } - pos += var->segments[j]->duration; - } - } - return ret; -} - -static int applehttp_probe(AVProbeData *p) -{ - /* Require #EXTM3U at the start, and either one of the ones below - * somewhere for a proper match. */ - if (strncmp(p->buf, "#EXTM3U", 7)) - return 0; - if (strstr(p->buf, "#EXT-X-STREAM-INF:") || - strstr(p->buf, "#EXT-X-TARGETDURATION:") || - strstr(p->buf, "#EXT-X-MEDIA-SEQUENCE:")) - return AVPROBE_SCORE_MAX; - return 0; -} - -AVInputFormat ff_applehttp_demuxer = { - .name = "applehttp", - .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"), - .priv_data_size = sizeof(AppleHTTPContext), - .read_probe = applehttp_probe, - .read_header = applehttp_read_header, - .read_packet = applehttp_read_packet, - .read_close = applehttp_close, - .read_seek = applehttp_read_seek, -}; diff --git a/libavformat/hls.c b/libavformat/hls.c new file mode 100644 index 0000000000..13b9b27378 --- /dev/null +++ b/libavformat/hls.c @@ -0,0 +1,711 @@ +/* + * Apple HTTP Live Streaming demuxer + * Copyright (c) 2010 Martin Storsjo + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Apple HTTP Live Streaming demuxer + * http://tools.ietf.org/html/draft-pantos-http-live-streaming + */ + +#include "libavutil/avstring.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "libavutil/dict.h" +#include "avformat.h" +#include "internal.h" +#include +#include "avio_internal.h" +#include "url.h" + +#define INITIAL_BUFFER_SIZE 32768 + +/* + * An apple http stream consists of a playlist with media segment files, + * played sequentially. There may be several playlists with the same + * video content, in different bandwidth variants, that are played in + * parallel (preferrably only one bandwidth variant at a time). In this case, + * the user supplied the url to a main playlist that only lists the variant + * playlists. + * + * If the main playlist doesn't point at any variants, we still create + * one anonymous toplevel variant for this, to maintain the structure. + */ + +enum KeyType { + KEY_NONE, + KEY_AES_128, +}; + +struct segment { + int duration; + char url[MAX_URL_SIZE]; + char key[MAX_URL_SIZE]; + enum KeyType key_type; + uint8_t iv[16]; +}; + +/* + * Each variant has its own demuxer. If it currently is active, + * it has an open AVIOContext too, and potentially an AVPacket + * containing the next packet from this stream. + */ +struct variant { + int bandwidth; + char url[MAX_URL_SIZE]; + AVIOContext pb; + uint8_t* read_buffer; + URLContext *input; + AVFormatContext *parent; + int index; + AVFormatContext *ctx; + AVPacket pkt; + int stream_offset; + + int finished; + int target_duration; + int start_seq_no; + int n_segments; + struct segment **segments; + int needed, cur_needed; + int cur_seq_no; + int64_t last_load_time; + + char key_url[MAX_URL_SIZE]; + uint8_t key[16]; +}; + +typedef struct AppleHTTPContext { + int n_variants; + struct variant **variants; + int cur_seq_no; + int end_of_segment; + int first_packet; + int64_t first_timestamp; + AVIOInterruptCB *interrupt_callback; +} AppleHTTPContext; + +static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) +{ + int len = ff_get_line(s, buf, maxlen); + while (len > 0 && isspace(buf[len - 1])) + buf[--len] = '\0'; + return len; +} + +static void free_segment_list(struct variant *var) +{ + int i; + for (i = 0; i < var->n_segments; i++) + av_free(var->segments[i]); + av_freep(&var->segments); + var->n_segments = 0; +} + +static void free_variant_list(AppleHTTPContext *c) +{ + int i; + for (i = 0; i < c->n_variants; i++) { + struct variant *var = c->variants[i]; + free_segment_list(var); + av_free_packet(&var->pkt); + av_free(var->pb.buffer); + if (var->input) + ffurl_close(var->input); + if (var->ctx) { + var->ctx->pb = NULL; + avformat_close_input(&var->ctx); + } + av_free(var); + } + av_freep(&c->variants); + c->n_variants = 0; +} + +/* + * Used to reset a statically allocated AVPacket to a clean slate, + * containing no data. + */ +static void reset_packet(AVPacket *pkt) +{ + av_init_packet(pkt); + pkt->data = NULL; +} + +static struct variant *new_variant(AppleHTTPContext *c, int bandwidth, + const char *url, const char *base) +{ + struct variant *var = av_mallocz(sizeof(struct variant)); + if (!var) + return NULL; + reset_packet(&var->pkt); + var->bandwidth = bandwidth; + ff_make_absolute_url(var->url, sizeof(var->url), base, url); + dynarray_add(&c->variants, &c->n_variants, var); + return var; +} + +struct variant_info { + char bandwidth[20]; +}; + +static void handle_variant_args(struct variant_info *info, const char *key, + int key_len, char **dest, int *dest_len) +{ + if (!strncmp(key, "BANDWIDTH=", key_len)) { + *dest = info->bandwidth; + *dest_len = sizeof(info->bandwidth); + } +} + +struct key_info { + char uri[MAX_URL_SIZE]; + char method[10]; + char iv[35]; +}; + +static void handle_key_args(struct key_info *info, const char *key, + int key_len, char **dest, int *dest_len) +{ + if (!strncmp(key, "METHOD=", key_len)) { + *dest = info->method; + *dest_len = sizeof(info->method); + } else if (!strncmp(key, "URI=", key_len)) { + *dest = info->uri; + *dest_len = sizeof(info->uri); + } else if (!strncmp(key, "IV=", key_len)) { + *dest = info->iv; + *dest_len = sizeof(info->iv); + } +} + +static int parse_playlist(AppleHTTPContext *c, const char *url, + struct variant *var, AVIOContext *in) +{ + int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; + enum KeyType key_type = KEY_NONE; + uint8_t iv[16] = ""; + int has_iv = 0; + char key[MAX_URL_SIZE] = ""; + char line[1024]; + const char *ptr; + int close_in = 0; + + if (!in) { + close_in = 1; + if ((ret = avio_open2(&in, url, AVIO_FLAG_READ, + c->interrupt_callback, NULL)) < 0) + return ret; + } + + read_chomp_line(in, line, sizeof(line)); + if (strcmp(line, "#EXTM3U")) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + + if (var) { + free_segment_list(var); + var->finished = 0; + } + while (!in->eof_reached) { + read_chomp_line(in, line, sizeof(line)); + if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { + struct variant_info info = {{0}}; + is_variant = 1; + ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, + &info); + bandwidth = atoi(info.bandwidth); + } else if (av_strstart(line, "#EXT-X-KEY:", &ptr)) { + struct key_info info = {{0}}; + ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args, + &info); + key_type = KEY_NONE; + has_iv = 0; + if (!strcmp(info.method, "AES-128")) + key_type = KEY_AES_128; + if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) { + ff_hex_to_data(iv, info.iv + 2); + has_iv = 1; + } + av_strlcpy(key, info.uri, sizeof(key)); + } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { + if (!var) { + var = new_variant(c, 0, url, NULL); + if (!var) { + ret = AVERROR(ENOMEM); + goto fail; + } + } + var->target_duration = atoi(ptr); + } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { + if (!var) { + var = new_variant(c, 0, url, NULL); + if (!var) { + ret = AVERROR(ENOMEM); + goto fail; + } + } + var->start_seq_no = atoi(ptr); + } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { + if (var) + var->finished = 1; + } else if (av_strstart(line, "#EXTINF:", &ptr)) { + is_segment = 1; + duration = atoi(ptr); + } else if (av_strstart(line, "#", NULL)) { + continue; + } else if (line[0]) { + if (is_variant) { + if (!new_variant(c, bandwidth, line, url)) { + ret = AVERROR(ENOMEM); + goto fail; + } + is_variant = 0; + bandwidth = 0; + } + if (is_segment) { + struct segment *seg; + if (!var) { + var = new_variant(c, 0, url, NULL); + if (!var) { + ret = AVERROR(ENOMEM); + goto fail; + } + } + seg = av_malloc(sizeof(struct segment)); + if (!seg) { + ret = AVERROR(ENOMEM); + goto fail; + } + seg->duration = duration; + seg->key_type = key_type; + if (has_iv) { + memcpy(seg->iv, iv, sizeof(iv)); + } else { + int seq = var->start_seq_no + var->n_segments; + memset(seg->iv, 0, sizeof(seg->iv)); + AV_WB32(seg->iv + 12, seq); + } + ff_make_absolute_url(seg->key, sizeof(seg->key), url, key); + ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); + dynarray_add(&var->segments, &var->n_segments, seg); + is_segment = 0; + } + } + } + if (var) + var->last_load_time = av_gettime(); + +fail: + if (close_in) + avio_close(in); + return ret; +} + +static int open_input(struct variant *var) +{ + struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no]; + if (seg->key_type == KEY_NONE) { + return ffurl_open(&var->input, seg->url, AVIO_FLAG_READ, + &var->parent->interrupt_callback, NULL); + } else if (seg->key_type == KEY_AES_128) { + char iv[33], key[33], url[MAX_URL_SIZE]; + int ret; + if (strcmp(seg->key, var->key_url)) { + URLContext *uc; + if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ, + &var->parent->interrupt_callback, NULL) == 0) { + if (ffurl_read_complete(uc, var->key, sizeof(var->key)) + != sizeof(var->key)) { + av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n", + seg->key); + } + ffurl_close(uc); + } else { + av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n", + seg->key); + } + av_strlcpy(var->key_url, seg->key, sizeof(var->key_url)); + } + ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); + ff_data_to_hex(key, var->key, sizeof(var->key), 0); + iv[32] = key[32] = '\0'; + if (strstr(seg->url, "://")) + snprintf(url, sizeof(url), "crypto+%s", seg->url); + else + snprintf(url, sizeof(url), "crypto:%s", seg->url); + if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ, + &var->parent->interrupt_callback)) < 0) + return ret; + av_opt_set(var->input->priv_data, "key", key, 0); + av_opt_set(var->input->priv_data, "iv", iv, 0); + if ((ret = ffurl_connect(var->input, NULL)) < 0) { + ffurl_close(var->input); + var->input = NULL; + return ret; + } + return 0; + } + return AVERROR(ENOSYS); +} + +static int read_data(void *opaque, uint8_t *buf, int buf_size) +{ + struct variant *v = opaque; + AppleHTTPContext *c = v->parent->priv_data; + int ret, i; + +restart: + if (!v->input) { + /* If this is a live stream and the reload interval has elapsed since + * the last playlist reload, reload the variant playlists now. */ + int64_t reload_interval = v->n_segments > 0 ? + v->segments[v->n_segments - 1]->duration : + v->target_duration; + reload_interval *= 1000000; + +reload: + if (!v->finished && + av_gettime() - v->last_load_time >= reload_interval) { + if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) + return ret; + /* If we need to reload the playlist again below (if + * there's still no more segments), switch to a reload + * interval of half the target duration. */ + reload_interval = v->target_duration * 500000; + } + if (v->cur_seq_no < v->start_seq_no) { + av_log(NULL, AV_LOG_WARNING, + "skipping %d segments ahead, expired from playlists\n", + v->start_seq_no - v->cur_seq_no); + v->cur_seq_no = v->start_seq_no; + } + if (v->cur_seq_no >= v->start_seq_no + v->n_segments) { + if (v->finished) + return AVERROR_EOF; + while (av_gettime() - v->last_load_time < reload_interval) { + if (ff_check_interrupt(c->interrupt_callback)) + return AVERROR_EXIT; + usleep(100*1000); + } + /* Enough time has elapsed since the last reload */ + goto reload; + } + + ret = open_input(v); + if (ret < 0) + return ret; + } + ret = ffurl_read(v->input, buf, buf_size); + if (ret > 0) + return ret; + if (ret < 0 && ret != AVERROR_EOF) + return ret; + ffurl_close(v->input); + v->input = NULL; + v->cur_seq_no++; + + c->end_of_segment = 1; + c->cur_seq_no = v->cur_seq_no; + + if (v->ctx && v->ctx->nb_streams) { + v->needed = 0; + for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; + i++) { + if (v->parent->streams[i]->discard < AVDISCARD_ALL) + v->needed = 1; + } + } + if (!v->needed) { + av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n", + v->index); + return AVERROR_EOF; + } + goto restart; +} + +static int applehttp_read_header(AVFormatContext *s) +{ + AppleHTTPContext *c = s->priv_data; + int ret = 0, i, j, stream_offset = 0; + + c->interrupt_callback = &s->interrupt_callback; + + if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0) + goto fail; + + if (c->n_variants == 0) { + av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); + ret = AVERROR_EOF; + goto fail; + } + /* If the playlist only contained variants, parse each individual + * variant playlist. */ + if (c->n_variants > 1 || c->variants[0]->n_segments == 0) { + for (i = 0; i < c->n_variants; i++) { + struct variant *v = c->variants[i]; + if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) + goto fail; + } + } + + if (c->variants[0]->n_segments == 0) { + av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); + ret = AVERROR_EOF; + goto fail; + } + + /* If this isn't a live stream, calculate the total duration of the + * stream. */ + if (c->variants[0]->finished) { + int64_t duration = 0; + for (i = 0; i < c->variants[0]->n_segments; i++) + duration += c->variants[0]->segments[i]->duration; + s->duration = duration * AV_TIME_BASE; + } + + /* Open the demuxer for each variant */ + for (i = 0; i < c->n_variants; i++) { + struct variant *v = c->variants[i]; + AVInputFormat *in_fmt = NULL; + char bitrate_str[20]; + if (v->n_segments == 0) + continue; + + if (!(v->ctx = avformat_alloc_context())) { + ret = AVERROR(ENOMEM); + goto fail; + } + + v->index = i; + v->needed = 1; + v->parent = s; + + /* If this is a live stream with more than 3 segments, start at the + * third last segment. */ + v->cur_seq_no = v->start_seq_no; + if (!v->finished && v->n_segments > 3) + v->cur_seq_no = v->start_seq_no + v->n_segments - 3; + + v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); + ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v, + read_data, NULL, NULL); + v->pb.seekable = 0; + ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url, + NULL, 0, 0); + if (ret < 0) { + /* Free the ctx - it isn't initialized properly at this point, + * so avformat_close_input shouldn't be called. If + * avformat_open_input fails below, it frees and zeros the + * context, so it doesn't need any special treatment like this. */ + avformat_free_context(v->ctx); + v->ctx = NULL; + goto fail; + } + v->ctx->pb = &v->pb; + ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL); + if (ret < 0) + goto fail; + v->stream_offset = stream_offset; + snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth); + /* Create new AVStreams for each stream in this variant */ + for (j = 0; j < v->ctx->nb_streams; j++) { + AVStream *st = avformat_new_stream(s, NULL); + if (!st) { + ret = AVERROR(ENOMEM); + goto fail; + } + st->id = i; + avcodec_copy_context(st->codec, v->ctx->streams[j]->codec); + if (v->bandwidth) + av_dict_set(&st->metadata, "variant_bitrate", bitrate_str, + 0); + } + stream_offset += v->ctx->nb_streams; + } + + c->first_packet = 1; + c->first_timestamp = AV_NOPTS_VALUE; + + return 0; +fail: + free_variant_list(c); + return ret; +} + +static int recheck_discard_flags(AVFormatContext *s, int first) +{ + AppleHTTPContext *c = s->priv_data; + int i, changed = 0; + + /* Check if any new streams are needed */ + for (i = 0; i < c->n_variants; i++) + c->variants[i]->cur_needed = 0;; + + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + struct variant *var = c->variants[s->streams[i]->id]; + if (st->discard < AVDISCARD_ALL) + var->cur_needed = 1; + } + for (i = 0; i < c->n_variants; i++) { + struct variant *v = c->variants[i]; + if (v->cur_needed && !v->needed) { + v->needed = 1; + changed = 1; + v->cur_seq_no = c->cur_seq_no; + v->pb.eof_reached = 0; + av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i); + } else if (first && !v->cur_needed && v->needed) { + if (v->input) + ffurl_close(v->input); + v->input = NULL; + v->needed = 0; + changed = 1; + av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i); + } + } + return changed; +} + +static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AppleHTTPContext *c = s->priv_data; + int ret, i, minvariant = -1; + + if (c->first_packet) { + recheck_discard_flags(s, 1); + c->first_packet = 0; + } + +start: + c->end_of_segment = 0; + for (i = 0; i < c->n_variants; i++) { + struct variant *var = c->variants[i]; + /* Make sure we've got one buffered packet from each open variant + * stream */ + if (var->needed && !var->pkt.data) { + ret = av_read_frame(var->ctx, &var->pkt); + if (ret < 0) { + if (!var->pb.eof_reached) + return ret; + reset_packet(&var->pkt); + } else { + if (c->first_timestamp == AV_NOPTS_VALUE) + c->first_timestamp = var->pkt.dts; + } + } + /* Check if this stream has the packet with the lowest dts */ + if (var->pkt.data) { + if (minvariant < 0 || + var->pkt.dts < c->variants[minvariant]->pkt.dts) + minvariant = i; + } + } + if (c->end_of_segment) { + if (recheck_discard_flags(s, 0)) + goto start; + } + /* If we got a packet, return it */ + if (minvariant >= 0) { + *pkt = c->variants[minvariant]->pkt; + pkt->stream_index += c->variants[minvariant]->stream_offset; + reset_packet(&c->variants[minvariant]->pkt); + return 0; + } + return AVERROR_EOF; +} + +static int applehttp_close(AVFormatContext *s) +{ + AppleHTTPContext *c = s->priv_data; + + free_variant_list(c); + return 0; +} + +static int applehttp_read_seek(AVFormatContext *s, int stream_index, + int64_t timestamp, int flags) +{ + AppleHTTPContext *c = s->priv_data; + int i, j, ret; + + if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) + return AVERROR(ENOSYS); + + timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ? + s->streams[stream_index]->time_base.den : + AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ? + AV_ROUND_DOWN : AV_ROUND_UP); + ret = AVERROR(EIO); + for (i = 0; i < c->n_variants; i++) { + /* Reset reading */ + struct variant *var = c->variants[i]; + int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? 0 : + av_rescale_rnd(c->first_timestamp, 1, + stream_index >= 0 ? s->streams[stream_index]->time_base.den : AV_TIME_BASE, + flags & AVSEEK_FLAG_BACKWARD ? AV_ROUND_DOWN : AV_ROUND_UP); + if (var->input) { + ffurl_close(var->input); + var->input = NULL; + } + av_free_packet(&var->pkt); + reset_packet(&var->pkt); + var->pb.eof_reached = 0; + + /* Locate the segment that contains the target timestamp */ + for (j = 0; j < var->n_segments; j++) { + if (timestamp >= pos && + timestamp < pos + var->segments[j]->duration) { + var->cur_seq_no = var->start_seq_no + j; + ret = 0; + break; + } + pos += var->segments[j]->duration; + } + } + return ret; +} + +static int applehttp_probe(AVProbeData *p) +{ + /* Require #EXTM3U at the start, and either one of the ones below + * somewhere for a proper match. */ + if (strncmp(p->buf, "#EXTM3U", 7)) + return 0; + if (strstr(p->buf, "#EXT-X-STREAM-INF:") || + strstr(p->buf, "#EXT-X-TARGETDURATION:") || + strstr(p->buf, "#EXT-X-MEDIA-SEQUENCE:")) + return AVPROBE_SCORE_MAX; + return 0; +} + +AVInputFormat ff_hls_demuxer = { + .name = "hls", + .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"), + .priv_data_size = sizeof(AppleHTTPContext), + .read_probe = applehttp_probe, + .read_header = applehttp_read_header, + .read_packet = applehttp_read_packet, + .read_close = applehttp_close, + .read_seek = applehttp_read_seek, +}; -- cgit v1.2.3 From 65cd7bf32f384db919b81b07d8e3201d6af4fb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 14 Feb 2012 11:50:51 +0200 Subject: hls: Rename the functions and context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Storsjö --- libavformat/hls.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/libavformat/hls.c b/libavformat/hls.c index 13b9b27378..d8b00204dc 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -93,7 +93,7 @@ struct variant { uint8_t key[16]; }; -typedef struct AppleHTTPContext { +typedef struct HLSContext { int n_variants; struct variant **variants; int cur_seq_no; @@ -101,7 +101,7 @@ typedef struct AppleHTTPContext { int first_packet; int64_t first_timestamp; AVIOInterruptCB *interrupt_callback; -} AppleHTTPContext; +} HLSContext; static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) { @@ -120,7 +120,7 @@ static void free_segment_list(struct variant *var) var->n_segments = 0; } -static void free_variant_list(AppleHTTPContext *c) +static void free_variant_list(HLSContext *c) { int i; for (i = 0; i < c->n_variants; i++) { @@ -150,7 +150,7 @@ static void reset_packet(AVPacket *pkt) pkt->data = NULL; } -static struct variant *new_variant(AppleHTTPContext *c, int bandwidth, +static struct variant *new_variant(HLSContext *c, int bandwidth, const char *url, const char *base) { struct variant *var = av_mallocz(sizeof(struct variant)); @@ -197,7 +197,7 @@ static void handle_key_args(struct key_info *info, const char *key, } } -static int parse_playlist(AppleHTTPContext *c, const char *url, +static int parse_playlist(HLSContext *c, const char *url, struct variant *var, AVIOContext *in) { int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; @@ -371,7 +371,7 @@ static int open_input(struct variant *var) static int read_data(void *opaque, uint8_t *buf, int buf_size) { struct variant *v = opaque; - AppleHTTPContext *c = v->parent->priv_data; + HLSContext *c = v->parent->priv_data; int ret, i; restart: @@ -443,9 +443,9 @@ reload: goto restart; } -static int applehttp_read_header(AVFormatContext *s) +static int hls_read_header(AVFormatContext *s) { - AppleHTTPContext *c = s->priv_data; + HLSContext *c = s->priv_data; int ret = 0, i, j, stream_offset = 0; c->interrupt_callback = &s->interrupt_callback; @@ -554,7 +554,7 @@ fail: static int recheck_discard_flags(AVFormatContext *s, int first) { - AppleHTTPContext *c = s->priv_data; + HLSContext *c = s->priv_data; int i, changed = 0; /* Check if any new streams are needed */ @@ -587,9 +587,9 @@ static int recheck_discard_flags(AVFormatContext *s, int first) return changed; } -static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt) +static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) { - AppleHTTPContext *c = s->priv_data; + HLSContext *c = s->priv_data; int ret, i, minvariant = -1; if (c->first_packet) { @@ -635,18 +635,18 @@ start: return AVERROR_EOF; } -static int applehttp_close(AVFormatContext *s) +static int hls_close(AVFormatContext *s) { - AppleHTTPContext *c = s->priv_data; + HLSContext *c = s->priv_data; free_variant_list(c); return 0; } -static int applehttp_read_seek(AVFormatContext *s, int stream_index, +static int hls_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { - AppleHTTPContext *c = s->priv_data; + HLSContext *c = s->priv_data; int i, j, ret; if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) @@ -686,7 +686,7 @@ static int applehttp_read_seek(AVFormatContext *s, int stream_index, return ret; } -static int applehttp_probe(AVProbeData *p) +static int hls_probe(AVProbeData *p) { /* Require #EXTM3U at the start, and either one of the ones below * somewhere for a proper match. */ @@ -702,10 +702,10 @@ static int applehttp_probe(AVProbeData *p) AVInputFormat ff_hls_demuxer = { .name = "hls", .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"), - .priv_data_size = sizeof(AppleHTTPContext), - .read_probe = applehttp_probe, - .read_header = applehttp_read_header, - .read_packet = applehttp_read_packet, - .read_close = applehttp_close, - .read_seek = applehttp_read_seek, + .priv_data_size = sizeof(HLSContext), + .read_probe = hls_probe, + .read_header = hls_read_header, + .read_packet = hls_read_packet, + .read_close = hls_close, + .read_seek = hls_read_seek, }; -- cgit v1.2.3 From 8bdab32f4ee402bbd7bf5a61b0ccbcc59569bfb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 14 Feb 2012 12:00:49 +0200 Subject: libavformat: Rename the applehttp protocol to hls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep the old protocol name around for backwards compatibility until the next bump. Deprecate the method of implicitly assuming the nested protocol. For applehttp://server/path, it might have felt logical, but supporting hls://server/path isn't quite as intuitive. Therefore only support hls+http://server/path from now on. Using this protocol at all is discouraged, since the hls demuxer is more complete and fits into the architecture better. There have been cases where the protocol implementation worked better than the demuxer, but this should no longer be the case. Signed-off-by: Martin Storsjö --- doc/protocols.texi | 11 +- libavformat/Makefile | 3 +- libavformat/allformats.c | 4 + libavformat/applehttpproto.c | 313 --------------------------------------- libavformat/hlsproto.c | 341 +++++++++++++++++++++++++++++++++++++++++++ libavformat/version.h | 3 + 6 files changed, 355 insertions(+), 320 deletions(-) delete mode 100644 libavformat/applehttpproto.c create mode 100644 libavformat/hlsproto.c diff --git a/doc/protocols.texi b/doc/protocols.texi index f5bb5324be..e00c1e11b2 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -19,20 +19,19 @@ supported protocols. A description of the currently available protocols follows. -@section applehttp +@section hls Read Apple HTTP Live Streaming compliant segmented stream as a uniform one. The M3U8 playlists describing the segments can be remote HTTP resources or local files, accessed using the standard file protocol. -HTTP is default, specific protocol can be declared by specifying -"+@var{proto}" after the applehttp URI scheme name, where @var{proto} +The nested protocol is declared by specifying +"+@var{proto}" after the hls URI scheme name, where @var{proto} is either "file" or "http". @example -applehttp://host/path/to/remote/resource.m3u8 -applehttp+http://host/path/to/remote/resource.m3u8 -applehttp+file://path/to/local/resource.m3u8 +hls+http://host/path/to/remote/resource.m3u8 +hls+file://path/to/local/resource.m3u8 @end example @section concat diff --git a/libavformat/Makefile b/libavformat/Makefile index a54a8f569a..dd55e42db4 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -329,11 +329,12 @@ OBJS-$(CONFIG_LIBRTMP) += librtmp.o # protocols I/O OBJS+= avio.o aviobuf.o -OBJS-$(CONFIG_APPLEHTTP_PROTOCOL) += applehttpproto.o +OBJS-$(CONFIG_APPLEHTTP_PROTOCOL) += hlsproto.o OBJS-$(CONFIG_CONCAT_PROTOCOL) += concat.o OBJS-$(CONFIG_CRYPTO_PROTOCOL) += crypto.o OBJS-$(CONFIG_FILE_PROTOCOL) += file.o OBJS-$(CONFIG_GOPHER_PROTOCOL) += gopher.o +OBJS-$(CONFIG_HLS_PROTOCOL) += hlsproto.o OBJS-$(CONFIG_HTTP_PROTOCOL) += http.o httpauth.o OBJS-$(CONFIG_HTTPPROXY_PROTOCOL) += http.o httpauth.o OBJS-$(CONFIG_HTTPS_PROTOCOL) += http.o httpauth.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index af6ad97e92..a60688c362 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -22,6 +22,7 @@ #include "rtp.h" #include "rdt.h" #include "url.h" +#include "version.h" #define REGISTER_MUXER(X,x) { \ extern AVOutputFormat ff_##x##_muxer; \ @@ -238,11 +239,14 @@ void av_register_all(void) REGISTER_MUXDEMUX (YUV4MPEGPIPE, yuv4mpegpipe); /* protocols */ +#if FF_API_APPLEHTTP_PROTO REGISTER_PROTOCOL (APPLEHTTP, applehttp); +#endif REGISTER_PROTOCOL (CONCAT, concat); REGISTER_PROTOCOL (CRYPTO, crypto); REGISTER_PROTOCOL (FILE, file); REGISTER_PROTOCOL (GOPHER, gopher); + REGISTER_PROTOCOL (HLS, hls); REGISTER_PROTOCOL (HTTP, http); REGISTER_PROTOCOL (HTTPPROXY, httpproxy); REGISTER_PROTOCOL (HTTPS, https); diff --git a/libavformat/applehttpproto.c b/libavformat/applehttpproto.c deleted file mode 100644 index 46758ba30f..0000000000 --- a/libavformat/applehttpproto.c +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Apple HTTP Live Streaming Protocol Handler - * Copyright (c) 2010 Martin Storsjo - * - * This file is part of Libav. - * - * Libav is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * Libav is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Libav; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -/** - * @file - * Apple HTTP Live Streaming Protocol Handler - * http://tools.ietf.org/html/draft-pantos-http-live-streaming - */ - -#include "libavutil/avstring.h" -#include "avformat.h" -#include "internal.h" -#include "url.h" -#include - -/* - * An apple http stream consists of a playlist with media segment files, - * played sequentially. There may be several playlists with the same - * video content, in different bandwidth variants, that are played in - * parallel (preferrably only one bandwidth variant at a time). In this case, - * the user supplied the url to a main playlist that only lists the variant - * playlists. - * - * If the main playlist doesn't point at any variants, we still create - * one anonymous toplevel variant for this, to maintain the structure. - */ - -struct segment { - int duration; - char url[MAX_URL_SIZE]; -}; - -struct variant { - int bandwidth; - char url[MAX_URL_SIZE]; -}; - -typedef struct AppleHTTPContext { - char playlisturl[MAX_URL_SIZE]; - int target_duration; - int start_seq_no; - int finished; - int n_segments; - struct segment **segments; - int n_variants; - struct variant **variants; - int cur_seq_no; - URLContext *seg_hd; - int64_t last_load_time; -} AppleHTTPContext; - -static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) -{ - int len = ff_get_line(s, buf, maxlen); - while (len > 0 && isspace(buf[len - 1])) - buf[--len] = '\0'; - return len; -} - -static void free_segment_list(AppleHTTPContext *s) -{ - int i; - for (i = 0; i < s->n_segments; i++) - av_free(s->segments[i]); - av_freep(&s->segments); - s->n_segments = 0; -} - -static void free_variant_list(AppleHTTPContext *s) -{ - int i; - for (i = 0; i < s->n_variants; i++) - av_free(s->variants[i]); - av_freep(&s->variants); - s->n_variants = 0; -} - -struct variant_info { - char bandwidth[20]; -}; - -static void handle_variant_args(struct variant_info *info, const char *key, - int key_len, char **dest, int *dest_len) -{ - if (!strncmp(key, "BANDWIDTH=", key_len)) { - *dest = info->bandwidth; - *dest_len = sizeof(info->bandwidth); - } -} - -static int parse_playlist(URLContext *h, const char *url) -{ - AppleHTTPContext *s = h->priv_data; - AVIOContext *in; - int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; - char line[1024]; - const char *ptr; - - if ((ret = avio_open2(&in, url, AVIO_FLAG_READ, - &h->interrupt_callback, NULL)) < 0) - return ret; - - read_chomp_line(in, line, sizeof(line)); - if (strcmp(line, "#EXTM3U")) - return AVERROR_INVALIDDATA; - - free_segment_list(s); - s->finished = 0; - while (!in->eof_reached) { - read_chomp_line(in, line, sizeof(line)); - if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { - struct variant_info info = {{0}}; - is_variant = 1; - ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, - &info); - bandwidth = atoi(info.bandwidth); - } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { - s->target_duration = atoi(ptr); - } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { - s->start_seq_no = atoi(ptr); - } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { - s->finished = 1; - } else if (av_strstart(line, "#EXTINF:", &ptr)) { - is_segment = 1; - duration = atoi(ptr); - } else if (av_strstart(line, "#", NULL)) { - continue; - } else if (line[0]) { - if (is_segment) { - struct segment *seg = av_malloc(sizeof(struct segment)); - if (!seg) { - ret = AVERROR(ENOMEM); - goto fail; - } - seg->duration = duration; - ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); - dynarray_add(&s->segments, &s->n_segments, seg); - is_segment = 0; - } else if (is_variant) { - struct variant *var = av_malloc(sizeof(struct variant)); - if (!var) { - ret = AVERROR(ENOMEM); - goto fail; - } - var->bandwidth = bandwidth; - ff_make_absolute_url(var->url, sizeof(var->url), url, line); - dynarray_add(&s->variants, &s->n_variants, var); - is_variant = 0; - } - } - } - s->last_load_time = av_gettime(); - -fail: - avio_close(in); - return ret; -} - -static int applehttp_close(URLContext *h) -{ - AppleHTTPContext *s = h->priv_data; - - free_segment_list(s); - free_variant_list(s); - ffurl_close(s->seg_hd); - return 0; -} - -static int applehttp_open(URLContext *h, const char *uri, int flags) -{ - AppleHTTPContext *s = h->priv_data; - int ret, i; - const char *nested_url; - - if (flags & AVIO_FLAG_WRITE) - return AVERROR(ENOSYS); - - h->is_streamed = 1; - - if (av_strstart(uri, "applehttp+", &nested_url)) { - av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl)); - } else if (av_strstart(uri, "applehttp://", &nested_url)) { - av_strlcpy(s->playlisturl, "http://", sizeof(s->playlisturl)); - av_strlcat(s->playlisturl, nested_url, sizeof(s->playlisturl)); - } else { - av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); - ret = AVERROR(EINVAL); - goto fail; - } - - if ((ret = parse_playlist(h, s->playlisturl)) < 0) - goto fail; - - if (s->n_segments == 0 && s->n_variants > 0) { - int max_bandwidth = 0, maxvar = -1; - for (i = 0; i < s->n_variants; i++) { - if (s->variants[i]->bandwidth > max_bandwidth || i == 0) { - max_bandwidth = s->variants[i]->bandwidth; - maxvar = i; - } - } - av_strlcpy(s->playlisturl, s->variants[maxvar]->url, - sizeof(s->playlisturl)); - if ((ret = parse_playlist(h, s->playlisturl)) < 0) - goto fail; - } - - if (s->n_segments == 0) { - av_log(h, AV_LOG_WARNING, "Empty playlist\n"); - ret = AVERROR(EIO); - goto fail; - } - s->cur_seq_no = s->start_seq_no; - if (!s->finished && s->n_segments >= 3) - s->cur_seq_no = s->start_seq_no + s->n_segments - 3; - - return 0; - -fail: - applehttp_close(h); - return ret; -} - -static int applehttp_read(URLContext *h, uint8_t *buf, int size) -{ - AppleHTTPContext *s = h->priv_data; - const char *url; - int ret; - int64_t reload_interval; - -start: - if (s->seg_hd) { - ret = ffurl_read(s->seg_hd, buf, size); - if (ret > 0) - return ret; - } - if (s->seg_hd) { - ffurl_close(s->seg_hd); - s->seg_hd = NULL; - s->cur_seq_no++; - } - reload_interval = s->n_segments > 0 ? - s->segments[s->n_segments - 1]->duration : - s->target_duration; - reload_interval *= 1000000; -retry: - if (!s->finished) { - int64_t now = av_gettime(); - if (now - s->last_load_time >= reload_interval) { - if ((ret = parse_playlist(h, s->playlisturl)) < 0) - return ret; - /* If we need to reload the playlist again below (if - * there's still no more segments), switch to a reload - * interval of half the target duration. */ - reload_interval = s->target_duration * 500000; - } - } - if (s->cur_seq_no < s->start_seq_no) { - av_log(h, AV_LOG_WARNING, - "skipping %d segments ahead, expired from playlist\n", - s->start_seq_no - s->cur_seq_no); - s->cur_seq_no = s->start_seq_no; - } - if (s->cur_seq_no - s->start_seq_no >= s->n_segments) { - if (s->finished) - return AVERROR_EOF; - while (av_gettime() - s->last_load_time < reload_interval) { - if (ff_check_interrupt(&h->interrupt_callback)) - return AVERROR_EXIT; - usleep(100*1000); - } - goto retry; - } - url = s->segments[s->cur_seq_no - s->start_seq_no]->url, - av_log(h, AV_LOG_DEBUG, "opening %s\n", url); - ret = ffurl_open(&s->seg_hd, url, AVIO_FLAG_READ, - &h->interrupt_callback, NULL); - if (ret < 0) { - if (ff_check_interrupt(&h->interrupt_callback)) - return AVERROR_EXIT; - av_log(h, AV_LOG_WARNING, "Unable to open %s\n", url); - s->cur_seq_no++; - goto retry; - } - goto start; -} - -URLProtocol ff_applehttp_protocol = { - .name = "applehttp", - .url_open = applehttp_open, - .url_read = applehttp_read, - .url_close = applehttp_close, - .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, - .priv_data_size = sizeof(AppleHTTPContext), -}; diff --git a/libavformat/hlsproto.c b/libavformat/hlsproto.c new file mode 100644 index 0000000000..244f270398 --- /dev/null +++ b/libavformat/hlsproto.c @@ -0,0 +1,341 @@ +/* + * Apple HTTP Live Streaming Protocol Handler + * Copyright (c) 2010 Martin Storsjo + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Apple HTTP Live Streaming Protocol Handler + * http://tools.ietf.org/html/draft-pantos-http-live-streaming + */ + +#include "libavutil/avstring.h" +#include "avformat.h" +#include "internal.h" +#include "url.h" +#include "version.h" +#include + +/* + * An apple http stream consists of a playlist with media segment files, + * played sequentially. There may be several playlists with the same + * video content, in different bandwidth variants, that are played in + * parallel (preferrably only one bandwidth variant at a time). In this case, + * the user supplied the url to a main playlist that only lists the variant + * playlists. + * + * If the main playlist doesn't point at any variants, we still create + * one anonymous toplevel variant for this, to maintain the structure. + */ + +struct segment { + int duration; + char url[MAX_URL_SIZE]; +}; + +struct variant { + int bandwidth; + char url[MAX_URL_SIZE]; +}; + +typedef struct AppleHTTPContext { + char playlisturl[MAX_URL_SIZE]; + int target_duration; + int start_seq_no; + int finished; + int n_segments; + struct segment **segments; + int n_variants; + struct variant **variants; + int cur_seq_no; + URLContext *seg_hd; + int64_t last_load_time; +} AppleHTTPContext; + +static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) +{ + int len = ff_get_line(s, buf, maxlen); + while (len > 0 && isspace(buf[len - 1])) + buf[--len] = '\0'; + return len; +} + +static void free_segment_list(AppleHTTPContext *s) +{ + int i; + for (i = 0; i < s->n_segments; i++) + av_free(s->segments[i]); + av_freep(&s->segments); + s->n_segments = 0; +} + +static void free_variant_list(AppleHTTPContext *s) +{ + int i; + for (i = 0; i < s->n_variants; i++) + av_free(s->variants[i]); + av_freep(&s->variants); + s->n_variants = 0; +} + +struct variant_info { + char bandwidth[20]; +}; + +static void handle_variant_args(struct variant_info *info, const char *key, + int key_len, char **dest, int *dest_len) +{ + if (!strncmp(key, "BANDWIDTH=", key_len)) { + *dest = info->bandwidth; + *dest_len = sizeof(info->bandwidth); + } +} + +static int parse_playlist(URLContext *h, const char *url) +{ + AppleHTTPContext *s = h->priv_data; + AVIOContext *in; + int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; + char line[1024]; + const char *ptr; + + if ((ret = avio_open2(&in, url, AVIO_FLAG_READ, + &h->interrupt_callback, NULL)) < 0) + return ret; + + read_chomp_line(in, line, sizeof(line)); + if (strcmp(line, "#EXTM3U")) + return AVERROR_INVALIDDATA; + + free_segment_list(s); + s->finished = 0; + while (!in->eof_reached) { + read_chomp_line(in, line, sizeof(line)); + if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { + struct variant_info info = {{0}}; + is_variant = 1; + ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, + &info); + bandwidth = atoi(info.bandwidth); + } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { + s->target_duration = atoi(ptr); + } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { + s->start_seq_no = atoi(ptr); + } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { + s->finished = 1; + } else if (av_strstart(line, "#EXTINF:", &ptr)) { + is_segment = 1; + duration = atoi(ptr); + } else if (av_strstart(line, "#", NULL)) { + continue; + } else if (line[0]) { + if (is_segment) { + struct segment *seg = av_malloc(sizeof(struct segment)); + if (!seg) { + ret = AVERROR(ENOMEM); + goto fail; + } + seg->duration = duration; + ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); + dynarray_add(&s->segments, &s->n_segments, seg); + is_segment = 0; + } else if (is_variant) { + struct variant *var = av_malloc(sizeof(struct variant)); + if (!var) { + ret = AVERROR(ENOMEM); + goto fail; + } + var->bandwidth = bandwidth; + ff_make_absolute_url(var->url, sizeof(var->url), url, line); + dynarray_add(&s->variants, &s->n_variants, var); + is_variant = 0; + } + } + } + s->last_load_time = av_gettime(); + +fail: + avio_close(in); + return ret; +} + +static int applehttp_close(URLContext *h) +{ + AppleHTTPContext *s = h->priv_data; + + free_segment_list(s); + free_variant_list(s); + ffurl_close(s->seg_hd); + return 0; +} + +static int applehttp_open(URLContext *h, const char *uri, int flags) +{ + AppleHTTPContext *s = h->priv_data; + int ret, i; + const char *nested_url; + + if (flags & AVIO_FLAG_WRITE) + return AVERROR(ENOSYS); + + h->is_streamed = 1; + + if (av_strstart(uri, "hls+", &nested_url)) { + av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl)); + } else if (av_strstart(uri, "hls://", &nested_url)) { + av_log(h, AV_LOG_ERROR, + "No nested protocol specified. Specify e.g. hls+http://%s\n", + nested_url); + ret = AVERROR(EINVAL); + goto fail; +#if FF_API_APPLEHTTP_PROTO + } else if (av_strstart(uri, "applehttp+", &nested_url)) { + av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl)); + av_log(h, AV_LOG_WARNING, + "The applehttp protocol is deprecated, use hls+%s as url " + "instead.\n", nested_url); + } else if (av_strstart(uri, "applehttp://", &nested_url)) { + av_strlcpy(s->playlisturl, "http://", sizeof(s->playlisturl)); + av_strlcat(s->playlisturl, nested_url, sizeof(s->playlisturl)); + av_log(h, AV_LOG_WARNING, + "The applehttp protocol is deprecated, use hls+http://%s as url " + "instead.\n", nested_url); +#endif + } else { + av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); + ret = AVERROR(EINVAL); + goto fail; + } + + if ((ret = parse_playlist(h, s->playlisturl)) < 0) + goto fail; + + if (s->n_segments == 0 && s->n_variants > 0) { + int max_bandwidth = 0, maxvar = -1; + for (i = 0; i < s->n_variants; i++) { + if (s->variants[i]->bandwidth > max_bandwidth || i == 0) { + max_bandwidth = s->variants[i]->bandwidth; + maxvar = i; + } + } + av_strlcpy(s->playlisturl, s->variants[maxvar]->url, + sizeof(s->playlisturl)); + if ((ret = parse_playlist(h, s->playlisturl)) < 0) + goto fail; + } + + if (s->n_segments == 0) { + av_log(h, AV_LOG_WARNING, "Empty playlist\n"); + ret = AVERROR(EIO); + goto fail; + } + s->cur_seq_no = s->start_seq_no; + if (!s->finished && s->n_segments >= 3) + s->cur_seq_no = s->start_seq_no + s->n_segments - 3; + + return 0; + +fail: + applehttp_close(h); + return ret; +} + +static int applehttp_read(URLContext *h, uint8_t *buf, int size) +{ + AppleHTTPContext *s = h->priv_data; + const char *url; + int ret; + int64_t reload_interval; + +start: + if (s->seg_hd) { + ret = ffurl_read(s->seg_hd, buf, size); + if (ret > 0) + return ret; + } + if (s->seg_hd) { + ffurl_close(s->seg_hd); + s->seg_hd = NULL; + s->cur_seq_no++; + } + reload_interval = s->n_segments > 0 ? + s->segments[s->n_segments - 1]->duration : + s->target_duration; + reload_interval *= 1000000; +retry: + if (!s->finished) { + int64_t now = av_gettime(); + if (now - s->last_load_time >= reload_interval) { + if ((ret = parse_playlist(h, s->playlisturl)) < 0) + return ret; + /* If we need to reload the playlist again below (if + * there's still no more segments), switch to a reload + * interval of half the target duration. */ + reload_interval = s->target_duration * 500000; + } + } + if (s->cur_seq_no < s->start_seq_no) { + av_log(h, AV_LOG_WARNING, + "skipping %d segments ahead, expired from playlist\n", + s->start_seq_no - s->cur_seq_no); + s->cur_seq_no = s->start_seq_no; + } + if (s->cur_seq_no - s->start_seq_no >= s->n_segments) { + if (s->finished) + return AVERROR_EOF; + while (av_gettime() - s->last_load_time < reload_interval) { + if (ff_check_interrupt(&h->interrupt_callback)) + return AVERROR_EXIT; + usleep(100*1000); + } + goto retry; + } + url = s->segments[s->cur_seq_no - s->start_seq_no]->url, + av_log(h, AV_LOG_DEBUG, "opening %s\n", url); + ret = ffurl_open(&s->seg_hd, url, AVIO_FLAG_READ, + &h->interrupt_callback, NULL); + if (ret < 0) { + if (ff_check_interrupt(&h->interrupt_callback)) + return AVERROR_EXIT; + av_log(h, AV_LOG_WARNING, "Unable to open %s\n", url); + s->cur_seq_no++; + goto retry; + } + goto start; +} + +#if FF_API_APPLEHTTP_PROTO +URLProtocol ff_applehttp_protocol = { + .name = "applehttp", + .url_open = applehttp_open, + .url_read = applehttp_read, + .url_close = applehttp_close, + .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, + .priv_data_size = sizeof(AppleHTTPContext), +}; +#endif + +URLProtocol ff_hls_protocol = { + .name = "hls", + .url_open = applehttp_open, + .url_read = applehttp_read, + .url_close = applehttp_close, + .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, + .priv_data_size = sizeof(AppleHTTPContext), +}; diff --git a/libavformat/version.h b/libavformat/version.h index 6f3b387ddc..443d752b8a 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -50,5 +50,8 @@ #ifndef FF_API_CLOSE_INPUT_FILE #define FF_API_CLOSE_INPUT_FILE (LIBAVFORMAT_VERSION_MAJOR < 55) #endif +#ifndef FF_API_APPLEHTTP_PROTO +#define FF_API_APPLEHTTP_PROTO (LIBAVFORMAT_VERSION_MAJOR < 55) +#endif #endif /* AVFORMAT_VERSION_H */ -- cgit v1.2.3 From 3975ca8957be408c118f101e0a44c6554a329667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 14 Feb 2012 12:04:50 +0200 Subject: doc: Move the hls protocol section into the right place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Storsjö --- doc/protocols.texi | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/protocols.texi b/doc/protocols.texi index e00c1e11b2..626886069d 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -19,21 +19,6 @@ supported protocols. A description of the currently available protocols follows. -@section hls - -Read Apple HTTP Live Streaming compliant segmented stream as -a uniform one. The M3U8 playlists describing the segments can be -remote HTTP resources or local files, accessed using the standard -file protocol. -The nested protocol is declared by specifying -"+@var{proto}" after the hls URI scheme name, where @var{proto} -is either "file" or "http". - -@example -hls+http://host/path/to/remote/resource.m3u8 -hls+file://path/to/local/resource.m3u8 -@end example - @section concat Physical concatenation protocol. @@ -80,6 +65,21 @@ specified with the name "FILE.mpeg" is interpreted as the URL Gopher protocol. +@section hls + +Read Apple HTTP Live Streaming compliant segmented stream as +a uniform one. The M3U8 playlists describing the segments can be +remote HTTP resources or local files, accessed using the standard +file protocol. +The nested protocol is declared by specifying +"+@var{proto}" after the hls URI scheme name, where @var{proto} +is either "file" or "http". + +@example +hls+http://host/path/to/remote/resource.m3u8 +hls+file://path/to/local/resource.m3u8 +@end example + @section http HTTP (Hyper Text Transfer Protocol). -- cgit v1.2.3 From 9cb9c6c42dcd08746e0684eeacccf8b89b12e571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 14 Feb 2012 12:09:09 +0200 Subject: hlsproto: Encourage users to try the hls demuxer instead of the proto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Storsjö --- doc/protocols.texi | 5 +++++ libavformat/hlsproto.c | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/doc/protocols.texi b/doc/protocols.texi index 626886069d..0eb4c69b49 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -80,6 +80,11 @@ hls+http://host/path/to/remote/resource.m3u8 hls+file://path/to/local/resource.m3u8 @end example +Using this protocol is discouraged - the hls demuxer should work +just as well (if not, please report the issues) and is more complete. +To use the hls demuxer instead, simply use the direct URLs to the +m3u8 files. + @section http HTTP (Hyper Text Transfer Protocol). diff --git a/libavformat/hlsproto.c b/libavformat/hlsproto.c index 244f270398..f611f7599c 100644 --- a/libavformat/hlsproto.c +++ b/libavformat/hlsproto.c @@ -222,6 +222,12 @@ static int applehttp_open(URLContext *h, const char *uri, int flags) ret = AVERROR(EINVAL); goto fail; } + av_log(h, AV_LOG_WARNING, + "Using the hls protocol is discouraged, please try using the " + "hls demuxer instead. The hls demuxer should be more complete " + "and work as well as the protocol implementation. (If not, " + "please report it.) To use the demuxer, simply use %s as url.\n", + s->playlisturl); if ((ret = parse_playlist(h, s->playlisturl)) < 0) goto fail; -- cgit v1.2.3 From 8c62d83fe58a117249cadae743bb963d4d45450b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Tue, 14 Feb 2012 12:16:31 +0200 Subject: hlsproto: Rename the functions and context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Storsjö --- libavformat/hlsproto.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/libavformat/hlsproto.c b/libavformat/hlsproto.c index f611f7599c..044b7ffe98 100644 --- a/libavformat/hlsproto.c +++ b/libavformat/hlsproto.c @@ -54,7 +54,7 @@ struct variant { char url[MAX_URL_SIZE]; }; -typedef struct AppleHTTPContext { +typedef struct HLSContext { char playlisturl[MAX_URL_SIZE]; int target_duration; int start_seq_no; @@ -66,7 +66,7 @@ typedef struct AppleHTTPContext { int cur_seq_no; URLContext *seg_hd; int64_t last_load_time; -} AppleHTTPContext; +} HLSContext; static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) { @@ -76,7 +76,7 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) return len; } -static void free_segment_list(AppleHTTPContext *s) +static void free_segment_list(HLSContext *s) { int i; for (i = 0; i < s->n_segments; i++) @@ -85,7 +85,7 @@ static void free_segment_list(AppleHTTPContext *s) s->n_segments = 0; } -static void free_variant_list(AppleHTTPContext *s) +static void free_variant_list(HLSContext *s) { int i; for (i = 0; i < s->n_variants; i++) @@ -109,7 +109,7 @@ static void handle_variant_args(struct variant_info *info, const char *key, static int parse_playlist(URLContext *h, const char *url) { - AppleHTTPContext *s = h->priv_data; + HLSContext *s = h->priv_data; AVIOContext *in; int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; char line[1024]; @@ -175,9 +175,9 @@ fail: return ret; } -static int applehttp_close(URLContext *h) +static int hls_close(URLContext *h) { - AppleHTTPContext *s = h->priv_data; + HLSContext *s = h->priv_data; free_segment_list(s); free_variant_list(s); @@ -185,9 +185,9 @@ static int applehttp_close(URLContext *h) return 0; } -static int applehttp_open(URLContext *h, const char *uri, int flags) +static int hls_open(URLContext *h, const char *uri, int flags) { - AppleHTTPContext *s = h->priv_data; + HLSContext *s = h->priv_data; int ret, i; const char *nested_url; @@ -258,13 +258,13 @@ static int applehttp_open(URLContext *h, const char *uri, int flags) return 0; fail: - applehttp_close(h); + hls_close(h); return ret; } -static int applehttp_read(URLContext *h, uint8_t *buf, int size) +static int hls_read(URLContext *h, uint8_t *buf, int size) { - AppleHTTPContext *s = h->priv_data; + HLSContext *s = h->priv_data; const char *url; int ret; int64_t reload_interval; @@ -329,19 +329,19 @@ retry: #if FF_API_APPLEHTTP_PROTO URLProtocol ff_applehttp_protocol = { .name = "applehttp", - .url_open = applehttp_open, - .url_read = applehttp_read, - .url_close = applehttp_close, + .url_open = hls_open, + .url_read = hls_read, + .url_close = hls_close, .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, - .priv_data_size = sizeof(AppleHTTPContext), + .priv_data_size = sizeof(HLSContext), }; #endif URLProtocol ff_hls_protocol = { .name = "hls", - .url_open = applehttp_open, - .url_read = applehttp_read, - .url_close = applehttp_close, + .url_open = hls_open, + .url_read = hls_read, + .url_close = hls_close, .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, - .priv_data_size = sizeof(AppleHTTPContext), + .priv_data_size = sizeof(HLSContext), }; -- cgit v1.2.3 From c1df37e59b516e9da47f0e9f7df748beafb2fc90 Mon Sep 17 00:00:00 2001 From: Alex Converse Date: Tue, 14 Feb 2012 11:17:00 -0800 Subject: hls: Re-add legacy applehttp name to preserve interface compatibility. --- libavformat/hls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/hls.c b/libavformat/hls.c index d8b00204dc..82aa7647ae 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -700,7 +700,7 @@ static int hls_probe(AVProbeData *p) } AVInputFormat ff_hls_demuxer = { - .name = "hls", + .name = "hls,applehttp", .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"), .priv_data_size = sizeof(HLSContext), .read_probe = hls_probe, -- cgit v1.2.3 From dc4e57489fa0f9cf4faf4c85cc405d6db77d84db Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Tue, 14 Feb 2012 17:36:20 +0000 Subject: CDXL demuxer and decoder Signed-off-by: Paul B Mahol Signed-off-by: Diego Biurrun --- Changelog | 1 + doc/general.texi | 4 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/avcodec.h | 1 + libavcodec/cdxl.c | 278 +++++++++++++++++++++++++++++++++++++++++++++++ libavcodec/version.h | 2 +- libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/cdxl.c | 170 +++++++++++++++++++++++++++++ libavformat/version.h | 2 +- 11 files changed, 460 insertions(+), 2 deletions(-) create mode 100644 libavcodec/cdxl.c create mode 100644 libavformat/cdxl.c diff --git a/Changelog b/Changelog index cc7420cb45..8bcccf499e 100644 --- a/Changelog +++ b/Changelog @@ -6,6 +6,7 @@ version : - XWD encoder and decoder - Support for fragmentation in the mov/mp4 muxer - ISMV (Smooth Streaming) muxer +- CDXL demuxer and decoder version 0.8: diff --git a/doc/general.texi b/doc/general.texi index 50ae764541..14624bc521 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -131,6 +131,8 @@ library: @tab Multimedia format used by Delphine Software games. @item CD+G @tab @tab X @tab Video format used by CD+G karaoke disks +@item Commodore CDXL @tab @tab X + @tab Amiga CD video format @item Core Audio Format @tab @tab X @tab Apple Core Audio Format @item CRC testing format @tab X @tab @@ -435,6 +437,8 @@ following image formats are supported: @tab fourcc: CSCD @item CD+G @tab @tab X @tab Video codec for CD+G karaoke disks +@item CDXL @tab @tab X + @tab Amiga CD video codec @item Chinese AVS video @tab E @tab X @tab AVS1-P2, JiZhun profile, encoding through external library libxavs @item Delphine Software International CIN video @tab @tab X diff --git a/libavcodec/Makefile b/libavcodec/Makefile index a891651f26..2eaef6b651 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -102,6 +102,7 @@ OBJS-$(CONFIG_C93_DECODER) += c93.o OBJS-$(CONFIG_CAVS_DECODER) += cavs.o cavsdec.o cavsdsp.o \ mpeg12data.o mpegvideo.o OBJS-$(CONFIG_CDGRAPHICS_DECODER) += cdgraphics.o +OBJS-$(CONFIG_CDXL_DECODER) += cdxl.o OBJS-$(CONFIG_CINEPAK_DECODER) += cinepak.o OBJS-$(CONFIG_CLJR_DECODER) += cljr.o OBJS-$(CONFIG_CLJR_ENCODER) += cljr.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index cda71e0a1a..1c7217720c 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -86,6 +86,7 @@ void avcodec_register_all(void) REGISTER_DECODER (C93, c93); REGISTER_DECODER (CAVS, cavs); REGISTER_DECODER (CDGRAPHICS, cdgraphics); + REGISTER_DECODER (CDXL, cdxl); REGISTER_DECODER (CINEPAK, cinepak); REGISTER_ENCDEC (CLJR, cljr); REGISTER_DECODER (CSCD, cscd); diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index 6afa140ebd..51b956ba81 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -244,6 +244,7 @@ enum CodecID { CODEC_ID_DXTORY, CODEC_ID_V410, CODEC_ID_XWD, + CODEC_ID_CDXL, /* various PCM "codecs" */ CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs diff --git a/libavcodec/cdxl.c b/libavcodec/cdxl.c new file mode 100644 index 0000000000..a53a001ea4 --- /dev/null +++ b/libavcodec/cdxl.c @@ -0,0 +1,278 @@ +/* + * CDXL video decoder + * Copyright (c) 2011-2012 Paul B Mahol + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/imgutils.h" +#include "avcodec.h" +#include "get_bits.h" + +typedef struct { + AVCodecContext *avctx; + AVFrame frame; + int bpp; + const uint8_t *palette; + int palette_size; + const uint8_t *video; + int video_size; + uint8_t *new_video; + int new_video_size; +} CDXLVideoContext; + +static av_cold int cdxl_decode_init(AVCodecContext *avctx) +{ + CDXLVideoContext *c = avctx->priv_data; + + avcodec_get_frame_defaults(&c->frame); + c->new_video_size = 0; + c->avctx = avctx; + + return 0; +} + +static void import_palette(CDXLVideoContext *c, uint32_t *new_palette) +{ + int i; + + for (i = 0; i < c->palette_size / 2; i++) { + unsigned rgb = AV_RB16(&c->palette[i * 2]); + unsigned r = ((rgb >> 8) & 0xF) * 0x11; + unsigned g = ((rgb >> 4) & 0xF) * 0x11; + unsigned b = (rgb & 0xF) * 0x11; + AV_WN32(&new_palette[i], (r << 16) | (g << 8) | b); + } +} + +static void bitplanar2chunky(CDXLVideoContext *c, int width, + int linesize, uint8_t *out) +{ + GetBitContext gb; + int x, y, plane; + + init_get_bits(&gb, c->video, c->video_size * 8); + memset(out, 0, linesize * c->avctx->height); + for (plane = 0; plane < c->bpp; plane++) + for (y = 0; y < c->avctx->height; y++) + for (x = 0; x < width; x++) + out[linesize * y + x] |= get_bits1(&gb) << plane; +} + +static void cdxl_decode_rgb(CDXLVideoContext *c) +{ + uint32_t *new_palette = (uint32_t *)c->frame.data[1]; + int padded_width = FFALIGN(c->avctx->width, 16); + + import_palette(c, new_palette); + bitplanar2chunky(c, padded_width, c->frame.linesize[0], c->frame.data[0]); +} + +static void cdxl_decode_ham6(CDXLVideoContext *c) +{ + AVCodecContext *avctx = c->avctx; + uint32_t new_palette[16], r, g, b; + uint8_t *ptr, *out, index, op; + int x, y; + + ptr = c->new_video; + out = c->frame.data[0]; + + import_palette(c, new_palette); + bitplanar2chunky(c, avctx->width, avctx->width, c->new_video); + + for (y = 0; y < avctx->height; y++) { + r = new_palette[0] & 0xFF0000; + g = new_palette[0] & 0xFF00; + b = new_palette[0] & 0xFF; + for (x = 0; x < avctx->width; x++) { + index = *ptr++; + op = index >> 4; + index &= 15; + switch (op) { + case 0: + r = new_palette[index] & 0xFF0000; + g = new_palette[index] & 0xFF00; + b = new_palette[index] & 0xFF; + break; + case 1: + b = index * 0x11; + break; + case 2: + r = index * 0x11 << 16; + break; + case 3: + g = index * 0x11 << 8; + break; + } + AV_WN32(out + x * 3, r | g | b); + } + out += c->frame.linesize[0]; + } +} + +static void cdxl_decode_ham8(CDXLVideoContext *c) +{ + AVCodecContext *avctx = c->avctx; + uint32_t new_palette[64], r, g, b; + uint8_t *ptr, *out, index, op; + int x, y; + + ptr = c->new_video; + out = c->frame.data[0]; + + import_palette(c, new_palette); + bitplanar2chunky(c, avctx->width, avctx->width, c->new_video); + + for (y = 0; y < avctx->height; y++) { + r = new_palette[0] & 0xFF0000; + g = new_palette[0] & 0xFF00; + b = new_palette[0] & 0xFF; + for (x = 0; x < avctx->width; x++) { + index = *ptr++; + op = index >> 6; + index &= 63; + switch (op) { + case 0: + r = new_palette[index] & 0xFF0000; + g = new_palette[index] & 0xFF00; + b = new_palette[index] & 0xFF; + break; + case 1: + b = (index << 2) | (b & 3); + break; + case 2: + r = (index << 18) | (r & (3 << 16)); + break; + case 3: + g = (index << 10) | (g & (3 << 8)); + break; + } + AV_WN32(out + x * 3, r | g | b); + } + out += c->frame.linesize[0]; + } +} + +static int cdxl_decode_frame(AVCodecContext *avctx, void *data, + int *data_size, AVPacket *pkt) +{ + CDXLVideoContext *c = avctx->priv_data; + AVFrame * const p = &c->frame; + int ret, w, h, encoding, format, buf_size = pkt->size; + const uint8_t *buf = pkt->data; + + if (buf_size < 32) + return AVERROR_INVALIDDATA; + encoding = buf[1] & 7; + format = buf[1] & 0xE0; + w = AV_RB16(&buf[14]); + h = AV_RB16(&buf[16]); + c->bpp = buf[19]; + c->palette_size = AV_RB16(&buf[20]); + c->palette = buf + 32; + c->video = c->palette + c->palette_size; + c->video_size = buf_size - c->palette_size - 32; + + if (c->palette_size > 512) + return AVERROR_INVALIDDATA; + if (buf_size < c->palette_size + 32) + return AVERROR_INVALIDDATA; + if (c->bpp < 1) + return AVERROR_INVALIDDATA; + if (c->bpp > 8) { + av_log_ask_for_sample(avctx, "unsupported pixel size: %d\n", c->bpp); + return AVERROR_PATCHWELCOME; + } + if (format) { + av_log_ask_for_sample(avctx, "unsupported pixel format: %d\n", format); + return AVERROR_PATCHWELCOME; + } + + if ((ret = av_image_check_size(w, h, 0, avctx)) < 0) + return ret; + if (w != avctx->width || h != avctx->height) + avcodec_set_dimensions(avctx, w, h); + + if (encoding == 0) { + if (c->video_size < FFALIGN(avctx->width, 16) * + avctx->height * c->bpp / 8) + return AVERROR_INVALIDDATA; + avctx->pix_fmt = PIX_FMT_PAL8; + } else if (encoding == 1 && (c->bpp == 6 || c->bpp == 8)) { + if (c->palette_size != (1 << (c->bpp - 1))) + return AVERROR_INVALIDDATA; + if (c->video_size < avctx->width * avctx->height * c->bpp / 8) + return AVERROR_INVALIDDATA; + avctx->pix_fmt = PIX_FMT_BGR24; + } else { + av_log_ask_for_sample(avctx, "unsupported encoding %d and bpp %d\n", + encoding, c->bpp); + return AVERROR_PATCHWELCOME; + } + + if (p->data[0]) + avctx->release_buffer(avctx, p); + + p->reference = 0; + if ((ret = avctx->get_buffer(avctx, p)) < 0) { + av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n"); + return ret; + } + p->pict_type = AV_PICTURE_TYPE_I; + + if (encoding) { + av_fast_padded_malloc(&c->new_video, &c->new_video_size, + h * w + FF_INPUT_BUFFER_PADDING_SIZE); + if (!c->new_video) + return AVERROR(ENOMEM); + if (c->bpp == 8) + cdxl_decode_ham8(c); + else + cdxl_decode_ham6(c); + } else { + cdxl_decode_rgb(c); + } + *data_size = sizeof(AVFrame); + *(AVFrame*)data = c->frame; + + return buf_size; +} + +static av_cold int cdxl_decode_end(AVCodecContext *avctx) +{ + CDXLVideoContext *c = avctx->priv_data; + + av_free(c->new_video); + if (c->frame.data[0]) + avctx->release_buffer(avctx, &c->frame); + + return 0; +} + +AVCodec ff_cdxl_decoder = { + .name = "cdxl", + .type = AVMEDIA_TYPE_VIDEO, + .id = CODEC_ID_CDXL, + .priv_data_size = sizeof(CDXLVideoContext), + .init = cdxl_decode_init, + .close = cdxl_decode_end, + .decode = cdxl_decode_frame, + .capabilities = CODEC_CAP_DR1, + .long_name = NULL_IF_CONFIG_SMALL("Commodore CDXL video"), +}; diff --git a/libavcodec/version.h b/libavcodec/version.h index 485b60e993..56aadb24a4 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -21,7 +21,7 @@ #define AVCODEC_VERSION_H #define LIBAVCODEC_VERSION_MAJOR 54 -#define LIBAVCODEC_VERSION_MINOR 0 +#define LIBAVCODEC_VERSION_MINOR 1 #define LIBAVCODEC_VERSION_MICRO 0 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ diff --git a/libavformat/Makefile b/libavformat/Makefile index dd55e42db4..aa3cf7c1d8 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -55,6 +55,7 @@ OBJS-$(CONFIG_CAF_DEMUXER) += cafdec.o caf.o mov.o mov_chan.o \ OBJS-$(CONFIG_CAVSVIDEO_DEMUXER) += cavsvideodec.o rawdec.o OBJS-$(CONFIG_CAVSVIDEO_MUXER) += rawenc.o OBJS-$(CONFIG_CDG_DEMUXER) += cdg.o +OBJS-$(CONFIG_CDXL_DEMUXER) += cdxl.o OBJS-$(CONFIG_CRC_MUXER) += crcenc.o OBJS-$(CONFIG_DAUD_DEMUXER) += daud.o OBJS-$(CONFIG_DAUD_MUXER) += daud.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index a60688c362..f5be7aacb1 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -76,6 +76,7 @@ void av_register_all(void) REGISTER_DEMUXER (CAF, caf); REGISTER_MUXDEMUX (CAVSVIDEO, cavsvideo); REGISTER_DEMUXER (CDG, cdg); + REGISTER_DEMUXER (CDXL, cdxl); REGISTER_MUXER (CRC, crc); REGISTER_MUXDEMUX (DAUD, daud); REGISTER_DEMUXER (DFA, dfa); diff --git a/libavformat/cdxl.c b/libavformat/cdxl.c new file mode 100644 index 0000000000..f2956dd2f2 --- /dev/null +++ b/libavformat/cdxl.c @@ -0,0 +1,170 @@ +/* + * CDXL demuxer + * Copyright (c) 2011-2012 Paul B Mahol + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/parseutils.h" +#include "libavutil/opt.h" +#include "avformat.h" +#include "internal.h" + +#define CDXL_HEADER_SIZE 32 + +typedef struct CDXLDemuxContext { + AVClass *class; + int sample_rate; + char *framerate; + AVRational fps; + int read_chunk; + uint8_t header[CDXL_HEADER_SIZE]; + int video_stream_index; + int audio_stream_index; +} CDXLDemuxContext; + +static int cdxl_read_header(AVFormatContext *s) +{ + CDXLDemuxContext *cdxl = s->priv_data; + int ret; + + if ((ret = av_parse_video_rate(&cdxl->fps, cdxl->framerate)) < 0) { + av_log(s, AV_LOG_ERROR, + "Could not parse framerate: %s.\n", cdxl->framerate); + return ret; + } + + cdxl->read_chunk = 0; + cdxl->video_stream_index = -1; + cdxl->audio_stream_index = -1; + + s->ctx_flags |= AVFMTCTX_NOHEADER; + + return 0; +} + +static int cdxl_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + CDXLDemuxContext *cdxl = s->priv_data; + AVIOContext *pb = s->pb; + uint32_t current_size; + uint16_t audio_size, palette_size; + int32_t video_size; + int64_t pos; + int ret; + + if (pb->eof_reached) + return AVERROR_EOF; + + pos = avio_tell(pb); + if (!cdxl->read_chunk && + avio_read(pb, cdxl->header, CDXL_HEADER_SIZE) != CDXL_HEADER_SIZE) + return AVERROR_EOF; + if (cdxl->header[0] != 1) { + av_log(s, AV_LOG_ERROR, "non-standard cdxl file\n"); + return AVERROR_INVALIDDATA; + } + + current_size = AV_RB32(&cdxl->header[2]); + palette_size = AV_RB16(&cdxl->header[20]); + audio_size = AV_RB16(&cdxl->header[22]); + + if (palette_size > 512) + return AVERROR_INVALIDDATA; + if (current_size < audio_size + palette_size + CDXL_HEADER_SIZE) + return AVERROR_INVALIDDATA; + video_size = current_size - audio_size - CDXL_HEADER_SIZE; + + if (cdxl->read_chunk && audio_size) { + if (cdxl->audio_stream_index == -1) { + AVStream *st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_tag = 0; + st->codec->codec_id = CODEC_ID_PCM_S8; + st->codec->channels = cdxl->header[1] & 0x10 ? 2 : 1; + st->codec->sample_rate = cdxl->sample_rate; + cdxl->audio_stream_index = st->index; + avpriv_set_pts_info(st, 32, 1, cdxl->sample_rate); + } + + ret = av_get_packet(pb, pkt, audio_size); + if (ret < 0) + return ret; + pkt->stream_index = cdxl->audio_stream_index; + pkt->pos = pos; + pkt->duration = audio_size; + cdxl->read_chunk = 0; + } else { + if (cdxl->video_stream_index == -1) { + AVStream *st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_tag = 0; + st->codec->codec_id = CODEC_ID_CDXL; + st->codec->width = AV_RB16(&cdxl->header[14]); + st->codec->height = AV_RB16(&cdxl->header[16]); + cdxl->video_stream_index = st->index; + avpriv_set_pts_info(st, 63, cdxl->fps.den, cdxl->fps.num); + } + + if (av_new_packet(pkt, video_size + CDXL_HEADER_SIZE) < 0) + return AVERROR(ENOMEM); + memcpy(pkt->data, cdxl->header, CDXL_HEADER_SIZE); + ret = avio_read(pb, pkt->data + CDXL_HEADER_SIZE, video_size); + if (ret < 0) { + av_free_packet(pkt); + return ret; + } + pkt->stream_index = cdxl->video_stream_index; + pkt->flags |= AV_PKT_FLAG_KEY; + pkt->pos = pos; + cdxl->read_chunk = audio_size; + } + + return ret; +} + +#define OFFSET(x) offsetof(CDXLDemuxContext, x) +static const AVOption cdxl_options[] = { + { "sample_rate", "", OFFSET(sample_rate), AV_OPT_TYPE_INT, { .dbl = 11025 }, 1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, + { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, { .str = "10" }, 0, 0, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass cdxl_demuxer_class = { + .class_name = "CDXL demuxer", + .item_name = av_default_item_name, + .option = cdxl_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_cdxl_demuxer = { + .name = "cdxl", + .long_name = NULL_IF_CONFIG_SMALL("Commodore CDXL video format"), + .priv_data_size = sizeof(CDXLDemuxContext), + .read_header = cdxl_read_header, + .read_packet = cdxl_read_packet, + .extensions = "cdxl,xl", + .flags = AVFMT_GENERIC_INDEX, + .priv_class = &cdxl_demuxer_class, +}; diff --git a/libavformat/version.h b/libavformat/version.h index 443d752b8a..b944589e2f 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -30,7 +30,7 @@ #include "libavutil/avutil.h" #define LIBAVFORMAT_VERSION_MAJOR 54 -#define LIBAVFORMAT_VERSION_MINOR 0 +#define LIBAVFORMAT_VERSION_MINOR 1 #define LIBAVFORMAT_VERSION_MICRO 0 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ -- cgit v1.2.3