libavformat/vivo.c
a0958949
 /*
  * Vivo stream demuxer
  * Copyright (c) 2009 Daniel Verkamp <daniel at drv.nu>
  *
  * 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 Vivo stream demuxer
  * @author Daniel Verkamp <daniel at drv.nu>
  * @sa http://wiki.multimedia.cx/index.php?title=Vivo
  */
 
 #include "libavutil/parseutils.h"
 #include "avformat.h"
 #include "internal.h"
 
 typedef struct VivoContext {
     int version;
 
     int type;
     int sequence;
     int length;
 
     uint8_t  text[1024 + 1];
 } VivoContext;
 
 static int vivo_probe(AVProbeData *p)
 {
     const unsigned char *buf = p->buf;
     unsigned c, length = 0;
 
     // stream must start with packet of type 0 and sequence number 0
     if (*buf++ != 0)
         return 0;
 
     // read at most 2 bytes of coded length
     c = *buf++;
     length = c & 0x7F;
     if (c & 0x80) {
         c = *buf++;
         length = (length << 7) | (c & 0x7F);
     }
     if (c & 0x80 || length > 1024 || length < 21)
         return 0;
 
     if (memcmp(buf, "\r\nVersion:Vivo/", 15))
         return 0;
     buf += 15;
 
     if (*buf < '0' && *buf > '2')
         return 0;
 
     return AVPROBE_SCORE_MAX;
 }
 
 static int vivo_get_packet_header(AVFormatContext *s)
 {
     VivoContext *vivo = s->priv_data;
     AVIOContext *pb = s->pb;
     unsigned c, get_length = 0;
 
     if (url_feof(pb))
         return AVERROR_EOF;
 
     c = avio_r8(pb);
     if (c == 0x82) {
         get_length = 1;
         c = avio_r8(pb);
     }
 
     vivo->type     = c >> 4;
     vivo->sequence = c & 0xF;
 
     switch (vivo->type) {
     case 0:   get_length =   1; break;
     case 1: vivo->length = 128; break;
     case 2:   get_length =   1; break;
     case 3: vivo->length =  40; break;
     case 4: vivo->length =  24; break;
     default:
         av_log(s, AV_LOG_ERROR, "unknown packet type %d\n", vivo->type);
         return AVERROR_INVALIDDATA;
     }
 
     if (get_length) {
         c = avio_r8(pb);
         vivo->length = c & 0x7F;
         if (c & 0x80) {
             c = avio_r8(pb);
             vivo->length = (vivo->length << 7) | (c & 0x7F);
 
             if (c & 0x80) {
                 av_log(s, AV_LOG_ERROR, "coded length is more than two bytes\n");
                 return AVERROR_INVALIDDATA;
             }
         }
     }
 
     return 0;
 }
 
 static int vivo_read_header(AVFormatContext *s)
 {
     VivoContext *vivo = s->priv_data;
     AVRational fps = { 1, 25};
     AVStream *ast, *vst;
     unsigned char *line, *line_end, *key, *value;
     long value_int;
     int ret, value_used;
     int64_t duration = 0;
     char *end_value;
 
     vst = avformat_new_stream(s, NULL);
     ast = avformat_new_stream(s, NULL);
     if (!ast || !vst)
         return AVERROR(ENOMEM);
 
     ast->codec->sample_rate = 8000;
 
     while (1) {
         if ((ret = vivo_get_packet_header(s)) < 0)
             return ret;
 
         // done reading all text header packets?
         if (vivo->sequence || vivo->type)
             break;
 
         if (vivo->length <= 1024) {
             avio_read(s->pb, vivo->text, vivo->length);
             vivo->text[vivo->length] = 0;
         } else {
             av_log(s, AV_LOG_WARNING, "too big header, skipping\n");
             avio_skip(s->pb, vivo->length);
             continue;
         }
 
         line = vivo->text;
         while (*line) {
             line_end = strstr(line, "\r\n");
             if (!line_end)
                 break;
 
             *line_end = 0;
             key = line;
             line = line_end + 2; // skip \r\n
 
             if (line_end == key) // skip blank lines
                 continue;
 
             value = strchr(key, ':');
             if (!value) {
                 av_log(s, AV_LOG_WARNING, "missing colon in key:value pair '%s'\n",
                        value);
                 continue;
             }
 
             *value++ = 0;
 
             av_log(s, AV_LOG_DEBUG, "header: '%s' = '%s'\n", key, value);
 
             value_int = strtol(value, &end_value, 10);
             value_used = 0;
             if (*end_value == 0) { // valid integer
                 av_log(s, AV_LOG_DEBUG, "got a valid integer (%ld)\n", value_int);
                 value_used = 1;
                 if (!strcmp(key, "Duration")) {
                     duration = value_int;
                 } else if (!strcmp(key, "Width")) {
                     vst->codec->width = value_int;
                 } else if (!strcmp(key, "Height")) {
                     vst->codec->height = value_int;
                 } else if (!strcmp(key, "TimeUnitNumerator")) {
                     fps.num = value_int / 1000;
                 } else if (!strcmp(key, "TimeUnitDenominator")) {
                     fps.den = value_int;
                 } else if (!strcmp(key, "SamplingFrequency")) {
                     ast->codec->sample_rate = value_int;
                 } else if (!strcmp(key, "NominalBitrate")) {
                 } else if (!strcmp(key, "Length")) {
                     // size of file
                 } else {
                     value_used = 0;
                 }
             }
 
             if (!strcmp(key, "Version")) {
                 if (sscanf(value, "Vivo/%d.", &vivo->version) != 1)
                     return AVERROR_INVALIDDATA;
                 value_used = 1;
             } else if (!strcmp(key, "FPS")) {
                 AVRational tmp;
 
                 value_used = 1;
                 if (!av_parse_ratio(&tmp, value, 10000, AV_LOG_WARNING, s))
                     fps = av_inv_q(tmp);
             }
 
             if (!value_used)
                 av_dict_set(&s->metadata, key, value, 0);
         }
     }
 
     avpriv_set_pts_info(ast, 64, 1, ast->codec->sample_rate);
     avpriv_set_pts_info(vst, 64, fps.num, fps.den);
     if (duration)
         s->duration = av_rescale(duration, 1000, 1);
 
     vst->start_time        = 0;
     vst->codec->codec_tag  = 0;
     vst->codec->codec_type = AVMEDIA_TYPE_VIDEO;
 
     if (vivo->version == 1) {
92f630ea
         vst->codec->codec_id = AV_CODEC_ID_H263;
         ast->codec->codec_id = AV_CODEC_ID_G723_1;
a0958949
         ast->codec->bits_per_coded_sample = 8;
4e6f9db4
         ast->codec->block_align = 24;
         ast->codec->bit_rate = 6400;
a0958949
     }
 
     ast->start_time        = 0;
     ast->codec->codec_tag  = 0;
     ast->codec->codec_type = AVMEDIA_TYPE_AUDIO;
     ast->codec->channels = 1;
 
     return 0;
 }
 
 static int vivo_read_packet(AVFormatContext *s, AVPacket *pkt)
 {
     VivoContext *vivo = s->priv_data;
     AVIOContext *pb = s->pb;
     unsigned old_sequence = vivo->sequence, old_type = vivo->type;
     int stream_index, ret = 0;
 
 restart:
 
     if (url_feof(pb))
         return AVERROR_EOF;
 
     switch (vivo->type) {
     case 0:
         avio_skip(pb, vivo->length);
         if ((ret = vivo_get_packet_header(s)) < 0)
             return ret;
         goto restart;
     case 1:
     case 2: // video
         stream_index = 0;
         break;
     case 3:
     case 4: // audio
         stream_index = 1;
         break;
     default:
         av_log(s, AV_LOG_ERROR, "unknown packet type %d\n", vivo->type);
         return AVERROR_INVALIDDATA;
     }
 
     if ((ret = av_get_packet(pb, pkt, vivo->length)) < 0)
         goto fail;
 
     // get next packet header
     if ((ret = vivo_get_packet_header(s)) < 0)
         goto fail;
 
     while (vivo->sequence == old_sequence &&
            (((vivo->type - 1) >> 1) == ((old_type - 1) >> 1))) {
         if (url_feof(pb)) {
             ret = AVERROR_EOF;
             break;
         }
 
         if ((ret = av_append_packet(pb, pkt, vivo->length)) < 0)
             break;
 
         // get next packet header
         if ((ret = vivo_get_packet_header(s)) < 0)
             break;
     }
 
     pkt->stream_index = stream_index;
 
 fail:
     if (ret < 0)
         av_free_packet(pkt);
     return ret;
 }
 
 AVInputFormat ff_vivo_demuxer = {
     .name           = "vivo",
     .long_name      = NULL_IF_CONFIG_SMALL("Vivo"),
     .priv_data_size = sizeof(VivoContext),
     .read_probe     = vivo_probe,
     .read_header    = vivo_read_header,
     .read_packet    = vivo_read_packet,
     .extensions     = "viv",
 };