DLNA에 대해서 좀 알아 보려고 했더니만 뜬금없는 uPNP가 나오네??? DLNA가 uPNP에서 파생되었다고 하는데...어쩐지 miniDLNA소스를 봐도 uPNP 밖에 안 나온다… 그래서 uPNP에 대해서 먼저 학습을 하고 DLNA로 넘어가야 겠다.


uPNP

Universal Plug and Play 로 마이크로소프트사에 의해 제안된 기술이란다. PnP랑 이름이 같길래, 난 그건줄 알았더니 거의 유사하다. PC에 연결해서 주변기기를 알아서 등록, 재생 하는 그 기술의 확장판이랄까?? 요건 네트워크에 딱 등록되면 네트워크 연동해서 !!! 사용이 가능 하게 해주는 기술이랄까???

여튼, 요즘같이 스마트 시대에 아주 적당한 기술이라 할 수 있겠다. 프린트, 라디오, 비디오, 스마트폰이 네트워크로 연결되서 서로 사용이 가능 하다면 얼마나 편하겠나~~~ 좋은 세상이야..

uPNP는 TCP/IP, SSDP, SOAP, GENA, HTTP, XML같은 통신 표준과 기술을 바탕으로 한다고 한다.

TCP/IP는 알겠는데 SSDP, SOAP, GENA는 또 뭐냐??? 점점 꼬리에 꼬리를 물고 알아야 할게 늘어난다 ㅡㅡ;;;




SSDP
Simple Service Discovery Protocol로 네트워크상 장치들을 찾기 위한 프로토콜.

이라는데, 이게 뭔 소린지…흠..그냥 프로토콜 인데(?)

네트워크 상에서 1900 포트로 브로드캐스트 를 하면 같이 연결된 장치들이 그에 응답하여 서로 장치의 IP주소를 획득. 이렇게 해서 서로 같은 네트워크 상에서 장치를 인식하게 해주는 프로토콜(?) 인가 보다.



SOAP

Simple Object Access Protocol로 HTTP, HTTPS, SMTP등을 사용하여 XML 기반의 메시지를 컴퓨터 네트워크 상에서 교환하는 형태의 프로토콜이다.

요건 모.. 그냥 XML 데이터를 HTTP, HTTPS, SMTP 프로토콜로 서로 전송, 수신 한다는 말이고…



GENA

General Event Notification Architecture는 이벤트를 HTTP로 송.수신 한다.

뭔말인지… 그냥 이벤트를 HTTP를 통해서 전달한다는 말이겠지… 이벤트 전용 프로토콜인건가??



정리하믄 SSDP로 각 장치를 찾고, SOAP으로 데이터를 송수신 하고 GENA로 이벤트를 송수신 한다는 말인건가?

 



uPNP는 검색, 서술???(Description), 제어, 이벤트?(Eventing), 프리젠테이션  요런 단계로 나뉘어 진단다..




검색

Control Point 라는 놈(장치를 제어하는 놈)이 동일 네트워크 상에 장치(서비스 하는 기기)를 검색할 때 사용하나 보다. (사용할려면 어떤놈들이 있는지 부터 알아야 하니… ㅋㅋㅋ) 근데 Control Point 놈만 이걸 보내는 게 아니라, 장치 들도 네트워에 연결되면 알아서 ㅋ “나 여기 있어요~~” 라고 통지 메시지를 보낸다고 한다. 제법 기특해!~

Control Point는 M-SEARCH 메시지를 보내는데 “이 네트워크 있는 기기 들 있으면 모두 손!!!” 과 같다고 봐야 할 것 같다. 손 들라고 했으니 이 메시지를 수신 한 기기들은 Control Point로 응답 메시지(“저 여기 있어요~~~”)를 보낸다.

M-SEARCH * HTTP/1.1
HOST: 239.255.255.255:1900
MAN: “ssdp:discover”
MX: 30
ST: ssdp:all


M-SEARCH * HTTP/1.1 기기찾는 다는 M-SEARCH와 HTTP/1.1 버젼 정보
HOST: 239.255.255.255:1900 uPNP에서 약속된 브로드캐스팅 IP와 포트.
MAN: “ssdp:discover” 요건 필수!
MX: 30 Control Point라는 놈이 검색 한다고 하고 무한정 기다릴 수 없으니… 초 단위로 1에서 120 사이에 값.
ST: ssdp:all 네트워크에 있는 모든 장치 그리고 서비스를 찾는다. 문서를 보면 ssdp:all이 아닌 특정 장치, 특정 서비스(?)등을 찾는 세부적으로 나눠서 검색 할 수 있는 것 같은데.. 세부적인 내용에 대해선 뭔지 모르겠다…

M-SEARCH * HTTP/1.1 : HOST: 239.255.255.255:1900 uPNP에서 약속된 브로드캐스팅 IP와 포트 요건 안 바뀐다.

Control Point는 요런 형태로 네트워크 상에 있는 기기를 찾는다고 메시지를 보낸다.


HTTP/1.1 200 OK
CACHE-CONTROL: max-age=100
DATE: Tue, 18 Jun 2013 03:05:18 GMT
EXT:
LOCATION: http://192.168.10.100:8080/api/test.xml
SERVER: Linux/2.6.18 UPnP/1.0 Test Device/2.0
ST: ssdp:all
USN:



HTTP/1.1 200 OK HTTP 버전 및 응답
CACHE-CONTROL: max-age=100 장치가 Control Point 한테 본인이 유효한 시간(초 단위로 최대 1800)을 표기한다. 뭔말이냐 .ㅡㅡ;;; 이 시간 만큼 Control Point가 요청하는 내용에 대해서 응답을 할 수 있다는 말인가?? 흠.
DATE:Tue, 18 Jun 2013 03:05:18 GMT 응답 메시지가 만들어진 날짜/시간.
EXT: 요건 나도 몰라.
LOCATION: http://192.168.10.100:8080/api/test.xml 요 밑에서 언급될 서술? 설명? (Description)에 대한 장치의 정보를 담고 있는 위치.
SERVER:Linux/2.6.18 UPnP/1.0 Test Device/2.0 OS/버전, 장치/버전등의 정보.
ST: ssdp:all M-SEARCH의 ST 값에 의해 달라진다. 위에서 ssdp:all 이라고 했으니 ssdp:all 이라고 했다.
USN 서비스 이름. 식별자 인것 같기도 하고.. 몰겠어..

그러면 각 장치들은 요렇게 메시지를 만들어서 Control Point에 응답한다.

 

위에서 장치들이 네트워크에 연결이 되면 알.아.서 본인들이 통지 메시지를 보낼 수도 있다고 했다.

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=100
LOCATION: http://192.168.10.100:8080/api/test/xml
NT: upnp:rootdevice
NTS: ssdp:alive
SERVER: Linux/2.6.18 UPnp/1.0 Test Device/2.0
USN: ~~~


NOTIFY * HTTP/1.1 장치가 알아서 응답한다.
HOST: 239.255.255.250:1900
NT: upnp:rootdevice upnp:rootdevice라는 놈이 보냈다 정도???
NTS: ssdp:alive 요렇게 명시해 줘야 한단다.

그래, “나 여기 있어요” 라고 보내는 참으로 기특하다. 근데 네트워크에서 제거 될 때도 알아서 통지 메시지를 보낸다.

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
NT: upnp:rootdevice
NTS: ssdp:byebye
USN: ~~~


NTS: ssdp:byebye 제거 될때 요렇게 해줘야 한단다.




서술? 설명? (Description)

Control Point 가 이제 네트워크 상에서 장치들이 어디(IP)에 있는지 알았다. 근데 이놈들이 정확히 어떤 놈들인지에 대한 확실한 정보는 없다. 그래서 검색 시에 장치들이 응답한 데이터 중 LOCATION 위치에 있는 정보(UTF-8 로 인코딩 되어 있단다)를 요청한다. 그냥 HTTP 프로토콜 GET 이다.

근데 이게 ㅡㅡ;;; XML 로 많은 내용이 있다.

<?xml version="1.0"?> 
 <root xmlns="urn:schemas-upnp-org:device-1-0"> 
 <specVersion> 
 <major>1</major> 
 <minor>0</minor> 
 </specVersion> 
 <URLBase>base URL for all relative URLs</URLBase> 
 <device> 
 <deviceType>urn:schemas-upnp-org:device:deviceType:v</deviceType> 
 <friendlyName>short user-friendly title</friendlyName> 
 <manufacturer>manufacturer name</manufacturer> 
 <manufacturerURL>URL to manufacturer site</manufacturerURL> 
 <modelDescription>long user-friendly title</modelDescription> 
 <modelName>model name</modelName> 
 <modelNumber>model number</modelNumber> 
 <modelURL>URL to model site</modelURL> 
 <serialNumber>manufacturer's serial number</serialNumber> 
 <UDN>uuid:UUID</UDN> 
 <UPC>Universal Product Code</UPC> 
 <iconList> 
 <icon> 
 <mimetype>image/format</mimetype> 
 <width>horizontal pixels</width> 
 <height>vertical pixels</height> 
 <depth>color depth</depth> 
 <url>URL to icon</url> 
 </icon> 
XML to declare other icons, if any, go here
 </iconList> 
 <serviceList> 
 <service> 
 <serviceType>urn:schemas-upnp-org:service:serviceType:v</serviceType> 
 <serviceId>urn:upnp-org:serviceId:serviceID</serviceId> 
 <SCPDURL>URL to service description</SCPDURL> 
 <controlURL>URL for control</controlURL> 
 <eventSubURL>URL for eventing</eventSubURL> 
 </service> 
Declarations for other services defined by a UPnP Forum working committee (if any)
go here
Declarations for other services added by UPnP vendor (if any) go here
 </serviceList> 
 <deviceList> 
Description of embedded devices defined by a UPnP Forum working committee (if any)
go here
Description of embedded devices added by UPnP vendor (if any) go here
 </deviceList> 
 <presentationURL>URL for presentation</presentationURL> 
 </device> 
 </root> 
 

복잡하다… ㅡㅡ;; 이제 각 속성들을 살펴 봐야 겠다.


root xmlns ”run:schemas-upnp-org:device-1-0 같이 xmlns이 반드시 있어야 한단다.
specVersion major, minor major , minor 버전. major 는 반드시 1이어야 한다고 하네?
URLBase   Optional.
device   장치
deviceType   장치 타입이라는데 “urn:schemas-upnp-org:device:” 로 시작해야 한다고 한다.  타입이 뭔지 모르겠다. devicetype:v 이렇게 있는데 v는 버전 정보란다.
friendlyName   그냥, 최종 사용자를 위한 짧은 이름?
manufacturer   제조사
manufacturerURL   제조사 URL
modelDescription   모델 설명
modelName   모델 이름
modelNumber   모델 넘버
modelURL   모델 웹 사이트.
serialNumber   시리얼 넘버
UDN   유일한 장치 이름. 장치 식별자.
UPC   제품 코드
iconList   장치에서 사용하는 아이콘 리스트
  icon Control point UI에서 표시될 아이콘
  mimetype 아이콘 MIME TYPE “image/png”.
  width 가로
  height 세로
  depth depth
  url 아이콘 이미지 파일일 있는 URL 주소
serviceList   장치에서 제공하는 서비스들(?)^^
  service 무슨 서비스를 할 것인가를 설명한 건가?? 참 어려다잉.
  serviceType 말 그대로 서비스 타입. “urn:schemas-upnp-org:service:”로 시작 되어야 한다고 한다. devicetype처럼 servicetype:버전 이렇게 설정한다.
  serviceId 서비스 식별자. “urn:upnp-org:serviceId:serviceID” 요렇게 설정.
  SPCDURL 서비스 설명을 위한 URL
  controlURL 컨트롤을 위한 URL
  eventSubURL 이벤팅을 위한 URL
devicelist   장치 안에 장치가 또 있나 보다. 또 디바이스 리스트.
presentationURL   presentation을 위한 URL

 

휴 많다… 이제까지 root device라는 용어가 나오는데 root device가 메인 장비?? 장치를 말하나 보다. 그 장치안에 또 장치가 있는 모양. ㅎㅎㅎ

이제, 장치에 대한 정보를 알았으니, 장치가 가지고 있는 서비스 정보에 대해서 알아야 한다. 요게 장치에서 제공하는 서비스의 액션, 변수, 데이터, 범위, 이벤트등의 정보를 가지고 있다고 한다. 그러니까 장치를 제어 하기 위해 필요한 정보 모음집으로 봐야 하나?? 하~~ 망할 영어…

Control Point는 장치 기술, 설명(description)을 HTTP GET으로 요청하고 다시 서비스 Description을 요청(HTTP GET)한단다.

 

<?xml version="1.0"?> 
 <scpd xmlns="urn:schemas-upnp-org:service-1-0"> 
 <specVersion> 
 <major>1</major> 
 <minor>0</minor> 
 </specVersion> 
 <actionList> 
 <action> 
 <name>actionName</name> 
 <argumentList> 
 <argument> 
 <name>formalParameterName</name> 
 <direction>in xor out</direction> 
 <retval /> 
 <relatedStateVariable>stateVariableName</relatedStateVariable> 
 </argument> 
Declarations for other arguments defined by UPnP Forum working committee (if any)
go here
 </argumentList> 
 </action> 
Declarations for other actions defined by UPnP Forum working committee (if any)
go here
Declarations for other actions added by UPnP vendor (if any) go here
 </actionList> 
 <serviceStateTable> 
 <stateVariable sendEvents="yes"> 
 <name>variableName</name> 
 <dataType>variable data type</dataType> 
 <defaultValue>default value</defaultValue> 
 <allowedValueList> 
 <allowedValue>enumerated value</allowedValue> 
Other allowed values defined by UPnP Forum working committee (if any) go here
 </allowedValueList> 
 </stateVariable> 
 <stateVariable sendEvents="yes"> 
 <name>variableName</name> 
 <dataType>variable data type</dataType> 
 <defaultValue>default value</defaultValue> 
 <allowedValueRange> 
 <minimum>minimum value</minimum> 
 <maximum>maximum value</maximum> 
 <step>increment value</step> 
 </allowedValueRange> 
 </stateVariable> 
Declarations for other state variables defined by UPnP Forum working committee
(if any) go here
Declarations for other state variables added by UPnP vendor (if any) go here
 </serviceStateTable> 
 </scpd> 

 


요놈도 장난이 아니다. 그냥 하나 하나 뜯어서 살펴 볼려니, 심신이 괴롭다. 아니 정확히 머리가 아프다..

 

scpd   반드시 urn:schemas-upnp-org:service-1-0으로 시작해야 한단다.
specVersion major, minor major, minor 버전 정보. major는 반드시 1이어야 한단다.
actionList   액션(?) 리스트?
action name 액션 이름. 동작 함수 인듯.
argumentList   액션이 함수라면 인자도 있겠지~~ 그 인자 리스트 정의.
argument name 인자 이름.
  direction 인자가 IN 타입인지 OUT 타입인지를 말하나 보다. IN, OUT 개념도 있네..흠.
  retval 액션 리턴 값.
  relatedStateVariable 액션 내부에서 사용할 StateVariable 이름??? 아직 모르겠다.
serviceStateTable   변수 테이블?
stateVariable name 변수 이름
  dateType 데이터 타입
  defaultValue 기본 값
allowedValueList allowedValue. 문자열에서 쓰나 본데 모르겠다.
allowValueRange minimum, maximum,step 변수값에 최소, 최대 값 정의.


휴~ 요렇게 해서 Description 마쳤다.

 

Control Point는 장치를 제어 하기 위한 정보를 Description을 통해서 다 얻었다. 이제 장치를 제어해 봐야 겠지~~




제어

장치의 제어는 서비스 Description에서 얻은 액션이름(함수명??)으로 가능하단다. 근데 위에서 언급했듯이 SOAP이라는 놈으로 구성되어 있다.(아~~ 망할…)

