nuv.c 13 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
static int nuv_probe(const AVProbeData *p)
51
{
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(AVFormatContext *s, 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
                ast->codecpar->codec_tag             = avio_rl32(pb);
                ast->codecpar->sample_rate           = avio_rl32(pb);
117 118 119 120
                if (ast->codecpar->sample_rate <= 0) {
                    av_log(s, AV_LOG_ERROR, "Invalid sample rate %d\n", ast->codecpar->sample_rate);
                    return AVERROR_INVALIDDATA;
                }
121 122 123
                ast->codecpar->bits_per_coded_sample = avio_rl32(pb);
                ast->codecpar->channels              = avio_rl32(pb);
                ast->codecpar->channel_layout        = 0;
124

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

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

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

153 154 155
    return 0;
}

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

165
    avio_read(pb, id_string, 12);
166
    is_mythtv = !memcmp(id_string, "MythTVVideo", 12);
167 168 169
    avio_skip(pb, 5);       // version string
    avio_skip(pb, 3);       // padding
    width  = avio_rl32(pb);
170
    height = avio_rl32(pb);
171 172 173 174
    avio_rl32(pb);          // unused, "desiredwidth"
    avio_rl32(pb);          // unused, "desiredheight"
    avio_r8(pb);            // 'P' == progressive, 'I' == interlaced
    avio_skip(pb, 3);       // padding
175
    aspect = av_int2double(avio_rl64(pb));
176 177
    if (aspect > 0.9999 && aspect < 1.0001)
        aspect = 4.0 / 3.0;
178
    fps = av_int2double(avio_rl64(pb));
179 180 181 182 183 184 185 186 187
    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;
        }
    }
188 189

    // number of packets per stream type, -1 means unknown, e.g. streaming
190 191 192
    v_packs = avio_rl32(pb);
    a_packs = avio_rl32(pb);
    avio_rl32(pb); // text
193

194
    avio_rl32(pb); // keyframe distance (?)
195 196

    if (v_packs) {
197
        vst = avformat_new_stream(s, NULL);
198 199
        if (!vst)
            return AVERROR(ENOMEM);
200 201
        ctx->v_id = vst->index;

202
        ret = av_image_check_size(width, height, 0, s);
203 204 205
        if (ret < 0)
            return ret;

206 207 208 209 210
        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;
211 212
        vst->sample_aspect_ratio          = av_d2q(aspect * height / width,
                                                   10000);
213 214 215 216
#if FF_API_R_FRAME_RATE
        vst->r_frame_rate =
#endif
        vst->avg_frame_rate = av_d2q(fps, 60000);
217
        avpriv_set_pts_info(vst, 32, 1, 1000);
218 219 220 221
    } else
        ctx->v_id = -1;

    if (a_packs) {
222
        ast = avformat_new_stream(s, NULL);
223 224
        if (!ast)
            return AVERROR(ENOMEM);
225 226
        ctx->a_id = ast->index;

227 228 229 230 231 232 233 234
        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;
235
        avpriv_set_pts_info(ast, 32, 1, 1000);
236 237 238
    } else
        ctx->a_id = -1;

239
    if ((ret = get_codec_data(s, pb, vst, ast, is_mythtv)) < 0)
240 241
        return ret;

242
    ctx->rtjpg_video = vst && vst->codecpar->codec_id == AV_CODEC_ID_NUV;
243

244 245 246 247 248
    return 0;
}

#define HDRSIZE 12

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

257
    while (!avio_feof(pb)) {
258
        int copyhdrsize = ctx->rtjpg_video ? HDRSIZE : 0;
259 260
        uint64_t pos    = avio_tell(pb);

261
        ret = avio_read(pb, hdr, HDRSIZE);
262 263
        if (ret < HDRSIZE)
            return ret < 0 ? ret : AVERROR(EIO);
264

265
        frametype = hdr[0];
266 267
        size      = PKTSIZE(AV_RL32(&hdr[8]));

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

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

320
    return AVERROR(EIO);
321 322
}

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

360
    while (!avio_feof(pb) && avio_tell(pb) < pos_limit) {
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 394 395 396 397
        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;
}


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