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
Video 썸네일 추출하기  (0) 2011.03.18
Windows 화면보호기/절전모드 방지하기  (0) 2011.02.07