libavformat/swfdec.c
3b35f4ab
 /*
376aefdd
  * Flash Compatible Streaming Format demuxer
406792e7
  * Copyright (c) 2000 Fabrice Bellard
  * Copyright (c) 2003 Tinic Uro
3b35f4ab
  *
  * This file is part of FFmpeg.
  *
  * FFmpeg 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.
  *
  * FFmpeg 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 FFmpeg; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
711ffb84
 #include "libavutil/avassert.h"
a3949fe1
 #include "libavutil/channel_layout.h"
711ffb84
 #include "libavutil/imgutils.h"
6a5d31ac
 #include "libavutil/intreadwrite.h"
3b35f4ab
 #include "swf.h"
 
2804ba6a
 static const AVCodecTag swf_audio_codec_tags[] = {
15c71dfd
     { AV_CODEC_ID_PCM_S16LE,  0x00 },
     { AV_CODEC_ID_ADPCM_SWF,  0x01 },
     { AV_CODEC_ID_MP3,        0x02 },
     { AV_CODEC_ID_PCM_S16LE,  0x03 },
 //  { AV_CODEC_ID_NELLYMOSER, 0x06 },
     { AV_CODEC_ID_NONE,          0 },
2804ba6a
 };
 
471fe57e
 static int get_swf_tag(AVIOContext *pb, int *len_ptr)
3b35f4ab
 {
     int tag, len;
 
     if (url_feof(pb))
99d7d151
         return AVERROR_EOF;
3b35f4ab
 
e63a3628
     tag = avio_rl16(pb);
3b35f4ab
     len = tag & 0x3f;
     tag = tag >> 6;
     if (len == 0x3f) {
e63a3628
         len = avio_rl32(pb);
3b35f4ab
     }
     *len_ptr = len;
     return tag;
 }
 
 
 static int swf_probe(AVProbeData *p)
 {
     /* check file header */
     if ((p->buf[0] == 'F' || p->buf[0] == 'C') && p->buf[1] == 'W' &&
         p->buf[2] == 'S')
         return AVPROBE_SCORE_MAX;
     else
         return 0;
 }
 
588eaa10
 #if CONFIG_ZLIB
 static int zlib_refill(void *opaque, uint8_t *buf, int buf_size)
 {
     AVFormatContext *s = opaque;
     SWFContext *swf = s->priv_data;
     z_stream *z = &swf->zstream;
     int ret;
 
 retry:
     if (!z->avail_in) {
         int n = avio_read(s->pb, swf->zbuf_in, ZBUF_SIZE);
0c40220b
         if (n < 0)
588eaa10
             return n;
         z->next_in  = swf->zbuf_in;
         z->avail_in = n;
     }
 
     z->next_out  = buf;
     z->avail_out = buf_size;
 
     ret = inflate(z, Z_NO_FLUSH);
     if (ret < 0)
         return AVERROR(EINVAL);
     if (ret == Z_STREAM_END)
         return AVERROR_EOF;
 
     if (buf_size - z->avail_out == 0)
         goto retry;
 
     return buf_size - z->avail_out;
 }
 #endif
 
6e9651d1
 static int swf_read_header(AVFormatContext *s)
