| 1 | /* |
| 2 | Copyright (c) 2012, Broadcom Europe Ltd |
| 3 | All rights reserved. |
| 4 | |
| 5 | Redistribution and use in source and binary forms, with or without |
| 6 | modification, are permitted provided that the following conditions are met: |
| 7 | * Redistributions of source code must retain the above copyright |
| 8 | notice, this list of conditions and the following disclaimer. |
| 9 | * Redistributions in binary form must reproduce the above copyright |
| 10 | notice, this list of conditions and the following disclaimer in the |
| 11 | documentation and/or other materials provided with the distribution. |
| 12 | * Neither the name of the copyright holder nor the |
| 13 | names of its contributors may be used to endorse or promote products |
| 14 | derived from this software without specific prior written permission. |
| 15 | |
| 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
| 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | */ |
| 27 | #include <stdio.h> |
| 28 | #include <stdlib.h> |
| 29 | #include <string.h> |
| 30 | |
| 31 | #include "containers/core/containers_private.h" |
| 32 | #include "containers/core/containers_io_helpers.h" |
| 33 | #include "containers/core/containers_utils.h" |
| 34 | #include "containers/core/containers_logging.h" |
| 35 | |
| 36 | #include "raw_video_common.h" |
| 37 | |
| 38 | /****************************************************************************** |
| 39 | Defines. |
| 40 | ******************************************************************************/ |
| 41 | #define 1024 |
| 42 | #define 256 |
| 43 | #define OPTION_SIZE_MAX 32 |
| 44 | |
| 45 | /****************************************************************************** |
| 46 | Type definitions |
| 47 | ******************************************************************************/ |
| 48 | typedef struct VC_CONTAINER_MODULE_T |
| 49 | { |
| 50 | VC_CONTAINER_TRACK_T *track; |
| 51 | VC_CONTAINER_STATUS_T status; |
| 52 | |
| 53 | bool yuv4mpeg2; |
| 54 | bool non_standard; |
| 55 | char option[OPTION_SIZE_MAX]; |
| 56 | |
| 57 | bool ; |
| 58 | unsigned int ; |
| 59 | |
| 60 | int64_t data_offset; |
| 61 | unsigned int block_size; |
| 62 | unsigned int block_offset; |
| 63 | unsigned int frames; |
| 64 | |
| 65 | } VC_CONTAINER_MODULE_T; |
| 66 | |
| 67 | /****************************************************************************** |
| 68 | Function prototypes |
| 69 | ******************************************************************************/ |
| 70 | VC_CONTAINER_STATUS_T rawvideo_reader_open( VC_CONTAINER_T * ); |
| 71 | |
| 72 | /****************************************************************************** |
| 73 | Local Functions |
| 74 | ******************************************************************************/ |
| 75 | static VC_CONTAINER_STATUS_T read_yuv4mpeg2_option( VC_CONTAINER_T *ctx, |
| 76 | unsigned int *bytes_left ) |
| 77 | { |
| 78 | VC_CONTAINER_MODULE_T *module = ctx->priv->module; |
| 79 | unsigned int size, i; |
| 80 | |
| 81 | /* Start by skipping spaces */ |
| 82 | while (*bytes_left && PEEK_U8(ctx) == ' ') |
| 83 | (*bytes_left)--, _SKIP_U8(ctx); |
| 84 | |
| 85 | size = PEEK_BYTES(ctx, module->option, |
| 86 | MIN(sizeof(module->option), *bytes_left)); |
| 87 | |
| 88 | /* The config option ends at next space or newline */ |
| 89 | for (i = 0; i < size; i++) |
| 90 | { |
| 91 | if (module->option[i] == ' ' || module->option[i] == 0x0a) |
| 92 | { |
| 93 | module->option[i] = 0; |
| 94 | break; |
| 95 | } |
| 96 | } |
| 97 | if (i == 0) |
| 98 | return VC_CONTAINER_ERROR_NOT_FOUND; |
| 99 | |
| 100 | *bytes_left -= i; |
| 101 | SKIP_BYTES(ctx, i); |
| 102 | |
| 103 | /* If option is too long, we just discard it */ |
| 104 | if (i == size) |
| 105 | { |
| 106 | while (*bytes_left && PEEK_U8(ctx) != ' ' && PEEK_U8(ctx) != 0x0a) |
| 107 | (*bytes_left)--, _SKIP_U8(ctx); |
| 108 | return VC_CONTAINER_ERROR_NOT_FOUND; |
| 109 | } |
| 110 | |
| 111 | return VC_CONTAINER_SUCCESS; |
| 112 | } |
| 113 | |
| 114 | static VC_CONTAINER_STATUS_T ( VC_CONTAINER_T *ctx ) |
| 115 | { |
| 116 | VC_CONTAINER_MODULE_T *module = ctx->priv->module; |
| 117 | unsigned int bytes_left = FILE_HEADER_SIZE_MAX - 10; |
| 118 | unsigned int value1, value2; |
| 119 | char codec[OPTION_SIZE_MAX] = "420" ; |
| 120 | uint8_t h[10]; |
| 121 | |
| 122 | /* Check for the YUV4MPEG2 signature */ |
| 123 | if (READ_BYTES(ctx, h, sizeof(h)) != sizeof(h)) |
| 124 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
| 125 | |
| 126 | if (memcmp(h, "YUV4MPEG2 " , sizeof(h))) |
| 127 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
| 128 | |
| 129 | /* Parse parameters */ |
| 130 | while (read_yuv4mpeg2_option(ctx, &bytes_left) == VC_CONTAINER_SUCCESS) |
| 131 | { |
| 132 | if (sscanf(module->option, "W%i" , &value1) == 1) |
| 133 | ctx->tracks[0]->format->type->video.width = value1; |
| 134 | else if (sscanf(module->option, "H%i" , &value1) == 1) |
| 135 | ctx->tracks[0]->format->type->video.height = value1; |
| 136 | else if (sscanf(module->option, "S%i" , &value1) == 1) |
| 137 | module->block_size = value1; |
| 138 | else if (sscanf(module->option, "F%i:%i" , &value1, &value2) == 2) |
| 139 | { |
| 140 | ctx->tracks[0]->format->type->video.frame_rate_num = value1; |
| 141 | ctx->tracks[0]->format->type->video.frame_rate_den = value2; |
| 142 | } |
| 143 | else if (sscanf(module->option, "A%i:%i" , &value1, &value2) == 2) |
| 144 | { |
| 145 | ctx->tracks[0]->format->type->video.par_num = value1; |
| 146 | ctx->tracks[0]->format->type->video.par_den = value2; |
| 147 | } |
| 148 | else if (module->option[0] == 'C') |
| 149 | { |
| 150 | strcpy(codec, module->option+1); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | /* Check the end marker */ |
| 155 | if (_READ_U8(ctx) != 0x0a) |
| 156 | { |
| 157 | LOG_ERROR(ctx, "missing end of header marker" ); |
| 158 | return VC_CONTAINER_ERROR_CORRUPTED; |
| 159 | } |
| 160 | |
| 161 | /* Find out which codec we are dealing with */ |
| 162 | if (from_yuv4mpeg2(codec, &ctx->tracks[0]->format->codec, &value1, &value2)) |
| 163 | { |
| 164 | module->block_size = ctx->tracks[0]->format->type->video.width * |
| 165 | ctx->tracks[0]->format->type->video.height * value1 / value2; |
| 166 | } |
| 167 | else |
| 168 | { |
| 169 | memcpy(&ctx->tracks[0]->format->codec, codec, 4); |
| 170 | module->non_standard = true; |
| 171 | } |
| 172 | |
| 173 | return VC_CONTAINER_SUCCESS; |
| 174 | } |
| 175 | |
| 176 | static VC_CONTAINER_STATUS_T ( VC_CONTAINER_T *ctx ) |
| 177 | { |
| 178 | VC_CONTAINER_MODULE_T *module = ctx->priv->module; |
| 179 | unsigned int bytes_left = FRAME_HEADER_SIZE_MAX - 5; |
| 180 | unsigned int value1; |
| 181 | char [5]; |
| 182 | |
| 183 | if (READ_BYTES(ctx, header, sizeof(header)) != sizeof(header) || |
| 184 | memcmp(header, "FRAME" , sizeof(header))) |
| 185 | { |
| 186 | LOG_ERROR(ctx, "missing frame marker" ); |
| 187 | return STREAM_STATUS(ctx) != VC_CONTAINER_SUCCESS ? |
| 188 | STREAM_STATUS(ctx) : VC_CONTAINER_ERROR_CORRUPTED; |
| 189 | } |
| 190 | |
| 191 | /* Parse parameters */ |
| 192 | while (read_yuv4mpeg2_option(ctx, &bytes_left) == VC_CONTAINER_SUCCESS) |
| 193 | { |
| 194 | if (module->non_standard && sscanf(module->option, "S%i" , &value1) == 1) |
| 195 | module->block_size = value1; |
| 196 | } |
| 197 | |
| 198 | /* Check the end marker */ |
| 199 | if (_READ_U8(ctx) != 0x0a) |
| 200 | { |
| 201 | LOG_ERROR(ctx, "missing end of frame header marker" ); |
| 202 | return VC_CONTAINER_ERROR_CORRUPTED; |
| 203 | } |
| 204 | |
| 205 | module->frame_header_size = FRAME_HEADER_SIZE_MAX - bytes_left - 1; |
| 206 | return VC_CONTAINER_SUCCESS; |
| 207 | } |
| 208 | |
| 209 | static VC_CONTAINER_STATUS_T rawvideo_parse_uri( VC_CONTAINER_T *ctx, |
| 210 | VC_CONTAINER_FOURCC_T *c, unsigned int *w, unsigned int *h, |
| 211 | unsigned int *fr_num, unsigned int *fr_den, unsigned *block_size ) |
| 212 | { |
| 213 | VC_CONTAINER_FOURCC_T codec = 0; |
| 214 | unsigned int i, matches, width = 0, height = 0, fn = 0, fd = 0, size = 0; |
| 215 | const char *uri = ctx->priv->io->uri; |
| 216 | |
| 217 | /* Try and find a match for the string describing the format */ |
| 218 | for (i = 0; uri[i]; i++) |
| 219 | { |
| 220 | if (uri[i] != '_' && uri[i+1] != 'C') |
| 221 | continue; |
| 222 | |
| 223 | matches = sscanf(uri+i, "_C%4cW%iH%iF%i#%iS%i" , (char *)&codec, |
| 224 | &width, &height, &fn, &fd, &size); |
| 225 | if (matches >= 3) |
| 226 | break; |
| 227 | } |
| 228 | if (!uri[i]) |
| 229 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
| 230 | |
| 231 | if (!size) |
| 232 | { |
| 233 | switch (codec) |
| 234 | { |
| 235 | case VC_CONTAINER_CODEC_I420: |
| 236 | case VC_CONTAINER_CODEC_YV12: |
| 237 | size = width * height * 3 / 2; |
| 238 | break; |
| 239 | default: break; |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | if (!width || !height || !size) |
| 244 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
| 245 | |
| 246 | if (block_size) *block_size = size; |
| 247 | if (c) *c = codec; |
| 248 | if (w) *w = width; |
| 249 | if (h) *h = height; |
| 250 | if (fr_num) *fr_num = fn; |
| 251 | if (fr_den) *fr_den = fd; |
| 252 | if (block_size) *block_size = size; |
| 253 | |
| 254 | return VC_CONTAINER_SUCCESS; |
| 255 | } |
| 256 | |
| 257 | /***************************************************************************** |
| 258 | Functions exported as part of the Container Module API |
| 259 | *****************************************************************************/ |
| 260 | static VC_CONTAINER_STATUS_T rawvideo_reader_read( VC_CONTAINER_T *ctx, |
| 261 | VC_CONTAINER_PACKET_T *packet, uint32_t flags ) |
| 262 | { |
| 263 | VC_CONTAINER_MODULE_T *module = ctx->priv->module; |
| 264 | unsigned int size; |
| 265 | |
| 266 | if (module->status != VC_CONTAINER_SUCCESS) |
| 267 | return module->status; |
| 268 | |
| 269 | if (module->yuv4mpeg2 && !module->block_offset && |
| 270 | !module->frame_header) |
| 271 | { |
| 272 | module->status = read_yuv4mpeg2_frame_header(ctx); |
| 273 | if (module->status != VC_CONTAINER_SUCCESS) |
| 274 | return module->status; |
| 275 | |
| 276 | module->frame_header = true; |
| 277 | } |
| 278 | |
| 279 | if (!module->block_offset) |
| 280 | packet->pts = packet->dts = module->frames * INT64_C(1000000) * |
| 281 | ctx->tracks[0]->format->type->video.frame_rate_den / |
| 282 | ctx->tracks[0]->format->type->video.frame_rate_num; |
| 283 | else |
| 284 | packet->pts = packet->dts = VC_CONTAINER_TIME_UNKNOWN; |
| 285 | packet->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END | |
| 286 | VC_CONTAINER_PACKET_FLAG_KEYFRAME; |
| 287 | if (!module->block_offset) |
| 288 | packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START; |
| 289 | packet->frame_size = module->block_size; |
| 290 | packet->size = module->block_size - module->block_offset; |
| 291 | packet->track = 0; |
| 292 | |
| 293 | if (flags & VC_CONTAINER_READ_FLAG_SKIP) |
| 294 | { |
| 295 | size = SKIP_BYTES(ctx, packet->size); |
| 296 | module->block_offset = 0; |
| 297 | module->frames++; |
| 298 | module->frame_header = 0; |
| 299 | module->status = STREAM_STATUS(ctx); |
| 300 | return module->status; |
| 301 | } |
| 302 | |
| 303 | if (flags & VC_CONTAINER_READ_FLAG_INFO) |
| 304 | return VC_CONTAINER_SUCCESS; |
| 305 | |
| 306 | size = MIN(module->block_size - module->block_offset, packet->buffer_size); |
| 307 | size = READ_BYTES(ctx, packet->data, size); |
| 308 | module->block_offset += size; |
| 309 | packet->size = size; |
| 310 | |
| 311 | if (module->block_offset == module->block_size) |
| 312 | { |
| 313 | module->block_offset = 0; |
| 314 | module->frame_header = 0; |
| 315 | module->frames++; |
| 316 | } |
| 317 | |
| 318 | module->status = size ? VC_CONTAINER_SUCCESS : STREAM_STATUS(ctx); |
| 319 | return module->status; |
| 320 | } |
| 321 | |
| 322 | /*****************************************************************************/ |
| 323 | static VC_CONTAINER_STATUS_T rawvideo_reader_seek( VC_CONTAINER_T *ctx, int64_t *offset, |
| 324 | VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) |
| 325 | { |
| 326 | VC_CONTAINER_MODULE_T *module = ctx->priv->module; |
| 327 | VC_CONTAINER_PARAM_UNUSED(mode); |
| 328 | |
| 329 | module->frames = *offset * |
| 330 | ctx->tracks[0]->format->type->video.frame_rate_num / |
| 331 | ctx->tracks[0]->format->type->video.frame_rate_den / INT64_C(1000000); |
| 332 | module->block_offset = 0; |
| 333 | |
| 334 | if ((flags & VC_CONTAINER_SEEK_FLAG_FORWARD) && |
| 335 | module->frames * INT64_C(1000000) * |
| 336 | ctx->tracks[0]->format->type->video.frame_rate_den / |
| 337 | ctx->tracks[0]->format->type->video.frame_rate_num < *offset) |
| 338 | module->frames++; |
| 339 | |
| 340 | module->frame_header = 0; |
| 341 | |
| 342 | module->status = |
| 343 | SEEK(ctx, module->data_offset + module->frames * |
| 344 | (module->block_size + module->frame_header_size)); |
| 345 | |
| 346 | return module->status; |
| 347 | } |
| 348 | |
| 349 | /*****************************************************************************/ |
| 350 | static VC_CONTAINER_STATUS_T rawvideo_reader_close( VC_CONTAINER_T *ctx ) |
| 351 | { |
| 352 | VC_CONTAINER_MODULE_T *module = ctx->priv->module; |
| 353 | for (; ctx->tracks_num > 0; ctx->tracks_num--) |
| 354 | vc_container_free_track(ctx, ctx->tracks[ctx->tracks_num-1]); |
| 355 | free(module); |
| 356 | return VC_CONTAINER_SUCCESS; |
| 357 | } |
| 358 | |
| 359 | /*****************************************************************************/ |
| 360 | VC_CONTAINER_STATUS_T rawvideo_reader_open( VC_CONTAINER_T *ctx ) |
| 361 | { |
| 362 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
| 363 | const char *extension = vc_uri_path_extension(ctx->priv->uri); |
| 364 | VC_CONTAINER_MODULE_T *module = 0; |
| 365 | bool yuv4mpeg2 = false; |
| 366 | uint8_t h[10]; |
| 367 | |
| 368 | /* Check if the user has specified a container */ |
| 369 | vc_uri_find_query(ctx->priv->uri, 0, "container" , &extension); |
| 370 | |
| 371 | /* Check for the YUV4MPEG2 signature */ |
| 372 | if (PEEK_BYTES(ctx, h, sizeof(h)) != sizeof(h)) |
| 373 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
| 374 | if (!memcmp(h, "YUV4MPEG2 " , sizeof(h))) |
| 375 | yuv4mpeg2 = true; |
| 376 | |
| 377 | /* Or check if the extension is supported */ |
| 378 | if (!yuv4mpeg2 && |
| 379 | !(extension && !strcasecmp(extension, "yuv" ))) |
| 380 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
| 381 | |
| 382 | LOG_DEBUG(ctx, "using raw video reader" ); |
| 383 | |
| 384 | /* Allocate our context */ |
| 385 | module = malloc(sizeof(*module)); |
| 386 | if (!module) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
| 387 | memset(module, 0, sizeof(*module)); |
| 388 | ctx->priv->module = module; |
| 389 | ctx->tracks_num = 1; |
| 390 | ctx->tracks = &module->track; |
| 391 | ctx->tracks[0] = vc_container_allocate_track(ctx, 0); |
| 392 | if (!ctx->tracks[0]) |
| 393 | { |
| 394 | status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
| 395 | goto error; |
| 396 | } |
| 397 | ctx->tracks[0]->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; |
| 398 | ctx->tracks[0]->is_enabled = true; |
| 399 | ctx->tracks[0]->format->type->video.frame_rate_num = 25; |
| 400 | ctx->tracks[0]->format->type->video.frame_rate_den = 1; |
| 401 | ctx->tracks[0]->format->type->video.par_num = 1; |
| 402 | ctx->tracks[0]->format->type->video.par_den = 1; |
| 403 | |
| 404 | if (yuv4mpeg2) |
| 405 | { |
| 406 | status = read_yuv4mpeg2_file_header(ctx); |
| 407 | if (status != VC_CONTAINER_SUCCESS) |
| 408 | goto error; |
| 409 | |
| 410 | module->data_offset = STREAM_POSITION(ctx); |
| 411 | |
| 412 | status = read_yuv4mpeg2_frame_header(ctx); |
| 413 | if (status != VC_CONTAINER_SUCCESS) |
| 414 | goto error; |
| 415 | module->frame_header = true; |
| 416 | } |
| 417 | else |
| 418 | { |
| 419 | VC_CONTAINER_FOURCC_T codec; |
| 420 | unsigned int width, height, fr_num, fr_den, block_size; |
| 421 | |
| 422 | status = rawvideo_parse_uri(ctx, &codec, &width, &height, |
| 423 | &fr_num, &fr_den, &block_size); |
| 424 | if (status != VC_CONTAINER_SUCCESS) |
| 425 | goto error; |
| 426 | ctx->tracks[0]->format->codec = codec; |
| 427 | ctx->tracks[0]->format->type->video.width = width; |
| 428 | ctx->tracks[0]->format->type->video.height = height; |
| 429 | if (fr_num && fr_den) |
| 430 | { |
| 431 | ctx->tracks[0]->format->type->video.frame_rate_num = fr_num; |
| 432 | ctx->tracks[0]->format->type->video.frame_rate_den = fr_den; |
| 433 | } |
| 434 | module->block_size = block_size; |
| 435 | } |
| 436 | |
| 437 | /* |
| 438 | * We now have all the information we really need to start playing the stream |
| 439 | */ |
| 440 | |
| 441 | LOG_INFO(ctx, "rawvideo %4.4s/%ix%i/fps:%i:%i/size:%i" , |
| 442 | (char *)&ctx->tracks[0]->format->codec, |
| 443 | ctx->tracks[0]->format->type->video.width, |
| 444 | ctx->tracks[0]->format->type->video.height, |
| 445 | ctx->tracks[0]->format->type->video.frame_rate_num, |
| 446 | ctx->tracks[0]->format->type->video.frame_rate_den, module->block_size); |
| 447 | ctx->priv->pf_close = rawvideo_reader_close; |
| 448 | ctx->priv->pf_read = rawvideo_reader_read; |
| 449 | ctx->priv->pf_seek = rawvideo_reader_seek; |
| 450 | module->yuv4mpeg2 = yuv4mpeg2; |
| 451 | return VC_CONTAINER_SUCCESS; |
| 452 | |
| 453 | error: |
| 454 | LOG_DEBUG(ctx, "rawvideo: error opening stream (%i)" , status); |
| 455 | rawvideo_reader_close(ctx); |
| 456 | return status; |
| 457 | } |
| 458 | |
| 459 | /******************************************************************************** |
| 460 | Entrypoint function |
| 461 | ********************************************************************************/ |
| 462 | |
| 463 | #if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) |
| 464 | # pragma weak reader_open rawvideo_reader_open |
| 465 | #endif |
| 466 | |