• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * NuppelVideo demuxer.
3  * Copyright (c) 2006 Reimar Doeffinger
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * 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  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * 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  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 
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 "avio_internal.h"
28 #include "demux.h"
29 #include "internal.h"
30 #include "riff.h"
31 
32 static const AVCodecTag nuv_audio_tags[] = {
33     { AV_CODEC_ID_PCM_S16LE, MKTAG('R', 'A', 'W', 'A') },
34     { AV_CODEC_ID_MP3,       MKTAG('L', 'A', 'M', 'E') },
35     { AV_CODEC_ID_NONE,      0 },
36 };
37 
38 typedef struct NUVContext {
39     int v_id;
40     int a_id;
41     int rtjpg_video;
42 } NUVContext;
43 
44 typedef enum {
45     NUV_VIDEO     = 'V',
46     NUV_EXTRADATA = 'D',
47     NUV_AUDIO     = 'A',
48     NUV_SEEKP     = 'R',
49     NUV_MYTHEXT   = 'X'
50 } nuv_frametype;
51 
nuv_probe(const AVProbeData * p)52 static int nuv_probe(const AVProbeData *p)
53 {
54     if (!memcmp(p->buf, "NuppelVideo", 12))
55         return AVPROBE_SCORE_MAX;
56     if (!memcmp(p->buf, "MythTVVideo", 12))
57         return AVPROBE_SCORE_MAX;
58     return 0;
59 }
60 
61 /// little macro to sanitize packet size
62 #define PKTSIZE(s) (s &  0xffffff)
63 
64 /**
65  * @brief read until we found all data needed for decoding
66  * @param vst video stream of which to change parameters
67  * @param ast video stream of which to change parameters
68  * @param myth set if this is a MythTVVideo format file
69  * @return 0 or AVERROR code
70  */
get_codec_data(AVFormatContext * s,AVIOContext * pb,AVStream * vst,AVStream * ast,int myth)71 static int get_codec_data(AVFormatContext *s, AVIOContext *pb, AVStream *vst,
72                           AVStream *ast, int myth)
73 {
74     nuv_frametype frametype;
75 
76     if (!vst && !myth)
77         return 1; // no codec data needed
78     while (!avio_feof(pb)) {
79         int size, subtype, ret;
80 
81         frametype = avio_r8(pb);
82         switch (frametype) {
83         case NUV_EXTRADATA:
84             subtype = avio_r8(pb);
85             avio_skip(pb, 6);
86             size = PKTSIZE(avio_rl32(pb));
87             if (vst && subtype == 'R') {
88                 if ((ret = ff_get_extradata(NULL, vst->codecpar, pb, size)) < 0)
89                     return ret;
90                 size = 0;
91                 if (!myth)
92                     return 0;
93             }
94             break;
95         case NUV_MYTHEXT:
96             avio_skip(pb, 7);
97             size = PKTSIZE(avio_rl32(pb));
98             if (size != 128 * 4)
99                 break;
100             avio_rl32(pb); // version
101             if (vst) {
102                 vst->codecpar->codec_tag = avio_rl32(pb);
103                 vst->codecpar->codec_id =
104                     ff_codec_get_id(ff_codec_bmp_tags, vst->codecpar->codec_tag);
105                 if (vst->codecpar->codec_tag == MKTAG('R', 'J', 'P', 'G'))
106                     vst->codecpar->codec_id = AV_CODEC_ID_NUV;
107             } else
108                 avio_skip(pb, 4);
109 
110             if (ast) {
111                 int id;
112 
113                 ast->codecpar->codec_tag             = avio_rl32(pb);
114                 ast->codecpar->sample_rate           = avio_rl32(pb);
115                 if (ast->codecpar->sample_rate <= 0) {
116                     av_log(s, AV_LOG_ERROR, "Invalid sample rate %d\n", ast->codecpar->sample_rate);
117                     return AVERROR_INVALIDDATA;
118                 }
119                 ast->codecpar->bits_per_coded_sample = avio_rl32(pb);
120                 av_channel_layout_uninit(&ast->codecpar->ch_layout);
121                 ast->codecpar->ch_layout.order       = AV_CHANNEL_ORDER_UNSPEC;
122                 ast->codecpar->ch_layout.nb_channels = avio_rl32(pb);
123                 if (ast->codecpar->ch_layout.nb_channels <= 0) {
124                     av_log(s, AV_LOG_ERROR, "Invalid channels %d\n", ast->codecpar->ch_layout.nb_channels);
125                     return AVERROR_INVALIDDATA;
126                 }
127 
128                 id = ff_wav_codec_get_id(ast->codecpar->codec_tag,
129                                          ast->codecpar->bits_per_coded_sample);
130                 if (id == AV_CODEC_ID_NONE) {
131                     id = ff_codec_get_id(nuv_audio_tags, ast->codecpar->codec_tag);
132                     if (id == AV_CODEC_ID_PCM_S16LE)
133                         id = ff_get_pcm_codec_id(ast->codecpar->bits_per_coded_sample,
134                                                  0, 0, ~1);
135                 }
136                 ast->codecpar->codec_id = id;
137 
138                 ffstream(ast)->need_parsing = AVSTREAM_PARSE_FULL;
139             } else
140                 avio_skip(pb, 4 * 4);
141 
142             size -= 6 * 4;
143             avio_skip(pb, size);
144             return 0;
145         case NUV_SEEKP:
146             size = 11;
147             break;
148         default:
149             avio_skip(pb, 7);
150             size = PKTSIZE(avio_rl32(pb));
151             break;
152         }
153         avio_skip(pb, size);
154     }
155 
156     return 0;
157 }
158 
nuv_header(AVFormatContext * s)159 static int nuv_header(AVFormatContext *s)
160 {
161     NUVContext *ctx = s->priv_data;
162     AVIOContext *pb = s->pb;
163     char id_string[12];
164     double aspect, fps;
165     int is_mythtv, width, height, v_packs, a_packs, ret;
166     AVStream *vst = NULL, *ast = NULL;
167 
168     avio_read(pb, id_string, 12);
169     is_mythtv = !memcmp(id_string, "MythTVVideo", 12);
170     avio_skip(pb, 5);       // version string
171     avio_skip(pb, 3);       // padding
172     width  = avio_rl32(pb);
173     height = avio_rl32(pb);
174     avio_rl32(pb);          // unused, "desiredwidth"
175     avio_rl32(pb);          // unused, "desiredheight"
176     avio_r8(pb);            // 'P' == progressive, 'I' == interlaced
177     avio_skip(pb, 3);       // padding
178     aspect = av_int2double(avio_rl64(pb));
179     if (aspect > 0.9999 && aspect < 1.0001)
180         aspect = 4.0 / 3.0;
181     fps = av_int2double(avio_rl64(pb));
182     if (fps < 0.0f) {
183         if (s->error_recognition & AV_EF_EXPLODE) {
184             av_log(s, AV_LOG_ERROR, "Invalid frame rate %f\n", fps);
185             return AVERROR_INVALIDDATA;
186         } else {
187             av_log(s, AV_LOG_WARNING, "Invalid frame rate %f, setting to 0.\n", fps);
188             fps = 0.0f;
189         }
190     }
191 
192     // number of packets per stream type, -1 means unknown, e.g. streaming
193     v_packs = avio_rl32(pb);
194     a_packs = avio_rl32(pb);
195     avio_rl32(pb); // text
196 
197     avio_rl32(pb); // keyframe distance (?)
198 
199     if (v_packs) {
200         vst = avformat_new_stream(s, NULL);
201         if (!vst)
202             return AVERROR(ENOMEM);
203         ctx->v_id = vst->index;
204 
205         ret = av_image_check_size(width, height, 0, s);
206         if (ret < 0)
207             return ret;
208 
209         vst->codecpar->codec_type            = AVMEDIA_TYPE_VIDEO;
210         vst->codecpar->codec_id              = AV_CODEC_ID_NUV;
211         vst->codecpar->width                 = width;
212         vst->codecpar->height                = height;
213         vst->codecpar->bits_per_coded_sample = 10;
214         vst->sample_aspect_ratio          = av_d2q(aspect * height / width,
215                                                    10000);
216 #if FF_API_R_FRAME_RATE
217         vst->r_frame_rate =
218 #endif
219         vst->avg_frame_rate = av_d2q(fps, 60000);
220         avpriv_set_pts_info(vst, 32, 1, 1000);
221     } else
222         ctx->v_id = -1;
223 
224     if (a_packs) {
225         ast = avformat_new_stream(s, NULL);
226         if (!ast)
227             return AVERROR(ENOMEM);
228         ctx->a_id = ast->index;
229 
230         ast->codecpar->codec_type            = AVMEDIA_TYPE_AUDIO;
231         ast->codecpar->codec_id              = AV_CODEC_ID_PCM_S16LE;
232         ast->codecpar->ch_layout             = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO;
233         ast->codecpar->sample_rate           = 44100;
234         ast->codecpar->bit_rate              = 2 * 2 * 44100 * 8;
235         ast->codecpar->block_align           = 2 * 2;
236         ast->codecpar->bits_per_coded_sample = 16;
237         avpriv_set_pts_info(ast, 32, 1, 1000);
238     } else
239         ctx->a_id = -1;
240 
241     if ((ret = get_codec_data(s, pb, vst, ast, is_mythtv)) < 0)
242         return ret;
243 
244     ctx->rtjpg_video = vst && vst->codecpar->codec_id == AV_CODEC_ID_NUV;
245 
246     return 0;
247 }
248 
249 #define HDRSIZE 12
250 
nuv_packet(AVFormatContext * s,AVPacket * pkt)251 static int nuv_packet(AVFormatContext *s, AVPacket *pkt)
252 {
253     NUVContext *ctx = s->priv_data;
254     AVIOContext *pb = s->pb;
255     uint8_t hdr[HDRSIZE];
256     nuv_frametype frametype;
257     int ret, size;
258 
259     while (!avio_feof(pb)) {
260         int copyhdrsize = ctx->rtjpg_video ? HDRSIZE : 0;
261         uint64_t pos    = avio_tell(pb);
262 
263         ret = ffio_read_size(pb, hdr, HDRSIZE);
264         if (ret < 0)
265             return ret;
266 
267         frametype = hdr[0];
268         size      = PKTSIZE(AV_RL32(&hdr[8]));
269 
270         switch (frametype) {
271         case NUV_EXTRADATA:
272             if (!ctx->rtjpg_video) {
273                 avio_skip(pb, size);
274                 break;
275             }
276         case NUV_VIDEO:
277             if (ctx->v_id < 0) {
278                 av_log(s, AV_LOG_ERROR, "Video packet in file without video stream!\n");
279                 avio_skip(pb, size);
280                 break;
281             }
282             ret = av_new_packet(pkt, copyhdrsize + size);
283             if (ret < 0)
284                 return ret;
285 
286             pkt->pos          = pos;
287             pkt->flags       |= hdr[2] == 0 ? AV_PKT_FLAG_KEY : 0;
288             pkt->pts          = AV_RL32(&hdr[4]);
289             pkt->stream_index = ctx->v_id;
290             memcpy(pkt->data, hdr, copyhdrsize);
291             ret = avio_read(pb, pkt->data + copyhdrsize, size);
292             if (ret < 0) {
293                 return ret;
294             }
295             if (ret < size)
296                 av_shrink_packet(pkt, copyhdrsize + ret);
297             return 0;
298         case NUV_AUDIO:
299             if (ctx->a_id < 0) {
300                 av_log(s, AV_LOG_ERROR, "Audio packet in file without audio stream!\n");
301                 avio_skip(pb, size);
302                 break;
303             }
304             ret               = av_get_packet(pb, pkt, size);
305             pkt->flags       |= AV_PKT_FLAG_KEY;
306             pkt->pos          = pos;
307             pkt->pts          = AV_RL32(&hdr[4]);
308             pkt->stream_index = ctx->a_id;
309             if (ret < 0)
310                 return ret;
311             return 0;
312         case NUV_SEEKP:
313             // contains no data, size value is invalid
314             break;
315         default:
316             avio_skip(pb, size);
317             break;
318         }
319     }
320 
321     return AVERROR(EIO);
322 }
323 
324 /**
325  * \brief looks for the string RTjjjjjjjjjj in the stream too resync reading
326  * \return 1 if the syncword is found 0 otherwise.
327  */
nuv_resync(AVFormatContext * s,int64_t pos_limit)328 static int nuv_resync(AVFormatContext *s, int64_t pos_limit) {
329     AVIOContext *pb = s->pb;
330     uint32_t tag = 0;
331     while(!avio_feof(pb) && avio_tell(pb) < pos_limit) {
332         tag = (tag << 8) | avio_r8(pb);
333         if (tag                  == MKBETAG('R','T','j','j') &&
334            (tag = avio_rb32(pb)) == MKBETAG('j','j','j','j') &&
335            (tag = avio_rb32(pb)) == MKBETAG('j','j','j','j'))
336             return 1;
337     }
338     return 0;
339 }
340 
341 /**
342  * \brief attempts to read a timestamp from stream at the given stream position
343  * \return timestamp if successful and AV_NOPTS_VALUE if failure
344  */
nuv_read_dts(AVFormatContext * s,int stream_index,int64_t * ppos,int64_t pos_limit)345 static int64_t nuv_read_dts(AVFormatContext *s, int stream_index,
346                             int64_t *ppos, int64_t pos_limit)
347 {
348     NUVContext *ctx = s->priv_data;
349     AVIOContext *pb = s->pb;
350     uint8_t hdr[HDRSIZE];
351     nuv_frametype frametype;
352     int size, key, idx;
353     int64_t pos, dts;
354 
355     if (avio_seek(pb, *ppos, SEEK_SET) < 0)
356         return AV_NOPTS_VALUE;
357 
358     if (!nuv_resync(s, pos_limit))
359         return AV_NOPTS_VALUE;
360 
361     while (!avio_feof(pb) && avio_tell(pb) < pos_limit) {
362         if (avio_read(pb, hdr, HDRSIZE) < HDRSIZE)
363             return AV_NOPTS_VALUE;
364         frametype = hdr[0];
365         size = PKTSIZE(AV_RL32(&hdr[8]));
366         switch (frametype) {
367             case NUV_SEEKP:
368                 break;
369             case NUV_AUDIO:
370             case NUV_VIDEO:
371                 if (frametype == NUV_VIDEO) {
372                     idx = ctx->v_id;
373                     key = hdr[2] == 0;
374                 } else {
375                     idx = ctx->a_id;
376                     key = 1;
377                 }
378                 if (stream_index == idx) {
379 
380                     pos = avio_tell(s->pb) - HDRSIZE;
381                     dts = AV_RL32(&hdr[4]);
382 
383                     // TODO - add general support in av_gen_search, so it adds positions after reading timestamps
384                     av_add_index_entry(s->streams[stream_index], pos, dts, size + HDRSIZE, 0,
385                             key ? AVINDEX_KEYFRAME : 0);
386 
387                     *ppos = pos;
388                     return dts;
389                 }
390             default:
391                 avio_skip(pb, size);
392                 break;
393         }
394     }
395     return AV_NOPTS_VALUE;
396 }
397 
398 
399 const AVInputFormat ff_nuv_demuxer = {
400     .name           = "nuv",
401     .long_name      = NULL_IF_CONFIG_SMALL("NuppelVideo"),
402     .priv_data_size = sizeof(NUVContext),
403     .read_probe     = nuv_probe,
404     .read_header    = nuv_header,
405     .read_packet    = nuv_packet,
406     .read_timestamp = nuv_read_dts,
407     .flags          = AVFMT_GENERIC_INDEX,
408 };
409