POST path of control URL HTTP/1.1 
 HOST: host of control URL:port of control URL
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName" 




---------body ----------
 <?xml version="1.0"?> 
 <s:Envelope
 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 
 <s:Body> 
 <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v"> 
 <argumentName>in arg value</argumentName> 
other in args and their values go here, if any
 </u:actionName> 
 </s:Body> 
 </s:Envelope>

장치 제어는 HTTP 헤더와 body 요렇게 두 부분으로 구성되어 있다.

POST control URL HTTP/1.1 장치 Description에서 설정된 control URL.
HOST IP 주소, 도메인.
CONTENT-LENGTH body 크기
CONTENT-TYPE text/xml; charset=”utf-8” 로 사용하란다.
SOAPACTION urn:schemas-upnp-org:service:serviceType:번호#액션이름 이렇게 사용하란다. 뭔말인건지.. 제어할 액션이름.

헤더는 이렇고 body는…

 

Envelope   이건 그대로 쓰면 되겠다.
body   제어 부분
actionName   제어할 액션 이름을 쓴다.
xmlns:u=”urn:schemas-upnp-org:service:serviceType:v” 요건 모르겠다. 아놔 환장하겠네..
  argumentName 인자 이름. 인자 이름을 쓰고 해당 인자에 값을 표기.

근데 이렇게 장치로 POST 했는데 “405 Method Not allowed” 라는 메시지를 리턴 받으면 다시 M-POST로 메시지를 보내야 한다고 하네?  뭐이리 복잡해.

 

M-POST path of control URL HTTP/1.1 
 HOST: host of control URL:port of control URL
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
 MAN: "http://schemas.xmlsoap.org/soap/envelope/"; ns=01
01-SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName" 

 

자.. 이제 제어 명령을 보냈으니, 장치가 응답을 보내겠지. 주는게 있으니 받는것도 있는 법이니까..

 

 HTTP/1.1 200 OK 
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
 DATE: when response was generated
 EXT: 
 SERVER: OS/version UPnP/1.0 product/version


--------- body ----------

 <?xml version="1.0"?> 
 <s:Envelope
 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 
 <s:Body> 
 <u:actionNameResponse xmlns:u="urn:schemas-upnp-org:service:serviceType:v"> 
 <argumentName>out arg value</argumentName> 
other out args and their values go here, if any
 </u:actionNameResponse> 
 </s:Body> 
 </s:Envelope> 

여기서 actionNameResponse는 actionName + Response 가 더해서 만들어진 단어인가 보다.  그렇다고 하니 그렇겠지.

 

제어는 대충 이런식인가 보다. 액션을 호출 하는 것만 알아 볼련다. 각 변수값들도 요청하는 게 있나 본데 솔직히 귀찮다. DLNA 수정해 보려다 uPNP때문에 미치겠다. 그래서 더 미치기 전에 간단하게 넘어 갈련다.

 



이벤트

Control Point가 장치의 서비스 변수의 상태가 변경이 되었을 때 통지 받고자 하면 이벤트를 요청한다고 한다. 장치 서비스는 변수의 값이 변경이 될 때마다 Control Point에게 이 값을 전달 하는게 이벤트 라고 한다.

Subscriber, publisher 뭐 이런놈들이 있는데 이건 그냥 무시하겠다… 깊게는 들어가지는 들어가지 말아야지^^

그럼 Control Point가 장치에게 이벤트 요청 메시지를 어떻게 보내냐면..

SUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
CALLBACK: <delivery URL> 
NT: upnp:event
 TIMEOUT: Second-requested subscription duration 

이렇게 보낸단다.

SUBSCRIBE publisher path HTTP/1.1 SUBSCRIBE 명령에 장비 Description에서 설정되었던 이벤트 URL 을 설정.
HOST 장치 IP, 포트
CALLBACK 이벤트를 전달받을 URL로 설정. 이때 이 URL은 여러개가 될 수 있다. 이벤트 메시지를 보낼곳들을 설정한다.
NT upnp:event
TIMEOUT 이벤트 발생시 한번 보내고 끝내는게 아닌가 보다. 이 timeout 값에 의해 보내는 시간이 결정된단다. Second-XX 이렇게 설정하던지 아니면 infinite 무한 설정 이렇게 한단다.

이벤트 설정? 메시지 수신 후 장치는 응답 메시지를 보낸다. 뭐~ 잘 받았다는 메시지 겠지..

HTTP/1.1 200 OK 
 DATE: when response was generated
 SERVER: OS/version UPnP/1.0 product/version
SID: uuid:subscription-UUID
 TIMEOUT: Second-actual subscription duration 

이 메시지중 SID 이 필드값 이건 아마도 이벤트 요청을 한 Control Point의 식별자를 장치가 하나 발급하나 보다. 이 SID 필드 값으로 관리하는듯… Control Point가 이 메시지를 수신 후 SID 값을 저장해 놔야 하나 보다.. 왜? 그건 renewal 메시지에서 사용하니까~~

SUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
SID: uuid:subscription UUID
 TIMEOUT: Second-requested subscription duration 

renewal 메시지는 이벤트 요청 메시지랑 명령은 같다. 다만 NT와 CALLBACK만 빠져 있다. 아~ SID 가 추가 되어 있다. 그럼 renewal은 뭐냐?? 단순히 메세지만 봐서는 TIMEOUT 값만 다시 변경 할 때 사용하나 보다. 추가된 SID는 장치에서 송신한 SID 필드값이겠지?? ㅋ 그러므로 Control Point도 SID값을 저장해 놔야 하는듯.

이벤트를 보내 달라고 했으니 보내지 말라는 메시지도 있겠지? 당연히~~ 있네…

UNSUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
SID: uuid:subscription UUID

이렇게 보내면 장치는 이벤트 메시지를 보내지 않는다고 한다.


이벤트를 요청하고 갱신?하고 이벤트 중지 하는 메시지 까지 했으니 이제 실제 이벤트 발생시 보내지는 메시지만 남았다.

NOTIFY delivery path HTTP/1.1 
 HOST: delivery host:delivery port
 CONTENT-TYPE: text/xml 
 CONTENT-LENGTH: Bytes in body
NT: upnp:event
NTS: upnp:propchange
SID: uuid:subscription-UUID
SEQ: event key


---------- body ----------------------

 <?xml version="1.0"?> 
 <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0"> 
 <e:property> 
 <variableName>new value</variableName> 
 </e:property> 
</e:propertyset>

생김새는 제어 쪽에서 본 메시지랑 비슷하다.

NOTIFY delivery path HTTP/1.1   NOTIFY 요건 통지 관련 메시지다는 의미일테고 delivery path는 이벤트 요청시 설정한 CALLBACK url이란다.
CONTENT-TYPE   text/xml
CONTENT-LENGTH   body 크기
NT   upnp:event
NTS   upnp:propchange
SID   SID
SEQ   0에서 시작해서 1씩 증가한다고 한다.
propertyset   urn:schemas-upnp-org:event-1-0. 이렇게 써주고.
property variableName variableName변수의 변경된 값.

이벤트 메시지를 받았으니 메시지 잘 받았다고 응답 보내줘야지?

 HTTP/1.1 200 OK 

휴~~~

이 이벤트를 끝으로 uPNP는 그만 볼련다. DLNA를 시작으로 uPNP까지 머리도 터지겠고 보기도 싫다. 이거 뭐 되지도 않는 영어 문서 붙잡고.. 하나 하나 볼려니 오역80%에 개념 상실 20%.

그래도 uPNP 어느정도 개념은 잡았으니 miniDLNA 소스 코드는 어느정도 분석은 가능하겠군.. 근데 DLNA 프로토콜도 찾아서 봐야 하는데. 이 정도라면 그만 둘까 라는 생각이 든다.

 

참고문서:

http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf

 

  

영상을 다루는 사람들은 FFMPEG 라이브러리를 모르는 사람을 없을 것이다. 그래.. 많다. 안 쓰이는 곳이 없으니 웬만한 동영상 플레이어도 FFMPEG 라이브러리를 사용해서 재생한다. 물론 Directshow 를 이용해서 하기도 하지만 Directshow의 ffdshow도 FFMPEG 라이브러를 사용하니…

이 라이브러리는 방대하다. 웬만한 코덱은 다 지원하다 보니 덩치가 겁나 크다. 그런데 한동안 libav 라고 해서 잠시 분리되어 프로젝트가 진행되기도 하다 보니 API 인터페이스가 다른(?) 상황도 있었다. 뭐 지금은 다시 통합되었지만… 구글에서 ffmpeg 관련에서 문의 하다 보면 관련 샘플 코드가 어떤건 libav , 어떤건 ffmpeg 으로 작성되어 있는 경우도 있었다. 뭐 그다지 크게 다르진 않고 조금 다르긴 하지만 헷갈렸던 건 사실이다.

FFMPEG 라이브러리를 이용한다는건 어떤 파일을 다른 파일 포맷으로 변환 한다던지(재인코딩), 영상 스트림을 디코딩, 인코딩을 사용하기 위해서 일것이다. 여기서 난 윈도우 기반에서 개발한다는 가정하에 디코딩,인코딩 API 만 짚을 예정이다.


개발환경

- Windows 32bit
- Visual Studio 2010

FFMPEG 라이브러리 구하기

FFMPEG라이브러리를 사용하기 위해선 파일을 다운 받아야 한다. FFMPEG 라이브러리는 리눅스 기반에서 개발되었으며, 거의 모든게 GCC 컴파일러에 맞춰 있다. 물론 윈도우에서 컴파일 하는 방법은 있지만 그 방법이 쉽지는 않다. 많은 사람들이 어려웠던지 윈도우용 DLL 만 따로 모아놓은 사이트가 있다.

http://ffmpeg.zeranoe.com/builds/ 

여기에 윈도우용 ffmpeg 라이브러리 DLL 파일이 있다. 32 비트 파일과 64비트 파일 둘 다 지원한다. 또한 static, share, dev 용으로 구분 되어져 있는데 share과 dev용을 다운 받는다.

dev 압축파일을 적당한 위치에 압축을 풀어준다. 그리고 share 압축 파일에서 BIN 디렉토리의 DLL만 선택 후 dev 압축이 풀린 폴더에서 lib 폴더에 복사한다. dev 압축 파일에 include 파일과 lib 파일만 공개하고 실행시 필요한 DLL 파일은 share에서 포함되어 있기 때문이다.

개발환경

Visual Studio에서 ffmpeg 라이브러릴 사용하기 위해서 프로젝트 속성설정에서 DEV 압축 풀린 폴더에서 include 및 DLL 파일을 설정한다.

프로젝트 속성->C/C++ –>일반 –> 추가 포함 디렉토리 –> DEV 압축 푼 폴더/include

그리고 관련 lib 연결 소스 코드 Stdafx.h에서 연결했다.

   1: #include "ffmpeg/include/libswscale/swscale.h"
   2: #include "ffmpeg/include/libavformat/avformat.h"
   3: #include "ffmpeg/include/libavformat/avio.h"
   4: #include "ffmpeg/include/libavcodec/avcodec.h"
   7:  
   8: #pragma comment( lib, "ffmpeg/lib/avcodec.lib")
   9: #pragma comment( lib, "ffmpeg/lib/avformat.lib")
  10: #pragma comment( lib, "ffmpeg/lib/avutil.lib")
  11: #pragma comment( lib, "ffmpeg/lib/swscale.lib")

영상 디코딩

FFMPEG을 사용하기 위해선 인코딩 이던 디코딩이던 라이브러리를 초기화가 필요하다. 전체 프로그램에서 한번만 호출 해 주면 된다.

avcodec_register_all();

예전 버전에는 avcodec_init(); 함수도 있었지만 버전업 되면서 이 함수가 없어졌다.

영상을 디코딩 하기 위해서

AVCodec 과 AVCodecContext 두 변수가 필요하다.

AVCodec은 어떤 코덱(MPEG, MJPEG, H264)인지를 나타내는 정보를 가지고 있으며

AVCodecContext는 스트림을 어떻게 디코딩 할 것인지 정보를 가지고 있다.

이 두 변수를 각각 전역이든 멤버 변수든 선언해 준다.

   1: AVCodec *                m_codec;
   2: AVCodecContext *        m_context;

그리고 각 변수를 초기화 해준다.

   1: m_codec = avcodec_find_decoder(CODEC_ID_H264);
   2: m_context = avcodec_alloc_context3(m_codec);
   3: avcodec_get_context_defaults(m_context);

avcodec_find_decoder(CodecID)

이 함수는 디코딩 코덱을 선택해서 m_codec을 초기화 해준다. CodecID는 avcodec.h에 정의되어 있다. 여기선 H264 코덱을 선택했다.

avcodec_alloc_context3(AVCodec*)

이 함수는 m_context 변수를 초기화 해주는 함수로 AVCodec 정보를 가지고 context를 메모리에 할당한다. 계속 FFMPEG이 버전업 되면서 끝에 숫자가 증가하는 것 같다. 이유? 나도 모른다. 검색해서 찾아 볼 여유가 없었다. ㅋㅋ

avcodec_get_context_defaults(AVCodecContext*)

context 변수를 초기화 하는 함수이다. 호출 안해도 상관 없다. 왜??? avcodec_alloc_context3에서 초기화가 이루어 졌으니까..

초기화가 되었으니까, 이제 어떤 영상 스트림을 디코딩 할것인지 직접 입력해 줘야 한다.

   1: m_context->width = 320;
   2: m_context->height = 240;
   3: m_context->codec_id = CODEC_ID_H264;
   4: m_context->codec_type = AVMEDIA_TYPE_VIDEO;
   5: m_context->flags2 |= CODEC_FLAG2_FAST;
   6: m_context->thread_count = 0;

 

width, height는 320, 240이고, 코덱은 H264, AVMEDIA_TYPE_VIDEO로 비디오를 디코딩 한다는걸 설정하고 CODEC_FLAG2_FAST는 그냥 샘플 코드에 있어서 넣어 봤다. 빼도 상관 없다. thread_count 몇개의 쓰레드로 디코딩 하는지 명시하는 부분인데, 나도 모르겠다. 저 값에 10을 넣으나 1을 넣으나 속도는 같은 것 같은데 왜 적용이 안 되는지 모르겠다. 0은 라이브러리가 알아서 쓰레드 만든다 라고 FFMPEG 문서에 있다. 이것도 제대로 되는지 모르겠다. 흠흠…

설정 정보 다 입력했으면 최종 FFMPEG 라이브러리를 설정해야 한다.

   1: avcodec_open2(m_context, m_codec);

요렇게만 하면 이제 디코딩 하기 위한 준비는 다 끝났다.

이제 스트림 데이터가 있는 변수를 ffmpeg 라이브러리에 넘겨 주고 디코딩 시키면 된다.

   1: AVPacket avpkt;
   2:  
   3:  
   4: av_init_packet(&avpkt);
   5:  
   6: avpkt.data = pEncodedData;
   7: avpkt.size = EncodedSize;

예전 버전에서는 스트림 데이터를 바로 이용했는데 어느 순간 버전업 되면서 AVPacket을 이용해야 했다. av_init_packet으로 초기화 해주고 data와 size에 스트림 데이터, 데이크 크기를 전달 한다.

   1: int got;
   2: int ret;
   3: AVFrame *m_picture;
   4:  
   5: m_picture = avcodec_alloc_frame();
   6:  
   7: ret = avcodec_decode_video2(m_context,m_picture, &got, &avpkt );
   8:  
   9: if( got > 0 )
  10: {
  11:   ...........
  12: }

 

깜박하고 디코딩시 디코딩 된 이미지 데이터를 저장할 변수를 초기화 하지 않았다. ^^ avcodec_alloc_frame()으로 이미지 변수를 할당 받는다.

