정보공간_1

[6기 부산 박천경]SDL (Simple DirectMedia layer) #3 본문

IT 놀이터

[6기 부산 박천경]SDL (Simple DirectMedia layer) #3

알 수 없는 사용자 2014. 10. 9. 18:25

안녕하세요 부산 멤버십 박천경 입니다. 

SDL (Simple DirectMedia layer) #1 과 #2 에서는 SDL의 설치법 과, window에 이미지 생성 

그리고 사운드 카드 지정 후 소리 재생에 관해서 알아 보았습니다. 

이번 시간은 SDL 의 마지막  시간 입니다. 지금까지 SDL(Simple DirectMedia layer)의 

소스들을 토대로 ffmpeg 라이브러리의 사용법을 간단히 익힌 후 ffmpeg을 

SDL2.0버전에서 동영상 재생하는 작업을 연재 하겠습니다.

참고로 필자는 window 환경에서 개발을 진행 하였습니다.


1. ffmpeg의 간단한 정의

FFmpeg은 크로스 플랫폼을 지원하는 오픈소스 멀티미디어 프레임워크입니다. 

FFmpeg을 이용해 인코딩/디코딩, 트랜스코딩(transcode), 먹싱/디먹싱, 스트림(stream)은

 물론 '재생'까지 멀티미디어와 관련한 거의 모든 기능을 다 갖추고 있습니다.

FFmpeg의 라이센스는 GPL과 LGPL입니다.

 FFmpeg에는 다음과 같은 여러 세부 라이브러리가 있습니다. 

libavcodec: 오디오/비디오의 인코더/디코더

libavformat: 오디오/비디어 컨테이너 포맷의 muxer/demuxer

libavutil: FFmpeg 개발 시 필요한 다양한 유틸리티

libpostproc: video post-processing

libswscale: 비디오의 image scaling, color-space, pixel-format 변환

libavfilter: 인코더와 디코더 사이에서 오디오/비디오를 변경하고 검사

libswresample: 오디오 리샘플링(audio resampling)

FFmpeg을 이용하여 동영상 파일을 읽어 원본 데이터를 추출하는 과정은 다음과 같습니다.


2. ffmpeg 개발 환경 구축


ffmpeg의 개발 환경은 https://www.ffmpeg.org/ 에서 dev 버전을
다운을 받고 SDL과 같은 방법으로 추가 라이브러리 와 추가 포함파일에 등록시켜 주게 되면 
손쉽게 ffmpeg의 환경 구축을 끝낼 수 있습니다.


3. ffmpeg 과 SDL 2.0 을 이용한 간단한 플레이어 만들기


자, 이제 ffmpeg에 대한 정의도 익혔고, ffmpeg의 환경구축도 완료되었기때문에

ffmpeg 과 SDL을 잘 mixing 하여서 avi 동영상을 SDL window 창을 이용하여 재생시키겠습니다.


extern "C"

{

#include <libavcodec\avcodec.h>

#include <libavformat\avformat.h>

#include <libswscale\swscale.h>

#include <SDL.h>

#include <SDL_thread.h>

}

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

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

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

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

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

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

ffmpeg 과 SDL의 초기화 부분입니다. 다음과 같은 파일을 프로젝트에 꼭 포함이 되어야됩니다.


AVFormatContext *pFormatCtx = NULL ;

int i , videoStream ; brief

AVCodecContext *pCodecCtx = NULL ;

AVCodec *pCodec = NULL ;

AVFrame *pFrame = NULL ;

AVPacket packet ;

int frameFinished ;


AVDictionary *optionDict = NULL ;

struct SwsContext *sws_ctx = NULL ;

SDL_Texture *bmp = NULL ;

SDL_Window *window = NULL ;

SDL_Rect rect ;

SDL_Event even ; 

const char *FilePath = "test.avi" ;


다음의 변수의 초기화 부분입니다. 
여기서 자세히 보아야 될 변수들은
SDL_Texture 은 영상의 화소  데이터의 스펙이 담긴 구조체 입니다.
SDL_window는 윈도우를 식별하는데 사용되는 구조체 입니다.
SDL_Rect SDL창의 가로,세로,너비,폭의 정보를 닮고 있는 구조체입니다. 
SDL_Event는 SDL에서 사용되는 이벤트들의 집합 입니다.
FilePath로 영상의 파일 경로를 저장해 놓았습니다.
이제 본격적으로 main() 함수에서 작업을 진행 해 보겠습니다.

av_register_all() ;

SDL_Init(SDL_INIT_VIDEO);

avformat_open_input(&pFormatCtx , FilePath , NULL , NULL) ;

avformat_find_stream_info(pFormatCtx , NULL);

av_dump_format(pFormatCtx , 0 , FilePath , 0) ;


av_register_all()은 ffmpeg의 설정을 초기화 하는 부분입니다.
마찬가지로 SDL_init 역시 SDL함수의 사용을 초기화 하는 부분입니다. 
지금 여기에서는 video 부분만을 초기화 해서 사용하겠습니다.
avformat_open_input() 함수는 첫번째 인자로 넘긴 AVFormatContext 
구조체에 I/O 및 Muxing/Demuxing에 필요한 정보를 채워주는 역할을 합니다.