3b35f4ab
 {
     SWFContext *swf = s->priv_data;
471fe57e
     AVIOContext *pb = s->pb;
3b35f4ab
     int nbits, len, tag;
 
e63a3628
     tag = avio_rb32(pb) & 0xffffff00;
588eaa10
     avio_rl32(pb);
3b35f4ab
 
     if (tag == MKBETAG('C', 'W', 'S', 0)) {
588eaa10
         av_log(s, AV_LOG_INFO, "SWF compressed file detected\n");
 #if CONFIG_ZLIB
         swf->zbuf_in  = av_malloc(ZBUF_SIZE);
         swf->zbuf_out = av_malloc(ZBUF_SIZE);
         swf->zpb = avio_alloc_context(swf->zbuf_out, ZBUF_SIZE, 0, s,
                                       zlib_refill, NULL, NULL);
         if (!swf->zbuf_in || !swf->zbuf_out || !swf->zpb)
             return AVERROR(ENOMEM);
         swf->zpb->seekable = 0;
         if (inflateInit(&swf->zstream) != Z_OK) {
             av_log(s, AV_LOG_ERROR, "Unable to init zlib context\n");
             return AVERROR(EINVAL);
         }
         pb = swf->zpb;
 #else
         av_log(s, AV_LOG_ERROR, "zlib support is required to read SWF compressed files\n");
3b35f4ab
         return AVERROR(EIO);
588eaa10
 #endif
     } else if (tag != MKBETAG('F', 'W', 'S', 0))
3b35f4ab
         return AVERROR(EIO);
     /* skip rectangle size */
e63a3628
     nbits = avio_r8(pb) >> 3;
3b35f4ab
     len = (4 * nbits - 3 + 7) / 8;
45a8a02a
     avio_skip(pb, len);
e63a3628
     swf->frame_rate = avio_rl16(pb); /* 8.8 fixed */
     avio_rl16(pb); /* frame count */
3b35f4ab
 
     swf->samples_per_frame = 0;
     s->ctx_flags |= AVFMTCTX_NOHEADER;
     return 0;
 }
 
9a0076f5
 static AVStream *create_new_audio_stream(AVFormatContext *s, int id, int info)
 {
624fb5f9
     int sample_rate_code, sample_size_code;
9a0076f5
     AVStream *ast = avformat_new_stream(s, NULL);
     if (!ast)
         return NULL;
     ast->id = id;
     if (info & 1) {
         ast->codec->channels       = 2;
         ast->codec->channel_layout = AV_CH_LAYOUT_STEREO;
     } else {
         ast->codec->channels       = 1;
         ast->codec->channel_layout = AV_CH_LAYOUT_MONO;
     }
     ast->codec->codec_type = AVMEDIA_TYPE_AUDIO;
     ast->codec->codec_id   = ff_codec_get_id(swf_audio_codec_tags, info>>4 & 15);
     ast->need_parsing = AVSTREAM_PARSE_FULL;
     sample_rate_code = info>>2 & 3;
624fb5f9
     sample_size_code = info>>1 & 1;
     if (!sample_size_code && ast->codec->codec_id == AV_CODEC_ID_PCM_S16LE)
         ast->codec->codec_id = AV_CODEC_ID_PCM_U8;
9a0076f5
     ast->codec->sample_rate = 44100 >> (3 - sample_rate_code);
     avpriv_set_pts_info(ast, 64, 1, ast->codec->sample_rate);
     return ast;
 }
 
