FFmpeg 4.2 Hello World C++
FFmpeg : Extract Image (.BMP) from Video (.mp4)
從 視頻 (.mp4) 擷取 圖像 (.bmp)
using Visual Studio 2019參考 : https://github.com/leandromoreira/ffmpeg-libav-tutorial
1. Download include / lib / dll file from https://ffmpeg.zeranoe.com/builds/
Architecture option choose Windows 32-bit , Linking option choose Dev & Shared
(Dev option has include & lib files , Shared option has dll files)
2.Set up C++ property and Linker property in project property
(Note : Set Linker option SAFESEH:NO)
3. ---------------------------Source.cpp--------------------------------
#define _CRT_SECURE_NO_WARNINGS
extern "C" {
#include"libavcodec/avcodec.h"
#include"libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include<Windows.h>
// print out the steps and errors
static void logging(const char* fmt, ...);
// decode packets into frames
static int decode_packet(AVPacket* pPacket, AVCodecContext* pCodecContext, AVFrame* pFrame, AVFrame* pFrameBGR, SwsContext* pSwsCtx);
// save a frame into a .pgm file
static void save_gray_frame(unsigned char* buf, int wrap, int xsize, int ysize, char* filename);
void SaveAsBMP(AVFrame* pFrameRGB, int width, int height, int index, int bpp)
{
char buf[5] = { 0 };
BITMAPFILEHEADER bmpheader;
BITMAPINFOHEADER bmpinfo;
FILE* fp;
char* filename = new char[255];
//文件存放路径,根据自己的修改
sprintf_s(filename, 255, "%d.bmp", index);
if ((fp = fopen(filename, "wb+")) == NULL) {
printf("open file failed!\n");
return;
}
bmpheader.bfType = 0x4d42;
bmpheader.bfReserved1 = 0;
bmpheader.bfReserved2 = 0;
bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmpheader.bfSize = bmpheader.bfOffBits + width * height * bpp / 8;
bmpinfo.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo.biWidth = width;
bmpinfo.biHeight = height;
bmpinfo.biPlanes = 1;
bmpinfo.biBitCount = bpp;
bmpinfo.biCompression = BI_RGB;
bmpinfo.biSizeImage = (width * bpp + 31) / 32 * 4 * height;
bmpinfo.biXPelsPerMeter = 100;
bmpinfo.biYPelsPerMeter = 100;
bmpinfo.biClrUsed = 0;
bmpinfo.biClrImportant = 0;
fwrite(&bmpheader, sizeof(bmpheader), 1, fp);
fwrite(&bmpinfo, sizeof(bmpinfo), 1, fp);
fwrite(pFrameRGB->data[0], width * height * bpp / 8, 1, fp);
fclose(fp);
}
int main(int argc, const char* argv[])
{
if (argc < 2) {
printf("You need to specify a media file.\n");
return -1;
}
logging("initializing all the containers, codecs and protocols.");
// AVFormatContext holds the header information from the format (Container)
// Allocating memory for this component
// http://ffmpeg.org/doxygen/trunk/structAVFormatContext.html
AVFormatContext* pFormatContext = avformat_alloc_context();
if (!pFormatContext) {
logging("ERROR could not allocate memory for Format Context");
return -1;
}
logging("opening the input file (%s) and loading format (container) header", argv[1]);
// Open the file and read its header. The codecs are not opened.
// The function arguments are:
// AVFormatContext (the component we allocated memory for),
// url (filename),
// AVInputFormat (if you pass NULL it'll do the auto detect)
// and AVDictionary (which are options to the demuxer)
// http://ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#ga31d601155e9035d5b0e7efedc894ee49
if (avformat_open_input(&pFormatContext, argv[1], NULL, NULL) != 0) {
logging("ERROR could not open the file");
return -1;
}
// now we have access to some information about our file
// since we read its header we can say what format (container) it's
// and some other information related to the format itself.
logging("format %s, duration %lld us, bit_rate %lld", pFormatContext->iformat->name, pFormatContext->duration, pFormatContext->bit_rate);
logging("finding stream info from format");
// read Packets from the Format to get stream information
// this function populates pFormatContext->streams
// (of size equals to pFormatContext->nb_streams)
// the arguments are:
// the AVFormatContext
// and options contains options for codec corresponding to i-th stream.
// On return each dictionary will be filled with options that were not found.
// https://ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#gad42172e27cddafb81096939783b157bb
if (avformat_find_stream_info(pFormatContext, NULL) < 0) {
logging("ERROR could not get the stream info");
return -1;
}
// the component that knows how to enCOde and DECode the stream
// it's the codec (audio or video)
// http://ffmpeg.org/doxygen/trunk/structAVCodec.html
AVCodec* pCodec = NULL;
// this component describes the properties of a codec used by the stream i
// https://ffmpeg.org/doxygen/trunk/structAVCodecParameters.html
AVCodecParameters* pCodecParameters = NULL;
int video_stream_index = -1;
// loop though all the streams and print its main information
for (int i = 0; i < pFormatContext->nb_streams; i++)
{
AVCodecParameters* pLocalCodecParameters = NULL;
pLocalCodecParameters = pFormatContext->streams[i]->codecpar;
logging("AVStream->time_base before open coded %d/%d", pFormatContext->streams[i]->time_base.num, pFormatContext->streams[i]->time_base.den);
logging("AVStream->r_frame_rate before open coded %d/%d", pFormatContext->streams[i]->r_frame_rate.num, pFormatContext->streams[i]->r_frame_rate.den);
logging("AVStream->start_time %" PRId64, pFormatContext->streams[i]->start_time);
logging("AVStream->duration %" PRId64, pFormatContext->streams[i]->duration);
logging("finding the proper decoder (CODEC)");
AVCodec* pLocalCodec = NULL;
// finds the registered decoder for a codec ID
// https://ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga19a0ca553277f019dd5b0fec6e1f9dca
pLocalCodec = avcodec_find_decoder(pLocalCodecParameters->codec_id);
if (pLocalCodec == NULL) {
logging("ERROR unsupported codec!");
return -1;
}
// when the stream is a video we store its index, codec parameters and codec
if (pLocalCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO) {
if (video_stream_index == -1) {
video_stream_index = i;
pCodec = pLocalCodec;
pCodecParameters = pLocalCodecParameters;
}
logging("Video Codec: resolution %d x %d", pLocalCodecParameters->width, pLocalCodecParameters->height);
}
else if (pLocalCodecParameters->codec_type == AVMEDIA_TYPE_AUDIO) {
logging("Audio Codec: %d channels, sample rate %d", pLocalCodecParameters->channels, pLocalCodecParameters->sample_rate);
}
// print its name, id and bitrate
logging("\tCodec %s ID %d bit_rate %lld", pLocalCodec->name, pLocalCodec->id, pCodecParameters->bit_rate);
}
// https://ffmpeg.org/doxygen/trunk/structAVCodecContext.html
AVCodecContext* pCodecContext = avcodec_alloc_context3(pCodec);
if (!pCodecContext)
{
logging("failed to allocated memory for AVCodecContext");
return -1;
}
// Fill the codec context based on the values from the supplied codec parameters
// https://ffmpeg.org/doxygen/trunk/group__lavc__core.html#gac7b282f51540ca7a99416a3ba6ee0d16
if (avcodec_parameters_to_context(pCodecContext, pCodecParameters) < 0)
{
logging("failed to copy codec params to codec context");
return -1;
}
// Initialize the AVCodecContext to use the given AVCodec.
// https://ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga11f785a188d7d9df71621001465b0f1d
if (avcodec_open2(pCodecContext, pCodec, NULL) < 0)
{
logging("failed to open codec through avcodec_open2");
return -1;
}
// https://ffmpeg.org/doxygen/trunk/structAVFrame.html
AVFrame* pFrame = av_frame_alloc();
AVFrame* pFrameBGR = av_frame_alloc();
if (!pFrame || !pFrameBGR)
{
logging("failed to allocated memory for AVFrame");
return -1;
}
int FrameBGRSize = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecContext->width, pCodecContext->height, 1);
uint8_t* buffer = (uint8_t*)av_malloc(FrameBGRSize * sizeof(uint8_t));
av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, buffer, AV_PIX_FMT_BGR24, pCodecContext->width, pCodecContext->height, 1);
SwsContext* pSwsCtx = sws_getContext(pCodecContext->width, pCodecContext->height,
pCodecContext->pix_fmt, pCodecContext->width, pCodecContext->height, AV_PIX_FMT_BGR24,
SWS_BILINEAR, NULL, NULL, NULL);
// https://ffmpeg.org/doxygen/trunk/structAVPacket.html
AVPacket* pPacket = av_packet_alloc();
if (!pPacket)
{
logging("failed to allocated memory for AVPacket");
return -1;
}
int response = 0;
int how_many_packets_to_process = 30;
// fill the Packet with data from the Stream
// https://ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#ga4fdb3084415a82e3810de6ee60e46a61
while (av_read_frame(pFormatContext, pPacket) >= 0)
{
// if it's the video stream
if (pPacket->stream_index == video_stream_index) {
logging("AVPacket->pts %" PRId64, pPacket->pts);
response = decode_packet(pPacket, pCodecContext, pFrame, pFrameBGR, pSwsCtx);
if (response < 0)
break;
// stop it, otherwise we'll be saving hundreds of frames
if (--how_many_packets_to_process <= 0) break;
}
// https://ffmpeg.org/doxygen/trunk/group__lavc__packet.html#ga63d5a489b419bd5d45cfd09091cbcbc2
av_packet_unref(pPacket);
}
logging("releasing all the resources");
avformat_close_input(&pFormatContext);
avformat_free_context(pFormatContext);
av_packet_free(&pPacket);
av_frame_free(&pFrame);
av_frame_free(&pFrameBGR);
avcodec_free_context(&pCodecContext);
av_free(buffer);
return 0;
}
static void logging(const char* fmt, ...)
{
va_list args;
fprintf(stderr, "LOG: ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
static int decode_packet(AVPacket* pPacket, AVCodecContext* pCodecContext, AVFrame* pFrame, AVFrame* pFrameBGR, SwsContext* pSwsCtx)
{
// Supply raw packet data as input to a decoder
// https://ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga58bc4bf1e0ac59e27362597e467efff3
int response = avcodec_send_packet(pCodecContext, pPacket);
if (response < 0) {
//logging("Error while sending a packet to the decoder: %s", av_err2str(response));
return response;
}
while (response >= 0)
{
// Return decoded output data (into a frame) from a decoder
// https://ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga11e6542c4e66d3028668788a1a74217c
response = avcodec_receive_frame(pCodecContext, pFrame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
}
else if (response < 0) {
//logging("Error while receiving a frame from the decoder: %s", av_err2str(response));
return response;
}
if (response >= 0) {
logging(
"Frame %d (type=%c, size=%d bytes) pts %d key_frame %d [DTS %d]",
pCodecContext->frame_number,
av_get_picture_type_char(pFrame->pict_type),
pFrame->pkt_size,
pFrame->pts,
pFrame->key_frame,
pFrame->coded_picture_number
);
char frame_filename[1024];
snprintf(frame_filename, sizeof(frame_filename), "%s-%d.pgm", "frame", pCodecContext->frame_number);
// save a grayscale frame into a .pgm file
save_gray_frame(pFrame->data[0], pFrame->linesize[0], pFrame->width, pFrame->height, frame_filename);
//反轉圖像,否則生成的BMP圖像是上下顛倒的
pFrame->data[0] += pFrame->linesize[0] * (pCodecContext->height - 1);
pFrame->linesize[0] *= -1;
pFrame->data[1] += pFrame->linesize[1] * (pCodecContext->height / 2 - 1);
pFrame->linesize[1] *= -1;
pFrame->data[2] += pFrame->linesize[2] * (pCodecContext->height / 2 - 1);
pFrame->linesize[2] *= -1;
sws_scale(pSwsCtx, pFrame->data,
pFrame->linesize, 0, pCodecContext->height,
pFrameBGR->data, pFrameBGR->linesize);
SaveAsBMP(pFrameBGR, pCodecContext->width, pCodecContext->height, pCodecContext->frame_number, 24);
}
}
return 0;
}
static void save_gray_frame(unsigned char* buf, int wrap, int xsize, int ysize, char* filename)
{
FILE* f;
int i;
f = fopen(filename, "w");
// writing the minimal required header for a pgm file format
// portable graymap format -> https://en.wikipedia.org/wiki/Netpbm_format#PGM_example
fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
// writing line by line
for (i = 0; i < ysize; i++)
fwrite(buf + i * wrap, 1, xsize, f);
fclose(f);
}
-----------------------------------------------------------------------------
4. Compile and run
It will pop up an error
To fix it, Copy the dll files in Shared package -> bin to the same folder where .exe is
(In my case : D:\Code\VS Project\C++\FFmpegDemo\Release )
5. in cmd
D:\Code\VS Project\C++\FFmpegDemo\Release>FFmpegDemo.exe MHA.mp4
(MHA.mp4 is a .mp4 file whatever you want)
留言
張貼留言