원래는 한 포스트로 다 쓸려고 했는데, 쓰다보니 너무 길어지고 continueAfterSetup에서 설명해야 할 것들이 너무 많아지는 감이 있어서 포스트를 두개로 나누었다. 아는 것도 어렵지만 아는 것을 글로 남기는 것은 전혀 또 다른 문제인 것 같다. ㅎㅎ

 

그럼 RTSP서비스로부터 SDP를 받고 이로부터 각 SubMediaSession에대해 setup명령을 전송하는 과정까지 설명했던 지난 포스트에 이어서 이번 포스트에서는 이 이후 단계인 continueAfterSetup부터 살펴보자.

 

continueAfterSetup은 RTSP서버로 SETUP명령을 보내는 sendSetupCommand 함수가 호출 된 후 서버로부터 처리 완료 신호가 클라이언트 단으로 도착했을 때 실행되는 콜백함수로 sendSetupCommand함수의 인자로 전달되었었다. 여튼 setup이 완료되어서 미디어 데이터를 전달받을 환경이 조성되어 실행되는 continueAfterSetup은 아래와 같다.

 

역시 잘 안보이지만 눈을 크게 뜨고 보자.. =_=

 

콜백으로 전달되는 함수들은 모두 static이기 때문에 인자로 전달되는 rtspClient 포인터로부터 이 함수가 어떤 rtspClient의 콜백으로 호출되었는지 밝히는 부분이 함수 첫 281 ~ 287에 나와있다. 이건 뭐 그다지 중요한 내용은 아니고 중요한 내용은 297로부터 시작된다.

 

사실 297이 이 전체 샘플코드의 핵심이다.

이 코드를 이용해서 전체 샘플코드가 사용할 파이프라인을 구성하고 있기 때문이다.

297줄에 보면 DummySink 인스턴스를 하나 만들고 subsession이 가지고 있는 sink에 할당한다.

이 과정을 통해 이DummySink가 subsession에 연결되어

 

"subSession (Source를 포함하는 객체) - DummySink"

 

구조의 파이프라인이 구축된다. 그리고 나서 306줄과 같이 startPlaying함수를 통해 파이프라인이 동작하도록 명령한다. 단, 이 playing이라는 의미는 파이프라인이 활성화 상태로 들어가게 한다는 의미이지 실제로 미디어 서버쪽에 미디어를 재생하라는 RTSP의 PLAY신호를 보낸다는 의미는 아니다.

 

실제 PLAY신호는 모든 Subsession들에 대해서 setup이 완료 되고 난 이후에 sendPlayCommand라는 함수에 의해서 RTSP서버쪽에 전달되어 실제 데이터 전송을 요청하게 된다.

 

==추후 갱신==

Posted by 굿쟌
,

Live555에 보면 RTSP스트림을 받을 수 있는 소스코드를 이미 제공하고 있다.

하나는 openRTSP.c이고 다른하나는 testRTSPClinet.c라는 소스코드이다.

 

openRTSP는 RTSP규약을 전부다 구현해 놓은 Client 프로그램으로 재생 정지 되감기 등의 RTSP기능을 가지고 있다. 즉, 좀 보기 복잡하다.. ㅎㅎ 반면에 testRTSPClient는 RTSP스트림을 받는 것만 구현이 되어 있어서 기능은 그닥 뭐 볼건 없지만, Live555가 제공하고 있는 파이프라인 구조라든지 RTSP client와 server간의 동작을 간단히 살펴 보는데는 더할 나위 없이 좋다. 

 

무엇보다도 내가 만들고자 했던 프로그램은 항상 Live영상만 H.264로 전달하는 IP Camera의 RTSP서비스를 활용하기 때문에 다양한 RTSP규약의 기능을 별로 알필요가 없어 구지 어려운 소스코드를 볼 필요가 없었다. =_=

 

어쨋든 이번 포스트에서는 testRTSPClient를 대상으로 해서 어떻게 RTSP서비스로부터 데이터를 수신할 것인지, 그리고 어떻게 파이프라인을 구성해서 활용하고 있는지를 살펴볼 것이다.

 

먼저 main부터 살펴보자

 

이미지로 찍어 올렸더니 잘 안보인다.

소스코드는 testRTSPClient.c에 들어가 있기 때문에 그 소스코드를 열어 보시면 좋겠다.

어찌됐든 위 부분이 console타입의 testRTSPClient프로그램의 도입부인 main함수이다.

 

생각외로 매우 조촐하다.

보이는 함수도 별로 없고 심지어는 지역변수도  별로 없다.

 

Main함수가 이토록 조촐한 이유는 Live555가 MFC의 메시지 드리븐 방식과 비슷하게 동작하기 때문이다.

(메시지 드리븐이라는 말은 큐에 메시지가 들어가고, 각 메시지가 발생한 순서대로 처리되는 방식을 의미한다.)

 

Live555에서는 이런 메시지 드리븐 방식의 동작구조를 TaskScheduler가 제공하고 있으며, main초반부에 보면 TaskScheduler를 생성하여 사용하고 잇는 것을 볼 수 있다. 아울러 그 다음에 생성된 UsageEnvironment는 이전 포스트에서 설명 했듯이, OS와 Live555의 기능 사이에서 소켓등 OS에 종속된 기능들을 추상화 하여 Live555에 제공하는 역할을 한다. 이 클래스 때문에 Live555함수의 기능들은 OS 따라 함수코드를 바꿀 필요 없이, UsageEnvironment만 바라보고 소스코드의 일관성을 유지 할 수 있다. 실제로 OS에 따라 바뀌는 것은 UsageEnvironment이다.

(OS에 따라 UsageEnvironment를 상속하여 적합한 UsageEnvironment를 제공해야 하며 Windows, Linux에서는 BasicUsageEnvironment를 사용하면 된다.) 

 