avcodec_decode_video2(AVContext*, AVFrame*, int *, AVPacket *)

스트림 데이터를 이미지로 변환하는 함수다. 만약 이미지로 변환이 성공하면 got 변수에 0 보다 큰 값을 리턴한다.



영상 인코딩

초기화 기본은 디코딩과 같다. 다만 avcodec_find_decoder가 avcodec_find_encoder로 변경되었을 뿐이다.

   1: m_codec = avcodec_find_encoder(CODEC_ID_H264);
   2: m_context = avcodec_alloc_context3(m_codec);
   3: avcodec_get_context_defaults(m_context);

그리고 압축을 하기위한 설정을 context에 입력한다.

   1:  
   2: m_context->bit_rate = 512*1024;
   3: m_context->bit_rate_tolerance = 512*1024*2;
   4: m_context->width = 320;
   5: m_context->height = 240;
   6: m_context->time_base.num = 1;
   7: m_context->time_base.den = 24;
   8: m_context->gop_size = 24;
   9: m_context->profile = FF_PROFILE_H264_BASELINE;
  10: m_context->pix_fmt = PIX_FMT_YUV420P; 
  11:  
  12: av_opt_set(m_context->priv_data, "preset", "fast", 0);
  13: av_opt_set(m_context->priv_data, "profile", "baseline", 0);
  14:  
  15:  

압축될 영상은 320, 240 사이즈로 설정하고 비트레이트는 512kbps 로 설정했다. 또한 FPS 24, GOP는 24다. 그리고 압축을 H264로 하기 때문에 프로파일은 BASELINE. 예전 버전에서는 없었는데 버전업 되면서 생긴 av_opt_set 으로 preset, profile을 설정했다. 뭐 이렇게 써라고 하니 썼다… 정확하지는 않는다. 그냥 인터넷에서 긁어 모으다 보니~~ ㅋㅋㅋ

   1: AVFrame* m_picture;
   2:  
   3: m_picture = avcodec_alloc_frame();
   4:  

디코딩과 마찬가지로 인코딩도 AVFrame 으로 영상 데이터를 입력 받는다. 그래서 AVFrame을 할당 받는다.

인코딩은 보통 YUV420P 로만 입력 받는다. RGB24로 입력 해봤지만 계속 실패해서 성공해 본적이 없다. 버전업 되면서 지원 되는지 모르겠지만.. 예전에는 안 되었다. 그래서 영상 데이터를 YUV420P 로 변환 해 줘야 한다.

   1: struct SwsContext *    m_convert_ctx;
   2: AVPicture dstpic;
   3: AVPicture srcpic;
   4:  
   5: m_convert_ctx = sws_getContext( 320, 240, PIX_FMT_BGR24, 320, 240, PIX_FMT_YUV420P, SWS_FAST_BILINEAR | SWS_CPU_CAPS_MMX, NULL, NULL);
   6:  
   7: avpicture_fill(&dstpic, m_YUVImageData, PIX_FMT_YUV420P, 320, 240);
   8: avpicture_fill(&srcpic, m_RGBImageData, PIX_FMT_BGR24, 320, 240);
   9:  
  10: sws_scale( m_convert_ctx, srcpic.data, srcpic.linesize, 0, 240, dstpic.data, dstpic.linesize);
  11:  

변환을 위해서 sws_getContext() 함수를 이용한다. sws_getContext()함수로 변환 핸들을 얻은 후 이 핸들을 sws_scale()로 실질적인 변환 작업을 한다. sws_getContext는 소스의 가로, 세로, 파일포맷, 변환할 가로, 세로, 포맷을 지정한다. sws_scale 함수는 소스 AVPicture를 변환 AVPicture로 변환해 준다. ㅋㅋㅋ 뭔 소린지 ㅋㅋㅋ

BGR24를 YUV420P로 변환 하였다. 이제 인코딩 하면 된다.

   1: int encoded_size = 0;
   2: BYTE *ENCODED_DATA = 1024*1024;
   3: int ENCODED_SIZE = 1024*1024;
   4:  
   5: avpicture_fill((AVPicture*)m_picture, m_YUVImageData, 320, 240);
   6:  
   7: encoded_size = avcodec_encode_video(m_context, ENCODED_DATA, ENCODED_SIZE, m_picture);
   8:  

자 요렇게 인코딩 하면 된다.

 

 

'프로그래밍 > C | C++' 카테고리의 다른 글

UPNP 프로토콜  (0) 2013.08.21
클래스 private, protected, public 멤버 및 상속  (0) 2013.08.14
클래스 가상함수, 순수가상함수  (0) 2013.08.14
윈도우 malloc  (0) 2013.08.14
RTPLIB 1.0b 라이브러리 예제  (0) 2013.08.14

멤버
private : 자식놈은 여기 있는 함수, 변수 사용못함

protected: 자식놈은 여기 있는 함수, 변수를 private처럼 사용할수 있음.

public: 개나 소나 다 사용할수 있음.

상속
private : 부모의 모든 속성이 private로 변함. 자식 사용 못함.

protected: 부모의 모든 속성이 protected로 변함. 함수, 변수를 protected처럼 사용할수 있음.

public: protected 멤버 이상 사용할수 있음.

'프로그래밍 > C | C++' 카테고리의 다른 글

UPNP 프로토콜  (0) 2013.08.21
FFMPEG 디코딩 API 사용법  (1) 2013.08.16
클래스 가상함수, 순수가상함수  (0) 2013.08.14
윈도우 malloc  (0) 2013.08.14
RTPLIB 1.0b 라이브러리 예제  (0) 2013.08.14
쉽게 설명해서.

가상함수 : 자식놈이 생성안해도 되는 함수.
virtual 부모::printf()
{
 printf("hi\n");
}

virtual 자식::printf()
{
 printf("hi 자식\n");
}

가상테이블에 printf에 대해서 등록하고
자식이 이걸 오버라이딩 하면 자식으로 등록됨.

1. 부모클래스 *a = new(자식클래스);
a->printf(); // 자식놈이 printf()

2. 부모클래스 *a = new(부모클래스);
a->printf(); // 부모 printf();

3. 자식클래스 *a = new(자식클래스);
a->printf(); // 자식 printf();

순수가상함수 : 자식놈이 반듯이 생성해야 하는 함수.
virtual printf()=0;

가상테이블에 함수를 만들고 자식이 생성해야지만 사용되는 함수.
자식이 호출 안하려면 생성 안해도 됨.
 

'프로그래밍 > C | C++' 카테고리의 다른 글

FFMPEG 디코딩 API 사용법  (1) 2013.08.16
클래스 private, protected, public 멤버 및 상속  (0) 2013.08.14
윈도우 malloc  (0) 2013.08.14
RTPLIB 1.0b 라이브러리 예제  (0) 2013.08.14
RTP 문서 한글 번역본  (0) 2013.08.14
struct _CrtMemBlockHeader
{
       struct _CrtMemBlockHeader * pBlockHeaderNext;
       struct _CrtMemBlockHeader * pBlockHeaderPrev;
       char *                      szFileName;
       int                         nLine;
       size_t                      nDataSize;
       int                         nBlockUse;
       long                        lRequest;
       unsigned char               gap[nNoMansLandSize];
} _CrtMemBlockHeader;

메모리 할당 하믄
할당된 메모리 이전에 요 구조체 데이터가 있다.
메모리를 링크 리스트 형태로 관리하는듯.
HeaderNext, HeaderPrev, FileName ??어디에 쓰지??
nLine 요것두??, nDataSize (사용자가 요청한 크기), nBlockUse (할당 되었는지
free되었는지 여부), lRequest pass --;;  nNoMansLandsize = 4byte
이 헤더 다음에 다시 4바이트가 붙고 그후 사용자가 할당한 메모리 주소가
나온다.
앞 4바이트는 뭔지 모르겠음. 그냥 chunk 인가.
 
요 예제..example1.c
example2.c 요거

전송 과 받는 측 예제인데...

RTPExecute()요함수 부분에서 리턴값을 검사를 해서
RTP_OK가 아니면 다음 처리로 넘어가야 하는데

 while (evt_queue != NULL && evt_queue->event_time <= now) {
   /* There is a pending RTP event (currently this means there's
    * an RTCP packet to send), so run it. */
   RTPExecute(evt_queue->cid, evt_queue->event_opaque);
   /* Advance the queue */
   next = evt_queue->next;
   free(evt_queue);
   evt_queue = next;
 }
봐라 소스... 리턴값 검사 안한다.
RTPExecute시, DONT_SEND_NOW가 발생할수 있다.
즉, 지금 현재 보낼수 없고 다음에 보낸다는 것인데.
리턴값 검사안하고 함수 호출후 현 이벤트큐를 지워 버린다.
즉, 상대방에게 SR 또는 RR를 보내지 못하게 된다.

못 보내면 어떻게 되냐고??
SR과 RR정보는 패킷이 몇개 수신 되었고 몇개 짤렸고
언제쯤 도착했다라는 정보가 있고
그 정보로 네트워크 전송 속도를 제어 한다든지 인코딩시
저화질 , 고화질로 선택 할수 있다는거지.

ADSL 라이트로 XVID 인코딩된 데이터 다중채널(2영상) 보내다가
컴퓨터가 디져븠다.

왜 죽을까 원인은 모른다^^
송신 버퍼가 가득 차서 일까^^

RTP 요거 물건이긴 한데
다중채널일시 네트워크 대역폭은 계산해야 한다는거!
 

'프로그래밍 > C | C++' 카테고리의 다른 글

클래스 가상함수, 순수가상함수  (0) 2013.08.14
윈도우 malloc  (0) 2013.08.14
RTP 문서 한글 번역본  (0) 2013.08.14
문자열 처리 함수 정리  (0) 2013.08.14
FFMPEG 압축 기본적인 사용법  (0) 2013.08.14

 

RTP: A Transport Protocol for Real-Time Applications(RFC1889)

 

1. 개요

 

   RTP 오디오, 비디오 시뮬레이션 데이터와 같은 실시간 데이터 멀티캐스트 또는 유니캐스트 네트웍을 이용해서 전송하는 응용 서비스에 알맞은 단말--단말 네트웍 전송 기능을 제공한다.  RTP 자원 예약을 수행하지 않으며, 따라서 적시 전달, 순차 전달과 같은 서비스 품질도 보장하지 않는다. RTP 데이터 전송 기능은 제어 프로토콜에 의해 확장되는데, RTCP 불리우는 제어 프로토콜은 데이터의 전달 상황을 감시하며, 최소한의 제어 기능 매체 식별 기능 제공한다. RTP RTCP 하위의 전송 네트웍 계층에 무관하게 설계되었다.

 

   RTP 별개의 독립 계층으로 구현되기 보다는 특정 응용에서 요구되는 정보를 제공하여 프로토콜의 처리가 응용의 처리 과정으로 통합될 있도록 설계되었다. 따라서 기존의 프로토콜들과는 달리 RTP 응용의 필요에 따라 헤더를 변경하거나 추가하여 응용에 맞는 프로토콜이 있도록 하는 일종의 맞춤형 프로토콜이다. 문서에서는 RTP 이용할 있을 것으로 추정되는 모든 응용들이 공통적으로 필요로 기능들 만을 명시하고 있다. 따라서, 특정 응용 서비스에 필요한 RTP 구현하기 위해서는 문서 이외에 RTP 페이로드의 종류와 형식을 정의하는 프로파일 문서(Profile Specification) 페이로드의 전송 방법을 정의한 페이로드 형식 문서(Payload Format Specification) 필요하다.

 

 

2. RTP 데이터 전송 프로토콜

 

    0                      1                      2                   3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |V=2|P|X|  CC  |M|       PT      |           sequence number         |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                                    timestamp                          |

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |               synchronization source (SSRC) identifier            |

  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                 contributing source (CSRC) identifiers             |

   |                                      ....                              |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

   모든 RTP 패킷의 상위 12바이트는 다음과 같이 고정되어 있으며, 이후의 CSRC 필드는 혼합기(Mixer) 삽입했을 경우에만 존재한다. 고정 헤더 필드의 부분은 다음의 의미를 가진다.

 

n  Version(V) : 2비트 필드로 현재는 항상 2 값을 가진다.

n  Padding(P) : 1비트 필드로 1 값을 가지면 패킷에 하나 이상의 채워넣기 바이트가 포함되어 있음을 나타낸다. 마지막 채워넣기 바이트는 패킷에서 무시되어야 하는 채워넣기 바이트의 수를 나타낸다.

n  Extension(X) : 1비트 필드로 1 값을 가지면 고정 헤더 이후에 정확히 하나의 확장 헤더가 등장함을 의미한다.

n  CSRC Count(CC) : 4비트 필드로 고정 헤더 이후에 나열되는 CSRC 식별자의 수를 나타낸다.

n  Marker(M) : 1비트 필드로 필드의 해석은 프로파일에 의해서 결정된다. 필드는 패킷 스트림 내에서 프레임 경계와 같은 중요한 이벤트들을 표시하는데 이용된다. 프로파일은 추가 표시 비트들을 정의하거나 PT 필드를 확장하여 표시 비트를 없앨 수도 있다.

n  Payload Type(PT) : 7비트 필드로 RTP 페이로드의 타입을 나타낸다. 프로파일에서 페이로드 타입의 값과 실제 페이로드 형식을 연결한다.

n  Sequence Number(SN) : 16비트 필드로 송신되는 RTP 패킷에 대해 1 증가하는 값을 가진다. 수신 측에서는 패킷 분실을 검출하거나 패킷의 순서를 맞추는데 이용된다. 초기값은 보안을 위해서 무작위로 설정된다.

n  Timestamp : 32비트 필드로 RTP 데이터 패킷의 첫번째 바이트의 샘플링 순간을 나타낸다. 시계의 주파수는 페이로드의 데이터 형식에 종속되고 형식을 정의하는 프로파일이나 페이로드 형식 문서에 정적으로 명시된다. 초기값은 순번과 마찬가지로 무작위 수로 설정된다.

n  Synchronization Source(SSRC) Identifier : 32비트 필드로 동기화 소스를 나타낸다. 값은 같은 RTP 세션 내에서 같은 SSRC 가진 동기화 소스가 두개 이상 나타나지 않도록 무작위로 선택된다.

n  Contributing Source(CSRC) Identifiers : 필드에는 0에서 15목록까지 포함될 있으며 목록은 32비트를 차지한다. CSRC 패킷에 포함된 페이로드에 기여한 제공 소스들을 나타낸다. 제공 소스가 15 이상일 경우에도 15개의 제공 소스만 기록된다. 필드는 혼합기에 의해 삽입되고 목록은 혼합되는 모든 소스들의 SSRC 식별자이다.

 

3. RTP 세션 다중화

 

   효과적인 프로토콜 처리를 위해서 다중화 점의 수는 최소화되어져야 한다. RTP 경우에 다중화는 목적지 전송 주소에 의해 수행된다.. 예를 들어 오디오와 비디오로 이루어지는 회의에서 매체는 자신만의 목적지 전송 주소를 가지는 독자의 RTP 세션으로 전송되어야 한다.

 

4. 프로파일에 따른 RTP 헤더 변경

 

   Marker 비트와 PT 필드는 프로파일에 종속된 정보를 수송하지만 고정 헤더에 할당되어 있다. 이유는 많은 응용들이 그것들을 필요로 것이고 이렇게 제공하지 않으면 값들을 위해 다른 32비트를 확장해야 하기 때문이다. PT 필드는 프로파일에서 재정의할 있으며 Marker 비트가 있는 경우는 필드의 최상위 비트에 기록되어야 한다. 외의 특정 페이로드 형식을 위해 필요한 정보들은 패킷의 페이로드 섹션에 기록된다.

 

   특정 응용에서 페이로드 형식과 무관한 부가적인 기능이 필요하면 응용이 따르는 프로파일에서 고정 헤더의 SSRC필드 이후에 바로 추가 헤더 필드를 정의한다. 그러한 응용들은 부가 필드를 바로 참조할 있으며, 프로파일과 무관한 감시기나 기록기들은 아직도 RTP 패킷의 12바이트 만을 해석하여 처리할 있다. 이후 부가 필드가 모든 응용들에서 필요할 것으로 판단되면 새로운 RTP 버전에서 필드를 고정 헤더 필드에 포함시켜 영구적으로 이용될 있도록 한다.

 

