2011년 12월 22일 목요일

FFmpeg에 x264 인코더 사용방법

출처 : http://iamlow.tistory.com/entry/FFmpeg%EC%97%90-x264-%EC%9D%B8%EC%BD%94%EB%8D%94-%EC%82%AC%EC%9A%A9%EB%B0%A9%EB%B2%95

1. FFmpeg을 통하여 x264를 사용하고자 하였다.
   용도는 카메라 영상을 H.264 로 인코딩하여 실시간으로 전송하는데 사용하려고 한다.
   이에 알맞는 x264 코덱의 환경 설정 방법들 중 중요한 부분을 아래 적는다.
   - Baseline Profile을 사용(B Frame 사용안함)
     * x264는 Constrained baseline 만 지원하고 Baseline Profile는 지원 안한댄다..
       그래서 Constrained baseline을 사용하기로 했다. ffmpeg에서 baseline으로 설정하면 자동으로 Constrained baseline으로 설정된다.
   - Level 1.3을 사용
   - RC(Rate Control): ABR(Average Bit Rate)
   * Baseline Profile/Level 1.3 등의 정보는 H.264 로 검색하면 쉽게 찾을 수 있다.
   * RC와 관련된 사항은 x264로 검색하면 어렵지 않게 찾을 수 있다. 
      --> 참고 사이트: http://yg05123.blog.me/70042737774

2. x264 코덱은 설정항목이 엄청 많다.. 30개는 넘는 거 같다.
 --> 참고사이트
111 /**
112  * @remark reference sites for x264 setting
113  * http://vidcorea.net/lecture/2156
114  * http://avidemux.org/admWiki/doku.php?id=tutorial:h.264
115  * http://vidcorea.net/2146
116  * http://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping
117  * http://mewiki.project357.com/wiki/X264_Settings
118  */
   게다가 인터넷을 검색해보면 ffmpeg 실행파일에 command arguement를 사용하여 영상을 인코딩 하는 코드는 쉽게 찾을 수 있다.
   그러나 ffmpeg API를 사용하여 코드 상에서 설정하는 방법이 있으나 대부분 미흡한 부분이 많았다. (내가 못 찾았을 확률이 크다.)
   그래서 ffmpeg.c 파일을 분석했다.. 함수포인터에다가.. command arguement 파싱하는 부분들이 많아서 코드보기 무지하게 난해했다.
   가장 문제가 되었던 부분은 2 부분이다.
   1) profile 설정 ==> 설정하는 방법을 인터넷에서 못찾았다.
   2) bitrate 설정 ==> 설정하는 부분을 찾았으나 실제 테스트 해보니 정상동작 안했다.
   이 두 부분은 4번 항목에서 설명하고 3번항목에서 x264 간단한 설정 방법을 아래서 설명한다.


3. ffmpeg에서 x264 영상설정하기
  - ffmpeg 0.8.2 버전을 사용하였다.
  - 테스트 코드는 ffmpeg 소스를 받아보면 doc/example 디렉토리에 encoding_example.c 파일을 수정하면서 테스트 했다. (여기 예제로만 x264 영상설정.... 택도없다...)
  - 일단 H.264 코드를 찾는다.
    codec = avcodec_find_encoder(CODEC_ID_H264);
  - 찾은 후 AVCodecContext 구조체에 영상 설정을 해야 한다.
    AVCodecContext *c = avcodec_alloc_context();
    이 구조체에 영상설정과 관련된 항목이 모여있다. x264 뿐만 아니라 다른 코덱도 동일하게 이 구조체를 사용한다.( 공통적으로 사용하려다 보니.. 변수명들이 x264에 어느 변수들이랑 매핑되는지 찾기가 좀 힘들다. libavcodec/libx264.c 파일에 X264_init함수보면 대충 알 수 있다.)
  - AVCodecContext 구조체에는 엄청나게 많은 필드가 있으나, 실제 사용을 위해 설정할 필드는 몇 개 안된다. 내가 사용한 필드 들은 아래와 같다.
    coder_type ==> CAVLC or CABAC 엔트로피 모드를 설정한다.(참고로 baseline profile은 CAVLC만 지원한다.)
    gop_size ==> group of picture 이다. 자세한건 인터넷 검색...
    bitrate ==> 말그대로 bitrate
    qmin ==> quality min~~ 영상화질의 범위의 최소값을 지정한다.
    qmax  ==> quality max ~~ 영상화질의 범위의 최대값을 지정한다.
    max_qdiff ==> quality 값이 변화량을 지정한다. 즉 여기에 정해진 값 단위로 증가되거나 감소된다는 뜻이다. 예를 들어 현재 q값이 10이면 14, 18이런식으로 증가되거나 감소된다는 말이다.
    pix_fmt ==> pixel format을 말한다.. RGB, YUV 이런거 말이다.
    width, height ==> 해상도다..
    time_base.num ==> 프레임레이트 설정할 때 쓰는데 그냥 1쓰면된다.
    time_base.den ==> 프레임레이트 넣으면 된다. 25프레임이면 25 넣으면 된다.

