简体   繁体   中英

How to save AVFrame as image in C++ using FFmpeg

In my project, i'd like to save one of the frames from Hevc file. I'm using FFmpeg in source code to decode the Hevc file and get AVFrame and AVCodecContext. What i need is to save the frame as picture(with full colors).

I have tried to save it as *.pgm file, so the picture is just grey, which not really i need.

Any suggesstion? Thanks!

void HevcDecoder::Images_Save(char* filename, AVFrame *frame)
{
    FILE* file;
    int i;

    fopen_s(&file, filename, "wb");
    fprintf(file, "P5\n%d %d\n%d\n", frame->width, frame->height, 255);
    for (i = 0; i < frame->height; i++)
        fwrite(frame->data[0] + i * frame->linesize[0], 1, frame->width, file);

    fclose(file);
}

void HevcDecoder::Decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt, const char* filename)
{
    char buf[1024];
    int ret;

    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0) {
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }

    while (ret >= 0) {
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }

        printf("saving frame %3d\n", dec_ctx->frame_number);
        fflush(stdout);

        /* the picture is allocated by the decoder. no need to
           free it */
        snprintf(buf, sizeof(buf), "%s-%d.pgm", filename, dec_ctx->frame_number);
        Images_Save(buf, frame/*, dec_ctx*/);
    }
}

Converting raw HEVC file to sequence of images image using FFmpeg CLI, is simple.

Assume input.265 is the input file (raw HEVC video stream):
Converting to PNG images:

ffmpeg -i input.265 %05d.png

Converting to PPM images:

ffmpeg -i input.265 %05d.ppm

In case the input video uses MP4 container and you want JPEG images:

ffmpeg -i input.265 %05d.jpg


Using FFmpeg C interface (Libav):

For making things reproducible, start by creating an input video file using FFmpeg CLI:

ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=10 -vcodec libx265 -pix_fmt yuv420p input.265

The above command creates HEVC (H.265) encoded stream - 10 frames with resolution 192x108 and pixel format YUV420 (synthetic pattern).
The encoded stream is raw video stream (without container).

Note:

  • RAW HEVC (H.265) video stream is not commonly used file format.
    Usually the stream is wrapped by container (like MP4 / MKV / AVI...).
    We use the raw video stream for educational purposes - the code used for decoding is simpler.

Saving the images as color images:

The code sample reuses the code from the this post .

  • PGM is a grayscale format, for equivalent color format we may use PPM format .
  • We may use SWS Scale to convert the format from YUV420 to RGB.

We can use the code sample from this post

Here is the code sample:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}

#define INBUF_SIZE 1024

//static void pgm_save(unsigned char* buf, int wrap, int xsize, int ysize, char* filename)
//{
//    FILE* f;
//    int i;
//
//    f = fopen(filename, "wb");
//    fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
//    for (i = 0; i < ysize; i++)
//        fwrite(buf + i * wrap, 1, xsize, f);
//    fclose(f);
//}


//Save RGB image as PPM file format
static void ppm_save(unsigned char* buf, int wrap, int xsize, int ysize, char* filename)
{
    FILE* f;
    int i;

    f = fopen(filename, "wb");
    fprintf(f, "P6\n%d %d\n%d\n", xsize, ysize, 255);

    for (i = 0; i < ysize; i++)
    {
        fwrite(buf + i * wrap, 1, xsize*3, f);
    }

    fclose(f);
}


static void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt, const char* filename)
{
    struct SwsContext* sws_ctx = NULL;
    char buf[1024];
    int ret;
    int sts;

    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0)
    {
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }

    //Create SWS Context for converting from decode pixel format (like YUV420) to RGB
    ////////////////////////////////////////////////////////////////////////////
    sws_ctx = sws_getContext(dec_ctx->width,
                             dec_ctx->height,
                             dec_ctx->pix_fmt,
                             dec_ctx->width,
                             dec_ctx->height,
                             AV_PIX_FMT_RGB24,
                             SWS_BICUBIC,
                             NULL,
                             NULL,
                             NULL);

    if (sws_ctx == nullptr)
    {
        return;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    //Allocate frame for storing image converted to RGB.
    ////////////////////////////////////////////////////////////////////////////
    AVFrame* pRGBFrame = av_frame_alloc();

    pRGBFrame->format = AV_PIX_FMT_RGB24;
    pRGBFrame->width = dec_ctx->width;
    pRGBFrame->height = dec_ctx->height;

    sts = av_frame_get_buffer(pRGBFrame, 0);

    if (sts < 0)
    {
        return;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    while (ret >= 0) 
    {
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
            return;
        }
        else if (ret < 0) 
        {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }

        printf("saving frame %3d\n", dec_ctx->frame_number);
        fflush(stdout);

        /* the picture is allocated by the decoder. no need to
           free it */
        //snprintf(buf, sizeof(buf), "%s_%03d.pgm", filename, dec_ctx->frame_number);
        //pgm_save(frame->data[0], frame->linesize[0],
        //    frame->width, frame->height, buf);
         
        //Convert from input format (e.g YUV420) to RGB and save to PPM:
        ////////////////////////////////////////////////////////////////////////////
        sts = sws_scale(sws_ctx,                //struct SwsContext* c,
                        frame->data,            //const uint8_t* const srcSlice[],
                        frame->linesize,        //const int srcStride[],
                        0,                      //int srcSliceY, 
                        frame->height,          //int srcSliceH,
                        pRGBFrame->data,        //uint8_t* const dst[], 
                        pRGBFrame->linesize);   //const int dstStride[]);

        if (sts != frame->height)
        {
            return;  //Error!
        }

        snprintf(buf, sizeof(buf), "%s_%03d.ppm", filename, dec_ctx->frame_number);
        ppm_save(pRGBFrame->data[0], pRGBFrame->linesize[0], pRGBFrame->width, pRGBFrame->height, buf);
        ////////////////////////////////////////////////////////////////////////////
    }

    //Free
    sws_freeContext(sws_ctx);
    av_frame_free(&pRGBFrame);
}