5. RTP 헤더 확장

 

   확장 기법은 개개의 구현에서 페이로드 형식에 무관하게 부가적인 정보를 필요로 하는 새로운 기능들을 실험할 있도록 하는데 이용된다. 기법은 확장과 무관한 다른 응용들에서는 확장부가 무시될 있도록 설계되었다. 특정 페이로드 형식에 필요한 부가 정보는 헤더 확장을 이용해서는 안되고, 패킷의 페이로드 섹션으로 전송되어야 한다.

 

    0                      1                      2                      3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |       defined by profile        |             length                |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                             header extension                          |

   |                                    ....                                 |

 

   고정 헤더 필드의 X 비트가 1이면 가변 길이 확장 헤더가 RTP 고정 헤더에 추가된다. 추가되는 위치는 SSRC 이후, 또는 CSRC 존재하면 CSRC 이후 이다. 확장 헤더는 16비트의 길이 필드를 가지고 있는데, 필드는 4바이트의 확장 헤더를 제외하고 확장에 포함된 32비트 워드의 수를 나타낸다. 하나의 확장 헤더만이 RTP 고정 헤더에 추가될 있다. 그러나 확장 헤더의 상위 16비트가 프로파일에서 정의할 있도록 남겨져 있기 때문에 필드를 이용해서 여러 가지의 확장 헤더들을 실험할 수도 있고 다수의 연동 응용들이 각자의 확장 헤더를 실험해 수도 있다.

 

6. RTP 제어 프로토콜 - RTCP

 

   RTCP 기본적으로 세션의 모든 참가자들에게 주기적인 제어 패킷을 보내어 다음의 기능들을 수행한다.

 

n  데이터 분배의 품질에 대한 피드백을 제공한다. 기능은 RTCP Sender Report(SR) Receiver Report(RR) 의해 수행된다.

n  RTP 소스의 식별을 위해 지속적인 식별자를 수송한다. 식별자는 CNAME(Canonical Name)이라 부른다. SSRC 충돌이 발생하거나 프로그램이 다시 시작될 경우에 변경될 있기 때문에 수신자는 참가자들을 일관성 있게 유지하기 위해서 CNAME 필요로 한다.

 

   기능은 모든 참가자들이 RTCP 패킷을 송신할 것을 요구한다. 따라서 RTP 많은 수의 참가자를 수용할 있기 위해서는 송신 비율이 제어되어야 한다. 참가자가 모든 다른 참가자들에게 제어 패킷을 전송하기 때문에 참가자는 전체 참가자의 수를 파악할 있게 된다. 참가자 수를 이용해서 제어 패킷 전송 간격을 조정한다.

 

n  최소한의 세션 제어 정보를 수송한다. 기능은 참가자가 마음대로 가입, 탈퇴할 있는 허술하게 제어되는 세션에서 유용하게 이용될 있는 기능으로 예를 들어 새로운 참가자의 신분 또는 이름을 제공함으로써 다른 모든 참가자의 사용자 인터페이스에 새로운 참가자의 이름을 알릴 있게 된다.

 

6.1 RTCP 패킷 형식

 

if encrypted: random 32-bit integer

    |

    |[------- packet -------][----------- packet -----------][-packet-]

    |

    |             receiver reports               chunk               chunk

    V                                            item  item        item  item

  --------------------------------------------------------------------

   |R[SR|# sender #site#site][SDES|# CNAME PHONE |#CNAME LOC][BYE##why]

   |R[  |# report #  1 #  2 ][     |#               |#          ][  ##    ]

   |R[  |#        #      #    ][    |#               |#          ][  ##    ]

   |R[  |#        #      #    ][    |#               |#          ][  ##    ]

  --------------------------------------------------------------------

   |<------------------  UDP packet (compound packet) --------------->|

 

   #: SSRC/CSRC

 

   RTCP 패킷은 RTP 데이터 패킷과 비슷하게 고정부로 시작을 해서 패킷의 종류에 따라 달라지는 가변 길이의 구조화된 요소들이 뒤따르고 항상 32비트 경계로 끝난다. 이러한 정렬 방식과 고정부의 길이 필드는 RTCP 패킷을 “Stackable”하게 만든다. , 다수의 RTCP 패킷들로 복합 RTCP 패킷을 만들어 하나의 하위 프로토콜 패킷으로 전송할 있게 한다. 복합 패킷을 이루는 RTCP 패킷의 수는 하위 프로토콜의 최대 길이에 의해 한정된다. 복합 패킷의 개별 RTCP 패킷들은 독립적으로 처리되지만 프로토콜의 기능을 수행하기 위해서 다음의 제약이 주어진다.

 

n  송수신 보고(SR, RR) 대역폭 제약이 허용하는 자주 보내서 송수신 통계 치의 정밀도를 최대화시켜야 한다. 따라서 주기적으로 전송되는 복합 RTCP 패킷에는 반드시 보고가 포함되어 있어야 한다.

n  새로운 수신자는 가능한 빨리 소스의 CNAME 수신해서 소스를 식별하고 Lip-Sync 같은 목적을 위해 그것을 매체와 연결해야 한다. 따라서 복합 RTCP 패킷은 SDES CNAME 포함해야만 한다.

n  모든 RTCP 패킷들은 적어도 두개의 개별 패킷으로 구성된 복합 패킷의 형태로 전송되어야 한다. 이때 다음의 형식이 권고된다.

 

1)      Encryption Prefix : 복합 패킷이 암화화 경우에만 무작위 32비트의 값이 전치된다. 값은 매번 전송되는 복합 패킷에 대해 새로운 값으로 선택된다.

2)      SR 또는 RR : 복합 패킷의 RTCP 패킷은 항상 보고 패킷이어야 한다. 이것은 수신되거나 송신된 데이터가 없을 경우에도 마찬가지로, 경우에는 RR 전송된다. 복합 패킷의 나머지 하나가 BYE 경우에도 마찬가지로 보고 패킷이 패킷으로 등장해야 한다.

3)      추가 RR : 수신 보고 소스의 수가 31 넘어서면 보고 패킷에 이어 첨가 RR 패킷이 나와야 한다.

4)      SDES : CNAME 포함하는 하나의 SDES 패킷은 복합 패킷에 반드시 포함되어야 한다. 다른 소스 설명 항목들도 응용에서 필요로 하면 대역폭 제한에 따라 포함될 수도 있다.

5)      BYE 또는 APP : 주어진 SSRC/CSRC 대해 마지막 패킷으로 전송되어야 하는BYE 제외한 나머지 패킷들은 어떤 순서로도 이어질 있다.

 

   변환기와 혼합기들은 다수의 소스로부터 전달되는 개별 RTCP 패킷들을 가능하면 항상 복합 패킷으로 합쳐서 보내는 것이 바람직하다. 그렇게 함으로써 패킷 오버헤드를 줄일 있기 때문이다. 복합 패킷의 전체 길이가 하위 프로토콜의 MTU 초과할 경우는 복합 패킷을 잘라서 여러 개의 하위 프로토콜 패킷에 포함시켜 보낼 있다.

 

6.2 RTCP 전송 간격

 

   RTP 수명에서 천명의 참가자를 하나의 세션에 참가시킬 있도록 설계되었다. 오디오 회의의 경우에 데이터 패킷은 참가자의 수에 상관없이 비교적 일정한 비트율을 가지지만(언제나 발언하는 사람은 사람 이므로) 제어 패킷의 경우에는 참가자의 수에 비례하여 비트율이 증가( 참가자가 나머지 모두에게 일정한 간격으로 제어 패킷을 전송하기 때문에)하게 된다. 따라서 제어 패킷의 전송 간격은 제어되어야 한다. RTCP 할당되는 대역폭은 세션 대역폭의 5% 고정되는 것이 바람직 하다.

 

6.2.1 세션 멤버 관리

 

   새로운 참가자에 대한 새로운 SSRC 가지는 다수의 패킷을 수신할 까지 참가자는 유효하지 않은 것으로 간주한다. 참가자는 어떤 사이트가 5개의 RTCP 보고 간격 동안 RTP 혹은 RTCP 패킷을 보내지 않으면 사이트를 비활성화 상태로 표시하거나 삭제한다. 일단 사이트가 유효화되면 나중에 비활성 상태로 표시되어도 30 까지는 사이트의 상태는 유지되고 RTCP 대역폭을 공유하는 전체 사이트의 수에 계속 계산된다.

 

6.2.2 SDES 대역폭 할당

 

   SDES 필수 항목인 CNAME 이외에 NAME, EMAIL 등과 같은 부가적인 정보에 제어 대역폭을 할당하는 것을 신중하게 고려해야 한다. 이유는 부가 정보들이 수신 보고와 CNAME 전송 간격을 늘려서 전체 프로토콜의 성능 저하를 유발할 있기 때문이다. 일반적으로 참가자에 할당된 RTCP 대역폭의 20% 미만이 이러한 정보들을 수송하는데 이용되도록 권유 된다. 더욱이 모든 SDES 항목이 모든 응용에 포함될 필요는 없다. 포함된 것들은 사용 정도에 따라 적정 비율의 대역폭을 할당받으면 된다.

 

6.3 송신자/수신자 보고

 

   RTP 수신자는 자신이 동시에 송신자일 경우와 아닌 경우에 각각 SR RR 보내서 수신 품질에 대한 피드백을 제공한다. 패킷 종류 코드 이외에 패킷의 다른점은 SR 활성화된 송신자들에 의해서 이용되는 20바이트의 송신자 정보 섹션을 가지고 있다는 것이다. 사이트가 최종 보고 이후 RTCP 전송 간격 동안 아무 데이터 패킷이라도 보냈으면 SR 보내고 그렇지 않았을 경우에는 RR 보낸다.

 

   SR이나 RR 모두 0 이상의 수신 보고 블럭을 가진다. 보고 블럭은 수신자의 최종 보고 이후에 수신자에게 RTP 데이터 패킷을 보낸 SSRC 각각에 대해 하나씩 존재한다. CSRC 리스트에 나열된 CSRC들에 대해서는 보고를 보내지 않는다. SR 이나 RR 패킷에는 최대 31개의 수신 보고 블럭이 포함될 있기 때문에 그것을 초과하는 경우에는 추가적인 RR 패킷들이 초기 SR 또는 RR 패킷에 연이어 쌓일 있다.

 

6.3.1 SR : 송신자 보고 RTCP 패킷

 

    0                      1                      2                      3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |V=2|P|    RC  |    PT=SR=200    |               length              |

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                             SSRC of sender                            |

   +=+=+=+=+=+=+=+=+=+=+=+=end of header=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                  NTP timestamp, most significant word              |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                NTP timestamp, least significant word               |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                             RTP timestamp                              |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                        sender's packet count                          |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                          sender's octet count                         |

   +=+=+=+=+=+=+=+=end of sender information=+=+=+=+=+=+=+=+=+=+=+=+

   |                    SSRC_1 (SSRC of first source)                     |

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   | fraction lost |         cumulative number of packets lost         | 

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |             extended highest sequence number received              |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                          interarrival jitter                          |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                             last SR (LSR)                              |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                      delay since last SR (DLSR)                      |

  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                    SSRC_2 (SSRC of second source)                   |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   :                               ...                                       :  

   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                     profile-specific extensions                      |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

   송신자 보고 패킷은 개의 섹션으로 구성되며, 프로파일에 따른 번째 확장 섹션이 추가될 수도 있다.

  

   헤더

n  Report Count(RC) : 5비트 필드로 패킷에 포함된 수신 보고 블럭의 수를 나타낸다.

n  Packet Type(PT) : 8비트 필드로 RTCP SR 패킷의 경우에는 200 값을 가진다.

n  Length : 16비트 필드로 32비트 워드로 계산된 전체 패킷의 길이에서 1 값으로 헤더와 채워넣기를 모두 포함한다. 0 값도 유효하다.

 

   송신자 정보

n  NTP Timestamp : 64비트 필드로 보고가 보내진 시간을 나타내고 Round-Trip 지연 계산에 이용된다.

n  RTP Timestamp : 32비트 필드로 NTP Timestamp 시간에 상응한다. 그러나 단위와 무작위 오프셋은 데이터 패킷의 RTP Timestamp들과 같다. 이것은 RTP Timestamp 계수와 실제 시간 간의 관계를 이용해서 해당 NTP Timestamp로부터 계산된다.

n  송신자 패킷 계수 : 32비트 필드로 전송 시작에서부터 SR 패킷이 생성될 까지 송신자에 의해 전송된 RTP 데이터 패킷의 수를 나타낸다. 값은 송신자가 SSRC 식별자를 변경하면 초기화 된다.

n  송신자 바이트 계수 : 32비트 필드로 전송 시작에서부터 SR 패킷이 생성될 까지 송신자에 의해 전송된 RTP 데이터 패킷에서 헤더와 채워넣기를 제외한 페이로드 바이트의 수를 나타낸다. 송신자가 SSRC 값을 변경하면 필드는 초기화 된다.

 

   수신 보고 블럭

n  SSRC_n : 32 비트 필드로, 수신 보고 블럭의 정보가 해당되는 소스의 SSRC 식별자를 나타낸다.

n  Fraction lost : 8비트 필드로, 이전 SR 또는 RR 패킷이 송신된 이후 분실된 RTP 데이터 패킷의 비율이 고정 소수로 표현된다. 이것은 분실 비율을 256으로 곱한 정수부를 취하는 것과 같다. 값은 분실된 패킷의 수를 기대된 패킷의 수로 나누어 얻어진다. 중복 수신에 의해 분실이 음의 값을 가질 경우 분실 비율은 0 된다.

n  Cumulative number of packets lost : 24비트 필드로, 소스 SSRC_n으로부터 수신 시작 이래로 분실한 RTP 데이터 패킷 수로 예측된 패킷 수에서 실제 수신된 패킷 수를 값으로 정의된다. 실제 수신된 패킷 수에는 지연 도착된 것과 중복 도착된 것도 포함된다.

n  Extended highest sequence number received : 32비트 필드로, 하위 16비트는 소스 SSRC_n으로부터 수신된 RTP 데이터 패킷의 최고 순번을 포함하고 상위 16비트는 순번을 부록A.1 알고리즘에 따라 관리되는 순번 사이클의 해당 계수로 확장한다.

n  Interarrival jitter : 32비트 필드로, Timestamp 단위로 측정되고 비부호 정수로 표현되는 RTP 데이터 패킷 도착 시간 간의 통계적 가변성의 측정값을 나타낸다. Si 패킷 i로부터의 RTP Timestamp이고 Ri 패킷 i RTP Timestamp 단위로 측정한 도착 시간일 , 패킷 i j 지터 D D(i,j) = (Rj-Ri) - (Sj-Si) = (Rj-Sj) - (Ri - Si) 같이 표현되고, 도착간 지터 J J = J + (|D(i-1, i)| - J)/16 정의된다. 여기서 1/16 Gain Parameter 적당한 수렴 속도를 보장하면서 좋은 소음 감쇄 율을 보장한다.

n  Last SR Timestamp(LSR) : 32비트 필드로, 소스 SSRC_n으로부터 최근에 받은 RTCP SR 일부인 64비트 NTP 타임스탬프의 중간 32비트를 나타낸다. SR 아직 받지 않았으면 필드는 0 값을 가진다.

