FFmpeg截取視頻幀
利用FFmpeg截取視頻的幀,保存為ppm圖像格式,如果需要截取攝像頭拍攝的當前幀(RTSP攝像頭),將地址換成攝像頭地址即可。
新手入門代碼,諸多漏洞,請多指教。
開發環境:
vs2017
需自行配置FFmpeg開發環境、取消SDL檢查(網上可以直接查到,都差不多。注意:在VS中如果選擇「為解決方案創建目錄」,會導致sln和vcxproj不在同一級目錄內。對此,FFmpeg中bin中的dll文件,需要放在vcxproj文件同級目錄下,而不是放在sln文件同級目錄)
問題:
有一些函數比較老,可能即將被淘汰
功能寫在主函數了,未分成函數調用
//ffpmeg文件已在stdafx.h頭文件中聲明,具體的聲明,貼在下方#include "stdafx.h"#include<conio.h>/*【stdafx.h中添加的引用頭文件內容】extern "C"{#include "includelibavcodecavcodec.h" #include "includelibavformatavformat.h" #include "includelibavutilchannel_layout.h" #include "includelibavutilcommon.h" #include "includelibavutilimgutils.h" #include "includelibswscaleswscale.h" #include "includelibavutilimgutils.h" #include "includelibavutilopt.h" #include "includelibavutilmathematics.h" #include "includelibavutilsamplefmt.h" }*//*將幀保存到文件*/void SaveFrame(AVFrame *pFrame,int width,int height,int iFrame) { FILE *pFile; //文件指針 char szFilename[32];//文件名(字元串) int y; // sprintf(szFilename,"frame%04d.ppm",iFrame); //生成文件名 pFile = fopen(szFilename, "wb"); //打開文件,只寫入 if (pFile == NULL) { return; } //getch(); fprintf(pFile,"P6
%d %d
255
",width,height);//在文檔中加入,必須加入,不然PPM文件無法讀取 for (y = 0; y < height;y++) { fwrite(pFrame->data[0] + y*pFrame->linesize[0], 1, width*3, pFile); } fclose(pFile);}int main(int argc, char* argv[]){ /* 流程: 1. 打開文件, 2. 從文件中讀取流stream, 3. 讀取數據包package, 4. 讀取幀frame(如果package中的frame不完整,則跳到步驟2 5. 處理幀frame */ //註冊所有組件 av_register_all(); //輸出ffmpeg配置信息,用於檢查ffmpeg配置是否 //printf("%s", avcodec_configuration()); //變數聲明 AVFormatContext *pFormatCtx = NULL; //多媒體容器指針(結構體) char filepath[] = "../../../../TestVideo01.mp4";//媒體文件相對路徑 int videoStream; //視頻流標記 /* argv[ ]: 指針數組,用來存放指向你的字元串參數的指針,每一個元素指向一個參數 argv[0] 指向程序運行的全路徑名 argv[1] 指向在DOS命令行中執行程序名後的第一個字元串 argv[2] 指向執行程序名後的第二個字元串 //【打開文件】(伺服器的連接和碼流頭部信息的拉取) //這裡是用來在計算機打開cmd用命令行運行exe時帶入參數用的,這裡先用手寫的路徑來編寫程序 if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0) { return -1; } */ if (avformat_open_input(&pFormatCtx,filepath,NULL,NULL) !=0) { return -1; } //讀出媒體信息(為pFormatCtx->streams填充對應的信息,同時調試用的av_dump_format變得可用) if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { return -1; } //列印文件容器信息(調試用) av_dump_format(pFormatCtx, 0, filepath, 0); //讀取視頻流信息,循環讀取(讀出編碼類型,圖像的寬度、高度,音頻的參數) videoStream = -1;//默認值 for (int i = 0; i < pFormatCtx->nb_streams;i++) { //讀取【視頻】碼流編碼信息(信息已在上一步填充,每個stream都記錄了一條流的信息) if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { //這裡判斷是視頻流,只對視頻流操作 videoStream = i; break; } } if (videoStream == -1) { //讀取失敗 return -1; } //【視頻流解碼器準備】 AVCodecContext *pCodecCtxOrig; //多媒體容器原始指針 AVCodec * pCodec;//視頻流的解碼器 AVCodecContext * pCodecCtx; //拷貝原始指針 //獲取指向視頻流的編解碼器上下文的指針(包含了這路流所使用的所有的 codec 的信息 pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec; //從視頻流中讀取 視頻流的解碼器 pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id); if (pCodec == NULL) { fprintf(stderr,"編碼器無法支持該文件的編碼
"); return -1; } //複製編碼器信息 /* 需要注意,我們不能直接使用視頻流中的 AVCodecContext, 所以我們需要用 avcodec_copy_context() 拷貝一份新的 AVCodecContext 出來。 */ pCodecCtx = avcodec_alloc_context3(pCodec);//釋放內容 if (avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) { fprintf(stderr, "無法複製編碼器上下文
"); return -1; } //打開codec if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { return -1; } //【存儲數據部分,讓數據載入內存】 //分配視頻幀 (處理視頻用視頻幀,用於之後的視頻格式轉換) AVFrame *pFrame = NULL; //處理用視頻幀 pFrame = av_frame_alloc(); AVFrame *pFrameRGB; //處理用視頻幀 pFrameRGB = av_frame_alloc(); if (pFrameRGB == NULL) { return -1; } //分配視頻幀需要內存 (存放原始數據用) int numBytes; //需要的內存大小 uint8_t *buffer = NULL; //獲取需要的內存大小 /* 1. av_image_fill_arrays 函數來關聯 frame 和我們剛才分配的內存 2. av_malloc 是一個 FFmpeg 的 malloc, 主要是對 malloc 做了一些封裝來保證地址對齊之類的事情, 它會保證你的代碼不發生內存泄漏、多次釋放或其他 malloc 問題 */ numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1);//獲取需要的內存大小 buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); //關聯frame和剛才分配的內容 av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1); //【從流中讀取視頻數據,之前需要 準備好解碼器 和 將流載入內存 】 /* 1. 從stream讀取packet, 2. 解碼到frame, 3. 獲取到完整的frame時,轉換格式並存儲 */ //變數聲明 AVPacket packet; //數據包變數 int frameFinished; //完整幀獲取標記(獲取到完整幀時,會被標記為真) struct SwsContext *sws_ctx = NULL; //初始化SWS上下文 sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); //讀取幀並將前5幀保存到磁碟 int i = 0; //從視頻流stream中讀取一個數據包packet //while (av_read_frame(pFormatCtx,&packet)) { while (int x = av_read_frame(pFormatCtx, &packet) >= 0) { if (packet.stream_index == videoStream) { //解碼視頻幀(將數據包packet轉成視頻幀frame,每解碼到一個完整幀,frameFinished會被設置為幀) avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); //如果是一個完整幀 if (frameFinished) { //將視頻幀原來的格式pCodecCtx->pix_fmt轉換成RGB sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); //只挑前【自定義】個完整幀進行保存 if (++i <= 100) { SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i); frameFinished = 0; } else { av_packet_unref(&packet); break; } } printf("%d
",i); } //釋放由av_read_frame分配的數據包 av_packet_unref(&packet); } //【釋放內存等掃尾工作】 av_free(buffer); av_frame_free(&pFrameRGB); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avcodec_close(pCodecCtxOrig); avformat_close_input(&pFormatCtx);//對應avformat_open_input getch(); return 0;}
推薦閱讀:
※【視頻】陳家溝太極文化旅遊季朱天才大師精彩太極拳表演
※【視頻欣賞】(42集)個性服裝量體裁剪製作教程
※假臉姐妹團成員大換血!甘薇出局李小璐稱王,撞臉似複製粘貼好尷尬?
※軍方視頻《較量無聲》被刪 的確較量無聲!
※文件夾【舞蹈視頻】