網(wǎng)站排名快速提升百度指數(shù)移動(dòng)版
一、引言
通過FFmpeg命令可以獲取到PS文件/PS流的視頻壓縮編碼格式、色彩格式(像素格式)、分辨率、幀率信息:
./ffmpeg -i XXX.ps
本文以H.264為例講述FFmpeg到底是從哪個(gè)地方獲取到這些視頻信息的。??
二、視頻壓縮編碼格式
(一)FFmpeg獲取PS流的視頻壓縮編碼格式的原理
FFmpeg獲取PS文件/PS流的視頻壓縮編碼格式,是從PES packet的有效載荷,即ES流數(shù)據(jù)中獲取的。從《音視頻入門基礎(chǔ):MPEG2-TS專題(18)——PES流簡(jiǎn)介》可以知道,PES packet的PES packet header里面存在一個(gè)stream_id屬性,指定ES流的類型和編號(hào)。但是僅根據(jù)這個(gè)stream_id屬性是無法判斷視頻壓縮編碼格式的:
所以要獲取視頻壓縮編碼格式,得從PES packet的有效載荷中獲取。用Elecard Stream Analyzer工具可以看到,如果PS流的視頻壓縮編碼格式為H.264,那PES packet的有效載荷中攜帶的就是以0x000001或0x00000001作為起始碼的AnnexB格式的H.264碼流(關(guān)于AnnexB可以參考:《音視頻入門基礎(chǔ):H.264專題(3)——EBSP, RBSP和SODB》):
所以這時(shí)候就可以通過解析PES packet的有效載荷,即ES流來獲取視頻壓縮編碼格式。下面講解相關(guān)代碼。
(二)FFmpeg獲取PS流的視頻壓縮編碼格式的實(shí)現(xiàn)
由《音視頻入門基礎(chǔ):MPEG2-PS專題(5)——FFmpeg源碼中,解析PS流中的PES流的實(shí)現(xiàn)》可以知道,FFmpeg源碼中通過mpegps_read_pes_header函數(shù)解析PS流中的一個(gè)PES packet,將其PES packet header里面的信息解析出來。而在調(diào)用完mpegps_read_pes_header函數(shù)后,指針s->pb.buf_ptr會(huì)指向該P(yáng)ES packet的有效載荷,如果PS流的視頻壓縮編碼格式為H.264,那就是指向以0x000001或0x00000001作為起始碼的AnnexB格式的H.264碼流:
然后在mpegps_read_packet函數(shù)中,會(huì)通過av_get_packet函數(shù)將s->pb.buf_ptr指向的H.264碼流數(shù)據(jù)保存到pkt->data指向的緩沖區(qū)中(關(guān)于av_get_packet函數(shù)的用法可以參考:《FFmpeg源碼:append_packet_chunked、av_get_packet、av_append_packet函數(shù)分析》):
static int mpegps_read_packet(AVFormatContext *s,AVPacket *pkt)
{
//...len = mpegps_read_pes_header(s, &dummy_pos, &startcode, &pts, &dts);
//...ret = av_get_packet(s->pb, pkt, len);
//...
}
之后在probe_codec函數(shù)中,會(huì)通過語句:memcpy(pd->buf + pd->buf_size, pkt->data, pkt->size) 將上述H.264碼流數(shù)據(jù)從pkt->data拷貝到pd->buf中:
static int probe_codec(AVFormatContext *s, AVStream *st, const AVPacket *pkt)
{
//...if (sti->request_probe > 0) {//...AVProbeData *const pd = &sti->probe_data;int end;av_log(s, AV_LOG_DEBUG, "probing stream %d pp:%d\n", st->index, sti->probe_packets);--sti->probe_packets;if (pkt) {uint8_t *new_buf = av_realloc(pd->buf, pd->buf_size+pkt->size+AVPROBE_PADDING_SIZE);if (!new_buf) {av_log(s, AV_LOG_WARNING,"Failed to reallocate probe buffer for stream %d\n",st->index);goto no_packet;}pd->buf = new_buf;memcpy(pd->buf + pd->buf_size, pkt->data, pkt->size);pd->buf_size += pkt->size;memset(pd->buf + pd->buf_size, 0, AVPROBE_PADDING_SIZE);}//...}
//...
}
然后probe_codec函數(shù)中會(huì)調(diào)用set_codec_from_probe_data函數(shù),set_codec_from_probe_data函數(shù)的定義如下:
static int set_codec_from_probe_data(AVFormatContext *s, AVStream *st,AVProbeData *pd)
{static const struct {const char *name;enum AVCodecID id;enum AVMediaType type;} fmt_id_type[] = {{ "aac", AV_CODEC_ID_AAC, AVMEDIA_TYPE_AUDIO },{ "ac3", AV_CODEC_ID_AC3, AVMEDIA_TYPE_AUDIO },{ "aptx", AV_CODEC_ID_APTX, AVMEDIA_TYPE_AUDIO },{ "dts", AV_CODEC_ID_DTS, AVMEDIA_TYPE_AUDIO },{ "dvbsub", AV_CODEC_ID_DVB_SUBTITLE, AVMEDIA_TYPE_SUBTITLE },{ "dvbtxt", AV_CODEC_ID_DVB_TELETEXT, AVMEDIA_TYPE_SUBTITLE },{ "eac3", AV_CODEC_ID_EAC3, AVMEDIA_TYPE_AUDIO },{ "h264", AV_CODEC_ID_H264, AVMEDIA_TYPE_VIDEO },{ "hevc", AV_CODEC_ID_HEVC, AVMEDIA_TYPE_VIDEO },{ "loas", AV_CODEC_ID_AAC_LATM, AVMEDIA_TYPE_AUDIO },{ "m4v", AV_CODEC_ID_MPEG4, AVMEDIA_TYPE_VIDEO },{ "mjpeg_2000", AV_CODEC_ID_JPEG2000, AVMEDIA_TYPE_VIDEO },{ "mp3", AV_CODEC_ID_MP3, AVMEDIA_TYPE_AUDIO },{ "mpegvideo", AV_CODEC_ID_MPEG2VIDEO, AVMEDIA_TYPE_VIDEO },{ "truehd", AV_CODEC_ID_TRUEHD, AVMEDIA_TYPE_AUDIO },{ "evc", AV_CODEC_ID_EVC, AVMEDIA_TYPE_VIDEO },{ "vvc", AV_CODEC_ID_VVC, AVMEDIA_TYPE_VIDEO },{ 0 }};int score;const AVInputFormat *fmt = av_probe_input_format3(pd, 1, &score);FFStream *const sti = ffstream(st);if (fmt) {av_log(s, AV_LOG_DEBUG,"Probe with size=%d, packets=%d detected %s with score=%d\n",pd->buf_size, s->max_probe_packets - sti->probe_packets,fmt->name, score);for (int i = 0; fmt_id_type[i].name; i++) {if (!strcmp(fmt->name, fmt_id_type[i].name)) {if (fmt_id_type[i].type != AVMEDIA_TYPE_AUDIO &&st->codecpar->sample_rate)continue;if (sti->request_probe > score &&st->codecpar->codec_id != fmt_id_type[i].id)continue;st->codecpar->codec_id = fmt_id_type[i].id;st->codecpar->codec_type = fmt_id_type[i].type;sti->need_context_update = 1;return score;}}}return 0;
}
可以看到set_codec_from_probe_data函數(shù)中調(diào)用了av_probe_input_format3函數(shù)來推測(cè)pd->buf中的碼流的格式。關(guān)于av_probe_input_format3函數(shù)的用法可以參考:《FFmpeg源碼:av_probe_input_format3函數(shù)和AVInputFormat結(jié)構(gòu)體分析(FFmpeg源碼5.0.3版本)》。對(duì)于H.264裸流,av_probe_input_format3函數(shù)中就是調(diào)用了h264_probe函數(shù)來檢測(cè)這段碼流是否為AnnexB格式的H.264裸流,具體可以參考:《音視頻入門基礎(chǔ):H.264專題(16)——FFmpeg源碼中,判斷某文件是否為H.264裸流文件的實(shí)現(xiàn)》。
判斷出這段碼流為H.264裸流后,set_codec_from_probe_data函數(shù)中會(huì)執(zhí)行語句:st->codecpar->codec_id ? = fmt_id_type[i].id,讓AVCodecParameters的codec_id得到視頻壓縮編碼格式:
static int set_codec_from_probe_data(AVFormatContext *s, AVStream *st,AVProbeData *pd)
{
//...if (fmt) {
//...for (int i = 0; fmt_id_type[i].name; i++) {if (!strcmp(fmt->name, fmt_id_type[i].name)) {if (fmt_id_type[i].type != AVMEDIA_TYPE_AUDIO &&st->codecpar->sample_rate)continue;if (sti->request_probe > score &&st->codecpar->codec_id != fmt_id_type[i].id)continue;st->codecpar->codec_id = fmt_id_type[i].id;st->codecpar->codec_type = fmt_id_type[i].type;sti->need_context_update = 1;return score;}}}return 0;
}
然后在set_codec_from_probe_data函數(shù)外部,通過avcodec_parameters_to_context函數(shù)將AVCodecParameters的codec_id賦值給AVCodecContext的codec_id:
int avcodec_parameters_to_context(AVCodecContext *codec,const AVCodecParameters *par)
{
//...codec->codec_id = par->codec_id;
//...
}
然后在dump_stream_format函數(shù)中,通過avcodec_string函數(shù)中的語句:codec_name = avcodec_get_name(enc->codec_id) 拿到AVCodecContext的codec_id對(duì)應(yīng)的視頻壓縮編碼格式名稱。最后再在dump_stream_format函數(shù)中將視頻壓縮編碼格式打印出來:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...codec_name = avcodec_get_name(enc->codec_id);
//...
}
所以FFmpeg獲取PS文件/PS流的視頻壓縮編碼格式,是從PES packet的有效載荷,即ES流數(shù)據(jù)中獲取的:
三、視頻壓縮編碼格式的profile
如果PS文件/PS流中的視頻壓縮編碼格式為H.264,FFmpeg獲取其視頻壓縮編碼格式的profile,是通過SPS的profile_idc屬性獲取到的,具體可以參考:《音視頻入門基礎(chǔ):H.264專題(17)——FFmpeg源碼中,獲取H.264視頻的profile的實(shí)現(xiàn)》:
四、視頻的色彩格式
如果PS文件/PS流中的視頻壓縮編碼格式為H.264,FFmpeg獲取其視頻的色彩格式,是通過SPS中的屬性chroma_format_idc獲取到的,具體可以參考:《音視頻入門基礎(chǔ):H.264專題(13)——FFmpeg源碼中通過SPS屬性獲取視頻色彩格式的實(shí)現(xiàn)》:
五、視頻分辨率
如果PS文件/PS流中的視頻壓縮編碼格式為H.264,FFmpeg獲取其視頻分辨率,是通過SPS中的屬性獲取的,具體可以參考:《音視頻入門基礎(chǔ):H.264專題(12)——FFmpeg源碼中通過SPS屬性計(jì)算視頻分辨率的實(shí)現(xiàn)》:
六、視頻碼率
由于PS文件/PS流的文件格式(包括TS Header、PES packet header)不包含視頻碼率信息,所以無法通過FFmpeg直接獲取到其視頻碼率。與之對(duì)應(yīng),由于FLV文件的Script Tag中包含視頻碼率信息,所以FFmpeg可以直接打印FLV文件的視頻碼率,具體可以參考:《音視頻入門基礎(chǔ):FLV專題(24)——FFmpeg源碼中,獲取FLV文件視頻信息的實(shí)現(xiàn)》。
七、視頻幀率
如果TS文件/TS流中的視頻壓縮編碼格式為H.264,對(duì)其視頻進(jìn)行編解碼時(shí),FFmpeg源碼內(nèi)部使用的是通過SPS中的屬性計(jì)算得到的視頻幀率(具體可以參考:《音視頻入門基礎(chǔ):H.264專題(15)——FFmpeg源碼中通過SPS屬性獲取視頻幀率的實(shí)現(xiàn)》)。