n  Delay since last SR(DLSR) : 32비트 필드로, 소스 SSRC_n으로부터의 최종 SR 패킷 수신과 수신 보고 블럭 송신간의 지연을 나타내며, 1/65536 단위로 표시된다. SSRC_n으로부터 아직 SR 패킷을 받지 못했을 경우 필드의 값은 0으로 설정된다.

 

[10 Nov 1995 11:33:25.125]           [10 Nov 1995 11:33:36.5]

   n                 SR(n)              A=b710:8000 (46864.500 s)

  ---------------------------------------------------------------->

                         v                 ^

   ntp_sec =0xb44db705 v               ^ dlsr=0x0005.4000 (    5.250s)

   ntp_frac=0x20000000  v            ^  lsr =0xb705:2000 (46853.125s)

      (3024992016.125 s)  v          ^

   r                          v         ^ RR(n)

  ---------------------------------------------------------------->

                              |<-DLSR->|

                              (5.250 s)

 

   A     0xb710:8000 (46864.500 s)

   DLSR -0x0005:4000 (    5.250 s)

   LSR  -0xb705:2000 (46853.125 s)

  -------------------------------

   delay 0x   6:2000 (    6.125 s)

 

   SSRC_r 현재의 RR 보내는 수신자를 의미할 , 소스 SSRC_n SSRC_r 대한 왕복 전송 지연을 다음과 같이 구할 있다. RR 수신된 시간(A) 기록하고, 왕복 전송 시간 A-LSR 최종 SR Timestamp 필드를 이용해서 계산한다. 그리고 여기서 DLSR 빼서 왕복 전송 지연을 구한다.(A - LSR - DLSR)

 

6.3.2 RR : 수신자 보고 RTCP 패킷

 

    0                      1                      2                      3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |V=2|P|    RC  |    PT=RR=201    |               length               |

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                     SSRC of packet sender                             |

  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                 SSRC_1 (SSRC of first source)                        |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   | fraction lost |       cumulative number of packets lost           | 

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |           extended highest sequence number received                |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                      interarrival jitter                              |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                         last SR (LSR)                                  |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                   delay since last SR (DLSR)                         |

   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                 SSRC_2 (SSRC of second source)                       |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   :                               ...                                        :  

  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                      profile-specific extensions                     |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

   RR 패킷의 형식은 패킷 종류 필드가 201 값을 가지고 송신자 정보 5워드(NTP/RTP Timestamp, Sender Packet/Byte Counter) 생략되어 있다는 것을 제외하고는 SR 같다. 보고할 데이터의 전송이나 수신이 없을 경우에는 복합 RTCP 패킷의 전단에 RC 값이 0 RR 패킷을 삽입한다.

 

6.3.3 송신자 수신자 보고 확장

 

   송신자 또는 수신자에 대해서 주기적으로 보고되어야 추가 정보가 있을 경우 프로파일은 프로파일에 따른(profile-specific) 또는 응용에 따른(application-specific) 송신자와 수신자 보고에 대한 확장을 정의해야 한다. 추가적인 송신자 정보가 필요할 경우, 우선은 송신자 보고의 확장부에 포함되어야 한다. 수신자에 관계되는 정보가 포함될 경우 데이터는 기존의 수신 보고 블럭의 배열에 병치하는 블럭의 배열로 구조화될 것이다. , 블럭의 수가 RC 필드에 반영될 것이다.

 

6.3.4 송신자 수신자 보고 분석

 

   수신 품질 피드백은 송신자 뿐만 아니라 다른 수신자들이나 제삼자 모니터들에도 유용할 것이다. 송신자는 피드백에 따라서 전송 율을 변경할 있고, 수신자는 문제가 지역적인 것인지, 전역적인 것인지를 결정할 있으며, 관리자는 멀티캐스트 분배를 위한 그들의 망의 성능을 평가하기 위해서 RTCP 패킷 만을 수신하는 프로파일에 무관한 모니터를 이용할 있다.

 

   송신자 정보와 수신자 보고 블럭에 모두 누적 계수가 이용되기 때문에 보고 사이의 차이점을 계산하여 단기 그리고 장기에 걸친 측정을 하고 보고의 분실에 대한 회복력을 제공한다. 수신된 최근 보고의 차이가 분배의 최근 품질을 측정하는데 이용될 있다. 보고 사이의 기간 동안 이러한 차이점으로부터 여러 가지 Rate들이 계산될 있도록 NTP 타임스탬프가 포함되었다. 타임스탬프는 데이터 인코딩을 위한 클럭 속도와 무관하기 때문에 인코딩과 프로파일에 무관한 품질 감시기를 구현할 있다.

 

6.4 SDES : 소스 설명 RTCP 패킷

 

    0                      1                      2                      3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |V=2|P|    SC  |    PT=SDES=202  |                 length            |

  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                          SSRC/CSRC_1                                  |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                           SDES items                                  |

   |                              ...                                       |

  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

   |                          SSRC/CSRC_2                                  |

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  

   |                           SDES items                                  |

   |                              ...                                       |

  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

 

   SDES 헤더와 0 이상의 Chunk 구성된 3-계층 구조로 Chunk Chunk 식별된 소스를 설명하는 Item들로 구성된다.

 

n  Packet Type(PT) : 8비트 필드로, RTCP SDES 패킷의 경우에는 202 값을 갖는다.

n  Source Count(SC) : 5비트 필드로, SDES 패킷에 포함된 SSRC/CSRC Chunk 수를 나타낸다. 0 값도 유효하지만 아무런 의미도 없다.

 

   Chunk SSRC/CSRC 식별자와 SSRC/CSRC 관계되는 정보를 수송하는 0 이상의 Item들의 리스트로 구성된다. Chunk 32비트 경계에서 시작한다. Item 8비트의 종류 필드와 텍스트의 길이를 나타내는 8비트의 바이트 계수, 그리고 텍스트 자체로 구성된다. 텍스트는 255 바이트까지 이용할 있지만 RTCP 대역폭 소비에 관계된다는 것을 인지해야 한다.

 

   텍스트는 ISO 10646 Annex F 명시된 UTF-2(UTF-8 또는 UTF-FSS로도 알려짐) 인코딩 방법에 따라 인코딩 된다. US-ASCII 인코딩 기법의 부분집합이기 때문에 부가적인 인코딩을 필요로 하지 않고, 캐릭터의 최상위 비트를 1 설정함으로써 멀티-바이트 인코딩 기법으로 인코딩 사실을 알린다.

 

   종단 시스템은 고정 RTP 헤더의 SSRC 같은 값의 소스 식별자를 포함하는 하나의 SDES 패킷을 보낸다. Item 가운데 CNAME 만이 필수이다.

 

      CNAME(Canonical End-point Identifier, 1) : CNAME 무작위로 할당된 SSRC 식별자에 충돌이 발생할 있고, 프로그램이 재시작 되었을 SSRC 식별자가 변경될 있기 때문에 새로운 SSRC 식별자와 상시적으로 남아있는 소스를 위한 식별자와의 바인딩을 제공한다. SSRC 식별자처럼 CNAME RTP 세션 내에서 유일해야 한다.

 

    0                      1                      2                      3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |    CNAME=1    |      length    |  user and domain name       ...|

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

   CNAME 가능하면 자동적으로 만들어져야 한다. 이러한 요구 사항을 만족시키기 위해서 프로파일이 별도로 명시하지 않은 “user@host” 또는 “host” 형식이 이용되어야 한다. 이러한 형식의 장점은 NAME 아이템을 별도로 전달할 필요가 없다는 점이고, 단점은 사용자가 호스트에서 다수의 소스를 생성할 경우 소스에 대해 유일한 식별자가 제공되지 못한다는 점이다.

 

   NAME(User name, 2) : 소스를 설명하는데 이용되는 실명으로, 사용자가 원하는 어떤 형태로도 기록될 있다. NAME 적어도 세션 기간 동안에는 변함이 없어야 하며, 세션 내에서 유일할 필요는 없다. 프로파일에서 Item들의 우선순위를 정하여 전송되는 횟수를 결정하는데 도움을 있다.

 

      EMAIL(Electronic mail address, 3) : RFC822 명시된 형식의 전자 메일 주소로, 세션 기간 동안에는 변함이 없어야 한다.

 

      PHONE(Phone number, 4) : 국제 접속 코드를 + 기호로 변경한 형태의 전화번호이다. , +1 908 555 1212

 

      LOC(Geographic user location, 5) : Item 응용에 따라서 정밀도가 달라진다. “Murray Hill, New Jersey”, “Room 2A244, AT&T BL MH” 같이 표현될 있다. 정밀도의 결정은 구현자나 사용자에 달려있지만 각각의 형식과 내용은 프로파일에 서술되어 있을 있다. 이동 호스트를 제외하고는 세션 기간 동안에는 변함이 없어야 한다.

 

      TOOL(Application or tool name, 6) : 실시간 스트림을 생성하는 응용의 이름과 버전 등을 나타낸다. 정보는 디버깅 목적에 유용하다. 정보 또한 세션 기간 동안에는 변함이 없어야 한다.

 

      NOTE(Notice/status, 7) : 소스의 상태를 설명하는 임시 메시지(, “on the phone, can’t talk”) 전달하거나 세미나 중일 경우 발표의 제목을 전달하는 등의 목적으로 이용되는 부분 이지만 프로파일에 의해 다른 용도로 정의될 수도 있다. NOTE 예외적인 정보를 수송하는 데만 이용되어야 하고 모든 참가자들이 일상적인 용도로 이용해서는 안된다. 그렇게 되면 제어 패킷 대역폭이 낭비되어 CNAME 같은 중요한 정보가 전달되는 간격이 늘어나기 때문이다. NOTE Item 활성화된 동안에는 디스플레이하는 것이 중요하기 때문에 NOTE Item RTCP 대역폭을 차지할 있도록 CNAME 아닌 다른 Item들의 전송을 자제하는 것이 좋다. 수신 측에서 NOTE Item 반복 기간의 기간 동안 또는 20~30 RTCP 기간 동안 NOTE 받지 못하면 NOTE Item 비활성화 것으로 간주해야 한다.

 

      PRIV(Private extensions, 8) : Item 실험적인 또는 응용에 따른 SDES 확장을 정의하는데 이용된다. Item 길이-스트링 쌍으로 구성된 전치부(prefix) Item 나머지를 채우고 원하는 정보를 수송하는 스트링(value string) 포함한다. 전치부 길이 필드는 8비트 필드이고 전치 스트링은 PRIV Item 응용이 수신할 다른 PRIV Item들에 대해 유일하게끔 정의하는 이름으로 사람에 의해서 선택된다. SDES PRIV 전치부는 IANA 등록되지 않는다. 대신 어떤 종류의 PRIV Item 일반적인 유용성을 가지고 있는 것으로 판명되면 IANA 등록된 정규 SDES Item 타입을 할당되어 전치부를 필요하지 않게 해야 한다. 이렇게 하는 것이 사용을 단순화 시키고 전송 효율을 증가시킨다.

 

6.5 BYE : Goodbye RTCP 패킷

 

   BYE 패킷은 소스가 이상 활성화 상태가 아님을 알린다.

 

n  Packet Type(PT) : 8비트 필드로, RTCP BYE 패킷은 203 값을 갖는다.

n  Source Count(SC) : 5비트 필드로 BYE 패킷에 포함된 SSRC/CSRC 식별자의 수를 나타낸다. 0 값도 유효하지만 의미는 없다.

 

   BYE 패킷이 혼합기에 도착하면 혼합기는 SSRC/CSRC 식별자 변경 없이 그대로 전달한다. 혼합기가 수행이 중지될 자신의 SSRC 식별자와 자신이 처리하는 모든 제공 소스들을 나열한 BYE 패킷을 보내야 한다. 부가적으로 BYE 패킷은 8비트 바이트 계수와 계수 바이트 크기의 탈퇴 이유(, “camera malfunction”, “RTP loop detected”) 나타내는 텍스트를 포함할 있다.

 

6.6 APP : Application-defined RTCP 패킷

 

   새로운 응용이나 새로운 기능이 개발되었을 패킷 종류 값을 등록할 필요 없이 실험을 목적으로 사용되기 위한 패킷이다. 인식할 없는 이름을 가진 APP 패킷은 무시되어야 한다. 실험 후에 광범위한 이용성이 판명되면 APP 패킷은 타입과 이름 필드 없이 재정의되어 RTCP 패킷 타입을 이용해서 IANA(Internet Assigned Number Authority) 등록하도록 권고된다.

 

n  Subtype : 5비트 필드로 일련의 APP 패킷들이 하나의 유일한 이름으로 정의될 있도록 하는 타입으로 사용되거나 응용에 종속되는 데이터를 위해 사용될 있다.

n  Packet Type(PT) : 8비트 필드로 RTCP APP 패킷은 204 값을 가진다.

n  Name : 4바이트 필드로 응용이 수신할 다른 APP 패킷들에 대해 유일하도록 APP 패킷을 정의하는 이름으로 사용자에 의해 선택된다.

n  Application-specific Data : 가변 길이의 필드로 APP 패킷에 나타날 수도, 나타나지 않을 수도 있다. 필드는 RTP 의해 해석되지 않고 응용에 의해 해석된다. 필드의 길이는 32비트의 배수여야 한다.

 

7. RTP 변환기와 혼합기

 

   RTP 변환기와 혼합기는 두개 이상의 전송 계층 “cloud” 연결한다. 일반적으로, cloud 공통 네트웍 전송 프로토콜(, IP/UDP) 멀티캐스트 주소 또는 쌍의 유니캐스트 주소, 그리고 전송 계층 목적지 포트로 정의된다.

 

   변환기 : RTP 패킷들을 SSRC 식별자 변경 없이 전달한다. 이렇게 하여 같은 변환기를 통과하여 변환기의 네트웍 소스 주소를 부여 받는 다양한 소스로부터의 스트림들이 수신기에서 구분될 있게 된다.

 

   혼합기 : 하나 이상의 소스로부터 RTP 데이터 패킷 스트림을 수신해서 데이터 형식을 변경하여 스트림들을 같은 방법으로 결합하고 결합된 스트림을 전달한다. 일반적으로 다수의 입력 소스들 간에 타이밍 동기가 맞지 않기 때문에 혼합기는 이들간에 타이밍 정렬을 수행하여 결합된 스트림을 위한 타이밍을 만들어낸다. 경우 혼합기가 결합된 스트림의 타이밍 소스가 되고, 혼합기에 의해 전달되는 모든 데이터 패킷들은 혼합기의 SSRC 식별자를 부여 받게 된다. 혼합된 패킷에 스트림을 제공한 원래 소스들의 식별자를 유지하기 위해서 혼합기는 그들의 SSRC 혼합된 패킷의 RTP 고정 헤더 바로 다음에 CSRC 식별자 리스트 필드에 기록한다. 혼합기 자신이 제공 소스인 경우에도 자신의 SSRC 식별자를 CSRC 리스트에 포함시킨다.

 

        [E1]                                          [E6]

          |                                             |

   E1:17 |                                      E6:15 |

          |                                            |   E6:15

          V  M1:48 (1,17)           M1:48 (1,17)    V   M1:48 (1,17)

        (M1)-------------><T1>-----------------><T2>-------------->[E7]

          ^                    ^     E4:47               ^   E4:47

    E2:1 |             E4:47 |                          |   M3:89 (64,45)

          |                    |                          |

         [E2]                [E4]        M3:89 (64,45) |

                                                           |         legend:

   [E3] --------->(M2)----------->(M3)------------|         [End system]

          E3:64           M2:12 (64)  ^                           (Mixer)

                                         | E5:45                   <Translator>

                                         |

                                      [E5]               source: SSRC (CSRCs)

                                                        ------------------->

 

