반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

buntalk.com

FFmpeg and SDL Tutorial - Outputting to the Screen 본문

FFmpeg

FFmpeg and SDL Tutorial - Outputting to the Screen

분석톡톡 2023. 3. 19. 18:06
반응형

2019/04/07

 

Tutorial 02: Outputting to the Screen
 
 
 
SDL and Video
 
스크린에 그리기위해 SDL을 사용한다. SDL은 Simple DirectMedia Layer 로서 멀티미디어를 위한 훌륭한 라이브러리이며 크로스 플랫폼이고 다양한 프로젝트에서 사용된다. 라이브러리는 get the library at the official website  거나 운영체제에 맞는 개발 패키지를 다운로드할 수 있다. 이 튜토리얼을 위해 라이브러리를 컴파일 해야 한다.
 
SDL은 스크린에 드로잉하기 위한 다양한 방법을 가지고 있는데 그 중에 하나가 스크린에 영화를 보여주는 것으로서 YUV오버레이라 부른다.  YUV (technically not YUV but YCbCr)  일러두기: "YCbCr" "YUV"라 불리는 용어에 대해 일러둘 사항이 있다. 일반적으로 말해 YUV는 아날로그 포맷이고 YCbCr은 디지털 형식이다. FFmpeg과 SDL은 이들 코드와 매크로에서 YCbCr을 YUV로 사용한다. RGB와 같은 로이미지 데이터를 저장하는 방법이다. 간단히 말해, Y는 밝기 (또는 luma) 요소, U와 V는 색상 요소이다. (RGB보다는 복잡한데 몇가지 색상정보는 무시되며 2개의 Y샘플에 대해 1개의 U, V샘플만 있다. SDL의 YUV오버레이는 YUV데이터 배열을 받아 표시한다. 이는 YUV포맷의 다른 4가지의 형식을 수용하며 YV12가 가장 빠르다. 다른 YUV형식으로 YUV420P가 있는데 YV12와 같지만 U, V배열이 바뀐 것이다. 420은 4:2:0 비율로 서브샘플됨을 의미하는데 매번 4개의 루마샘플에 대해 1개의 색상샘플만을 가짐을 의미하며 그럼으로서 색상 정보가 4분의 1이 된다. 이는 대역량을 줄이기 위한 것이며 인간의 눈이 이 변화에 민감하지 않기 때문이다. 이름에서의 P의 의미는 포맷이 "planar" 라는 뜻으로서 Y, U, V가 각각 구별된 배열에 담김을 의미한다. FFmpeg은 이미지를 YUV420P로 변환할 수 있으며 추가적으로 많은 비디오 스트림이 이미 해당 포맷을 가지고 있어 쉽게 그 포맷으로 변환할 수 있다.
 
이제 우리 계획은 튜토리얼1의 SaveFrame() 함수를 변경해 화면에 출력한다. 하지만 먼저 SDL라이브러리가 어떻게 사용되는지 봐야 한다. 먼저 라이브러리를 포함시키고 SDL을 초기화한다.
 
 
#include <SDL.h>
#include <SDL_thread.h>

if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
{
  fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
  exit(1);
}
SDL_Init() 는 라이브러리에 어떤 기능을 사용할지를 알린다. SDL_GetError()는 편리한 디버깅 함수이다.
 
 
디스플레이 생성하기
 
이제 스크린에 내용을 뿌려야 한다. SDL로 이미지를 보여주는 기본적인 영역을 서피스라 부른다.
 
 
SDL_Surface *screen;
screen = SDL_GetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
if (!screen)
{
  fprintf(stderr, "SDL: Could not set video mode - exiting\n");
  exit(1);
}
이 것은 주어진 너비와 높이로 스크린을 설정한다. 다음 옵션은 스크린의 비트 뎁스로서 0은 현 디스플레이와 같게를 의미한다. (OS X에서는 작동하지 않으니 소스 확인)
 
이제 스크린에 YUV오버레이를 생성하여 비디오를 입력하고 SWSContext를 설정해 이미지데이터를 YUV420으로 변환한다.
SDL_Overlay *bmp = NULL;
struct SWSContext *sws_ctx = NULL;
bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);

// 소프트웨어 스케일링을 위한 SWS 컨텍스트 초기화
sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL);

 

 
이미지를 표시하기 위해 YV12를 사용하며 FFmpeg으로부터 YUV420데이터를 받는다.
 
이미지 표시하기
 
