2011년 7월 8일 금요일

ffmpeg 비디오 디코더(decoder) API 사용법 - how to use ffmpeg video decoder API

#define FFMPEG_0_8   // ffmpeg-1.0 이전 버전

extern "C" {
#include "libavcodec\avcodec.h"
}

#define VIDEO_CODEC_H264 0
#define VIDEO_CODEC_MPEG4 1

// 코덱관련 변수들 선언
class VideoDecoder {
    AVCodec *m_pCodec;
    AVCodecContext *m_pCodecContext;
    AVFrame *m_pPicture;
    AVPacket m_avPacket;
...
};

static HANDLE hMutex;
static int g_numCPU = 1;

< 디코더 초기화 - initialize decoder >

void VideoDecoder::initCodec()
{
    static int init = 0;    // 코덱 초기화는 프로그램 돌때 한번만 해주면됨
    if (init == 0) {
        SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
g_numCPU = sysinfo.dwNumberOfProcessors;    // cpu 코어 갯수

#ifdef FFMPEG_0_8
avcodec_init();
#endif
avcodec_register_all();

        hMutex = CreateMutex(NULL, FALSE, NULL);
        init = 1;
    }
}

< 디코더 열기 - open decoder >

int VideoDecoder::openDecoder(int codec)
{
WaitForSingleObject(hMutex, INFINITE);    // 뮤텍스 처리 (thread-safe 하지않음)

av_init_packet(&m_avPacket);
if (codec == VIDEO_CODEC_H264)
m_pCodec = avcodec_find_decoder(CODEC_ID_H264); else if (codec == VIDEO_CODEC_MPEG4)
m_pCodec = avcodec_find_decoder(CODEC_ID_MPEG4);
else {
ReleaseMutex(hMutex);
return -1;
}

#ifdef FFMPEG_0_8
m_pCodecContext = avcodec_alloc_context();
#else
    m_pCodecContext = avcodec_alloc_context3(m_pCodec);
#endif

//m_pCodecContext->thread_type = FF_THREAD_FRAME; m_pCodecContext->thread_type = FF_THREAD_SLICE;
m_pCodecContext->thread_count = g_numCPU;

m_pPicture = avcodec_alloc_frame();

if (codec == VIDEO_CODEC_H264) // MPEG4('mp4v') 일때 영상깨짐 {
if (m_pCodec->capabilities&CODEC_CAP_TRUNCATED)
m_pCodecContext->flags |= CODEC_FLAG_TRUNCATED; }

#ifdef FFMPEG_0_8
if (avcodec_open(m_pCodecContext, m_pCodec) < 0) {
#else
    if (avcodec_open2(m_pCodecContext, m_pCodec, NULL) < 0)
#endif
  ReleaseMutex(hMutex);
  return -1;
 }

ReleaseMutex(hMutex);
    return 0;
}

< 디코더 닫기 - close decoder >

int VideoDecoder::closeDecoder()
{
WaitForSingleObject(hMutex, INFINITE);

if (m_pCodecContext)
  avcodec_close(m_pCodecContext);

av_free(m_pCodecContext);
av_free(m_pPicture);

ReleaseMutex(hMutex);

return 0;
}

< 디코더 사용 - decode >

int VideoDecoder::Decode(char *inBuf, int inLen)
{
int retLen, got_picture;


    char *buf = (char *)malloc(inLen+FF_INPUT_BUFFER_PADDING_SIZE);
    memset(&buf[inLen], 0, FF_INPUT_BUFFER_PADDING_SIZE);
    memcpy(buf, inBuf, inLen);
    m_avPacket.data = (uint8_t *)buf;
    m_avPacket.size = inLen;

retLen = avcodec_decode_video2(m_pCodecContext, m_pPicture, &got_picture, &m_avPacket);    // 디코딩 => m_pPicture->data[0], data[1], data[2] 이 각각 YCbCr 값을 가짐 (YUV420)
if (retLen < 0) {
  goto exit;
 }

if (!got_picture) {    // 한 프레임을 만들지 못함 -> 한 프레임을 잘라서 주지 못할경우 여기에 대한 처리가 필요
  goto exit;
}
...
exit:

    if (buf)
        free(buf);

    return retLen;
}

사용법은 클래스 생성후 initCodec()(프로그램 실행처음 한번만 하면됨) -> openDecoder() -> Decode() -> closeDecoder() 해주면 된다.

ffmpeg 디코더 라이브러리 => ffmpeg 디코더 참고소스

댓글 5개:

  1. 인코더 사용법도 알려주시면 안되나요? 굉장히 도움되네요!

    답글삭제
  2. 인코더는 ffmpeg 소스엔 없구요 보통 x264 소스를 이용해서 인코딩합니다. x264 소스에 예제가 있을거에요

    답글삭제
  3. 소스를 보고 참조 잘 하였습니다. 하지만 avcodec_decode_video2()후 결과값이 0값이 리턴되어 더 이상 진도가 나가지 않습니다. 어떻게 해결해야될까요?
    저는 IP카메라로부터 한프레임씩 인코딩될 데이터를 디코딩합니다...ffmpeg 52버전은 잘되는데 왜 53버전은 잘 안될까요? 예제를 똑같이 사용했는데...
    답변 부탁드립니다.

    답글삭제
  4. avcodec_decode_video2 리턴값이 0 이면 한 프레임을 디코딩하지 못했다는 의미인데 got_picture 값이 0 일거 같네요. got_picture 가 0 이면 인코딩된 한 프레임을 제대로 주지못했다는 의미입니다. 인코딩된 버퍼가 제대로된 한 프레임인지 확인해보세요. 인코딩된 버퍼를 그데로 파일로 저장해서 Elecard StreamEye 로 재생해보거나 Elecard Stream Analyzer 로 분석해보세요.

    답글삭제
  5. AAC 엔코딩에 PCM 샘플을 960을 넣었는데 성공하고 나서 디코딩해보면 1024가 나오고 뒤에 64샘플은 공백이 끼워져 있습니다. 왜그런지 혹시 짚이는데 있으신지요?

    답글삭제