int main(int argc, char** argv)
{
    const char* filename, * outfilename;
    const AVCodec* codec;
    AVCodecParserContext* parser;
    AVCodecContext* c = NULL;
    FILE* f;
    AVFrame* frame;
    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t* data;
    size_t   data_size;
    int ret;
    AVPacket* pkt;    

    filename = argv[1];
    outfilename = argv[2];

    pkt = av_packet_alloc();
    if (!pkt)
    {
        exit(1);
    }

    //memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
    memset(inbuf, 0, sizeof(inbuf));

    codec = avcodec_find_decoder(AV_CODEC_ID_HEVC);
    if (!codec)
    {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    parser = av_parser_init(codec->id);
    if (!parser)
    {
        fprintf(stderr, "parser not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);
    if (!c)
    {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    if (avcodec_open2(c, codec, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    f = fopen(filename, "rb");
    if (!f)
    {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame)
    {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    while (!feof(f))
    {
        /* read raw data from the input file */
        data_size = fread(inbuf, 1, INBUF_SIZE, f);

        if (!data_size)
        {
            break;
        }

        /* use the parser to split the data into frames */
        data = inbuf;
        while (data_size > 0) 
        {
            ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, data, (int)data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

            if (ret < 0)
            {
                fprintf(stderr, "Error while parsing\n");
                exit(1);
            }

            data += ret;
            data_size -= ret;


            if (pkt->data) 
            {
                printf("NICE\n");
                decode(c, frame, pkt, outfilename);
            }
        }
    }

    /* flush the decoder */
    decode(c, frame, NULL, outfilename);

    fclose(f);

    av_parser_close(parser);
    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_packet_free(&pkt);

    return 0;
}

Showing images using OpenCV:

One of the simplest ways to show an image is using OpenCV library.

Setting up a project that uses both FFmpeg and OpenCV for the first time may be challenging.

  • We need the image to be in BGR format.
  • For showing the image, use: cv::imshow followed by cv::waitKey .

Code sample:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//Use OpenCV for showing the inage
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>


extern "C" {
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}

#define INBUF_SIZE 1024

//static void pgm_save(unsigned char* buf, int wrap, int xsize, int ysize, char* filename)
//{
//    FILE* f;
//    int i;
//
//    f = fopen(filename, "wb");
//    fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
//    for (i = 0; i < ysize; i++)
//        fwrite(buf + i * wrap, 1, xsize, f);
//    fclose(f);
//}


//Save RGB image as PPM file format
//static void ppm_save(unsigned char* buf, int wrap, int xsize, int ysize, char* filename)
//{
//    FILE* f;
//    int i;
//
//    f = fopen(filename, "wb");
//    fprintf(f, "P6\n%d %d\n%d\n", xsize, ysize, 255);
//
//    for (i = 0; i < ysize; i++)
//    {
//        fwrite(buf + i * wrap, 1, xsize*3, f);
//    }
//
//    fclose(f);
//}


static void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt, const char* filename)
{
    struct SwsContext* sws_ctx = NULL;
    char filename_buf[1024];
    int ret;
    int sts;

    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0)
    {
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }

    //Create SWS Context for converting from decode pixel format (like YUV420) to BGR
    ////////////////////////////////////////////////////////////////////////////
    sws_ctx = sws_getContext(dec_ctx->width,
                             dec_ctx->height,
                             dec_ctx->pix_fmt,
                             dec_ctx->width,
                             dec_ctx->height,
                             AV_PIX_FMT_BGR24, //For OpenCV, we want BGR pixel format.
                             SWS_BICUBIC,
                             NULL,
                             NULL,
                             NULL);

    if (sws_ctx == nullptr)
    {
        return;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    //Allocate frame for storing image converted to RGB.
    ////////////////////////////////////////////////////////////////////////////
    AVFrame* pBGRFrame = av_frame_alloc();

    pBGRFrame->format = AV_PIX_FMT_BGR24;
    pBGRFrame->width = dec_ctx->width;
    pBGRFrame->height = dec_ctx->height;

    sts = av_frame_get_buffer(pBGRFrame, 0);

    if (sts < 0)
    {
        return;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    while (ret >= 0) 
    {
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
            return;
        }
        else if (ret < 0) 
        {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }

        printf("saving frame %3d\n", dec_ctx->frame_number);
        fflush(stdout);

        /* the picture is allocated by the decoder. no need to
           free it */
        //snprintf(buf, sizeof(buf), "%s_%03d.pgm", filename, dec_ctx->frame_number);
        //pgm_save(frame->data[0], frame->linesize[0],
        //    frame->width, frame->height, buf);
         
        //Convert from input format (e.g YUV420) to BGR:
        ////////////////////////////////////////////////////////////////////////////
        sts = sws_scale(sws_ctx,                //struct SwsContext* c,
                        frame->data,            //const uint8_t* const srcSlice[],
                        frame->linesize,        //const int srcStride[],
                        0,                      //int srcSliceY, 
                        frame->height,          //int srcSliceH,
                        pBGRFrame->data,        //uint8_t* const dst[], 
                        pBGRFrame->linesize);   //const int dstStride[]);

        if (sts != frame->height)
        {
            return;  //Error!
        }

        snprintf(filename_buf, sizeof(filename_buf), "%s_%03d.jpg", filename, dec_ctx->frame_number);
        //ppm_save(pBGRFrame->data[0], pBGRFrame->linesize[0], pBGRFrame->width, pBGRFrame->height, buf);        
        ////////////////////////////////////////////////////////////////////////////

        //Use OpenCV for showing the image (and save the image in JPEG format):
        ////////////////////////////////////////////////////////////////////////////
        cv::Mat img = cv::Mat(pBGRFrame->height, pBGRFrame->width, CV_8UC3, pBGRFrame->data[0], pBGRFrame->linesize[0]);    //cv::Mat is OpenCV "thin image wrapper".
        cv::imshow("img", img);
        cv::waitKey(100);   //Wait 100msec (relativly long time - for testing).

        //Save the inage in JPEG format using OpenCV
        cv::imwrite(filename_buf, img);
        ////////////////////////////////////////////////////////////////////////////

    }

    //Free
    sws_freeContext(sws_ctx);
    av_frame_free(&pBGRFrame);
}

int main(int argc, char** argv)
{
    const char* filename, * outfilename;
    const AVCodec* codec;
    AVCodecParserContext* parser;
    AVCodecContext* c = NULL;
    FILE* f;
    AVFrame* frame;
    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t* data;
    size_t   data_size;
    int ret;
    AVPacket* pkt;    

    filename = argv[1];
    outfilename = argv[2];

    pkt = av_packet_alloc();
    if (!pkt)
    {
        exit(1);
    }

    //memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
    memset(inbuf, 0, sizeof(inbuf));

    codec = avcodec_find_decoder(AV_CODEC_ID_HEVC);
    if (!codec)
    {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    parser = av_parser_init(codec->id);
    if (!parser)
    {
        fprintf(stderr, "parser not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);
    if (!c)
    {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    if (avcodec_open2(c, codec, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    f = fopen(filename, "rb");
    if (!f)
    {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame)
    {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    while (!feof(f))
    {
        /* read raw data from the input file */
        data_size = fread(inbuf, 1, INBUF_SIZE, f);

        if (!data_size)
        {
            break;
        }

        /* use the parser to split the data into frames */
        data = inbuf;
        while (data_size > 0) 
        {
            ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, data, (int)data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

            if (ret < 0)
            {
                fprintf(stderr, "Error while parsing\n");
                exit(1);
            }

            data += ret;
            data_size -= ret;


            if (pkt->data) 
            {
                printf("NICE\n");
                decode(c, frame, pkt, outfilename);
            }
        }
    }

    /* flush the decoder */
    decode(c, frame, NULL, outfilename);

    fclose(f);

    av_parser_close(parser);
    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_packet_free(&pkt);

    return 0;
}

Sample output:

output_001.jpg :
在此处输入图像描述

output_002.jpg :
在此处输入图像描述

output_003.jpg :
在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM