영상을 다루는 사람들은 FFMPEG 라이브러리를 모르는 사람을 없을 것이다. 그래.. 많다. 안 쓰이는 곳이 없으니 웬만한 동영상 플레이어도 FFMPEG 라이브러리를 사용해서 재생한다. 물론 Directshow 를 이용해서 하기도 하지만 Directshow의 ffdshow도 FFMPEG 라이브러를 사용하니…

이 라이브러리는 방대하다. 웬만한 코덱은 다 지원하다 보니 덩치가 겁나 크다. 그런데 한동안 libav 라고 해서 잠시 분리되어 프로젝트가 진행되기도 하다 보니 API 인터페이스가 다른(?) 상황도 있었다. 뭐 지금은 다시 통합되었지만… 구글에서 ffmpeg 관련에서 문의 하다 보면 관련 샘플 코드가 어떤건 libav , 어떤건 ffmpeg 으로 작성되어 있는 경우도 있었다. 뭐 그다지 크게 다르진 않고 조금 다르긴 하지만 헷갈렸던 건 사실이다.

FFMPEG 라이브러리를 이용한다는건 어떤 파일을 다른 파일 포맷으로 변환 한다던지(재인코딩), 영상 스트림을 디코딩, 인코딩을 사용하기 위해서 일것이다. 여기서 난 윈도우 기반에서 개발한다는 가정하에 디코딩,인코딩 API 만 짚을 예정이다.


개발환경

- Windows 32bit
- Visual Studio 2010

FFMPEG 라이브러리 구하기

FFMPEG라이브러리를 사용하기 위해선 파일을 다운 받아야 한다. FFMPEG 라이브러리는 리눅스 기반에서 개발되었으며, 거의 모든게 GCC 컴파일러에 맞춰 있다. 물론 윈도우에서 컴파일 하는 방법은 있지만 그 방법이 쉽지는 않다. 많은 사람들이 어려웠던지 윈도우용 DLL 만 따로 모아놓은 사이트가 있다.

http://ffmpeg.zeranoe.com/builds/ 

여기에 윈도우용 ffmpeg 라이브러리 DLL 파일이 있다. 32 비트 파일과 64비트 파일 둘 다 지원한다. 또한 static, share, dev 용으로 구분 되어져 있는데 share과 dev용을 다운 받는다.

dev 압축파일을 적당한 위치에 압축을 풀어준다. 그리고 share 압축 파일에서 BIN 디렉토리의 DLL만 선택 후 dev 압축이 풀린 폴더에서 lib 폴더에 복사한다. dev 압축 파일에 include 파일과 lib 파일만 공개하고 실행시 필요한 DLL 파일은 share에서 포함되어 있기 때문이다.

개발환경

Visual Studio에서 ffmpeg 라이브러릴 사용하기 위해서 프로젝트 속성설정에서 DEV 압축 풀린 폴더에서 include 및 DLL 파일을 설정한다.

프로젝트 속성->C/C++ –>일반 –> 추가 포함 디렉토리 –> DEV 압축 푼 폴더/include

그리고 관련 lib 연결 소스 코드 Stdafx.h에서 연결했다.

   1: #include "ffmpeg/include/libswscale/swscale.h"
   2: #include "ffmpeg/include/libavformat/avformat.h"
   3: #include "ffmpeg/include/libavformat/avio.h"
   4: #include "ffmpeg/include/libavcodec/avcodec.h"
   7:  
   8: #pragma comment( lib, "ffmpeg/lib/avcodec.lib")
   9: #pragma comment( lib, "ffmpeg/lib/avformat.lib")
  10: #pragma comment( lib, "ffmpeg/lib/avutil.lib")
  11: #pragma comment( lib, "ffmpeg/lib/swscale.lib")

영상 디코딩

FFMPEG을 사용하기 위해선 인코딩 이던 디코딩이던 라이브러리를 초기화가 필요하다. 전체 프로그램에서 한번만 호출 해 주면 된다.

avcodec_register_all();

예전 버전에는 avcodec_init(); 함수도 있었지만 버전업 되면서 이 함수가 없어졌다.

영상을 디코딩 하기 위해서

AVCodec 과 AVCodecContext 두 변수가 필요하다.

AVCodec은 어떤 코덱(MPEG, MJPEG, H264)인지를 나타내는 정보를 가지고 있으며