내가 코드에 설정한 값들은 아래와 같다.

 200         c->coder_type                   = FF_CODER_TYPE_VLC;
 201 //      c->refs                         = 3;
 202         c->flags                        |= CODEC_FLAG_LOOP_FILTER;
 203         c->deblockalpha                 = 0;
 204         c->deblockbeta                  = 0;
 205         c->partitions                   = X264_PART_I4X4
 206                                         | X264_PART_P8X8
 207                                         | X264_PART_B8X8;
 208 //      c->me_method                    = ME_HEX; 
 209 //      c->me_subpel_quality            = 7; 
 210 //      c->flags2                       |= CODEC_FLAG2_PSY;
 211 //      c->psy_rd                       = 1.0;
 212 //      c->psy_trellis                  = 0.0;
 213 //      c->flags2                       |= CODEC_FLAG2_MIXED_REFS;
 214 //      c->me_range                     = 16; 
 215 //      c->me_cmp                       = FF_CMP_CHROMA;
 216 //      c->trellis                      = 0;    // 0: speed up!
 217 //      c->flags2                       &= ~CODEC_FLAG2_8X8DCT;
 218 //      c->flags2                       |= CODEC_FLAG2_FASTPSKIP;
 219 //      c->chromaoffset                 = -2;
 220 //      c->thread_count                 = 1;
 221 //      c->slices                       = 0;
 222 //      c->noise_reduction              = 0;
 223 //      c->flags                        |= CODEC_FLAG_INTERLACED_DCT;
 224 //      c->max_b_frames                 = 0;
 225 //      c->weighted_p_pred              = 0;
 226         c->gop_size                     = 25;
 227 //      c->keyint_min                   = 25;
 228 //      c->scenechange_threshold        = 40;   
 229 //      c->flags2                       &= ~CODEC_FLAG2_INTRA_REFRESH;
 230 //      c->rc_lookahead                 = 40;
 231 //      c->crf                          = 23.0; // ABR
 232 //      c->cqp                          = -1;   // ABR
 233 //      c->flags2                       |= CODEC_FLAG2_MBTREE;
 234         c->bit_rate                     = 10 * 1000 * 1000;
 235 //      c->bit_rate_tolerance           = 1.0;  //not config
 236 //      c->qcompress                    = 0.6; 
 237         c->qmin                         = 10;
 238         c->qmax                         = 51;
 239         c->max_qdiff                    = 4;
 240 //      c->i_quant_factor               = 10/14; // i_qfactor=0.71
 241 //      c->aq_mode                      = 1;
 242 //      c->aq_strength                  = 1.00;
 243 
 244 //      c->rc_max_rate                  = 200 * 1000;
 245 //      c->rc_min_rate                  = 300 * 1000;
 246 //      c->rc_buffer_size               = c->rc_max_rate * 100;
 247 //      c->flags                        |= CODEC_FLAG_GLOBAL_HEADER;
 248 //      c->flags                        |= CODEC_FLAG_PASS1;
 249 
 250         /* */
 251         c->pix_fmt                      = PIX_FMT_YUV420P;
 252         c->width                        = 384; // 320; //176;
 253         c->height                       = 288; //240; // 144;
 254         c->time_base.num                = 1;
 255         c->time_base.den                = 25; //15;


