學習 FFmpeg 之視頻讀取

學習 FFmpeg 循序漸進的資料很少负蚊,前一段還在交代背景知識滩报,下一段就推你去看源代碼去了扇苞。
An ffmpeg and SDL Tutorial 算是我找到的比較好入門教程了抠藕,當然我并不關心 SDL 是啥,我只想知道 FFmpeg 是怎么使用的坯认。

嘗試編譯了以下 tutorial 1 的代碼,編譯器提示基本每一個用到的函數(shù)和字段都被標記為 depressed氓涣。當然編譯是可以通過的牛哺,也能夠如期的運行起來。不過劳吠,既然 FFmpeg 已經(jīng)發(fā)布到了 3.x.x 版本引润,也不妨升級使用最新的接口。

tutorial 1 代碼邏輯一致痒玩,讀取視頻 5 幀保存成 PPM 格式圖片淳附。
保存成 PPM 主要是因為這種格式簡單,查看確是大大不方便的凰荚,可以使用 ImageMagick 命令轉換成方便查看的格式:

convert in.ppm out.jpg

tutorial 1 不同的是燃观,增加了 seek 函數(shù),跳到視頻的某大間點的關鍵幀附近便瑟。編譯需要 C++11 支持缆毁,還依賴了 boost::format,如果想去掉 boost::format 的依賴還是很容易的到涂。

#include <iostream>
#include <fstream>
#include <functional>

#include <boost/format.hpp>

#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
}
#endif

class ScopeExit final {
public:
    explicit ScopeExit(std::function<void()> func)
        : _func(func) {
    }

    void clear() {
        _func = nullptr;
    }

