溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時(shí)間:2020-07-30 12:46:25 來源:網(wǎng)絡(luò) 閱讀:12269 作者:Jhuster 欄目:開發(fā)技術(shù)

本文是我的《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)容:


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


當(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é)果如下:


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


不過,這樣打印的信息還不夠,我們希望能通過代碼取到每一個(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 獲取最新的文章和資訊。

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


向AI問一下細(xì)節(jié)

免責(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)容。

AI