어쨋든 main에서는 이제 프로그램의 아규먼트로 입력받은 RTSP URL를 인자로 하여 openURL함수를 호출한다.

그리고서는 메시지 큐가 정상적으로 동작을 시작하도록 taskScheduler의 doEventLoop를 실행한다.

이 doEventLoop는 글로벌 변수로 어떤 변수를 받는데, 이 글로벌 변수가 0이 아닌 값이 들어가면, 이벤트 루프를 종료하고 return 0; 문으로 넘어가 프로그램이 종료되게 한다.

 

그럼 이제 RTSP URL로 뭘하는지 실제 OpenURL 함수의 구조를 살펴보자.

openURL함수는 아래와 같다.

 

역시 이미지로 올렸더니 흐릿하니 잘 안보인다. testRTSPClient.c를 확인하시라.. =_=

 

openURL에서는 ourRTSPClient를 하나 생성해서 해당 URL로 대표되는 RTSP서비스를 처리하도록 하고 있다.

그 다음에 sendDescribeCommand를 이용해 RTSP서버로 DESCRIBE명령을 전송한다. sendDescribeCommand의 인자로 주어진 continueAfterDESCRIBE는 콜백함수 포인터로, 서버로 보낸 DESCRIBE명령에 대한 응답이 돌아왔을 때 호출될 콜백함수의 포인터이다.

 

RTSP서비스를 제공하는 RTSP서버에 DESCRIBE명령을 전송하면 RTSP서버는 클라이언트로 SDP ( Simple Description Protocol ) 문서를 전달한다. 문서라고 하니까 무슨 파일로 되어 있는 그런걸 떠올릴 수 있는데 파일은 아니고 단순히 어떤 문자열에 불과하다. 이 SDP문자열에는 해당 RTSP서비스가 제공하고 있는 미디어스트림의 종류 등 정보가 들어가 있다.

 

예를 들자면.. 아래와 같은 SDP는 오디오, 비디오 2종류의 미디어를 제공하고 있다는 의미이다..

 

m=audio 49170 RTP/AVP 0 8 97

a=rtpmap:0 PCMU/8000

a=rtpmap:8 PCMA/8000

a=rtpmap:97 iLBC/8000

m=video 51372 RTP/AVP 31

a=rtpmap:31 H261/90000

 

특히 "m=audio", audio미디어를 49170포트로 Realtime Transport Protocol / Audio Video Payloadtype

 에 정의된 0,8,97번 즉, PCMU/8000, PCMA/8000, iLBC/8000으로 제공하고

"m=video", video미디어는 51372포트로 Realtime Transport Protocol / Audio Video Payloadtype 에 정의된 31번

즉, H261/90000으로 제공한다는 의미를 가지고 있다는 의미이다.

 

물론 이런 정보를 알아내기 위해서 SDP를 공부할 필요는 없다.

Live555에서 SDP를 파싱해서 우리가 이해 가능한 정보로 바꿔주는 기능을 제공해 주니 두려워 않아도 좋다.

 

그럼 이제 continueAfterDESCRIBE함수에서 어떻게 이 SDP를 처리하고 있는지 살펴보자. 

허허... 그림이 커질수록 점점 더 안보이기 시작하고 있다..=_=

 

어쨋든 눈을 부릅뜨고 한번 보자. 참고로 이 continueAfterDESCRIBE는 static 함수로 어떤 rtspClient의 DESCRIBE명령로부터 콜백 된 함수인지 모르기 때문에 Live555에서는 함수의 인자로 rtspClient를 전달해준다. 그리고 3번째 인자로 resultString을 건네주는데 이게 SDP 스트링이다.

 

이걸 SDP를 파싱해서 쓰기 위해서 205줄과 같이 MediaSession을 만든다. 인자로 sdp문자열을 전달하는데 이렇게 되면 자동으로 SDP에 들어있는 미디어 스트림 정보마다 MediaSubsession이 생성되어 MediaSession에 추가된다.

 

그리고는 221 줄과 같이 각 MediaSubsession에 대해 setup을 시도한다. setup은 각 MediaSubsession이 제공하는 미디어 스트림데이터를 받을 수 있는 서버-클라이언트 간 환경을 조성하는 과정이다. setupNextSubsession( ) 함수는 rtspClient를 인수로 받아 이과정을 수행한다.

 

참고로 ourRTSPClinet를 살펴보면 아래와 같이 RTSP연결을 의미하는 MediaSession, RTSP연결에서 제공하는 미디어 스트림연결을 의미하는 MediaSubsession, 그리고 각 MediaSubsession을 순회할 수 있는 MediaSubSessionIterator정보가.. 정확히는 이런 정보를 가지고 있는 StreamClientState 인스턴스가 들어가 있다.

 

 

자세한 부분은 이 포스트를 전체적으로 한번 읽어보고 중단점을 걸어 한단계씩 넘어가다보면 어떻게 ourRTSPClient가 각 MediaSubSession등의 정보를 저장하고 있는지 알 수 있을 것이다.

 

자, 그럼 이제 setupNextSubsession에서 어떻게 서버 - 클라이언트간 미디어 스트림 데이터 수신 환경을 구축하고 있는지 살펴보자. 아래는 setupNextSubsession이다.

 

 

허허.. 쪼매 길다. 코드가

 

 

어쨋든 처음부터 차근차근 살펴보자..

