nuv.c 12.8 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/imgutils.h"
24
#include "libavutil/intreadwrite.h"
25
#include "libavutil/intfloat.h"
26
#include "avformat.h"
27
#include "internal.h"
28
#include "riff.h"
29

30 31 32 33 34 35
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 },
};

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

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

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

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

/**
63 64 65 66
 * @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
67
 * @return 0 or AVERROR code
68
 */
69
static int get_codec_data(AVIOContext *pb, AVStream *vst,
70 71
                          AVStream *ast, int myth)
{
72
    nuv_frametype frametype;
73

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

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

112 113
            if (ast) {
                int id;
114

115 116 117 118 119
                ast->codecpar->codec_tag             = avio_rl32(pb);
                ast->codecpar->sample_rate           = avio_rl32(pb);
                ast->codecpar->bits_per_coded_sample = avio_rl32(pb);
                ast->codecpar->channels              = avio_rl32(pb);
                ast->codecpar->channel_layout        = 0;
120

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

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

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

149 150 151
    return 0;
}

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

161
    avio_read(pb, id_string, 12);
162
    is_mythtv = !memcmp(id_string, "MythTVVideo", 12);
163 164 165
    avio_skip(pb, 5);       // version string
    avio_skip(pb, 3);       // padding
    width  = avio_rl32(pb);
166
    height = avio_rl32(pb);
167 168 169 170
    avio_rl32(pb);          // unused, "desiredwidth"
    avio_rl32(pb);          // unused, "desiredheight"
    avio_r8(pb);            // 'P' == progressive, 'I' == interlaced
    avio_skip(pb, 3);       // padding
171
    aspect = av_int2double(avio_rl64(pb));
172 173
    if (aspect > 0.9999 && aspect < 1.0001)
        aspect = 4.0 / 3.0;
174
    fps = av_int2double(avio_rl64(pb));
175 176 177 178 179 180 181 182 183
    if (fps < 0.0f) {
        if (s->error_recognition & AV_EF_EXPLODE) {
            av_log(s, AV_LOG_ERROR, "Invalid frame rate %f\n", fps);
            return AVERROR_INVALIDDATA;
        } else {
            av_log(s, AV_LOG_WARNING, "Invalid frame rate %f, setting to 0.\n", fps);
            fps = 0.0f;
        }
    }
184 185

    // number of packets per stream type, -1 means unknown, e.g. streaming
186 187 188
    v_packs = avio_rl32(pb);
    a_packs = avio_rl32(pb);
    avio_rl32(pb); // text
189

190
    avio_rl32(pb); // keyframe distance (?)
191 192

    if (v_packs) {
193
        vst = avformat_new_stream(s, NULL);
194 195
        if (!vst)
            return AVERROR(ENOMEM);
196 197
        ctx->v_id = vst->index;

198
        ret = av_image_check_size(width, height, 0, s);
199 200 201
        if (ret < 0)
            return ret;

202 203 204 205 206
        vst->codecpar->codec_type            = AVMEDIA_TYPE_VIDEO;
        vst->codecpar->codec_id              = AV_CODEC_ID_NUV;
        vst->codecpar->width                 = width;
        vst->codecpar->height                = height;
        vst->codecpar->bits_per_coded_sample = 10;
207 208
        vst->sample_aspect_ratio          = av_d2q(aspect * height / width,
                                                   10000);
209 210 211 212
#if FF_API_R_FRAME_RATE
        vst->r_frame_rate =
#endif
        vst->avg_frame_rate = av_d2q(fps, 60000);
213
        avpriv_set_pts_info(vst, 32, 1, 1000);
214 215 216 217
    } else
        ctx->v_id = -1;

    if (a_packs) {
218
        ast = avformat_new_stream(s, NULL);
219 220
        if (!ast)
            return AVERROR(ENOMEM);
221 222
        ctx->a_id = ast->index;

223 224 225 226 227 228 229 230
        ast->codecpar->codec_type            = AVMEDIA_TYPE_AUDIO;
        ast->codecpar->codec_id              = AV_CODEC_ID_PCM_S16LE;
        ast->codecpar->channels              = 2;
        ast->codecpar->channel_layout        = AV_CH_LAYOUT_STEREO;
        ast->codecpar->sample_rate           = 44100;
        ast->codecpar->bit_rate              = 2 * 2 * 44100 * 8;
        ast->codecpar->block_align           = 2 * 2;
        ast->codecpar->bits_per_coded_sample = 16;
231
        avpriv_set_pts_info(ast, 32, 1, 1000);
232 233 234
    } else
        ctx->a_id = -1;

235 236 237
    if ((ret = get_codec_data(pb, vst, ast, is_mythtv)) < 0)
        return ret;

238
    ctx->rtjpg_video = vst && vst->codecpar->codec_id == AV_CODEC_ID_NUV;
239

240 241 242 243 244
    return 0;
}

#define HDRSIZE 12

245 246
static int nuv_packet(AVFormatContext *s, AVPacket *pkt)
{
247
    NUVContext *ctx = s->priv_data;
248
    AVIOContext *pb = s->pb;
249
    uint8_t hdr[HDRSIZE];
250
    nuv_frametype frametype;
251
    int ret, size;
252

253
    while (!avio_feof(pb)) {
254
        int copyhdrsize = ctx->rtjpg_video ? HDRSIZE : 0;
255 256
        uint64_t pos    = avio_tell(pb);

257
        ret = avio_read(pb, hdr, HDRSIZE);
258 259
        if (ret < HDRSIZE)
            return ret < 0 ? ret : AVERROR(EIO);
260

261
        frametype = hdr[0];
262 263
        size      = PKTSIZE(AV_RL32(&hdr[8]));

264
        switch (frametype) {
265 266 267
        case NUV_EXTRADATA:
            if (!ctx->rtjpg_video) {
                avio_skip(pb, size);
268
                break;
269 270 271 272
            }
        case NUV_VIDEO:
            if (ctx->v_id < 0) {
                av_log(s, AV_LOG_ERROR, "Video packet in file without video stream!\n");
273
                avio_skip(pb, size);
274
                break;
275 276 277 278
            }
            ret = av_new_packet(pkt, copyhdrsize + size);
            if (ret < 0)
                return ret;
279

280
            pkt->pos          = pos;
281
            pkt->flags       |= hdr[2] == 0 ? AV_PKT_FLAG_KEY : 0;
282 283 284 285 286
            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) {
287
                av_packet_unref(pkt);
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
                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;
313 314
        }
    }
315

316
    return AVERROR(EIO);
317 318
}

319 320 321 322 323 324 325
/**
 * \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;
326
    while(!avio_feof(pb) && avio_tell(pb) < pos_limit) {
327 328 329 330 331 332 333 334 335 336 337
        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
338
 * \return timestamp if successful and AV_NOPTS_VALUE if failure
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
 */
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;

356
    while (!avio_feof(pb) && avio_tell(pb) < pos_limit) {
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 383 384 385 386 387 388 389 390 391 392 393
        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;
}


394
AVInputFormat ff_nuv_demuxer = {
395
    .name           = "nuv",
396
    .long_name      = NULL_IF_CONFIG_SMALL("NuppelVideo"),
397 398 399 400
    .priv_data_size = sizeof(NUVContext),
    .read_probe     = nuv_probe,
    .read_header    = nuv_header,
    .read_packet    = nuv_packet,
401
    .read_timestamp = nuv_read_dts,
402
    .flags          = AVFMT_GENERIC_INDEX,
403
};