[C/C++] 한글 초성 추출 └ C/C++

프로젝트 기간 중에 필요해서 만든 초성 추출 함수들

CP949 (EUC_KR 포함), UTF-8, UNICODE ( UCS-2 Big & Little Endian ) 지원

달랑 C 파일 하나 있으므로, Header는 따로 만들어서 써야 합니다


MySql C 라이브러리 함수 만들다가 주의점 └ 기 타

C용 mysql 라이브러리 이용해서 Manager 만들면서 시행착오 겪었던 것 정리 (추후 업데이트 할지도??)
( Connection을 계속 유지한다는 가정 하에!! )

1. PROCEDURE 호출 시 주의사항!!!

다음과 같은 상황에서 발생합니다
Multiple Statement 옵션 (CLIENT_MULTI_STATEMENTS) 을 지정한 상태에서
아래와 같은 프로시져를 호출한다

CALL MY_PROCEDURE( @pRETVAL ); SELECT @pRETVAL;

MY_PROCEDURE는 내부적으로 업데이트를 처리한 후 pRETVAL에 결과 값을 저장한다
이 때 실질적으로 Result Set은 SELECT @pRETVAL; 하나만 존재하기 때문에

바로 mysql_store_result() 를 통해서 Result Set을 가져오려고 시도를 하는데
이 경우 mysql_store_result() 함수는 NULL을 리턴한다 (Result Set이 없다고 리턴!!)
mysql_error() 도 성공으로 뜬다

이 경우 Statement는 총 2개가 된다
프로시져 내부적으로 Result Set을 갖는 구문 (SELECT)가 없다 하더라도
프로시져 호출 자체적으로 비어있는 Result Set을 하나 가지고 있다

따라서 SELECT @pRETVAL; 에 대한 Result Set을 가져오려면
mysql_next_result() 로 프로시져 Call 에 대한 빈 Result Set을 무시하고
다음 Result Set을 가져와야 한다

만일 MY_PROCEDURE 안에 SELECT문이 있다면?
SELECT 문의 수 + 1 만큼의 mysql_next_result() 호출이 있어야 한다
+1 이 바로 프로시져 Call 에 의한 빈 Result Set

!!!여기서 중요하게 생각해야 하는 부분이 하나 더 있습니다!!!
그렇다면 그냥 PROCEDURE() 만 호출해 봅시다

CALL MY_PROCEDURE();

이 경우에 만약 mysql_next_result() 처리를 하지 않으면 어떻게 될까요?
라이브러리 단에서 fetch 해야 할 result set이 남아있다는 판단 하에...
out of sync 에러를 던져버립니다!!!
mysql_ping() 도 out of sync 에러를 던져버리기 때문에
sql 실행 전에 접속 체크를 위해서 mysql_ping() 쳐보면 에러 떨어집니다

주의하고 또 주의합시다!!!

[C/C++] MACRO함수 사용시 일어날 수 있는 문제들 └ C/C++

stackoverflow 눈팅하다가 괜찮아 보여서 링크~

http://stackoverflow.com/questions/9104568/macro-vs-function-in-c



[WINAPI] Terminate Process Nicely(?) └ C/C++

<일단, 이 글은 사용자가 직접 새로운 프로세스를 생성/실행 시켜 해당 프로세스의 pid 등을 알고 있다는 전제하에 작성>

유닉스/리눅스 계열에서는 kill() 함수에 의해서 프로그램을 강제 종료 시킬 수 있다.

윈도우즈는 어떨까?

만일 프로그램으로 하여금 일반적으로 윈도우창의 X버튼을 눌렀을 때와 같이 종료를 시키고자 한다면,
해당 프로그램에게 WM_CLOSE 메시지를 날리는 방법이 가장 안전할 것이다.

하면 어떻게 WM_CLOSE메시지를 날릴 수 있을까?

EnumWindows() 함수를 이용해서 현재 실행중인 윈도우즈를 조회하는 방법이 있다.

아래는 간단한 샘플 코드이다.

BOOL CALLBACK EnumWindowProc( HWND hWnd, LPARAM lParam )
{	
	if( GetParent( hWnd ) == NULL ) {		
		DWORD pid = 0;		
		GetWindowThreadProcessId( hWnd, &pid );		
		if( pid == (DWORD)lParam )	{			
			PostMessage( hWnd, WM_CLOSE, 0, 0 );			
			  //PostMessage( hWnd, WM_SYSCOMMAND, SC_CLOSE, 0 );		
		}	
	}	
       return TRUE;
}

