1. 程式人生 > >多媒體開發(10):從視訊中提取圖片

多媒體開發(10):從視訊中提取圖片

小白:提取視訊中的圖片嗎?那很簡單,播放視訊再截圖就行啦。

播放視訊再截圖的做法,當然也可以。但是,手動地截圖會太累而且無法保證準確度,特別是需要反覆提取圖片時,或者需要提取“105秒那一瞬間的美女圖片”時,或者我需要每秒出一張圖片時,那有別的辦法嗎?

本文介紹,如何使用FFmpeg實現從視訊中提取圖片的功能。

一般使用FFmpeg的方式,有兩種,一種是使用FFmpeg的命令(也就是呼叫ffmpeg這個程式),另一種是呼叫FFmpeg的庫檔案。這裡小程同樣從命令列以及程式碼呼叫這兩種方式,進行介紹。

(一)使用FFmpeg命令來解決問題

在安裝FFmpeg後,打個命令就可以實現這個功能。對於FFmpeg的安裝或除錯,之前介紹過。

提取圖片可以這樣,比如:

ffmpeg -ss 00:00:5 -i moments.mp4 -vframes 1 -f image2 -y a.png

引數的意思是這樣的:

ss表示開始提取圖片的時間點,既可以用時分秒格式,也可以是多少秒。
如果使用到這個引數,那應該把它作為第一個引數,因為可以讓FFmpeg提速。

i表示輸入檔案,就是視訊檔案。
vframes表示拿多少幀,也就是多少張圖片。注意,這個引數要放在-i引數之後。
f表示提取出來的圖片的格式。
y表示覆蓋已有同名的圖片。

再比如,可以這樣:

ffmpeg -i xxx.mp4 -r 1 -y -f image2 -t 5 -s 240*320 pc%3d.jpg

引數的意思是這樣的:

r表示每秒提取圖片的幀數,即幀率,預設是25fps,上面設定為一秒拿一張圖。
t表現持續提取多少秒,也可以用時分秒的格式來表示。
s表出來的圖片的尺寸。
3%d表示以001、002這樣的格式來命名輸出的圖片。

於是,

小白:那麼說,如果我發現視訊某個時間點有美女的話,那我就可以用ss從這個時間點再前一點,然後用t來持續提取5秒,或者用vframes來提取幾十張,那就準沒漏了!也就是這樣:

ffmpeg -ss 10 -t 5 -r 1 -i Movie-1.mp4 -f image2 -y pc-temp/image%3d.jpg

小白:看,這是提取到的美女圖:

另一方面,你在提取到若干成圖片後,有可能想把這些圖片編碼成視訊,這時同樣可以藉助FFmpeg命令來完成。需要注意,圖片變成視訊,是需要視訊編碼器的,所以在安裝FFmpeg時需要把視訊編碼器也帶上(比如x264),這個小程在之前有所介紹。

把圖片編碼成視訊的命令是這樣的:

ffmpeg -f image2 -i img%3d.jpg test.mp4

img%d表示以"img001", "img002"這種命名的檔案(也就是之前提取出來的圖片),按順序使用。注意f引數要在i引數之前。

你可能覺得mp4格式沒有gif格式通用,於是又有了把mp4轉成gif動態圖的需求,這時還是可以敲打ffmpeg命令:

ffmpeg -i hello.mp4 hello.gif

當然這只是簡單地把mp4轉成gif,你也可以加上解析度、位元速率之類的引數來控制,這裡不細說。

(二)寫程式碼呼叫FFmpeg庫來解決問題

通過寫程式碼呼叫FFmpeg庫的方式來提取圖片,並且儲存成24bit的點陣圖。

小程先貼上演示程式碼,再在後面做一些解釋:

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    unsigned int filesize;
    unsigned short reserved1;
    unsigned short reserved2;
    unsigned int dataoffset;
}BITMAP_FILE_HEADER;

typedef struct {
    unsigned int infosize;
    int width;
    int height;
    unsigned short planecount;
    unsigned short bitcount;
    unsigned int compressiontype;
    unsigned int imagedatasize;
    int xpixpermeter;
    int ypixpermeter;
    unsigned int colorusedcount;
    unsigned int colorimportantcount;
}BITMAP_INFO;