다소 복잡해 보이지만, 전체 적인 코드 구조는 아래와 같다.

 

    1. 인자로 넘겨받은 rtspClient포인터를 전달받아 ourRTSPClient객체를 찾는다.

    2. ourRTSPClient객체의 StreamClietState멤버로부터 MediaSession이 가진 MediaSubsession을 순회

    3. 각 MediaSubSession에 대해 Initiate를 호출해 초기화한다.

    4. 각 MediaSubSession에 대해 sendSetupCommand명령을 전송한다.

    5. 모든 MediaSubSession에 대해 sendSetupCommand를 전송하고 순회가 완료 (NULL)

    6. sendPlayCommand로 Play Command를 RTSP서버로 전송한다.

 

이 setupNextSubsession함수는 모든 MediaSubsession을 순회하도록 재귀함수 구조로 되어 있는데, MeidaSubsession이 실패한 경우는 이 함수내에서 직접적으로 setupNextSubsession을 247줄과 같이 호출해서, 성공한 경우는 sendSetupCommand의 인자로 전달한 continueAfterSetup 콜백에서 재귀적으로 다시 호출된다.

Posted by 굿쟌
,

Live555는 데이터를 처리하기 위해 데이터 파이프라인 구조를 제공하고 있다.

말이 어렵지만 사실 별거 없다.

파이프라인을 제공한다는 말은 물이 파이프를 따라 흐르듯이 데이터도 어떤 순서에 따라 파이프라인의

각 부분을 통과하게 되고, 각 부분에서 필요한 처리가 이뤄진다는 의미이다.

 

아울러 물이 파이프라인의 한쪽 방향에서 다른 방향으로만 흐르듯이

Live555에서도 데이터는 항상 한쪽 방향으로만 흐른다.

 

Live555의 파이프라인 구조는

단순히 데이터를 생산해 내다음 단계로 넘기는 Source,

데이터를 다음 단계로 넘기기도 하지만 받기도 하는 Filter

데이터를 받기만 하는 Sink로

이뤄져 있으며

 

Source -> Filter -> Filter -> Sink 혹은

Source -> Filter -> Sink 또는

Source -> Sink 와 같이 구성 할 수 있다.

 

Filter는 데이터를 전달하는 특성이 Source와 동일하기 때문에 클래스 구조상 Filter는 Source를 상속받은

클래스 이고 따라서 Filter에 대한 참조를 Source클래스의 포인터로 해도 무방하다. (다형성)

 

파이프라인을 구성하면 Sink는 자신의 왼쪽에 있는 Source에 대한 포인터를 가지고 있으며

그 포인터를 이용해 해당 Source의 getNextFrame함수를 호출함으로써

아래와 같이 그 Source에 데이터를 받을 Sink의 버퍼

데이터를 받고나서 실행될 콜백함수의 포인터인 afterGettingFunc

Source가 더이상 데이터를 만들어 내지 않을때 실행될 콜백의 함수포인터인 onCloseFunc등을 전달한다.

 

<코드가 잘 안보이는 건 기분탓이다. =_=>

 

그러면 Source에서는 전달받은 정보를 모두 자신의 멤버변수에 저장하고

doGetNextFrame을 통해 실제 데이터를 전달 받는 동작을 수행한다.

 

doGetNextFrame은 virtual로 선언되어 있는 순수 추상함수인데

그 이유는 이 Live555에서 제공하는 Source의 데이터가 여러 데이터원에 기인하기 때문이다.

H.264데이터도 있고, 263도 있고, 데이터 종류에 따라 데이터를 전달하기 이전에 수행해야 할 동작들이 많이 때문에

아래에 보이는 엄청나게 많은 Source원에서 각각 이 doGetNextFrame()을 override해서 필요한 동작들을 수행한다

 

 

 

<겁나게 많다 하지만 모두 다 알 필요는 없다>

 

아울러 이 doGetNextFrame에는 자신의 앞쪽에 있는 또다른 Source

로부터 다시 데이터를 수신하는 getNextFrame()을 호출하고 이 과정이 계속 반복되면서

최종적으로 Sink에 데이터가 전달된다.

 

그리고 각 단위마다 데이터를 전달받고 나서 실행되는 afterGettingFrame함수가 있어서

자신의 앞 블록으로부터 데이터를 받으면, 아래 코드와 같이 afterGettingFunc 콜백함수 포인터를

호출해 다음 블록에 데이터가 왔음을 알리게 된다.

 

(이 함수포인터는 getNextFrame때 자신의 오른쪽 블록으로부터 넘겨 받았었다.)

 

 

 

사실 이런 사항은 그다지 자세히 할 필요는 없다. =_=

소스코드가 잘 구현되어 있기 때문에 그닥 변경할 필요도 없을 뿐더러, 

LGPL라이선스로 이 코드를 사용하고 싶다면 이 소스코드를 변경해서도 안된다.

 

다만 이런 사항을 잘 알고 있다면, 자신만의 기능을 가지고 있는

Source나 Filter 혹은 Sink등을 만들어 사용할 때 좀더 도움이 될 수 있을 것 같다.

 

참고로 필자는 H.264데이터를 디코딩해서 글씨를 씌우고 다시 H.264로 인코딩 한 데이터를 RTPSink에 전달하는

Filter를 만들어서 사용하고 있는데 이런 구조를 이해하는게 썩 많은 도움이 되었다.

Posted by 굿쟌
,

Live555을 이용해서 RTSP로 전달되는 (엄밀히 말하자면, RTP로 전달되는) H264 스트림을 전달 받는 방법을 알 게 되었다. 그런데 막상 받아놓은 바이트 배열을 디코딩 하려고 보니 이 배열의 어디부터 어디까지를 넣어야 하는지가 막막했다. Live555에서도 h264FramedSource와 같은 소스 필터 클래스를 제공하긴 하지만 H.264자체에 대해서는 완전 까막눈이이다보니 어떻게 활용을 해야 할지는 더 막막해 차라리 디코딩을 위해 공부해야 하는 FFmpeg부터 역으로 접근하면서 H.264에 대해 공부하면 어떨까 하고 FFmpeg부터 공부하기로 결심했다.

 