AVCodecContext는 스트림을 어떻게 디코딩 할 것인지 정보를 가지고 있다.

이 두 변수를 각각 전역이든 멤버 변수든 선언해 준다.

   1: AVCodec *                m_codec;
   2: AVCodecContext *        m_context;

그리고 각 변수를 초기화 해준다.

   1: m_codec = avcodec_find_decoder(CODEC_ID_H264);
   2: m_context = avcodec_alloc_context3(m_codec);
   3: avcodec_get_context_defaults(m_context);

avcodec_find_decoder(CodecID)

이 함수는 디코딩 코덱을 선택해서 m_codec을 초기화 해준다. CodecID는 avcodec.h에 정의되어 있다. 여기선 H264 코덱을 선택했다.

avcodec_alloc_context3(AVCodec*)

이 함수는 m_context 변수를 초기화 해주는 함수로 AVCodec 정보를 가지고 context를 메모리에 할당한다. 계속 FFMPEG이 버전업 되면서 끝에 숫자가 증가하는 것 같다. 이유? 나도 모른다. 검색해서 찾아 볼 여유가 없었다. ㅋㅋ

avcodec_get_context_defaults(AVCodecContext*)

context 변수를 초기화 하는 함수이다. 호출 안해도 상관 없다. 왜??? avcodec_alloc_context3에서 초기화가 이루어 졌으니까..

초기화가 되었으니까, 이제 어떤 영상 스트림을 디코딩 할것인지 직접 입력해 줘야 한다.

   1: m_context->width = 320;
   2: m_context->height = 240;
   3: m_context->codec_id = CODEC_ID_H264;
   4: m_context->codec_type = AVMEDIA_TYPE_VIDEO;
   5: m_context->flags2 |= CODEC_FLAG2_FAST;
   6: m_context->thread_count = 0;

 

width, height는 320, 240이고, 코덱은 H264, AVMEDIA_TYPE_VIDEO로 비디오를 디코딩 한다는걸 설정하고 CODEC_FLAG2_FAST는 그냥 샘플 코드에 있어서 넣어 봤다. 빼도 상관 없다. thread_count 몇개의 쓰레드로 디코딩 하는지 명시하는 부분인데, 나도 모르겠다. 저 값에 10을 넣으나 1을 넣으나 속도는 같은 것 같은데 왜 적용이 안 되는지 모르겠다. 0은 라이브러리가 알아서 쓰레드 만든다 라고 FFMPEG 문서에 있다. 이것도 제대로 되는지 모르겠다. 흠흠…

설정 정보 다 입력했으면 최종 FFMPEG 라이브러리를 설정해야 한다.

   1: avcodec_open2(m_context, m_codec);

요렇게만 하면 이제 디코딩 하기 위한 준비는 다 끝났다.

이제 스트림 데이터가 있는 변수를 ffmpeg 라이브러리에 넘겨 주고 디코딩 시키면 된다.

   1: AVPacket avpkt;
   2:  
   3:  
   4: av_init_packet(&avpkt);
   5:  
   6: avpkt.data = pEncodedData;
   7: avpkt.size = EncodedSize;

예전 버전에서는 스트림 데이터를 바로 이용했는데 어느 순간 버전업 되면서 AVPacket을 이용해야 했다. av_init_packet으로 초기화 해주고 data와 size에 스트림 데이터, 데이크 크기를 전달 한다.

   1: int got;
   2: int ret;
   3: AVFrame *m_picture;
   4:  
   5: m_picture = avcodec_alloc_frame();
   6:  
   7: ret = avcodec_decode_video2(m_context,m_picture, &got, &avpkt );
   8:  
   9: if( got > 0 )
  10: {
  11:   ...........
  12: }

 

깜박하고 디코딩시 디코딩 된 이미지 데이터를 저장할 변수를 초기화 하지 않았다. ^^ avcodec_alloc_frame()으로 이미지 변수를 할당 받는다.

avcodec_decode_video2(AVContext*, AVFrame*, int *, AVPacket *)

스트림 데이터를 이미지로 변환하는 함수다. 만약 이미지로 변환이 성공하면 got 변수에 0 보다 큰 값을 리턴한다.



영상 인코딩