void extractpicture(const char* filepath) {
    av_register_all();
    av_log_set_level(AV_LOG_DEBUG);
    AVFormatContext* formatContext = avformat_alloc_context();
    int status = 0;
    int success = 0;
    int videostreamidx = -1;
    AVCodecContext* codecContext = NULL;
    status = avformat_open_input(&formatContext, filepath, NULL, NULL);
    if (status == 0) {
        status = avformat_find_stream_info(formatContext, NULL);
        if (status >= 0) {
            for (int i = 0; i < formatContext->nb_streams; i ++) {
                if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
                    videostreamidx = i;
                    break;
                }
            }
            if (videostreamidx > -1) {
                codecContext = formatContext->streams[videostreamidx]->codec;
                AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
                if (codec) {
                    status = avcodec_open2(codecContext, codec, NULL);
                    if (status == 0) {
                        success = 1;
                    }
                }
            }
        }
        else {
            av_log(NULL, AV_LOG_DEBUG, "avformat_find_stream_info error\n");
        }
    }
    if (success) {
        av_dump_format(formatContext, 0, filepath, 0);
        int gotframe = 0;
        AVFrame* frame = av_frame_alloc();
        int decodelen = 0;
        int limitcount = 10;
        int pcindex = 0;
        unsigned char* rgbdata = (unsigned char*)malloc(avpicture_get_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height));
        AVFrame* rgbframe = av_frame_alloc();
        avpicture_fill((AVPicture*)rgbframe, rgbdata, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height);
        struct SwsContext* swscontext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt, 
                codecContext->width, codecContext->height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
        while (pcindex < limitcount) {
            AVPacket packet;
            av_init_packet( &packet );
            status = av_read_frame(formatContext, &packet);
            if (status < 0) {
                if (status == AVERROR_EOF) {
                    av_log(NULL, AV_LOG_DEBUG, "read end for file\n");
                }
                else {
                    av_log(NULL, AV_LOG_DEBUG, "av_read_frame error\n");
                }
                av_packet_unref(&packet);
                break;  
            }
            else {
                if (packet.stream_index == videostreamidx) {
                    decodelen = avcodec_decode_video2(codecContext, frame, &gotframe, &packet);
                    if (decodelen > 0 && gotframe) {
                        frame->data[0] = frame->data[0] + frame->linesize[0] * (codecContext->height - 1);
                        frame->data[1] = frame->data[1] + frame->linesize[1] * (codecContext->height / 2 - 1);
                        frame->data[2] = frame->data[2] + frame->linesize[2] * (codecContext->height / 2 - 1);
                        frame->linesize[0] *= -1;
                        frame->linesize[1] *= -1;
                        frame->linesize[2] *= -1;
                        sws_scale(swscontext, frame->data, frame->linesize, 0, 
                                codecContext->height, rgbframe->data, rgbframe->linesize);  
                        char filename[12] = {0};
                        sprintf(filename, "pc%03d.bmp", ++ pcindex);
                        FILE* file = fopen(filename, "wb");
                        if (file) {
                            int pixcount = codecContext->width * codecContext->height;
                            BITMAP_FILE_HEADER fileheader = {0};
                            fileheader.filesize = 2+sizeof(BITMAP_FILE_HEADER)+sizeof(BITMAP_INFO)+pixcount * 3;
                            fileheader.dataoffset = 0x36;
                            BITMAP_INFO bmpinfo = {0};
                            bmpinfo.infosize = sizeof(BITMAP_INFO);
                            bmpinfo.width = codecContext->width;
                            bmpinfo.height = codecContext->height;
                            bmpinfo.planecount = 1;
                            bmpinfo.bitcount = 24;
                            bmpinfo.xpixpermeter = 5000;
                            bmpinfo.ypixpermeter = 5000;
                            unsigned short ftype = 0x4d42;
                            fwrite(&ftype, sizeof ftype, 1, file);
                            fwrite(&fileheader, sizeof fileheader, 1, file);
                            fwrite(&bmpinfo, sizeof bmpinfo, 1, file);
                            fwrite(rgbframe->data[0], pixcount*3, 1, file);
                            fclose(file);
                        }
                    }
                }
            } 
            av_packet_unref(&packet);
        }
        av_frame_free(&rgbframe);
        free(rgbdata);
        av_frame_free(&frame);
        sws_freeContext(swscontext);
    }
    avformat_free_context(formatContext);
}

int main(int argc, char *argv[])
{
    extractpicture("moments.mp4");
    return 0;
}
  • 程式演示了把解碼後的圖片資料儲存成點陣圖的過程。如果有需要可以做更多的修改,比如av_seek_frame到適當的位置再開始解碼與儲存點陣圖,也可以控制多少幀後儲存一張點陣圖,等等。
  • av_register_all註冊“所有”,所有的編解碼器、muxer與demuxer等等(前提是configure編譯時有enable,才會真正使用到),這一步是關鍵的初始化工作,沒有這一步,FFmpeg很可能不能如期工作。
  • avformat_open_input開啟輸入。“輸入”是一個抽象,這裡具體成檔案。這一步之後,就獲得了一些檔案格式資訊。
  • avformat_find_stream_info查詢流的資訊。多媒體資料由流組成,這一步就是獲取媒體格式資訊,有可能比較耗時。這一步後,流使用的編解碼器被確定下來。
  • avcodec_find_decoder找到解碼器。
  • avcodec_open2開啟解碼器。
  • av_read_frame讀取一個packet,未解碼。
  • avcodec_decode_video2解碼一個視訊幀。
  • sws_getContext獲取並初始化一個SwsContext場景,swscontext不僅可以縮放圖片,還可以轉換顏色佈局。
  • sws_scale縮放或轉換。
  • 在呼叫sws_scale之前,對frame->data跟frame->linesize做的處理,是為了調整座標系,讓圖片適合點陣圖的座標系(從下往上,從左往右),這樣轉換出來的點陣圖才不會顛倒。
  • 這裡選擇的是24位的點陣圖(沒有調色盤),在寫入rgb資料前,先把檔案頭與點陣圖資訊寫好。