이렇게만 설정하고 예제 프로그램을 돌렸다.
out_size = avcodec_encode_video(c, outbuf, outbuf_size, picture);
테스트 프로그램은 동작은 하는데 인코딩 되어 나오는 데이터가 0바이트라고 나온다.
뭐지?? 이런..
근데 인코딩 함수를 호출하는 루프문이 있는데.. 루프문을 더 오래 돌려보니까 인코딩된 데이터가 나온다. 예제 소스를 자세히 보면 알겠지만. 인코더에 YUV영상을 넣으면 인코딩된 영상이 바로 나오는 것이 아니라 나중에 호출한 함수에서 이전에 입력했던 YUV영상이 나온다. 그래서 예제 소스를 자세히 보면 아래와 같은 부분이 있다.

 923     /* get the delayed frames */
 924     for(out_size = 1; out_size; i++) {
 925         fflush(stdout);
 926 
 927         out_size = avcodec_encode_video(c, outbuf, outbuf_size, NULL);
 928         printf("write frame %3d (size=%5d)\n", i, out_size);
 929         fwrite(outbuf, 1, out_size, f);
 930         total_size += out_size;
 931     }
 932 

이 부분이 딜레이 된 인코딩 영상이 나오는 것을 처리한다.

일단 영상은 나온다. 그런데...

문제점이 있는게.. 난 baseline profile을 서야하는데 x264가 계속 Main Profile로 설정되는 것이다.
그리고 비트레이트 컨트롤이 안된다. 아까 앞서 말한 2 가지 문제점이다.

4. 두가지 문제점을 나누어 설명한다.

1) Baseline Profile 설정하기
  - 일단 설정하는 코드는 아래와 같다.

 306     c->priv_data = alloc_priv_context(codec->priv_data_size, codec->priv_class);
 307     int flags = AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM;
 308     if (av_find_opt(c->priv_data, "profile", NULL, flags, flags)) {
 309             if (av_set_string3(c->priv_data, "profile", "baseline", 1, NULL) < 0) {
 310                     fprintf(stderr, "Invalid value '%s' for option '%s'\n",
 311                                     "profile", "baseline");
 312                     exit(1);
 313             }
 314     }

 - 이 코드를 cmdutils.c 에서 찾았다. 인터넷 아무리 뒤져도 이렇게 쓰라는 설명 없다..ㅠㅠ
 - 알고나니 별거 없다.
 - 309번 라인 처럼 문자열을 설정하면 x264 라이브러리에 설정된다.
 - 원하는 프로파일을 그냥 문자열로 쓰면 설정할 수 있다.

2) Bitrate 설정하기
  - 프로그램을 실행하면 아래와 같은 경고 메시지가 뜬다.
    [libx264 @ 0x8455020] non-strictly-monotonic PTS
  
- 이것을 설정하는 방법은 인터넷에 나와 있다.
    ==> http://lists.libav.org/pipermail/libav-user/2011-February/005995.html
  - 근데 이대로 하면 안된다. 다른 곳 찾아봐도 안나온다.
  - 그래서 정상동작하는 ffmpeg 라이브러리에 이 값을 어떻게 쓰는지 로그를 찍어보았다. 근데.. 허무하다.. 그냥 1씩 증가 시켜써 썼다.. 그래서 나도 picture->pts = i++; 이렇게 쓰니.. bitrate 제어가 된다. 허무하다..

위 사항을 모두 반영하니 영상 설정도 된다.
단 위 사항들은 경험적으로 얻은 방법이기에 올바른 사용법이 아닐 확률이 높다.
참고만 합시다..!!

* 첨부파일로 내가 사용한 지저분한 테스트 프로그램을 올린다.
   나중에 정리가 되면 좀 더 깨끗한 코드를 올릴 수도 있겠지만..
   보는데 별 무리 없을 듯..




5. 이건 테스트 시 참고하세요.
  - ffmpeg 프로그램이나 x264 프로그램을 을 실행하여 인코딩 할 때 보면 예제로 사용하는 영상 타입이 y4m이라는 확장자를 사용하는데, 이 예제 영상이 필요하시면 여기서 다운받아 쓰세요.
 예제 사이트: http://samples.mplayerhq.hu/yuv4mpeg2/
  - ffmpeg API를 통하여 설정한 항목이 x264 라이브러리에 정상적으로 설정이 되는지를 확인하고 싶을 때 아래처럼 추가해서 테스트 하세요.
    (AVCodecContext *)c->flags                        |= CODEC_FLAG_GLOBAL_HEADER;
    그러면 libx264에서 설정된 옵션을 화면에 텍스트로 뿌려 줍니다.