사실 이전에 XProtect라고 하는 Miliestone사의 VMS로부터 한프레임의 H.264 데이터를 통으로 받아 디코딩 해본 경험은 있지만 막상 다시 새로운 프로젝트를 만들어서 공부를 하려고 하니 어떻게 FFmpeg를 Visual studio 2010에 연동했었는지 가물가물했다. 그래서 아예 이 과정을 정리해 공유하면 나도 나중에 참고해서 사용 할 수 있고, 다른사람들도 보고 좋을거 같아 연동하는 과정을 기록해 보고자 한다.

 

Step 1. 일단 라이브러리를 다운 받아야 된다.

 

물론 FFmpeg는 오픈 소스이니까 소스코드를 다운 받고, mingw와 msys으로 gcc를 활용할 수 있는 환경을 만들어 직접 빌드해 사용 할 수 있다. 그러나 그냥 나같이 라이트하게 사용만 하고자 하는 목적이라면, 이 과정이 그닥 정신건강에 유익하지 않다. 소스 코드를 컴파일 하다가 에러 하나가 띡 하고 올라와 구글링해서 고치면 다른 에러가 띡 올라오고 그 과정에서 멘탈이 남아나질 않는다.

 

<에러와 함께 내 멘탈도 부서졌다>

 

 

하지만 인터넷 세상에는 착한 사람들이 참 많다.

https://ffmpeg.zeranoe.com/

 

요기에 가보면 어떤사람이 windows에서 사용할 수 있는 FFmpeg의 빌드 결과물을 그것도 32bit, 64bit 각자의 구미에 맞게 제공하고 있다. 홈페이지에서 build탭으로 이동해 보면 현재 최신버전.. 인지는 모르겠지만, 꾸준히 최신버전의 FFmpeg버전의 빌드를 제공하는지 제법 최신 버전의 FFmpeg빌드를 제공하고 있다. 이글을 작성하는 시점으로는 git-3ecc063을 빌드해서 올려놨다.

 

 

 

 

32bit, 64bit으로 빌드 해 놓은 것은 각자가 만들고 싶은 프로그램의 빌드 타겟과 일치하는 버전의 바이너리를 다운 받으면 되겠고... Static, Shared, Dev를 설명하자면.. 음.. 일단 ffmpeg는 라이브러리가 아니다 원래는 콘솔프로그램인데 이 콘솔 프로그램이 사용하는 많은 외부 라이브러리가 있다. 그 외부 라이브러리들을 이용해서 ffmpeg라는 콘솔프로그램이 뭐 미디어 파일을 변경하고 하는 일을 하는것이다.

 

그래서 이 ffmpeg라는 콘솔프로그램을 만들때 이 외부라이브러리 소스코드를 한데다가 싹 다 모아서 하나의 exe로 만들것인지 아니면 외부라이브러리를 dll형태로 빼 내어 다소 많은 소스코드가 들어간 exe를 가볍게 할 것인지 결정할 수 있다. 전자는 static이라고 부르고 후자는 shared라고 부른다. 그래서 각각 받아보면 static은 exe 3개만 떨렁 들어 있고, shared에는 dll파일 여러개와 exe파일 3개가 들어있다.

 

마지막으로 Dev는 shared에 들어 있는 dll파일을 개발자가 사용할 수 있도록 헤더파일과 lib파일을 제공하는데 이 파일들을 이용해서 FFmpeg가 가지고 있는 기능들을 개발자가 직접 사용할 수 있게 되는 것이다.

 

뭐 어찌됐든 다 받자. 공짜다.

 

Step 2. Visual sutdio 2010 프로젝트에 연동하기

 

아래 파일들은 shared를 눌러서 다운 받은 파일들이다. ffmpeg.exe, ffplay.exe, ffprobe.exe는 모두 다른 dll로 모듈화 되어 있는 기능을 참조하여 가져다 쓴다. 우리가 visual stduio 2010 프로젝트에 연동해야 하는 기능들도 이 dll파일들에 들어 있으니 이 dll파일을 가져다가 visual studio 프로젝트의 실행파일이 생기는 폴더로 몽땅 이동시키자.

 

<다운로드 싸이트에서 shared를 선택해 다운 받은 파일들>

 

다음으로는 프로젝트에 헤더파일과 라이브러리 파일을 가져다가 사용할 차례다. 헤더파일과 lib파일은 Dev폴더의 lib, include폴더에 들어 있다. visual studio 2010프로젝트가 해당 헤더와 lib파일의 위치를 알 수 있도록 아래와 같이 Visual studio 2010의 프로젝트 속성에서 포함파일 위치랑 라이브러리 파일 위치를 지정해준다.

 

 

<추가 포함 디렉터리>

 

<추가 라이브러리 디렉터리>

 

다음으로는 이제 헤더와 lib파일의 위치를 알려줬으니까 직접 포함도 해보고 lib파일을 연결시켜보자.

콘솔 main()함수가 있는 파일에 쓰든지 아니면 미리컴파일된 헤더에 쓰든지 적당한 곳에 아래 구문을 넣으면 된다.

 

extern "C" //FFmpeg가 C라이브러리이기 때문에 이부분이 필요하다.

{

#include "libavcodec\avcodec.h"
#include "libavdevice\avdevice.h"
#include "libavfilter\avfilter.h"
#include "libavformat\avformat.h"
#include "libavutil\avutil.h"
#include "libpostproc\postprocess.h"
#include "libswresample\swresample.h"
#include "libswscale\swscale.h"

}

 

#pragma comment ( lib, "avcodec.lib" )
#pragma comment ( lib, "avdevice.lib")
#pragma comment ( lib, "avfilter.lib")
#pragma comment ( lib, "avformat.lib")
#pragma comment ( lib, "avutil.lib")
#pragma comment ( lib, "postproc.lib")
#pragma comment ( lib, "swresample.lib")
#pragma comment ( lib, "swscale.lib")

 