초기화 기본은 디코딩과 같다. 다만 avcodec_find_decoder가 avcodec_find_encoder로 변경되었을 뿐이다.

   1: m_codec = avcodec_find_encoder(CODEC_ID_H264);
   2: m_context = avcodec_alloc_context3(m_codec);
   3: avcodec_get_context_defaults(m_context);

그리고 압축을 하기위한 설정을 context에 입력한다.

   1:  
   2: m_context->bit_rate = 512*1024;
   3: m_context->bit_rate_tolerance = 512*1024*2;
   4: m_context->width = 320;
   5: m_context->height = 240;
   6: m_context->time_base.num = 1;
   7: m_context->time_base.den = 24;
   8: m_context->gop_size = 24;
   9: m_context->profile = FF_PROFILE_H264_BASELINE;
  10: m_context->pix_fmt = PIX_FMT_YUV420P; 
  11:  
  12: av_opt_set(m_context->priv_data, "preset", "fast", 0);
  13: av_opt_set(m_context->priv_data, "profile", "baseline", 0);
  14:  
  15:  

압축될 영상은 320, 240 사이즈로 설정하고 비트레이트는 512kbps 로 설정했다. 또한 FPS 24, GOP는 24다. 그리고 압축을 H264로 하기 때문에 프로파일은 BASELINE. 예전 버전에서는 없었는데 버전업 되면서 생긴 av_opt_set 으로 preset, profile을 설정했다. 뭐 이렇게 써라고 하니 썼다… 정확하지는 않는다. 그냥 인터넷에서 긁어 모으다 보니~~ ㅋㅋㅋ

   1: AVFrame* m_picture;
   2:  
   3: m_picture = avcodec_alloc_frame();
   4:  

디코딩과 마찬가지로 인코딩도 AVFrame 으로 영상 데이터를 입력 받는다. 그래서 AVFrame을 할당 받는다.

인코딩은 보통 YUV420P 로만 입력 받는다. RGB24로 입력 해봤지만 계속 실패해서 성공해 본적이 없다. 버전업 되면서 지원 되는지 모르겠지만.. 예전에는 안 되었다. 그래서 영상 데이터를 YUV420P 로 변환 해 줘야 한다.

   1: struct SwsContext *    m_convert_ctx;
   2: AVPicture dstpic;
   3: AVPicture srcpic;
   4:  
   5: m_convert_ctx = sws_getContext( 320, 240, PIX_FMT_BGR24, 320, 240, PIX_FMT_YUV420P, SWS_FAST_BILINEAR | SWS_CPU_CAPS_MMX, NULL, NULL);
   6:  
   7: avpicture_fill(&dstpic, m_YUVImageData, PIX_FMT_YUV420P, 320, 240);
   8: avpicture_fill(&srcpic, m_RGBImageData, PIX_FMT_BGR24, 320, 240);
   9:  
  10: sws_scale( m_convert_ctx, srcpic.data, srcpic.linesize, 0, 240, dstpic.data, dstpic.linesize);
  11:  

변환을 위해서 sws_getContext() 함수를 이용한다. sws_getContext()함수로 변환 핸들을 얻은 후 이 핸들을 sws_scale()로 실질적인 변환 작업을 한다. sws_getContext는 소스의 가로, 세로, 파일포맷, 변환할 가로, 세로, 포맷을 지정한다. sws_scale 함수는 소스 AVPicture를 변환 AVPicture로 변환해 준다. ㅋㅋㅋ 뭔 소린지 ㅋㅋㅋ

BGR24를 YUV420P로 변환 하였다. 이제 인코딩 하면 된다.

   1: int encoded_size = 0;
   2: BYTE *ENCODED_DATA = 1024*1024;
   3: int ENCODED_SIZE = 1024*1024;
   4:  
   5: avpicture_fill((AVPicture*)m_picture, m_YUVImageData, 320, 240);
   6:  
   7: encoded_size = avcodec_encode_video(m_context, ENCODED_DATA, ENCODED_SIZE, m_picture);
   8:  

자 요렇게 인코딩 하면 된다.

 

 

'프로그래밍 > C | C++' 카테고리의 다른 글

UPNP 프로토콜  (0) 2013.08.21
클래스 private, protected, public 멤버 및 상속  (0) 2013.08.14
클래스 가상함수, 순수가상함수  (0) 2013.08.14
윈도우 malloc  (0) 2013.08.14
RTPLIB 1.0b 라이브러리 예제  (0) 2013.08.14

+ Recent posts