[libx264 @ 0x974f020] 264 - core 116 r2057 0ba8a9c - H.264/MPEG-4 AVC codec - Copyleft 2003-2011 - http://www.videolan.org/x264.html - options: cabac=0 ref=1 deblock=1:0:0 analyse=0x1:0x111 me=dia subme=8 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=4 chroma_me=0 trellis=0 8x8dct=0 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=25 keyint_min=13 scenecut=0 intra_refresh=0 rc_lookahead=25 rc=abr mbtree=1 bitrate=10000 ratetol=1.0 qcomp=0.50 qpmin=10 qpmax=51 qpstep=4 ip_ratio=1.25 aq=1:1.00

  H.264 영상은 SPS, PPS 정보를 가지고 있는데, 이 정보는 avcodec_encode_video() 함수를 맨 처음 호출할 때 인코딩된 영상데이터 제일 앞에 포함되어 있다.
  그러나 SDP를 사용하거나 기타 등등 의 경우 SPS, PPS 정보를 따로 추출하여 사용해야 할 경우가 있다. 이럴 때 CODEC_FLAG_GLOBAL_HEADER 옵션을 사용하면 된다.
  이 옵션을 설정한 다음에 avcodec_encode_video() 함수를 호출하면 SPS, PPS 정보가 들어있지 않게 된다. 따라서 아래 방법을 통하여 SPS, PPS 정보를 얻어야 한다.

206         /* open codec */
207         ret = avcodec_open(f->ctx, codec);
208         assert(!ret, "avcodec_open is failed!");
209
210         /* get sps, pps */
211 #if 0
212         trace_sprop(f->ctx);
213 #endif
214         f->extradata_size = f->ctx->extradata_size;
215         memcpy(f->extradata, f->ctx->extradata, f->ctx->extradata_size);

  AVCodecContext 구조체 안에 extradata_size에는 SPS, PPS 크기정보가 들어있고, 실제 데이터는 extradata에 들어있다. 이 값은 avcodec_open() 호출한 후에 얻을 수 있다.

6. x264 라이브러리는 기본설정으로 SPS, PPS가 주기적으로 나온다.. 참고하시길....
    주기적으로 나오는 것을 설정하는 방법은 모름.

7. x264에 보면 아래와 같은 SEI 정보를 생성하여 인코딩된 영상에 들어가게 된다.

 264 - core 116 r2057 0ba8a9c - H.264/MPEG-4 AVC codec - Copyleft 2003-2011 -http://www.videolan.org/x264.html - options: cabac=0 ref=1 deblock=1:0:0 analyse=0x1:0x111 me=dia subme=8 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=4 chroma_me=0 trellis=0 8x8dct=0 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=25 keyint_min=13 scenecut=0 intra_refresh=0 rc_lookahead=25 rc=abr mbtree=1 bitrate=10000 ratetol=1.0 qcomp=0.50 qpmin=10 qpmax=51 qpstep=4 ip_ratio=1.25 aq=1:1.00

  - SEI 라는 것은 사용자가 독자적으로 정의하는 정보인데 x264가 이러한 정보를 넣는다.
  테스트 결과 이 정보가 없어도 인코딩된 영상을 디코딩하는데 아무런 문제가 없다.
  x264 API를 통하여서는 이 정보를 제거할 수는 없는 것 같다.
  아래는 실제 설정하는 코드가 위치하는 곳이다.
         
int x264_encoder_headers( x264_t *h, x264_nal_t **pp_nal, int *pi_nal )
{
...
 /* identify ourselves */                             
 x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );
 if( x264_sei_version_write( h, &h->out.bs ) )        
     return -1;                                       
 if( x264_nal_end( h ) )                              
     return -1;                                       
 ...
}                                                 
                                                      
  - FFmpeg에서는 libavcodec/libx264.c 라는 파일에서 x264와의 I/F를 정의하고 있다. 아래는 코드다

    avctx->extradata      = av_malloc(s);                                        
    avctx->extradata_size = encode_nals(avctx, avctx->extradata, s, nal, nnal, 1);

    마지막 인자의 값을 1 을 설정하면 avcodec_encode_video() 함수 호출 시에 SEI 정보가 나오고, 0으로 설정하면, extradata안에 SEI 정보가 들어있게 된다.

댓글 없음:

댓글 쓰기