복잡해 보이지만 복잡할 것 없다.

그냥 include 폴더에 들어 있는 폴더 이름이랑 같은 헤더들 다 포함시키고 동일 이름의 lib파일을 다 연결시킨 것이다.

 

Step 3. 드디어 컴파일

 

자 그럼 이제 컴파일 해보자.

그러면???

아래와 같은 에러가 발생한다.

 

 

위 에러는 헤더파일 폴더에 있는 libavutil폴더의 common.h이라는 헤더파일에서 inttypes.h라는 헤더를 포함하고 있는데 윈도우 os에서는 해당 헤더가 없기 때문에 발생하는 에러이다.. 어떻게 해결할지 구글님께 물어보자

 

stackoverflow에 보면 같은 증상을 호소하는 사람이 있다. 이 사람에게 다른사람이 알려준 inttypes.h헤더 파일이 있는데 이를 다운받아서 설치하도록 하자.

 

 

<동일한 병을 호소하는 환자 1>

 

 

<처방전>

 

this file은 여기서 다운 받을 수 있다.

http://code.google.com/p/msinttypes/downloads/list

 

여튼 이걸 다운 받고 이전단계에서 설정했던 포함 파일 디렉터리에다가 복사하자. 그리고 나서 아래와 같이 libavutile/common.h의 #include <inttypes.h>를 아래와 같이 수정해서 다운받은 헤더파일을 포함하자.

참고로 < >가 아니라 " "포함시켰다. 사용자가 만든 헤더는 " "로 포함시켜야 한다.

 

 

 

그리고 나서 아래 코드를 적어서 빌드를 해보자. 빌드가 잘 된다.

 

 

그런데 문제가 있다. 디버그 모드에서는 빌드도 되고 실행도 잘 되는데 Release모드에서는 빌드는 성공하지만 실행시키면 프로그램이 죽어버리는 것이다.이 문제에 대해서는 여러곳에서 구글링을 해봤는데 Release모드에서 다음과 같이 최적화 옵션을 변경하면 해결되는 것을 확인했다.

 

 

 

이 에러는 각 옵션이 활성화 되어 있을 때 최적화 과정에서 원치 않는 작동을 유발하여 발생하는 에러로 보인다. 그렇기 때문에 위와 같이 각 COMDAT, 참조 최적화 옵션을 disable시키면 더이상 이 에러가 발생하지 않는다.

(참고로 다른 싸이트에서 보면 프로젝트의 캐릭터 셋을 not Set으로 설정해 놓아야 한다고 하는 곳도 있는데, 유니코드로 설정해 두어도 문제는 없을 것으로 보인다.)

 

자 이제 FFmpeg를 가지고 놀 수 있는 환경이 되었다. GoGo

Posted by 굿쟌
,

Live555는 RTSP 관련 기능을 쉽게 사용 할 수 있도록 도와주는 여러 클래스를 제공하는 오픈소스 라이브러리이다. 단, 이 라이브러리에는 미디어 데이터를 디코딩 하거나 인코딩 하는 기능은 없고, 단순히 수신, 전송과 관련 된 기능을 제공하니 미디어 데이터의 인,디코딩 기능을 이용하고 싶다면, FFmpeg이나 quick sync등의 다른 API를 이용해 해당 기능을 구현해야 한다.

 

이번 포스트에서는 Live555를 Visual Studio 2010에서 사용 할 수 있도록 빌드하는 과정을 설명할 예정이다.

 

먼저 빌드에 사용할 소스코드를 Live555에서 다운받아보자.

 

처음 Live555싸이트에 들어가면 위와 같은 화면이 보이는데 우리가 다운받아야 할 소스코드는 LIVE555 Streaming Media 이부분에 들어가 있다. 왼쪽 부분에 Live555  Media Server는 이 LIVE555 Streaming Media를 기반으로 해서 만들어진 콘솔 프로그램으로 로컬폴더의 파일을 읽어 RTSP로 전송하는 기능을 제공한다.

 

어쨋든 소스 코드는 Live555 Streaming Media 이 부분에 있으니 클릭해서 들어가보자

 

소스 코드 단락에 보면 .tar.gz 파일을 받으라고 되어 있는 here 부분을  누르면 소스코드, 샘플용 미디어 파일을 다운 받을 수 있는 링크로 이동한다.

 

 

 

뭐 이런 저런 파일들이 많이 있는데 그냥 live555-latest.tar.gz를 다운 받으면 되겠다. 참고로 Live555는  구버전의 소스코드를 제공하지 않고 가장 최신 버전의 소스코드만 다운 받을 수 있다. 이유는 모르겠다. 그냥 live555에서 그런다고 한다. 어차피 Live555의 LGPL라이선스 때문에 이 라이브러리를 활용하는 제품은 항상 최신버전의 Live555를 제공해야 하는 필요성도 있고하니.. 그냥 일단 최신버전의 소스코드를 다운 받자.

 

다운을 받아 소스코드를 압축 해제해 보면 소스폴더와 이런저런 파일들이 있다. make파일을 생성해 주는 bat파일도 있어서  것 같은데 이 포스트에서는 그냥 직접 Visual Studio 2010에서 프로젝트를 만들어가지고 빌드할 예정이다. (사실 내가 어떻게 make파일 쓰는지를 모름 ㅎㅎ)

 

따라서 일단 Visual studio 2010에서 솔루션을 하나 맹글자.

그리고나서 아래와 같이 소스코드를 담고 있는 폴더명을 따서 프로젝트를 추가해주자.

 

정적 라이브러리로 빌드할 거니까

