亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

FFMPEG Tips (2) 如何提取碼流的基本信息

發布時間:2020-07-30 12:46:25 來源:網絡 閱讀:12274 作者:Jhuster 欄目:開發技術

本文是我的《FFMPEG Tips》系列的第二篇文章,上篇文章《FFMPEG Tips (1) 如何打印日志》主要分享了如何利用 ffmpeg 庫打印日志,而本文則主要分享一下如何利用 ffmpeg 庫拿到碼流的一些基本信息。


1.  碼流中的哪些信息值得關注 ?


[ ] 是否包含:音頻、視頻

[ ] 碼流的封裝格式

[ ] 視頻的編碼格式

[ ] 音頻的編碼格式

[ ] 視頻的分辨率、幀率、碼率

[ ] 音頻的采樣率、位寬、通道數

[ ] 碼流的總時長

[ ] 其他 Metadata 信息,如作者、日期等


2. 為什么需要拿到這些信息 ?


[ ] 碼流的封裝格式 -> 解封裝

[ ] 音頻、視頻的編碼格式 ->  初始化×××

[ ] 視頻的分辨率、幀率、碼率 -> 視頻的渲染

[ ] 音頻的采樣率、位寬、通道數 -> 初始化音頻播放器

[ ] 碼流的總時長 -> 展示、拖動

[ ] 其他 Metadata 信息 -> 展示


3. 這些關鍵信息都藏在哪 ?


這些關鍵的媒體信息,被稱作 “metadata”,常常記錄在整個碼流的開頭或者結尾處,例如:wav 格式主要由 wav header 頭來記錄音頻的采樣率、通道數、位寬等關鍵信息;mp4 格式,則存放在 moov box 結構中;而 FLV 格式則記錄在 onMetaData 中等等。


我們可以看看 FLV 格式的 onMetaData 記錄的信息包含有哪些內容:


FFMPEG Tips (2) 如何提取碼流的基本信息


當然,并不是所有的碼流都能簡單地通過 "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” 的結果如下:


FFMPEG Tips (2) 如何提取碼流的基本信息


不過,這樣打印的信息還不夠,我們希望能通過代碼取到每一個關鍵的媒體信息。因此,下面我們看看如何直接從 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 獲取最新的文章和資訊。

FFMPEG Tips (2) 如何提取碼流的基本信息


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

鲁山县| 房山区| 定结县| 遂溪县| 堆龙德庆县| 莱芜市| 定远县| 金寨县| 金秀| 通江县| 开化县| 济源市| 浦江县| 信阳市| 瓦房店市| 留坝县| 商河县| 读书| 惠州市| 大兴区| 武城县| 涟源市| 两当县| 藁城市| 彩票| 海伦市| 苍南县| 仁化县| 宁津县| 含山县| 中阳县| 金溪县| 淮北市| 石家庄市| 定日县| 扶沟县| 永济市| 榆社县| 大渡口区| 桃源县| 乌什县|