| 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 | |
| 28 | #include <stdlib.h> |
| 29 | #include <limits.h> |
| 30 | #include <string.h> |
| 31 | #include <stdio.h> |
| 32 | #include "containers/containers.h" |
| 33 | #include "containers/core/containers_common.h" |
| 34 | #include "containers/core/containers_logging.h" |
| 35 | #include "containers/core/containers_utils.h" |
| 36 | #include "containers/core/containers_io.h" |
| 37 | |
| 38 | #define BUFFER_SIZE 256*1024 |
| 39 | #define MAX_TRACKS 16 |
| 40 | #define MAX_SEEKS 16 |
| 41 | |
| 42 | static int container_test_info(VC_CONTAINER_T *ctx, bool b_reader); |
| 43 | static int container_test_parse_cmdline(int argc, char **argv); |
| 44 | |
| 45 | static const char *psz_in = 0; |
| 46 | static long packets_num = 0; |
| 47 | static long track_num = -1; |
| 48 | static int fps = 30; |
| 49 | static int margin = 5; // margin for frame interval, percent |
| 50 | |
| 51 | static struct |
| 52 | { |
| 53 | uint32_t mapping; |
| 54 | uint32_t frames; |
| 55 | uint32_t packets; |
| 56 | uint64_t bytes; |
| 57 | uint32_t frame_size; |
| 58 | int64_t first_dts; |
| 59 | int64_t first_pts; |
| 60 | int64_t last_dts; |
| 61 | int64_t last_pts; |
| 62 | } tracks[MAX_TRACKS]; |
| 63 | |
| 64 | static int32_t verbosity = VC_CONTAINER_LOG_ERROR|VC_CONTAINER_LOG_INFO; |
| 65 | |
| 66 | /*****************************************************************************/ |
| 67 | int main(int argc, char **argv) |
| 68 | { |
| 69 | int retval = 0; |
| 70 | VC_CONTAINER_T *p_ctx = 0; |
| 71 | VC_CONTAINER_STATUS_T status; |
| 72 | unsigned int i; |
| 73 | uint8_t *buffer = malloc(BUFFER_SIZE); |
| 74 | int32_t interval; |
| 75 | int64_t last_packet_pts = -1; |
| 76 | int32_t max_interval = 0, max_interval_after_first = 0; |
| 77 | int fail = 0; |
| 78 | |
| 79 | if(container_test_parse_cmdline(argc, argv)) |
| 80 | goto error_silent; |
| 81 | |
| 82 | LOG_INFO (0, "Require that no frame interval greater than 1/%d seconds (%d%% margin)" , fps, margin); |
| 83 | |
| 84 | /* Set the general verbosity */ |
| 85 | vc_container_log_set_verbosity(0, verbosity); |
| 86 | vc_container_log_set_default_verbosity(verbosity); |
| 87 | |
| 88 | p_ctx = vc_container_open_reader(psz_in, &status, 0, 0); |
| 89 | |
| 90 | if(!p_ctx) |
| 91 | { |
| 92 | LOG_ERROR(0, "error opening file %s (%i)" , psz_in, status); |
| 93 | goto error; |
| 94 | } |
| 95 | |
| 96 | if (verbosity & VC_CONTAINER_LOG_DEBUG) |
| 97 | { |
| 98 | container_test_info(p_ctx, true); |
| 99 | } |
| 100 | LOG_INFO (0, "Search for video track only" ); |
| 101 | |
| 102 | /* Disabling tracks which are not requested and enable packetisation if requested */ |
| 103 | for(i = 0; i < p_ctx->tracks_num; i++) |
| 104 | { |
| 105 | VC_CONTAINER_TRACK_T *track = p_ctx->tracks[i]; |
| 106 | track->is_enabled = (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO); |
| 107 | } |
| 108 | |
| 109 | |
| 110 | LOG_DEBUG(0, "TEST start reading" ); |
| 111 | for(i = 0; !packets_num || (long)i < packets_num; i++) |
| 112 | { |
| 113 | VC_CONTAINER_PACKET_T packet = {0}; |
| 114 | int32_t frame_num = 0; |
| 115 | |
| 116 | status = vc_container_read(p_ctx, &packet, VC_CONTAINER_READ_FLAG_INFO); |
| 117 | if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST info status: %i" , status); break;} |
| 118 | |
| 119 | if(packet.track < MAX_TRACKS) |
| 120 | { |
| 121 | if((packet.flags & VC_CONTAINER_PACKET_FLAG_FRAME_START)) |
| 122 | { |
| 123 | tracks[packet.track].frames++; |
| 124 | tracks[packet.track].frame_size = 0; |
| 125 | } |
| 126 | frame_num = tracks[packet.track].frames; |
| 127 | } |
| 128 | |
| 129 | tracks[packet.track].frame_size += packet.size; |
| 130 | |
| 131 | // LOG_DEBUG(0, "packet info: track %i, size %i/%i/%i, pts %"PRId64", flags %x%s, num %i", |
| 132 | // packet.track, packet.size, packet.frame_size, tracks[packet.track].frame_size, packet.pts, packet.flags, |
| 133 | // (packet.flags & VC_CONTAINER_PACKET_FLAG_KEYFRAME) ? " (keyframe)" : "", |
| 134 | // frame_num-1); |
| 135 | |
| 136 | if (last_packet_pts != -1) |
| 137 | interval = packet.pts - last_packet_pts; |
| 138 | else |
| 139 | interval = 0; // packet.pts; |
| 140 | last_packet_pts = packet.pts; |
| 141 | max_interval = MAX (max_interval, interval); |
| 142 | if (i >= 2) |
| 143 | max_interval_after_first = MAX (max_interval_after_first, interval); |
| 144 | |
| 145 | /* Check if interval (in us) exceeds 1/fps, with percentage margin */ |
| 146 | if (interval * fps > 1e4 * (100+margin)) |
| 147 | { |
| 148 | LOG_INFO (0, "Frame %d, interval %.3f FAILED" , i, interval / 1000.0f); |
| 149 | fail = 1; |
| 150 | } |
| 151 | |
| 152 | LOG_DEBUG (0, "Frame %d, interval %.3f" , i, interval / 1000.0f); |
| 153 | |
| 154 | if(track_num >= 0 && packet.track != (uint32_t)track_num) |
| 155 | { |
| 156 | status = vc_container_read(p_ctx, 0, VC_CONTAINER_READ_FLAG_SKIP); |
| 157 | if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST skip status: %i" , status); break;} |
| 158 | continue; |
| 159 | } |
| 160 | |
| 161 | packet.buffer_size = BUFFER_SIZE; |
| 162 | packet.data = buffer; |
| 163 | packet.size = 0; |
| 164 | status = vc_container_read(p_ctx, &packet, 0); |
| 165 | if(status != VC_CONTAINER_SUCCESS) {LOG_DEBUG(0, "TEST read status: %i" , status); break;} |
| 166 | |
| 167 | // LOG_DEBUG(0, "packet: track %i, size %i, pts %"PRId64", flags %x", packet.track, packet.size, packet.pts, packet.flags); |
| 168 | |
| 169 | if (tracks[packet.track].packets) |
| 170 | { |
| 171 | tracks[packet.track].last_dts = packet.dts; |
| 172 | tracks[packet.track].last_pts = packet.pts; |
| 173 | } else { |
| 174 | tracks[packet.track].first_dts = packet.dts; |
| 175 | tracks[packet.track].first_pts = packet.pts; |
| 176 | } |
| 177 | |
| 178 | if(packet.track < MAX_TRACKS) |
| 179 | { |
| 180 | tracks[packet.track].packets++; |
| 181 | tracks[packet.track].bytes += packet.size; |
| 182 | } |
| 183 | |
| 184 | } |
| 185 | LOG_DEBUG(0, "TEST stop reading" ); |
| 186 | |
| 187 | /* Output stats */ |
| 188 | for(i = 0; i < p_ctx->tracks_num; i++) |
| 189 | { |
| 190 | LOG_INFO(0, "track %u: read %u samples in %u packets for a total of %" PRIu64" bytes" , |
| 191 | i, tracks[i].frames, tracks[i].packets, tracks[i].bytes); |
| 192 | LOG_INFO(0, "Starting at %" PRId64"us (decode at %" PRId64"us), ending at %" PRId64"us (decode at %" PRId64"us)" , |
| 193 | tracks[i].first_pts, tracks[i].first_dts, tracks[i].last_pts, tracks[i].last_dts); |
| 194 | } |
| 195 | |
| 196 | LOG_INFO (0, "---\nMax interval = %.3f ms; max interval (after first) = %.3f ms\n" , (float) max_interval / 1000.0, (float) max_interval_after_first / 1000.0); |
| 197 | |
| 198 | end: |
| 199 | if(p_ctx) vc_container_close(p_ctx); |
| 200 | free(buffer); |
| 201 | |
| 202 | #ifdef _MSC_VER |
| 203 | getchar(); |
| 204 | #endif |
| 205 | |
| 206 | retval = fail; |
| 207 | if (fail) |
| 208 | { |
| 209 | LOG_INFO (0, "TEST FAILED: highest frame interval = %.3f ms" , max_interval / 1000.0f); |
| 210 | } |
| 211 | else |
| 212 | LOG_INFO (0, "TEST PASSED" ); |
| 213 | return retval; |
| 214 | |
| 215 | error: |
| 216 | LOG_ERROR(0, "TEST FAILED TO RUN" ); |
| 217 | error_silent: |
| 218 | retval = -1; |
| 219 | goto end; |
| 220 | } |
| 221 | |
| 222 | static int container_test_parse_cmdline(int argc, char **argv) |
| 223 | { |
| 224 | int i, j, k; |
| 225 | int32_t *p_verbosity; |
| 226 | |
| 227 | /* Parse the command line arguments */ |
| 228 | for(i = 1; i < argc; i++) |
| 229 | { |
| 230 | if(!argv[i]) continue; |
| 231 | |
| 232 | if(argv[i][0] != '-') |
| 233 | { |
| 234 | /* Not an option argument so will be the input URI */ |
| 235 | psz_in = argv[i]; |
| 236 | continue; |
| 237 | } |
| 238 | |
| 239 | /* We are now dealing with command line options */ |
| 240 | switch(argv[i][1]) |
| 241 | { |
| 242 | case 'v': |
| 243 | j = 2; |
| 244 | p_verbosity = &verbosity; |
| 245 | *p_verbosity = VC_CONTAINER_LOG_ERROR|VC_CONTAINER_LOG_INFO; |
| 246 | for(k = 0; k < 2 && argv[i][j+k] == 'v'; k++) |
| 247 | *p_verbosity = (*p_verbosity << 1) | 1 ; |
| 248 | break; |
| 249 | case 'f': |
| 250 | if(i+1 == argc || !argv[i+1]) goto invalid_option; |
| 251 | fps = strtol(argv[++i], 0, 0); |
| 252 | break; |
| 253 | case 'h': goto usage; |
| 254 | default: goto invalid_option; |
| 255 | } |
| 256 | continue; |
| 257 | } |
| 258 | |
| 259 | /* Sanity check that we have at least an input uri */ |
| 260 | if(!psz_in) |
| 261 | { |
| 262 | LOG_ERROR(0, "missing uri argument" ); |
| 263 | goto usage; |
| 264 | } |
| 265 | |
| 266 | return 0; |
| 267 | |
| 268 | invalid_option: |
| 269 | LOG_ERROR(0, "invalid command line option (%s)" , argv[i]); |
| 270 | |
| 271 | usage: |
| 272 | psz_in = strrchr(argv[0], '\\'); if(psz_in) psz_in++; |
| 273 | if(!psz_in) {psz_in = strrchr(argv[0], '/'); if(psz_in) psz_in++;} |
| 274 | if(!psz_in) psz_in = argv[0]; |
| 275 | LOG_INFO(0, "" ); |
| 276 | LOG_INFO(0, "usage: %s [options] uri" , psz_in); |
| 277 | LOG_INFO(0, "options list:" ); |
| 278 | LOG_INFO(0, " -vxx : general verbosity level (replace xx with a number of \'v\')" ); |
| 279 | LOG_INFO(0, " -f : required frame rate/second (frame interval must not exceed 1/f)" ); |
| 280 | LOG_INFO(0, " -h : help" ); |
| 281 | return 1; |
| 282 | } |
| 283 | |
| 284 | static int container_test_info(VC_CONTAINER_T *ctx, bool b_reader) |
| 285 | { |
| 286 | const char *name_type; |
| 287 | unsigned int i; |
| 288 | |
| 289 | LOG_INFO(0, "" ); |
| 290 | if(b_reader) LOG_INFO(0, "----Reader Information----" ); |
| 291 | else LOG_INFO(0, "----Writer Information----" ); |
| 292 | |
| 293 | LOG_INFO(0, "duration: %2.2fs, size: %" PRId64, ctx->duration/1000000.0, ctx->size); |
| 294 | LOG_INFO(0, "capabilities: %x" , ctx->capabilities); |
| 295 | LOG_INFO(0, "" ); |
| 296 | |
| 297 | for(i = 0; i < ctx->tracks_num; i++) |
| 298 | { |
| 299 | VC_CONTAINER_TRACK_T *track = ctx->tracks[i]; |
| 300 | |
| 301 | switch(track->format->es_type) |
| 302 | { |
| 303 | case VC_CONTAINER_ES_TYPE_AUDIO: name_type = "audio" ; break; |
| 304 | case VC_CONTAINER_ES_TYPE_VIDEO: name_type = "video" ; break; |
| 305 | case VC_CONTAINER_ES_TYPE_SUBPICTURE: name_type = "subpicture" ; break; |
| 306 | default: name_type = "unknown" ; break; |
| 307 | } |
| 308 | |
| 309 | if (!strcmp (name_type, "video" )) |
| 310 | { |
| 311 | LOG_INFO(0, "track: %i, type: %s, fourcc: %4.4s" , i, name_type, (char *)&track->format->codec); |
| 312 | LOG_INFO(0, " bitrate: %i, framed: %i, enabled: %i" , track->format->bitrate, |
| 313 | !!(track->format->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED), track->is_enabled); |
| 314 | LOG_INFO(0, " extra data: %i, %p" , track->format->extradata_size, track->format->extradata); |
| 315 | switch(track->format->es_type) |
| 316 | { |
| 317 | case VC_CONTAINER_ES_TYPE_VIDEO: |
| 318 | LOG_INFO(0, " width: %i, height: %i, (%i,%i,%i,%i)" , |
| 319 | track->format->type->video.width, track->format->type->video.height, |
| 320 | track->format->type->video.x_offset, track->format->type->video.y_offset, |
| 321 | track->format->type->video.visible_width, track->format->type->video.visible_height); |
| 322 | LOG_INFO(0, " pixel aspect ratio: %i/%i, frame rate: %i/%i" , |
| 323 | track->format->type->video.par_num, track->format->type->video.par_den, |
| 324 | track->format->type->video.frame_rate_num, track->format->type->video.frame_rate_den); |
| 325 | break; |
| 326 | |
| 327 | default: break; |
| 328 | } |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | for (i = 0; i < ctx->meta_num; ++i) |
| 333 | { |
| 334 | const char *name, *value; |
| 335 | if (i == 0) LOG_INFO(0, "" ); |
| 336 | name = vc_container_metadata_id_to_string(ctx->meta[i]->key); |
| 337 | value = ctx->meta[i]->value; |
| 338 | if(!name) continue; |
| 339 | LOG_INFO(0, "metadata(%i) : %s : %s" , i, name, value); |
| 340 | } |
| 341 | |
| 342 | LOG_INFO(0, "--------------------------" ); |
| 343 | LOG_INFO(0, "" ); |
| 344 | |
| 345 | return 0; |
| 346 | } |
| 347 | |
| 348 | |
| 349 | |