void KillProcess( PROCESS_INFORMATION* pInfo ){	
	EnumWindows( EnumWindowProc, pInfo->dwProcessId );
	if( WaitForSingleObject( pInfo->hProcess, 2000 ) != WAIT_OBJECT_0 )	{
		///> 2sec timeout
TerminateProcess ( pInfo->hProcess, 0 );
	}
}

윈도우들을 조회하면서 동일한 PID를 갖는 모든 hWnd에 WM_CLOSE 메시지를 보내는 것이다.

일단, 왠만한 상황에서는 이 코드가 정상적으로 동작하는 것을 확인했다.

EnumWindows() 밑에 부분은 2초동안 프로세스 종료를 기다려보고, 

그래도 종료가 안되었다면 응답없음으로 판단하고 강제 종료 시키는 부분이다.

단, TerminateProcess의 경우 DLL에 의해 관리되는 전역 데이터 상태가 손상될 수 있고, 

DLL들에게 detaching notify가 안날라가는 등, 추후 문제의 소지가 될 수 있다.


*** 추가적으로 위 코드가  TerminateProcess를 타는 몇가지 경우가 있었는데...

가장 치명적이었던 경우는 바로  VLC로 영상을 재생 중에 종료를 시도할 때였다

재생 중이 아니라면 정상 종료되었으나 재생 중에는 위 방법을 사용했다가, VLC가 Hang이 걸리는 문제가 발생했다.

보통 하나의 프로세스 내에 여러 개의 hWnd가 존재할 수 있는데 그 중 하나가 WM_CLOSE 메시지를 받고

그대로 Blocking 상태가 되어버린 것으로 추측하고 있다.

하드 코딩으로 여러개의 hWnd 중 주 윈도우 핸들 ( 실질적으로 윈도우 메시지를 처리하는 중심 윈도우 ? ) 에게만

WM_CLOSE 메시지를 보내줄 때는 정상적으로 종료가 됨을 확인했다.

문제는 그 주 Window 핸들을 특정지을만한 방법이 우리에게는 없다는 것이다.

몇가지 편법으로 IsWindowVisible() 이나, GetWindow( hWnd, GW_CHILD ) 함수를 사용해서

화면에 보이고 있거나, Child Window가 존재하는 녀석이 주 윈도우라고 판단하고 WM_CLOSE 메시지를 보내도록 해보기도 했다.

일단 VLC의 경우에 한해서는 성공적이었으나, 이게 모든 case를 커버할 것이라고는 생각하지 않는다.


=====================================================================================================


그 다음으로 찾은 방법은 WindowsXP Pro 이상에서만 가능한 방법인데

taskkill 유틸리티를 이용하는 방법이다.

taskkill 유틸리티는 유닉스/리눅스 계열의 kill 커멘드와 유사하다고 보면된다.

다만, 직접적으로 함수로 있는게 아닌 cmd 유틸리티이므로

CreateProcess() 나 ShellExecute() 계열의 함수를 이용해서 실행해 주면 된다.

커멘드는 : [ taskkill /im 프로그램이름 ] 과 같이 사용하면 된다.

가령, vlc를 종료한다고 하면, "taskkill /im vlc.exe" 가 된다.

한가지 단점은, 화면에 떠있는 모든 vlc 로 종료 시그널이 날라가기 때문에

특정 vlc만 죽이고 싶을 때는 좋은 방법이 아니다.




FFmpeg Player with Visual Studio - 05 FFmpeg을 이용한 Video Decoding └ FFMPEG

요즘 회사일이 좀 바빠서 업로드가 많이 늦었습니다 ㅜㅜ 
벌써 5번째 강좌군요 그럼 시작하겠습니다

05. FFmpeg을 이용한 Video Decoding


저번 강좌에서 FFmpeg 라이브러리를 이용하여 미디어 파일을 열어보고 해당 미디어 파일의 정보를 가져오는 부분까지 알아봤습니다


이번 시간에는 가져온 미디어 정보를 바탕으로 본격적인 디코딩을 위해서 코덱을 생성하고, 실제 스트림 패킷을 읽어서 디코딩을 해보는 과정까지 진행해 볼까 합니다


> Streams


지루하겠지만, 일단 이론을 좀만 더 보시죠. 스트림( Stream ) 이란 녀석에 대해서 좀 이야기를 해볼 까 합니다. 일반적으로 일관된 데이터의 흐름을 스트림 이라고 합니다. 우리가 다루고 있는 미디어 파일에서 본다면, 비디오나 오디오 데이터가 이에 해당된다고 볼 수 있겠죠?


일반적으로 우리가 접하는 미디어 파일은 비디오 스트림과 오디오 스트림이 각각 하나씩 들어있습니다.


하지만 파일에 따라서는 비디오 / 오디오 이외에 특별한 목적을 가지고 넣는 private data 스트림이 들어가는 경우도 있습니다. 또한 DVD 영화와 같이 자막 스트림이 같이 포함되는 경우도 있습니다.


또한, 제작자가 원한다면 미디어 파일에 다중 비디오 / 오디오 스트림을 넣는 경우도 볼 수 있죠. 가령, DVD 영화의 경우 영어 / 우리말 / 감독의 주석 등 여러가지 오디오가 한번에 들어 있는 경우를 보셨을 겁니다.


반대로 CCTV와 같이 영상만 필요한 경우는 오디오 스트림이 존재하지 않는 경우도 있을거라고 생각해 볼 수 있겠습니다.


일반적으로 미디어 파일을 재생하면, 한번에 하나의 비디오와 오디오만 렌더링하게 되므로, 만일 다중 비디오 및 다중 오디오 스트림이 존재하는 미디어 파일이 있다면 Decoding을 할 대상을 특정지어야 됩니다.


FFMpeg Library에서는 이 Stream들의 목록을 어떻게 관리할까요?


앞서 avformat_open_input() 함수와 avformat_find_stream_info() 함수를 통해 AVFormatContext 구조체에 미디어 파일의 정보를 채웠던 것을 기억하실 겁니다. AVFormatContext 구조체를 살표보면 streams 라는 변수가 있습니다. AVStream 구조체 포인터의 배열이라고 생각하시면 됩니다. 총 몇개의 Stream이 존재하는지는 nb_streams 변수를 참고 하시면 됩니다.


일반적으로 FFmpeg Library에서 처리하는 Stream의 Type은 6가지 이며, 각 타입은 상수로 다음과 같이 정의되어 있습니다.


enum AVMediaType {
   AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA
   AVMEDIA_TYPE_VIDEO,
   AVMEDIA_TYPE_AUDIO,
   AVMEDIA_TYPE_DATA,       ///< Opaque data information usually continuous
   AVMEDIA_TYPE_SUBTITLE,
   AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse
   AVMEDIA_TYPE_NB
};


마지막 AVMEDIA_TYPE_NB는 타입 정의가 아니라 미디어 타입이 총 5개 있음을 의미합니다. enum 규칙에 따라서 AVMEDIA_TYPE_NB에 5가 들어가지요.


위의 타입 중 실질적으로 우리가 반드시 처리해야하는 경우는 AVMEDIA_TYPE_VIDEO와 AVMEDIA_TYPE_AUDIO이며, DVD와 같이 내장된 자막을 처리하고자 한다면 AVMEDIA_TYPE_SUBTITLE까지 신경쓰시면 됩니다. ( 여기서 말한 자막 Stream은 smi나 sub 파일과 같이 별도로 존재하는 자막 파일이 아닌 미디어 파일에 내장되어 있는 자막을 의미합니다. 만일 별도파일의 자막을 처리하고 싶으시다면, 해당 내용을 따로 구현하셔야 합니다. )


아래 코드는 미디어 파일의 스트림 정보를 조회하며, 해당 스트림의 타입을 출력하는 코드입니다.


AVFormatContext *pFmtCtx; ///> 파일 열기 과정 생략
int i;
for( i = 0 ; i < pFmtCtx->nb_streams ; i++ ) {
printf("Stream %d is ", i);
switch( pFmtCtx->streams[i]->codec->codec_type ) {
case AVMEDIA_TYPE_VIDEO:
printf("Video\n");
break;
case AVMEDIA_TYPE_AUDIO:
printf("Audio\n");
break;
case AVMEDIA_TYPE_SUBTITLE:
printf("Subtitle\n");
break;
case AVMEDIA_TYPE_DATA:
case AVMEDIA_TYPE_ATTACHMENT:
printf("Data\n");
break;
default:
printf("Unknown\n");
break;
}
}


여기서 stream 배열의 index 값이 되는 저 i 값이 중요합니다. 저 i 값은 AVStream 구조체 내에 index라는 변수명으로 또한 따로 저장되어 있습니다. 저 값이 중요한 이유는 Decoding을 위해서 미디어 파일의 스트림 데이터를 읽어들일 때, 읽어들인 데이터가 어떤 스트림용 데이터인지 구분지을 때, 저 index 값을 사용하기 때문입니다. 이는 실제 데이터를 읽어들이는 부분에서 다루도록 하겠습니다.


> AVCodecContext


앞서 우리는 Demuxing을 위해서 AVFormatContext 구조체를 사용했습니다. 앞선 강좌에서 AVFormatContext는 I/O, Muxing, Demuxing에 필요한 정보를 저장한 구조체라고 했다는 내용을 기억하시는지요?

그렇다면 자연스럽게, Encoding, Decoding에도 이와 비슷한 역할을 하는 대표 구조체가 존재할 수 있다고 추측해 볼 수 있습니다. 그 구조체가 바로 AVCodecContext 입니다


기본적으로 AVCodecContext 구조체에는 Encoding/Decoding에 필요한 정보들이 거의 모두 포함되어 있다고 보시면 됩니다. 가령, 비디오의 경우 비디오 이미지의 해상도와 frame rate 등의 정보가 있고, 오디오의 경우 sample rate, channel layout 등의 정보가 포함되어 있습니다. 여기까지는 Encoding / Decoding 공통 정보이고, 여기에 추가적으로 Decoder에 대한 정보를 붙이게 되면, AVCodecContext 구조체는 Decoding을 위한 용도로, 반대로 Encoder에 대한 정보를 붙이게 되면 Encoding을 위한 용도로 사용됩니다.


Encoding을 하기 위해서는 AVCodecContext를 직접 생성 해 줘야 하는 경우가 일반적이지만, Decoding의 경우에는 앞서 avformat_open_input() 함수와 avformat_find_stream_info() 함수를 거치면서 AVFormatContext 구조체 내의 AVStream 구조체 내에 codec 이라는 변수명으로 자동으로 생성 되어 있으며, 사용자는 여기에 추가적으로 Decoder에 대한 정보만 붙여주면 됩니다.


> Decoder 준비하기


이제 직접 코드를 통해서 어떻게 Decoder를 준비하는지 보도록 하겠습니다.

이하 코드들은 저번 시간에 살펴본 avformat_find_stream_info() 함수까지 정상적으로 실행되었다는 가정하에 작성된 것입니다.


1. 대상 비디오 / 오디오 스트림 찾기


먼저 Decoding의 대상을 가져와 봅시다. Decoding의 대상이라고 한다면, 당연히 미디어 파일 안에 포함되어 있는 오디오 및 비디오 스트림이 될 것입니다. 일단 “찾는다” 라는 표현을 사용했습니다만, 여기서는 Decoding 대상을 특정짓는다 라고 하는게 맞는 표현일지도 모르겠네요.


아래 코드는 위의 스트림 타입 출력 코드를 살짝 변형 시켜서 만든 첫번 째 비디오와 오디오 스트림을 찾는 소스코드 입니다.


AVFormatContext *pFmtCtx; ///> 파일 열기 과정 생략
int i;
int nVSI = -1; ///> Video Stream Index
int nASI = -1; ///> Audio Stream Index
for( i = 0 ; i < pFmtCtx->nb_streams ; i++ ) {
if( pFmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) {
nVSI = nVSI < 0 ? i : nVSI;
}
else if( pFmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) {
nASI = nASI < 0 ? i : nASI;
}
}


위와 같이 코드를 작성하면 (제 생각에는…) 90%의 미디어 파일에 대해서 이상 없이 사용하실 수 있습니다. 나머지 10%는 어떤 경우일까요? 일반적으로는 접하기 어려운 파일인데, 방송용으로 만들어진 스트림의 경우 하나의 미디어 파일 ( 이라기보다는 스트리밍이 더 옳은 표현이겠네요… 테스트 목적 이외에는 보통 파일로 저장해 놓지 않으니까요 ) 안에는 여러개의 채널이 존재할 수 있습니다. 이 경우는 똑같은 다중 스트리밍 이지만 DVD와는 달리 하나의 그룹이 아닌 다중 그룹(채널 별로 그룹지어지겠죠?)이기 때문에 위와 같은 코드를 사용할 경우 문제가 발생할 수 있습니다. 가령, 영상은 1번채널 영상인데, 음성은 2번채널 음성이 나오면 이상하겠죠? 물론 이 경우는 매우 특수한 경우이기 때문에 보통은 위의 코드만으로도 충분합니다.


만약 저런 특수한 경우까지 고려해서 연관된 비디오 / 오디오 스트림을 찾을려면 어떻게 해야할까요? FFmpeg Library에서는 위의 사항을 고려해서 적용된 Index를 가져오는 API 함수를 따로 제공하고 있습니다. FFmpeg에서 제공하는 플레이어 ffplay에서는 해당 API를 이용해서 스트림의 Index를 가져오도록 하고 있습니다. 아래 코드를 직접 보면서 설명하겠습니다.


AVFormatContext *pFmtCtx; ///> 파일 열기 과정 생략
int nVSI = -1; ///> Video Stream Index
int nASI = -1; ///> Audio Stream Index


///> 비디오 스트림 찾기
nVSI = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
///> 오디오 스트림 찾기 (비디오 스트림 있다면 연관된)
nASI = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_AUDIO, -1, nVSI, NULL, 0);


사용된 함수는 av_find_best_stream() 함수 입니다.


  • 첫번째 인자인 ic는 미디어 파일 열기에 사용했던 AVFormatContext 구조체 입니다.

  • 두번째 인자는 찾고자 하는 스트림 타입입니다. 보통 비디오, 오디오 또는 자막이 되겠습니다.

  • 세번째 인자는 특별히 지정하고자 하는 스트림 Index 번호입니다. 범위는 0 ~ pFmtCtx->nb_streams - 1 이며, -1 값을 지정할 경우 자동 선택입니다. 일반적으로 -1 값을 가장 많이 사용합니다.

  • 네번째 인자는 찾고자 하는 스트림의 연관된 스트림 Index 번호입니다. -1을 넣을 경우, 가장 처음으로 만나는 Target 스트림 Index를 얻어옵니다. 위의 코드를 보시면, 비디오 스트림은 가장 처음 검색되는 Index를 가져오고, 오디오 스트림을 검색할 때는 해당 비디오 스트림의 Index 값을 넘겨서 연관된 오디오 스트림을 찾도록 하고 있습니다.

  • 다섯번째 인자는 Decoder까지 함께 검색할지 여부입니다. AVCodec 구조체의 주소를 넘기도록 되어있습니다. 만약 NULL 값이 아니면 넘겨진 AVCodec 구조체에 Decoder 정보를 채워줍니다. 보통은 NULL 값을 넣고 사용자가 직접 FFmpeg Library에서 해당 Decoder를 검색하거나 생성합니다.

  • 마지막 인자는 나중에 확장을 위해서 만들어 놓은 값으로 현재는 따로 flag가 정의되어 있는 것이 없으므로, 그냥 0 값을 집어넣으면 됩니다.


이런 방법도 있다고만 알아두시면 될 것 같습니다. 실제로는 처음 제시했던 코드를 더 많이 사용하며, ffplay에서도 처음 미디어 오픈 시에만 위의 함수를 사용하고, 다중 오디오 재생을 위해서 다음 오디오 스트림을 열 때는 결국 첫번째 방법을 사용해서 하더군요.


av_find_best_stream() 함수를 참고하여 자신만의 find_stream 함수를 만들어 보는 것도 나쁘지 않은 방법입니다. av_find_best_stream() 함수는 libavformat/utils.c 에 구현되어 있습니다.


2. Decoder 검색 및 초기화


Decoding 대상인 비디오 / 오디오 스트림을 찾았다면, 해당 스트림이 어떤 Codec으로 Encoding 되어있는지 확인한 후에 FFmpeg Library에서 해당 Codec 용 Decoder 정보를 검색한 후, 해당 Decoder 정보를 이용하여 AVCodecContext 구조체를 Decoder로서 초기화 해보도록 하겠습니다.


아래 코드는 일단 비디오 스트림을 대상으로 Decoder를 초기화하는 코드입니다만, 오디오 스트림에 대해서도 똑같이 적용할 수 있습니다.


AVFormatContext *ic; ///> 스트림 열기 생략
int nVSI; ///> 비디오 Index 찾기 생략

///> Find Video Decoder
AVCodec *pVideoCodec = avcodec_find_decoder(

   pFmtCtx->streams[nVSI]->codec->codec_id

);
if( pVideoCodec == NULL ) {
av_log( NULL, AV_LOG_ERROR, "No Decoder was Found\n" );
exit( -1 );
}

///> Initialize Codec Context as Decoder
if( avcodec_open2( pFmtCtx->streams[nVSI]->codec, pVideoCodec, NULL ) < 0 ) {
av_log( NULL, AV_LOG_ERROR, "Fail to Initialize Decoder\n" );
exit( -1 );
}


먼저 해당 스트림이 어떤 Codec을 사용했는지에 대한 정보는 AVCodecContext 구조체 내의 codec_id 값을 확인하면 됩니다. 저번 시간에 올렸던 소스코드예제를 보신 분이라면, 저 정보를 이용해서 Codec 정보를 출력했다는 것을 기억하실 겁니다.


avcodec_find_decoder() 함수에 codec_id 값을 넘겨줌으로서 FFmpeg Library에 해당 Codec용 Decoder정보가 있는지 찾아볼 수 있습니다. 일반적으로 FFmpeg Library 자체 S/W Decoder가 먼저 검색되어지며, 만약 하나의 Codec에 대해서 복수개의 Decoder가 존재하며, 다른 Decoder를 사용하고 싶다거나 할 경우에는 avcodec_find_decoder_by_name() 함수를 통해서 이름으로 Decoder를 검색할 수도 있습니다. 하지만 보통은 FFmpeg Library를 빌드 할 때 옵션을 통해서 코덱당 하나의 Decoder만 나오도록 조절 합니다. 만약, 해당 코덱용 Decoder가 없는 경우에는 NULL 값을 리턴합니다.


만일 Decoder 정보가 존재한다면, AVCodecContext에 해당 정보를 넘겨줘서 Decoder로서 초기화 하는 일이 남았습니다. 이 때 사용하는 함수가 avcodec_open2() 입니다.


  • 첫번째 인자는 AVCodecContext 구조체 입니다.

  • 두번째 인자는 방금 찾은 AVCodec 구조체 입니다.

  • 마지막 인자는 Decoder 초기화에 필요한 추가 옵션입니다. 가령, 비디오의 경우 bitrate정보 등을 넣어 줄 수도 있고, decoding 시 thread 사용 여부 등을 결정해 줄 수도 있습니다. 일반적으로 NULL 값을 사용합니다.


여기까지 성공했다면, 이제 Decoding을 위한 기본 준비는 모두 마친 상태입니다.


> 실제 파일을 읽으면서 디코딩을 해보자!!


이제 마지막 단계입니다. 실제로 비디오 / 오디오 데이터를 읽어들이면서 디코딩을 해보도록 하겠습니다. 사실 상 이 단계까지 마치면 Player 제작 과정 중 FFmpeg Library를 이용해서 할 수 있는 일은 거의 다 했다고 보시면 됩니다. ( 물론 실제 Player를 만들 때는 설계가 많이 바뀌게 되지만, 호출하는 함수는 동일합니다 )


먼저 데이터를 읽는 과정입니다. 아래 코드를 보시죠.


AVFormatContext *pFmtCtx; ///> 초기화 생략
AVPacket packet;

while( av_read_frame( pFmtCtx, &packet ) >= 0 ) {
/**
* Decoding Process Here
*/

// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}


새로운 구조체가 등장했습니다. AVPacket 입니다. AVPacket 구조체는 FFmpeg Library에서 인코딩된 비디오 / 오디오 데이터를 저장하기 위한 구조체 입니다. Decoder에 이 구조체의 데이터가 전달되서 Decoding 과정을 수행합니다.


파일로부터 인코딩된 비디오 / 오디오 데이터를 읽기 위해서는 av_read_frame() 함수를 이용합니다. 파일 뿐만 아니라 network stream 일 경우에도 이 함수를 이용해서 패킷 데이터를 읽어올 수 있습니다. 첫번째 인자로는 미디어 파일을 여는데 사용한 AVFormatContext 구조체가 들어갑니다. 그리고 두번째 인자로는 읽어들인 데이터를 저장하기 위한 AVPacket 구조체가 들어갑니다.


여기서 한가지 주의해야 할 점이 있는데, AVPacket 구조체 자체는 위 소스코드에서 보시면 일반 변수로 선언되어 있습니다만, AVPacket 구조체 내에 data 라는 변수자체가 포인터 변수입니다. av_read_frame() 함수가 정상적으로 데이터를 읽어오게 되면, 이 data라는 포인터 변수에 동적할당을 통해 데이터를 기록합니다. 따라서 동적할당 된 데이터를 다 사용한 후 (디코딩 후가 되겠죠?) 에는 Release해줘야 할 필요가 있습니다. while() 문 마지막에 보시면 av_free_packet() 이라는 함수를 통해서 말이죠. ( **여기서 av_free_packet() 함수는 동적 할당 된 AVPacket 구조체 데이터를 Release하는게 아닌 AVPacket 구조체 내에 av_read_frame()함수를 통해 동적할당 된 데이터를 Release하는 것입니다. 주의합시다. )


이번에는 디코딩을 해보도록 합시다. 이번에도 코드를 먼저 보고 설명을 드리겠습니다.


AVFormatContext *pFmtCtx; ///> 초기화 생략
int nVSI, nASI; ///> Stream Index 찾기 과정 생략

AVCodecContext *pVCtx = pFmtCtx->streams[nVSI]->codec;
AVCodecContext *pACtx = pFmtCtx->streams[nASI]->codec;

AVPacket packet;
AVFrame *pVFrame = avcodec_alloc_frame();
AVFrame *pAFrame = avcodec_alloc_frame();

int bGotPicture = 0;
int bGotSound = 0;

while( av_read_frame( pFmtCtx, &packet ) >= 0 ) {
if( packet.stream_index == nVSI ) {
// Decode Video
avcodec_decode_video2( pVCtx, pVFrame, &bGotPicture, &packet );
if( bGotPicture ) {
// Ready to Render Image
}
}
else if( packet.stream_index == nASI ) {
// Decode Audio
avcodec_decode_audio4( pACtx, pAFrame, &bGotSound, &packet );
if( bGotSound ) {
// Ready to Render Sound
}
}

// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}

av_free( pVFrame );
av_free( pAFrame );


소스코드가 좀 길어보입니다. 이번에도 새로 보는 변수들이 막 등장하는군요. ^^;;


av_read_frame() 을 통해서 인코딩 된 데이터를 불러왔을 경우, 이 데이터가 비디오인지 오디오인지 사용자가 알아야 해당 Decoder를 통해 Decoding을 할 수 있겠죠? 이번 강좌 제일 처음 Stream 편 마지막에 index값이 중요하다고 했던거 아직 기억하고 계신가요? 그렇습니다. 코드에 보시면 AVPacket 구조체 내에 stream_index라는 변수가 나오는데 바로 이 값이 위에서 stream을 검색할 때 찾았던 바로 그 Index 값이 됩니다. 이 index 값을 이용해서 우리의 목표 스트림인 비디오 / 오디오 데이터를 구분짓는 것입니다.


stream_index로 비디오 또는 오디오 데이터를 찾았다면 이제 디코딩을 해야 합니다. 비디오 / 오디오 각각 avcodec_decode_video2() 함수와 avcodec_decode_audio4() 함수를 사용합니다. ( ** avcodec_open2() 나 avcodec_decode_audio4() 와 같이 함수 뒤에 숫자가 붙은 경우가 있는데, 이는 함수들이 버전 업을 하면서 붙은 숫자들입니다. 일부 과거 호환성을 위해서 코드상으로는 아직 남아있는 경우도 있는데 deprecated 된 함수이므로 사용하지 맙시다. )


decode 함수의 첫번째 인자는 당연히 Decoder 입니다. Decoder용으로 초기화한 AVCodecContext 구조체를 넘겨주시면 됩니다.


위에서 av_read_frame() 설명할 때, AVPacket 구조체가 파일로부터 인코딩 된 비디오 / 오디오 데이터를 읽어 저장하기 위한 구조체라고 했었습니다. 그렇다면 반대로 디코딩 된 결과 데이터를 저장하는 구조체도 있지 않을까요? 그렇습니다. 바로 그 역할을 하는 것이 AVFrame 구조체 입니다. decode 함수의 두번째 인자가 되겠습니다.


세번째 인자는 decoding된 결과가 이제 Rendering되어도 되는지 알려주는 flag 값이라고 보시면 됩니다. 이 flag 값이 0이면 Decode된 비디오 / 오디오 데이터가 없다는 의미입니다. (Decoding에 실패했다는 의미는 아닙니다. 가령, 비디오 패킷 데이터가 다음 비디오 이미지를 디코딩하기 위한 선행 정보라면 그 때는 디코딩은 성공했지만 비디오 데이터는 없겠죠? 디코딩의 실패는 함수 자체의 리턴 값으로 판단하시면 됩니다.) flag 값이 non-zero 값이면 Decode된 비디오 / 오디오 데이터가 AVFrame 구조체에 저장되었다는 의미이므로, AVFrame 구조체의 내용을 화면 / 스피커에 Rendering 하기만 하면 됩니다.


마지막 인자는 인코딩된 비디오 / 오디오 데이터를 가지고 있는 AVPacket 구조체입니다. av_read_frame() 함수를 통해 읽은 데이터를 여기에 넘기시면 됩니다.


실제로 Decoding된 데이터를 출력해야하는 문제가 남아있지만, 그 부분은 FFmpeg Library를 벗어나는 주제이므로 일단 이번 장에서는 생략하고, 이후 강좌에서 다루도록 하겠습니다. 미리 언급드리자면 윈도우에서 영상은 OpenGL을 통해서 출력할 것이며, 음성은 DirectSound를 통해서 출력할 예정입니다. 다만, 이를 처음부터 설명드리기에는 본 강의 방향성에 맞지 않기 때문에 가능한 실습 레벨에서는 위 기능들을 라이브러리 화 해서 제공할 예정이며, 혹시 직접 구현에 관심있으신분들이 있으실 수도 있으므로 따로 강좌 페이지를 제공할 예정입니다. (언제가 될지는 장담을 못드리지만 ^^;;;)


아래 첨부된 소스코드는 5장까지 배웠던 모든 내용을 정리해 본 것입니다. 다시 한번 정리해 보시는 것도 괜찮을 거라고 생각합니다. 그리고 비디오 출력이 정상적으로 이루어졌는지 확인도 안해보면 이상할 것 같아서 첫 비디오 프레임을 ascii 도트로 찍도록 해봤습니다. (가능하면 해상도가 (매우) 낮으면서 첫 장면이 gray scale로 봤을 때 무리없이 볼 수 있는 비디오로 테스트 해보시기 바랍니다.)

(** main 시작 부분에 szFilePath에 테스트 할 비디오 경로를 넣어주시면 됩니다 )


FFmpegTutorial.7z.001


FFmpegTutorial.7z.002


> 5장을 마치며


이번 장에서 우리는 Decoder를 초기화하고 파일을 읽으면서 실제 데이터를 Decoding 해보는 과정까지 살펴보았습니다. 사실상 FFmpeg Library를 이용한 Player의 Core 부분은 거의 5장까지 배운 범위 내에서 이루어집니다. 물론 이것으로 FFmpeg을 이용한 Player 개발이 완료되는 것이 아닙니다. 간단한 예제를 통해서 일단 핵심이 되는 FFmpeg 함수들을 살펴본 것이며, 다음 장 부터는 Player 개발에 있어서 실질적인 문제들을 다루어 가볼까 합니다. 이제부터는 본격적으로 Player 개발에 있어서 가장 힘든 부분들이 됩니다. “비디오 / 오디오를 어떻게 Rendering 할 것인가?”, “비디오 / 오디오의 동기화는 어떻게 처리하는게 좋은가?”,  “Player 구조는 어떻게 처리할 것인가?” 등 벌써부터 머리가 아파오시죠 ^^;;;

덧붙여 본격적인 Player 개발에 들어가게 되면 thread나 mutex, condition 등도 사용하게 되는데요. 해당 개념을 처음 들으시는 분들은 미리 공부를 하고 들으셔야 이후 강좌를 들으시기가 좀 더 수월하실 거라고 생각합니다.




1 2 3 4 5 6 7 8 9 10 다음