윈도우 32 콘솔프로그램 -> 생성마법사에서 정적 라이브러리 체크

로 해서 정적 라이브러리 빌드용 파일을 만든다.

(미리 컴파일 된 헤더도 체크해제한다.)

 

취향에 맞게 DLL로 만들어도 뭐.. 무방하다.

 

 

 

아참.. 그 전에 일단 각 소스코드 폴더별로 간단히 설명을 해야 겠다.

 

 

먼저 Live555 에서 각종 기능을 제공하는 소스코드. 즉, 라이브러리로 빌드할 소스코드는

 

UsageEnvironment

BasicUsageEnvironment

groupsock

liveMedia

 

여기 들어 있는게 전부다.

이 폴더안에 들어 있는 소스코드는 정적라이브러리 빌드용 프로젝트로 빌드하면 되겠다.

그 외에

 

proxyServer

mediaServer

testProgs

 

는 콘솔 프로그램으로 Live555에서 만들어서 제공하는 프로그램의 소스코드이다.

따라서 이 소스파일들은 빌드할 때는 console프로그램으로 프로젝트를 만들어 빌드해야 한다.

 

WindowsAudioInputDevice에 들어 있는 소스코드는 정확히는 모르겠지만 윈도우 OS에 연결된 오디오 장비로부터 음성 데이터를 수신할 수 있도록 해주는 소스코드가 들어 있는 것 같다. 구지 빌드하지 않아도 된다.

 

어쨋든 아래와 같이 각 소스코드별로 프로젝트를 만들고 각 폴더안에 들어 있는 헤더와 소스코드를 몽창 넣어주자.

 

 

그 다음 각 폴더에 대해 아래와 같이 헤더의 위치를 찾을 수 있도록 Include path 를 지정해 준다.

포함시켜야 할 include path는 라이브러리용 소스코드에 들어 있는 헤더파일의 위치로 아래와 같다.

 

헤더파일 경로

$(SolutionDir)\BasicUsageEnvironment\include;

$(SolutionDir)\UsageEnvironment\include;

$(SolutionDir)\groupsock\include;

$(SolutionDir)\liveMedia\include;

 

이것으로 라이브러리의 빌드를 위한 설정은 모두 완료 되었다.

 

UsageEnvironment

BasicUsageEnvironment

groupsock

liveMedia

 

위 네가지 프로젝트를 모두 Debug모드 Release모드로 빌드하면, 다른 프로젝트에서도 이 라이브러리를 사용 할 수 있도록 lib파일이 생성된다. 빌드해 낸 이 라이브러리를 사용하기 위해서는 아래와 같이 헤더와 추가 종속성을 프로젝트 속성에서 지정해 주면 된다.

 

헤더파일 경로

$(SolutionDir)\BasicUsageEnvironment\include;

$(SolutionDir)\UsageEnvironment\include;

$(SolutionDir)\groupsock\include;

$(SolutionDir)\liveMedia\include;

 

라이브러리 패스

이전에 lib파일이 생성 된 폴더

 

추가종속성

liveMedia.lib
BasicUsageEnvironment.lib
groupsock.lib
UsageEnvironment.lib
wsock32.lib

 

유념 해야 할것은 추가 종속성에서 swock32.lib파일을 추가해 줘야 하는 것이다. 이 라이브러리는 소켓 동작과 관련된 기능을 제공하는 윈도우 기본 라이브러리로, 이 종속성이 있어야 프로그램이 정상적으로 빌드 된다.

 

 

Posted by 굿쟌
,

 

 

 gStreamer를 한번 써먹어 보겠다고 아웅다웅 한 10여일 정도를 gObject도 공부하고 glib도 공부하고 했는데, 아무래도 좀 무린것 같다. 윈도우 환경에서만 개발하고 리눅스써본거는 거의 한 2년전에 대학원에서 잠깐 써먹어본 것이 전부라 이 선수과목들을 배우는거는 커녕 윈도우에서 돌아가도록 빌드하는 것도 쉽지 않은 일이었다.

 

결국 깔끔하게 gStreamer는 포기 ㅋㅋ..

 

코덱이나 RTSP전송, 수신이나 다 한가지 라이브러리 내에서 사용 할 수 있다는 측면에서 gStreamer를 배워 놓으면 좋을것 같긴하지만, 아무래도 glib와 gObject를 기반으로하는 gStreamer를 사용하기에는 배워야 하는것이 너무 많아서 좀 무리였던 것 같다. 다음을 기약하며 일단은 대안으로 생각해 놨던, Live555 라이브러리를 이용하는 방법을 생각해 봐야겠다.

 

다행히 Live555도 LGPL을 이용하기 때문에 소스코드 수정만 안하면 소스코드 공개 없이 상용배포도 가능하다고 하고, 더군다나 gStreamer수준으로 복잡하지는 않아 좋은 것 같다. 그도 그럴것이 아무래도 Live555에는 코덱과 관련된 기능이 배제되어 있기 때문인데, H.264와 관련된 기능은 나중에 FFmpeg를 통해 구현하기로 하고, 일단은  RTSP의 송수신만 Live555로 구현하는 것을 목표로 해야겠다.

 

Posted by 굿쟌
,

gStreamer를 공부해서 뭘 만들어 보려고 의욕에 가득 찬 마음으로 gStremaer Application Development Manual을 찾아 열어 봤는데.. 젠장 선수 과목이 있다.

 

gStreamer는 크로스 플랫폼( 컴파일만 가능하면 리눅스에서도 돌아가고 윈도우에서도 돌아감), multi language binding (C로 짜놨어도 각 객체 타입간 관계만 알 수 있으면 인터프리터 언어 등 개발언어와 상관없이 가져다 쓸 수 있음) 이라는 특성을 구현하기 위해, C기반 객체지향 구조와 multi language binding을 지원하는 gLib라고 하는 플랫폼을 이용하여 구현했기 때문에 gStreamer를 공부하기 전에 이 gLib라고 하는 플랫폼을 공부해야 하는 필요성이 생겼다.

 