pCodecCtx = pFormatCtx->streams[videoStream]->codec ;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id) ;
avcodec_open2(pCodecCtx , pCodec , &optionDict);
pFrame = avcodec_alloc_frame() ;
AVFrame *pFrameYUV = avcodec_alloc_frame() ;

avcodec_find_decoder() 함수에 codec_id 값을 넘겨줌으로서
ffmpeg Library에 해당 Codec용 Decoder정보가 있는지 찾아볼 수 있습니다.
만일 Decoder 정보가 존재한다면, AVCodecContext에 해당 정보를 넘겨줘서
 Decoder로서 초기화 하는 일이 남았습니다. 이 때 사용하는 함수가 avcodec_open2() 입니다.

window = SDL_CreateWindow("Welcome SSM SDL" , SDL_WINDOWPOS_UNDEFINED 
SDL_WINDOWPOS_UNDEFINED , pCodecCtx->width , pCodecCtx->height ,
SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS) ;
SDL_Renderer *renderer = SDL_CreateRenderer(window , -1 , 0) ;
bmp = SDL_CreateTexture(renderer , SDL_PIXELFORMAT_IYUV , SDL_TEXTUREACCESS_STREAMING , 
pCodecCtx->width , pCodecCtx->height) ;

SDL을 성공적으로 초기화 했다면 SDL_CreateWindow함수를 이용하여 하나의 창을 만들어 줍니다.
기존 SDL1.2버전에서 조금 차이가 있는 부분 입니다.
SDL_CreateTexture는 렌더링으로 받아온 컨텍스트를 질감과 형식그리고 영상의 폭과 높이를 설정해 줍니다.

sws_ctx = sws_getContext(pCodecCtx->width ,pCodecCtx->height , pCodecCtx->pix_fmt ,

pCodecCtx->width ,pCodecCtx->height ,PIX_FMT_YUV420P ,

SWS_BILINEAR , NULL , NULL , NULL) ;

int numBytes = avpicture_get_size(PIX_FMT_YUV420P , pCodecCtx->width , pCodecCtx->height) ;

uint8_t *buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t)) ;

avpicture_fill((AVPicture *)pFrameYUV , buffer , PIX_FMT_YUV420P ,

 pCodecCtx->width , pCodecCtx->height) ;

rect.x = 0 ; rect.y = 0 ; rect.w = pCodecCtx->width ;rect.h = pCodecCtx->height ;


영상 데이터를 YUV420P로 변환 해줘야 되기 때문에 sws_getContext()함수로
 변환 핸들을 얻은후 이핸들을 sws_scale()로 실질적인 작업을 해 줄수 있습니다.
avpicture_get_size  영상의 size 를 받아 올 수 있습니다.
avpicture_fill()을 이용해서 Frame 과 우리가 새로 할당한 버퍼를 연결합니다.

while(av_read_frame(pFormatCtx , &packet) >=0)
{
if(packet.stream_index == videoStream)
{
avcodec_decode_video2(pCodecCtx , pFrame , &frameFinished , &packet) ;
if(frameFinished)
{
sws_scale( sws_ctx ,(uint8_t const * const *)pFrame->data ,pFrame->linesize ,
0 ,pCodecCtx->height ,pFrameYUV->data ,pFrameYUV->linesize) ;
SDL_UpdateTexture(bmp , &rect , pFrameYUV->data[0] , pFrameYUV->linesize[0]) ;
SDL_RenderClear(renderer) ;
SDL_RenderCopy(renderer , bmp , &rect , &rect) ;
SDL_RenderPresent(renderer) ;
}
SDL_Delay(30) ;
}
av_free_packet(&packet) ;
SDL_PollEvent(&even) ;
 switch(even.key.keysym.sym) 
 { 
 case SDLK_SPACE: SDL_Quit() ;
 exit(1) ;
break ;
default:
break ;
}
}

 메인 루프 문 입니다.
av_read_frame()는 패킷에서 읽어와서 AVPacket struct 에 저장합니다.
SDL_Delay를 이용해서 영상의 배속을 조절할수 있습니다.
switch, case문으로 key이벤트 및 각종 이벤트롤 삽입하실 수 있습니다.
이 반복문에서 프레임을 차례차례 넣어서 시간을 가지고
영사을 만들어 주는 것 같습니다.


SDL_DestroyTexture(bmp) ;
av_free(pFrame) ;
av_free(pFrameYUV) ;
avcodec_close(pCodecCtx) ;
avformat_close_input(&pFormatCtx) ;

마지막으로 SDL함수들와 ffmpeg의 함수들을 설정 해제하는 부분입니다.


지금까지 잘 따라 오셨다면 무난히 원하는 영상을 보실 수 있을 거라고 믿습니다.ㅎ
SDL을 이용하여 영상 재생 그리고 재미있는 게임을 만들 수 있으니 
SDL의 매력에 흡뻑 빠져 보시길 바랍니다.
이만 SDL(Simple DirectMedia layer)의 블로그를 마무리 짓겠습니다.!
수고하셨습니다.!ㅎ