우선 코덱 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();: picture를 AVC 규격으로 저장할 것 이기 떄문에 YUV420P로 저장할것을 약속한다.
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);
- 인코딩에 필요한 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 로 전송하여 실시간 스트리밍서버를 구성하였다.
libx 라이브러리가 설치가 안되서 혹시 방법을 알 수 있을끼요...ㅠㅠ
답글삭제