FFmpeg多媒体文件格式探测
FFmpeg版本:3.4
在FFmpeg中,每一种文件容器格式都对应一种AVInputFormat 结构,位于源码中libavformat文件夹中。当调用avformat_open_input的时候,FFmpeg会根据媒体封装格式的特点(主要是根据AVInputFormat结构的read_probe函数根据传入的一段buffer来判断传入的文件是否能被解析为该AVInputFormat对应的容器格式),对全部已知的格式进行判断并设置一个分值,取其中最高的分值来关联到一种文件容器格式。
我们通过下面的函数调用堆栈信息来分析avformat_open_input是怎样一步步的关联到具体的文件容器格式的。
static int init_input(AVFormatContext *s, const char *filename, AVDictionary **options) { //... return av_probe_input_buffer2(s->pb, &s->iformat, filename, s, 0, s->format_probesize); }
int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size) { //... //把AVFormatContext和具体的AVInputFormat联系起来了 *fmt = av_probe_input_format2(&pd, 1, &score); //... }
FFmpeg中实现探测的函数是av_probe_input_buffer2和av_probe_input_format3。其核心代码如下:
//遍历所有的Demuxer while ((fmt1 = av_demuxer_iterate(&i))) { if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2")) continue; score = 0; if (fmt1->read_probe) { score = fmt1->read_probe(&lpd); if (score) av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size); if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) { switch (nodat) { case NO_ID3: score = FFMAX(score, 1); break; case ID3_GREATER_PROBE: case ID3_ALMOST_GREATER_PROBE: score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1); break; case ID3_GREATER_MAX_PROBE: score = FFMAX(score, AVPROBE_SCORE_EXTENSION); break; } } } else if (fmt1->extensions) { if (av_match_ext(lpd.filename, fmt1->extensions)) score = AVPROBE_SCORE_EXTENSION; } if (av_match_name(lpd.mime_type, fmt1->mime_type)) { if (AVPROBE_SCORE_MIME > score) { av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME); score = AVPROBE_SCORE_MIME; } } if (score > score_max) { score_max = score; fmt = (AVInputFormat*)fmt1; } else if (score == score_max) fmt = NULL; }
上面代码的原理就是优先使用demuxer的read_probe函数,然后参考文件名的后缀名来关联某个AVInputFormat。
const AVInputFormat *av_demuxer_iterate(void **opaque) { static const uintptr_t size = sizeof(demuxer_list)/sizeof(demuxer_list[0]) - 1; uintptr_t i = (uintptr_t)*opaque; const AVInputFormat *f = NULL; if (i < size) { //遍历demuxer_list中的所有demuxer f = demuxer_list[i]; } else if (outdev_list) { f = indev_list[i - size]; } if (f) *opaque = (void*)(i + 1); return f; }
demuxer_list数组定义在libavformat/demuxer_list.c中,该文件是编译FFMPEG configure的时候生成的
print_enabled_components libavformat/demuxer_list.c AVInputFormat demuxer_list $DEMUXER_LIST
static const AVInputFormat * const demuxer_list[] = { &ff_aa_demuxer, &ff_aac_demuxer, &ff_ac3_demuxer, &ff_acm_demuxer, &ff_act_demuxer, &ff_adf_demuxer, &ff_adp_demuxer, &ff_ads_demuxer, &ff_adx_demuxer, &ff_aea_demuxer, &ff_afc_demuxer, &ff_aiff_demuxer, &ff_aix_demuxer, &ff_amr_demuxer, &ff_amrnb_demuxer, &ff_amrwb_demuxer, &ff_anm_demuxer, &ff_apc_demuxer, &ff_ape_demuxer, &ff_apng_demuxer, &ff_aptx_demuxer, &ff_aptx_hd_demuxer, &ff_aqtitle_demuxer, &ff_asf_demuxer, &ff_asf_o_demuxer, &ff_ass_demuxer, &ff_ast_demuxer, &ff_au_demuxer, &ff_avi_demuxer, &ff_avr_demuxer, &ff_avs_demuxer, &ff_bethsoftvid_demuxer, &ff_bfi_demuxer, &ff_bintext_demuxer, &ff_bink_demuxer, &ff_bit_demuxer, &ff_bmv_demuxer, &ff_bfstm_demuxer, &ff_brstm_demuxer, &ff_boa_demuxer, &ff_c93_demuxer, &ff_caf_demuxer, &ff_cavsvideo_demuxer, &ff_cdg_demuxer, &ff_cdxl_demuxer, &ff_cine_demuxer, &ff_codec2_demuxer, &ff_codec2raw_demuxer, &ff_concat_demuxer, &ff_data_demuxer, &ff_daud_demuxer, &ff_dcstr_demuxer, &ff_dfa_demuxer, &ff_dirac_demuxer, &ff_dnxhd_demuxer, &ff_dsf_demuxer, &ff_dsicin_demuxer, &ff_dss_demuxer, &ff_dts_demuxer, &ff_dtshd_demuxer, &ff_dv_demuxer, &ff_dvbsub_demuxer, &ff_dvbtxt_demuxer, &ff_dxa_demuxer, &ff_ea_demuxer, &ff_ea_cdata_demuxer, &ff_eac3_demuxer, &ff_epaf_demuxer, &ff_ffmetadata_demuxer, &ff_filmstrip_demuxer, &ff_fits_demuxer, &ff_flac_demuxer, &ff_flic_demuxer, &ff_flv_demuxer, &ff_live_flv_demuxer, &ff_fourxm_demuxer, &ff_frm_demuxer, &ff_fsb_demuxer, &ff_g722_demuxer, &ff_g723_1_demuxer, &ff_g726_demuxer, &ff_g726le_demuxer, &ff_g729_demuxer, &ff_gdv_demuxer, &ff_genh_demuxer, &ff_gif_demuxer, &ff_gsm_demuxer, &ff_gxf_demuxer, &ff_h261_demuxer, &ff_h263_demuxer, &ff_h264_demuxer, &ff_hevc_demuxer, &ff_hls_demuxer, &ff_hnm_demuxer, &ff_ico_demuxer, &ff_idcin_demuxer, &ff_idf_demuxer, &ff_iff_demuxer, &ff_ilbc_demuxer, &ff_image2_demuxer, &ff_image2pipe_demuxer, &ff_image2_alias_pix_demuxer, &ff_image2_brender_pix_demuxer, &ff_ingenient_demuxer, &ff_ipmovie_demuxer, &ff_ircam_demuxer, &ff_iss_demuxer, &ff_iv8_demuxer, &ff_ivf_demuxer, &ff_ivr_demuxer, &ff_jacosub_demuxer, &ff_jv_demuxer, &ff_lmlm4_demuxer, &ff_loas_demuxer, &ff_lrc_demuxer, &ff_lvf_demuxer, &ff_lxf_demuxer, &ff_m4v_demuxer, &ff_matroska_demuxer, &ff_mgsts_demuxer, &ff_microdvd_demuxer, &ff_mjpeg_demuxer, &ff_mjpeg_2000_demuxer, &ff_mlp_demuxer, &ff_mlv_demuxer, &ff_mm_demuxer, &ff_mmf_demuxer, &ff_mov_demuxer, &ff_mp3_demuxer, &ff_mpc_demuxer, &ff_mpc8_demuxer, &ff_mpegps_demuxer, &ff_mpegts_demuxer, &ff_mpegtsraw_demuxer, &ff_mpegvideo_demuxer, &ff_mpjpeg_demuxer, &ff_mpl2_demuxer, &ff_mpsub_demuxer, &ff_msf_demuxer, &ff_msnwc_tcp_demuxer, &ff_mtaf_demuxer, &ff_mtv_demuxer, &ff_musx_demuxer, &ff_mv_demuxer, &ff_mvi_demuxer, &ff_mxf_demuxer, &ff_mxg_demuxer, &ff_nc_demuxer, &ff_nistsphere_demuxer, &ff_nsp_demuxer, &ff_nsv_demuxer, &ff_nut_demuxer, &ff_nuv_demuxer, &ff_ogg_demuxer, &ff_oma_demuxer, &ff_paf_demuxer, &ff_pcm_alaw_demuxer, &ff_pcm_mulaw_demuxer, &ff_pcm_f64be_demuxer, &ff_pcm_f64le_demuxer, &ff_pcm_f32be_demuxer, &ff_pcm_f32le_demuxer, &ff_pcm_s32be_demuxer, &ff_pcm_s32le_demuxer, &ff_pcm_s24be_demuxer, &ff_pcm_s24le_demuxer, &ff_pcm_s16be_demuxer, &ff_pcm_s16le_demuxer, &ff_pcm_s8_demuxer, &ff_pcm_u32be_demuxer, &ff_pcm_u32le_demuxer, &ff_pcm_u24be_demuxer, &ff_pcm_u24le_demuxer, &ff_pcm_u16be_demuxer, &ff_pcm_u16le_demuxer, &ff_pcm_u8_demuxer, &ff_pjs_demuxer, &ff_pmp_demuxer, &ff_pva_demuxer, &ff_pvf_demuxer, &ff_qcp_demuxer, &ff_r3d_demuxer, &ff_rawvideo_demuxer, &ff_realtext_demuxer, &ff_redspark_demuxer, &ff_rl2_demuxer, &ff_rm_demuxer, &ff_roq_demuxer, &ff_rpl_demuxer, &ff_rsd_demuxer, &ff_rso_demuxer, &ff_rtp_demuxer, &ff_rtsp_demuxer, &ff_s337m_demuxer, &ff_sami_demuxer, &ff_sap_demuxer, &ff_sbc_demuxer, &ff_sbg_demuxer, &ff_scc_demuxer, &ff_sdp_demuxer, &ff_sdr2_demuxer, &ff_sds_demuxer, &ff_sdx_demuxer, &ff_segafilm_demuxer, &ff_shorten_demuxer, &ff_siff_demuxer, &ff_sln_demuxer, &ff_smacker_demuxer, &ff_smjpeg_demuxer, &ff_smush_demuxer, &ff_sol_demuxer, &ff_sox_demuxer, &ff_spdif_demuxer, &ff_srt_demuxer, &ff_str_demuxer, &ff_stl_demuxer, &ff_subviewer1_demuxer, &ff_subviewer_demuxer, &ff_sup_demuxer, &ff_svag_demuxer, &ff_swf_demuxer, &ff_tak_demuxer, &ff_tedcaptions_demuxer, &ff_thp_demuxer, &ff_threedostr_demuxer, &ff_tiertexseq_demuxer, &ff_tmv_demuxer, &ff_truehd_demuxer, &ff_tta_demuxer, &ff_txd_demuxer, &ff_tty_demuxer, &ff_ty_demuxer, &ff_v210_demuxer, &ff_v210x_demuxer, &ff_vag_demuxer, &ff_vc1_demuxer, &ff_vc1t_demuxer, &ff_vivo_demuxer, &ff_vmd_demuxer, &ff_vobsub_demuxer, &ff_voc_demuxer, &ff_vpk_demuxer, &ff_vplayer_demuxer, &ff_vqf_demuxer, &ff_w64_demuxer, &ff_wav_demuxer, &ff_wc3_demuxer, &ff_webm_dash_manifest_demuxer, &ff_webvtt_demuxer, &ff_wsaud_demuxer, &ff_wsd_demuxer, &ff_wsvqa_demuxer, &ff_wtv_demuxer, &ff_wve_demuxer, &ff_wv_demuxer, &ff_xa_demuxer, &ff_xbin_demuxer, &ff_xmv_demuxer, &ff_xvag_demuxer, &ff_xwma_demuxer, &ff_yop_demuxer, &ff_yuv4mpegpipe_demuxer, &ff_image_bmp_pipe_demuxer, &ff_image_dds_pipe_demuxer, &ff_image_dpx_pipe_demuxer, &ff_image_exr_pipe_demuxer, &ff_image_j2k_pipe_demuxer, &ff_image_jpeg_pipe_demuxer, &ff_image_jpegls_pipe_demuxer, &ff_image_pam_pipe_demuxer, &ff_image_pbm_pipe_demuxer, &ff_image_pcx_pipe_demuxer, &ff_image_pgmyuv_pipe_demuxer, &ff_image_pgm_pipe_demuxer, &ff_image_pictor_pipe_demuxer, &ff_image_png_pipe_demuxer, &ff_image_ppm_pipe_demuxer, &ff_image_psd_pipe_demuxer, &ff_image_qdraw_pipe_demuxer, &ff_image_sgi_pipe_demuxer, &ff_image_svg_pipe_demuxer, &ff_image_sunrast_pipe_demuxer, &ff_image_tiff_pipe_demuxer, &ff_image_webp_pipe_demuxer, &ff_image_xpm_pipe_demuxer, &ff_image_xwd_pipe_demuxer, NULL };
read_probe实现
一般probe有两种情况。
对于在文件开头存在特征码的封装格式,比如avi、rm/rmvb、flv、mkv、asf可以直接使用特征码。
AVI的特征码是RIFF AVI,下面是FFmpeg中的avi探测函数实现:
//libavformat/avidec.c static const char avi_headers[][8] = { { 'R', 'I', 'F', 'F', 'A', 'V', 'I', ' ' }, { 'R', 'I', 'F', 'F', 'A', 'V', 'I', 'X' }, { 'R', 'I', 'F', 'F', 'A', 'V', 'I', 0x19 }, { 'O', 'N', '2', ' ', 'O', 'N', '2', 'f' }, { 'R', 'I', 'F', 'F', 'A', 'M', 'V', ' ' }, { 0 } }; static int avi_probe(AVProbeData *p) { int i; /* check file header */ for (i = 0; avi_headers[i][0]; i++) if (AV_RL32(p->buf ) == AV_RL32(avi_headers[i] ) && AV_RL32(p->buf + 8) == AV_RL32(avi_headers[i] + 4)) return AVPROBE_SCORE_MAX; return 0; }
rm/rmvb文件的特征码是.RMF、.ra,下面是FFmpeg中rm探测函数:
//libavformat/rmdec.c static int rm_probe(AVProbeData *p) { /* check file header */ if ((p->buf[0] == '.' && p->buf[1] == 'R' && p->buf[2] == 'M' && p->buf[3] == 'F' && p->buf[4] == 0 && p->buf[5] == 0) || (p->buf[0] == '.' && p->buf[1] == 'r' && p->buf[2] == 'a' && p->buf[3] == 0xfd)) return AVPROBE_SCORE_MAX; else return 0; }
flv文件的特征码是FLV,下面是FFmpeg中flv探测函数:
//libavformat/flvdec.c static int probe(AVProbeData *p, int live) { const uint8_t *d = p->buf; unsigned offset = AV_RB32(d + 5); if (d[0] == 'F' && d[1] == 'L' && d[2] == 'V' && d[3] < 5 && d[5] == 0 && offset + 100 < p->buf_size && offset > 8) { int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10); if (live == is_live) return AVPROBE_SCORE_MAX; } return 0; } static int flv_probe(AVProbeData *p) { return probe(p, 0); }
mkv的特征码就是EBML_Header_ID和已知的DocType,下面是FFmpeg的mkv探测函数:
//libavformat/matroskadec.c /* top-level master-IDs */ #define EBML_ID_HEADER 0x1A45DFA3 static int matroska_probe(AVProbeData *p) { uint64_t total = 0; int len_mask = 0x80, size = 1, n = 1, i; /* EBML header? */ if (AV_RB32(p->buf) != EBML_ID_HEADER) return 0; /* length of header */ total = p->buf[4]; while (size <= 8 && !(total & len_mask)) { size++; len_mask >>= 1; } if (size > 8) return 0; total &= (len_mask - 1); while (n < size) total = (total << 8) | p->buf[4 + n++]; /* Does the probe data contain the whole header? */ if (p->buf_size < 4 + size + total) return 0; /* The header should contain a known document type. For now, * we don't parse the whole header but simply check for the * availability of that array of characters inside the header. * Not fully fool-proof, but good enough. */ for (i = 0; i < FF_ARRAY_ELEMS(matroska_doctypes); i++) { size_t probelen = strlen(matroska_doctypes[i]); if (total < probelen) continue; for (n = 4 + size; n <= 4 + size + total - probelen; n++) if (!memcmp(p->buf + n, matroska_doctypes[i], probelen)) return AVPROBE_SCORE_MAX; } // probably valid EBML header but no recognized doctype return AVPROBE_SCORE_EXTENSION; }
ASF/WMV文件的特征码就是ASF_HEADER_GUID,下面是FFmpeg中asf探测函数:
//libavformat/asfdec_f.c const ff_asf_guid ff_asf_header = { 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }; static int asf_probe(AVProbeData *pd) { /* check file header */ if (!ff_guidcmp(pd->buf, &ff_asf_header)) return AVPROBE_SCORE_MAX; else return 0; }
MP4文件是由不同BOX构成的,需要根据box type来探测实际类型,FFmpeg中mp4探测函数(也包括mov,m4a,3gp,3g2)如下:
//libavformat/mov.c static int mov_probe(AVProbeData *p) { int64_t offset; uint32_t tag; int score = 0; int moov_offset = -1; /* check file header */ offset = 0; for (;;) { /* ignore invalid offset */ if ((offset + 8) > (unsigned int)p->buf_size) break; tag = AV_RL32(p->buf + offset + 4); switch(tag) { /* check for obvious tags */ case MKTAG('m','o','o','v'): moov_offset = offset + 4; case MKTAG('m','d','a','t'): case MKTAG('p','n','o','t'): /* detect movs with preview pics like ew.mov and april.mov */ case MKTAG('u','d','t','a'): /* Packet Video PVAuthor adds this and a lot of more junk */ case MKTAG('f','t','y','p'): if (AV_RB32(p->buf+offset) < 8 && (AV_RB32(p->buf+offset) != 1 || offset + 12 > (unsigned int)p->buf_size || AV_RB64(p->buf+offset + 8) == 0)) { score = FFMAX(score, AVPROBE_SCORE_EXTENSION); } else if (tag == MKTAG('f','t','y','p') && ( AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ') || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ') )) { score = FFMAX(score, 5); } else { score = AVPROBE_SCORE_MAX; } offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; break; /* those are more common words, so rate then a bit less */ case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */ case MKTAG('w','i','d','e'): case MKTAG('f','r','e','e'): case MKTAG('j','u','n','k'): case MKTAG('p','i','c','t'): score = FFMAX(score, AVPROBE_SCORE_MAX - 5); offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; break; case MKTAG(0x82,0x82,0x7f,0x7d): case MKTAG('s','k','i','p'): case MKTAG('u','u','i','d'): case MKTAG('p','r','f','l'): /* if we only find those cause probedata is too small at least rate them */ score = FFMAX(score, AVPROBE_SCORE_EXTENSION); offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; break; default: offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; } } if(score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) { /* moov atom in the header - we should make sure that this is not a * MOV-packed MPEG-PS */ offset = moov_offset; while(offset < (p->buf_size - 16)){ /* Sufficient space */ /* We found an actual hdlr atom */ if(AV_RL32(p->buf + offset ) == MKTAG('h','d','l','r') && AV_RL32(p->buf + offset + 8) == MKTAG('m','h','l','r') && AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')){ av_log(NULL, AV_LOG_WARNING, "Found media data tag MPEG indicating this is a MOV-packed MPEG-PS.\n"); /* We found a media handler reference atom describing an * MPEG-PS-in-MOV, return a * low score to force expanding the probe window until * mpegps_probe finds what it needs */ return 5; }else /* Keep looking */ offset+=2; } } return score; }
对于像TS这种流媒体格式,通常通过0x47同步字节和pid判断,FFmpeg中ts的探测函数如下:
//libavformat/mpegts.c static int analyze(const uint8_t *buf, int size, int packet_size, int probe) { int stat[TS_MAX_PACKET_SIZE]; int stat_all = 0; int i; int best_score = 0; memset(stat, 0, packet_size * sizeof(*stat)); for (i = 0; i < size - 3; i++) { if (buf[i] == 0x47) { int pid = AV_RB16(buf+1) & 0x1FFF; int asc = buf[i + 3] & 0x30; if (!probe || pid == 0x1FFF || asc) { int x = i % packet_size; stat[x]++; stat_all++; if (stat[x] > best_score) { best_score = stat[x]; } } } } return best_score - FFMAX(stat_all - 10*best_score, 0)/10; } static int mpegts_probe(AVProbeData *p) { const int size = p->buf_size; int maxscore = 0; int sumscore = 0; int i; int check_count = size / TS_FEC_PACKET_SIZE; #define CHECK_COUNT 10 #define CHECK_BLOCK 100 if (!check_count) return 0; for (i = 0; i<check_count; i+=CHECK_BLOCK) { int left = FFMIN(check_count - i, CHECK_BLOCK); int score = analyze(p->buf + TS_PACKET_SIZE *i, TS_PACKET_SIZE *left, TS_PACKET_SIZE , 1); int dvhs_score = analyze(p->buf + TS_DVHS_PACKET_SIZE*i, TS_DVHS_PACKET_SIZE*left, TS_DVHS_PACKET_SIZE, 1); int fec_score = analyze(p->buf + TS_FEC_PACKET_SIZE *i, TS_FEC_PACKET_SIZE *left, TS_FEC_PACKET_SIZE , 1); score = FFMAX3(score, dvhs_score, fec_score); sumscore += score; maxscore = FFMAX(maxscore, score); } sumscore = sumscore * CHECK_COUNT / check_count; maxscore = maxscore * CHECK_COUNT / CHECK_BLOCK; ff_dlog(0, "TS score: %d %d\n", sumscore, maxscore); if (check_count > CHECK_COUNT && sumscore > 6) { return AVPROBE_SCORE_MAX + sumscore - CHECK_COUNT; } else if (check_count >= CHECK_COUNT && sumscore > 6) { return AVPROBE_SCORE_MAX/2 + sumscore - CHECK_COUNT; } else if (check_count >= CHECK_COUNT && maxscore > 6) { return AVPROBE_SCORE_MAX/2 + sumscore - CHECK_COUNT; } else if (sumscore > 6) { return 2; } else { return 0; } }
参考文章:
https://www.cnblogs.com/tocy/p/media_container_9-probe-utility.html