/* * Copyright (c) 2007-2010 Stefano Sabatini * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg is distributed in the hope that it will be useful, * 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 * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @file * simple media prober based on the FFmpeg libraries */ #include "config.h"#include "libavutil/ffversion.h" #include <string.h> #include "libavformat/avformat.h"#include "libavcodec/avcodec.h"#include "libavutil/avassert.h"#include "libavutil/avstring.h"#include "libavutil/bprint.h"#include "libavutil/display.h"#include "libavutil/hash.h"#include "libavutil/mastering_display_metadata.h"#include "libavutil/opt.h"#include "libavutil/pixdesc.h"#include "libavutil/spherical.h"#include "libavutil/stereo3d.h"#include "libavutil/dict.h"#include "libavutil/intreadwrite.h"#include "libavutil/libm.h"#include "libavutil/parseutils.h"#include "libavutil/timecode.h"#include "libavutil/timestamp.h"#include "libavdevice/avdevice.h"#include "libswscale/swscale.h"#include "libswresample/swresample.h"#include "libpostproc/postprocess.h"#include "cmdutils.h" #include "libavutil/thread.h" #if !HAVE_THREADS# ifdef pthread_mutex_lock# undef pthread_mutex_lock# endif# define pthread_mutex_lock(a) do{}while(0)# ifdef pthread_mutex_unlock# undef pthread_mutex_unlock# endif# define pthread_mutex_unlock(a) do{}while(0)#endif typedef struct InputStream { AVStream *st; AVCodecContext *dec_ctx;} InputStream; typedef struct InputFile { AVFormatContext *fmt_ctx; InputStream *streams; int nb_streams;} InputFile; const char program_name[] = "ffprobe";const int program_birth_year = 2007; static int do_bitexact = 0;static int do_count_frames = 0;static int do_count_packets = 0;static int do_read_frames = 0;static int do_read_packets = 0;static int do_show_chapters = 0;static int do_show_error = 0;static int do_show_format = 0;static int do_show_frames = 0;static int do_show_packets = 0;static int do_show_programs = 0;static int do_show_streams = 0;static int do_show_stream_disposition = 0;static int do_show_data = 0;static int do_show_program_version = 0;static int do_show_library_versions = 0;static int do_show_pixel_formats = 0;static int do_show_pixel_format_flags = 0;static int do_show_pixel_format_components = 0;static int do_show_log = 0; static int do_show_chapter_tags = 0;static int do_show_format_tags = 0;static int do_show_frame_tags = 0;static int do_show_program_tags = 0;static int do_show_stream_tags = 0;static int do_show_packet_tags = 0; static int show_value_unit = 0;static int use_value_prefix = 0;static int use_byte_value_binary_prefix = 0;static int use_value_sexagesimal_format = 0;static int show_private_data = 1; static char *print_format;static char *stream_specifier;static char *show_data_hash; typedef struct ReadInterval { int id; ///< identifier int64_t start, end; ///< start, end in second/AV_TIME_BASE units int has_start, has_end; int start_is_offset, end_is_offset; int duration_frames;} ReadInterval; static ReadInterval *read_intervals;static int read_intervals_nb = 0; static int find_stream_info = 1; /* section structure definition */ #define SECTION_MAX_NB_CHILDREN 10 struct section { int id; ///< unique id identifying a section const char *name; #define SECTION_FLAG_IS_WRAPPER 1 ///< the section only contains other sections, but has no data at its own level#define SECTION_FLAG_IS_ARRAY 2 ///< the section contains an array of elements of the same type#define SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys. /// For these sections the element_name field is mandatory. int flags; int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1 const char *element_name; ///< name of the contained element, if provided const char *unique_name; ///< unique section name, in case the name is ambiguous AVDictionary *entries_to_show; int show_all_entries;}; typedef enum { SECTION_ID_NONE = -1, SECTION_ID_CHAPTER, SECTION_ID_CHAPTER_TAGS, SECTION_ID_CHAPTERS, SECTION_ID_ERROR, SECTION_ID_FORMAT, SECTION_ID_FORMAT_TAGS, SECTION_ID_FRAME, SECTION_ID_FRAMES, SECTION_ID_FRAME_TAGS, SECTION_ID_FRAME_SIDE_DATA_LIST, SECTION_ID_FRAME_SIDE_DATA, SECTION_ID_FRAME_LOG, SECTION_ID_FRAME_LOGS, SECTION_ID_LIBRARY_VERSION, SECTION_ID_LIBRARY_VERSIONS, SECTION_ID_PACKET, SECTION_ID_PACKET_TAGS, SECTION_ID_PACKETS, SECTION_ID_PACKETS_AND_FRAMES, SECTION_ID_PACKET_SIDE_DATA_LIST, SECTION_ID_PACKET_SIDE_DATA, SECTION_ID_PIXEL_FORMAT, SECTION_ID_PIXEL_FORMAT_FLAGS, SECTION_ID_PIXEL_FORMAT_COMPONENT, SECTION_ID_PIXEL_FORMAT_COMPONENTS, SECTION_ID_PIXEL_FORMATS, SECTION_ID_PROGRAM_STREAM_DISPOSITION, SECTION_ID_PROGRAM_STREAM_TAGS, SECTION_ID_PROGRAM, SECTION_ID_PROGRAM_STREAMS, SECTION_ID_PROGRAM_STREAM, SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_VERSION, SECTION_ID_PROGRAMS, SECTION_ID_ROOT, SECTION_ID_STREAM, SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAMS, SECTION_ID_STREAM_TAGS, SECTION_ID_STREAM_SIDE_DATA_LIST, SECTION_ID_STREAM_SIDE_DATA, SECTION_ID_SUBTITLE,} SectionID; static struct section sections[] = { [SECTION_ID_CHAPTERS] = { SECTION_ID_CHAPTERS, "chapters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_CHAPTER, -1 } }, [SECTION_ID_CHAPTER] = { SECTION_ID_CHAPTER, "chapter", 0, { SECTION_ID_CHAPTER_TAGS, -1 } }, [SECTION_ID_CHAPTER_TAGS] = { SECTION_ID_CHAPTER_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "chapter_tags" }, [SECTION_ID_ERROR] = { SECTION_ID_ERROR, "error", 0, { -1 } }, [SECTION_ID_FORMAT] = { SECTION_ID_FORMAT, "format", 0, { SECTION_ID_FORMAT_TAGS, -1 } }, [SECTION_ID_FORMAT_TAGS] = { SECTION_ID_FORMAT_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "format_tags" }, [SECTION_ID_FRAMES] = { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, SECTION_ID_SUBTITLE, -1 } }, [SECTION_ID_FRAME] = { SECTION_ID_FRAME, "frame", 0, { SECTION_ID_FRAME_TAGS, SECTION_ID_FRAME_SIDE_DATA_LIST, SECTION_ID_FRAME_LOGS, -1 } }, [SECTION_ID_FRAME_TAGS] = { SECTION_ID_FRAME_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "frame_tags" }, [SECTION_ID_FRAME_SIDE_DATA_LIST] ={ SECTION_ID_FRAME_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "frame_side_data_list" }, [SECTION_ID_FRAME_SIDE_DATA] = { SECTION_ID_FRAME_SIDE_DATA, "side_data", 0, { -1 } }, [SECTION_ID_FRAME_LOGS] = { SECTION_ID_FRAME_LOGS, "logs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_LOG, -1 } }, [SECTION_ID_FRAME_LOG] = { SECTION_ID_FRAME_LOG, "log", 0, { -1 }, }, [SECTION_ID_LIBRARY_VERSIONS] = { SECTION_ID_LIBRARY_VERSIONS, "library_versions", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LIBRARY_VERSION, -1 } }, [SECTION_ID_LIBRARY_VERSION] = { SECTION_ID_LIBRARY_VERSION, "library_version", 0, { -1 } }, [SECTION_ID_PACKETS] = { SECTION_ID_PACKETS, "packets", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, [SECTION_ID_PACKET] = { SECTION_ID_PACKET, "packet", 0, { SECTION_ID_PACKET_TAGS, SECTION_ID_PACKET_SIDE_DATA_LIST, -1 } }, [SECTION_ID_PACKET_TAGS] = { SECTION_ID_PACKET_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "packet_tags" }, [SECTION_ID_PACKET_SIDE_DATA_LIST] ={ SECTION_ID_PACKET_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "packet_side_data_list" }, [SECTION_ID_PACKET_SIDE_DATA] = { SECTION_ID_PACKET_SIDE_DATA, "side_data", 0, { -1 } }, [SECTION_ID_PIXEL_FORMATS] = { SECTION_ID_PIXEL_FORMATS, "pixel_formats", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PIXEL_FORMAT, -1 } }, [SECTION_ID_PIXEL_FORMAT] = { SECTION_ID_PIXEL_FORMAT, "pixel_format", 0, { SECTION_ID_PIXEL_FORMAT_FLAGS, SECTION_ID_PIXEL_FORMAT_COMPONENTS, -1 } }, [SECTION_ID_PIXEL_FORMAT_FLAGS] = { SECTION_ID_PIXEL_FORMAT_FLAGS, "flags", 0, { -1 }, .unique_name = "pixel_format_flags" }, [SECTION_ID_PIXEL_FORMAT_COMPONENTS] = { SECTION_ID_PIXEL_FORMAT_COMPONENTS, "components", SECTION_FLAG_IS_ARRAY, {SECTION_ID_PIXEL_FORMAT_COMPONENT, -1 }, .unique_name = "pixel_format_components" }, [SECTION_ID_PIXEL_FORMAT_COMPONENT] = { SECTION_ID_PIXEL_FORMAT_COMPONENT, "component", 0, { -1 } }, [SECTION_ID_PROGRAM_STREAM_DISPOSITION] = { SECTION_ID_PROGRAM_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "program_stream_disposition" }, [SECTION_ID_PROGRAM_STREAM_TAGS] = { SECTION_ID_PROGRAM_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_stream_tags" }, [SECTION_ID_PROGRAM] = { SECTION_ID_PROGRAM, "program", 0, { SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_STREAMS, -1 } }, [SECTION_ID_PROGRAM_STREAMS] = { SECTION_ID_PROGRAM_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM_STREAM, -1 }, .unique_name = "program_streams" }, [SECTION_ID_PROGRAM_STREAM] = { SECTION_ID_PROGRAM_STREAM, "stream", 0, { SECTION_ID_PROGRAM_STREAM_DISPOSITION, SECTION_ID_PROGRAM_STREAM_TAGS, -1 }, .unique_name = "program_stream" }, [SECTION_ID_PROGRAM_TAGS] = { SECTION_ID_PROGRAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" }, [SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } }, [SECTION_ID_PROGRAMS] = { SECTION_ID_PROGRAMS, "programs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -1 } }, [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER, { SECTION_ID_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, SECTION_ID_STREAMS, SECTION_ID_PACKETS, SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_LIBRARY_VERSIONS, SECTION_ID_PIXEL_FORMATS, -1} }, [SECTION_ID_STREAMS] = { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } }, [SECTION_ID_STREAM] = { SECTION_ID_STREAM, "stream", 0, { SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAM_TAGS, SECTION_ID_STREAM_SIDE_DATA_LIST, -1 } }, [SECTION_ID_STREAM_DISPOSITION] = { SECTION_ID_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_disposition" }, [SECTION_ID_STREAM_TAGS] = { SECTION_ID_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_tags" }, [SECTION_ID_STREAM_SIDE_DATA_LIST] ={ SECTION_ID_STREAM_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "stream_side_data_list" }, [SECTION_ID_STREAM_SIDE_DATA] = { SECTION_ID_STREAM_SIDE_DATA, "side_data", 0, { -1 } }, [SECTION_ID_SUBTITLE] = { SECTION_ID_SUBTITLE, "subtitle", 0, { -1 } },}; static const OptionDef *options; /* FFprobe context */static const char *input_filename;static AVInputFormat *iformat = NULL; static struct AVHashContext *hash; static const struct { double bin_val; double dec_val; const char *bin_str; const char *dec_str;} si_prefixes[] = { { 1.0, 1.0, "", "" }, { 1.024e3, 1e3, "Ki", "K" }, { 1.048576e6, 1e6, "Mi", "M" }, { 1.073741824e9, 1e9, "Gi", "G" }, { 1.099511627776e12, 1e12, "Ti", "T" }, { 1.125899906842624e15, 1e15, "Pi", "P" },}; static const char unit_second_str[] = "s" ;static const char unit_hertz_str[] = "Hz" ;static const char unit_byte_str[] = "byte" ;static const char unit_bit_per_second_str[] = "bit/s"; static int nb_streams;static uint64_t *nb_streams_packets;static uint64_t *nb_streams_frames;static int *selected_streams; #if HAVE_THREADSpthread_mutex_t log_mutex;#endiftypedef struct LogBuffer { char *context_name; int log_level; char *log_message; AVClassCategory category; char *parent_name; AVClassCategory parent_category;}LogBuffer; static LogBuffer *log_buffer;static int log_buffer_size; static void log_callback(void *ptr, int level, const char *fmt, va_list vl){ AVClass* avc = ptr ? *(AVClass **) ptr : NULL; va_list vl2; char line[1024]; static int print_prefix = 1; void *new_log_buffer; va_copy(vl2, vl); av_log_default_callback(ptr, level, fmt, vl); av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix); va_end(vl2); #if HAVE_THREADS pthread_mutex_lock(&log_mutex); new_log_buffer = av_realloc_array(log_buffer, log_buffer_size + 1, sizeof(*log_buffer)); if (new_log_buffer) { char *msg; int i; log_buffer = new_log_buffer; memset(&log_buffer[log_buffer_size], 0, sizeof(log_buffer[log_buffer_size])); log_buffer[log_buffer_size].context_name= avc ? av_strdup(avc->item_name(ptr)) : NULL; if (avc) { if (avc->get_category) log_buffer[log_buffer_size].category = avc->get_category(ptr); else log_buffer[log_buffer_size].category = avc->category; } log_buffer[log_buffer_size].log_level = level; msg = log_buffer[log_buffer_size].log_message = av_strdup(line); for (i=strlen(msg) - 1; i>=0 && msg[i] == '\n'; i--) { msg[i] = 0; } if (avc && avc->parent_log_context_offset) { AVClass** parent = *(AVClass ***) (((uint8_t *) ptr) + avc->parent_log_context_offset); if (parent && *parent) { log_buffer[log_buffer_size].parent_name = av_strdup((*parent)->item_name(parent)); log_buffer[log_buffer_size].parent_category = (*parent)->get_category ? (*parent)->get_category(parent) :(*parent)->category; } } log_buffer_size ++; } pthread_mutex_unlock(&log_mutex);#endif} static void ffprobe_cleanup(int ret){ int i; for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) av_dict_free(&(sections[i].entries_to_show)); #if HAVE_THREADS pthread_mutex_destroy(&log_mutex);#endif} struct unit_value { union { double d; long long int i; } val; const char *unit;}; static char *value_string(char *buf, int buf_size, struct unit_value uv){ double vald; long long int vali; int show_float = 0; if (uv.unit == unit_second_str) { vald = uv.val.d; show_float = 1; } else { vald = vali = uv.val.i; } if (uv.unit == unit_second_str && use_value_sexagesimal_format) { double secs; int hours, mins; secs = vald; mins = (int)secs / 60; secs = secs - mins * 60; hours = mins / 60; mins %= 60; snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs); } else { const char *prefix_string = ""; if (use_value_prefix && vald > 1) { long long int index; if (uv.unit == unit_byte_str && use_byte_value_binary_prefix) { index = (long long int) (log2(vald)) / 10; index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); vald /= si_prefixes[index].bin_val; prefix_string = si_prefixes[index].bin_str; } else { index = (long long int) (log10(vald)) / 3; index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); vald /= si_prefixes[index].dec_val; prefix_string = si_prefixes[index].dec_str; } vali = vald; } if (show_float || (use_value_prefix && vald != (long long int)vald)) snprintf(buf, buf_size, "%f", vald); else snprintf(buf, buf_size, "%lld", vali); av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || show_value_unit ? " " : "", prefix_string, show_value_unit ? uv.unit : ""); } return buf;} /* WRITERS API */ typedef struct WriterContext WriterContext; #define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1#define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2 typedef enum { WRITER_STRING_VALIDATION_FAIL, WRITER_STRING_VALIDATION_REPLACE, WRITER_STRING_VALIDATION_IGNORE, WRITER_STRING_VALIDATION_NB} StringValidation; typedef struct Writer { const AVClass *priv_class; ///< private class of the writer, if any int priv_size; ///< private size for the writer context const char *name; int (*init) (WriterContext *wctx); void (*uninit)(WriterContext *wctx); void (*print_section_header)(WriterContext *wctx); void (*print_section_footer)(WriterContext *wctx); void (*print_integer) (WriterContext *wctx, const char *, long long int); void (*print_rational) (WriterContext *wctx, AVRational *q, char *sep); void (*print_string) (WriterContext *wctx, const char *, const char *); int flags; ///< a combination or WRITER_FLAG_*} Writer; #define SECTION_MAX_NB_LEVELS 10 struct WriterContext { const AVClass *class; ///< class of the writer const Writer *writer; ///< the Writer of which this is an instance char *name; ///< name of this writer instance void *priv; ///< private data for use by the filter const struct section *sections; ///< array containing all sections int nb_sections; ///< number of sections int level; ///< current level, starting from 0 /** number of the item printed in the given section, starting from 0 */ unsigned int nb_item[SECTION_MAX_NB_LEVELS]; /** section per each level */ const struct section *section[SECTION_MAX_NB_LEVELS]; AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section, /// used by various writers unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section unsigned int nb_section_frame; ///< number of the frame section in case we are in "packets_and_frames" section unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames int string_validation; char *string_validation_replacement; unsigned int string_validation_utf8_flags;}; static const char *writer_get_name(void *p){ WriterContext *wctx = p; return wctx->writer->name;} #define OFFSET(x) offsetof(WriterContext, x) static const AVOption writer_options[] = { { "string_validation", "set string validation mode", OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, { "sv", "set string validation mode", OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, { "ignore", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_IGNORE}, .unit = "sv" }, { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_REPLACE}, .unit = "sv" }, { "fail", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_FAIL}, .unit = "sv" }, { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}}, { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str="\xEF\xBF\xBD"}}, { NULL }}; static void *writer_child_next(void *obj, void *prev){ WriterContext *ctx = obj; if (!prev && ctx->writer && ctx->writer->priv_class && ctx->priv) return ctx->priv; return NULL;Lines 1–500 of 3692. Load more
/* * Copyright (c) 2007-2010 Stefano Sabatini * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg is distributed in the hope that it will be useful, * 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 * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @file * simple media prober based on the FFmpeg libraries */ #include "config.h" #include "libavutil/ffversion.h" #include <string.h> #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" #include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/bprint.h" #include "libavutil/display.h" #include "libavutil/hash.h" #include "libavutil/mastering_display_metadata.h" #include "libavutil/opt.h" #include "libavutil/pixdesc.h" #include "libavutil/spherical.h" #include "libavutil/stereo3d.h" #include "libavutil/dict.h" #include "libavutil/intreadwrite.h" #include "libavutil/libm.h" #include "libavutil/parseutils.h" #include "libavutil/timecode.h" #include "libavutil/timestamp.h" #include "libavdevice/avdevice.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" #include "libpostproc/postprocess.h" #include "cmdutils.h" #include "libavutil/thread.h" #if !HAVE_THREADS # ifdef pthread_mutex_lock # undef pthread_mutex_lock # endif # define pthread_mutex_lock(a) do{}while(0) # ifdef pthread_mutex_unlock # undef pthread_mutex_unlock # endif # define pthread_mutex_unlock(a) do{}while(0) #endif typedef struct InputStream { AVStream *st; AVCodecContext *dec_ctx; } InputStream; typedef struct InputFile { AVFormatContext *fmt_ctx; InputStream *streams; int nb_streams; } InputFile; const char program_name[] = "ffprobe"; const int program_birth_year = 2007; static int do_bitexact = 0; static int do_count_frames = 0; static int do_count_packets = 0; static int do_read_frames = 0; static int do_read_packets = 0; static int do_show_chapters = 0; static int do_show_error = 0; static int do_show_format = 0; static int do_show_frames = 0; static int do_show_packets = 0; static int do_show_programs = 0; static int do_show_streams = 0; static int do_show_stream_disposition = 0; static int do_show_data = 0; static int do_show_program_version = 0; static int do_show_library_versions = 0; static int do_show_pixel_formats = 0; static int do_show_pixel_format_flags = 0; static int do_show_pixel_format_components = 0; static int do_show_log = 0; static int do_show_chapter_tags = 0; static int do_show_format_tags = 0; static int do_show_frame_tags = 0; static int do_show_program_tags = 0; static int do_show_stream_tags = 0; static int do_show_packet_tags = 0; static int show_value_unit = 0; static int use_value_prefix = 0; static int use_byte_value_binary_prefix = 0; static int use_value_sexagesimal_format = 0; static int show_private_data = 1; static char *print_format; static char *stream_specifier; static char *show_data_hash; typedef struct ReadInterval { int id; ///< identifier int64_t start, end; ///< start, end in second/AV_TIME_BASE units int has_start, has_end; int start_is_offset, end_is_offset; int duration_frames; } ReadInterval; static ReadInterval *read_intervals; static int read_intervals_nb = 0; static int find_stream_info = 1; /* section structure definition */ #define SECTION_MAX_NB_CHILDREN 10 struct section { int id; ///< unique id identifying a section const char *name; #define SECTION_FLAG_IS_WRAPPER 1 ///< the section only contains other sections, but has no data at its own level #define SECTION_FLAG_IS_ARRAY 2 ///< the section contains an array of elements of the same type #define SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys. /// For these sections the element_name field is mandatory. int flags; int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1 const char *element_name; ///< name of the contained element, if provided const char *unique_name; ///< unique section name, in case the name is ambiguous AVDictionary *entries_to_show; int show_all_entries; }; typedef enum { SECTION_ID_NONE = -1, SECTION_ID_CHAPTER, SECTION_ID_CHAPTER_TAGS, SECTION_ID_CHAPTERS, SECTION_ID_ERROR, SECTION_ID_FORMAT, SECTION_ID_FORMAT_TAGS, SECTION_ID_FRAME, SECTION_ID_FRAMES, SECTION_ID_FRAME_TAGS, SECTION_ID_FRAME_SIDE_DATA_LIST, SECTION_ID_FRAME_SIDE_DATA, SECTION_ID_FRAME_LOG, SECTION_ID_FRAME_LOGS, SECTION_ID_LIBRARY_VERSION, SECTION_ID_LIBRARY_VERSIONS, SECTION_ID_PACKET, SECTION_ID_PACKET_TAGS, SECTION_ID_PACKETS, SECTION_ID_PACKETS_AND_FRAMES, SECTION_ID_PACKET_SIDE_DATA_LIST, SECTION_ID_PACKET_SIDE_DATA, SECTION_ID_PIXEL_FORMAT, SECTION_ID_PIXEL_FORMAT_FLAGS, SECTION_ID_PIXEL_FORMAT_COMPONENT, SECTION_ID_PIXEL_FORMAT_COMPONENTS, SECTION_ID_PIXEL_FORMATS, SECTION_ID_PROGRAM_STREAM_DISPOSITION, SECTION_ID_PROGRAM_STREAM_TAGS, SECTION_ID_PROGRAM, SECTION_ID_PROGRAM_STREAMS, SECTION_ID_PROGRAM_STREAM, SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_VERSION, SECTION_ID_PROGRAMS, SECTION_ID_ROOT, SECTION_ID_STREAM, SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAMS, SECTION_ID_STREAM_TAGS, SECTION_ID_STREAM_SIDE_DATA_LIST, SECTION_ID_STREAM_SIDE_DATA, SECTION_ID_SUBTITLE, } SectionID; static struct section sections[] = { [SECTION_ID_CHAPTERS] = { SECTION_ID_CHAPTERS, "chapters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_CHAPTER, -1 } }, [SECTION_ID_CHAPTER] = { SECTION_ID_CHAPTER, "chapter", 0, { SECTION_ID_CHAPTER_TAGS, -1 } }, [SECTION_ID_CHAPTER_TAGS] = { SECTION_ID_CHAPTER_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "chapter_tags" }, [SECTION_ID_ERROR] = { SECTION_ID_ERROR, "error", 0, { -1 } }, [SECTION_ID_FORMAT] = { SECTION_ID_FORMAT, "format", 0, { SECTION_ID_FORMAT_TAGS, -1 } }, [SECTION_ID_FORMAT_TAGS] = { SECTION_ID_FORMAT_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "format_tags" }, [SECTION_ID_FRAMES] = { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, SECTION_ID_SUBTITLE, -1 } }, [SECTION_ID_FRAME] = { SECTION_ID_FRAME, "frame", 0, { SECTION_ID_FRAME_TAGS, SECTION_ID_FRAME_SIDE_DATA_LIST, SECTION_ID_FRAME_LOGS, -1 } }, [SECTION_ID_FRAME_TAGS] = { SECTION_ID_FRAME_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "frame_tags" }, [SECTION_ID_FRAME_SIDE_DATA_LIST] ={ SECTION_ID_FRAME_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "frame_side_data_list" }, [SECTION_ID_FRAME_SIDE_DATA] = { SECTION_ID_FRAME_SIDE_DATA, "side_data", 0, { -1 } }, [SECTION_ID_FRAME_LOGS] = { SECTION_ID_FRAME_LOGS, "logs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_LOG, -1 } }, [SECTION_ID_FRAME_LOG] = { SECTION_ID_FRAME_LOG, "log", 0, { -1 }, }, [SECTION_ID_LIBRARY_VERSIONS] = { SECTION_ID_LIBRARY_VERSIONS, "library_versions", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LIBRARY_VERSION, -1 } }, [SECTION_ID_LIBRARY_VERSION] = { SECTION_ID_LIBRARY_VERSION, "library_version", 0, { -1 } }, [SECTION_ID_PACKETS] = { SECTION_ID_PACKETS, "packets", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, [SECTION_ID_PACKET] = { SECTION_ID_PACKET, "packet", 0, { SECTION_ID_PACKET_TAGS, SECTION_ID_PACKET_SIDE_DATA_LIST, -1 } }, [SECTION_ID_PACKET_TAGS] = { SECTION_ID_PACKET_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "packet_tags" }, [SECTION_ID_PACKET_SIDE_DATA_LIST] ={ SECTION_ID_PACKET_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "packet_side_data_list" }, [SECTION_ID_PACKET_SIDE_DATA] = { SECTION_ID_PACKET_SIDE_DATA, "side_data", 0, { -1 } }, [SECTION_ID_PIXEL_FORMATS] = { SECTION_ID_PIXEL_FORMATS, "pixel_formats", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PIXEL_FORMAT, -1 } }, [SECTION_ID_PIXEL_FORMAT] = { SECTION_ID_PIXEL_FORMAT, "pixel_format", 0, { SECTION_ID_PIXEL_FORMAT_FLAGS, SECTION_ID_PIXEL_FORMAT_COMPONENTS, -1 } }, [SECTION_ID_PIXEL_FORMAT_FLAGS] = { SECTION_ID_PIXEL_FORMAT_FLAGS, "flags", 0, { -1 }, .unique_name = "pixel_format_flags" }, [SECTION_ID_PIXEL_FORMAT_COMPONENTS] = { SECTION_ID_PIXEL_FORMAT_COMPONENTS, "components", SECTION_FLAG_IS_ARRAY, {SECTION_ID_PIXEL_FORMAT_COMPONENT, -1 }, .unique_name = "pixel_format_components" }, [SECTION_ID_PIXEL_FORMAT_COMPONENT] = { SECTION_ID_PIXEL_FORMAT_COMPONENT, "component", 0, { -1 } }, [SECTION_ID_PROGRAM_STREAM_DISPOSITION] = { SECTION_ID_PROGRAM_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "program_stream_disposition" }, [SECTION_ID_PROGRAM_STREAM_TAGS] = { SECTION_ID_PROGRAM_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_stream_tags" }, [SECTION_ID_PROGRAM] = { SECTION_ID_PROGRAM, "program", 0, { SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_STREAMS, -1 } }, [SECTION_ID_PROGRAM_STREAMS] = { SECTION_ID_PROGRAM_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM_STREAM, -1 }, .unique_name = "program_streams" }, [SECTION_ID_PROGRAM_STREAM] = { SECTION_ID_PROGRAM_STREAM, "stream", 0, { SECTION_ID_PROGRAM_STREAM_DISPOSITION, SECTION_ID_PROGRAM_STREAM_TAGS, -1 }, .unique_name = "program_stream" }, [SECTION_ID_PROGRAM_TAGS] = { SECTION_ID_PROGRAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" }, [SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } }, [SECTION_ID_PROGRAMS] = { SECTION_ID_PROGRAMS, "programs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -1 } }, [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER, { SECTION_ID_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, SECTION_ID_STREAMS, SECTION_ID_PACKETS, SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_LIBRARY_VERSIONS, SECTION_ID_PIXEL_FORMATS, -1} }, [SECTION_ID_STREAMS] = { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } }, [SECTION_ID_STREAM] = { SECTION_ID_STREAM, "stream", 0, { SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAM_TAGS, SECTION_ID_STREAM_SIDE_DATA_LIST, -1 } }, [SECTION_ID_STREAM_DISPOSITION] = { SECTION_ID_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_disposition" }, [SECTION_ID_STREAM_TAGS] = { SECTION_ID_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_tags" }, [SECTION_ID_STREAM_SIDE_DATA_LIST] ={ SECTION_ID_STREAM_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "stream_side_data_list" }, [SECTION_ID_STREAM_SIDE_DATA] = { SECTION_ID_STREAM_SIDE_DATA, "side_data", 0, { -1 } }, [SECTION_ID_SUBTITLE] = { SECTION_ID_SUBTITLE, "subtitle", 0, { -1 } }, }; static const OptionDef *options; /* FFprobe context */ static const char *input_filename; static AVInputFormat *iformat = NULL; static struct AVHashContext *hash; static const struct { double bin_val; double dec_val; const char *bin_str; const char *dec_str; } si_prefixes[] = { { 1.0, 1.0, "", "" }, { 1.024e3, 1e3, "Ki", "K" }, { 1.048576e6, 1e6, "Mi", "M" }, { 1.073741824e9, 1e9, "Gi", "G" }, { 1.099511627776e12, 1e12, "Ti", "T" }, { 1.125899906842624e15, 1e15, "Pi", "P" }, }; static const char unit_second_str[] = "s" ; static const char unit_hertz_str[] = "Hz" ; static const char unit_byte_str[] = "byte" ; static const char unit_bit_per_second_str[] = "bit/s"; static int nb_streams; static uint64_t *nb_streams_packets; static uint64_t *nb_streams_frames; static int *selected_streams; #if HAVE_THREADS pthread_mutex_t log_mutex; #endif typedef struct LogBuffer { char *context_name; int log_level; char *log_message; AVClassCategory category; char *parent_name; AVClassCategory parent_category; }LogBuffer; static LogBuffer *log_buffer; static int log_buffer_size; static void log_callback(void *ptr, int level, const char *fmt, va_list vl) { AVClass* avc = ptr ? *(AVClass **) ptr : NULL; va_list vl2; char line[1024]; static int print_prefix = 1; void *new_log_buffer; va_copy(vl2, vl); av_log_default_callback(ptr, level, fmt, vl); av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix); va_end(vl2); #if HAVE_THREADS pthread_mutex_lock(&log_mutex); new_log_buffer = av_realloc_array(log_buffer, log_buffer_size + 1, sizeof(*log_buffer)); if (new_log_buffer) { char *msg; int i; log_buffer = new_log_buffer; memset(&log_buffer[log_buffer_size], 0, sizeof(log_buffer[log_buffer_size])); log_buffer[log_buffer_size].context_name= avc ? av_strdup(avc->item_name(ptr)) : NULL; if (avc) { if (avc->get_category) log_buffer[log_buffer_size].category = avc->get_category(ptr); else log_buffer[log_buffer_size].category = avc->category; } log_buffer[log_buffer_size].log_level = level; msg = log_buffer[log_buffer_size].log_message = av_strdup(line); for (i=strlen(msg) - 1; i>=0 && msg[i] == '\n'; i--) { msg[i] = 0; } if (avc && avc->parent_log_context_offset) { AVClass** parent = *(AVClass ***) (((uint8_t *) ptr) + avc->parent_log_context_offset); if (parent && *parent) { log_buffer[log_buffer_size].parent_name = av_strdup((*parent)->item_name(parent)); log_buffer[log_buffer_size].parent_category = (*parent)->get_category ? (*parent)->get_category(parent) :(*parent)->category; } } log_buffer_size ++; } pthread_mutex_unlock(&log_mutex); #endif } static void ffprobe_cleanup(int ret) { int i; for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) av_dict_free(&(sections[i].entries_to_show)); #if HAVE_THREADS pthread_mutex_destroy(&log_mutex); #endif } struct unit_value { union { double d; long long int i; } val; const char *unit; }; static char *value_string(char *buf, int buf_size, struct unit_value uv) { double vald; long long int vali; int show_float = 0; if (uv.unit == unit_second_str) { vald = uv.val.d; show_float = 1; } else { vald = vali = uv.val.i; } if (uv.unit == unit_second_str && use_value_sexagesimal_format) { double secs; int hours, mins; secs = vald; mins = (int)secs / 60; secs = secs - mins * 60; hours = mins / 60; mins %= 60; snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs); } else { const char *prefix_string = ""; if (use_value_prefix && vald > 1) { long long int index; if (uv.unit == unit_byte_str && use_byte_value_binary_prefix) { index = (long long int) (log2(vald)) / 10; index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); vald /= si_prefixes[index].bin_val; prefix_string = si_prefixes[index].bin_str; } else { index = (long long int) (log10(vald)) / 3; index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); vald /= si_prefixes[index].dec_val; prefix_string = si_prefixes[index].dec_str; } vali = vald; } if (show_float || (use_value_prefix && vald != (long long int)vald)) snprintf(buf, buf_size, "%f", vald); else snprintf(buf, buf_size, "%lld", vali); av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || show_value_unit ? " " : "", prefix_string, show_value_unit ? uv.unit : ""); } return buf; } /* WRITERS API */ typedef struct WriterContext WriterContext; #define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1 #define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2 typedef enum { WRITER_STRING_VALIDATION_FAIL, WRITER_STRING_VALIDATION_REPLACE, WRITER_STRING_VALIDATION_IGNORE, WRITER_STRING_VALIDATION_NB } StringValidation; typedef struct Writer { const AVClass *priv_class; ///< private class of the writer, if any int priv_size; ///< private size for the writer context const char *name; int (*init) (WriterContext *wctx); void (*uninit)(WriterContext *wctx); void (*print_section_header)(WriterContext *wctx); void (*print_section_footer)(WriterContext *wctx); void (*print_integer) (WriterContext *wctx, const char *, long long int); void (*print_rational) (WriterContext *wctx, AVRational *q, char *sep); void (*print_string) (WriterContext *wctx, const char *, const char *); int flags; ///< a combination or WRITER_FLAG_* } Writer; #define SECTION_MAX_NB_LEVELS 10 struct WriterContext { const AVClass *class; ///< class of the writer const Writer *writer; ///< the Writer of which this is an instance char *name; ///< name of this writer instance void *priv; ///< private data for use by the filter const struct section *sections; ///< array containing all sections int nb_sections; ///< number of sections int level; ///< current level, starting from 0 /** number of the item printed in the given section, starting from 0 */ unsigned int nb_item[SECTION_MAX_NB_LEVELS]; /** section per each level */ const struct section *section[SECTION_MAX_NB_LEVELS]; AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section, /// used by various writers unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section unsigned int nb_section_frame; ///< number of the frame section in case we are in "packets_and_frames" section unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames int string_validation; char *string_validation_replacement; unsigned int string_validation_utf8_flags; }; static const char *writer_get_name(void *p) { WriterContext *wctx = p; return wctx->writer->name; } #define OFFSET(x) offsetof(WriterContext, x) static const AVOption writer_options[] = { { "string_validation", "set string validation mode", OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, { "sv", "set string validation mode", OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, { "ignore", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_IGNORE}, .unit = "sv" }, { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_REPLACE}, .unit = "sv" }, { "fail", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_FAIL}, .unit = "sv" }, { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}}, { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str="\xEF\xBF\xBD"}}, { NULL } }; static void *writer_child_next(void *obj, void *prev) { WriterContext *ctx = obj; if (!prev && ctx->writer && ctx->writer->priv_class && ctx->priv) return ctx->priv; return NULL;