nuv.c 12.4 KB
Newer Older
1 2
/*
 * NuppelVideo demuxer.
3
 * Copyright (c) 2006 Reimar Doeffinger
4
 *
5 6 7
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
8 9
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
13 14 15 16 17
 * 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
18
 * License along with FFmpeg; if not, write to the Free Software
19 20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
21

Justin Ruggles's avatar
Justin Ruggles committed
22
#include "libavutil/channel_layout.h"
23
#include "libavutil/intreadwrite.h"
24
#include "libavutil/intfloat.h"
25
#include "avformat.h"
26
#include "internal.h"
27
#include "riff.h"
28

29 30 31 32 33 34
static const AVCodecTag nuv_audio_tags[] = {
    { AV_CODEC_ID_PCM_S16LE, MKTAG('R', 'A', 'W', 'A') },
    { AV_CODEC_ID_MP3,       MKTAG('L', 'A', 'M', 'E') },
    { AV_CODEC_ID_NONE,      0 },
};

35 36 37
typedef struct {
    int v_id;
    int a_id;
38
    int rtjpg_video;
39 40 41
} NUVContext;

typedef enum {
42
    NUV_VIDEO     = 'V',
43
    NUV_EXTRADATA = 'D',
44 45 46
    NUV_AUDIO     = 'A',
    NUV_SEEKP     = 'R',
    NUV_MYTHEXT   = 'X'
47
} nuv_frametype;
48

49 50
static int nuv_probe(AVProbeData *p)
{
51 52 53 54 55 56 57
    if (!memcmp(p->buf, "NuppelVideo", 12))
        return AVPROBE_SCORE_MAX;
    if (!memcmp(p->buf, "MythTVVideo", 12))
        return AVPROBE_SCORE_MAX;
    return 0;
}

58
/// little macro to sanitize packet size
59 60 61
#define PKTSIZE(s) (s &  0xffffff)

/**
62 63 64 65
 * @brief read until we found all data needed for decoding
 * @param vst video stream of which to change parameters
 * @param ast video stream of which to change parameters
 * @param myth set if this is a MythTVVideo format file
66
 * @return 0 or AVERROR code
67
 */
68
static int get_codec_data(AVIOContext *pb, AVStream *vst,
69 70
                          AVStream *ast, int myth)
{
71
    nuv_frametype frametype;
72

73 74 75 76
    if (!vst && !myth)
        return 1; // no codec data needed
    while (!url_feof(pb)) {
        int size, subtype;
77

78
        frametype = avio_r8(pb);
79
        switch (frametype) {
80 81 82 83 84
        case NUV_EXTRADATA:
            subtype = avio_r8(pb);
            avio_skip(pb, 6);
            size = PKTSIZE(avio_rl32(pb));
            if (vst && subtype == 'R') {
85 86 87 88 89 90 91
                if (vst->codec->extradata) {
                    av_freep(&vst->codec->extradata);
                    vst->codec->extradata_size = 0;
                }
                vst->codec->extradata = av_malloc(size);
                if (!vst->codec->extradata)
                    return AVERROR(ENOMEM);
92 93 94 95
                vst->codec->extradata_size = size;
                avio_read(pb, vst->codec->extradata, size);
                size = 0;
                if (!myth)
96
                    return 0;
97 98 99 100 101 102
            }
            break;
        case NUV_MYTHEXT:
            avio_skip(pb, 7);
            size = PKTSIZE(avio_rl32(pb));
            if (size != 128 * 4)
103
                break;
104 105 106 107 108 109 110 111 112
            avio_rl32(pb); // version
            if (vst) {
                vst->codec->codec_tag = avio_rl32(pb);
                vst->codec->codec_id =
                    ff_codec_get_id(ff_codec_bmp_tags, vst->codec->codec_tag);
                if (vst->codec->codec_tag == MKTAG('R', 'J', 'P', 'G'))
                    vst->codec->codec_id = AV_CODEC_ID_NUV;
            } else
                avio_skip(pb, 4);
113

114 115
            if (ast) {
                int id;
116

117 118 119 120 121
                ast->codec->codec_tag             = avio_rl32(pb);
                ast->codec->sample_rate           = avio_rl32(pb);
                ast->codec->bits_per_coded_sample = avio_rl32(pb);
                ast->codec->channels              = avio_rl32(pb);
                ast->codec->channel_layout        = 0;
122

123 124 125 126 127 128 129 130 131
                id = ff_wav_codec_get_id(ast->codec->codec_tag,
                                         ast->codec->bits_per_coded_sample);
                if (id == AV_CODEC_ID_NONE) {
                    id = ff_codec_get_id(nuv_audio_tags, ast->codec->codec_tag);
                    if (id == AV_CODEC_ID_PCM_S16LE)
                        id = ff_get_pcm_codec_id(ast->codec->bits_per_coded_sample,
                                                 0, 0, ~1);
                }
                ast->codec->codec_id = id;
132

133 134 135
                ast->need_parsing = AVSTREAM_PARSE_FULL;
            } else
                avio_skip(pb, 4 * 4);
136

137 138
            size -= 6 * 4;
            avio_skip(pb, size);
139
            return 0;
140 141 142 143 144 145 146
        case NUV_SEEKP:
            size = 11;
            break;
        default:
            avio_skip(pb, 7);
            size = PKTSIZE(avio_rl32(pb));
            break;
147
        }
148
        avio_skip(pb, size);
149
    }
150

151 152 153
    return 0;
}

