검색결과 리스트
글
Video 썸네일 추출하기
(FFmpeg 을 이용한 Thumbnail 추출하기)
Intro
오늘은 FFmpeg 이라는 오픈소스를 이용하여 썸네일(Still Shot)을 추출하는 방법에 대해서 알아보도록 하겠습니다.
참고로 FFmpeg 의 라이센스는 LGPL(GNU Library General Public License)입니다. 즉, Dynamic Link 를 하는 것에 대해서는 Copy Left 에 대해서 License 부담이 없다고 보시면 됩니다. 그래서 이번 포스팅에서는 Dynamic Link 방식으로 설명을 하도록 하겠습니다.
FFmpeg
FFmpeg(http://www.ffmpeg.org) 는 KMPlayer 등 많은 동영상 재생 프로그램에서 사용할 정도로 안정적이고 성능면에서 우수하다고 생각합니다. 그리고 리눅스, 애플, 윈도우 등 많은 O/S 에서 사용이 가능합니다. 지원하는 코덱도 상당히 많습니다. 아래를 참고하시기 바랍니다.
보시는것 처럼 FFmpeg 은 지원하는 기능도 많고 코덱도 많아서 사용하는 방법도 많이 까다롭습니다. =_= 물론 영상과 음원에 대한 지식이 많다면 쉬울 것입니다. 저와 같이 미디어에 무지한 사람은 어렵더라구요..후.. 왜냐면 코딩 자체가 어렵다기보다는 용어를 모르니, 많이 헤매었습니다. 사실 지금도 개발을 하면서 용어들에 대해서 추측만 할 뿐, 명확히 알지 못하겠네요.
Download FFmpeg Source
개발을 하려면 먼저 FFmpeg 을 구해야겠지요. http://www.ffmpeg.org 에 가시면, 소스코드를 받는 방법에 대해서 나와있습니다. 하지만 build 된 소스는 제공하지 않지요. 따라서 직접 소스를 받으신 후에 build 하셔야 합니다. 윈도우에서 build 하는 방법에 대해서는 인터넷에 많이 나와 있으니 검색해 보시면, 쉽게 하실 수 있을거라고 믿습니다.
그리고 귀찮으신 분은 build 된 FFmpeg의 dll 을 받으시면 됩니다. 이 고마운 일을 해 주는 사이트가 있습니다. :)
(주소가 기억나지 않네요 -_- 찾으면 링크를 드리겠습니다.)
Save Image to Jpeg
기본적으로 FFmpeg 에서 추출한 이미지는 PPM 이라는 파일포멧입니다. 사실 미디어쪽으로 사용하려는게 아닌 이상, jpg 포멧의 이미지가 필요할 것입니다. 그래서 FFmpeg 을 이용해서 jpg 로 이미지를 저장하는 방법에 대해서 살펴보겠습니다. PPM 을 변환하는 방법에는 OpenCV 나 다른 라이브러리를 사용하시면 됩니다. 그런데 라이센스에 자유롭지 않은 경우, 이것저것 라이브러리를 섞어서 쓰는데 많은 부담이 있습니다. 또 생각해보면, FFmpeg.exe 에서는 jpg 로 이미지를 뽑아낼 수 있도록 옵션을 제공해 줍니다. 그렇다는 이야기는 FFmpeg 라이브러리만 가지고 충분히 jpg 을 뽑을 수 있다라는 이야기겠지요.
[ppm 을 jpeg 으로 저장하는 코드]
int SaveFrameToJPEG (AVCodecContext *pCodecCtx, AVFrame *pFrame, const char * file_name, const char * save_file_name, int size){
AVCodecContext *pOCodecCtx;
AVCodec *pOCodec;
uint8_t *Buffer;
int BufSiz;
int BufSizActual;
PixelFormat ImgFmt = PIX_FMT_YUVJ420P; //for the newer ffmpeg version, this int to pixelformat
BufSiz = avpicture_get_size (PixelFormat::PIX_FMT_RGB24 ,pCodecCtx->width, pCodecCtx->height );
Buffer = (uint8_t *)malloc ( BufSiz );
if ( Buffer == NULL )
return ( 0 );
memset ( Buffer, 0, BufSiz );
pOCodecCtx = avcodec_alloc_context ( );
if ( !pOCodecCtx ) {
free ( Buffer );
return ( 0 );
}
pOCodecCtx->bit_rate = pCodecCtx->bit_rate;
pOCodecCtx->width = pCodecCtx->width;
pOCodecCtx->height = pCodecCtx->height;
pOCodecCtx->pix_fmt = ImgFmt;
pOCodecCtx->codec_id = CODEC_ID_MJPEG;
pOCodecCtx->codec_type = CODEC_TYPE_VIDEO;
pOCodecCtx->time_base.num = pCodecCtx->time_base.num;
pOCodecCtx->time_base.den = pCodecCtx->time_base.den;
pOCodec = avcodec_find_encoder ( pOCodecCtx->codec_id );
if ( !pOCodec ) {
free ( Buffer );
return ( 0 );
}
if ( avcodec_open ( pOCodecCtx, pOCodec ) < 0 ) {
free ( Buffer );
return ( 0 );
}
pOCodecCtx->mb_lmin = pOCodecCtx->lmin = pOCodecCtx->qmin * FF_QP2LAMBDA;
pOCodecCtx->mb_lmax = pOCodecCtx->lmax = pOCodecCtx->qmax * FF_QP2LAMBDA;
pOCodecCtx->flags = CODEC_FLAG_QSCALE;
pOCodecCtx->global_quality = pOCodecCtx->qmin * FF_QP2LAMBDA;
pFrame->pts = 1;
pFrame->quality = pOCodecCtx->global_quality;
BufSizActual = avcodec_encode_video(pOCodecCtx,Buffer,BufSiz,pFrame );
//ResizeImage
void * m_buffer = ::GlobalAlloc(GMEM_MOVEABLE, BufSizActual);
if(m_buffer) {
void * pBuffer = ::GlobalLock(m_buffer);
if(pBuffer) {
CopyMemory(pBuffer, Buffer, BufSizActual);
IStream * pStream = NULL;
if(::CreateStreamOnHGlobal(m_buffer, FALSE, &pStream) == S_OK) {
// 이미지 생성
CImage image_org;
image_org.Load(pStream);
pStream->Release();
if(image_org.GetHeight() <= size) {
CString saveFileName(save_file_name);
image_org.Save(saveFileName);
image_org.Destroy();
} else {
// 리사이즈 크기
int height = size;
int width = height * image_org.GetWidth() / image_org.GetHeight();
// 사본 생성
CImage image_cpy;
image_cpy.Create(width, height, image_org.GetBPP());
HDC hdc = image_cpy.GetDC();
SetStretchBltMode(hdc, HALFTONE);
image_org.StretchBlt(hdc, 0, 0, width, height, SRCCOPY);
// 저장
CString saveFileName(save_file_name);
image_cpy.Save(saveFileName);
// 리소스 해제
image_cpy.ReleaseDC();
image_cpy.Destroy();
image_org.Destroy();
}
}
::GlobalUnlock(pBuffer);
}
::GlobalFree(m_buffer);
}
avcodec_close ( pOCodecCtx );
free ( Buffer );
return ( BufSizActual );
}
int ExtractFrame(const char * video_path, const char * save_file_name, int size, double pos)
{
AVFormatContext *pFormatCtx;
int i, videoStream;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVFrame *pFrame;
AVFrame *pFrameRGB;
AVPacket packet;
int frameFinished;
int numBytes;
uint8_t *buffer;
// Register all formats and codecs
av_register_all();
// Open video file
if(av_open_input_file(&pFormatCtx, video_path, NULL, 0, NULL)!=0)
return -1; // Couldn't open file
// Retrieve stream information
if(av_find_stream_info(pFormatCtx)<0)
return -1; // Couldn't find stream information
// Dump information about file onto standard error
//dump_format(pFormatCtx, 0, video_path, false);
// 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)
return -1; // Didn't find a video stream
// Get a pointer to the codec context for the video stream
pCodecCtx=pFormatCtx->streams[videoStream]->codec;
// Find the decoder for the video stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL)
return -1; // Codec not found
// Open codec
if(avcodec_open(pCodecCtx, pCodec)<0)
return -1; // Could not open codec
// Allocate video frame
pFrame=avcodec_alloc_frame();
// Allocate an AVFrame structure
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
return -1;
// Determine required buffer size and allocate buffer
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
// Assign appropriate parts of buffer to image planes in pFrameRGB
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);
// Read frames and save first five frames to disk
i=0;
//////////////////////////////////////////////////////////
// Seeking
//
AVStream *pStream = pFormatCtx->streams[videoStream];
double duration = (double) pFormatCtx->duration / AV_TIME_BASE;
if (duration <= 0)
{
double tmpDuration = 0.0;
if (pStream->codec->bit_rate > 0 && pFormatCtx->file_size > 0)
{
if (pStream->codec->bit_rate >= 8)
{
tmpDuration = 0.9 * pFormatCtx->file_size / (pStream->codec->bit_rate / 8);
}
if (tmpDuration > 0)
{
duration = tmpDuration;
}
}
}
if(duration <= 0) { return -1; }
int64_t tarPos = (long)(duration * pos);
if(tarPos < 0) { tarPos = 0; }
else if(tarPos >= duration) { tarPos = (duration - 1) / av_q2d(pStream->time_base); }
else { tarPos = tarPos / av_q2d(pStream->time_base); }
av_seek_frame(pFormatCtx, videoStream, tarPos, 0);
while(av_read_frame(pFormatCtx, &packet) >= 0) {
if(packet.stream_index == videoStream) {
//////////////////////////////////////////////////////////
// Decode video frame
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size);
if(frameFinished) {
static int sws_flags = SWS_BICUBIC;
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PixelFormat::PIX_FMT_RGB24,
sws_flags, NULL, NULL, NULL);
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
sws_freeContext(img_convert_ctx);
SaveFrameToJPEG(pCodecCtx, pFrame, video_path, save_file_name, size);
av_free_packet(&packet);
break;
}
}
av_free_packet(&packet);
}
av_free(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);
return 0;
}
'Microsoft > C++' 카테고리의 다른 글
[MFC] UTF8 / UTF16 Convert (0) | 2011.04.05 |
---|---|
Windows 화면보호기/절전모드 방지하기 (0) | 2011.02.07 |
RECENT COMMENT