至此,在視訊中提取圖片的實現,就介紹完畢了。

總結一下,本文從直接使用ffmpeg命令列,以及寫程式碼呼叫FFmpeg庫檔案的兩種方式入手,介紹瞭如何實現從視訊中提取圖片的功能。


相關推薦

多媒體開發10視訊提取圖片

小白:提取視訊中的圖片嗎?那很簡單,播放視訊再截圖就行啦。 播放視訊再截圖的做法,當然也可以。但是,手動地截圖會太累而且無法保證準確度,特別是需要反覆提取圖片時,或者需要提取“105秒那一瞬間的美女圖片”時,或者我需要每秒出一張圖片時,那有別的辦法嗎? 本文介紹,如何使用FFmpeg實現從視訊中提取圖片的

多媒體開發2錄製視訊

上一節介紹了用ffplay來播放檔案(或url),這裡有一個概念,如果是播放已經存在的檔案,那叫“回放”,也就是Playback(從流媒體的角度也叫點播),如果播放的是正在錄製的資料(邊錄邊播),那叫直播。 不管是回放還是直播,都需要有媒體資料,那這個媒體資料是怎麼來的呢?從已有的檔案編輯而來是一個辦法,但

多媒體開發10提取圖片以及點陣圖儲存

> 小白:提取視訊中的圖片嗎?那很簡單,播放視訊再截圖就行啦。 播放視訊再截圖的做法,當然可以。但是,手動截圖會太累而且無法保證準確度,特別是需要反覆提取圖片時,或者需要提取“105秒那一瞬間的美女圖片”時,或者我需要每秒出一張圖片時,那有別的辦法嗎? **本文介紹,如何使用FFmpeg實現從視訊中

多媒體開發6濾鏡實現各種圖片效果 | Video-Filters | 變色

命令行 let img 很多 保持 yuv 黑白 多媒體 ati 之前講過使用FFmpeg的drawtext濾鏡(把圖片或文字加到視頻上),而實際上,FFmpeg的濾鏡很強大,遠不止加字幕或加圖片的功能。濾鏡是很有趣的,可以把圖片變模糊、變色、縮放旋轉,等等。 本文介紹FF

Windows Phone開發10常用控件

androi chm att size near grid txt idt inf Windows Phone的控件有幾個來源,和傳統的桌面應用程序開發或Web開發一樣,有默認提供的控件和第三方開者發布的控件。一般而言,如果不是過於復雜的界面布局,使用默認控件就足矣。相比之

多媒體開發3直播

特點 nss ams 測試的 方式 input cat nginx 成功 之前介紹了如何錄制音視頻,以及相關的多媒體的概念。對於已經錄制的多媒體進行“就地”播放(參考前文),就是回放,除了“回放”這個流程,還有一個流程也會經常遇到,那就是“直播”。 本文介紹直播的實現。 “

多媒體開發8調試FFmpeg

run 包括 啟用 return tar.bz2 %d 參考 efi turn 編譯FFmpeg得到二進制文件,之後就是對二進制庫的調用,這時FFmpeg就像一個黑盒子。作為程序員,難道不想研究一下FFmpeg的具體實現?比如是怎麽拿到歌曲信息的、怎麽解碼的、怎麽推流的,等

多媒體開發9聲音采集的概念 | 振幅 | 頻率 | 共振 | 電平化

坐標 波形 上下 樣本 形狀 多少 為什麽不使用 dsd 運動 之前介紹通過ffmpeg程序來錄制聲音或圖像,這個辦法是一個操作的過程,很少涉及到概念上的東西。 而本文,要介紹的是聲音采集的一些流程與概念。 聲音的采集流程與概念,是枯燥的,你如果不想了解的話,到這裏就可以退

多媒體開發11Android平臺上裁剪m4a

Android手機上設定鈴聲的操作比較靈活,你聽到一首喜歡的歌曲,馬上就可以對這首歌曲進行裁剪,裁剪到片段後,再通過系統的介面設定為鈴聲(電話鈴聲、鬧鐘鈴聲等)。前提是,播放這首歌的APP,需要提供裁剪歌曲的功能。 那麼,怎麼樣實現擷取音訊檔案的功能呢? 基於之前的介紹,你可能很自然就想到使用FFmpeg命令

