2016년 2월 18일 목요일

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


라즈베리 파이에 ffmpeg을 활용해 라이브 스트리밍 서버를 탑재하면서 배웠던 지식을 글로 남기고자 작성한다. 서버는 단순 udp서버임으로 생략하기로 하고, ffmpeg 라이브러리를 활용하여 decoding 하고 encoding 하는 과정을 소개해 보기로 한다.


환경


grep . /etc/*-release

PRETTY_NAME="Raspbian GNU/Linux 7 (wheezy)"
NAME="Raspbian GNU/Linux"
VERSION_ID="7"
VERSION="7 (wheezy)"
ID=raspbian
ID_LIKE=debian
ANSI_COLOR="1;31"
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

라즈베리 파이 2로 작업하였다. 

update 시 공개키 문제가 있어서 

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys
(or)
wget http://archive.raspberrypi.org/debian/raspberrypi.gpg.key
apt-key add raspberrypi.gpg.key
이렇게 해결하였다.

구성요소 : 라즈베리 파이2, 내부망, Usb camera
설치 라이브러리 : ffmpeg
ffmpeg 설치가이드 :
https://www.bitpi.co/2015/08/19/how-to-compile-ffmpeg-on-a-raspberry-pi/
꼭 x264 라이브러리도 함께 설치해 주길 바란다. 


  • 관련 헤더파일
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/common.h>
#include <libavutil/avutil.h>
#include <libavfilter/avfilter.h>
#include <libavutil/eval.h>
#include <libavutil/opt.h>
#include <libavutil/mem.h>
#include <libavutil/pixdesc.h>
#include <libavutil/stereo3d.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/pixfmt.h>

ffmpeg 라이브러리를 사용하여 /dev/video0 이미지를 받아서 디코딩 해보자


  • 가장 기본적으로 필요한 자료구조를 선언한다
AVFormatContext   *pFormatCtx = NULL;
AVCodecContext    *pCodecCtxOrig = NULL;
AVCodec              *pCodec = NULL;
AVFrame              *pFrame = av_frame_alloc();
AVPacket               packet;
 각각이 어떤역할을 하는지 대략적으로 알아야한다. 다음 예제를 따라가며 짐작해보길 바란다.  
  • 등록을 수행한다. 
  av_register_all();
  avcodec_register_all();
  avdevice_register_all();
  • /dev/video 에서 받기위한 작업
  AVInputFormat *inputFormat = av_find_input_format("v4l2");
  AVDictionary *options = NULL;
  av_dict_set(&options,"framerate","25",0);
  int ret;
  ret = avformat_open_input(&pFormatCtx,"/dev/video0",inputFormat,&options);
  if(ret != 0)
        exit(1);
   이부분에서 ret 값이 -16이 되면서 실행이 안되는 경우가 있는데, 이는 usb camera가 이미 점유당하고 있다는 뜻이다. 꺼져있는게 분명한데 점유가 되있는 상황이 있는 경우 재량껏 해결하시길 바란다. ( dev/video0 권한을 775 로 수정하는것이 도움이 되었지만 완벽히 해결해주진 않았다. )
  • 스트림 정보를 추출
  if(avformat_find_stream_info(pFormatCtx, NULL)<0) {
    printf("Couldn't find stream information\n");
      return -1;
  }
  • 첫번째 비디오 스트림을 찾는다.
  int videoStream=-1;
  for(int i=0; i<pFormatCtx->nb_streams; i++)
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
      videoStream=i;
      break;
    }
  • 비디오 스트림에서 디코딩에 필요한 코덱 정보를 찾아낸다.
  pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
  pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if(pCodec==NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }
  • 해당 코덱을 받아오자.
   pCodecCtx = avcodec_alloc_context3(pCodec);
  if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1;
  }
  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
    return -1;
  •  이제 디코딩을 시작하지
  while(av_read_frame(pFormatCtx, &packet)>=0 )//&& frame_count<=100 )
  {
    if(packet.stream_index==videoStream)
    {
      // Decode video frame
      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
/... Raw image in packet 처리 ( ex. 파일에 쓴다던지 ).../ 
    }
      av_free_packet(&packet); // memory leak을 피하기 위해
      av_init_packet(&packet); 
}
 Read 할 경우 pFormatCtx 의 정보를 바탕으로 packet 에 데이터를 넣는것을 추측할 수 있다. 이때 받는 이미지는 raw 이미지가 아니고 카메라 자체에서 나름대로 인코딩된 규격을 받아온다. ( 콘솔창에서 정보를 확인할 수 있다. 카메라마다 다르기때문에 일정한 규격으로 전송하기 위해선 transcoding 이 필요하다. )
 packet 으로 읽은 데이터를 pCodecCtx 정보를 바탕으로 pFrame에 디코딩 시킨다. 이때 받아온 데이터는 raw 이미지임을 알 수 있다. 디코딩 직후 frame에 남아있는 raw image data를 확인하고 싶다면 파일에 써서 보길 바란다.

  • 빠짐없이 free 해준다.
  av_frame_free(&pFrame);
  avcodec_close(pCodecCtxOrig);
  avformat_close_input(&pFormatCtx);
  av_free(pCodecCtxOrig);

댓글 없음:

댓글 쓰기