多媒體開發(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程序來錄制聲音或圖像,這個辦法是一個操作的過程,很少涉及到概念上的東西。 而本文,要介紹的是聲音采集的一些流程與概念。 聲音的采集流程與概念,是枯燥的,你如果不想了解的話,到這裏就可以退
多媒體開發(11):Android平臺上裁剪m4a
Android手機上設定鈴聲的操作比較靈活,你聽到一首喜歡的歌曲,馬上就可以對這首歌曲進行裁剪,裁剪到片段後,再通過系統的介面設定為鈴聲(電話鈴聲、鬧鐘鈴聲等)。前提是,播放這首歌的APP,需要提供裁剪歌曲的功能。 那麼,怎麼樣實現擷取音訊檔案的功能呢? 基於之前的介紹,你可能很自然就想到使用FFmpeg命令
多媒體開發(12):解碼aac到wav檔案
簡單來說,aac是一種音訊編碼格式,需要解碼後才能用於音訊輸出。aac編碼格式,已經是一種很常見的音訊編碼格式,以至於很多系統都支援aac的編解碼,比如iOS上的AudioConverterRef介面、Android上的MediaCodec介面等。 但是,不要以為用了系統的介面就是用了硬體解碼,因為,這個系統
多媒體開發(14):媒體格式的概念
之前講了一些音視訊的錄製操作,還有聲音採集的概念。採集只是多媒體操作流程中的一個環節,更多的環節可以看看這個圖: 聲音或視訊採集後,就是編碼、寫檔案或推流。不管是編碼還是寫“檔案”,你都能找到相應的程式(比如FFmpeg)來完成,一般加上自己的業務程式碼就能實現自己的功能需求。那就沒有東西好說的了? 沒東
多媒體開發(15):H264的常見概念
H264,是你常見的技術術語了吧。 那h264是什麼東西呢? H.264是視訊編碼標準,又是標準,得標準得天下啊。 在術語的拼寫上,小程以能理解為準。 本文介紹H264的常見概念。 預警,本文相對枯燥,你可隨時放棄閱讀。 (1)H264從哪裡來? 之前介紹媒體格式的概念時,有提到過國際標準化組織(ISO)
多媒體開發(16):幀率與位元速率的概念
為什麼說音視訊開發入門較難,因為涉及到很多概念,之前還專門講“媒體格式”、“h264概念”的東西。現在又來,“幀率”跟“位元速率”,這也是兩個常見的概念。你應該經常聽到“重新整理的幀率是多少”或“位元速率比較高所以網速要比較快”的表達吧。 本文介紹音視訊的幀率與位元速率的概念。 (1)幀率 幀率,表示的是頻率
多媒體開發(18):FFmpeg的常見結構體
除了之前講的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程式來錄製聲音或影象,這個辦法是一個操作的過程,很少涉及到概念上的東西。而**本文,要介紹的是聲音採集的一些流程與概念。** 聲音的採集流程與概念,是枯燥的,但是,我也會盡量說一些有趣的現象來緩解這種枯燥。 聽得到的,或聽不到的聲音,抽象來說,都是模擬訊號,也可以形象一點,叫能量波
DPDK(10):報文處理中的指令預取(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