FFMPEG 이용해 decoding 하기.
FFMPEG 이용해 사용자가 임의로 만든 read 함수를 통해 h.264 file을 decoding하는 방법을 정리합니다.
1. 우선 초기화가 필요하겠지요.
코드는 아래와 같음.
// Init
av_register_all();
// Init to play streaming contents, if you want to play or decode media from internet, must be called this function.
// avformat_network_init();
// Create internal Buffer for FFmpeg:
const int iBufSize = 32 * 1024;
unsigned char* pBuffer = new unsigned char[iBufSize];
// Allocate the AVIOContext:
// The fourth parameter (pStream) is a user parameter which will be passed to our callback functions
AVIOContext* pIOCtx = avio_alloc_context(
pBuffer, iBufSize, // internal Buffer and its size
0, // bWriteable (1=true,0=false)
this, // user data ; will be passed to our callback functions
ReadFunc,
NULL, // Write callback function (not used in this example)
NULL //SeekFunc
);
// Allocate the AVFormatContext:
AVFormatContext* pCtx = avformat_alloc_context();
AVInputFormat *pInputFormat = av_find_input_format("H264");
// Set the IOContext:
pCtx->pb = pIOCtx;
pCtx->flags = AVFMT_FLAG_CUSTOM_IO; // the flag that ffmpeg
pCtx->iformat = pInputFormat;
int nResult = 0;
AVCodecContext *pCodecCtx;
nResult = avformat_open_input(&pCtx, "", pInputFormat, NULL);
if (nResult < 0)
{
av_log(NULL, 0, "failed to open");
}
else
{
nResult = av_find_stream_info(pCtx);
if (nResult < 0)
{
return;
}
else
{
if (pCtx->nb_streams < 1)
{
av_log(NULL, 0, "error");
return;
}
pCodecCtx = pCtx->streams[0]->codec;
}
}
AVCodec *pCodec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (!pCodec)
{
return;
}
/* Open codec */
nResult = avcodec_open2(pCodecCtx, pCodec, NULL);
if (nResult < 0)
{
return;
}
av_log(NULL, 0, "Opened input stream\n");
ReadFunc을 파라미터로 넣은 avio_alloc_context 함수를 호출하여 AVIOContext context를 얻고
AVFormatContext를 할당하고는 각 멤버들을 채워줍니다.
avformat_open_input() 라는 함수를 호출면서 AVFormatContext instance와 몇몇 추가 데이터를 함께 넘깁니다.
성공할 경우 file의 codec 정보를 얻어올 수 있고 각 정보에서 id를 얻어 codec context를 얻어와
최종적으로 avcodec_open2() 함수를 호출합니다
이렇게 되면 실제 codec를 이용해 file을 처리할 준비 상태가 되네요.
추가로 ReadFunc 의 코드는 아래와 같습니다.
ptr은 avio_alloc_context() 함수 호출 시 전달된 parameter 중 (위의 코드에서는 this)에 해당하는 user data 입니다. user data를 설정하면 그 값이 ReadFunc으로 전달됩니다.
그리고 buf는 읽을 buffer의 주소, buf_size는 읽고자 하는 data size가 됩니다.
혹 memory로 부터 읽어야 한다면 실제 fread함수 말고 임의로 정의한 메모리를 파라미터로 받은 buf에 복사해서 넘겨주는 방식을 취하셔도 됩니다.
int ReadFunc(void* ptr, uint8_t* buf, int buf_size)
{
FILE* pFile = NULL;
pFile = reinterpret_cast<FILE*>(ptr);
return fread(buf, 1, buf_size, hFile);
}
기본적으로 decoding 시 순서는 다음과 같습니다.
1. 스트림으로 부터 packet 읽기
2. 읽은 packet decode 하기 (H.264의 경우 YUV 형태로 decode 된다)
3. 원하는 형태로 변환하기
그럼 예제 코드를 좀 보자.
AVStream *pStream;
AVPacket packet;
int result;
while (...)
{
result = av_read_frame(m_pFC, &packet);
if (result < 0)
{
continue;
}
pStream = m_pFC->streams[packet.stream_index];
if (AVMEDIA_TYPE_VIDEO == pStream->codec->codec_type)
{
// insert packet to Q
}
else
{
av_free_packet(&packet);
}
}
m_pFC 는 open 과정에서 얻는 AVFormatContext를 가리키는 pointer이고
av_read_frame()이라는 함수를 호출하는 것을 볼 수 있습니다.
위 함수를 호출하면 stream으로 부터 packet을 추출합니다.
packet은 video나 audio의 하나의 chunk라 보면 될 것 같습니다.
H.264 video의 경우 I, P, B frame으로 구성되어 있는데 각 packet은 decode되지 않은 I, P, B frame data입니다.
각 packet을 decode하면 실제 각 packet이 어떤 형태의 frame이였는지 확인 가능합니다.
자.. 그럼 decode part를 보죠.
int len = 0;AVCodecContext* pCC = m_pFC->streams[0]->codec;AVFrame* pFrame = av_frame_alloc();int got_frame = 0;AVPacket packet; while (...){ ( Get a packet from Q )
if (AVMEDIA_TYPE_VIDEO == pCC->codec_type) { len = avcodec_decode_video2(pCC, pFrame, &got_frame, &packet); if (len <= 0) { continue; }
if (got_frame) { (process decoded frame) } }
av_free_packet(&packet);}
av_frame_free(&pFrame);
codec type을 확인하고 video가 있으면 video decode를 위해
avcodec_decode_video2()함수를 호출합니다.
실제 이 함수가 video를 decode하기 위한 함수이고
함수의 parameter로
AVCodecContext*, AVFrame*, int*, AVPacket* type의 4개 파라미터가 전달됩니다.
packet을 decode해 실제 pFrame이라는 놈에게 그 data를 저장해 주죠
그리고 잘 저장되었으면 got_frame이라는 변수가 0이 아닌 다른 값이 설정됩니다.
pFrame은 미리 할당 받은 후 사용되어야합니다.
사용이 완전히 끝난 후에는 반드시 av_frame_free()함수로 free해야죠. 아니면 memory leak
마지막으로 option으로 변환은 어떻게 하느냐..
int Decode(...)
{
...
// change format
{
AVPicture pict;
AVFrame* pConvertedFrm;
pConvertedFrm = ... // alloc AVFrame
if (pConvertedFrm)
{
pict.data[0] = pConvertedFrm->data[0];
pict.data[1] = pConvertedFrm->data[2];
pict.data[2] = pConvertedFrm->data[1];
pict.linesize[0] = pConvertedFrm->linesize[0];
pict.linesize[1] = pConvertedFrm->linesize[2];
pict.linesize[2] = pConvertedFrm->linesize[1];
// convert
ConvertFrame(pCC, &pict, format, pFrame);
}
}
...
}
int ConvertFrame(AVCodecContext *pCC, AVPicture *pict, AVPixelFormat format, AVFrame *pFrame)
{
struct SwsContext *pSwsC;
pSwsC = sws_getContext(
pCC->width, pCC->height, pCC->pix_fmt,
pCC->width, pCC->height, format,
SWS_BICUBIC, NULL, NULL, NULL
);
if (pSwsC == NULL)
{
return -1;
}
if (pict)
{
sws_scale(pSwsC,
pFrame->data, pFrame->linesize,
0, pCC->height,
pict->data, pict->linesize
);
}
sws_freeContext(pSwsC);
return 0;
}
2개의 함수로 분리해 놨지만
중요한 부분은 두번째 ConvertFrame 함수쪽이다.
ffmpeg에서는 file format 변환 시 swscale 라이브러리에 있는 sws_scale이라는 함수를 사용한다.
이 함수는 말그대로 scale을 변경하는 것 처럼 보이지만 format도 변환 시킬 수 있다.
우선 sws_getContext()로 원본 이미지의 폭, 너비, format을, 그리고 변환하고자 하는 width, height, format을 설정
그리고 보간법은 어떤 것을 사용할지에 대해서도 설정한다. 나머지 파라미터는 filter와 관련된 것들이라 생략한다.
그냥 NULL로 채워주면 됩니다. 필요하다면 각 type에 맞게 filter 정의해서 넣어주면 됩니다.
암튼
그후 정상적으로 context 가져오면 sws_scale함수를 호출합니다.
pFrame->data는 바꾸고자 하는 frame data의 배열의 포인터고
pFrame->linesize는 바꾸고자 하는 frame의 각 plane별 linesize입니다.
(linesize는 아래에서 정리합니다)
이러한 과정을 통해 stream으로부터 각 frame을 decode하고 변환하여
영상을 화면에 뿌리거나 스피커를 통해 오디오를 출력할 수 있습니다.
오디오는 여기서 생략했지만 같은 비슷한 방식을 통해 구현할 수 있습니다.
/**
* Allocate and return an SwsContext. You need it to perform
* scaling/conversion operations using sws_scale().
*
* @param srcW the width of the source image
* @param srcH the height of the source image
* @param srcFormat the source image format
* @param dstW the width of the destination image
* @param dstH the height of the destination image
* @param dstFormat the destination image format
* @param flags specify which algorithm and options to use for rescaling
* @return a pointer to an allocated context, or NULL in case of error
* @note this function is to be removed after a saner alternative is
* written
* @deprecated Use sws_getCachedContext() instead.
*/
struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
/**
* Scale the image slice in srcSlice and put the resulting scaled
* slice in the image in dst. A slice is a sequence of consecutive
* rows in an image.
*
* Slices have to be provided in sequential order, either in
* top-bottom or bottom-top order. If slices are provided in
* non-sequential order the behavior of the function is undefined.
*
* @param c the scaling context previously created with
* sws_getContext()
* @param srcSlice the array containing the pointers to the planes of
* the source slice
* @param srcStride the array containing the strides for each plane of
* the source image
* @param srcSliceY the position in the source image of the slice to
* process, that is the number (counted starting from
* zero) in the image of the first row of the slice
* @param srcSliceH the height of the source slice, that is the number
* of rows in the slice
* @param dst the array containing the pointers to the planes of
* the destination image
* @param dstStride the array containing the strides for each plane of
* the destination image
* @return the height of the output slice
*/
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);
* Linesize ?
이미지.. 예를 들면 흔히 보는 bmp 구조를 보죠
우리가 흔히 1920x1200, 1024x768 등 여러 해상도 혹은 크기의 이미지를 볼 수 있습니다.
여기서 기본 단위는 pixel이라는 것이죠.
다시 말해 pixel이라는 놈이 가로로 1920개, 새로로 1200개 있다고 해도 무방하겠습니다.
그럼 pixel은 무엇이냐?
pixel은 하나의 색을 화면에 표현하는 하나의 단위입니다.
pixel은 경우에 따라 1byte, 2byte, 3byte, 4byte 등 여러 크기가 될 수 있습니다.
그럼 실제 각 크기에 따라 이미지의 실 data크기는 어떻게 정해질까요?
예를 들면 1920 * 1200 size의 1 pixel = 4 byte 라 한다면
1920 * 1200 * 4 가 실제 data가 크기가 됩니다.
실제 의미 상으로는 (1920 * 4) * 1200 이 되죠.
무슨 말이냐.
00 00 00 00 11 11 11 11 22 22 22 22 33 33 33 33 ...
각 4byte가 하나의 pixel로 이루어 진다는 말이죠.
이렇게 해서 총 1920 * 4 byte가 실제 line size가 됩니다.
linesize라는 말은
마치 pixel 수를 적용한 커다란 배열에서
행의 크기를 말합니다.
그런데 여기서 중요한 것이 하나 더 있습니다.
bitmap의 경우 1 pixel이 2, 3byte라면 linesize가 어떻게 될까요?
그냥 1920 * 3 * 1200 이렇게 계산하면 되느냐..
사실 이렇게 처리하지 않습니다. ^^;;
왜?? 대중적이던 컴퓨터가 32bit 그러니까 4byte 단위로 구성되어 있어서 계산시 편의와 빠른 계산을 위해
4byte로 정렬을 시켜놨습니다.
그래서
보통 size 검출 시에는
((int)((width*bpps/8+3)&~3))
이런 식의 macro로를 사용하기도 합니다.
width * bpp (bits per pixel) 에 3을 더하고
~3 (0xFFFFFFFC : 3에 대한 보수) 값으로 & 를 하면
결국 뭐냐.. 항상 하위 두 bit는 '00'이 되므로
linesize를 width * bpp 보다 큰 4의 배수로 만들겠다는 것입니다.
'Multimedia > FFmpeg' 카테고리의 다른 글
FFMPEG : get duration (0) | 2014.04.04 |
---|