7.1 변환기에서의 RTCP 처리

 

   변경된 또는 원래의 데이터 패킷을 전달하는 이외에 변환기와 혼합기는 RTCP 처리도 담당해야 한다. 많은 경우 이것들은 종단 시스템으로부터 전송된 복합 RTCP 패킷들을 분리하여 SDES 정보를 합치고 SR 또는 RR 패킷을 변경한다. 정보의 재전송은 패킷의 도착이나 변환기나 혼합기의 RTCP 간격 타이머에 의해 활성화된다.

 

7.2 혼합기에서의 RTCP 처리

 

   혼합기는 새로운 데이터 스트림을 생성하기 때문에 SR 또는 RR 패킷들을 전혀 통과시키지 않고 대신 혼합기 양단의 종단 시스템을 위한 새로운 정보를 생성한다.

 

7.3 직렬 혼합기(Cascaded Mixers)

 

   직렬로 연결된 혼합기들 가운데 번째 혼합기는 혼합기에서 보내오는 결합된 패킷의 CSRC 리스트와 새로운 입력 패킷들의 SSRC 식별자들을 이용해서 CSRC 리스트를 새롭게 구성한다.

 

8. SSRC 식별자 할당과 이용

 

   RTP 헤더와 RTCP 패킷의 다양한 필드들에 기록되는 SSRC 식별자는 RTP 세션 내에서 유일해야 하는 32비트 무작위 수이다. 특히 같은 네트웍의 참가자들이나 같은 시간에 시작하는 참가자들이 같은 수를 선택하지 않도록 하는 것은 아주 중요하다.

 

8.1 충돌 확률

 

   식별자들이 무작위로 선택되기 때문에, 두개 이상의 소스들이 같은 번호를 선택할 가능성이 있다. 충돌은 모든 소스들이 동시에 시작할 경우 가능성이 가장 높다. 소수의 수가 N이고 식별자의 길이가 L(여기서는 32비트) , 소스가 독립적으로 같은 값을 선택할 확률은 대략 1 - exp(-N**2 / 2**(L+1)) large N 정도 이다. 예를 들어, N 1000이면 확률은 10**-4 이다.

 

   일반적인 충돌 확률은 위의 최악의 경우에 비해 훨씬 낮다. 새로운 소스가 모든 소스들이 유일한 식별자를 가지고 있는 RTP 세션에 참가할 , 충돌 확률은 공간 밖에서 이용되는 숫자들의 일부(the fraction of numbers used out of the space) N/2**L 이다. 다시 N 1000 확률은 2*10**-7 이다.

 

   충돌 확률은 소스가 자신의 패킷을 송신하기 이전에 다른 참가자들로부터 패킷을 수신할 기회가 높다는 점에서 더욱 줄어들게 된다. , 소스가 다른 참가자들을 미리 추적해 있다면 자신의 식별자가 다른 소스들의 식별자와 충돌하는지를 살펴볼 있고, 충돌의 경우 다른 식별자를 선택할 있게 되는 것이다.

 

8.2 충돌 해소와 루프 검출

 

   SSRC 식별자 충돌 확률이 낮기는 하지만 모든 RTP 시스템들은 충돌 검출 기능과 충돌을 해결할 적절한 수단이 준비되어 있어야 한다. SSRC 식별자 충돌을 발견한 소스는 RTCP BYE 패킷을 보내고 다른 식별자를 선택해야 한다. 다른 소스의 식별자가 충돌하는 것을 발견(SSRC 같고 소스 전송 주소나 CNAME 다른) 소스는 하나로부터의 패킷은 포기하고 나머지는 받아들일 있다. 경우 이러한 상황이 오래 지속되지 않도록 충돌하는 소스가 충돌을 해결해야 한다.

 

9. 보안

 

   RTP 응용들에서 요구하는 인증(Authentication), 무결성(Integrity), 그리고 신뢰성(Confidentiality) 등과 같은 보안 서비스는 궁극적으로 하위 계층 프로토콜들이 모두 제공할 것이다. 이러한 서비스들이 최근에 IP 명시되었다. RTP 이용할 것으로 보이는 초기의 오디오/비디오 응용들이 신뢰성 서비스의 필요성을 확립 시켜 놓았기 때문에 다음 섹션에 하위 계층 서비스가 유용할 까지 RTP, RTCP 함께 이용될 신뢰성 서비스를 정의한다. 서비스를 위한 프로토콜상의 부담(Overhead) 그리 많지 않기 때문에 가까운 장래에 하위 계층 서비스에 의해 쓸모 없어진다 하더라도 피해는 크지 않을 것이다.

 

9.1 신뢰성

 

   신뢰성은 의도된 수신자만이 수신된 패킷들을 디코딩할 있어야 함을 의미한다. 대부분의 경우 내용의 신뢰성은 암호화(Encryption) 의해 성취된다. 기본 암호화 알고리즘은 RFC1423 설명된 cipher block chaining(CBC) 모드의 DES(Data Encryption Standard) 알고리즘을 이용한다.

 

9.2 인증과 메시지 무결성

 

   서비스들은 관리 하부구조 없이는 구현할 없기 때문에 RTP 버전에서는 명시하지 않는다. 장래에는 하위 계층 프로토콜에 의해서 서비스들이 지원될 것이다.

 

10. 네트웍 전송 프로토콜상의 RTP

 

   RTP RTP 데이터와 RTCP 제어 스트림의 분리를 하위 프로토콜에 의존한다. UDP 비슷한 프로토콜들의 경우, RTP 짝수 포트 번호를 이용하고 RTP 해당하는 RTCP 스트림은 바로 상위의 홀수 포트 번호를 이용한다. RTP 데이터 패킷들은 길이 필드나 경계를 포함하지 않기 때문에 길이 확인은 하위 프로토콜에 의존한다. RTP 패킷의 최대 길이는 오직 하위 프로토콜에 의해서만 제한된다.

 

   RTP 패킷들이 메시지 단위가 아닌 연속된 바이트 스트림으로 추상화하는 하위 프로토콜에 수송되어야 경우, RTP 패킷의 캡슐화는 프레이밍 기법을 지원하도록 정의되어야 한다. 프레이밍 기법은 여기서 정의하지 않는다.

 

 

'프로그래밍 > C | C++' 카테고리의 다른 글

윈도우 malloc  (0) 2013.08.14
RTPLIB 1.0b 라이브러리 예제  (0) 2013.08.14
문자열 처리 함수 정리  (0) 2013.08.14
FFMPEG 압축 기본적인 사용법  (0) 2013.08.14
[펌] C 프로그래머가 알아야할 것들 #4  (0) 2013.08.14
가끔 문자열 처리나오면 새로 만드는 귀찮니즘 때문에



"C기초에서 그래픽까지" 김정철님께서 쓰신 책의 내용을 정리해서



올려봅니다..



조금이나마 도움이 됐으면 합니다...



** 문자열 처리 함수 **

#include <string.h>



1) 문자열의 길이 측정(strlen)

   strlen(문자열)



2) 문자열 결합(strcat)

   char *ptr;

   ptr = strcat(문자열, 문자열2)



3) 문자열 비교(strcmp)

   int i;

   i = strcmp(문자열, 문자열2)

   i == 0 <--- 같다.

   i != 1 같지 않다.



4) 문자열 복사(strcpy)

   char *ptr;

   strcpy(문자열, 문자열2)

   문자열2를 문자열로 복사



5) 문자열 변환(atoi, atol, atof)

   atoi -> 정수

   atol -> long

   atof -> 부동 소수점.

   

   long b; float c;

   int a;

   a = atoi("1234");

   b = atol("-544334");

   c = atof("43.5456);



6) 문자열 교환 함수 (str_swap)

   void str_cpy(char *ptr1, char *ptr2)

   {

      while(*ptr2)

       *ptr1++ = *ptr2++;

      *ptr1 = NULL;

   }



   void str_swap(char *a, char *b)

   {

      char temp[255];



      str_cpy(temp, a)

      str_cpy(a, b);

      str_cpy(b, temp);

   }

7) 문자열 내에서 특정 문자의 개수를 카운트 하는 함수(char_cnt)

   int char_cnt(char *ptr, char ch)

   {

       int i = 0;

       wile(*ptr)

       if(*ptr++ == ch)

       i++;

       return(i);

   }  



8) 문자열 내의 특정 문자를 다른 문자로 바꾸는 함수(str_chg)

   int str_chg(char *ptr, char ch1, char ch2)

   {

      while(*ptr)

      {

       if(*ptr == ch1)

          *ptr = ch2;

       ptr++;

      }

   }



9) 문자열 내의 특정 문자를 탐색(str_fine)

   char *str_find(char *ptr, char ch)

   {

      while(*ptr)

      {

       if(*ptr == ch)

          return(ptr);

       ptr++;

      }

      return((char *)(-1));

   }



10) 문자열 내의 소문자를 대문자로 바꾸는 함수(str_upp)

   void str_upp(char *ptr)

   {

      while(*ptr)

      {

       if(islower(*ptr))

          *ptr = toupper(*ptr);

       ptr++;

       }

   }



11) 문자열 내의 대문자를 소문자로 바꾸는 함수(str_low)

   void str_low(char *ptr)

   {

      while(*ptr)

      {

       if(isupper(*ptr))

          *ptr = tolower(*ptr);

       ptr++;

      }

   }



12) 대문자와 소문자 상호 교환 함수(str_case)

   void str_case(char *ptr)

   {

      while(*ptr)

      {

       if(isupper(*ptr))

          *ptr = tolower(*ptr);

       else if(islower(*ptr)

          *ptr = toupper(*ptr);

       ptr++;

      }

   }



13) 각 문장의 첫 문자만 대문자로 교환하는 함수(str_fst)

   void str_fst(char *ptr)

   {

       if(islower(*ptr))

       *ptr = toupper(*ptr);

       while(*ptr)

       {

       if(*ptr == '.')

       {

          ptr++;

          while(*ptr == ' ')

           ptr++;

          if(islower(*ptr))

           *ptr = toupper(*ptr);

       }

       }

       ptr++;

   }



14) 문자열 내의 지정한 위치에 다른 문자열을 삽입하는 함수(str_ins)

   char *str_ins(char ptr1[], char ptr2[],int t)

   {

      char temp[255];

      int i, j, k;

      if(t>=str_len(ptr1) return (-1);

     

      for(k=0; i=0; i<t; i++; k++)

       temp[k] = ptr1[i];

   

      for(j=0; ptr2[j] !=NULL; j++, k++)  

       temp[k] = ptr2[j];



      while(ptrl1[i])

       temp[k++] = ptr1[i++];

      temp[k] = NULL;

   }



15) 문자열 앞에 지정한 개수 만큼의 공백을 추가하는 함수(str_blk)

   void str_blk(char ptr[], int t)

   {

      static char temp[255];

      int i;

      for(i = 0; i<t; i++)

       temp[i] = BLANK;

      temp[i] = NULL;

      str_cat(temp ptr);

      str_cpy(ptr, temp);

   }



16) 문자열 내의 모든 공백을 삭제하는 함수(rmv_blk)

   void rmv_blk(char ptrp[])

   {

      char temp[255];

      int i, k;



      for(i=0, k=0; ptr[i] != NULL; i++)

       if(ptr[i] != BLANK)

          temp[k++] = ptr[i];

      temp[k] = NULL;

      str_cpy(ptr, temp);

   }



17) 문자열 내에서 원하는 개수 만큼 문자를 삭제하는 함수(str_rmv)

   void str_rmv(char *ptr, int loc, int count)

   {

      int len, i, j;

      len = str_len(ptr);

      if(loc >= len)

       return (-1);

      if(loc + count <= len)

      {

       j = loc + count;

       for(i=loc; ptr[j] != 0; i++)

       {

          ptr[i] = ptr[j];

          j++;

       }

           ptr[i] = NULL

      }

      else

       ptr[loc] = NULL;

   }



18) 특정한 문자열이 기억된 위치를 계산하는 함수(str_loc)

   int str_loc(char ptr1[], char ptr2[])

   {

      int i, j, k;

      for(i=0; ptr1[i] != NULL; i++)

       for(j=i, k=0; ptr2[k] == ptr1[j]; k++, j++)

          return (i);

      return (-1);

   }



19) 특정한 문자열이 나온 개수를 카운트 하는 함수(str_cnt)

   int str_cnt(char ptr1[], char ptr2[])

   {

      int i, j, k;

      int count = 0;

      for(i=0; ptr1[i] != NULL; i++)

       for(j=i, k=0; ptr2[k] == ptr1[j]; k++, j++)

          if(ptr2[k+1] == NULL)

          {

           count++;

           break;

          }

       return ((count == 0) ? -1 : count);

   }
제목: ffmpeg의 apiexample을 사용한 mpeg encoding 방법
날짜: 2004년 4월 18일
작성: 정성욱 (director@smu.ac.kr)

-Content
---------

1. ffmpeg library 사용법
2. ffmpeg encoding 방법의 종류
 2.1 YUV
 2.2 RGB->YUV
 2.3 YUV library함수 사용하기
 2.4 RGB->YUV library함수 사용하기
3. V4L에서 영상 받기
 3.1 TV Card
 3.2 USB
4. ffmpeg를 사용하여 encoding하기
 4.1 saveToFile
 4.2 포맷에 따는 각각의 MPEG압축 방법
   4.2.1 YUV420P
   4.2.2 RGB24->YUV420P
   4.2.3 YUV library함수 사용하기
   4.2.4 RGB24->YUV420P library함수 사용하기
5. 요약

-서문
------

본 문서는 ffmpeg를 사용하여 video 신호를 mpeg codec로 encoding하는 방법을 소개한다. 여기서 사용하는 source는 libavcodec의 예제 source인 apiexample.c를 가지고 변형하여 작성한 문서임을 밝힌다. Reference문서가 많지 않아서 정확한 의미를 알지 못하는 부분이 있다. 만약 그러한 부분에 대한 정화한 의미를 아는 사람이 있다면, director@smu.ac.kr로 연락주기 바란다. 그럼 이제부터 WebCAM과 TV Card를 사용한 ffmpeg encoding 방법에 대해서 알아보자

1. ffmpeg library 사용법
-------------------------

우리가 ffmpeg library를 사용하기 위해서는 동적 라이브러리 파일을 컴파일시 연결시켜 주어야 한다. 동적 라이브러리 파일은 "*.a"파일로서, 우리가 ffmpeg를 통해 인코디하기 위해 사용하는 파일은 "/libavcodec/libavcodec.a"파일이다. 이렇게 ffmpeg library를 사용하는 파일을 컴파일하는 방법은 다음과 같다.

--------------------------------------------------------------------------------
$ gcc -g -O3 -Wall -o [filename] [filename.c] libavcodec.a -lm -lz -ldl
--------------------------------------------------------------------------------


2. ffmpeg encoding 방법의 종류
-------------------------------

장치가 가지는 신호의 종류는 여러가지이다. 그러나 여기서는 RGB24와 YUV420P두가지의 방식을 사용함을 먼저 밝힌다. RGB24와 YUV420P 두가지의 방식을 사용하여 기본적으로 apiexample에서 제공하는 방식과 함수를 사용하는 방식 2가지로 구분하면 총 4가지의 방식이 나오게 된다. 이제부터 이 4가지의 방식에 대해 소개 하려 한다.

2.1 encoding_YUV.c
-------------------

apiexample.c에서 사용한 기본적인 방식을 사용한 방식이다. AVFrame이라는 구조체를 선헌하고 구조체의 data배열에 실제 영상을 넣어주어 encoding하는 방식이다.

2.2 encoding_YUV_f.c
---------------------

AVPicture 구조체에 avpicture_fill()함수를 사용하여 영상데이터를 library함수로 넣어주는 방식이다.

