/src/ffmpeg/libavformat/rtpdec_rfc4175.c
Line | Count | Source |
1 | | /* |
2 | | * RTP Depacketization of RAW video (TR-03) |
3 | | * Copyright (c) 2016 Savoir-faire Linux, Inc |
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 | | /* Development sponsored by CBC/Radio-Canada */ |
23 | | |
24 | | #include "avio_internal.h" |
25 | | #include "rtpdec_formats.h" |
26 | | #include "libavutil/avassert.h" |
27 | | #include "libavutil/avstring.h" |
28 | | #include "libavutil/imgutils.h" |
29 | | #include "libavutil/mem.h" |
30 | | #include "libavutil/pixdesc.h" |
31 | | #include "libavutil/parseutils.h" |
32 | | |
33 | | struct PayloadContext { |
34 | | char *sampling; |
35 | | AVRational framerate; |
36 | | int depth; |
37 | | int width; |
38 | | int height; |
39 | | int interlaced; |
40 | | int field; |
41 | | |
42 | | uint8_t *frame; |
43 | | unsigned int frame_size; |
44 | | unsigned int pgroup; /* size of the pixel group in bytes */ |
45 | | unsigned int xinc; |
46 | | |
47 | | uint32_t timestamp; |
48 | | }; |
49 | | |
50 | | static int rfc4175_parse_format(AVStream *stream, PayloadContext *data) |
51 | 0 | { |
52 | 0 | enum AVPixelFormat pixfmt; |
53 | 0 | int tag; |
54 | 0 | const AVPixFmtDescriptor *desc; |
55 | |
|
56 | 0 | if (!strncmp(data->sampling, "YCbCr-4:2:2", 11)) { |
57 | 0 | tag = MKTAG('U', 'Y', 'V', 'Y'); |
58 | 0 | data->xinc = 2; |
59 | |
|
60 | 0 | if (data->depth == 8) { |
61 | 0 | data->pgroup = 4; |
62 | 0 | pixfmt = AV_PIX_FMT_UYVY422; |
63 | 0 | stream->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; |
64 | 0 | } else if (data->depth == 10) { |
65 | 0 | data->pgroup = 5; |
66 | 0 | pixfmt = AV_PIX_FMT_YUV422P10; |
67 | 0 | stream->codecpar->codec_id = AV_CODEC_ID_BITPACKED; |
68 | 0 | } else { |
69 | 0 | return AVERROR_INVALIDDATA; |
70 | 0 | } |
71 | 0 | } else if (!strncmp(data->sampling, "YCbCr-4:2:0", 11)) { |
72 | 0 | tag = MKTAG('I', '4', '2', '0'); |
73 | 0 | data->xinc = 4; |
74 | |
|
75 | 0 | if (data->depth == 8) { |
76 | 0 | data->pgroup = 6; |
77 | 0 | pixfmt = AV_PIX_FMT_YUV420P; |
78 | 0 | stream->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; |
79 | 0 | } else { |
80 | 0 | return AVERROR_INVALIDDATA; |
81 | 0 | } |
82 | 0 | } else if (!strncmp(data->sampling, "RGB", 3)) { |
83 | 0 | tag = MKTAG('R', 'G', 'B', 24); |
84 | 0 | if (data->depth == 8) { |
85 | 0 | data->xinc = 1; |
86 | 0 | data->pgroup = 3; |
87 | 0 | pixfmt = AV_PIX_FMT_RGB24; |
88 | 0 | stream->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; |
89 | 0 | } else { |
90 | 0 | return AVERROR_INVALIDDATA; |
91 | 0 | } |
92 | 0 | } else if (!strncmp(data->sampling, "BGR", 3)) { |
93 | 0 | tag = MKTAG('B', 'G', 'R', 24); |
94 | 0 | if (data->depth == 8) { |
95 | 0 | data->xinc = 1; |
96 | 0 | data->pgroup = 3; |
97 | 0 | pixfmt = AV_PIX_FMT_BGR24; |
98 | 0 | stream->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; |
99 | 0 | } else { |
100 | 0 | return AVERROR_INVALIDDATA; |
101 | 0 | } |
102 | 0 | } else { |
103 | 0 | return AVERROR_INVALIDDATA; |
104 | 0 | } |
105 | | |
106 | 0 | desc = av_pix_fmt_desc_get(pixfmt); |
107 | 0 | stream->codecpar->format = pixfmt; |
108 | 0 | stream->codecpar->codec_tag = tag; |
109 | 0 | stream->codecpar->bits_per_coded_sample = av_get_bits_per_pixel(desc); |
110 | 0 | data->frame_size = data->width * data->height * data->pgroup / data->xinc; |
111 | |
|
112 | 0 | if (data->interlaced) |
113 | 0 | stream->codecpar->field_order = AV_FIELD_TT; |
114 | 0 | else |
115 | 0 | stream->codecpar->field_order = AV_FIELD_PROGRESSIVE; |
116 | |
|
117 | 0 | if (data->framerate.den > 0) { |
118 | 0 | stream->avg_frame_rate = data->framerate; |
119 | 0 | stream->codecpar->bit_rate = data->frame_size * av_q2d(data->framerate) * 8; |
120 | 0 | } |
121 | |
|
122 | 0 | return 0; |
123 | 0 | } |
124 | | |
125 | | static int rfc4175_parse_fmtp(AVFormatContext *s, AVStream *stream, |
126 | | PayloadContext *data, const char *attr, |
127 | | const char *value) |
128 | 0 | { |
129 | 0 | if (!strncmp(attr, "width", 5)) |
130 | 0 | data->width = atoi(value); |
131 | 0 | else if (!strncmp(attr, "height", 6)) |
132 | 0 | data->height = atoi(value); |
133 | 0 | else if (data->sampling == NULL && !strncmp(attr, "sampling", 8)) |
134 | 0 | data->sampling = av_strdup(value); |
135 | 0 | else if (!strncmp(attr, "depth", 5)) |
136 | 0 | data->depth = atoi(value); |
137 | 0 | else if (!strncmp(attr, "interlace", 9)) |
138 | 0 | data->interlaced = 1; |
139 | 0 | else if (!strncmp(attr, "exactframerate", 14)) { |
140 | 0 | if (av_parse_video_rate(&data->framerate, value) < 0) |
141 | 0 | return AVERROR(EINVAL); |
142 | 0 | } else if (!strncmp(attr, "TCS", 3)) { |
143 | 0 | if (!strncmp(value, "SDR", 3)) |
144 | 0 | stream->codecpar->color_trc = AVCOL_TRC_BT709; |
145 | 0 | else if (!strncmp(value, "PQ", 2)) |
146 | 0 | stream->codecpar->color_trc = AVCOL_TRC_SMPTE2084; |
147 | 0 | else if (!strncmp(value, "HLG", 3)) |
148 | 0 | stream->codecpar->color_trc = AVCOL_TRC_ARIB_STD_B67; |
149 | 0 | else if (!strncmp(value, "LINEAR", 6)) |
150 | 0 | stream->codecpar->color_trc = AVCOL_TRC_LINEAR; |
151 | 0 | else if (!strncmp(value, "ST428-1", 7)) |
152 | 0 | stream->codecpar->color_trc = AVCOL_TRC_SMPTEST428_1; |
153 | 0 | else |
154 | 0 | stream->codecpar->color_trc = AVCOL_TRC_UNSPECIFIED; |
155 | 0 | } else if (!strncmp(attr, "colorimetry", 11)) { |
156 | 0 | if (!strncmp(value, "BT601", 5)) { |
157 | 0 | stream->codecpar->color_primaries = AVCOL_PRI_BT470BG; |
158 | 0 | stream->codecpar->color_space = AVCOL_SPC_BT470BG; |
159 | 0 | } else if (!strncmp(value, "BT709", 5)) { |
160 | 0 | stream->codecpar->color_primaries = AVCOL_PRI_BT709; |
161 | 0 | stream->codecpar->color_space = AVCOL_SPC_BT709; |
162 | 0 | } else if (!strncmp(value, "BT2020", 6)) { |
163 | 0 | stream->codecpar->color_primaries = AVCOL_PRI_BT2020; |
164 | 0 | stream->codecpar->color_space = AVCOL_SPC_BT2020_NCL; |
165 | 0 | } |
166 | 0 | } else if (!strncmp(attr, "RANGE", 5)) { |
167 | 0 | if (!strncmp(value, "NARROW", 6)) |
168 | 0 | stream->codecpar->color_range = AVCOL_RANGE_MPEG; |
169 | 0 | else if (!strncmp(value, "FULL", 4)) |
170 | 0 | stream->codecpar->color_range = AVCOL_RANGE_JPEG; |
171 | 0 | } |
172 | | |
173 | 0 | return 0; |
174 | 0 | } |
175 | | |
176 | | static int rfc4175_parse_sdp_line(AVFormatContext *s, int st_index, |
177 | | PayloadContext *data_arg, const char *line) |
178 | 0 | { |
179 | 0 | const char *p; |
180 | |
|
181 | 0 | if (st_index < 0) |
182 | 0 | return 0; |
183 | | |
184 | 0 | av_assert0(!data_arg->sampling); |
185 | | |
186 | 0 | if (av_strstart(line, "fmtp:", &p)) { |
187 | 0 | AVStream *stream = s->streams[st_index]; |
188 | 0 | PayloadContext data0 = *data_arg, *data = &data0; |
189 | 0 | int ret = ff_parse_fmtp(s, stream, data, p, rfc4175_parse_fmtp); |
190 | |
|
191 | 0 | if (!data->sampling || !data->depth || !data->width || !data->height) |
192 | 0 | ret = AVERROR(EINVAL); |
193 | |
|
194 | 0 | if (ret < 0) |
195 | 0 | goto fail; |
196 | | |
197 | 0 | ret = av_image_check_size(data->width, data->height, 0, s); |
198 | 0 | if (ret < 0) |
199 | 0 | goto fail; |
200 | | |
201 | 0 | stream->codecpar->width = data->width; |
202 | 0 | stream->codecpar->height = data->height; |
203 | |
|
204 | 0 | ret = rfc4175_parse_format(stream, data); |
205 | 0 | av_freep(&data->sampling); |
206 | 0 | if (ret >= 0) |
207 | 0 | *data_arg = *data; |
208 | 0 | fail: |
209 | 0 | av_freep(&data->sampling); |
210 | 0 | return ret; |
211 | 0 | } |
212 | | |
213 | 0 | return 0; |
214 | 0 | } |
215 | | |
216 | | static int rfc4175_finalize_packet(PayloadContext *data, AVPacket *pkt, |
217 | | int stream_index) |
218 | 0 | { |
219 | 0 | int ret = AVERROR(EAGAIN); |
220 | |
|
221 | 0 | pkt->stream_index = stream_index; |
222 | 0 | if (!data->interlaced || data->field) { |
223 | 0 | ret = av_packet_from_data(pkt, data->frame, data->frame_size); |
224 | 0 | if (ret < 0) { |
225 | 0 | av_freep(&data->frame); |
226 | 0 | } |
227 | 0 | data->frame = NULL; |
228 | 0 | } |
229 | |
|
230 | 0 | data->field = 0; |
231 | |
|
232 | 0 | return ret; |
233 | 0 | } |
234 | | |
235 | | static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, |
236 | | AVStream *st, AVPacket *pkt, uint32_t *timestamp, |
237 | | const uint8_t * buf, int len, |
238 | | uint16_t seq, int flags) |
239 | 0 | { |
240 | 0 | int length, line, offset, cont, field; |
241 | 0 | const uint8_t *headers, *payload; |
242 | 0 | int payload_len; |
243 | 0 | int missed_last_packet = 0; |
244 | |
|
245 | 0 | uint8_t *dest; |
246 | |
|
247 | 0 | if (!buf) { |
248 | 0 | *timestamp = data->timestamp; |
249 | 0 | return rfc4175_finalize_packet(data, pkt, st->index); |
250 | 0 | } |
251 | | |
252 | 0 | headers = buf + 2; /* skip extended seqnum */ |
253 | 0 | payload = buf + 2; |
254 | 0 | payload_len = len - 2; |
255 | |
|
256 | 0 | if (*timestamp != data->timestamp) { |
257 | 0 | if (data->frame && (!data->interlaced || data->field)) { |
258 | | /* |
259 | | * if we're here, it means that we missed the cue to return |
260 | | * the previous AVPacket, that cue being the RTP_FLAG_MARKER |
261 | | * in the last packet of either the previous frame (progressive) |
262 | | * or the previous second field (interlace). Let's finalize the |
263 | | * previous frame (or pair of fields) anyway by filling the AVPacket. |
264 | | */ |
265 | 0 | av_log(ctx, AV_LOG_ERROR, "Missed previous RTP Marker\n"); |
266 | 0 | missed_last_packet = rfc4175_finalize_packet(data, pkt, st->index) >= 0; |
267 | 0 | } |
268 | |
|
269 | 0 | if (!data->frame) |
270 | 0 | data->frame = av_mallocz(data->frame_size + AV_INPUT_BUFFER_PADDING_SIZE); |
271 | |
|
272 | 0 | if (missed_last_packet) |
273 | 0 | FFSWAP(uint32_t, data->timestamp, *timestamp); |
274 | 0 | else |
275 | 0 | data->timestamp = *timestamp; |
276 | |
|
277 | 0 | if (!data->frame) { |
278 | 0 | av_log(ctx, AV_LOG_ERROR, "Out of memory.\n"); |
279 | 0 | return missed_last_packet ? 0 : AVERROR(ENOMEM); |
280 | 0 | } |
281 | 0 | } |
282 | | |
283 | | /* |
284 | | * looks for the 'Continuation bit' in scan lines' headers |
285 | | * to find where data start |
286 | | */ |
287 | 0 | do { |
288 | 0 | if (payload_len < 6) |
289 | 0 | goto invalid; |
290 | | |
291 | 0 | cont = payload[4] & 0x80; |
292 | 0 | payload += 6; |
293 | 0 | payload_len -= 6; |
294 | 0 | } while (cont); |
295 | | |
296 | | /* and now iterate over every scan lines */ |
297 | 0 | do { |
298 | 0 | int copy_offset; |
299 | |
|
300 | 0 | if (payload_len < data->pgroup) |
301 | 0 | goto invalid; |
302 | | |
303 | 0 | length = (headers[0] << 8) | headers[1]; |
304 | 0 | field = (headers[2] & 0x80) >> 7; |
305 | 0 | line = ((headers[2] & 0x7f) << 8) | headers[3]; |
306 | 0 | offset = ((headers[4] & 0x7f) << 8) | headers[5]; |
307 | 0 | cont = headers[4] & 0x80; |
308 | 0 | headers += 6; |
309 | 0 | data->field = field; |
310 | |
|
311 | 0 | if (!data->pgroup || length % data->pgroup) |
312 | 0 | goto invalid; |
313 | | |
314 | 0 | if (length > payload_len) |
315 | 0 | length = payload_len; |
316 | |
|
317 | 0 | if (data->interlaced) |
318 | 0 | line = 2 * line + field; |
319 | |
|
320 | 0 | if (line >= data->height) |
321 | 0 | goto invalid; |
322 | | |
323 | | /* prevent ill-formed packets to write after buffer's end */ |
324 | 0 | copy_offset = (line * data->width + offset) * data->pgroup / data->xinc; |
325 | 0 | if (copy_offset + length > data->frame_size || !data->frame) |
326 | 0 | goto invalid; |
327 | | |
328 | 0 | dest = data->frame + copy_offset; |
329 | 0 | memcpy(dest, payload, length); |
330 | |
|
331 | 0 | payload += length; |
332 | 0 | payload_len -= length; |
333 | 0 | } while (cont); |
334 | | |
335 | 0 | if (missed_last_packet) { |
336 | 0 | return !!(flags & RTP_FLAG_MARKER); |
337 | 0 | } else if ((flags & RTP_FLAG_MARKER)) { |
338 | 0 | return rfc4175_finalize_packet(data, pkt, st->index); |
339 | 0 | } |
340 | | |
341 | 0 | return AVERROR(EAGAIN); |
342 | | |
343 | 0 | invalid: |
344 | 0 | return missed_last_packet ? 0 : AVERROR_INVALIDDATA; |
345 | 0 | } |
346 | | |
347 | | static void rfc4175_close_context(PayloadContext *data) |
348 | 0 | { |
349 | 0 | av_freep(&data->frame); |
350 | 0 | } |
351 | | |
352 | | const RTPDynamicProtocolHandler ff_rfc4175_rtp_handler = { |
353 | | .enc_name = "raw", |
354 | | .codec_type = AVMEDIA_TYPE_VIDEO, |
355 | | .codec_id = AV_CODEC_ID_NONE, |
356 | | .priv_data_size = sizeof(PayloadContext), |
357 | | .parse_sdp_a_line = rfc4175_parse_sdp_line, |
358 | | .parse_packet = rfc4175_handle_packet, |
359 | | .close = rfc4175_close_context, |
360 | | }; |