본문 바로가기
놀기/기초 공부

[FFMPEG] 프레임 추출하기

by Hi~ 2023. 2. 15.

ffmpeg 소스에는 다양한 예제가 있다. 이 예제를 사용하면 간단히 프로그램을 만들 수 있고 ffmpeg / ffplay 소스 코드를 사용하여 입맛에 맞는 좋은 프로그램도 만들 수 있다. 물론, 노력과 시간이 필요하겠지만.

 

어쨌든, 이번에는 ffmpeg 내에 있는 decode_simple.c / decode_simple.h를 사용하여 동영상의 프레임을 저장하는 예제를 간단히 만들어 봤다. 프레임을 뽑아 YUV로 저장하는 예제인데, 아래와 같이 YUV Viewer로 확인 가능하다.

 

2023.02.15 - [놀기/잡스러운 것] - YUV Viewer

 

YUV Viewer

예전에는 YUV Viewer도 입맛에 맞는 것이 없었고 쓸만한 것은 유료였는데, 요즘은 이런 좋은 프로그램이 있네.. ㅎㅎ https://github.com/IENT/YUView GitHub - IENT/YUView: The Free and Open Source Cross Platform YUV Viewer w

busyman.tistory.com

 

 

YUV 포맷은 이 자체만으로도 분량이 많으니 위키와 인터넷상의 여러 게시물을 참조하시길

https://namu.wiki/w/YUV

 

YUV - 나무위키

크로마 서브샘플링(Chroma Subsampling)을 단어대로 해석한다면 색차를 덜 표본화(sampling)한다는 뜻으로 영상에서 픽셀 개수의 표본화 결과인 해상도를 줄이는 손실 압축 인코딩이다. 여기서 해상도

namu.wiki

 

아래는 메인 함수다. 비디오 코덱에 따라 안 되는 경우가 있는데, 그냥 간단히 만드는 예제라 문제없이 동작하는 동영상을 넣어 테스트했다.

#include <QCoreApplication>
#include <QTimer>
#include <QDebug>

#include <signal.h>
#include <inttypes.h>

#include "decode_simple.h"

extern "C" {
    #include "libavutil/video_enc_params.h"
    #include "libavcodec/avcodec.h"
}

#pragma comment(lib, "Secur32.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "bcrypt.lib")
#pragma comment(lib, "Mfplat.lib")
#pragma comment(lib, "Mfuuid.lib")
#pragma comment(lib, "Strmiids.lib")
#pragma comment(lib, "ole32.lib")

#pragma comment(lib, "User32.lib")

#pragma comment(lib, "libavcodec.a")
#pragma comment(lib, "libavdevice.a")
#pragma comment(lib, "libavfilter.a")
#pragma comment(lib, "libavformat.a")
#pragma comment(lib, "libavutil.a")
#pragma comment(lib, "libpostproc.a")
#pragma comment(lib, "libswresample.a")
#pragma comment(lib, "libswscale.a")

// AVFrame으로 전달된 프레임을 YUV 포맷으로 저장한다.
void save_vframe(AVFrame *avFrame, int frameNumber)
{
    char filename[32];
    sprintf(filename, "frame%d.yuv", frameNumber);
    FILE *fDump = fopen(filename, "ab");

    uint32_t pitchY = avFrame->linesize[0];
    uint32_t pitchU = avFrame->linesize[1];
    uint32_t pitchV = avFrame->linesize[2];

    uint8_t *avY = avFrame->data[0];
    uint8_t *avU = avFrame->data[1];
    uint8_t *avV = avFrame->data[2];

    for (int32_t i = 0; i < avFrame->height; i++) {
        fwrite(avY, avFrame->width, 1, fDump);
        avY += pitchY;
    }

    for (int32_t i = 0; i < avFrame->height/2; i++) {
        fwrite(avU, avFrame->width/2, 1, fDump);
        avU += pitchU;
    }

    for (int32_t i = 0; i < avFrame->height/2; i++) {
        fwrite(avV, avFrame->width/2, 1, fDump);
        avV += pitchV;
    }

    fclose(fDump);
}

// ds_run()이 호출되면 디코딩이 시작되고 아래 함수가 호출되어 후처리를 할 수 있다.
// 프레임 저장이 목적이므로 간단히 저장하는 함수만 호출한다.
static int process_frame(DecodeContext *dc, AVFrame *frame)
{
    static bool _show_info = true;

    if ((NULL == dc) || (NULL == frame)) {
        return -1;
    }

    if (true == _show_info) {
        fprintf(stderr, "%d x %d\n", dc->frame->width, dc->frame->height);
        _show_info = false;
    }
    save_vframe(dc->frame, dc->decoder->frame_number);

    return 0;
}

int run_main(void)
{
    DecodeContext dc;

    unsigned int stream_idx, max_frames;
    const char *filename;
    int ret = 0;

    filename = "E:/_001/test.avi";
    stream_idx  = 0;
    max_frames  = 50; // 앞에서 부터 50개의 frame을 읽어 저장

    ret = ds_open(&dc, filename, stream_idx);
    if (ret < 0) {
        goto finish;
    }

    dc.process_frame = process_frame;
    dc.max_frames    = max_frames;

    ret = ds_run(&dc);

finish:
    ds_free(&dc);
    return ret;

    return 0;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QTimer::singleShot(0,[](){
         run_main();
         QCoreApplication::exit(0);
    });

    return a.exec();
}

 

 

save_frame.pro
0.00MB
decode_simple.cpp
0.00MB
decode_simple.h
0.00MB
main.cpp
0.00MB

댓글