154 155
static int nuv_header(AVFormatContext *s)
{
156
    NUVContext *ctx = s->priv_data;
157
    AVIOContext *pb = s->pb;
158
    char id_string[12];
159
    double aspect, fps;
160
    int is_mythtv, width, height, v_packs, a_packs, ret;
161
    AVStream *vst = NULL, *ast = NULL;
162

163
    avio_read(pb, id_string, 12);
164
    is_mythtv = !memcmp(id_string, "MythTVVideo", 12);
165 166 167
    avio_skip(pb, 5);       // version string
    avio_skip(pb, 3);       // padding
    width  = avio_rl32(pb);
168
    height = avio_rl32(pb);
169 170 171 172
    avio_rl32(pb);          // unused, "desiredwidth"
    avio_rl32(pb);          // unused, "desiredheight"
    avio_r8(pb);            // 'P' == progressive, 'I' == interlaced
    avio_skip(pb, 3);       // padding
173
    aspect = av_int2double(avio_rl64(pb));
174 175
    if (aspect > 0.9999 && aspect < 1.0001)
        aspect = 4.0 / 3.0;
176
    fps = av_int2double(avio_rl64(pb));
177 178

    // number of packets per stream type, -1 means unknown, e.g. streaming
179 180 181
    v_packs = avio_rl32(pb);
    a_packs = avio_rl32(pb);
    avio_rl32(pb); // text
182

183
    avio_rl32(pb); // keyframe distance (?)
184 185

    if (v_packs) {
186
        vst = avformat_new_stream(s, NULL);
187 188
        if (!vst)
            return AVERROR(ENOMEM);
189 190
        ctx->v_id = vst->index;

191 192 193 194
        vst->codec->codec_type            = AVMEDIA_TYPE_VIDEO;
        vst->codec->codec_id              = AV_CODEC_ID_NUV;
        vst->codec->width                 = width;
        vst->codec->height                = height;
195
        vst->codec->bits_per_coded_sample = 10;
196 197
        vst->sample_aspect_ratio          = av_d2q(aspect * height / width,
                                                   10000);
198 199 200 201
#if FF_API_R_FRAME_RATE
        vst->r_frame_rate =
#endif
        vst->avg_frame_rate = av_d2q(fps, 60000);
202
        avpriv_set_pts_info(vst, 32, 1, 1000);
203 204 205 206
    } else
        ctx->v_id = -1;

    if (a_packs) {
207
        ast = avformat_new_stream(s, NULL);
208 209
        if (!ast)
            return AVERROR(ENOMEM);
210 211
        ctx->a_id = ast->index;

212 213 214 215 216 217 218
        ast->codec->codec_type            = AVMEDIA_TYPE_AUDIO;
        ast->codec->codec_id              = AV_CODEC_ID_PCM_S16LE;
        ast->codec->channels              = 2;
        ast->codec->channel_layout        = AV_CH_LAYOUT_STEREO;
        ast->codec->sample_rate           = 44100;
        ast->codec->bit_rate              = 2 * 2 * 44100 * 8;
        ast->codec->block_align           = 2 * 2;
219
        ast->codec->bits_per_coded_sample = 16;
220
        avpriv_set_pts_info(ast, 32, 1, 1000);
221 222 223
    } else
        ctx->a_id = -1;

224 225 226
    if ((ret = get_codec_data(pb, vst, ast, is_mythtv)) < 0)
        return ret;

227
    ctx->rtjpg_video = vst && vst->codec->codec_id == AV_CODEC_ID_NUV;
228

229 230 231 232 233
    return 0;
}

#define HDRSIZE 12

234 235
static int nuv_packet(AVFormatContext *s, AVPacket *pkt)
{
236
    NUVContext *ctx = s->priv_data;
237
    AVIOContext *pb = s->pb;
238
    uint8_t hdr[HDRSIZE];
239
    nuv_frametype frametype;
240
    int ret, size;
241

242
    while (!url_feof(pb)) {
243
        int copyhdrsize = ctx->rtjpg_video ? HDRSIZE : 0;
244 245
        uint64_t pos    = avio_tell(pb);

246
        ret = avio_read(pb, hdr, HDRSIZE);
247 248
        if (ret < HDRSIZE)
            return ret < 0 ? ret : AVERROR(EIO);
249

250
        frametype = hdr[0];
251 252
        size      = PKTSIZE(AV_RL32(&hdr[8]));

253
        switch (frametype) {
254 255 256
        case NUV_EXTRADATA:
            if (!ctx->rtjpg_video) {
                avio_skip(pb, size);
257
                break;
258 259 260 261
            }
        case NUV_VIDEO:
            if (ctx->v_id < 0) {
                av_log(s, AV_LOG_ERROR, "Video packet in file without video stream!\n");
262
                avio_skip(pb, size);
263
                break;
264 265 266 267
            }
            ret = av_new_packet(pkt, copyhdrsize + size);
            if (ret < 0)
                return ret;
268

269
            pkt->pos          = pos;
270
            pkt->flags       |= hdr[2] == 0 ? AV_PKT_FLAG_KEY : 0;
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
            pkt->pts          = AV_RL32(&hdr[4]);
            pkt->stream_index = ctx->v_id;
            memcpy(pkt->data, hdr, copyhdrsize);
            ret = avio_read(pb, pkt->data + copyhdrsize, size);
            if (ret < 0) {
                av_free_packet(pkt);
                return ret;
            }
            if (ret < size)
                av_shrink_packet(pkt, copyhdrsize + ret);
            return 0;
        case NUV_AUDIO:
            if (ctx->a_id < 0) {
                av_log(s, AV_LOG_ERROR, "Audio packet in file without audio stream!\n");
                avio_skip(pb, size);
                break;
            }
            ret               = av_get_packet(pb, pkt, size);
            pkt->flags       |= AV_PKT_FLAG_KEY;
            pkt->pos          = pos;
            pkt->pts          = AV_RL32(&hdr[4]);
            pkt->stream_index = ctx->a_id;
            if (ret < 0)
                return ret;
            return 0;
        case NUV_SEEKP:
            // contains no data, size value is invalid
            break;
        default:
            avio_skip(pb, size);
            break;
302 303
        }
    }
304

305
    return AVERROR(EIO);
306 307
}

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
/**
 * \brief looks for the string RTjjjjjjjjjj in the stream too resync reading
 * \return 1 if the syncword is found 0 otherwise.
 */
static int nuv_resync(AVFormatContext *s, int64_t pos_limit) {
    AVIOContext *pb = s->pb;
    uint32_t tag = 0;
    while(!url_feof(pb) && avio_tell(pb) < pos_limit) {
        tag = (tag << 8) | avio_r8(pb);
        if (tag                  == MKBETAG('R','T','j','j') &&
           (tag = avio_rb32(pb)) == MKBETAG('j','j','j','j') &&
           (tag = avio_rb32(pb)) == MKBETAG('j','j','j','j'))
            return 1;
    }
    return 0;
}

/**
 * \brief attempts to read a timestamp from stream at the given stream position
Lou Logan's avatar
Lou Logan committed
327
 * \return timestamp if successful and AV_NOPTS_VALUE if failure
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
 */
static int64_t nuv_read_dts(AVFormatContext *s, int stream_index,
                            int64_t *ppos, int64_t pos_limit)
{
    NUVContext *ctx = s->priv_data;
    AVIOContext *pb = s->pb;
    uint8_t hdr[HDRSIZE];
    nuv_frametype frametype;
    int size, key, idx;
    int64_t pos, dts;

    if (avio_seek(pb, *ppos, SEEK_SET) < 0)
        return AV_NOPTS_VALUE;

    if (!nuv_resync(s, pos_limit))
        return AV_NOPTS_VALUE;

    while (!url_feof(pb) && avio_tell(pb) < pos_limit) {
        if (avio_read(pb, hdr, HDRSIZE) < HDRSIZE)
            return AV_NOPTS_VALUE;
        frametype = hdr[0];
        size = PKTSIZE(AV_RL32(&hdr[8]));
        switch (frametype) {
            case NUV_SEEKP:
                break;
            case NUV_AUDIO:
            case NUV_VIDEO:
                if (frametype == NUV_VIDEO) {
                    idx = ctx->v_id;
                    key = hdr[2] == 0;
                } else {
                    idx = ctx->a_id;
                    key = 1;
                }
                if (stream_index == idx) {

                    pos = avio_tell(s->pb) - HDRSIZE;
                    dts = AV_RL32(&hdr[4]);

                    // TODO - add general support in av_gen_search, so it adds positions after reading timestamps
                    av_add_index_entry(s->streams[stream_index], pos, dts, size + HDRSIZE, 0,
                            key ? AVINDEX_KEYFRAME : 0);

                    *ppos = pos;
                    return dts;
                }
            default:
                avio_skip(pb, size);
                break;
        }
    }
    return AV_NOPTS_VALUE;
}


383
AVInputFormat ff_nuv_demuxer = {
384
    .name           = "nuv",
385
    .long_name      = NULL_IF_CONFIG_SMALL("NuppelVideo"),
386 387 388 389
    .priv_data_size = sizeof(NUVContext),
    .read_probe     = nuv_probe,
    .read_header    = nuv_header,
    .read_packet    = nuv_packet,
390
    .read_timestamp = nuv_read_dts,
391
    .flags          = AVFMT_GENERIC_INDEX,
392
};