您好,登錄后才能下訂單哦!
本文是我的《FFMPEG Tips》系列的第二篇文章,上篇文章《FFMPEG Tips (1) 如何打印日志》主要分享了如何利用 ffmpeg 庫打印日志,而本文則主要分享一下如何利用 ffmpeg 庫拿到碼流的一些基本信息。
1. 碼流中的哪些信息值得關(guān)注 ?
[ ] 是否包含:音頻、視頻
[ ] 碼流的封裝格式
[ ] 視頻的編碼格式
[ ] 音頻的編碼格式
[ ] 視頻的分辨率、幀率、碼率
[ ] 音頻的采樣率、位寬、通道數(shù)
[ ] 碼流的總時(shí)長
[ ] 其他 Metadata 信息,如作者、日期等
2. 為什么需要拿到這些信息 ?
[ ] 碼流的封裝格式 -> 解封裝
[ ] 音頻、視頻的編碼格式 -> 初始化×××
[ ] 視頻的分辨率、幀率、碼率 -> 視頻的渲染
[ ] 音頻的采樣率、位寬、通道數(shù) -> 初始化音頻播放器
[ ] 碼流的總時(shí)長 -> 展示、拖動(dòng)
[ ] 其他 Metadata 信息 -> 展示
3. 這些關(guān)鍵信息都藏在哪 ?
這些關(guān)鍵的媒體信息,被稱作 “metadata”,常常記錄在整個(gè)碼流的開頭或者結(jié)尾處,例如:wav 格式主要由 wav header 頭來記錄音頻的采樣率、通道數(shù)、位寬等關(guān)鍵信息;mp4 格式,則存放在 moov box 結(jié)構(gòu)中;而 FLV 格式則記錄在 onMetaData 中等等。
我們可以看看 FLV 格式的 onMetaData 記錄的信息包含有哪些內(nèi)容:
當(dāng)然,并不是所有的碼流都能簡單地通過 "metadata" 解析出這些媒體信息,有些碼流還需要通過試讀、解碼等一系列復(fù)雜的操作判斷之后,才能準(zhǔn)確地判斷真實(shí)的媒體信息,在 ffmpeg 中,函數(shù) avformat_find_stream_info 就是干這事的。
4. 如何從 ffmpeg 取出這些信息 ?
(1)首先打開碼流,并解析“metadata”
播放器要完成的第一件事,就是 “打開碼流”,然后再“ 解析碼流信息”,在 ffmpeg 中,這兩步任務(wù)主要通過 `avformat_open_input` 和 `avformat_find_stream_info` 函數(shù)來完成,前者負(fù)責(zé)服務(wù)器的連接和碼流頭部信息的拉取,后者則主要負(fù)責(zé)媒體信息的探測(cè)和分析工作,這兩步的示例代碼如下:
AVFormatContext *ic = avformat_alloc_context(); if (avformat_open_input(&ic, url, NULL, NULL) < 0) { LOGE("could not open source %s", url); return -1; } if (avformat_find_stream_info(ic, NULL) < 0) { LOGE("could not find stream information"); return -1; }
當(dāng)這兩步執(zhí)行成功后,媒體信息就已經(jīng)成功保存在了 ffmpeg 相關(guān)的結(jié)構(gòu)體成員變量中了,下一步我們看看如何拿到這些信息,為我所用。
(2)利用 ffmpeg 系統(tǒng)函數(shù) dump 碼流信息
ffmpeg 提供了一個(gè)函數(shù)直接幫助你打印出解析到的媒體信息,用法如下:
av_dump_format(ic, 0, ic->filename, 0);
例如,打印 “rtmp://live.hkstv.hk.lxdns.com/live/hks” 的結(jié)果如下:
不過,這樣打印的信息還不夠,我們希望能通過代碼取到每一個(gè)關(guān)鍵的媒體信息。因此,下面我們看看如何直接從 AVFormatContext 上下文結(jié)構(gòu)體中提取這些信息。
(3)手動(dòng)從 ffmpeg 的上下文結(jié)構(gòu)體中提取
首先,我們看看 AVFormatContext 變量有哪些跟媒體信息有關(guān)的成員變量:
- struct AVInputFormat *iformat; // 記錄了封裝格式信息 - unsigned int nb_streams; // 記錄了該 URL 中包含有幾路流 - AVStream **streams; // 一個(gè)結(jié)構(gòu)體數(shù)組,每個(gè)對(duì)象記錄了一路流的詳細(xì)信息 - int64_t start_time; // 第一幀的時(shí)間戳 - int64_t duration; // 碼流的總時(shí)長 - int bit_rate; // 碼流的總碼率,bps - AVDictionary *metadata; // 一些文件信息頭,key/value 字符串
由此可見,封裝格式、總時(shí)長和總碼率可以拿到了。另外,由于 AVStream **streams 還詳細(xì)記錄了每一路流的媒體信息,可以進(jìn)一步挖一挖,看看它有哪些成員變量。
我們通過 av_find_best_stream 函數(shù)來取出指向特定指定路數(shù)的 AVStream 對(duì)象,比如視頻流的 AVStream 和 音頻流的 AVStream 對(duì)象分別通過如下方法來取到:
int video_stream_idx = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); AVStream video_stream = ic->streams[video_stream_idx]; int audio_stream_idx = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); AVStream audio_stream = ic->streams[audio_stream_idx];
拿到了 video_stream 和 audio_stream ,我們就可以把 AVStream 結(jié)構(gòu)體中的信息提取出來了,其關(guān)鍵的成員變量如下:
- AVCodecContext *codec; // 記錄了該碼流的編碼信息 - int64_t start_time; // 第一幀的時(shí)間戳 - int64_t duration; // 該碼流的時(shí)長 - int64_t nb_frames; // 該碼流的總幀數(shù) - AVDictionary *metadata; // 一些文件信息頭,key/value 字符串 - AVRational avg_frame_rate; // 平均幀率
到這里,我們拿到了平均的幀率,其中,AVCodecContext 詳細(xì)記錄了每一路流的具體的編碼信息,我們?cè)龠M(jìn)一步挖一挖,看看 AVCodecContext 有哪些成員變量。
- const struct AVCodec *codec; // 編碼的詳細(xì)信息 - enum AVCodecID codec_id; // 編碼類型 - int bit_rate; // 平均碼率 /* video only */ - int width, height; // 圖像的寬高尺寸,碼流中不一定存在該信息,會(huì)由解碼后覆蓋 - enum AVPixelFormat pix_fmt; // 原始圖像的格式,碼流中不一定存在該信息,會(huì)由解碼后覆蓋 /* audio only */ - int sample_rate; // 音頻的采樣率 - int channels; // 音頻的通道數(shù) - enum AVSampleFormat sample_fmt; // 音頻的格式,位寬 - int frame_size; // 每個(gè)音頻幀的 sample 個(gè)數(shù)
原來我們最關(guān)心的編碼類型、圖片的寬高、音頻的參數(shù)藏在這里了!經(jīng)過層層解析后,我們想要的媒體信息,基本上在這些結(jié)構(gòu)體變量中都找到了。
5. 代碼示例
我們可以嘗試手動(dòng)把我們找到的媒體信息都打印出來看看,代碼示例如下(你也可以到我的 Github 查看源代碼: https://github.com/Jhuster/clib):
#include <libavutil/log.h> #define LOGD(format, ...) av_log(NULL, AV_LOG_DEBUG, format, ##__VA_ARGS__); int ff_dump_stream_info(char * url) { AVFormatContext *ic = avformat_alloc_context(); if (avformat_open_input(&ic, url, NULL, NULL) < 0) { LOGD("could not open source %s", url); return -1; } if (avformat_find_stream_info(ic, NULL) < 0) { LOGD("could not find stream information"); return -1; } LOGD("---------- dumping stream info ----------"); LOGD("input format: %s", ic->iformat->name); LOGD("nb_streams: %d", ic->nb_streams); int64_t start_time = ic->start_time / AV_TIME_BASE; LOGD("start_time: %lld", start_time); int64_t duration = ic->duration / AV_TIME_BASE; LOGD("duration: %lld s", duration); int video_stream_idx = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (video_stream_idx >= 0) { AVStream *video_stream = ic->streams[video_stream_idx]; LOGD("video nb_frames: %lld", video_stream->nb_frames); LOGD("video codec_id: %d", video_stream->codec->codec_id); LOGD("video codec_name: %s", avcodec_get_name(video_stream->codec->codec_id)); LOGD("video width x height: %d x %d", video_stream->codec->width, video_stream->codec->height); LOGD("video pix_fmt: %d", video_stream->codec->pix_fmt); LOGD("video bitrate %lld kb/s", (int64_t) video_stream->codec->bit_rate / 1000); LOGD("video avg_frame_rate: %d fps", video_stream->avg_frame_rate.num/video_stream->avg_frame_rate.den); } int audio_stream_idx = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); if (audio_stream_idx >= 0) { AVStream *audio_stream = ic->streams[audio_stream_idx]; LOGD("audio codec_id: %d", audio_stream->codec->codec_id); LOGD("audio codec_name: %s", avcodec_get_name(audio_stream->codec->codec_id)); LOGD("audio sample_rate: %d", audio_stream->codec->sample_rate); LOGD("audio channels: %d", audio_stream->codec->channels); LOGD("audio sample_fmt: %d", audio_stream->codec->sample_fmt); LOGD("audio frame_size: %d", audio_stream->codec->frame_size); LOGD("audio nb_frames: %lld", audio_stream->nb_frames); LOGD("audio bitrate %lld kb/s", (int64_t) audio_stream->codec->bit_rate / 1000); } LOGD("---------- dumping stream info ----------"); avformat_close_input(&ic); }
6. 小結(jié)
關(guān)于如何使用 FFMPEG 如何提取碼流的基本信息就介紹到這兒了,文章中有不清楚的地方歡迎留言或者來信 lujun.hust@gmail.com 交流,關(guān)注我的新浪微博 @盧_俊 或者 微信公眾號(hào) @Jhuster 獲取最新的文章和資訊。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。