친절하게도 gStreamer매뉴얼에 gLib와 gLib에서 사용하는 gObject에 대해서 설명해 놓은 메뉴얼 싸이트를 링크해 놧는데 들어가보면, 대략 "gLib는 GTK+나 GNOME등에서도 이런 다중언어 바인딩, C를 기반으로 하는 객체지향 설계등을 반영하기 위해서 선택된 플랫폼으로 오픈 소스이다." 라는 설명과 함께 gLib가 어떻게 타입들을 정의하고 사용하는지에 대한 장대한 설명이 펼쳐진다. 물론 영어로.

 

즉, gStreamer를 이해하려면 최소한 그 기반에 깔려있는 gLib의 타입구조와 gObject를 생성하고, 소멸하는 정도는 이해하고 있어야 한다. 공부할게 또 늘었다. 세상 참 쉽게 되는게 없다.

Posted by 굿쟌
,

 한 동안 그냥 미뤄두고만 있었던 RTSP 미디어 서버 기능이 현재 진행하고 있는 프로젝트에서 필요하게 되어 더이상 미룰 수 없게 되었다. 원래는 RTSP 표준 문서인 RFC인가 치킨집 이름과 비슷한 표준 규약을 분석해서 직접 소켓으로 구현하고 싶었으나, stackoverflow에 한 글을 보고 관련 라이브러리를 찾아 보게 되었다.

 

Q) RTSP 미디어 서버를 구현하려고 하는데 어떻게 하면 되나요?

A) RFC 규약을 구현해 본 경험이 있습니다. 왜 스스로 지옥으로 걸어 들어가려고 하나요?

 

실제답변)

Don't do it yourself if you've never written a networking application before. It's no easy task to write a scalable and robust networking app and it's even harder to implement an existing protocol so that the implementation is fully compatible with the specification

 

그래서 일단은 미디어 서버를 구현하는데 기존의 라이브러리를 사용하기로 마음먹고, 상용으로 사용 가능한 라이브러리들을 찾아보았다. 구글링 결과 LIVE555 Streaming Media, 다윈 스트리밍 미디어, gStreamer를 찾았는데, 이 세 라이브러리 중에 gStreamer가 훨씬 보기 쉬운 API 문서를 제공하고 있는데다가, 전송, 수신, 코덱처리, 렌더링과 같은 다양한 기능을 모두 제공하고 있어서 배워두면 이곳저곳 여러모로 쓸 수 있을 것 같아 일단은 요걸 좀 살펴보고 잘 안된다 싶으면, LIVE555로 갈아 타야지 하고 마음먹었다.

 

(Darwin은 안 찾아 봐서 잘 모르겠고, LIVE555 streaming media, gStreamer는 둘다 LGPL을 따르고 있어서 상용으로 쓸 때도 소스코드를 공개할 필요는 없는것으로 보였다.)

 

그리하야... 학습 목표는

 

1순위 gStreamer

2순위 LIVE555 streaming media

 

요렇게 결정이 되었다.

Posted by 굿쟌
,

어떤 영상 매체로부터 영상 데이터를 받아 (RTSP, RTP, Onvif, 비디오 파일 등등) 디코딩해서 이것저것 지지고 볶고 가지고 놀다가 다시 인코딩해서 이 영상을 다른곳으로 전송하는 프로그램을 제작해보고자 한다. 사실 처음부터 끝까지 혼자 만들 수 있는 싸이즈의 기능은 아닌 것으로 보이나 시도와 과정중에서 배우는게 많을 것으로 생각한다. 물론 먼저 이와 관련 된 오픈소스를 찾아야 하겠다. 가능하면 상업적 용도로도 활용할 수 있도록 LGPL이나 BSD라이선스로 올라와 있는것들이 많으면 좋겠다.

Posted by 굿쟌
,

윈도우 서비스의 복구 동작 옵션 변경

 

윈도우 서비스는 프로그램 동작중에 예외가 발생하여 프로그램이 죽게 된 경우, 아래와 같은 Recovery 옵션이 있어 프로그램을 다시 시작하거나 (restart), 또는 전체 시스템을 리부팅하는 등의 대처를 자동으로 수행해 주는 옵션이 있다. 이 Recovery 옵션은 서비스 속성에서 설정 할 수 있는데, 속성에서 제공하는 것들을 잠깐 살펴보면 아래와 같다.

 

 

1. 다음기간 이후에 실패 횟수 다시설정

 

서비스는 에러가 발생하게 되면 이놈이 몇 번 째 에러인지 에러카운트를 올리는데, 여기에 설정 된 시간만큼 에러가 없으면 에러 카운트는 다시 0이 되어 새로운 에러가 발생하면 그 에러를 1번째 에러로 간주하여 첫째 실패에 대한 액션을 취하게 되는 식이다. 예를 들어 이 값이 30초로 (물론 속성창에서는 일 단위로만 가능하지만, 다른 방법을 통해 30초 설정이 가능함)설정되어 있다면, 아래와 같이 에러가 몇번 째 에러인지 세어 나가게 된다.

 

(error cnt : 0) - 에러 (error cnt : 1) - 10초 - 2번에러(error cnt : 2) - 30초

- (error cnt : 0) -  에러 (error cnt : 1) - 12초 - 에러 (error cnt : 2)

 

2. 다음 시간 이후에 서비스 다시 시작

 

에러가 발생하고 에러에 대한 대응 액션으로 서비스 다시 시작이 설정되어 있다면, 여기에 설정되어 있는 시간 만큼 에러 이후에 기다리고서 다시 서비스를 시작하게 된다.

 