多媒體開發12解碼aac到wav檔案

簡單來說,aac是一種音訊編碼格式,需要解碼後才能用於音訊輸出。aac編碼格式,已經是一種很常見的音訊編碼格式,以至於很多系統都支援aac的編解碼,比如iOS上的AudioConverterRef介面、Android上的MediaCodec介面等。 但是,不要以為用了系統的介面就是用了硬體解碼,因為,這個系統

多媒體開發14媒體格式的概念

之前講了一些音視訊的錄製操作,還有聲音採集的概念。採集只是多媒體操作流程中的一個環節,更多的環節可以看看這個圖: 聲音或視訊採集後,就是編碼、寫檔案或推流。不管是編碼還是寫“檔案”,你都能找到相應的程式(比如FFmpeg)來完成,一般加上自己的業務程式碼就能實現自己的功能需求。那就沒有東西好說的了? 沒東

多媒體開發15H264的常見概念

H264,是你常見的技術術語了吧。 那h264是什麼東西呢? H.264是視訊編碼標準,又是標準,得標準得天下啊。 在術語的拼寫上,小程以能理解為準。 本文介紹H264的常見概念。 預警,本文相對枯燥,你可隨時放棄閱讀。 (1)H264從哪裡來? 之前介紹媒體格式的概念時,有提到過國際標準化組織(ISO)

多媒體開發16幀率與位元速率的概念

為什麼說音視訊開發入門較難,因為涉及到很多概念,之前還專門講“媒體格式”、“h264概念”的東西。現在又來,“幀率”跟“位元速率”,這也是兩個常見的概念。你應該經常聽到“重新整理的幀率是多少”或“位元速率比較高所以網速要比較快”的表達吧。 本文介紹音視訊的幀率與位元速率的概念。 (1)幀率 幀率,表示的是頻率

多媒體開發18FFmpeg的常見結構體

除了之前講的avpacket跟avframe,FFmpeg還有其它一些結構經常在流程中出現。FFmpeg還有哪些常見的結構呢?先來看一下這個截圖: 這張圖中的主角,是AVFormatContext。AVFormatContext是FFmpeg的基本結構之一,對應於封裝格式(或容器格式)。 圍繞FFmpeg

多媒體開發6用濾鏡實現各種圖片效果

之前講過使用FFmpeg的drawtext濾鏡(把圖片或文字加到視訊上),而實際上,FFmpeg的濾鏡很強大,遠不止加字幕或加圖片的功能。濾鏡很有趣,可以把圖片變模糊、變色、縮放旋轉,等等。 **本文介紹FFmpeg濾鏡的使用。目的是讓你感受一下FFmepg的濾鏡效果,這樣在實際需要某種效果時,可以考慮使用

多媒體開發7編譯Android與iOS平臺的FFmpeg

編譯FFmpeg,一個古老的話題,但我還是介紹一遍,就當記錄。之前介紹怎麼給視訊新增水印時,就已經提到FFmpeg的編譯,並且在編譯時指定了濾鏡的功能。 但是,在手機盛行的時代,你可能更需要的是能在iOS或Android平臺上執行的FFmpeg,而對於命令列的ffmpeg,你可以在個人電腦上面使用(因為它簡

多媒體開發8除錯FFmpeg

編譯FFmpeg得到二進位制檔案,之後就是對二進位制庫的呼叫,這時FFmpeg就像一個黑盒子。作為程式設計師,難道不想研究一下FFmpeg的具體實現?比如是怎麼拿到歌曲資訊的、怎麼解碼的、怎麼推流的,等等。 看原始碼是理解程式碼實現的一個辦法,而單步除錯能從另一個維度去幫到你。**本文介紹如何單步除錯FFm

多媒體開發9我是聲音

之前介紹通過ffmpeg程式來錄製聲音或影象,這個辦法是一個操作的過程,很少涉及到概念上的東西。而**本文,要介紹的是聲音採集的一些流程與概念。** 聲音的採集流程與概念,是枯燥的,但是,我也會盡量說一些有趣的現象來緩解這種枯燥。 聽得到的,或聽不到的聲音,抽象來說,都是模擬訊號,也可以形象一點,叫能量波

DPDK10報文處理的指令預取prefetcht0

在DPDK的例子中報文處理時讀取報文內容時添加了指令預取命令(prefetcht0): /* * Read packet from RX queues */ for (i = 0; i < qconf->n_rx_port; i++) {

Python菜鳥到高手10循環

ems 銀行卡 講解 條件表達式 gda while 依次 continue 大於等於   我們現在已經知道了如何使用if語句讓程序沿著不同的路徑執行,不過程序最大的用處就是利用CPU和GPU強大的執行能力不斷重復執行某段代碼,想想Google的Alph