    ~ScopeExit() {
        if (_func) {
            _func();
        }
    }

private:
    std::function<void()> _func = nullptr;
};

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
    std::string filename = (boost::format("frame%d.ppm") % iFrame).str();
    std::ofstream ofs(filename, std::ofstream::out);
    if (!ofs) {
        return;
    }

    ofs << boost::format("P6\n%d %d\n255\n") % width % height;

    // Write pixel data
    for(int y = 0; y < height; y++) {
        ofs.write((const char*)(pFrame->data[0] + y * pFrame->linesize[0]), width * 3);
    }
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        std::cerr << "Please provide a movie file" << std::endl;
        return -1;
    }
    // Register all formats and codecs
    av_register_all();

    // Open video file
    AVFormatContext* pFormatCtx = NULL;
    if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0) {
        return -1; // Couldn't open file
    }
    ScopeExit format_ctx_closer([&pFormatCtx] {
        avformat_close_input(&pFormatCtx);
    });

    // Retrieve stream information
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        return -1; // Couldn't find stream information
    }

    // Dump information about file onto standard error
    av_dump_format(pFormatCtx, 0, argv[1], 0);

    // Find the first video stream
    int videoStream = -1;
    for (size_t i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1) {
        return -1; // Didn't find a video stream
    }
    auto time_base = pFormatCtx->streams[videoStream]->time_base;
    std::cout << boost::format("time_base num:%1% den::%2%") % time_base.num % time_base.den
              << std::endl;

    // Get a pointer to the codec context for the video stream
    const auto* codecpar = pFormatCtx->streams[videoStream]->codecpar;

    // Find the decoder for the video stream
    AVCodec* pCodec = avcodec_find_decoder(codecpar->codec_id);
    if (pCodec == NULL) {
        std::cerr << "Unsupported codec!" << std::endl;
        return -1; // Codec not found
    }

    // Copy context
    AVCodecContext* pCodecCtx = avcodec_alloc_context3(pCodec);
    ScopeExit avcodec_closer([&pCodecCtx] {
        if (pCodecCtx) {avcodec_close(pCodecCtx); }
    });
    if (0 != avcodec_parameters_to_context(pCodecCtx, codecpar)) {
        std::cerr << "avcodec_parameters_to_context error" << std::endl;
        return -1;
    }

    // Open codec
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        return -1; // Could not open codec
    }

    // Allocate video frame
    AVFrame* pFrame = av_frame_alloc();
    if (pFrame == NULL) {
        return -1;
    }
    ScopeExit frame_deleter([&pFrame] {
        if (pFrame) {av_frame_free(&pFrame); }
    });

    // Allocate an AVFrame structure
    AVFrame* pFrameRGB = av_frame_alloc();
    if (pFrameRGB == NULL) {
        return -1;
    }
    ScopeExit frame_rgb_deleter([&pFrameRGB] {
        if (pFrameRGB) {av_frame_free(&pFrameRGB); }
    });
    pFrameRGB->width = pCodecCtx->width;
    pFrameRGB->height = pCodecCtx->height;
    pFrameRGB->format = AV_PIX_FMT_RGB24;

    auto numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24,
                                             pFrameRGB->width,
                                             pFrameRGB->height,
                                             1);
    uint8_t* buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
    ScopeExit buffer_deleter([&buffer] {
        if (buffer) {av_freep(&buffer); }
    });
    av_image_fill_arrays(pFrameRGB->data,
                         pFrameRGB->linesize,
                         buffer,
                         AV_PIX_FMT_RGB24,
                         pFrameRGB->width,
                         pFrameRGB->height,
                         1);

    // initialize SWS context for software scaling
    struct SwsContext* sws_ctx = sws_getContext(pCodecCtx->width,
                                                pCodecCtx->height,
                                                pCodecCtx->pix_fmt,
                                                pCodecCtx->width,
                                                pCodecCtx->height,
                                                AV_PIX_FMT_RGB24,
                                                SWS_BILINEAR,
                                                NULL,
                                                NULL,
                                                NULL);

    // seek to 21 second, actually before 21 second
    av_seek_frame(pFormatCtx, videoStream , 21 * time_base.den, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY);

    // Read frames and save first five frames to disk
    int i = 0;
    AVPacket packet;
    while (av_read_frame(pFormatCtx, &packet) >= 0 && i <= 5) {
        ScopeExit unrefer([&packet] {
            av_packet_unref(&packet);
        });
        if (packet.stream_index != videoStream) {
            continue;
        }
        int ret_packet = avcodec_send_packet(pCodecCtx, &packet);
        if (ret_packet < 0) {
            break;
        }
        int ret_frame = avcodec_receive_frame(pCodecCtx, pFrame);
        if (ret_frame >= 0) {
            // just save key frame
            // if (!pFrame->key_frame) {
            //     continue;
            // }
            auto pts = av_frame_get_best_effort_timestamp(pFrame);
            if (pts == AV_NOPTS_VALUE) {
                pts = 0;
            }
            std::cout << boost::format("pts:%1% time:%2%") % pts % (pts * av_q2d(time_base))
                      << std::endl;
            // Convert the image from its native format to RGB
            sws_scale(sws_ctx,
                      (uint8_t const * const *)pFrame->data,
                      pFrame->linesize,
                      0,
                      pCodecCtx->height,
                      pFrameRGB->data,
                      pFrameRGB->linesize);

            // Save the frame to disk
            if (++i <= 5) {
                SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
            }
        } else if (ret_frame != AVERROR(EAGAIN) && ret_frame != AVERROR_EOF) {
            break;
        }
    }

    return 0;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末脊框,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子践啄,更是在濱河造成了極大的恐慌浇雹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屿讽,死亡現(xiàn)場離奇詭異昭灵,居然都是意外死亡,警方通過查閱死者的電腦和手機伐谈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門烂完,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诵棵,你說我怎么就攤上這事抠蚣。” “怎么了履澳?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵嘶窄,是天一觀的道長怀跛。 經(jīng)常有香客問我,道長柄冲,這世上最難降的妖魔是什么吻谋? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮羊初,結果婚禮上滨溉,老公的妹妹穿的比我還像新娘。我一直安慰自己长赞,他們只是感情好晦攒,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著得哆,像睡著了一般脯颜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贩据,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天栋操,我揣著相機與錄音,去河邊找鬼饱亮。 笑死矾芙,一個胖子當著我的面吹牛,可吹牛的內容都是我干的近上。 我是一名探鬼主播剔宪,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壹无!你這毒婦竟也來了葱绒?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤斗锭,失蹤者是張志新(化名)和其女友劉穎地淀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岖是,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡帮毁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了豺撑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片作箍。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖前硫,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情荧止,我是刑警寧澤屹电,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布阶剑,位于F島的核電站,受9級特大地震影響危号,放射性物質發(fā)生泄漏牧愁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一外莲、第九天 我趴在偏房一處隱蔽的房頂上張望猪半。 院中可真熱鬧,春花似錦偷线、人聲如沸磨确。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乏奥。三九已至,卻和暖如春亥曹,著一層夾襖步出監(jiān)牢的瞬間邓了,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工媳瞪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留骗炉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓蛇受,卻偏偏與公主長得像句葵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子龙巨,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容

  • 教程一:視頻截圖(Tutorial 01: Making Screencaps) 首先我們需要了解視頻文件的一些基...
    90后的思維閱讀 4,700評論 0 3
  • 0 概述 FFmpeg是一套領先的音視頻多媒體處理開源框架笼呆,采用LGPL或GPL許可證。它提供了對音視頻的采集旨别、編...
    但行耕者閱讀 6,814評論 0 19
  • 本文轉自:FFmpeg 入門(1):截取視頻幀 | www.samirchen.com 背景 在 Mac OS 上...
    SamirChen閱讀 9,438評論 6 15
  • 根據(jù)ffmpeg官方網(wǎng)站上的例子程序開始學習ffmpeg和SDL編程诗赌。 SDL是一個跨平臺的多媒體開發(fā)包。適用于游...
    762683ff5d3d閱讀 1,801評論 0 2
  • 同樣一個問題秸弛,靜下心來铭若,效率翻倍。一直以來都知道心態(tài)很重要递览,能控制好心態(tài)是需要修煉的叼屠,在工作中也一樣,逃避拖延不能...
    雨林中的陽光閱讀 1,380評論 0 9