3b35f4ab
 static int swf_read_packet(AVFormatContext *s, AVPacket *pkt)
 {
     SWFContext *swf = s->priv_data;
471fe57e
     AVIOContext *pb = s->pb;
3b35f4ab
     AVStream *vst = NULL, *ast = NULL, *st = 0;
31632e73
     int tag, len, i, frame, v, res;
3b35f4ab
 
588eaa10
 #if CONFIG_ZLIB
     if (swf->zpb)
         pb = swf->zpb;
 #endif
 
3b35f4ab
     for(;;) {
384c9c2f
         uint64_t pos = avio_tell(pb);
3b35f4ab
         tag = get_swf_tag(pb, &len);
         if (tag < 0)
99d7d151
             return tag;
c7368539
         if (len < 0) {
e70c5b03
             av_log(s, AV_LOG_ERROR, "invalid tag length: %d\n", len);
c7368539
             return AVERROR_INVALIDDATA;
         }
4e35ffa9
         if (tag == TAG_VIDEOSTREAM) {
e63a3628
             int ch_id = avio_rl16(pb);
4e35ffa9
             len -= 2;
 
             for (i=0; i<s->nb_streams; i++) {
                 st = s->streams[i];
72415b2a
                 if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && st->id == ch_id)
4e35ffa9
                     goto skip;
             }
 
e63a3628
             avio_rl16(pb);
             avio_rl16(pb);
             avio_rl16(pb);
             avio_r8(pb);
3b35f4ab
             /* Check for FLV1 */
84ad31ff
             vst = avformat_new_stream(s, NULL);
3b35f4ab
             if (!vst)
cd78192d
                 return AVERROR(ENOMEM);
84ad31ff
             vst->id = ch_id;
72415b2a
             vst->codec->codec_type = AVMEDIA_TYPE_VIDEO;
90f7e617
             vst->codec->codec_id = ff_codec_get_id(ff_swf_codec_tags, avio_r8(pb));
c3f9ebf7
             avpriv_set_pts_info(vst, 16, 256, swf->frame_rate);
4e35ffa9
             len -= 8;
         } else if (tag == TAG_STREAMHEAD || tag == TAG_STREAMHEAD2) {
3b35f4ab
             /* streaming found */
4e35ffa9
 
             for (i=0; i<s->nb_streams; i++) {
                 st = s->streams[i];
72415b2a
                 if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && st->id == -1)
4e35ffa9
                     goto skip;
             }
 
e63a3628
             avio_r8(pb);
             v = avio_r8(pb);
             swf->samples_per_frame = avio_rl16(pb);
9a0076f5
             ast = create_new_audio_stream(s, -1, v); /* -1 to avoid clash with video stream ch_id */
3b35f4ab
             if (!ast)
cd78192d
                 return AVERROR(ENOMEM);
3b35f4ab
             len -= 4;
a41c824c
         } else if (tag == TAG_DEFINESOUND) {
             /* audio stream */
             int ch_id = avio_rl16(pb);
 
             for (i=0; i<s->nb_streams; i++) {
                 st = s->streams[i];
                 if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && st->id == ch_id)
                     goto skip;
             }
 
             // FIXME: The entire audio stream is stored in a single chunk/tag. Normally,
             // these are smaller audio streams in DEFINESOUND tags, but it's technically
             // possible they could be huge. Break it up into multiple packets if it's big.
             v = avio_r8(pb);
9a0076f5
             ast = create_new_audio_stream(s, ch_id, v);
a41c824c
             if (!ast)
cd78192d
                 return AVERROR(ENOMEM);
a41c824c
             ast->duration = avio_rl32(pb); // number of samples
             if (((v>>4) & 15) == 2) { // MP3 sound data record
                 ast->skip_samples = avio_rl16(pb);
                 len -= 2;
             }
             len -= 7;
             if ((res = av_get_packet(pb, pkt, len)) < 0)
                 return res;
             pkt->pos = pos;
             pkt->stream_index = ast->index;
             return pkt->size;
3b35f4ab
         } else if (tag == TAG_VIDEOFRAME) {
e63a3628
             int ch_id = avio_rl16(pb);
3b35f4ab
             len -= 2;
             for(i=0; i<s->nb_streams; i++) {
                 st = s->streams[i];
72415b2a
                 if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && st->id == ch_id) {
e63a3628
                     frame = avio_rl16(pb);
e70c5b03
                     len -= 2;
                     if (len <= 0)
                         goto skip;
                     if ((res = av_get_packet(pb, pkt, len)) < 0)
31632e73
                         return res;
7e04cfba
                     pkt->pos = pos;
3b35f4ab
                     pkt->pts = frame;
                     pkt->stream_index = st->index;
                     return pkt->size;
                 }
             }
