이미 하마티(http://www.himytv.com/)에 PC 화면 녹화 기능은 있지만, 일반 동영상 포멧에 대한 요구사항 때문에 만든 샘플입니다.  예전에는 VFW를 썼는데, 이번에는 ffmpeg을 이용해서 만들었습니다.


주 목적은 제 동영상 코덱을 표준 포멧으로 변환하는데 있기 때문에, 기능 구현 이전에 테스트 삼아 만든 것이라, 음성 싱크에 신경 쓰지 못했습니다.  음성 싱크 문제 해결과 아울러 조만간 해상도도 마음대로 선택 할 수 있는 버전을 만들어 볼 예정입니다.


하마티는 잠시 녹화를 중단시켜놔서, 재생 샘플만 보실 수가 있습니다.  http://ryulib.tistory.com/256

1시간 강의에 40MB도 안되는 용량입니다.



DeskCam.7z

  • 압축을 푸시고 실행하신 후 Start 버턴을 클릭하면 좌상단 화면 600x480 크기를 녹화합니다.
  • 녹화를 중지하고 나서, 같은 폴더에 있는 Test.avi 파일을 재생하면 됩니다.
  • 혹시 시간이 남는 분들은 테스트 후 버그 좀 알려주시면 감사하겠습니다 ^^;

실제 녹화는 하마티의 프로그램으로 하고 나서, 최종 결과물을 사용자가 avi로 변경할 수 있도록 할 예정입니다.  일반 포멧의 동영상은 압축률도 낮지만, 압축 속도가 느려서 큰 화면에서는 불리하기 때문입니다.

사용한 코덱은
  • Video: VP8
  • Audio: WMA2

언제 시간이되면 게임 화면 등에 최적화를 해봐야 겠네요.  자주 쓰이는 거 같던데.



Posted by 류종택

좀 더 재미있는 주제를 찾아서 고민하고 준비한 후 조속히 다시 온라인 강의를 재개하겠습니다 ^^*


소수의 몇 분께서 ffmpeg에 대한 몇 가지 다른 주제를 요청하셨으나, 참여가 1회에 비해서 저조하기도 했고, 이후 반응도 다소 미지근했던 지라, 보다 자극적인 주제를 찾아봐야 할 듯 합니다 ㅠ.ㅠ 


1회 70명 넘어갔을 때 와!! 하면서 놀랬닥, 2회에서 급실망 ㅠ.ㅠ


사실 강사의 준비가 미흡하기 때문에 연기하는 겁니다 ^^;


다음 강의에 대한 요청이 접수되면, 제가 못다루는 분야라고 해도, 해당 분야의 전문가를 졸라서라도 진행해 보겠습니다!!


현재까지의 강의에서 미흡하거나 요청을 받은 것들을 정리하고 "ffmpeg for Delphi"는 잠정적으로 마무리하려고 합니다.



오디오 기준 싱크의 문제점


현재까지 진행했던 강의는 제가 2년 전에 진행한 어떤 프로젝트의 부산물을 정리한 것입니다.  원래는 안드로이드 플랫폼이 타켓인 프로젝트였는데, ffmpeg을 처음 다룬 것이어서, 일단 익숙한 델파이로 구현을 해보고 안드로이드로 다시 구현하는 과정을 겪었습니다.


해당 프로젝트의 특이성 때문에 저에게는 문제가 없는 방식이었으나, 일반적인 방식에서는 고려해야 할 사항 들이 있습니다.


그 중 하나가 바로, 오디오가 없는 비디오를 다룰 때입니다.  헉!!  


가끔은 일부 구간 또는 전체 구간에 오디오가 없는 경우가 있습니다.  오디오가 없는 구간에서는 비디오가 미친듯이 빠르게 재생됩니다 ^^;;


따라서, 이런 경우를 다루기 위해서는 "libRyuMPEG.dll" 소스에서 오디오 부분에서만 현재 재생 위치를 가져오는 것을 비디오에서도 가져와야 합니다.  아래 소스를 참고하세요, 빌드하지 않고 메모장에서 수정해서 올립니다 ^^;


이후 오디오에서처럼 비디오가 너무 바쁜 경우에도 패킷 처리를 유보시켜주시면 됩니다.

extern int readData(RyuMPEG_Handle *pHandle, void *pData, int *pDataSize, int *pPacketType, int *pPosition) {
	AVPacket packet;

	 av_init_packet(&packet);

	if ((pHandle->isEOF == false) && (av_read_frame(pHandle->pFormatCtx, &packet) >= 0)) {
		if (packet.stream_index == pHandle->audioStream) {
			*pPacketType = AUDIO_PACKET;

			pHandle->currentPosition = pHandle->pFormatCtx->streams[pHandle->audioStream]->cur_dts *
					av_q2d(pHandle->pFormatCtx->streams[pHandle->audioStream]->time_base)*1000;
		} else if (packet.stream_index == pHandle->videoStream) {
			*pPacketType = VIDEO_PACKET;


			pHandle->currentPosition = pHandle->pFormatCtx->streams[pHandle->videoStream]->cur_dts *
					av_q2d(pHandle->pFormatCtx->streams[pHandle->videoStream]->time_base)*1000;
		}

		*pDataSize = packet.size;

		if (packet.size <= READ_BUFFER_SIZE) {
			memcpy(pData, packet.data, packet.size);
		}

		*pPosition = pHandle->currentPosition;

		av_free_packet(&packet);

		return true;
	} else {
		pHandle->isEOF = true;

		*pDataSize = 0;
		*pPacketType = UNKNOWN_PACKET;
		*pPosition = 0;

		return false;
	}
}



비디오 디코딩을 원할하게


두 번째 강의에서 첫 번째 강의를 정리해서 다시 보내드린 것처럼, 매 강의를 단계적으로 진행하면서 현재 포커스가 되는 부분은 막코딩을 하고 이전 진행 된 부분 정리하는 형식을 취하려고 했습니다.  이유는 OOP 등을 남발하게 되면 초보분들이 따라오기가 어렵기 때문입니다.


두 번째 강의에서는 메인 스레드에서 패킷을 무한히 반복하면서 읽고 처리하고 있는데, 패킷 읽기도 비디오 디코딩도 오디오와 같이 별도의 스레드에서 처리되는 것이 좋습니다.



가장 많이(?) 요구받은 자막 처리


자막 처리는 대략 십 여년 전 DSPack에 포함 된 동영상 플레이어 위에 오버레이를 씌우다가 하도 에러가 나서 꼼수로 넘어간 이후 다룰 일이 없어서 저도 준비가 필요한 부분입니다.  (오버레이 용어가 맞는지 기억도 잘 ^^;)


꼼수는 바로 양병규님이 오픈해주신 BitmapRgn 이라는 유닛을 이용해서 자막 크기의 윈도우에 자막을 그리고, 자막 이외의 공간은 구멍내서 비디오 화면 위에 덮어 씌우는 것입니다.  다루기도 편하고 특별히 문제가 될 만한 것은 없어서 그렇게 마무리 하고 말았습니다.


제 강의 프로그램에 보면 화면 크기를 변경 할 때 아래 그림처럼 구멍 뚫린 공간 안에 글자(숫자)가 쓰여져 있습니다.  같은 원리를 이용한 것 입니다.



해당 유닛은 제가 조금 수정한 상태로 다음과 같은 링크에서 구하실 수가 있습니다.

http://code.google.com/p/ryulib4delphi/source/browse/trunk/XE2/BitmapRgn.pas



부족한 강의에 참석해주신 분들에게 다시 한 번 감사드리며, 보다 재미있는 주제를 준비해서 조만간 다시 온라인 강의를 진행하겠습니다 ^^*



Posted by 류종택

너무 오랫 동안 글을 쓰지 않았더니,

글쓰기 기능이 퇴화한 것 같다.


슬픈데 슬픔을 표현 할 방도가 없다.

글자는 눈물을 흘리지 않으므로,

스스로 소리 내어 홀로 우는 것 밖에 할 수 없다.


바른 마음을 갖는 것보다 중요한 것은 강해지는 것이다.

바른 마음이 행동으로 싹 틀 수 있도록...


Posted by 류종택

투표 합시다!

etc 2012.12.10 22:09

찍을 넘이 없어도 투표는 거르지 맙시다.


제일 덜 싫은 넘이라도 찍어줘야,

조금씩이라도 세상이 바뀌게 됩니다.


적어도, 

정친인들이 유권자를 무서워해야,

나라가 바로 섭니다.


당신이 오른쪽이든 왼쪽이든,

투표 거르지 맙시다!!





Posted by 류종택




"딜레이 없는 실시간 방송 솔루션"


http://www.himytv.com



제가 1998년 즈음부터 관심을 가지고 꾸준히 개발해왔던 PC 화면 압축 및 전송 기술을 적용한 솔루션입니다.  압축률과 속도 등에서 월등한 성능을 보여주고 있습니다.


가장 많이 사용되는 분야는 증권 실시간 방송 입니다.


Full HD 가 넘는 해상도를 화질의 열화를 느낄 수 없는 수준으로 실시간 압축 및 전송이 가능합니다.


백문이 불여일견!!


무료체험 신청하기http://demo.himytv.com/list




Posted by 류종택




델파이를 통해서 프로그래밍을 가르쳐 보니, 재미있어하고 계속 배우려고는 했으나, 제가 판단 했을 때 아직은 무리다 싶어서 포기했었습니다.  그 뒤로도 간간히 프로그래밍 가르쳐 달라고 조르던 아이를 타일렀지요 ㅡ.ㅡ;


"쉬우면서 질리지 않아야 한다"

이것이 제가 고집하는 입문자 교육의 가이드-라인 인데, 아무리 머리를 짜내도 쉬운 것을 유지하면서도 흥미를 가질만한 것을 찾을 수가 없더군요.  아드님의 연세와 현재 상태를 고려했을 때, 역시나 델파이를 더 가르치는 것은 무리라고 판단 ㅋㅋ  (방년 만 6세 ㅡ.ㅡ; 우리 나라 나이 8살 생일을 두 달 남기셨음)


그러다가 Scratch를 한 번 훝어 보니, 왜 그동안 이 넘을 그렇게 무시했었던지 ㅡ.ㅡa

생각보다 상당히 잘 만들어져 있더군요.  아쉬운 점이 많긴 하지만 ㅡ.ㅡ;;

이제 곧 2.0도 나올 것으로 보이는데, 아쉬웠던 점들이 다소 해소 될 것으로 보입니다.


여하튼 요즘 Scratch의 매력에 푹 빠져 있는 아드님 ㅋㅋ

하지만, 제가 사업 때문에 시간이 없어서 혼자 책을 보며 독학 중이십니다.


처음에는 책을 보며, http://scratch.mit.edu/projects/do-young/2742885 이런 것을 만드시더니, 이후 게임 비수구리 한 것들을 만드시길래, 요즘 틈 나는 대로 슈팅 게임을 가르쳐 드리고 있습니다.  아래의 링크가 진행 중인 상태입니다.  아직은 우주선으로 지나가는 행성 터트리기 ㅡ.ㅡ;;


http://scratch.mit.edu/projects/do-young/2769850


외국인에게서 댓글도 받으심.  아드님에게 답변을 물어서 제가 달아드렸습니다 ㅋㅋ


슈팅 게임 만들면서 이미 도영군의 이해 수준의 한계를 넘어섰기 때문에 어제부터는 기초적인 부분을 다시 반복하고 있습니다.  이 고비를 아드님께서 넘기실지 모르겠지만, 안되면 다시 중단하고 다른 흥미 거리를 찾아줘야 할 듯..  


제일 문제가 되는 것은 if문 제어가 반복 될 수 밖에 없을 때, 하나의 동작을 제어하는 코드(?)의 길이가 길어졌을 때, 전체의 그림을 놓치고는 합니다.  그래서, 당분간은 분기문이나 제어문에 대한 훈련을 양념처럼 넣어서 재미있고 간단한 예제를 만들어 가려고 합니다.  도영군 교육이 다 끝나면 책이라도 한 권? ㅡ.ㅡa


근래, 바둑에 부쩍 관심을 가지셔서 어린이 바둑 교실을 알아보는데, 집 근처에는 없네요.  바둑은 웬지 가르치고 싶지 않은 개인적인 ㅡ.ㅡ;;  하지만, 자신이 원하니 ㅡ.ㅡ;;;


사실 언어를 직접 만들어서 도영군을 가르치려고 몇 년 전부터 준비했었는데, 손 놓은 지 오래 되었네요..  Scratch가 맘에 들지 않아서 요즘 다시 고민에 빠졌습니다.


여하튼 저처럼 아이들에게 프로그래밍을 가르치려는 분들에게는 Scratch가 현재로서는 가장 훌륭한 대안이 아닐 까 합니다.  내년에 도영군 학교에서 부모 수업 신청을 다시 받으면, 스크래치 프로그래밍을 가르쳐 볼 생각입니다.


아차!!  여유 되시는 분들은 도영군에게 응원 한 마디씩 부탁합니다 ^^;

(어제 외국인 댓글 받고 업 되셨음 ㅋㅋ)


추천서적: http://www.aladin.co.kr/shop/wproduct.aspx?ISBN=8962100932&start=slayer



Posted by 류종택

http://www.programmingbasics.org/en/


내가 만들려고 오래 전부터 메모하고 기획하고 준비하던  것과 조금 유사하다.  8살짜리 아들 넘에게 시켜보니 꽤나 재밌어 한다.  오래 전에 파스칼을 가르쳤던 것을 조금씩 기억해 내면서 그날 끝까지 달려 버렸다.


아이들을 위한 언어를 만들려고 컴파일러 제작에 대한 공부도 틈틈히 했었는데, 선수 당한 느낌 ㅋㅋ


Posted by 류종택

아닌 밤에 갑자기 어느 게시판에서 봐서 함 풀어 봤습니다.


a(n)이 n자리수까지 8의 개수라고 하면, 우선 a(0) = 0으로 두고 (당연히 0자리수는 아무것도 없으니)


a(n) = a(n-1)*10 + Power(10, n-1)


즉, 

a(1) = 1  (0부터 9까지 8의 개수는 1, 10을 넣어도 마찬가지)

a(2) = 1 * 10 + Power(10, 2-1) = 20

a(3) = 20 * 10 + Power(10, 3-1) = 300

a(4) = 300 * 10 + Power(10, 4-1) = 4000


따라서, 1부터 10000까지의 8의 개수는 4000개!


왜냐하면, 이전 자리의 덧샘이 10번 반복되고, 이전 자리에 없었던 자리에 8의 자리가 10배씩 증가한다.


이전 자리에 없었던 자리에 8의 자리라함은, 1부터 10에는 없었던 80-89 이렇게 10개 늘어나고,

1부터 100에 없었던 800-899 이렇게 100개 늘어나기 시작한다.  

이때 맨 앞에 8이하는 이미 셈에 들어 갔기 때문에, 88 등을 고민 할 필요가 없다.


풀고나서 검색해보니, 더 쉽게 찾으신 분도 있군요.

0000부터 시작해서 9999로 모든 숫자 개수는 4 * 10000 개 (0이 아니라 0000처럼 세는 것에 주의)

8은 1/10의 비율로 존재하니까 4000 개라고 푸신 분이 있네요.

'etc > 프로그래밍 퀴즈' 카테고리의 다른 글

구글 입사 문제 1부터 10000까지 8은 몇 개?  (3) 2012.06.22
Slump Slimp Slurpy  (0) 2012.04.25

Posted by 류종택

New Rally X - BGM

etc 2012.04.30 16:55

작업하면서 마음을 안정시키기 위해 듣는 용도로 사용 중인, 일명 방구차의 BGM ㅋㅋ

디아블로와 스타크래프트 BGM도 사용한 적이 있었는데, 문제는 이게 반복할 때 끊어지는 느낌이 문제네 ㅡ.ㅜ







Posted by 류종택

오랫만에 놀러 간 사이트에 퀴즈가 올라와 있길래 한 번 풀어봤습니다.  컴파일러와도 연관이 있어서 분류를 컴파일러로 해뒀습니다.


문제 : http://codejob.co.kr/code/view/109/


우선 문제를 BNF로 표시해 보면 다음과 같습니다.


<Slurpy> --> <Slimp> <Slump>

<Slimp> --> AH | AB<Slimp>C | A<Slump>C

<Slump> --> D<F>G | E<F>G | D<F><Slump> |  E<F><Slump>

<F> --> F | F<F>


이제 YACC를 이용해서 문제를 가볍게 해결할 수도 있겠지만, 퀴즈를 풀어 보는 것이 목적이므로 직접 코딩을 해보도록 하겠습니다.


코딩을 하기 이전에 위의 BNF가 어떻게 처리되야 하는 지를 [그림 1]과 같이 약간 변칙적인 상태도로 표현해봤습니다.  이전 포스트(http://ryulib.tistory.com/73)에서도 상태도를 이용해서 BNF를 표현하는 방법에 대해서 설명했듯이, 저는 상태도를 이용하여 BNF를 저만의 방식으로 처리하고 있습니다.  이번에는 좌측 결합은 없고 우측 결합만이 있기 때문에 비교적 간단하게 처리할 수가 있습니다.  읽는 방법은 제가 프로젝트에 쫓기고 있어서 생략합니다.  첨부하는 코드를 참고하시기 바랍니다.



[그림 1] BNF를 상태도로 표현


[소스1] 

procedure TfmMain.FormCreate(Sender: TObject);
var
  Slurpy : TSlurpy;
begin
  Slurpy := TSlurpy.Create;
  try
    Slurpy.Source := 'AH';
    moMsg.Lines.Add(Format('%s IsSlimp is %s', [Slurpy.Source, BoolToStr(Slurpy.IsSlimp, true)]));

    Slurpy.Source := 'ABAHC';
    moMsg.Lines.Add(Format('%s IsSlimp is %s', [Slurpy.Source, BoolToStr(Slurpy.IsSlimp, true)]));

    Slurpy.Source := 'ADFGC';
    moMsg.Lines.Add(Format('%s IsSlimp is %s', [Slurpy.Source, BoolToStr(Slurpy.IsSlimp, true)]));

    Slurpy.Source := 'ABAHD';
    moMsg.Lines.Add(Format('%s IsSlimp is %s', [Slurpy.Source, BoolToStr(Slurpy.IsSlimp, true)]));

    Slurpy.Source := 'AHDFG';
    moMsg.Lines.Add(Format('%s IsSlurpy is %s', [Slurpy.Source, BoolToStr(Slurpy.IsSlurpy, true)]));

    Slurpy.Source := 'DFGAH';
    moMsg.Lines.Add(Format('%s IsSlurpy is %s', [Slurpy.Source, BoolToStr(Slurpy.IsSlurpy, true)]));
  finally
    Slurpy.Free;
  end;
end;

[소스 1]은 IsSlump, IsSlimp, IsSlurpy를 판별할 수 있는 TSlurpy 클래스의 사용방법입니다.  7: 라인에서처럼 Source 멤버에 검사할 문자열을 입력하시고,  IsSlump / IsSlimp / IsSlurpy 등의 메소드를 통해서 해당 문자열이 Slump인지, Slimp인지 또는 Slurpy인지를 판별할 수가 있습니다.


[소스 2]

function TSlurpy.IsSlurpy: boolean;
begin
  try
    Result := IsSlimp and IsSlump;
  except
    Result := false;
  end;
end;

function TSlurpy.IsSlimp: boolean;
begin
  try
    FState := FStateSlimpBase;
    FIsFinished := false;
    repeat
      FState.Execute;
    until FIsFinished;

    Result := true;
  except
    Result := false;
  end;
end;

function TSlurpy.IsSlump: boolean;
begin
  try
    FState := FStateSlumpBase;
    FIsFinished := false;
    repeat
      FState.Execute;
    until FIsFinished;

    Result := true;
  except
    Result := false;
  end;
end;

[소스 2]는 TSlurpy 클래스의 일부분 입니다.  IsSlurpy는 간단하여 설명을 생략합니다.  IsSlimp와 IsSlump의 경우에는 동일한 구조이기 때문에 IsSlimp만을 설명하도록 하겠습니다.


13: [그림 1]에서는 모든 시작을 Base라고 표시했으나, IsSlimp를 점검하는 조건만을 보면 처리가 달라지기 때문에 FStateSlimpBase를 기초 상태로 두고 있습니다.


14-17: FIsFinished가 true가 될 때까지 반복을 하고 있습니다.  FIsFinished가 true라는 것은 현재의 검사가 성공적으로 완료되었다는 의미입니다.  반복하는 동안 계속 현재의 State의 Execute 메소드만을 실행합니다.  내부적으로 무엇을 할지는 캡슐화되어 있어서 정확히는 모르겠지만, 조건이 만족되거나 에러가 날 때까지 계속 문자열을 검색한다는 것을 직감적으로 알 수가 있습니다.


21: 만약 파싱 중간에 에러가 있다면 Exception을 발생시킬 에정입니다.  Exception이 발생된다면, 현재의 점검은 false로 리턴합니다.


[소스 3]

procedure TStateSlimpBase.Execute;
begin
  case Next of
    'A': begin
      SetState(FSlurpy.FStateSlimpA);
    end;

    else raise Exception.Create('Error in ' + ClassName);
  end;
end;

3: 다음 문자를 읽어 들입니다.


4-6: 'A'라는 문자열이면 상태를 FStateSlimpA로 옮겨서 검사를 계속 진행합니다.


8: 다른 문자열이 온다면 에러 입니다.


[소스 4]

procedure TStateSlimpA.Execute;
begin
  case Peek(1) of
    'H': begin
      Next;
      FSlurpy.FIsFinished := true;
    end;

    'B': begin
      Next;
      SetState(FSlurpy.FStateSlimpAB);
    end;

    else begin
      if FSlurpy.IsSlump then FSlurpy.FState := FSlurpy.FStateSlimpASlump
      else raise Exception.Create('Error in ' + ClassName);
    end;
  end;
end;

3: 이번에는 다음 문자열을 읽지 않고, 살짜 훔쳐(Peek) 봅니다.


4-7: 'H' 일 경우에는 검사가 완료되고 IsSlimp는 true를 리턴하게 됩니다.  훔쳐보기만 했기 때문에, 문자열 하나를 읽어서 다음 문자를 읽을 준비가 되도록 합니다.


9-12: 'B' 일 경우에는 상태만 계속 옮겨가면서 검사를 하게 됩니다.


14-17: 'B', 'H' 가 아닌 경우에는 다음에 <Slump>가 오게 되는 지 검사하게 됩니다.  이러한 과정들은 당연히 [그림 1]에 표시된 그대로 입니다.  3:에서 Next를 해서 문자를 읽어 버리면, IsSlump가 한 자를 생략한 채로 검사하게 되기 때문에 Peek(1)로 훔쳐보기만 한 것 입니다.  다음에 <Slump>가 오지 않는 다면 에러입니다.


다른 모든 상태에 대해서도 원리는 같습니다.  


이처럼 상태도를 이용해서 State Pattern을 사용하다보면 전체의 줄거리와 각각의 구현이 완전히 분리가 되어 개발이 한층 쉬워지는 것을 알 수가 있습니다.  사실 이 문제 정도의 복잡도라면 State Pattern을 사용하지 않고 재귀 호출만으로도 쉽게 구현되기는 하지만, 코드의 가독성은 State Pattern이 훨씬 앞선다고 생각합니다.


언제가 될 지, 프로젝트가 한가해지면 그 동안 공부했었던 컴파일러 제작에 대한 강좌를 하고 싶지만, 잘 안되네요 ^^;


아래 첨부는 소스 전체와 사용된 비지오 파일입니다.  샘플이 작아서 제대로 동작하고 있는 지는 잘 모르겠습니다 ^^;


Slump Slimp Slurpy.7z











'etc > 프로그래밍 퀴즈' 카테고리의 다른 글

구글 입사 문제 1부터 10000까지 8은 몇 개?  (3) 2012.06.22
Slump Slimp Slurpy  (0) 2012.04.25

Posted by 류종택


티스토리 툴바