2016년 2월 21일 일요일

ffmpeg 라이브러리를 활용하여 x264 규격으로 transcoding , live streaming server 제작 (encoding)

: 이미 디코딩 된 raw image를 encoding 하기 위해선, 필터링 ( 혹은 software scaling ) , 코덱 설정 등의 과정이 필요하다.

우선 코덱 format 을 지정할 컨텍스트와, 데이터를 받을 frame 과 packet 자료형이 필요하며, software scaling에 쓰일 자료형도 필요하다.

특이한 점은 raw 데이터를 받을 frame에 미리 buffer 를 연결해야 한다는 것 이다.

여기서 우리는 AVC 규격으로 인코딩 할 것임으로, x264 함수를 직접 작성하여 인코딩을 해보도록 한다. ( 원래는 ffmpeg 설치폴더에 libavutil/libx264.c에 작성되어있는 코드이지만, 필요한 부분만 발췌하여 사용하기 위해 따로 x264function.c를 만들었다. )

( 아래 예제 코드는 이전 게시물과 연동되어 사용됩니다. )


  • 자료형 선언
  AVCodecContext     *pCodecCtx = NULL;
  AVCodecContext     *pCodecCtxOut= NULL;
  AVFrame               *pFrameOut = NULL;
  AVPacket                packetOut;
  uint8_t                  *buffer = NULL;
  struct SwsContext    *sws_ctx = NULL;
  int                         frameFinished;
  int                         numBytes;
  int                         got_output;
  int                         frame_count;
  int                         i, videoStream;
  • 송출할 frame 과 buffer 를 연결한다.
  pFrameOut=av_frame_alloc();
  if(pFrameOut==NULL)
    return -1;
  // Determine required buffer size and allocate buffer
  numBytes=avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width,pCodecCtx->height);
  buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
  printf("Required buffer size : %d bytes\n",numBytes);
  avpicture_fill((AVPicture *)pFrameOut, buffer, AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height);
 : picture를 AVC 규격으로 저장할 것 이기 떄문에 YUV420P로 저장할것을 약속한다.
  • 인코딩에 필요한 codec context 를 구성한다.
pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!pCodec || pCodec==NULL) {
   fprintf(stderr, "Codec not found\n");
   exit(1);
pCodecCtxOut = avcodec_alloc_context3(pCodec);
if (!pCodecCtxOut || pCodecCtxOut==NULL) {
   fprintf(stderr, "Could not allocate video codec context\n");
   exit(1);
} // put sample parameters 
pCodecCtxOut->bit_rate = BIT_RATE;
pCodecCtxOut->width = WIDTH;
pCodecCtxOut->height = HEIGHT;
pCodecCtxOut->time_base = (AVRational){1,FPS};
pCodecCtxOut->gop_size = GOP_SIZE;
pCodecCtxOut->max_b_frames = 1;
pCodecCtxOut->pix_fmt = AV_PIX_FMT_YUV420P;
av_opt_set(pCodecCtxOut->priv_data, "preset", "slow", 0);
if(avcodec_open2(pCodecCtxOut, pCodec, NULL)<0) return -1;
pFrameOut->width = WIDTH;
pFrameOut->height = HEIGHT;
pFrameOut->format = pCodecCtxOut->pix_fmt;
ret = av_image_alloc(pFrameOut->data, pFrameOut->linesize, pCodecCtxOut->width,pCodecCtxOut->height,pCodecCtxOut->pix_fmt, 32);
if (ret < 0) {
fprintf(stderr, "Could not allocate raw picture buffer\n");
exit(1);  
}

코덱과 picture 를받을 frame 환경설정을 한다. BIT_RATE WIDTH HEIGHT FPS GOP_SIZE 등을 조절하여 AVC 의 프로필레벨을 조절할 수 있다.
( 필자는 800000, 176, 144, 15, 1로 하여 쓸만한 화질을 얻을 수 있었다.) 
  • X264 코덱 적용
X264_init(pCodecCtxOut);
: ffmpeg/libavcodec/libx264.c 에서 정의된 함수이다. 위에서 정의한 코덱 환경설정을 avc 코덱 규격에 맞게 적용시켜주는 역할을 한다. ( 인코딩 시 x264_frame 함수로 할 것 이기 때문에 규격을 맞춰주는것은 필수이다. )
  • software scaling 준비 ( 스케일러 환경설정 )
sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtxOut->width,
pCodecCtxOut->height,
pCodecCtxOut->pix_fmt,
SWS_FAST_BILINEAR,
NULL,
NULL,
NULL
);
: 소프트웨어 스케일링이란 일종의 필터링 같은 개념이다. 디코딩되어 얻은 raw image를 인코딩 규격에 맞게 변환시켜 주는 과정이다. ffmpeg 는 스케일링 기능을 포함한 필터링 기능을 지원하지만, 우선은 스케일링만으로 사용해본다.
 
  • 인코딩 

while(av_read_frame(pFormatCtx, &packet)>=0 )
{
  if(packet.stream_index==videoStream)
  {
   // Decode video frame
   avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
   av_init_packet(&packetOut);
   fflush(stdout);
   if(frameFinished)
   {
   pFrameOut->pts=frame_count;
   // software scaleling
   sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,pFrame->linesize, 0, pCodecCtx->height,pFrameOut->data, pFrameOut->linesize);
   // Encode video frame by x264
   ret = X264_frame(pCodecCtxOut, &packetOut, pFrameOut, &got_output);
   if (ret < 0) {
     fprintf(stderr, "Error encoding frame\n");
     exit(1);
   }
   if (got_output) {
      // ... 패킷 처리 ( 전송 or 파일쓰기 ) ... //
     }
   }
   frame_count++;
   //To avoid memory leak
   av_free_packet(&packet);
   av_init_packet(&packet);
   av_free_packet(&packetOut);
   }
}
: 흐름을 간단히 설명하면 , 파일 읽음 -> 디코딩 -> 스케일링 -> 인코딩 -> 반복 , 으로 구성된다. 지난 글에서 디코딩까지 진행했음으로 인코딩 부분만 적용시키면 된다. 이미지 컨데이너는 packet -> frame -> FrameOut -> packetOut 순으로 변경된다. 메모리 leak을 피하기 위해 반복문이 끝나면 항상 packet을 free 해준다. got_output은 packet을 통해 인코딩된 이미지가 정상적으로 받았다는것을 의미함으로, got_output을 확인하여 패킷단위로 데이터를 처리하면 될 것 이다.
int state = sendto(pkt_sock,pPacket,packetOut.size+2,0,(struct sockaddr*)&client_addr,sizeof(client_addr));
필자는 sendto 로 전송하여 실시간 스트리밍서버를 구성하였다.



댓글 1개:

  1. libx 라이브러리가 설치가 안되서 혹시 방법을 알 수 있을끼요...ㅠㅠ

    답글삭제