711ffb84
         } else if (tag == TAG_DEFINEBITSLOSSLESS || tag == TAG_DEFINEBITSLOSSLESS2) {
 #if CONFIG_ZLIB
             long out_len;
             uint8_t *buf = NULL, *zbuf = NULL, *pal;
             uint32_t colormap[AVPALETTE_COUNT] = {0};
             const int alpha_bmp = tag == TAG_DEFINEBITSLOSSLESS2;
             const int colormapbpp = 3 + alpha_bmp;
             int linesize, colormapsize = 0;
 
             const int ch_id   = avio_rl16(pb);
             const int bmp_fmt = avio_r8(pb);
             const int width   = avio_rl16(pb);
             const int height  = avio_rl16(pb);
 
             len -= 2+1+2+2;
 
             switch (bmp_fmt) {
             case 3: // PAL-8
                 linesize = width;
                 colormapsize = avio_r8(pb) + 1;
                 len--;
                 break;
             case 4: // RGB15
                 linesize = width * 2;
                 break;
             case 5: // RGB24 (0RGB)
                 linesize = width * 4;
                 break;
             default:
                 av_log(s, AV_LOG_ERROR, "invalid bitmap format %d, skipped\n", bmp_fmt);
                 goto bitmap_end_skip;
             }
 
             linesize = FFALIGN(linesize, 4);
 
             if (av_image_check_size(width, height, 0, s) < 0 ||
                 linesize >= INT_MAX / height ||
                 linesize * height >= INT_MAX - colormapsize * colormapbpp) {
                 av_log(s, AV_LOG_ERROR, "invalid frame size %dx%d\n", width, height);
                 goto bitmap_end_skip;
             }
 
             out_len = colormapsize * colormapbpp + linesize * height;
 
             av_dlog(s, "bitmap: ch=%d fmt=%d %dx%d (linesize=%d) len=%d->%ld pal=%d\n",
                     ch_id, bmp_fmt, width, height, linesize, len, out_len, colormapsize);
 
             zbuf = av_malloc(len);
             buf  = av_malloc(out_len);
             if (!zbuf || !buf) {
                 res = AVERROR(ENOMEM);
                 goto bitmap_end;
             }
 
             len = avio_read(pb, zbuf, len);
             if (len < 0 || (res = uncompress(buf, &out_len, zbuf, len)) != Z_OK) {
                 av_log(s, AV_LOG_WARNING, "Failed to uncompress one bitmap\n");
                 goto bitmap_end_skip;
             }
 
             for (i = 0; i < s->nb_streams; i++) {
                 st = s->streams[i];
                 if (st->codec->codec_id == AV_CODEC_ID_RAWVIDEO && st->id == -3)
                     break;
             }
             if (i == s->nb_streams) {
                 vst = avformat_new_stream(s, NULL);
                 if (!vst) {
                     res = AVERROR(ENOMEM);
                     goto bitmap_end;
                 }
                 vst->id = -3; /* -3 to avoid clash with video stream and audio stream */
                 vst->codec->codec_type = AVMEDIA_TYPE_VIDEO;
                 vst->codec->codec_id = AV_CODEC_ID_RAWVIDEO;
                 avpriv_set_pts_info(vst, 64, 256, swf->frame_rate);
                 st = vst;
             }
             st->codec->width  = width;
             st->codec->height = height;
 
             if ((res = av_new_packet(pkt, out_len - colormapsize * colormapbpp)) < 0)
                 goto bitmap_end;
             pkt->pos = pos;
             pkt->stream_index = st->index;
 
             switch (bmp_fmt) {
             case 3:
                 st->codec->pix_fmt = AV_PIX_FMT_PAL8;
                 for (i = 0; i < colormapsize; i++)
                     if (alpha_bmp)  colormap[i] = buf[3]<<24 | AV_RB24(buf + 4*i);
                     else            colormap[i] = 0xffU <<24 | AV_RB24(buf + 3*i);
                 pal = av_packet_new_side_data(pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE);
                 if (!pal) {
                     res = AVERROR(ENOMEM);
                     goto bitmap_end;
                 }
                 memcpy(pal, colormap, AVPALETTE_SIZE);
                 break;
             case 4:
                 st->codec->pix_fmt = AV_PIX_FMT_RGB555;
                 break;
             case 5:
                 st->codec->pix_fmt = alpha_bmp ? AV_PIX_FMT_ARGB : AV_PIX_FMT_0RGB;
                 break;
             default:
                 av_assert0(0);
             }
abe68364
 
             if (linesize * height > pkt->size) {
                 res = AVERROR_INVALIDDATA;
                 goto bitmap_end;
             }
711ffb84
             memcpy(pkt->data, buf + colormapsize*colormapbpp, linesize * height);
 
             res = pkt->size;
 
 bitmap_end:
             av_freep(&zbuf);
             av_freep(&buf);
             return res;
 bitmap_end_skip:
             av_freep(&zbuf);
             av_freep(&buf);
 #else
             av_log(s, AV_LOG_ERROR, "this file requires zlib support compiled in\n");
 #endif
3b35f4ab
         } else if (tag == TAG_STREAMBLOCK) {
4e35ffa9
             for (i = 0; i < s->nb_streams; i++) {
                 st = s->streams[i];
72415b2a
                 if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && st->id == -1) {
d04c17c9
                     if (st->codec->codec_id == AV_CODEC_ID_MP3) {
                         avio_skip(pb, 4);
                         len -= 4;
                         if (len <= 0)
                             goto skip;
                         if ((res = av_get_packet(pb, pkt, len)) < 0)
                             return res;
                     } else { // ADPCM, PCM
                         if (len <= 0)
                             goto skip;
                         if ((res = av_get_packet(pb, pkt, len)) < 0)
                             return res;
                     }
                     pkt->pos          = pos;
                     pkt->stream_index = st->index;
                     return pkt->size;
4e35ffa9
                 }
             }
