From http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html
Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
2004년 2월 18일
Update (2004/7/22) : 내가 최초로 제공한 코드가 메모리 누수현상을(av_free_packet()을 실행하지 않는) 가지고 있음을 발견했다. 나는 답례로 누수현상을 제거한 코드와 데모 프로그램으로 갱신했다.
Update (2004/7/21) : ffmpeg(0.4.9-pre1)를 새로게 prelease했다. 나는 libavformat/libavcodec API의 변경사항을 기술한다.
libavformat과 libavcodec 라이브러리는 (ffmpeg에 부수적으로 따르는) 아주 다양한 비디오 파일 포멧을 다루는 가장 좋은 방법이다. 불행이도, 여러분의 프로그램에서 이 라이브러리들을 사용할때(적어도 나는 찾을 수가 없었다.) 실질적인 문서가 존재한지 않는다. 그리고 예제 프로그램들도 정말로 도움이 되지 않는다.
이러한 상황으로 내가 최근 프로젝트에서 libavformat/libavcodec을 이용했을때 사용방법을 찾아내기 위해서 아주 많은 시행 착오를 거쳤다. 여기의 내용은 내가 배운 것들이다. - 부디 다른 이들이 똑같은 trial-and-error의 과정을 거치는 것으로 부터 도움이 되길 바란다. 또한 여러분들이 다운로드할 수 있는 작은 데모프로그램들도 있다. 이 코드는 ffmpeg 의 0.4.8 (내가 이글을 작성할 당시의 가장 최근버전인)버전에 포함된 libavformat/libavcodec으로 한 작업을 제공한다. 만약 여러분들이 이 코드를 break하는 이후의 버전을 찾는 다면, 나에게 알려주길 바란다.
이 문서에서 나는 단지 파일에서 비디오 스트림을 읽는 방법을 다룰것이다.: 오디오 스트림은 아주 많이 동일한 방법으로 동작한다. 그러나 나는 실질적으로 그것들을 사용하지은 않았다. 그래서 어떤 예제 코드들도 제공할 수 없다.
여러분들은 두개의 라이브러리, libformat과 libavcodec이 존재하는 하는지 의아해 한다. 많은 비디오 파일 포멧들은 (AVI가 본질적인 예로) 어떤 코덱이 오디오, 비디오 데이타를 엔코딩하는 데 사용되어야 하는지 일일이 열거하지 않는다. 그져 어떻게 오디오 비디오 스트림이 (혹은 간혹 몇몇 오디오 비디오 스트림) 하나의 파일로 합성되어야 하는지를 정의한다. 이것은 가끔 여러분들이 AVI파일을 열때, 왜 그림은 없고 소리만 나오게 되는 이유다. - 그건 올바른 비디오 코덱이 여러분의 시스템에 인스톨 되지 않았기 때문이다. 이와 같이, libavformat은 비디오 파일을 분석하고 포함된 스트림들을 구분하는 것을 다룬다. 그리고 libavcodec은 가공하지 않은 오디오 비디오 스트림들을 디코딩하는것을 다룬다.
Opening a Video File
처음 할 것들은 먼저 - 어떻게 비디오 파일을 여는지 살펴보고 포함된 스트림들을 얻어 보자. 우리가 처음 해야 할것은 libavformat과 libavcodec을 초기화 하는 것이다.
이것은 포멧과 코덱을 포함하는 파일이 열렸을 때 자동적으로 사용되어지게 하기 위해, 라이브러리에 연관된 모든 유용한 파일 포멧과 코덱들을 등록한다. 여러분들은 단지 av_register_all()을 한번 콜하기만 하면된다. 여러분의 startup 코드에 이것만으로도 충분히 훌룡하다. 만약 여러분들이 원한다면, 단지 어떤 특정 파일 포멧과 코덱들만 등록하는 것도 가능하다. 하지만 보통 여러분이 그렇게 해야할 이유는 없다.
다음은, 파일 열기
AVFormatContext *pFormatCtx;
const char *filename="myvideo.mpg";
// Open video file
if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0)
handle_error(); // Couldn't open file
마지막 세개의 파라미터들은 파일 포멧, 버퍼 사이즈, 그리고 포멧 파라미터들을 나타낸다. 그저 단순히 NULL이나 0으로 명기함으로써 libavformat이 포멧을 자동으로 인식하고 디폴트 버퍼 사이즈를 사용할 것을 요청한다. 여러분의 application을 위해선 handle_error()를 적절한 에러 핸들링 코드로 대체해라.
다음, 파일에 포함된 스트림에 관한 정보를 받기 원한다.
// Retrieve stream information
if(av_find_stream_info(pFormatCtx)<0)
handle_error(); // Couldn't find stream information
이것은
AVFormatContext의 streams 영역을 유용한 정보로 체운다. 디버깅을 돕기 위해서 이 정보를 표준 에러로 살펴볼것이다. 그러나 물론 여러분은 제품 application에서는 하지 말아야 한다.
dump_format(pFormatCtx, 0, filename, false);
introduction에서 언급한 데로, 비디오 스트림만 다루고, 오디오 스트림은 다루지 않을 것이다. 명료하고 쉽게 하기 위해서 단지 맨 처음 찾게 되는 비디오 스트림을 사용한다.
int i, videoStream;
AVCodecContext *pCodecCtx;
// Find the first video stream
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec.codec_type==CODEC_TYPE_VIDEO)
{
videoStream=i;
break;
}
if(videoStream==-1)
handle_error(); // Didn't find a video stream
// Get a pointer to the codec context for the video stream
pCodecCtx=&pFormatCtx->streams[videoStream]->codec;
그럼, 여기서 우리는 비디오 스트림을 위한 코덱 context라 불리는 것의 포인터를 얻는다. 그러나 아직 실행할 코덱을 찾고 열어야 한다.
AVCodec *pCodec;
// Find the decoder for the video stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL)
handle_error(); // Codec not found
// Inform the codec that we can handle truncated bitstreams -- i.e.,
// bitstreams where frame boundaries can fall in the middle of packets
if(pCodec->capabilities & CODEC_CAP_TRUNCATED)
pCodecCtx->flags|=CODEC_FLAG_TRUNCATED;
// Open codec
if(avcodec_open(pCodecCtx, pCodec)<0)
handle_error(); // Could not open codec
("truncated bitstreams"에 대해서 알겠는가? 그럼 우리가 주지하다시피, 비디오 스트림에 있는 데이타는 패킷들로 나누어 진다. 비디오 프레임당 데이타의 양은 변할 수 있기때문에, 두 비디오 프레임들 사이의 경계는 패킷경계와 일치할 필요 없다. 여기서, 우리는 이 상황을 다룰수 있는 코덱을 이야기하고 있다.)
AVCodecContext 스트럭쳐에 저장된 정보중 중요한 한 부분은 비디오의 프레임 레이트이다. 정수가 아닌 프레임 레이트를 허용하기 위해서 (NTSC의 29.97fps와 같이), 레이트는 분수형태로 저장된다. (pCodecCtx->frame_rate인 분자와 pCodecCtx->frame_rate_base인 분모를 갖는) 서로 다른 비디오 파일들을 갖는 라이브러리를 테스트할때, 나는 어떤 코덱들은 (특히 ASF경우) 이 부분이 잘못 채워있는지에(fram_rate_base가 1000대신 1을 포함한다) 주의했다. 다음의 해킹한 수정사항들은 이것:
// Hack to correct wrong frame rates that seem to be generated by some
// codecs
if(pCodecCtx->frame_rate>1000 && pCodecCtx->frame_rate_base==1)
pCodecCtx->frame_rate_base=1000;
버그가 어느날 수정될었을때 조차 해당 위치에 이 수정사항들을 남겨두어서 문제가 되지 말아야 한다. - 드물게 비디오가 1000fps이상의 프레임 레이트들 갖곤한다. 하나더 해야할 것이 남아 있다. 디코드된 영상들을 저장할 비디오 프레임을 확보해라.
AVFrame *pFrame;
pFrame=avcodec_alloc_frame();
그럼 이제 비디오 디코딩 작업을 시작하자.
Decoding Video Frames
내가 이미 언급한대로, 비디오 파일은 몇개의 오디오 비디오 스트림을 포함할 수 있다. 그리고 이 스트림은 각각 특정 사이즈의 패킷으로 나누어 진다. 우리가 할일은 이 패킷들을 하나씩 하나씩 libavformat을 이용해서 읽고, 우리가 원하던 비디오 스트림의 부분이 아닌 것들을 걸러 내고, 디코딩을 하기 위해서 그것들을 libavcodec으로 넘겨 주는 것이다. 이것을 하는데 있어서 우리는 두 프래임간의 경계가 패킷의 중간에 발생할 수 있다는 사실에 주의해야 할 것이다. 사운드가 복합된 것은? 다행이도, 우리는 루틴에 있는 다음 비디오 프레임을 넘겨주는 이 모든 과정들로 요약할 수 있다.
bool GetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx,
int videoStream, AVFrame *pFrame)
{
static AVPacket packet;
static int bytesRemaining=0;
static uint8_t *rawData;
static bool fFirstTime=true;
int bytesDecoded;
int frameFinished;
// First time we're called, set packet.data to NULL to indicate it
// doesn't have to be freed
if(fFirstTime)
{
fFirstTime=false;
packet.data=NULL;
}
// Decode packets until we have decoded a complete frame
while(true)
{
// Work on the current packet until we have decoded all of it
while(bytesRemaining > 0)
{
// Decode the next chunk of data
bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame,
&frameFinished, rawData, bytesRemaining);
// Was there an error?
if(bytesDecoded < 0)
{
fprintf(stderr, "Error while decoding frame\n");
return false;
}
bytesRemaining-=bytesDecoded;
rawData+=bytesDecoded;
// Did we finish the current frame? Then we can return
if(frameFinished)
return true;
}
// Read the next packet, skipping all packets that aren't for this
// stream
do
{
// Free old packet
if(packet.data!=NULL)
av_free_packet(&packet);
// Read new packet
if(av_read_packet(pFormatCtx, &packet)<0)
goto loop_exit;
} while(packet.stream_index!=videoStream);
bytesRemaining=packet.size;
rawData=packet.data;
}
loop_exit:
// Decode the rest of the last frame
bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
rawData, bytesRemaining);
// Free last packet
if(packet.data!=NULL)
av_free_packet(&packet);
return frameFinished!=0;
}
지금, 우리가 해야할 모든 것들은 루프문에 있다. (false를 되돌려 줄때까지
GetNextFrame()을 통해서) 그저 하나더 주의해야 할 것은: 대부분의 코덱들이 YUV 420 포멧의 영상들을 되돌려 준다. (한개의 luminance, 하나의 luminance 채널에 반개의 샘플을 갖는 두개의 chrominace 채널) 여러분은 비디오 데이타를 원하는 목적에 따라, RGB로 변환할 수 있다. (주의, 비록 당신이 원하던것들이 비디오 데이타를 디스플레이 하는 것이라면 할 필요 없지만, YUV-to-RGB와 하드웨어에서 스케일링작업을 하는 X11 Xvideo 확장을 살펴보라) 다행이도, libavcodec은 img_convert인 변환 루틴을 제공한다. 이것은 다양한 영상 포멧뿐만 아니라 YUV와 RGB사이의 변환을 한다. 비디오를 디코드하는 루프문은 다음과 같다.
while(GetNextFrame(pFormatCtx, pCodecCtx, videoStream, pFrame))
{
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
// Process the video frame (save to disk etc.)
DoSomethingWithTheImage(pFrameRGB);
}
RGB영상인 (
AVFrame *)형태의 pFrameRGB은 다음과 같이 확보된다.
AVFrame *pFrameRGB;
int numBytes;
uint8_t *buffer;
// Allocate an AVFrame structure
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
handle_error();
// Determine required buffer size and allocate buffer
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
buffer=new uint8_t[numBytes];
// Assign appropriate parts of buffer to image planes in pFrameRGB
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
Cleaning up
좋습니다. 우리는 비디오를 읽고 처리해서 지금 해야할 것은 스스로 clean up하는 것이다.
// Free the RGB image
delete [] buffer;
av_free(pFrameRGB);
// Free the YUV frame
av_free(pFrame);
// Close the codec
avcodec_close(pCodecCtx);
// Close the video file
av_close_input_file(pFormatCtx);
끝.
Sample Code
이 일련의 포함하는 샘플 어블리케이션은 여기에(
http://www.inb.uni-luebeck.de/~boehme/avcodec_sample.cpp) 있다. 만약 당신이 어떤 추가적인 코멘트가 있다면, boehme@inb.uni-luebeckREMOVETHIS.de로 나에게 접촉해 달라. Standard disclaimer: I assume no liability for the correct functioning of the code and techniques presented in this article.