2.3 encoding_RGB.c
-------------------

encoding_YUV.c와 방식은 동일하다. context의 세팅값은 PIX_FMT_YUV420P방식으로 세팅되어 있다. 단지 영상을 V4L을 통해서 받아 들일때 우리는 RGB24로 받아오게 되는것이다. RGB24로 받아진 영상은 AVPicture라는 구조체에 저장되고 저장된 AVPicture를 img_convert()를 통해서 YUV420P로 변환한다. 그런후에 AVFrame 구조체의 배열인 data에 넣어주고 인코딩하게 되는것이다.

2.4 encoding_RGB_f.c
---------------------

모든 것은 위의 encoding_RGB.c와 동일하나 단지 apiexample.c에서 영상을 받아오는 방법과 틀릴뿐이다. apiexample.c에서는 영상을 받아와 AVFrmae *picture의 data에 넣어 주었다 하지만 여기서는 avpicture_fill()을 사용하여 캡쳐된 영상을 AVPicture 타입의 구조체에 넣어준다. RGB타입인 이 영상을 YUV방식으로 변환하기 위하여 img_convert()함수를 사용하여 RGB24형태의 영상을 YUV420P포맷으로 변환한 후 avcodec_encode_video()를 사용하여 인코드 시키는 것이다.


3. V4L에서 영상받기
--------------------

자세한 내용은 http://pl.smu.ac.kr/researches/projects/linux-dvr/ 의2004-02-02-ELF-Video4linux-Capture.txt 문서를 참조 하기 바란다. 여기서는 TV Card와 USB Camera를 세팅하기 위한 부분만 보도록 하겠다.

--------------------------------------------------------------------------------
int setChannel(struct video_channel chan, int channel_no, int device_type)
{
 switch(device_type)
 {
   /* Case 1 : TV Card setting */
   case 0 :
     chan.channel = channel_no;
     chan.flags = VIDEO_VC_TUNER;
     chan.type = VIDEO_TYPE_TV;
     chan.norm = VIDEO_MODE_NTSC;
     if((rv = ioctl(video, VIDIOCSCHAN, &chan)) < 0)
     {
return -1;
     }
     break;
   case 1 :
     chan.channel = channel_no;
     chan.type = VIDEO_TYPE_CAMERA;
     chan.norm = VIDEO_MODE_NTSC;
     if((rv = ioctl(video, VIDIOCSCHAN, &chan)) < 0)
     {
return -1;
     }
     break;
   default :
     break;
 }
 return 0;
}
--------------------------------------------------------------------------------

3.1 TV Card
------------

위의 소스 코드를 보면 TV Card의 경우 device_type의 값이 0일경우 대응됨을 알 수 있다. 이경우 우리는 총 4가지를 설정해준다. channel값과 flags, type, norm값등이다. 이런 세팅값들은 다른 TV Card들에서도 동일하다.
여기서 channel값은 일반적으로 가정용 비디오에서의 Input의 종류를 의미한다. flag값은 비디오 장치가 tuner를 가지고 있다는 것을 알려준다. tuner가 없을 경우 설정하지 않아도 된다. type값은 비디오 장치의 종류를 의미하고 여기서는 TV Card이기 때문에 VIDEO_TYPE_TVnorm값은 NTSC와 PAL이라는 TV신호의 종류를 의미한다.

3.2 USB
--------

위의 소스 코드를 보면 USB의 경우 device_type의 값이 1일경우 대응됨을 알 수 있다. 이경우 우리는 3가지를 설정해주는데 channel값과 type, norm값을 설정해 주면된다.


4. ffmpeg를 사용하여 encoding하기
----------------------------------

ffmpeg를 사용하여 encoding을 하기 위해서 우리는 크게 5가지의 단계를 거칠 것이다.
첫번째, avcodec_init();
두번째, avcodec_register_all();
세번째, InitCaptureDevice(DEVICE_NAME, VIDEO_INPUT);
네번째, CaptureImage();
다섯번재, SaveToFile(nMaxCount, map, IMG_W, IMG_H);
첫번째 단계는 avcodec_init()함수를 사용하여 avcodec을 초기화 하는 단계이다.
다음으로 우리는 초기화된 avcodec의 모든 codec을 등록해야 하는데 이일을 avcodec_register_all()함수가 해주게 된다.
그러면 일단 avcodec과 관련된 기본적인 설정은 끝난것이다.
다음으로 우리는 우리가 만든 initCaptureDevice()라는 함수를 부르게 된다. 여기에는 DEVICE_NAME와, VIDEO_INPUT이 인수로 들어가게 되는데 둘다 상수 값으로서 DEVICE_NAME는 TV 또는 USB Camera를 의미하고 VIDEO_INPUT은 말 그대로 input의 종류를 의미한다.
그 다음으로 우리는 설정된 디바이스에서 메모리로 사진을 가져오게 된다. 영상은 여러장의 사진이 모아진 것이므로 우리는 먼저 사진을 찍어야하고 이사진들을 여러장 찍어서 동영상을 만드는 작업을 하게 되는 것이다. 따라서, captureImage()라는 함수를 호출하게 된다. 이 함수는 장치 디바이스로 부터 사진을 받아와 v_map이라는 곳에다 저장하게 된다. 그것을 우리는 in_addr이라는 포인터로 접근 할 수 있다.
이렇게 가져온 사진들을 동영상으로 만들기위해 saveToFile(nMaxCount, map, IMG_W, IMG_H);와 같이 saveToFile()함수를 부르게 된다. 이때 같이 넘겨주는 인수는 총 프레임의 수와 v_map영상이 저장된 곳의 주소(여기서는 in_addr) 포인터, 이미지의 가로와 세로 길이를 넘겨준다. saveToFile()함수는 이러한 인수를 받아서 nMaxCount만큼 돌면서 map주소에서 IMG_W*IMG_H크기 만큼의 사진을 얻어와 우리가 설정해준 값에 따라서 파일로 저장해 준다.
그렇다면 이렇게 저장되기 위해서 우리는 어떠한 설정을 해주어야 하는 것이라는 것을 알수 있다. 우리는 encoding을 하기 전에 AVContext라는 구조체의 값을 설정해주어야 한다. 이것은 우리가 encoding할 파일의 정보를 설정하는 것으로서 bitrate, framerate등 영상파일에 대한 정보를 가지고 있다. 자세한 내용은 별첨 문서에서 알아보기로 한다.

4.1 saveToFile
---------------

-----------------------------------------------------------------------------
 /*
  *
  * Function    : saveToFile
  *
  * Description : After encoding a frame, save to file
  *
  * Global      : n_frame_count
  *
  * Arguments   : int n_max_count, char *int_addr, int n_width, int n_height
  *
  */
 void saveToFile( int n_max_count, char *in_addr, int n_width, int n_height )
 {
   unsigned char *y;
   unsigned char *u;
   unsigned char *v;
   int yy;
   int xx;
   int i = 0;

   AVCodec *codec = NULL;
   AVCodecContext *c = NULL;
   int out_size = 0, size = 0, outbuf_size = 0;
   AVFrame *picture = NULL;
   uint8_t *outbuf = 0, *picture_buf = 0;

   if ( n_frame_count == 0 )
   {
     f = fopen ( "test.mpg", "w" );
     if ( !f )
     {
       fprintf( stderr, "could not open test.mpg\n" );
       exit( 1 );
     }
   }

   if( n_frame_count < n_max_count )
   {
     n_frame_count++;
     printf( "Video encoding\n" );

     /* find the mpeg1 video encoder */
     codec = avcodec_find_encoder( CODEC_ID_MPEG1VIDEO );
     if ( !codec )
     {
       fprintf( stderr, "codec not found\n" );
       exit( 1 );
     }
   
     c = avcodec_alloc_context();
     picture = avcodec_alloc_frame();

     /*format*/
     c->get_format = PIX_FMT_YUV420P;
     /* put sample parameters */
     c->bit_rate = 400000;
     /* resolution must be a multiple of two */
     printf("%d, %d\n", n_width, n_height);
     c->width = n_width;
     c->height = n_height;
     /* frames per second */
     c->frame_rate = 30;  
     c->frame_rate_base= 1;
     c->gop_size = 10; /* emit one intra frame every ten frames */
     
     /*maximum number of b frames between non b frames.
       note: the output will be delayed by max_b_frames+! relative to th input
       - encodeing : set by user.
       - decoding: unused*/
     c->max_b_frames=1;

     /* open it */
     if ( avcodec_open( c, codec ) < 0 )
     {
       fprintf( stderr, "could not open codec\n" );
       exit( 1 );
     }
   
     /* the codec gives us the frame size, in samples */
     /* alloc image and output buffer */
     outbuf_size = 100000;
     outbuf = malloc( outbuf_size );
     size = c->width * c->height;
     
     picture_buf = malloc( ( size * 3 ) / 2 ); /* size for YUV 420 */
     picture->data[0] = picture_buf;
     picture->data[1] = picture->data[0] + size;
     picture->data[2] = picture->data[1] + size / 4;
     picture->linesize[0] = c->width;
     picture->linesize[1] = c->width / 2;
     picture->linesize[2] = c->width / 2;
   
     //dp = (unsigned char *)out_addr;
     y = ( unsigned char * )in_addr;
     u = y + n_width * n_height;
     v = u + n_width * n_height / 4;

     fflush( stdout );
     /* prepare a dummy image */
     /* Y */
     for( yy = 0; yy < c->height; yy++ )
     {
       for( xx = 0; xx < c->width; xx++ )
       {
   picture->data[0][yy * picture->linesize[0] + xx] = *y;
   y++;
       }
     }
     /* Cb and Cr */
     for( yy = 0; yy < c->height / 2; yy++ )
     {
       for( xx = 0; xx < c->width / 2; xx++ )
       {
 picture->data[1][yy * picture->linesize[1] + xx] = *u;
     picture->data[2][yy * picture->linesize[2] + xx] = *v;
     u++; v++;
       }
     }

     /* encode the image */
     for( i = 0; i < 2; i++ )
       out_size = avcodec_encode_video( c, outbuf, outbuf_size, picture );
     
     printf( "encoding frame %3d (size=%5d)\n", i, out_size );
     fwrite(outbuf, 1, out_size, f);
   }
 
   if ( n_frame_count == n_max_count )
   {
     n_frame_count++;
     
     /* get the delayed frames */    
     for( ; out_size; i++ )
     {
       fflush( stdout);        
       out_size = avcodec_encode_video( c, outbuf, outbuf_size, NULL );
       printf( "write frame %3d (size=%5d)\n", i, out_size );
       fwrite( outbuf, 1, out_size, f );
     }

     /* add sequence end code to have a real mpeg file */
     outbuf[0] = 0x00;
     outbuf[1] = 0x00;
     outbuf[2] = 0x01;
     outbuf[3] = 0xb7;
     fwrite( outbuf, 1, 4, f );
     fclose( f );
     free( picture_buf );
     free( outbuf );

     avcodec_close( c );
     free( c );
     free( picture );
     printf( "\n" );
   }
 }
-----------------------------------------------------------------------------
위의 소스를 보고 순서 대로 알아보자
여기서는 MPEG1을 사용한 인코딩의 방법만을 알아본다.
우리는 먼저 avcodec_find_encoder() 함수를 사용하여 우리가 사용할 코덱을 찾는다 이 작업이 끝나면 AVContext 형의 c에 avcodec_alloc_context();를 사용하여 메모리 공간을 할당해 준다. 그 다음에 AVFrame 형의 picture에 avcodec_alloc_frame();함수를 사용하여 메모리 공간을 할당하여 준다. 그런후 구조체 c에 값들을 세팅해준다. 여기서 세팅하는 값은 나중에 encoding을 할경우 인자로 같이 넘겨줄 값이다.
그런 다음 우리는 실제 영상이 들어갈 곳을 실제 영상이 있는 곳과 연결 시켜 준다. 이부분은 RGB이인지 YUV인지에 따라서 틀려지는 부분이 될 것이다. 여기서는 YUV420P포멧이기 때문에 Y, U, V 3개로 나눈다. 이것은 picture의 의 멤버 변수인 data[]배열에 연결 시켜주는데, 우리가 v_map에서 얻어온 in_addr의 주소를 data[0]에 넣어주고 data[1]와 data[2]에는 Y값이 차지하는 공간을 계산하고 in_addr주소에 Y가 차지하는 주소 공간을 더한 값을 넣어주게 된다. 이렇게 세팅을 한후 이미지를 인코딩 하는데 이미지가 처음에는 지연에 의해서 인코딩 되지 않기 때문에 2번의 인코딩을 함으로써 우리는 실제 데이터를 얻을 수 있다. 따라서, for문 안에 avcodec_encode_video( c, outbuf, outbuf_size, picture );를 2번 돌려서 우리가 원하는 압축된 사진을 얻을수 있는 것이다. 이렇게 나온 사진을 우리는 바로 파일에 저장한다. 이렇게 끝난후에 우리는 지연된 이미지가 있는지 알아보기위해 처음부터 out_size만큼 지연된 프레임을 확인하고 다시 파일에 저장 하게 된다. 그런후 파일의 마지막에 mpeg헤더를 추가한후에 모든 자원을 해제하고 끝내게 된다.

4.2 포맷에 따는 각각의 MPEG압축 방법
-------------------------------------

포맷과 함수를 사용하는지 마는지에 따라서 우리는 전부 4가지 경우의 사례를 볼 수 있다. 이러한 부분은 전부가 picture의 data배열에 자료를 넣어주는 방법에서 차이를 보이게 된다. 이러한 차이에 대해서 이제부터 알아볼것이다.

4.2.1 YUV420P
--------------

-----------------------------------------------------------------------------
     picture_buf = malloc( ( size * 3 ) / 2 ); /* size for YUV 420 */
     picture->data[0] = picture_buf;
     picture->data[1] = picture->data[0] + size;
     picture->data[2] = picture->data[1] + size / 4;
     picture->linesize[0] = c->width;
     picture->linesize[1] = c->width / 2;
     picture->linesize[2] = c->width / 2;
   
     //dp = (unsigned char *)out_addr;
     y = ( unsigned char * )in_addr;
     u = y + n_width * n_height;
     v = u + n_width * n_height / 4;

     fflush( stdout );
     /* prepare a dummy image */
     /* Y */
     for( yy = 0; yy < c->height; yy++ )
     {
       for( xx = 0; xx < c->width; xx++ )
       {
   picture->data[0][yy * picture->linesize[0] + xx] = *y;
   y++;
       }
     }
     /* Cb and Cr */
     for( yy = 0; yy < c->height / 2; yy++ )
     {
       for( xx = 0; xx < c->width / 2; xx++ )
       {
 picture->data[1][yy * picture->linesize[1] + xx] = *u;
     picture->data[2][yy * picture->linesize[2] + xx] = *v;
     u++; v++;
       }
     }
-----------------------------------------------------------------------------
AVFrame형의 data[0]에는 우리는 원래 YUV420P포맷의 영상을 가지고 있는 주소를 넣어준다. 그런후에 포인터를 Y의 크기 만큼 증가 시킨후에 U의 주소를 data[1]에 넣어주고 V의 주소를 data[2]에 넣어준다. 그런후에 linesize를 정해주는데, Y는 원래 width이고 U,V는 크기가 압축되어 원래 width크기의 반절밖에 안되기 때문에 라인사이즈는 width/2가 된다. 그리고 for문을 돌리면서 각각의  data의 개개별 값에 y, u, v값을 넣어주게 된다.

4.2.2 RGB24->YUV420P
---------------------

