/* * Vividas VIV format Demuxer * Copyright (c) 2012 Krzysztof Klinikowski * Copyright (c) 2010 Andrzej Szombierski * based on vivparse Copyright (c) 2007 Måns Rullgård * * 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 */ /** * @file * @brief Vividas VIV (.viv) file demuxer * @author Andrzej Szombierski [qq at kuku eu org] (2010-07) * @sa http://wiki.multimedia.cx/index.php?title=Vividas_VIV */ #include "libavutil/avassert.h" #include "libavutil/intreadwrite.h" #include "libavutil/mem.h" #include "avio_internal.h" #include "avformat.h" #include "demux.h" #include "internal.h" #define MAX_AUDIO_SUBPACKETS 100 typedef struct VIV_SB_block { int size, n_packets; int64_t byte_offset; int64_t packet_offset; } VIV_SB_block; typedef struct VIV_SB_entry { int size, flag; } VIV_SB_entry; typedef struct VIV_AudioSubpacket { int start, pcm_bytes; } VIV_AudioSubpacket; typedef struct VividasDemuxContext { int n_sb_blocks; VIV_SB_block *sb_blocks; int num_audio; uint32_t sb_key; int64_t sb_offset; int current_sb, current_sb_entry; uint8_t *sb_buf; AVIOContext *sb_pb; int n_sb_entries; VIV_SB_entry *sb_entries; int n_audio_subpackets; int current_audio_subpacket; int64_t audio_sample; VIV_AudioSubpacket audio_subpackets[MAX_AUDIO_SUBPACKETS]; } VividasDemuxContext; static int viv_probe(const AVProbeData *p) { if (memcmp(p->buf, "vividas03", 9)) return 0; return AVPROBE_SCORE_MAX; } static const uint8_t keybits[32] = { 20, 52, 111, 10, 27, 71, 142, 53, 82, 138, 1, 78, 86, 121, 183, 85, 105, 152, 39, 140, 172, 11, 64, 144, 155, 6, 71, 163, 186, 49, 126, 43, }; static uint32_t decode_key(uint8_t *buf) { uint32_t key = 0; for (int i = 0; i < 32; i++) { unsigned p = keybits[i]; key |= ((buf[p] >> ((i*5+3)&7)) & 1u) << i; } return key; } static void put_v(uint8_t *p, unsigned v) { if (v>>28) *p++ = ((v>>28)&0x7f)|0x80; if (v>>21) *p++ = ((v>>21)&0x7f)|0x80; if (v>>14) *p++ = ((v>>14)&0x7f)|0x80; if (v>>7) *p++ = ((v>>7)&0x7f)|0x80; } static unsigned recover_key(unsigned char sample[4], unsigned expected_size) { unsigned char plaintext[8] = { 'S', 'B' }; put_v(plaintext+2, expected_size); return AV_RL32(sample) ^ AV_RL32(plaintext); } static void xor_block(void *p1, void *p2, unsigned size, int key, unsigned *key_ptr) { unsigned *d1 = p1; unsigned *d2 = p2; unsigned k = *key_ptr; size >>= 2; while (size > 0) { *d2 = *d1 ^ (HAVE_BIGENDIAN ? av_bswap32(k) : k); k += key; d1++; d2++; size--; } *key_ptr = k; } static void decode_block(uint8_t *src, uint8_t *dest, unsigned size, uint32_t key, uint32_t *key_ptr, int align) { unsigned s = size; char tmp[4]; int a2; if (!size) return; align &= 3; a2 = (4 - align) & 3; if (align) { uint32_t tmpkey = *key_ptr - key; if (a2 > s) { a2 = s; avpriv_request_sample(NULL, "tiny aligned block"); } memcpy(tmp + align, src, a2); xor_block(tmp, tmp, 4, key, &tmpkey); memcpy(dest, tmp + align, a2); s -= a2; } if (s >= 4) { xor_block(src + a2, dest + a2, s & ~3, key, key_ptr); s &= 3; } if (s) { size -= s; memcpy(tmp, src + size, s); xor_block(&tmp, &tmp, 4, key, key_ptr); memcpy(dest + size, tmp, s); } } static uint32_t get_v(uint8_t *p, int len) { uint32_t v = 0; const uint8_t *end = p + len; do { if (p >= end || v >= UINT_MAX / 128 - *p) return v; v <<= 7; v += *p & 0x7f; } while (*p++ & 0x80); return v; } static uint8_t *read_vblock(AVIOContext *src, uint32_t *size, uint32_t key, uint32_t *k2, int align) { uint8_t tmp[4]; uint8_t *buf; unsigned n; if (avio_read(src, tmp, 4) != 4) return NULL; decode_block(tmp, tmp, 4, key, k2, align); n = get_v(tmp, 4); if (n < 4) return NULL; buf = av_malloc(n); if (!buf) return NULL; *size = n; n -= 4; memcpy(buf, tmp, 4); if (avio_read(src, buf + 4, n) == n) { decode_block(buf + 4, buf + 4, n, key, k2, align); } else { av_free(buf); buf = NULL; } return buf; } static uint8_t *read_sb_block(AVIOContext *src, unsigned *size, uint32_t *key, unsigned expected_size) { uint8_t *buf; uint8_t ibuf[8], sbuf[8]; uint32_t k2; unsigned n; if (avio_read(src, ibuf, 8) < 8) return NULL; k2 = *key; decode_block(ibuf, sbuf, 8, *key, &k2, 0); n = get_v(sbuf+2, 6); if (sbuf[0] != 'S' || sbuf[1] != 'B' || (expected_size>0 && n != expected_size)) { uint32_t tmpkey = recover_key(ibuf, expected_size); k2 = tmpkey; decode_block(ibuf, sbuf, 8, tmpkey, &k2, 0); n = get_v(sbuf+2, 6); if (sbuf[0] != 'S' || sbuf[1] != 'B' || expected_size != n) return NULL; *key = tmpkey; } if (n < 8) return NULL; buf = av_malloc(n); if (!buf) return NULL; memcpy(buf, sbuf, 8); *size = n; n -= 8; if (avio_read(src, buf+8, n) != n) { av_free(buf); return NULL; } decode_block(buf + 8, buf + 8, n, *key, &k2, 0); return buf; } static int track_header(VividasDemuxContext *viv, AVFormatContext *s, const uint8_t *buf, int size) { int i, j, ret; int64_t off; int val_1; int num_video; FFIOContext pb0; AVIOContext *const pb = &pb0.pub; ffio_init_read_context(&pb0, buf, size); ffio_read_varlen(pb); // track_header_len avio_r8(pb); // '1' val_1 = ffio_read_varlen(pb); for (i=0;iid = i; st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; st->codecpar->codec_id = AV_CODEC_ID_VP6; off = avio_tell(pb); off += ffio_read_varlen(pb); avio_r8(pb); // '3' avio_r8(pb); // val_7 num = avio_rl32(pb); // frame_time den = avio_rl32(pb); // time_base avpriv_set_pts_info(st, 64, num, den); st->nb_frames = avio_rl32(pb); // n frames st->codecpar->width = avio_rl16(pb); // width st->codecpar->height = avio_rl16(pb); // height avio_r8(pb); // val_8 avio_rl32(pb); // val_9 avio_seek(pb, off, SEEK_SET); } off = avio_tell(pb); off += ffio_read_varlen(pb); // val_10 avio_r8(pb); // '4' viv->num_audio = avio_r8(pb); avio_seek(pb, off, SEEK_SET); if (viv->num_audio != 1) av_log(s, AV_LOG_WARNING, "number of audio tracks %d is not 1\n", viv->num_audio); for(i=0;inum_audio;i++) { int q; AVStream *st = avformat_new_stream(s, NULL); if (!st) return AVERROR(ENOMEM); st->id = num_video + i; st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; st->codecpar->codec_id = AV_CODEC_ID_VORBIS; off = avio_tell(pb); off += ffio_read_varlen(pb); // length avio_r8(pb); // '5' avio_r8(pb); //codec_id avio_rl16(pb); //codec_subid st->codecpar->ch_layout.nb_channels = avio_rl16(pb); // channels st->codecpar->sample_rate = avio_rl32(pb); // sample_rate if (st->codecpar->sample_rate <= 0 || st->codecpar->ch_layout.nb_channels <= 0) return AVERROR_INVALIDDATA; avio_seek(pb, 10, SEEK_CUR); // data_1 q = avio_r8(pb); avio_seek(pb, q, SEEK_CUR); // data_2 avio_r8(pb); // zeropad if (avio_tell(pb) < off) { int num_data; int xd_size = 1; int data_len[256]; int offset = 1; uint8_t *p; ffio_read_varlen(pb); // val_13 avio_r8(pb); // '19' ffio_read_varlen(pb); // len_3 num_data = avio_r8(pb); for (j = 0; j < num_data; j++) { int64_t len = ffio_read_varlen(pb); if (len < 0 || len > INT_MAX/2 - xd_size) { return AVERROR_INVALIDDATA; } data_len[j] = len; xd_size += len + 1 + len/255; } ret = ff_alloc_extradata(st->codecpar, xd_size); if (ret < 0) return ret; p = st->codecpar->extradata; p[0] = 2; for (j = 0; j < num_data - 1; j++) { unsigned delta = av_xiphlacing(&p[offset], data_len[j]); av_assert0(delta <= xd_size - offset); offset += delta; } for (j = 0; j < num_data; j++) { int ret = avio_read(pb, &p[offset], data_len[j]); if (ret < data_len[j]) { st->codecpar->extradata_size = 0; av_freep(&st->codecpar->extradata); break; } av_assert0(data_len[j] <= xd_size - offset); offset += data_len[j]; } if (offset < st->codecpar->extradata_size) st->codecpar->extradata_size = offset; } } return 0; } static int track_index(VividasDemuxContext *viv, AVFormatContext *s, const uint8_t *buf, unsigned size) { int64_t off; int64_t poff; int maxnp=0; FFIOContext pb0; AVIOContext *const pb = &pb0.pub; int i; int64_t filesize = avio_size(s->pb); uint64_t n_sb_blocks_tmp; ffio_init_read_context(&pb0, buf, size); ffio_read_varlen(pb); // track_index_len avio_r8(pb); // 'c' n_sb_blocks_tmp = ffio_read_varlen(pb); if (n_sb_blocks_tmp > size / 2) return AVERROR_INVALIDDATA; viv->sb_blocks = av_calloc(n_sb_blocks_tmp, sizeof(*viv->sb_blocks)); if (!viv->sb_blocks) { return AVERROR(ENOMEM); } viv->n_sb_blocks = n_sb_blocks_tmp; off = 0; poff = 0; for (i = 0; i < viv->n_sb_blocks; i++) { uint64_t size_tmp = ffio_read_varlen(pb); uint64_t n_packets_tmp = ffio_read_varlen(pb); if (size_tmp > INT_MAX || n_packets_tmp > INT_MAX) return AVERROR_INVALIDDATA; viv->sb_blocks[i].byte_offset = off; viv->sb_blocks[i].packet_offset = poff; viv->sb_blocks[i].size = size_tmp; viv->sb_blocks[i].n_packets = n_packets_tmp; off += viv->sb_blocks[i].size; poff += viv->sb_blocks[i].n_packets; if (maxnp < viv->sb_blocks[i].n_packets) maxnp = viv->sb_blocks[i].n_packets; } if (filesize > 0 && poff > filesize) return AVERROR_INVALIDDATA; viv->sb_entries = av_calloc(maxnp, sizeof(VIV_SB_entry)); if (!viv->sb_entries) return AVERROR(ENOMEM); return 0; } static void load_sb_block(AVFormatContext *s, VividasDemuxContext *viv, unsigned expected_size) { uint32_t size = 0; int i; AVIOContext *pb = 0; if (viv->sb_pb) { av_free(viv->sb_pb); viv->sb_pb = NULL; } if (viv->sb_buf) av_free(viv->sb_buf); viv->sb_buf = read_sb_block(s->pb, &size, &viv->sb_key, expected_size); if (!viv->sb_buf) { return; } pb = avio_alloc_context(viv->sb_buf, size, 0, NULL, NULL, NULL, NULL); if (!pb) return; viv->sb_pb = pb; avio_r8(pb); // 'S' avio_r8(pb); // 'B' ffio_read_varlen(pb); // size avio_r8(pb); // junk ffio_read_varlen(pb); // first packet viv->n_sb_entries = viv->sb_blocks[viv->current_sb].n_packets; for (i = 0; i < viv->n_sb_entries; i++) { viv->sb_entries[i].size = ffio_read_varlen(pb); viv->sb_entries[i].flag = avio_r8(pb); } ffio_read_varlen(pb); avio_r8(pb); viv->current_sb_entry = 0; } static int viv_read_header(AVFormatContext *s) { VividasDemuxContext *viv = s->priv_data; AVIOContext *pb = s->pb; int64_t header_end; int num_tracks; uint32_t key, k2; uint32_t v; uint8_t keybuffer[187]; uint32_t b22_size = 0; uint32_t b22_key = 0; uint8_t *buf = 0; int ret; avio_skip(pb, 9); header_end = avio_tell(pb); header_end += ffio_read_varlen(pb); num_tracks = avio_r8(pb); if (num_tracks != 1) { av_log(s, AV_LOG_ERROR, "number of tracks %d is not 1\n", num_tracks); return AVERROR(EINVAL); } v = avio_r8(pb); avio_seek(pb, v, SEEK_CUR); if (avio_read(pb, keybuffer, 187) != 187) return AVERROR_INVALIDDATA; key = decode_key(keybuffer); viv->sb_key = key; avio_rl32(pb); for (;;) { int64_t here = avio_tell(pb); int block_len, block_type; if (here >= header_end) break; block_len = ffio_read_varlen(pb); if (avio_feof(pb) || block_len <= 0) return AVERROR_INVALIDDATA; block_type = avio_r8(pb); if (block_type == 22) { avio_read(pb, keybuffer, 187); b22_key = decode_key(keybuffer); b22_size = avio_rl32(pb); } avio_seek(pb, here + block_len, SEEK_SET); } if (b22_size) { k2 = b22_key; buf = read_vblock(pb, &v, b22_key, &k2, 0); if (!buf) return AVERROR(EIO); av_free(buf); } k2 = key; buf = read_vblock(pb, &v, key, &k2, 0); if (!buf) return AVERROR(EIO); ret = track_header(viv, s, buf, v); av_free(buf); if (ret < 0) return ret; buf = read_vblock(pb, &v, key, &k2, v); if (!buf) return AVERROR(EIO); ret = track_index(viv, s, buf, v); av_free(buf); if (ret < 0) return ret; viv->sb_offset = avio_tell(pb); if (viv->n_sb_blocks > 0) { viv->current_sb = 0; load_sb_block(s, viv, viv->sb_blocks[0].size); } else { viv->current_sb = -1; } return 0; } static int viv_read_packet(AVFormatContext *s, AVPacket *pkt) { VividasDemuxContext *viv = s->priv_data; AVIOContext *pb; int64_t off; int ret; if (!viv->sb_pb) return AVERROR(EIO); if (avio_feof(viv->sb_pb)) return AVERROR_EOF; if (viv->current_audio_subpacket < viv->n_audio_subpackets) { AVStream *astream; int size = viv->audio_subpackets[viv->current_audio_subpacket+1].start - viv->audio_subpackets[viv->current_audio_subpacket].start; pb = viv->sb_pb; ret = av_get_packet(pb, pkt, size); if (ret < 0) return ret; pkt->pos += viv->sb_offset + viv->sb_blocks[viv->current_sb].byte_offset; pkt->stream_index = 1; astream = s->streams[pkt->stream_index]; pkt->pts = av_rescale_q(viv->audio_sample, av_make_q(1, astream->codecpar->sample_rate), astream->time_base); viv->audio_sample += viv->audio_subpackets[viv->current_audio_subpacket].pcm_bytes / 2 / astream->codecpar->ch_layout.nb_channels; pkt->flags |= AV_PKT_FLAG_KEY; viv->current_audio_subpacket++; return 0; } if (viv->current_sb_entry >= viv->n_sb_entries) { if (viv->current_sb+1 >= viv->n_sb_blocks) return AVERROR(EIO); viv->current_sb++; load_sb_block(s, viv, 0); viv->current_sb_entry = 0; } pb = viv->sb_pb; if (!pb) return AVERROR(EIO); off = avio_tell(pb); if (viv->current_sb_entry >= viv->n_sb_entries) return AVERROR_INVALIDDATA; off += viv->sb_entries[viv->current_sb_entry].size; if (viv->sb_entries[viv->current_sb_entry].flag == 0) { uint64_t v_size = ffio_read_varlen(pb); int last = 0, last_start; if (!viv->num_audio) return AVERROR_INVALIDDATA; ffio_read_varlen(pb); if (v_size > INT_MAX || !v_size) return AVERROR_INVALIDDATA; ret = av_get_packet(pb, pkt, v_size); if (ret < 0) return ret; pkt->pos += viv->sb_offset + viv->sb_blocks[viv->current_sb].byte_offset; pkt->pts = viv->sb_blocks[viv->current_sb].packet_offset + viv->current_sb_entry; pkt->flags |= (pkt->data[0]&0x80)?0:AV_PKT_FLAG_KEY; pkt->stream_index = 0; for (int i = 0; i < MAX_AUDIO_SUBPACKETS - 1; i++) { int start, pcm_bytes; start = ffio_read_varlen(pb); pcm_bytes = ffio_read_varlen(pb); if (i > 0 && start == 0) break; if (start < last) return AVERROR_INVALIDDATA; viv->n_audio_subpackets = i + 1; last = viv->audio_subpackets[i].start = start; viv->audio_subpackets[i].pcm_bytes = pcm_bytes; } last_start = viv->audio_subpackets[viv->n_audio_subpackets].start = (int)(off - avio_tell(pb)); if (last_start < last) return AVERROR_INVALIDDATA; viv->current_audio_subpacket = 0; } else { uint64_t v_size = ffio_read_varlen(pb); if (v_size > INT_MAX || !v_size) return AVERROR_INVALIDDATA; ret = av_get_packet(pb, pkt, v_size); if (ret < 0) return ret; pkt->pos += viv->sb_offset + viv->sb_blocks[viv->current_sb].byte_offset; pkt->pts = viv->sb_blocks[viv->current_sb].packet_offset + viv->current_sb_entry; pkt->flags |= (pkt->data[0] & 0x80) ? 0 : AV_PKT_FLAG_KEY; pkt->stream_index = 0; } viv->current_sb_entry++; return 0; } static int viv_read_close(AVFormatContext *s) { VividasDemuxContext *viv = s->priv_data; av_freep(&viv->sb_pb); av_freep(&viv->sb_buf); av_freep(&viv->sb_blocks); av_freep(&viv->sb_entries); return 0; } static int viv_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { VividasDemuxContext *viv = s->priv_data; int64_t frame; if (stream_index == 0) frame = timestamp; else frame = av_rescale_q(timestamp, s->streams[0]->time_base, s->streams[stream_index]->time_base); for (int i = 0; i < viv->n_sb_blocks; i++) { if (frame >= viv->sb_blocks[i].packet_offset && frame < viv->sb_blocks[i].packet_offset + viv->sb_blocks[i].n_packets) { viv->current_sb = i; // seek to ith sb block avio_seek(s->pb, viv->sb_offset + viv->sb_blocks[i].byte_offset, SEEK_SET); // load the block load_sb_block(s, viv, 0); if (viv->num_audio) { const AVCodecParameters *par = s->streams[1]->codecpar; // flush audio packet queue viv->current_audio_subpacket = 0; viv->n_audio_subpackets = 0; // most problematic part: guess audio offset viv->audio_sample = av_rescale_q(viv->sb_blocks[i].packet_offset, av_make_q(par->sample_rate, 1), av_inv_q(s->streams[0]->time_base)); // hand-tuned 1.s a/v offset viv->audio_sample += par->sample_rate; } viv->current_sb_entry = 0; return 1; } } return 0; } const FFInputFormat ff_vividas_demuxer = { .p.name = "vividas", .p.long_name = NULL_IF_CONFIG_SMALL("Vividas VIV"), .priv_data_size = sizeof(VividasDemuxContext), .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP, .read_probe = viv_probe, .read_header = viv_read_header, .read_packet = viv_read_packet, .read_close = viv_read_close, .read_seek = viv_read_seek, };