그렇다. 그렇게 하면 된다. 그런데, 문제는 이게 아니다.

 

서비스 복구 옵션을 이렇게 사용자가 직접 정하는건 상당히 없어 보인다. 그게 문제다.

설치 패키지를 배포하고, 추가 설명으로 " 서비스 속성에 들어가서 어떻게 어떻게 하세요" 라고 적어놓는건 상당히 모양 빠지는 일이다.

 

그래서, 지금 개발하고 있는 C# windows service에서 이런 복구 옵션을 설정하여, 사용자가 할일이라고는 만들어놓은 설치 패키지를 더블 클릭하는 것 뿐이도록 만들어 보고자 구글링을 열심히 했다.

 

그런데 결론은 C# Windows service 프로젝트에는 이런 옵션 없ㅋ슴ㅋ.

ㅋㅋ..

 

물론 방법이 아예 없는 것은 아니다. 남들이 다 하는걸 못하는건 방법이 없어서가 아니라 내가 그냥 모르는 거라는 걸 다시한번 느꼈다. 그 방법은 크게 2가지로 나뉘는데 첫번째 방법은 "advapi32.dll의 함수를 C#에 import하여 사용하는 방법"이고 나머지 방법은 "sc.exe라는 콘솔 프로그램을 이용하여 윈도우 서비스의 속성을 변경하는 방법"이다.

 

전자에 나와 있는 방법 어떤 착한 턱수염 아저씨가 코드 프로젝트에잘 설명 해 놨다.

 

http://www.codeproject.com/KB/install/sercviceinstallerext.aspx?display=Print

 

그런데 좀 어렵다. serviceInstaller라는 클래스를 상속한 확장 클래스를 만드는데 자세한 사항을 공부하면 이편이 좀더 깔끔한 방식이 될 것 같기는 하다.

 

그러나 지금 서비스 프로그램은 64bit로 개발하고 있는데 외부에서 import한 라이브러리가 내가 알지 못하는 어떤 예외를 던질까 무서워 일단 이방법은 보류하고 나머지 sc.exe라는 프로그램을 이용하는 방법을 선택하기로 했다.

 

sc.exe가 32bit 든 64bit든 그냥 프로세스로 sc를 만들어 일반 콘솔에서 명령어 치듯 사용할 것이기 때문에 좀더 속편하게 쓸 수 있는 장점과 간단하다는 장점이 있는 방법이라 하겠다. 쓸 수 있는 기능면에서도 앞서 설명한 방법과 차이가 없다는 장점도 있다.

 

(참고로 sc.exe에 대한 자세한 아규먼트는 아래 문서를 참조하면 된다.)

http://technet.microsoft.com/en-us/library/cc754599.aspx

 

sc.exe는 서비스와 관련된 여러가지 작업을 할 수 있게 해주는 콘솔 프로그램으로서, fail에 대한 recovery를 설정하기 위해서는 failure라는 인자를 사용하여 아래와 같이원하는 서비스의 리커버리 옵션을 설정 할 수 있다.

 

 

failure에 대한 풀 옵션은 위에 있는 링크를 타고 들어가서 확인하면 되겠고..

위 콘솔창에 나와 잇는 정보만 살펴보자면 Envision_AES라는 서비스이름을 가지는 서비스를 마지막 에러를 기점으로 300초마다 error cnt를 0으로 재 설정하면서, 에러가 발생할 때 20000 ms의 지연을 가지고나서 서비스를 재 시작하도록 한다라는 의미를 가지고 있다.

 

이걸 InstallUtil.exe가 서비스를 설치하고나서 자동으로 설치되게 하려면 서비스의 serviceInstall가 가지는 이벤트 중에 Commited라는 이벤트의 콜백함수에다가 이 기능을 하는 코드를 입력해주면 된다. committed라는 이벤트는 설치가 모두 완료되면 발생하는 이벤트 쯤 되는 것 같다.

 

 

 

 

 

 

흐릿해서 잘 안보일 수도 있겠지만 간단히 설명하자면 Process 인스턴스를 하나 만들고 프로세스의 startinfo에 실행하고자 하는 프로그램이름인 sc 설정, 보이지 않는 hidden 프로세스로 설정, 그리고 아규먼트에 failure이후에 들어갈 값들을 지정하고 프로세스를 시작한 것 밖에 없다. "start()"

 

서비스 설치시 자동 시작

 

그런데 서비스를 installutil.exe로 설치하고 나면 서비스 시작 유형을 automatic으로 설정해 두었음에도 불구하고 서비스는 중지됨 상태로 있게 된다. 이것은 automatic이라는 의미가 "시스템이 부팅되었을 때 자동 시작됨"이기 때문이다. 설치는 했지만 컴퓨터가 리부팅 되지는 않았으니 어찌보면 당연한 말이기도 하다.

 

그래서 이번에는 서비스를 설치하자 마자 자동으로 서비스 프로그램을 프로세스로 만들어 실행시키는 구문을 만들어보고자 한다.

 

이것 또한 서비스 설치시 발생되는 committed 이벤트의 콜백함수에 집어 넣을 껀데, 사실 바로 위에 있는 그림파일에 이미 들어가 있다.

 

int exitCode 위에 있는 코드가 그것인데, 서비스 리커버리 옵션 고치는 것보다 오히려 코드는 간단하다. 서비스 실행을 관장하는 ServiceController의 인스턴스를 하나 만들고 (생성자 인수로 서비스의 이름을 받는다.) 서비스를 실행 " start() " 시키고, 서비스가 running 상태에 접어들기를 기다렸다가 ServicController를 닫는 " Close() " 과정밖에 없다.

 

간단하다.

 

Posted by 굿쟌
,