이제 이미지를 표시해야 한다. 프레임을 끝마쳤던 곳으로 가보자. RGB프레임을 얻기위한 모든 작업을 수행했고 디스플레이코드에 맞게 SaveFrame()을 변경할 것이다. 이미지를 표시하려면 AVPicture 구조체를 만들어 이 데이터 포인터를 설정하고 YUV오버레이에 라인사이즈를 설정한다.
if (frameFinished)
{
  SDL_LockYUVOverlay(bmp);
  AVPicture pict;
  pict.data[0] = bmp->pixels[0];
  pict.data[1] = bmp->pixels[2];
  pict.data[2] = bmp->pixels[1];

  pict.linesize[0] = bmp->pitches[0];
  pict.linesize[1] = bmp->pitches[2];
  pict.linesize[2] = bmp->pitches[1];

  // 이미지를 SDL이 사용하는 YUV 포맷으로 변환한다
  sws_scale(sws_ctx, (uint8_t const *const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pict.data, pict.linesize);
  SDL_UnlockYUVOverlay(bmp);
}

 

 
먼저, 오버레이를 락하여 쓸 수 있게 한다. 이는 좋은 습관으로서 나중에 문제가 생기는 것을 감안하는 것이다. AVPicture 구조체는 data포인터를 가지며 4개의 포인터 배열로 되어있다. YUV420P를 다룰 것이므로 우리는 3채널만 필요하며 데이터의 3세트만 있다. 다른 포맷은 알파채널과 같은 것을 위해 네 번째 포인터를 가질 수도 있다. linesize는 이름에서 의미하는 것과 같다. YUV 오버레이내의 기본 구조는 pixels와 piches 변수이다. (피치는 SDL이 사용하는 용어로서 데이터의 주어진 선의 너비이다. 그래서 여기서 우리가 할 것은 pict.data의 세 배열에 대한 것이므로 pict에 쓸 때는 오버레이에 작성하는 것이고 이는 필요한 공간이 할당되어 있다. 비슷하게, 오버레이에서 라인사이즈 정보를 얻는다. PIX_FMT_YUV420P로 포맷을 변경하니 이전 처럼 sws_scale을 사용한다.
 
이미지 그리기
 
하지만 여전히 SDL에게 실제적으로 우리가 주어준 데이터를 보여지게 해야 한다. 사각형을 함수에 전달해 영상이 어디에 보여질지 그리고 너비와 높이는 스케일될지를 지정한다. 이런 방법으로 SDL은 스케일링을 수행해주고, 그리고 그래픽 프로세서를 통해 빠른 스케일링이 가능할 수도 있다.
 
SDL_Rect rect;

if (frameFinished)
{
  /* .. code .. */
  // 이미지를 SDL이 사용하는 YUV포맷으로 컨버트한다.
  sws_scale(sws_ctx, (uint8_t const *const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pict.data, pict.linesize);
  SDL_UnlockYUVOverlay(bmp);
  rect.x = 0;
  rect.y = 0;
  rect.w = pCodecCtx->width;
  rect.h = pCodecCtx->height;
  SDL_DisplayYUVOverlay(bmp, &rect);
}
 
이제 비디오가 보여진다!
 
이제 SDL의 다른 기능을 살펴보자. 이벤트 시스템이다. SDL 설정으로 어플리케이션에서 타이핑하거나 마우스를 움직이거나 또는 시그널을 보내면 이벤트를 생성한다. 프로그램은 이들 이벤트를 확인해 사용자 입력을 처리한다. 또한 프로그램에서 이벤트를 만들어 SDL이벤트 시스템에 보낼 수도 있다. 이는 특히 SDL 을 통한 멀티스레드 프로그래밍에서 유용한데 이는 튜토리얼 4에서 살펴볼것이다. 프로그램에서 패킷을 처리한 후 이벤트를 보낸다. 이제 SDL_QUIT 이벤트를 처리해 종료할 수 있게 할 것이다.
 
SDL_Event event;

av_free_packet(&packet);
SDL_PollEvent(&event);
switch (event.type)
{
case SDL_QUIT:
  SDL_Quit();
  exit(0);
  break;
default:
  break;
}
 
끝났다! 모든 것을 수행한 후 컴파일할 준비가 되었다. 리눅스와 같은 시스템이라면 SDL라이브러리를 다음과 같이 컴파일하는 것이 최상의 방법이다.
 
gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
 
sdl-config 는 gcc에 적합한 플래그를 표시하므로 gcc가 적절히 SDL라이브러리를 포함하게 한다. 시스템에서 추가적인 작업을 수행해야 할 수 있는데 SDL문서를 통해 시스템에 적합한 정보를 얻기바란다. 일단 컴파일하고 실행해보자.
 
이 프로그램을 실행하면 어떤 일이 벌어지는가? 비디오가 이상하다. 영화 파일에서 최대한 빨리 얻어와 비디오 프레임을 보여준다. 코드에는 언제 비디오를 보여줄지를 처리하지 않았다. 특히 (튜토리얼5에서) 비디오 싱킹을 살펴볼 것이다. 일단 한가지 중요한 것을 빠뜨렸다. 사운드이다!
 
 
 
// tutorial02.c
// A pedagogical video player that will stream through every video frame as fast as it can.
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard,
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
//
// Use
//
// gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed,
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial02 myvideofile.mpg
//
// to play the video stream on your screen.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <SDL.h>
#include <SDL_thread.h>

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include <stdio.h>

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 28, 1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

int main(int argc, char *argv[])
{
  AVFormatContext *pFormatCtx = NULL;
  int i, videoStream;
  AVCodecContext *pCodecCtxOrig = NULL;
  AVCodecContext *pCodecCtx = NULL;
  AVCodec *pCodec = NULL;
  AVFrame *pFrame = NULL;
  AVPacket packet;
  int frameFinished;
  float aspect_ratio;
  struct SwsContext *sws_ctx = NULL;

  SDL_Overlay *bmp;
  SDL_Surface *screen;
  SDL_Rect rect;
  SDL_Event event;

  if (argc < 2)
  {
    fprintf(stderr, "Usage: test <file>\n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();

  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
  {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

  // Open video file
  if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0)
    return -1; // Couldn't open file

  // Retrieve stream information
  if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    return -1; // Couldn't find stream information

  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, argv[1], 0);

  // Find the first video stream
  videoStream = -1;
  for (i = 0; i < pFormatCtx->nb_streams; i++)
    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
    {
      videoStream = i;
      break;
    }
  if (videoStream == -1)
    return -1; // Didn't find a video stream

  // Get a pointer to the codec context for the video stream
  pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec;
  // Find the decoder for the video stream
  pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if (pCodec == NULL)
  {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }

  // Copy context
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if (avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0)
  {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Open codec
  if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    return -1; // Could not open codec

  // Allocate video frame
  pFrame = av_frame_alloc();

// Make a screen to put our video
#ifndef __DARWIN__
  screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
  screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
  if (!screen)
  {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }

  // Allocate a place to put our YUV image on that screen
  bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
                             pCodecCtx->height,
                             SDL_YV12_OVERLAY,
                             screen);

  // initialize SWS context for software scaling
  sws_ctx = sws_getContext(pCodecCtx->width,
                           pCodecCtx->height,
                           pCodecCtx->pix_fmt,
                           pCodecCtx->width,
                           pCodecCtx->height,
                           PIX_FMT_YUV420P,
                           SWS_BILINEAR,
                           NULL,
                           NULL,
                           NULL);

  // Read frames and save first five frames to disk
  i = 0;
  while (av_read_frame(pFormatCtx, &packet) >= 0)
  {
    // Is this a packet from the video stream?
    if (packet.stream_index == videoStream)
    {
      // Decode video frame
      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

      // Did we get a video frame?
      if (frameFinished)
      {
        SDL_LockYUVOverlay(bmp);

        AVPicture pict;
        pict.data[0] = bmp->pixels[0];
        pict.data[1] = bmp->pixels[2];
        pict.data[2] = bmp->pixels[1];

        pict.linesize[0] = bmp->pitches[0];
        pict.linesize[1] = bmp->pitches[2];
        pict.linesize[2] = bmp->pitches[1];

        // Convert the image into YUV format that SDL uses
        sws_scale(sws_ctx, (uint8_t const *const *)pFrame->data,
                  pFrame->linesize, 0, pCodecCtx->height,
                  pict.data, pict.linesize);

        SDL_UnlockYUVOverlay(bmp);

        rect.x = 0;
        rect.y = 0;
        rect.w = pCodecCtx->width;
        rect.h = pCodecCtx->height;
        SDL_DisplayYUVOverlay(bmp, &rect);
      }
    }

    // Free the packet that was allocated by av_read_frame
    av_free_packet(&packet);
    SDL_PollEvent(&event);
    switch (event.type)
    {
    case SDL_QUIT:
      SDL_Quit();
      exit(0);
      break;
    default:
      break;
    }
  }

  // Free the YUV frame
  av_frame_free(&pFrame);

  // Close the codec
  avcodec_close(pCodecCtx);
  avcodec_close(pCodecCtxOrig);

  // Close the video file
  avformat_close_input(&pFormatCtx);

  return 0;
}
반응형

'FFmpeg' 카테고리의 다른 글

FFmpeg and SDL Tutorial - Synching Video  (0) 2023.03.19
FFmpeg and SDL Tutorial - Spawning Threads  (0) 2023.03.19
FFmpeg and SDL Tutorial - Playing Sound  (0) 2023.03.19
FFmpeg and SDL Tutorial - Making Screencaps  (0) 2023.03.19
muxing, demuxing  (0) 2023.03.19