3b35f4ab
         } else if (tag == TAG_JPEG2) {
             for (i=0; i<s->nb_streams; i++) {
                 st = s->streams[i];
36ef5369
                 if (st->codec->codec_id == AV_CODEC_ID_MJPEG && st->id == -2)
3b35f4ab
                     break;
             }
             if (i == s->nb_streams) {
84ad31ff
                 vst = avformat_new_stream(s, NULL);
3b35f4ab
                 if (!vst)
cd78192d
                     return AVERROR(ENOMEM);
84ad31ff
                 vst->id = -2; /* -2 to avoid clash with video stream and audio stream */
72415b2a
                 vst->codec->codec_type = AVMEDIA_TYPE_VIDEO;
36ef5369
                 vst->codec->codec_id = AV_CODEC_ID_MJPEG;
c3f9ebf7
                 avpriv_set_pts_info(vst, 64, 256, swf->frame_rate);
3b35f4ab
                 st = vst;
             }
e63a3628
             avio_rl16(pb); /* BITMAP_ID */
e70c5b03
             len -= 2;
             if (len < 4)
                 goto skip;
             if ((res = av_new_packet(pkt, len)) < 0)
31632e73
                 return res;
e63a3628
             avio_read(pb, pkt->data, 4);
3b35f4ab
             if (AV_RB32(pkt->data) == 0xffd8ffd9 ||
                 AV_RB32(pkt->data) == 0xffd9ffd8) {
                 /* old SWF files containing SOI/EOI as data start */
                 /* files created by swink have reversed tag */
                 pkt->size -= 4;
e63a3628
                 avio_read(pb, pkt->data, pkt->size);
3b35f4ab
             } else {
e63a3628
                 avio_read(pb, pkt->data + 4, pkt->size - 4);
3b35f4ab
             }
7e04cfba
             pkt->pos = pos;
3b35f4ab
             pkt->stream_index = st->index;
             return pkt->size;
0877f64f
         } else {
             av_log(s, AV_LOG_DEBUG, "Unknown tag: %d\n", tag);
3b35f4ab
         }
4e35ffa9
     skip:
d276f28b
         if(len<0)
             av_log(s, AV_LOG_WARNING, "Cliping len %d\n", len);
e70c5b03
         len = FFMAX(0, len);
45a8a02a
         avio_skip(pb, len);
3b35f4ab
     }
 }
 
588eaa10
 #if CONFIG_ZLIB
 static av_cold int swf_read_close(AVFormatContext *avctx)
 {
     SWFContext *s = avctx->priv_data;
     inflateEnd(&s->zstream);
     av_freep(&s->zbuf_in);
     av_freep(&s->zbuf_out);
     av_freep(&s->zpb);
     return 0;
 }
 #endif
 
66355be3
 AVInputFormat ff_swf_demuxer = {
dfc2c4d9
     .name           = "swf",
0177b7d2
     .long_name      = NULL_IF_CONFIG_SMALL("SWF (ShockWave Flash)"),
dfc2c4d9
     .priv_data_size = sizeof(SWFContext),
     .read_probe     = swf_probe,
     .read_header    = swf_read_header,
     .read_packet    = swf_read_packet,
588eaa10
 #if CONFIG_ZLIB
     .read_close     = swf_read_close,
 #endif
3b35f4ab
 };