-----------------------------------------------------------------------------
   picture_buf = malloc((size * 3) / 2); /* size for YUV 420 */
   picture->data[0] = picture_buf;
   picture->data[1] = picture->data[0] + size;
   picture->data[2] = picture->data[1] + size / 4;
   picture->linesize[0] = c->width;
   picture->linesize[1] = c->width / 2;
   picture->linesize[2] = c->width / 2;

   newPicture.data[0] = picture->data[0];
   newPicture.data[1] = picture->data[1];
   newPicture.data[2] = picture->data[2];
   newPicture.linesize[0] = picture->linesize[0];
   newPicture.linesize[1] = picture->linesize[1];
   newPicture.linesize[2] = picture->linesize[2];

   avpicture_fill(&myPicture, in_addr, PIX_FMT_BGR24, c->width, c->height);
   img_convert(&newPicture, PIX_FMT_YUV420P, &myPicture, PIX_FMT_BGR24, c->width, c->height);
   
   fflush(stdout);
   /* prepare a dummy image */
   /* Y */
   for(y=0;y<c->height;y++)
   {
     for(x=0;x<c->width;x++)
     {
picture->data[0][y * picture->linesize[0] + x] = newPicture.data[0][y * newPicture.linesize[0] + x];
     }
   }
   
   /* Cb and Cr */
   for(y=0;y<c->height/2;y++)
   {
     for(x=0;x<c->width/2;x++)
     {
picture->data[1][y * picture->linesize[1] + x] = newPicture.data[1][y * newPicture.linesize[1] + x];;
picture->data[2][y * picture->linesize[2] + x] = newPicture.data[2][y * newPicture.linesize[2] + x];;
     }
   }
-----------------------------------------------------------------------------
RGB24->YUV420P와 YUV420P와 다른 점은 encoding은 YUV420P로 하지만 처음에 영상을 받은 후에 이미지를 컨버트 하는 점이다. 우리는 avpicture_fill()이란 함수를 사용해 AVPicture형의 구조체에 영상을 받아오게 된다. 받아온 영상은 RGB24포맷이고 이것을 우리는 img_convert()함수를 사용해 YUV420P포맷으로 변화하게 된다. 변화된 영상은 새로운 AVPicture형의 구조체에 할당 되게 되고 이것을 AVFrame의 구조체의 data[0]부터 data[1], data[2]에 넣어주므로서 그냥 YUV420P포맷을 사용했을때와 별 다를바없이 MPEG encoding을 할수 있게 된다.

4.2.3 YUV library함수 사용하기
-------------------------------

-----------------------------------------------------------------------------
   avpicture_fill(picture, in_addr, PIX_FMT_YUV420P, c->width, c->height);
-----------------------------------------------------------------------------
위에서 보았던 것과 같이 AVFrame의 data배열에 직접 넣어주어야 했던 실제 데이터 영상을 우리는 avpicture_fill()이라는 함수를 통해서 자동으로 할 수 있다. 하지만, 여기서는 약간의 문제가 생기게 된다. 무엇이냐면, avpicture_fill()함수의 첫번째인자는 즉 데이터가 들어가게 되는 곳의 형이 AVPicture타입이라는 점이다. AVPicture와 AVFrame의 차이점은 별첨 문서에서 확인하게 될것이다. 이런 것은 우리가 원하는 영상을 얻는데 영향을 미치지는 않는다. 하지만, 컴파일시 warning메세지를 출력하게 된다.
이렇게 간단하게 해주고 우리는 encoding함수를 호출하면 된다.


4.2.4 RGB24->YUV420P library함수 사용하기
------------------------------------------

-----------------------------------------------------------------------------
   avpicture_fill(&temp_picture, buf1, PIX_FMT_BGR24, c->width, c->height);
   avpicture_fill(&temp_picture, in_addr, PIX_FMT_BGR24, c->width, c->height);
   avpicture_fill(&new_picture, buf, PIX_FMT_YUV420P, c->width, c->height);
   img_convert(&new_picture, PIX_FMT_YUV420P, &temp_picture, PIX_FMT_BGR24, c->width, c->height);
   picture = (AVFrame *)&new_picture;
-----------------------------------------------------------------------------
RGB24에서 YUV420P로 변화시키는 과정은 위에서 보았던 함수를 사용하지 않는 것과 차이가 없다. 단지 data배열에 넣는 과정을 함수화 했다는 것이다. 실제 ffmpeg library에 있는 함수를 사용하게 되는것이다. 그리고 형의 충돌을 막기위해 AVFrame형의 picture에 AVPicture형의 new_picture를 넣을때 (AVFrame *)으로 형변환을 해주게 된다.

5. 요약
--------

ffmpeg library에 대한 문서가 정확히 나와있지 않은 상황에서 작성된 문서라 많은 부분이 부족 할 것으로 생각된다. 이 문서를 작성하는 동안에도 새로운 것을 알게 되었고 그부분에 대해서 추가 하지는 못했다. 새로운 방향으로 소스 코드를 고쳐야 하는 문제가 생겼기 때문이다. 그렇지만 기본적으로 영상을 저장하는데는 문제가 없었고 약 1개월동안 Mailing List를 보면서 알게된 점에대해서 부족하지만 적어 보았다. 많은 도움이 되기를 바라고 문제가 있을시에는 director@smu.ac.kr으로 연락 바란다.


끝.
C 프로그래머가 알아야할 것들 - Chapter 4. 프로그램 언어

김성훈 (sunghun84@nate.com)

Chapter 4. 프로그램 언어

(1) 왜 문법을 배워야하는가?
(2) 내가 이걸 배워서 과연 실전에 사용할 수 있을까?
(3) 무엇을 위한 프로그램인가?

(1) 왜 문법을 배워야 하는가?

사실 이건 당연한 이야기 일 수 있는데요, 한국어를 할줄 모르는 독일인과, 독일어를 할줄 모르는 한국인과 대화가 가능할까요? 바디 랭귀지로 하면 되지 않느냐는 분도 계시겠지만 그것도 어느정도 한계가 있기에, 제대로된 의사소통은 불가능할겁니다.

컴퓨터는 0과 1 (2진수)밖에 인식하지 못한다고 앞에서 배웠습니다.
그렇다고 컴퓨터에게 00001110 01010101 이런식으로 모든 명령을 내려야한다면 프로그래머는 정말 수학에 능통한 사람이 아니면 힘들껍니다. (수학에 능통하더라도, 구현할 수 있는 수준은 한계가 있겠죠)

그래서, 기계어에서는 16진수 (2진수를 4개씩 묶어서) 표현하고 있습니다. 2진수일 때보다 조금 나아졌지만, 과연 이걸로 프로그램을 만들 수 있을까요? 이걸로도 뭔가 많이 부족해보입니다.

좀 더 사용자(프로그래머)가 이해하기 쉬운 언어가 필요했습니다.

그래서 나온 어셈블리어는 좀 더 사용자가 알기 편하게 하기 위해, 오퍼랜드, 오퍼레이터, 명령 니모닉등으로 구성되어 있는데, 그렇기에 프로그래머가 기계어보다는 이해하기 쉬웠습니다만, 규모가 큰 프로그램에는 적합하지 못했죠.

결국 프로그래머가 사용하기도 편하고, 기능도(비교적) 우수한 구조형 언어 (파스칼, 코볼,포트란, C언어)등이 나오게되었습니다.

프로그램 언어를 통해 컴퓨터에게 일을 시키기 위해선 어떻게 해야 할까요?

"야 지금부터 시간재라". "야 지금부터 음악 재생좀 해봐."

이렇게 컴퓨터에게 우리가 사용하는 말로 말하면 알까요? (음성인식 프로그램은 예외입니다) 컴퓨터는 컴퓨터가 알 수 있는 말로 해야하기 때문에, 기계어로 말하는건 사실상 불가능에 가까우니, 프로그램 언어의 힘을 빌려야하는데, 그 프로그램 언어가 가진 규칙이 문법입니다. 프로그래머가 컴퓨터와 의사소통을 하기 위해선, 사용하는 프로그램 언어의 문법을 지켜야만 원하는 일을 시키고, 컴퓨터에게 상황을 보고 받을 수 있기 때문에 문법에 대해 알아야 하는건 당연하겠죠?

(2) 내가 이걸 배워서 과연 실전에 사용할 수 있을까?

많은 분들이 하시는 고민. 지금 이 글을 쓰고 있는 저도 마찬가지였던 고민.
바로 그것이 과연 지금 배운 문법들로 프로그램을 만들 수 있겠는가 하는것입니다.
당연한 얘기지만, 정답은 "그렇다"는 것입니다.

그런데, 왜 문법까진 이해했는데, 왜 난 프로그램을 만들 수 없을까하는 생각을 하시는 분들이 많은데요, 그 이유는 프로그래밍에 대한 막연함과, 프로그램 개발 과정에 대한 부족한 이해도등의 이유도 있고, 또 프로그램을 구성하고 있는 기본 법칙이나 내부 원리에 대한 이해가 부족한 이유도 있습니다.

프로그래밍에 대한 막연함이란, 입문서 혹은 문법서에 나온 에제정도만 작성해보았지, 실제 사용할 만한 프로그램 개발 경험이 전무하기 때문에 가지는 부담감이라 할 수 있을겁니다.

프로그램 개발과정에 대한 부족한 이해도란, 프로그래밍이란 단순히 프로그램 언어만 안다고 되는 그런 간단한 것이 아닙니다. 프로그래머는 속도와 메모리의 최적화를 해야할 사명이 있는 것이고, 그것은 하드웨어의 발달과는 무관하게 당연한 것입니다. 프로그램의 개발 과정에는 분석,설계등의 선행 작업이 있고, 분석 과정은 개발할 프로그램이 무엇을 필요로 하고, 무엇이 필요한지, 설계과정에서는 데이터 구조, 사용할 알고리즘등 정해야하죠. 그리고 그것을 문서화하는데, 이러한 과정들이 없이 막연히 어떠한 프로그램을 만든다는 것은 한계가 있고, 그러한 프로그램들은 유지보수, 또는 재사용은 불가능에 가깝다고 봐야합니다. (이 부분은 XP=eXtream Programming과 의견을 달리하는데, 그 방법을 무시하는 것이 아니라, 저는 이 방식이 옳은 방식이라고 보기 때문입니다) 차근 차근 하나 하나 설계해나가고, 작성해나가면 생각보다 쉽게 풀립니다.

한마디로, 소프트웨어 개발 방법론에 대한 이해가 필요하다는 것이죠.
프로그램은 기본적으로 순차적이고, 그 분기는 순환문 또는 조건문을 통해 이루어지고 있고, 처리 과정이 계산을 통해 이루어져있다는 것만 이해한다면, 그다지 어려운 것이 아닙니다.

컴퓨터의 내부 원리에 대한 이해도 기본적으로 되어있어야 어떻게 프로그램을 구성해야 될지 감이 오게됩니다. (추상화=캡슐화를 통해 이런 부분까지 알지 않아도 가능한 시대가 왔다는 사람들도 있지만, 여전히 이 것에 대한 이해는 중요합니다) 우리는 API를 사용함으로써 키보드 장치에 대한 프로그래밍을 하지 않아도, 키보드 입력에 관한 정보를 얻을 수 있고, 어떤식으로 화면이 점을 찍는지 내부원리를 알지 못해도, SetPixel()함수를 통해서 점을 찍을 수 있습니다. 이 것은 매우 유용하고 저를 비롯하여 많은 프로그래머들이 이런 기능을 사용하고 있습니다. 그렇지만, 점을 어떤식으로 화면상이 표시하는지에 대한 원리, 키보드 입력이 어떻게 하여 프로그램으로 메시지로 전달되는지에 대한 이해가 되어있는 사람과, 그렇지 않은 사람과의 실력차이는 분명합니다.

근본 원리를 알고 있는 사람은 좀 더 멀리 내다볼 수 있고, 신 기술에 대한 적응도 빠릅니다. 음악파일의 근본 원리를 아는 사람은, 새로운 포맷을 만들어 낼 수도 있고, 압축과 신장 방법을 알아낼 수 있는등, 그 것의 효율적인 사용법을 알아 낼 수도 있지만, 원리는 모르고 MFC혹은 각종 라이브러리에서 제공하는 함수의 사용법만으로 프로그램을 개발하는 사람은 그 함수가 지원하는 기능으로 프로그램을 구성하는 그 이상은 불가능합니다.

문법을 배워서 실전(프로그래밍)에 사용할 수 있는 것은 가능한 일이고 당연한 일이지만, 더 좋은 프로그램, 더 나은 프로그래밍을 위해서는, 프로그램 언어 이외에도 배워야할 것들이 많고, 그것들을 놓치면 안되는것입니다.

(3) 무엇을 위한 프로그램인가?

프로그램이란 사용자를 위한 것입니다. 우리나라에서는 각종 시스템이 사용자 혹은 고객을 위한 경우가 매우 드뭅니다. (요새는 많이 나아진 편이지만, 그렇지 않은 곳이 여전히 존재하죠.) 기능의 업그레이드나, 버그 수정은 말할거 없이 '당연한'것이고, 부가적으로 사용자 입장을 고려한 프로그램이 개발 되어야하는데, 이 것도 부족한 경우가 상당히 많습니다.

저같은 경우는 자주 사용하는 기기가, 게임기, 컴퓨터, MP3정도인데, 가장 불만인 것이 이 MP3입니다. 해당 개발업체가 아니면 수정할수 없는 펌웨어의 경우 좀 더 신중한 테스트가 필요하고, 사용과정에서 쉽게 눈에 띄는 버그는 존재해선 안됩니다. 자주 발생하지 않는 특정상황이나, 특정 환경에서만 발생하는 버그가 아니라면 말이죠.

어떠한 프로그램이던지, 버그로부터 자유로울 수는 없습니다. 우리가 입문서등에서 가장 처음 접하는 "Hello World!"라는 문장을 출력하는 간단한 프로그램마저도여타 프로그램과 충돌또는 예기치 못할 문제에 이해 오작동할 가능성도 배재할 수 없습니다. 하지만, 눈에 보이는 버그. 테스트 과정에서 쉽게 발견할 수 있는 버그의 경우는 발견즉시 수정을 위한 노력을 기울여야하고, 기업은 자신들이 발매한 프로그램에 대한 문제수정을 위한 인력(프로그래머든, AS직원이든)을 배치해둘 필요가 있습니다. 이런 기본적인 사용자를 위한 배려도 되지 않는다는 것은 상식적으로 이해할 수 없는 일이지만, 이런 일이 빈번하게 벌어지고 있다는 것이 문제입니다.

프로그램 개발 기간에 대한 이야기도 해보자면, 투자자 혹은 기업주의 압박과 독촉에 의하여, 충분한 테스트 기간과, 완성도를 갖추지 못하고 제품이 출시되는 경우가 종종 있습니다. 이런 사례는 게임업계에서 유독 심한걸로 알려져있는데, 요새야 온라인 게임이 대세이다보니 클로즈베타, 오픈베타과정을 거쳐 정식 서비스를 하기에 이런 문제가 적어지고 있는 추세지만, 이전 PC패키지 게임이 주를 이루던 시기에 버그로 인한 사용자의 피해는 말도 못할정도였습니다. (마그나 카르타와, 포가튼 사가는 두고 두고 화자되고 있지요. 창세기전 시리즈, 어스토니시아 스토리,화이트 데이등 국내에서 손꼽히는 명작들도 버그로 인해 그 작품성에 흠을 내곤했으니 말이죠.) 개발자들의 노고와 힘든 상황을 이해 못하는 것은 아니지만, 그것은 자신들의 상황을 핑계로 사용자에게 피해를 입히는 행위로, 이런일이 다신 벌어지지 않아야할 것이고, 그러기 위해서는 개발자들을 위한 환경이 지금보다 많이 개선되어야하겠고, 개발자들도 사용자를 위해 완성도 높은 프로그램을 목표로 꾸준히 노력하는 자세를 가지도록해야한다고 생각합니다.

+ Recent posts