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