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

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

播放視頻再截圖的作法,固然也能夠。可是,手動地截圖會太累並且沒法保證準確度,特別是須要反覆提取圖片時,或者須要提取「105秒那一瞬間的美女圖片」時,或者我須要每秒出一張圖片時,那有別的辦法嗎?佈局

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

通常使用FFmpeg的方式,有兩種,一種是使用FFmpeg的命令(也就是調用ffmpeg這個程序),另外一種是調用FFmpeg的庫文件。這裏小程一樣從命令行以及代碼調用這兩種方式,進行介紹。命令行

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

在安裝FFmpeg後,打個命令就能夠實現這個功能。對於FFmpeg的安裝或調試,以前介紹過。3d

提取圖片能夠這樣,好比:調試

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

參數的意思是這樣的:orm

ss表示開始提取圖片的時間點,既能夠用時分秒格式,也能夠是多少秒。
若是使用到這個參數,那應該把它做爲第一個參數,由於可讓FFmpeg提速。

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

再好比,能夠這樣:視頻

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

參數的意思是這樣的:

r表示每秒提取圖片的幀數,即幀率,默認是25fps,上面設置爲一秒拿一張圖。
t表現持續提取多少秒,也能夠用時分秒的格式來表示。
s表出來的圖片的尺寸。
3%d表示以00一、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庫文件的兩種方式入手,介紹瞭如何實現從視頻中提取圖片的功能。


right

相關文章
相關標籤/搜索