실리콘벨리 탐방기록(4) - The J. Lohr San Jose Wine Center[2015.07.01]


 샌프란시스코에 관광을 오신 분들 중 술에 관심을 가지고 계신 분들이라면 캘리포니아 내 최대 와인 생산지인 나파 밸리를 방문하고자 하시는 분들이 대부분이실 것입니다. 실제로 이 곳을 돌아다니면서 가게 별로 와인을 시음할 수 있는 것이 상당히 큰 매력이지요.


와인을 주상품으로 하여 방문객들을 맞는 나파 밸리

(사진 출저 : Wikipedia)


 그러나 실리콘밸리를 탐방하다 보면 주요 회사들이 위치한 산호세에서 나파 밸리까지 가는 데엔 거리가 상당하기 때문에 실리콘밸리를 탐방하던 도중에 따로 시간을 내지 않고는 갈 엄두가 나지 않을 수 있습니다.

 이러한 분들에게 드리는 희소식! 실리콘밸리가 밀집한 산호세 내에 나바 밸리에서처럼 와인을 접할 수 있는 곳이 있다는 것입니다! 혹시 귀국전에 와인을 구매하고 싶으신 분들이 계시다면 이곳 산호세에 위치한 와인 공장을 방문해 보셨으면 합니다!




산호세에 위치한 J.LOHR의 입구입니다.

차를 운전하시는 분이라면 주차장도 완비되어 있으니 부담없이 주차를 하실 수 있을겁니다.



입구에서 바라본 모습.

와인을 시음할 수 있다고 써있으며 영업시간도 같이 젹혀있습니다.



J.Lohr의 주소와 이벤트 알림판.



입구에 들어가기 전 앞모습입니다.



간판쪽에서 입구를 바라본 모습입니다.



가게 내부에는 다양한 종류의 와인들이 준비되어 있었습니다.



인기있는 와인의 경우 복도쪽에 보시는 바와 같이 진열해 두는 것으로 보입니다.



카운터 측에서 본 모습입니다.



한쪽 벽면에는 이 가게를 운영하고 계신 분들의 사진이 걸려있는 듯 합니다.



개인적으로 레드와인 보다는 좀 더 부담없는 느낌의 화이트와인을 선호하는 편입니다.

화이트와인 쪽이 좀 더 스윗한 느낌이 들어서일까요?



이곳에서 또한 나파 밸리에서 처럼 시음에 참가하실 수 있습니다.

총 6개의 와인을 맛보실 수 있는데 나파 밸리에서 보통 최대 4잔까지 가능한 걸 생각하면 꽤 많은 와인을 맛볼 수 있는 곳입니다!



한 구석에는 기념품을 구매할 수 있는 장소가 마련되어 있습니다.



시음에 응하겠다고 말을 하면 주인분께서 보시는 것과 같이 리스트를 보여주십니다.

여기서 본인이 마시고자 하는 와인을 최대 6종류까지 선택하실 수 있습니다.



시음대 앞에 높여져 있는 캘리포니아 전도.

캘리포니아 내에서 와인 생산지로 유명한 지역들이 소개되어 있습니다.



주인분께서 와인 시음을 준비하고 계십니다.



개인적으로 화이트와인을 좋아하기 때문에 첫 시음부터 이것을 골랐습니다.

같은 화이트와인이어도 화이트와인 특유의 달달한 맛의 정도가 약간의 차이를 보이더군요.



그래도 아직 본인이 와인을 즐겨마시는 편이 아니다보니 각 와인의 특징을 확실히 느끼기는 어려웠습니다.

시음후 주인분께서 말씀하시는 설명을 듣고 그 때서야 어떠한 차이가 있는지 알 수 있었지요.



한 편 가게 내부에서 와인이 만들어지는 과정을 볼 수 있는 공간이 있습니다.



처음에 저는 와인 생산지에서 만든 후 이곳에 가져오는 방식으로 되어있는 줄 알았는데

보시는 바와 같이 내부에서 와인을 만들고 있는 모습을 보실 수 있습니다.



공장 내부에는 직원들이 열심히 작업을 하고 있는 모습을 볼 수 있습니다.



 가게 내부에서 판매되는 와인들의 경우 대부분은 인근 주류 매장에서 팔기도 하며 심지어는 주류 매장에서 판매되는 와인의 가격이 더 싼 경우도 있습니다. 혹시 자신이 와인을 구매하기 전 시음을 해보고자 하는데 나파 밸리까지 가기에는 거리상의 부담이 있으신 실리콘 밸리 탐방객이시라면 이 곳 'The J. Lohr San Jose Wine Center'를 소개해 드리고자 합니다!



이름        : The J. Lohr San Jose Wine Center

주소       : 1000 Lenzen Avenue, San Jose, CA 95126

전화번호    : 1-408-918-2160

홈페이지    : http://www.jlohr.com/visitus/sanjose

영업시간    : 10:00 a.m. to 5:00 p.m. Daily

(closed New Years Day, Easter, Thanksgiving and Christmas Day)



300x250

[Tiva] GPIOIntTypeSet()

임베디드/TI 2015. 7. 13. 10:55

void GPIOIntTypeSet(uint32_t ui32Port, uint8_t ui8Pins, uint32_t ui32IntType)

설명 : 특정한 핀에 대한 인터럽트 방식을 설정합니다.


uint32_t ui32Port

GPIO의 포트를 설정합니다.


uint8_t ui8Pins

설정하고자 하는 해당 포트의 핀번호를 선택합니다.


uint32_t ui32IntType

자신이 원하는 인터럽트의 신호를 선택합니다.


 여기서 ui32IntType를 통하여 다음과 같은 인터럽트 신호들을 감지할 수 있도록 설정할 수 있습니다.


GPIO_FALLING_EDGE

핀의 입력이 1에서 0으로 내려가는 순간에 인터럽트를 발생시킵니다.


GPIO_RISING_EDGE

핀의 입력이 0에서 1로 올라가는 순간에 인터럽트를 발생시킵니다.


GPIO_BOTH_EDGE

위의 GPIO_FALLING_EDGE와 GPIO_RISING_EDGE가 발생시 인터럽트를 발생시킵니다.


GPIO_LOW_LEVEL

핀의 입력이 0이 되었을 때 인터럽트를 발생시킵니다.


GPIO_HIGH_LEVEL

핀의 입력이 1이 되었을 때 인터럽트를 발생시킵니다.


GPIO_DISCRETE_INT

GPIO 포트에서의 각 핀에 대한 별개의 인터럽트를 설정합니다.



 인터럽트를 설정할 때 의도치 않은 인터럽트 설정을 피하기 위해 위의 함수를 사용할 시에 GPIO의 입력이 안정된 상태어야 합니다. 즉, Floating 현상으로 인해 의도치 않은 인터럽트가 발생할 수 있다는 점을 유의해야 합니다. Floating에 대한 자세한 설명은 아래 포스팅을 참조해 주시기 바랍니다.


http://elecs.tistory.com/15

300x250

실리콘벨리 탐방기록(3) - 산호세 뮤니시펄 로즈 가든[2015.07.01]

사진 출저 : Wikipedia


 산호세 인근의 실리콘벨리를 탐방하던 도중 상당히 인상깊었던 공원이 있어 방문을 하였습니다. 이 공원은 산호세 시에서 직접 관리하는 MUNICIPAL ROSE GARDEN으로 공원 절반이 다양한 종류의 장미로 꾸며져 있습니다. 혹시 실리콘벨리를 탐방하시던 도중 잠시 쉬어갈 만한 곳을 찾으시는 분이시라면 이 곳을 방문해 보시는 것을 추천해드립니다.



공원 입구에 들어서면 중앙에 분수를 중심으로 수많은 장미들이 그 주변을 장식하고 있는 것을 보실 수 있습니다.



공원 외곽에는 시민들이 쉴 수 있는 잔디르 꾸며져 있습니다.

캐치볼과 같은 간단한 야외 활동을 즐기기 딱 좋은 분위기였습니다.



아름다운 장미는 당연히 눈으로만 봐야겠지요? ㅎㅎ



분수 옆에서 바라본 장면입니다.










장미꽃 하나하나가 상당히 아름답게 생겼습니다!

이렇게 많은 장미들을 시에서 직접 관리한다는 게 상당히 신기했습니다.



공원 인근 잔디밭에서 여가를 즐기는 시민들의 모습이 눈에 보입니다.







회사 탐방 도중 지친 마음을 달래고자 하시는 분들이라면 이곳 산호세 로즈가든을 추천드리고 싶습니다!


주소 : 95126 California, San Jose, Dana Ave & Naglee Ave

300x250

실리콘벨리 탐방기록(2) - 링크드인(LinkedIn) 방문기[2015.07.01]

 2015년 오늘날의 인터넷 환경은 SNS로 개인과 개인의 커뮤니케이션이 중점이 된 시대가 되었습니다. twitter와 facebook은 기존의 대형 커뮤니티에서 많은 사람들이 홤께 활동하던 인터넷 세계가 각 개인이 정보의 전달매체가 되었다고 볼 수 있겠습니다.

 이러한 인터넷 생태계의 변화는 커뮤니케이션을 넘어 다른 분야에서도 SNS와 같이 개인과 개인을 연결해주는 매체가 등장하게 됩니다. 이번에 소개하게 될 링크드인(LinkedIn)의 경우가 가장 대표적인 회사라고 할 수 있겠습니다. 그렇다면 링크드인은 무엇을 하는 회사일까요?



 링크드인은 구직자와 구인자를 서로 연결해주는 것을 목표로 만들어진 회사입니다. 구직자는 자신의 경력이나 학력 등 고용자가 원하는 정보를 제공하며 고용자는 자신이 고용하기를 원하는 분야의 구직자를 찾기 쉽게 연결해주는 서비스라고 할 수 있겠습니다. 미국에서는 많은 직장인들과 회사가 링크드인을 통해 구인구직이 이루어지고 있습니다.

 국내의 경우 대기업의 공개채용 위주로 구직자를 고용하기 때문에 국내에서는 링크드인의 입지가 약한 편입니다. 링크드인 또한 이러한 국내 시장의 특성에 맞추어 다양한 시도를 하고 있습니다.




 링크드인은 2002년에 공동창업자인 리드 호프만(Reid Hoffman)에 의해 설립되었습니다. 스티브 잡스의 애플처럼 자신의 집 거실에서 회사를 창업하였으며 2015년 현재 전 세계적으로 3억여명의 구직자들을 회원으로 보유한 세계 최대 비즈니스 네트워크 사이트로 거듭나게 됩니다.. 링크드인은 채용 솔루션 및 마케팅/세일즈 솔루션, 프리미엄 멤버십에서 오는 수익과 다양한 비즈니스 모델을 갖추어 그 규모를 점차 키우고 있습니다.어찌보면 링크드인은 소셜 네트워크 분야에서 구인구직이라는 블루오션 분야를 발견해 내어 크게 성장한 사례라고 생각할 수 있겠습니다.



 이번에 방문하게 된 링크드인의 위치는 산호세 인근 서니베일에 위치해 있습니다.



링크드인 메인 데스크에서 방문객을 맞이하고 있습니다.

이 곳에서 방문객 등록을 하면 직원의 안내에 따라 회사 내부를 탐방할 수 있게 됩니다.



로비는 상당히 깔끔한 편입니다. 뒤편의 커다란 디스플레이는 회사의 홍보영상이 반복적으로 상영되고 있습니다..

바로 옆에는 사내 식당 입구가 보입니다.



로비 한 켠에 비치되어 있는 자전거 헬멧들입니다.

링크드인 직원이라면 자전거를 탈 때 누구든지 자유롭게 사용할 수 있도록 되어 있더군요.



링크드인 사내 식당 입구의 모습입니다.



사내 식당의 모습입니다. 구글의 사내 식당처럼 누구든지 자유롭게 자신이 원하는 식단을 선택할 수 있습니다.






직원분들의 식사 장면입니다. 마치 고급 레스토랑에 온 듯한 분위기를 뿜내는 것이 인상적입니다.



사내 체육관의 모습입니다.

실리콘벨리에서 자유로운 분위기로 유명한 구글에 버금가는 시설을 갖추고 있습니다.



사내 내부 복도의 모습입니다.



복도 옆면에는 주변 자연경관들을 주제로 하여 구성되어 있는 것을 볼 수 있습니다.



사내 사무실 견학을 마친 후 직원 휴게실에 도착하였습니다.

휴게실은 상당히 자유로운 분위기로 꾸며져 있는 것을 볼 수 있었습니다.



휴게실 한 켠에는 조금은 놀라운 자판기를 보게 되었습니다.

내부에는 마우스와 같은 컴퓨터 악세사리들이 구비되어 있었는데요



놀랍게도 이 모든것이 사원증만 있으면 무상으로 사용할 수 있다는 것입니다!

구글에서도 보지 못했던 시설이라 조금은 깜짝놀랐습니다.



역시 휴게실 내에는 게임시설도 갖추어져 있습니다!

직원들이 한창 게임 삼매경에 빠져있는 모습을 볼 수 있었습니다.



게임을 마치고 업무에 복귀하는 직원분들.



휴게실에는 게임기 뿐 아니라 탁구와 당구 등의 보드게임을 즐길수 있는 시설들이 완비되어 있습니다.



가만 보면 의외로 미국사람들은 고전 게임을 즐기는 분들이 많아보입니다.

지금 시점에서는 고전 게임과 같아 보이지만 이들은 지금도 회사 내에서는 현역으로 작동되고 있습니다.



사무실에서 일을 하던 도중에도 이렇게 휴게실 내에 비치된 좌석에 앉아 일을 수행할 수도 있습니다.

제가 이 곳에 입사한다면 노트북을 무릎에 얹고 일을 하고 싶군요.



지금까지 둘러본 구글과 링크드인. 실리콘벨리에서 큰 영향력을 가지고 있으면서도 회사 분위기도 자유로운 쪽에 속하는 회사들이지요.

우리나라에서 처럼 하루 종일 책상에 앉아 일만 하는 것으로는 결코 업무 효율이 높아지지 않는다는 것을 이 두 회사가 제대로 보여주는 것 같습니다.



 아직 우리나라에는 인지도가 많이 낮은 링크드인이지만 지속적으로 국내 시장을 개척해낸다면 페이스북이 싸이월드를 압도하였던 것과 같이 국내의 취업사이트의 지형에도 큰 변화를 미칠 날이 올지도 모르는 일이지요.

 이번 실리콘벨리 기업 탐방을 통해 컴퓨터공학을 전공하는 본인으로서 국내의 인터넷 환경에서 큰 혁신을 몰고오는 인재가 되보고자 하는 생각을 한 번 되새겨봅니다.


300x250

실리콘벨리 탐방기록(1) - Google 방문기[2015.06.30]

 컴퓨터 공학 관련 분야에 종사하시는 분들이라면 거의 대다수가 미국 산호세 인근에 위치한 실리콘벨리에 대해 잘 아시리라 생각합니다. 컴퓨터공학 분야가 가장 많이 발전된 곳이기도 하며 전세계에 간판정도는 거뜬히 달고 다니는 굴지의 컴퓨터 관련 분야의 기업들이 밀집한 지역이기도 하지요. 특히 우리들에게는 구글과 애플이 위치한 곳이기도 하니 컴퓨터 공학을 전공하는 분들이라면 누구나 한 번 쯤은 이 곳에서 일해보고픈 꿈을 품은 분들도 계시리라 생각합니다.


 본 포스팅에서는 그 첫번째로 오늘날의 스마트폰 시장을 정복했다고 할 수 있는 안드로이드 운영체제를 세상에 널리 떨친 구글 방문기를 다루어보도록 하겠습니다.

 구글 또한 실리콘 벨리의 상징이기도 하며 산호세 인근의 마운틴뷰에 위치하고 있습니다.



실리콘벨리에서 구글은 대략 이 정도에 위치하고 있습니다.



구글 로비의 광경입니다.

데스크 옆에 설치된 기기는 구글 방문객들을 등록하기 위해 사용되며

구글 투어는 반드시 구글 직원과 동행하는 조건으로 탐방이 진행됩니다.




탐방을 시작하기에 앞서 구글의 랜드마크라 할 수 있느 안드로이드 모형들을 보는 것이 정례라 할 수 있겠습니다.

본래 이 모형들은 구글 헤드쿼터 인근에 위치해 있었습니다만

방문객들이 너무나도 많이 찾아오다 보니 현재는 회사의 외각지역 옮겨두어 이 곳에서 관광객을 맞이하고 있습니다.



우리들을 가장 먼저 반기고 있던 것은 가장 최근에 발표된 버전인 'Lollipop'이었습니다.

지난달에 안드로이드 프로젝트 M이 공개되었으니

머지않아 롤리팝의 전성시대도 막을 내릴 것으로 보입니다.



한때 높음 점유율을 유지하였던 진저브레드.

그 옆은 안드로이드 4.0 버전의 시대를 열었던 아이스크림샌드위치.



이 쪽에는 허니컴과 도넛이 위치해 있습니다.



구글 회사 내부에서 쉽게 찾아보실 수 있는 직원 전용 자전거 입니다.

회사 자체가 상당히 크다보니 이렇게 자전거를 타지 않으면 정말 많이 걸어다녀야 합니다...



불과 몇 달 전만 해도 생생한 신입이었던 킷캣도 이제는 구시대의 버전이 되어버렸군요.

지금도 상표명인 킷캣이 버전명으로 결정되었던 점이 생각해보면 참 신기하기도 했습니다.

구글 직원들의 말대로 킷캣 이상으로 맛있는 디저트는 었다고 말할 정도였으니 말이죠!



롤리팝 앞에서 기념사진을 찍고 있는 관광객들



안드로이드 랜드마크가 있던 곳 인근에는 보시는 바와 같이 방문객을 위한 공간이 마련되어 있습니다.

공간 내부에는 각양각색의 안드로이들이 방문객을 맞이합니다.



한 쪽 책장 안에는 안드로이드들이 꾸며져 있었습니다.



크롬 마크를 띄고 있는 의자

한때 핫할 당시엔 빠른 속도를 자랑하였습니다만

지금은 램 용량을 잡아먹는 돼지 취급을 받게 되었죠(...)



입구 옆에는 의미심장한 그래프가 붙어 있습니다. 자세히 들여다 보면..



시기별로 검색량에 대해 표시가 되어 있음을 알 수 있습니다.

각 색은 각 검색회사들 별로 나타내고 있습니다.



그래프의 끝부분을 자세히 보면 그 당시 무슨 일이 생겼는지를 짐작해둔 것을 적어둔 것을 볼 수 있습니다.

이는 정확하지는 않지만 그 당시 사람들이 어떤 생각을 하고 있었고 그 당시 검색어로 무엇이 대두되었는 가늠할 수 있습니다.



방문객 센터 한 구석에는 보시는 바와 같이 기념품을 판매하고 있는 것을 보실 수 있습니다.



극 초반기 당시의 안드로이드 기기의 모습입니다.

지금 보니 디자인이 구식으로 보입니다만

그 당시에는 상당히 참신했던 이미지임을 생각하면

정말이지 스마트폰도 짫은 시간동안 많이 발전했음을 느낄 수 있었습니다.



구글 크롬북들을 방문자센터에서 직접 다루어 볼 수 있습니다!



위에서 보았던 구글 기념품점을 들어가보도면



보시는 바와 같이 다양한 종류의 기념품들이 팔리고 있는 것을 보실 수 있습니다.

그런데 가격은 많이 비싸더군요.

정말 자신이 구글을 좋아한다면 구매하셔도 좋을 듯 합니다 ...



이 곳에서는 5대의 TV를 활용한 구글어스를 활용한 기기를 체험하실 수 있습니다.



화면의 중앙에 위치한 버튼을 조작하면 마치 자신이 지구를 직접 조작하는 듯 한 기분이 들게 합니다.



구글어스를 통해 바라본 서울의 모습입니다.

미국과는 달리 아직 많은 건물들이 3D로 구성되지 않은 점이 상당히 아쉽더군요.



심지어 구글어스로는 북한의 평양 시내도 훤이 볼 수 있습니다.

더 놀라운 것은 주요 시설들이 3D로 구현되어 있다는 것이지요!



방문객들을 위해 설치되어 있는 구글 직원들의 실제 근무환경을 재연한 사무실입니다.



사무실 인근에는 이렇게 직원들의 피로 회복을 위한 기기들이 마련되어 있습니다.



구글 사무실의 모습입니다.

오른쪽의 책상은 높낮이를 조절할 수 있어서

근무중 자세를 바꾸려 할 때 용이하게 쓸 수 있습니다.





조금은 평범하면서도 조금은 일반적인 회사들과 살짝 분위기가 다른 구글의 사무실의 모습들.



실제로 구글 내부에서는 직원들의 유희시설들이 상당히 잘 구축되어 있음을 알 수 있습니다.

실내에서 간단하게 골프를 칠 수 있는 환경이 조성되어 있던 것을 보면

작업실이 참으로 구글스럽다는 느낌이 듭니다.



구글 회원들을 위해 준비된 음악연주 공간입니다.

이 곳에서 회원들은 자신이 연주하고자 하는 악기를 가지고 연습할 수 있습니다.



피아노 한 대를 볼 수 있었는데 무려 3000만원이나 한다는군요.

물론 회사 차원에서는 직원들의 복지가 우선이니

매우 중요한 부분이라 생각됩니다.



방문객들 앞에서 자신의 피아노 실력을 과시하는 구글 직원님!

이 곳에서 일을 하다보면 저도 언젠간 피아니스트가 될 수 있으려나요?



구글 회사 인근의 풍경입니다.



탐방 도중 지붕 위에 카메라를 달고 있는 차 한대가 지나가려 합니다.

최근 구글에서 연구하고 있는 무인자통차 시스템이라 합니다.

미국 당국도 구글의 개발에 대해 허가를 내주었을 정도로 관련 분야에 대한 투자가 상당한데요

머지않아 정말로 자동차가 스스로 운전을 하는 시대가 도래할 것으로 보입니다.



무인자동차의 뒷모습. 이제 주행을 마치고 돌아가는 모습입니다.



구글의 방문객 등록기는 구글 사내 곳곳에 위치해 있습니다.

구글 직원들이 방문객들을 데러고 다니기 용이하게 하려는 의도로 보여지기도 하면서

많은 방문객들이 구글을 방문하기 쉽도록 한 거 같습니다.



구글 내부에 위치한 게임센터.

이 곳에서 조금은 구식이지만 상당히 흥미로운 게임들을 만나볼 수 있었습니다.

특히 실물 핀볼은 이 곳에서 처음 플레이 해보았는데

PC게임으로만 즐겨왔던 핀볼을 현실에서 구현해 놓은 듯한 착각이 들 정도로 잘 만들어져 있더군요.



구글 직원분과 동행하여 들어가게 된 구글 사내식당입니다.

한창 저녁식사 시간이라서 인지 사람들이 붐비고 있습니다.



식당 내부에는 다양한 국가의 식단들이 준비되어 있습니다.

모든 메뉴는 자신이 먹고 싶은 것을 고를 수 있게 되어 있으며

회사 차원에서 제공하는 저녁이므로 추가적인 비용은 없다고 볼 수 있을듯 합니다.



저는 이번 저녁을 부리또로 정하였습니다.

한국에서 먹던 부리또와는 향이 다르겠지만

한국에 있었을 때도 부리또를 많이 먹어보기도 해서 이번엔 현지의 맛을 보기로 해보았습니다.



부리도의 내용물은 구글 직원분들이 직접 고를 수 있습니다.

자신이 먹지 않는 재료의 경우 모우 제거해서 먹는 것이 가능합니다.



부리또의 내용물을 친절하게 설명하고 있습니다.

혹시 자신의 취향에 맞지 않는 재료가 있는지 확인해봅시다.



야외 식당의 전경입니다.

내부에서도 상당히 멋진 분위기로 식사를 할 수 있긴 하디만

날씨 좋은 산호세에서는 이렇게 외부에서의 식사도 상당히 큰 재미라 할 수 있겠습니다.



본격적으로 들어가게 된 구글 사무실의 내부 모습입니다.



간단한 회의가 있거나 담소를 나누고자 할 땐 이렇게 작은 책상을 마련하여 이를 할 수 있도록 하고 있습니다.



실제 구글 사무실 내에서 위와 같이 써있는 곳은 방문객이 접근을 삼가해야 하는 곳 중 하나입니다.

직원들이 일을 하는 공간이기도 하며 회사의 기밀을 다루고 있기도 하니 들어가는 건 자재를 해야 겠지요? ㅎㅎ



직원들이 간단하게 사용할 수 있는 휴게실입니다.

커피나 빵 등 간단한 다과를 즐길 수 있는 환경을 제공하고 있습니다.



구글 탐방도 어느덧 막바지가 되어가고 있습니다.

주변이 점점 어두워지면서 구글의 화려한 장식들이 눈에 띄기 시작합니다.



밤중에도 불을 키고 일을 하는 직원들.

비록 구글은 출퇴근시간이 정해져 있지 않지만

각 직원들이 원하는 근무시간에 일을 할 수 있도록 구성되어 있습니다.



이렇게 해서 구글 직원분을 통한 회사 방문기를 마무리하게 되었습니다.


 지금은 전 세계 스마트폰 시장의 절반정도를 안드로이드로 지배한 구글이 지금의 영향력을 갖출 수 있게 된 계기는 아무래도 개발자를 최대한 배려한 환경이 아닐까 싶습니다. 회사 자체가 놀이터처럼 구성되어 일을 하던 도중에도 쉴 수 있으며, 결근을 하던 휴가를 하던 자신이 맡은 일을 문제없이 한다면 되는 것이지요.


 하지만 이러한 자유문방한 분위기 속에서도 직원들은 보이지 않는 경쟁을 하고 있습니다. 구글 또한 기업인 만큼 실적이 좋지 않으면 회사에서 해고당하기 때문에 직원들은 편온함 속에서도 긴장감을 유지하면 일을 하고 있다고 합니다. 이러한 구글의 특이한 근무환경이 반대로 서로의 경쟁력을 키우는 요소가 아닌가 싶습니다.


 지금 이 순간에도 컴퓨터 공학 분야를 공부하는 분들이라면 구글은 천국과 같은 회사임은 분명합니다. 혹시 자신의 꿈이 확실하다면 땀과 열정을 품고 실리콘벨리에 있는 구글에 입사한다면 어떨가요? 어쩌면 여러분들이 컴퓨터 공학 분야의 새로운 지평을 여는 인재가 될 수 있으니 말죠!


300x250

GFDM(Generalized Frequency Domain Multiplexing)

공대생의 팁 2015. 6. 6. 18:42

 무선통신 방식은 하루가 다르게 빠른 속도로 발전하고 있습니다. 현세대의 이동통신인 4세대 이동통신인 LTE의 등장으로 우리나라에서의 휴대전화 데이터 전송 속도가 과거 3G 방식에 비해 매우 빨라졌음을 알 수 있습니다.

 본 포스팅에서는 지난 2014년에 발표된 새로운 개념의 통신방식인 GFDM(Generalized Frequency Domain Multiplexing)에 대해 간단하게 설명해볼까 합니다. 혹시 GFDM에 대해 좀 더 자세히 알고 싶으신 분께서는 출저에 표기된 논문을 읽어보셨으면 합니다.


1. Issue about 5G

 


 그래프는 지금까지 등장한 통신방식들과 그 방식의 속도를 나타내고 있습니다. 연도가 올라갈수록 WLAN과 Cellular 의 통신 속도가 급속도로 빨라지고 있는 경향을 확인할 수 있는데요. 통신 속도가 기하급수적으로 증가하여 오늘날의 통신기술인 LTE 방식을 통해 실시간으로 동영상을 다운로드하며 볼수 있는 환경에 도달할 수 있게 되었습니다.

 앞으로의 5G의 이슈로는 현재보다도 더 빠른 무선통신 환경이 구축될 것이며, 통신시 저전력 전송방식 또한 중요해질 것으로 보입니다. 또한, 통신중 latency의 최소화 또한 5G에서의 이슈가 될 것으로 보입니다.


2.GFDM이란 무엇인가?

 GFDM(Generalized Frequency Division Multiplexing)은 기존에는 없었던 새로운 개념의 다중주파수 전송방식입니다. 기존에 존재하였던 OFDM과 SC-FDE의 방식의 장점들을 유연하게 다루고 있으며 현재 사용되는 방식인 LTE 대역에서도 GFDM이 사용될 수 있다는 것이지요.



  


 시간 대 주파수 그래프를 통해 GFDM의 동작방식에 대해 좀 더 자세히 살펴보도록 하겠습니다. OFDM은 각 주파수 대역 별로 각각 데이터를 전송하는 방식입니다. 이는 각 Subcarrier에 동시간대에 데이터를 전송할 수 있어 짧은 시간에 많은 데이터를 전송할 수 있는 장점을 갖추고 있습니다. OFDM의 최대 단점으로는 전력 소모가 매우크며 ISI(Intersymbol Interference)가 발생할 확률이 높다는 점입니다. 반면, SC-FDE의 경우 전력 소모가 OFDM에 비해 낮으며 그로 인해 역방향 채녈의 커버리지를 넓게 사용이 가능하다는 장점을 가지고 있으나 OFDM에 비해 여전히 데이터 속도와 용량 등 전송 데이터량이 적다는 단점을 가지고 있습니다.




 이러한 OFDM과 SC-FDE의 장점을 아우르고 있는 방식이 바로 GFDM인데요 그 특징을 그래프를 통해 살펴보겠습니다. 보시는 바와 같이 GFDM은 각각의 단위를 독립적인 블록과 같은 개념으로 보실 수 있는데요 각 블록은 Subcarrier와 Sub-symbol로 구성되어 있습니다. 하나의 Subcarrier가 여러개의 Sub-symbol을 전송하는 방식으로 구성되어 있기 때문에 블록 구조는 낮은 latency조건을 설계할 수 있습니다.


3.Details of the GFDM modulator

Block diagram of the transceiver


 위 그림은 GFDM의 송수신과정을 블록 다이어그램으로 나타낸 모습입니다. 겉모습으로 보기에는 다른 전파방식과는 큰 차이가 없어보입니다. 이번에는 GFDM modulator을 살펴보도록 합시다.



 위 그림은 GFDM의 modulator의 내부 동작 방식을 그림으로 나타낸 것입니다. GFDM에 입력되는 N개의 입력값 d[n]이 modulator을 통과하고 있는 모습입니다. GFDM은 해당 입력을 각 Subcarrier에 값을 분산시키며 해당 Subcarrier 내에서도 각각 별개의 Sub-symbol에 값을 적용한 후 값을 전달하는 모습을 나타내고 있습니다. 이 때의 값은 D[K-1][M-1]로 타나나고 있습니다. 이때 각 K와 M의 값은 다음과 같습니다.


N : GFDM에 입력된 값의 총 갯수

K : Subcarrier의 총 갯수

M : Sub-Symbol의 총 갯수


 N개의 입력이 K개의 Subcarrier에 나누어 들어가게 되었으며 또한 각 Subcarrier에는 M개의 Sub-symbol로 나누어져 값이 입력됩니다. 즉, 이는 아래의 공식이 성립됩니다.

N = K × M


 각 Subcarrier에 도달하게 된 데이터값 d는 해당 Subcarrier의 Sub-symbol에 할당되었을 때 d[k][m]에 값이 들어가게 됩니다. 이는 곧 위에서 설명해드린 한 단위의 '블록'이 되는 것이지요. 이렇게 만들어진 각 블록은 이에 해당되는 파형에 적용되는데요 해당 파형의 공식은 다음과 같습니다.




그리고 위의 파형화 블록의 곱을 모두 합하면 전송되는 실제 파형이 만들어지게 됩니다.




 이 때 x[n]의 값이 GFDM을 통해 출력되는 값을 나타냅니다. 위 식은 아래와 같이 간단하게 표현될 수 있습니다.



 이 때 A가 바로 GFDM의 modulation matrix라 할 수 있겠습니다. 입력벡터 d에 modulation matrix를 행렬곱하면 출력값으로 벡터 x가 나온다고 이해하시면 되겠습니다. 아래는 modulation matrix인 A의 값을 그램으로 표현한 모습입니다. 해당 예시의 그림은 4개의 Subcarrier와 7깨의 Sub-Symbol로 구성된 28 × 28 matrix입니다.



 이렇게 GFDM modulation을 통과한 x[n]이 실제 전파될 때는 수신측에서 받게되는 신호 y[n]는 아래의 식으로 표현될 수 있습니다.


 여기서 GFDM 모듈레이터 값인 x와 h는 순환 컨볼루션을 적용한 후 AWGN 값을 더해줍니다. 여기서 AWGN(Additive White Gaussian Noise)이란 노이즈 주파수를 값에 더하여 실제 신호와 같게 만들어 기 위해서 적용하는 값입니다.


4.Details of the GFDM demodulator


 다음으로 GFDM의 demodulator에 대해 살펴보도록 하겠습니다. demodulator에 값을 적용하기 전에 먼저 Frequency Domain Equalization을 통해 새로운 y[n]값을 얻습니다. 


 그렇게 FDE가 적용된 새로운 y[n]값에 Demodulation matrix인 B를 행렬곱하면 수신값 d[n]을 얻게 됩니다.


 여기서 Demodulation matrix인 B의 값을 구하는 방법 3가지를 살펴보도록 하겠습니다.


-Matched Filter Receiver

 이 방식은 애르미드 공액 방식을 활용하는 방식으로 각 서브캐리어 당 SNR 값을 최적화 함으로서 전송 도중 손상되는 데이터 심볼을 회복시키는 것을 목표로 합니다. 단점으로는 ISI와 ICI(Intercarriers Interference) 문제가 발생한다는 점입니다.


-Zero-forcing Receiver

 이 방식은 단순히 modulation matrix A의 역행렬 방식을 취하며 Subcarrier 간의 간섭을 없애는 것을 목표로 하고 있습니다. 이를 통해 ISI와 ICI 문제를 해결할 수 있으나 송신측에서 전송하는 데이터 값에 따라 값이 손실될 가능성이 발생합니다.


-Minimum Mean Square Error Receiver

 이 방식은 이전 두 가지 방식인 Matched Filter Receiver 방식과 Zero-forcing Receiver 방식을 혼합한 방법으로 이 식 자체에 신호를 equalize하는 기능이 포함되어 있기 때문에 Frequency Domain Equalization을 사용하지 않아도 된다는 장점이 있습니다만, 이와 같은 구조는 수신측에서 값을 계산하는 방식이 복잡해진다는 단점을 가지고 있습니다.

 

5. LTE compatibility

 GFDM은 5세대 이동통신으로서의 머물지 않고 현재 4세대 통신기술인 LTE에도 이론적으로 적용할 수 있다는 특징을 가지고 있습니다. 이러한 GFDM의 특성을 활용한다면 이전 세대에서 사용하던 기술들을 혼용함으로서 혼합형 디바이스를 설계하는 것이 좀 더 수월해질 수 있습니다. GFDM의 주파수를 현재 운용중인 LTE의 클럭주파수인 30.72MHz를 활용하면 LTE의 시간-주파수 grid를 사용할 수 있습니다. 또한, 이렇게 적용된 GFDM 방식을 통해 기존 LTE 방식에서도 좀더 짫은 Latency를 기대할 수 있게 됩니다.


 GFDM 방식이 LTE에 적용되는 방법을 설명하기에 앞서 먼저 LTE의 프레임 구조를 살펴보도록 하겠습니다. 그림에서 보시는 바와 같이 LTE는 프레임 단위로 단말기와 통신을 하고 있는 것을 확인하실 수 있습니다. 하나의 프레임은 10개의 서브프레임으로 구성되어 있으며, 서브프레임은 2개의 슬롯으로 이루어져 있으며 해당 슬롯은 Cyclic prefix와 심볼로 구성되어 있음을 확인하실 수 있습니다.


- LTE-FDD 방식에서의 OFDM


CP : 16μs

Symbol : 66.7μs

 LTE 기술 중 하나인 LTE-FDD 방식에 대해 살펴보도록 하겠습니다. 위 그림에서 보시는 바와 같이 LTE-FDD 방식은 여러개의 Subcarrier를 활용하는 OFDM 방식을 사용하고 있음을 확 수 있습니다. Cyclic prefix의 간격은 16μs이며, 각 심볼은 66.7μs입니다. GFDM 방식과 비교하였을 때, LTE-FDD 방식의 단점으로는, 각 Symbol이 만들어질 때 마다 Cyclic prefix가 사용되어지고 있으며, 이는 전송시 Frame의 간격을 최대한으로 줄일 수 없어 Latency 문제가 발생하게 됩니다.


- GFDM 방식이 적용된 LTE-FDD

CP : 4.17μs

Subsymbol : 4.17μs

 그렇다면 LTE 통신방식에 GFDM을 적용하였을 때 어떤 효과가 나타나는지 보도록 하겠습니다. 보시는 바와 같이 스펙적인 면에서 확인하였을 때, Cyclic prefix와 Symbol이 4.17μs로 눈에 띄게 줄어들었음을 확인하실 수 있습니다. GFDM의 경우 LTE-FDD와는 달리 여러개의 Subsymbol을 하나의 Cyclic prefix를 통해 전송하는 것이 가능하며, Symbol duration을 무려 7.5배 작게 할 수 있기 때문에 전송시 프레임 단위 하나의 duration을 줄일 수 있습니다. 즉, 이는 무선통신시의 Latency를 확연히 줄일 수 있어 이론적으로 통신 속도가 증가함을 알 수 있습니다.


 GFDM이 OFDM을 대체하게 되었을 경우 얻을 수 있는 또 다른 장점으로는 Out-of-Base emission을 줄일 수 있다는 점입니다. 여기서 Out-of-Base emission이란 통신시 자신에게 해당되는 대역폭을 벗어난 주파수 영역으로 데이터가 전송되는 현상으로 이는 신호간의 간섭을 유발할 수 있습니다. 특히 각 주파수 대역별로 근접해 있는 OFDM의 경우 Out-of-Base emission에 취약하다는 단점을 가지고 있습니다. 반면 GFDM은 시간-주파수 영역이 블록 단위로 구성되어 있고 이 구조는 순환적으로 변하기 때문에 Out-of-Base emission 문제를 해결할 수 있습니다. 이렇게 GFDM이 오늘날의 통신 방식인 LTE에 적용하였을 때에도 상당히 효율이 높아질 것으로 기대할 수 있겠습니다.


- Conclusion

 GFDM 방식은 기존의 통신방식인 LTE에서도 적용할 수 있어 이전 세대의 통신방식을 아우를 수 있는 기존에는 없던 새로운 방식이라는 점이 상당히 매력적입니다. GFDM의 특성으로 Latency 이슈가 해결된다면 머지않은 미래에는 촉감 인터넷(Tactile Internet)과 같이 기존의 방식으로는 구현하기 힘들었던 분야가 새롭게 부상할 것으로 보입니다. 또한, MIMO와 같이 GFDM 또한 잠재적인 5G 시대의 기술이 될 것으로 기대됩니다.


- Reference

1. I.Gaspar, L.Mendes, M.Matthe, N.Michailow, A.Festag, G.Fettweis, "LTE-compatible 5G PHY base on Generalized Frequency Division Multiplexing", Wireless Communications Systems (ISWCS), 2014 11th International Symposium, pp. 209 - 213, 26-29 Aug. 2014


2. N. Michailow, M.Matthe, I.S.Gaspar, A.N.Caldevilla, L.L.Mendes, A.Festag, G.Fettweis, "Generalized Frequency Division Multiplexing for 5th Generation Cellular Networks", IEEE Transactions Communications, Vol.62, pp.3045 - 3061, Sept. 2014

300x250

TIZEN Native 단계에서 LOG를 활용해보자!

공대생의 팁 2015. 6. 5. 23:59

 프로그래머로서 코딩을 할 때 가장 중요한 요소가 무엇이냐 누군가가 묻는다면 저는 자신의 결과물을 직접 확인할 수 있는 LOG를 보는 것이라고 말하고 싶군요. 프로그램을 제작하던 도중 잘못된 동작을 하는 부분이 어떠한 문제가 있는지 알고 싶을 때 이 LOG가 어떤 때보다 가장 빛을 발하게 되지요!

 특히 안드로이드의 경우 logcat(로그캣)이라는 기능이 있어 자신이 제작한 프로그램의 로그를 손쉽게 확인할 수 있는 기능을 갖추고 있습니다.


 이토록 편리한 로그캣과 같은 기능이 TIZEN에서도 사용이 가능할까요? 정답을 말씀드리자면 Yes입니다. 다만, Android의 로그캣을 쓰는 것 처럼 Java 단계에서 손쉽게 로그를 보는 방식이 아니라는 점이 상당히 아쉬운 부분입니다.




 어찌보면 타이젠의 SDK도 안드로이드의 로그캣을 밴치마킹한 듯 비슷해 보이지만 아무쪼록 TIZEN의 Native 단계에서 로그를 확인하는 방법을 살펴보도록 하겠습니다!


#include <dlog.h>

TIZEN의 Native 단계에서 로그를 확인하고 싶을 때 해당 소스코드에 dlog.h를 include 해줍니다.


int      dlog_print (log_priority prio, const char *tag, const char *fmt,...)


이 부분에서 자신이 출력하고자 하는 내용을 입력합니다.


log_priority prio

log_priority 부분에는 해당 로그의 속성을 설정합니다. 로그 속성의 종류는 다음과 같습니다.


DLOG_DEBUG    : 개발자가 확인해보고자 하는 내용

DLOG_INFO         : 일반적인 정보 메시지. 이 로그는 항상 출력됩니다.

DLOG_WARN      : 에러는 아니지만 의도하지 않은 에러가 발생할 가능성이 있을 때 나타나는 메시지. 이 로그는 항상 출력됩니다.

DLOG_ERROR     : 에러 발생시 출력. 이 로그는 항상 출력됩니다.


대략적으로 로그의 속성들은 위와 같이 정의되고 있습니다만, 자신이 쓰고 싶은 속성을 선택하시면 해당 선택사항대로 로그가 출력됩니다.


const char *tag

 해당 로그의 태그를 입력합니다 태그를 설정해두면 이후 자신이 찾고자 하는 로그를 확인하는 것이 수월해집니다.


const char *fmt,...

 해당 로그에서 출력하고자 하는 메시지를 입력합니다.


 위에서 설명드린 dlog_print() 함수를 적절히 사용하신다면 TIZEN의 Native 환경에서 로그를 보는 것이 상당히 수월해질 것입니다. 혹시 출력하고자 하는 메시지가 있다면  sprintf() 함수를 통해 출력하고자 하는 내용을 설정해 주시면 되겠습니다.


 아래는 dlog_print() 함수가 적용된 예제와 그 결과를 나타내고 있습니다. 아래의 소스코드를 참조하시고 여러분들도 TIZEN Native 프로그레밍이 한층 더 수월하게 진행되셨으면 합니다!


/framework/system/deviced/src/display/core.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <dlog.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <vconf-keys.h>
#include <Ecore.h>
 
....
 
static int default_trans(int evt)
{
    struct state *st = &states[pm_cur_state];
    int next_state;
 
    next_state = (enum state_t)trans_table[pm_cur_state][evt];
 
 
    sprintf(pbuffer,"current state : %d, next state : %d, evt : %d", pm_cur_state ,next_state, evt);
    dlog_print(DLOG_INFO, "USR_TAG", pbuffer);
 
    /* check conditions */
    while (st->check && !st->check(next_state)) {
        /* There is a condition. */
        if (standby_mode) {
            _D("standby mode, goto next_state %s",
                state_string[next_state]);
            break;
        }
        _I("%s -> %s : check fail", state_string[pm_cur_state],
               state_string[next_state]);
        if (!check_processes(next_state)) {
            /* this is valid condition - the application that sent the condition is running now. */
            return -1;
        }
    }
 
    /* smart stay */
    if (display_info.face_detection &&
        (pm_status_flag & SMAST_FLAG) && hallic_open) {
        if (display_info.face_detection(evt, pm_cur_state, next_state))
            return 0;
    }
 
    /* state transition */
    pm_old_state = pm_cur_state;
    pm_cur_state = next_state;
    st = &states[pm_cur_state];
 
    /* enter action */
    if (st->action) {
        if (pm_cur_state == S_LCDOFF)
            update_lcdoff_source(VCONFKEY_PM_LCDOFF_BY_TIMEOUT);
 
        if (pm_cur_state == S_NORMAL || pm_cur_state == S_LCDOFF)
            if (set_custom_lcdon_timeout(0== true)
                update_display_time();
 
        if (check_lcdoff_direct() == true) {
            /* enter next state directly */
            states[pm_cur_state].trans(EVENT_TIMEOUT);
        }
        else {
            st->action(st->timeout);
        }
    }
 
    return 0;
}
cs



/framework/system/deviced/src/display/device-interface.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <dlog.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <math.h>
#include <journal/display.h>
 
....
 
static int _bl_brt(PMSys *p, int brightness, int delay)
{
    int ret = -1;
    int cmd;
    int prev;
    char pbuffer[100];
 
    sprintf(pbuffer,"bright : %d, delay : %d", brightness, delay);
    dlog_print(DLOG_INFO, "USR_TAG", pbuffer);
 
    if (delay > 0)
        usleep(delay);
 
    if (force_brightness > 0 && brightness != p->dim_brt) {
        _I("brightness(%d), force brightness(%d)",
            brightness, force_brightness);
        brightness = force_brightness;
    }
 
    cmd = DISP_CMD(PROP_DISPLAY_BRIGHTNESS, DEFAULT_DISPLAY);
    ret = device_get_property(DEVICE_TYPE_DISPLAY, cmd, &prev);
 
    /* Update new brightness to vconf */
    if (!ret && (brightness != prev)) {
        vconf_set_int(VCONFKEY_PM_CURRENT_BRIGHTNESS, brightness);
    }
 
    /* Update device brightness */
    ret = device_set_property(DEVICE_TYPE_DISPLAY, cmd, brightness);
 
    _I("set brightness %d, %d", brightness, ret);
 
    return ret;
}
cs


Native 단계에서 위와 같이 설정을 해주셨다면 아래와 같이 자신이 설정한 프로그램의 진행중인 값을 로그를 통해 실시간으로 확인하실 수 있습니다.

(아래 스크린샷의 경우 필자가 내용을 수정하였기 때문에 각자의 환경에서는 로그가 다른 결과로 출력됩니다.)




300x250

Tizen Mobile Native App Programming - 네이티브 센서 예제 분석

공대생의 팁 2015. 5. 26. 13:13

 타이젠의 Web 애플리케이션 제작은 개발자가 약간의 직감으로 마치 안드로이드의 XML을 꾸미듯이 작업을 하는 것이 용이합니다만 Native 단계에서 애플리케이션을 제작하려면 무식하게도 C와 C++을 통해 애플리케이션을 설계해야 하지요. 물론 안드로이드에서도 JAVA 소스코드로 UI를 디자인 하는 경우도 있지만 왠만해선 XML를 많이 사용하긴 하죠.


 본 포스팅에서는 타이젠의 Native 애플리케이션의 예제 중 하나인 SensorApp Sample을 예를 들어 설명해 나가도록 하겠습니다.


 SensorApp 샘플 애플리케이션은 타이젠 기반의 디바이스 내의 센서들의 동작을 확인할 수 있도록 만들어져있습니다. 일단 실행을 해보면 대략 어떠한 방식으로 애플리케이션이 센서값을 받고 있는지 짐작하실 수 있을 것입니다.



타이젠 SensorApp 예제의 소스코드는 다음과 같이 구성되어 있습니다.(버전은 2.3.3 기준)






각 소스의 내용을 살펴보면 다음과 같습니다.


General


main.c

SensorApp의 시작포인트를 제공합니다. 애플리케이션의 instance를 생성한 후 실행합니다.


main-app.c

애플리케이션 life-cycle을 관리합니다. 애플리케이션의 main window, naviframe, 센서 리스트의 view를 생성합니다.


Utils


logger.h

로고 메시지를 작성하기 위한 매크로를 제공합니다.


color-utils.h

integer 값으로 저장된 color값에 대한 매크로를 제공합니다.


Model


sensor-info.h

각각의 센서에 대한 데이터 타입을 정의합니다.


sensor-list.h

SensorApp에서 제공하는 센서 리스트를 정의합니다.


sensor-magnetic.c

마그네틱 센서의 세기값을 계산하는 알고리즘을 정의합니다.


View


window.c

애플리케이션의 main window를 담당합니다.


sensor-list-view.c

센서의 리스트뷰를 제공합니다.


snesor-data-view.c

센서로부터 받은 데이터값과 그 값을 그래픽으로 표현하는 부분을 담당합니다.


sensor-data-chart.c

기본적인 데이터의 그림 기능을 정의합니다.


snesor-data-chart-private.h

벡터나 앵글값을 나타내는 챠트에 대한 센서 데이터값을 정의합니다.


snesor-vector-chart.c

화살표를 사용한 벡터값을 그려주는 일을 수행합니다.


sensor-angle-chart.c

각도값을 활용하여 pie chart를 그리는 역할을 합니다.



다음은 SensorApp 애플리케이션이 구현되는 과정을 나타내 보겠습니다.


1. 먼저 Sensor API의 헤더를 등록합니다.

1
#include <sensor.h>
cs


2. 다음으로 센서의 핸들값과 하드웨어의 정보를 검색합니다.

sensor-list.c 코드 내에 있는 sensor_list_init() 함수는 센서의 핸들값과 하드웨어의 정보를 검색한 후 이를 sensor_info 배열에 저장합니다. 검색된 센서의 정보는 이후 센서 데이터를 표현하는데 사용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void sensor_list_init()
{
   sensor_info *info = sensors;
   sensor_info *end = info + sensor_count;
   for (; info != end; ++info)
   {
      float resolution = 1.0;
      // Retrieve sensor handle using sensor type
      sensor_get_default_sensor(info->type, &info->sensor);
      // Retrieve sensor minimal and maximal values
      sensor_get_min_range(info->sensor, &info->value_min);
      sensor_get_max_range(info->sensor, &info->value_max);
      // Retrieve sensor resolution
      sensor_get_resolution(info->sensor, &resolution);
   }
}
cs


<sensor-info.h> 헤더파일은 센서의 리스트와 데이터를 표현하는 데에 사용될 센서에 대한 정보를 저장하기 위한 자료구조를 정의합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct _sensor_info
{
   sensor_h sensor; // Sensor handle
   sensor_type_e type; // Sensor type
   sensor_unit_e units; // Value measurement units
 
   const char *name; // Sensor display name
   const char **value_names; // Value names array of value_count size
   int value_count; // Values count
 
   float value_min; // Minimal value
   float value_max; // Maximal value
};
cs


3. 센서의 유효성을 확인합니다.

sensor-list-view.c 파일은 sensor-list.c 파일에 의해 제공되는 센서 리스트들을 사용하는 elm_list 를 생성합니다. 센서의 리스트들이 채워지는 동안, _list_view_fill() 함수는 sensor_is_supported() 함수를 통해 디바이스의 센서 들이 사용가능한지에 대한 여부를 체크합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void _list_view_fill(list_view *view)
{
   unsigned count = 0;
   const sensor_info *item = sensor_list_get(&count);
   const sensor_info *end = item + count;
 
   RETM_IF(!item, "item is NULL");
   for (; item != end; ++item)
   {
      bool is_supported = false;
      // Check whether sensor is supported by device
      sensor_is_supported(item->type, &is_supported);
      if (is_supported)
      {
         elm_list_item_append(view->list, item->name, NULL, NULL, _list_view_sel_cb, item);
      }
   }
}
cs


어떤 센서가 선택되었을 때,  _list_view_sel_cb() 함수가 선택된 함수 정보를 보내 센서의 데이터를 찾습니다.

1
2
3
4
5
6
static void _list_view_sel_cb(void *data, Evas_Object *obj, void *event_info)
{
   sensor_info *item = data;
 
   sensor_data_view_create(view->navi, item);
}
cs


4. 센서 데이터를 수신합니다.

 센서 데이터의 수신을 시작할 때, _data_view_sensor_start() 함수가 센서의 listener를 등록한 후, 센서의 event callback를 설정한 다음, 센서의 동작을 개시합니다.

1
2
3
4
5
6
7
8
static void _data_view_sensor_start(data_view *view)
{
   sensor_error_e err = SENSOR_ERROR_NONE;
   err = sensor_create_listener(view->sensor_info->sensor, &view->sensor_listener);
   RETM_IF(err != SENSOR_ERROR_NONE, "sensor_create_listener() failed(%d)", err);
   sensor_listener_set_event_cb(view->sensor_listener, SENSOR_INTERVAL, _data_view_sensor_cb, view);
   sensor_listener_start(view->sensor_listener);
}
cs


_data_view_sensor_cb() 센서 이벤트 callback 함수가 호출되었을 때, _data_view_value_items_update() 함수가 수신된 센서의 데이터값을 chart에 적용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static void _data_view_sensor_cb(sensor_h sensor, sensor_event_s *sensor_data, void *user_data)
{
   data_view *view = user_data;
 
   _data_view_value_items_update(view, sensor_data->values);
   _data_view_extra_items_update(view, sensor_data->values);
}
 
static void _data_view_value_items_update(data_view *view, float *values)
{
   bool update_chart = false;
 
   // Update genlist items with values received from the sensor
   for (; item != end; ++item, ++value)
   {
      if (item->value != *value)
      {
         // Chart MUST be updated if any value has changed
         update_chart = true;
         item->value = *value;
         // Update genlist item part that displays value
         elm_genlist_item_fields_update(item->obj_item, PART_VALUE, ELM_GENLIST_ITEM_FIELD_TEXT);
      }
   }
 
   // Update chart if necessary
   if (view->chart && update_chart)
   {
      sensor_data_chart_update(view->chart, view->sensor_info->value_range,
         view->sensor_info->axes, values, data_view_item_colors,
         view->sensor_info->value_count);
   }
}
cs


5. 센서의 동작을 중단합니다.

센서 데이터의 view가 destroy 상태가 되었을 때, 센서의 데이터값 수신을 중단하기 위해, _data_view_destroy_cb() 함수가 sensor_listener_stop() 함수를 호출하고 난 후 sensor_destroy_listener() 함수를 사용하여 listener를 중단시킵니다.

1
2
3
4
5
static void _data_view_destroy_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   sensor_listener_stop(view->sensor_listener);
   sensor_destroy_listener(view->sensor_listener);
}
cs




Reference : Tizen Help guide

300x250

공유기에 연결된 Linux(Ubuntu) 컴퓨터를 외부에서 원격 데스크톱 연결을 통해 조작하기

공대생의 팁 2015. 5. 7. 16:51

최근에는 노트북의 성능도 꽤 좋아진 편이라 한 대만 들고다녀도 어지간한 데스크탑 컴퓨터의 기능은 무리없이 수행할 수 있게 되었습니다만 그럼에도 아직까지는 데스크톱의 성능을 따라가지는 못하는 것이 현실이지요. 그렇다고 해서 데스크탑 컴퓨터를 이리저리 들고다닐 수는 없는 노릇이지요.


 이러한 경우를 위해 최근의 운영체제에서는 다른 컴퓨터를 통해 원격으로 조작이 가능한 기능이 구현되어 있는데요 이 원격 기능이 서로 다른 운영체제끼리도 호환이 된다는 점이 있습니다. 그렇기에 자신의 노트북 컴퓨터가 Windows라 하더라도 자신의 집에 있는 데스크탑이 Ubuntu라면 원격접속을 통해 데스크탑 PC를 마치 내 노트북에서 사용하는 듯이 원거리에서 조작이 가능합니다!


 이번 포스팅에서는 공유기와 연결되어 있는 Ubuntu 운영체제 데스크톱을 야외 등 외부에서 인터넷을 통해 접속하여 데스크톱을 원격조작하는 방식에 대해 알아보고자 합니다. 그럼 시작해보도록 하겠습니다.


1. 먼저 Ubuntu에 다음 명령어를 입력하여 프로그램을 설치합니다.


$ sudo apt-get install vnc4server xrdp


4. 설치한 프로그램을 바로 적용합니다.


$ sudo service xrdp restart


 만약 자신의 컴퓨터가 다른 포트가 설정되어 xrdp와 충돌하게 될 경우 설정을 변경해야 되는 경우가 있습니다. 이는 /etc/xrdp/xrdp.ini 와

/etc/xrdp/sesman.ini 파일을 수정해 주시면 되겠습니다.


/etc/xrdp/xrdp.ini

 위 화면의 설정을 보았을 때 현재 [globals]에서 포트번호가 기본으로 3389번으로 설정되어 있습니다. 만약 자신의 환경에서 해당 포트를 다른 곳에서 사용하고 있거나 자신이 설정하고자 하는 다른 포트번호가 있으면 해당 부분을 변경해 주시면 되겠습니다.


3. 아래 명령어를 입력하여 포트가 제대로 설정되었는지 확인합니다.


$ netstat -antp




 위의 명령어를 통해 화면에 나오는 것처럼 자신이 설정한 포트가 LISTEN으로 설정되어 있다면 외부에서 접속을 받을 준비가 되었다는 의미입니다.


 4. 외부의 원격 조정 요청을 처리하는 부분을 설정해줍니다. Windows버튼 (혹은 우분투 왼쪽 상단 버튼)을 누르신후 한글로 '데스크톱'이라고 입력하시면 '데스크톱 공유'라는 이름의 프로그램이 나타납니다. 해당 프로그램을 실행합니다.



 5. 실행시 '데스크톱 공유 기본 설정'창이 나타납니다. 해당 설정을 아래와 같이 해줍니다. 다음으로 외부에서 접속하였을 때 입력할 비밀번호를 설정해주신 후 닫기 버튼을 클릭합니다.



 여기까지 진행하셨다면 Ubuntu 데스크톱에서 설정은 모두 끝났습니다. 만약 자신의 데스크톱PC가 인터넷선과 직접적으로 연결되어 있다면 자신의 IP 주소만 기억해두시면 바로 원격조정이 가능합니다만 공유기의 경우 포트포워딩을 통해 공유기와 연결된 데스크탑 PC로 연결될 수 있도록 해주어야 합니다. 포트포워딩에 대해 좀 더 자세히 알고 싶신 분께서는 아래 포스팅을 참조해 주시기 바랍니다.


WF2411 공유기를 통한 외부 기기와 소켓 통신 프로그래밍

http://elecs.tistory.com/45


 이번에는 자신의 공유기에 포트포워딩을 설정해줍니다. 본 포스팅에서는 Netis사의 WF2411 공유기를 기준으로 설명하겠습니다. 자신의 공유기를 통한 포트포워딩 설정에 대한 자세한 사항은 공유기 제조사 홈페이지를 참조해주시길 바랍니다.


 6. 현재 자신의 PC에 설정된 IP 주소를 확인합니다. 이 과정을 통해 확인하게 되는 IP주소는 공유기가 자신과 연결된 기기에 할당한 가상 IP로서 공유기와 연결된 기기 사이에서만 사용될 수 있는 내부 IP 주소입니다.


$ ifconfig




 Terminal을 통해 현재 Ubuntu 데스크탑 컴퓨터가 공유기로부터 192.168.1.9 번의 주소를 할당 받고 있음을 확인하실 수 있습니다. 이 주소를 기억하신 후 포트포워딩 설정시 적용하도록 합니다.


 7. 자신의 공유기 설정 모드로 로그인합니다. 일반적으로 공유기 설정 모드에 접속하는 주소는 http://192.168.1.1 입니다.



 8. 포트포워딩 메뉴에 들어가신 후 자신의 컴퓨터 환경에 맞추어 접속 포트를 설정해줍니다.


이곳에서 입력하셔야 할 내용은 다음과 같습니다.

내부 IP주소(서버 PC) - 자신의 Ubuntu 데스크탑이 공유기로부터 할당받은 주소값을 입력합니다.

포트번호(외부) - 원격 접속을 할 컴퓨터가 공유기에 접속하게 될 때 사용할 포트 번호를 입력합니다.

포트번호(내부) - 공유기가 Ubuntu 데스크탑으로 접속하게 될 때 사용할 포트번호를 입력합니다. Ubuntu xrdp의 경우 내부 포트 설정 기본값이 3389로 설정되어 있습니다.


 여기서 외부 포트번호는 원격접속을 하게 될 컴퓨터가 접속하고자 하는 포트 번호를 입력하고 외부에서 해당 포트번호를 입력하게 되면 공유기는 해당 신호를 내부 IP주소가 설정된 컴퓨터로 내부 포트번호를 통해 접속하게 됩니다. 자세한 사항은 아래 그림을 참조해주시면 되겠습니다.

이미지 출저 : http://documentation.commvault.com/hds/v10/article?p=features/firewall/port_forward_gateway.htm


 443번과 444번은 외부 포트번호를 나타내고 있으며 해당 포트 번호로 공유기에 연결을 시도하면 공유기는 이를 해당 색깔과 같은 색깔로 내부 IP주소가 설정된 대로 신호를 전송합니다. 이 때 해당 신호는 내부 포트번호가 440으로 설정된 상황입니다.


  9. 이제 자신의 공유기가 외부로부터 할당받은 IP 주소를 확인합니다. 이 또한 자신의 공유기 설정 페이지에서 확인이 가능합니다.


 위에서 보셨을 때 WAN IP 주소 부분에 적혀있는 것이 바로 자신의 공유기가 외부에서 할당받은 주소입니다. 앞으로 우리는 위 주소를 통해여 원격 데스크톱에 접속할 것입니다.


 이번에는 외부의 컴퓨터를 통해 Ubuntu 데스크톱 컴퓨터를 조종해 보도록 하겠습니다. 먼저 시작(윈도) 버튼을 누르신 후 검색창에 '원격 데스크톱' 을 검색합니다.



 '원격 데스크톱 연결' 프로그램을 실행하신 후 Ubuntu 데스크탑과 연결된 공유기의 IP 주소를 입럭하신 후 포트포워딩으로 설정해 두었던 포트번호를 입력하신 후 '연결' 버튼을 클릭합니다.



 노란 경고 화면이 등장하면서 연결 여부를 확인합니다. '예(Y)'버튼을 클릭합니다.



 아래와 같은 화면이 나온다면 Ubuntu 데스크탑에 연결되는 데에 성공한 것입니다. Module을 'console'로 선택하신 후 우분투에서 '데스크톱 공유 기본설정'시 설정한 비밀번호를 입력합니다.



 올바른 비밀번호를 입력하시게 되면 아래와 같이 원격으로 접속된 Ubuntu 데스크탑 화면이 등장하게 됩니다. 이제 여러분들께서 하고자 하는 작업을 원격으로 수행해 보시기를 바랍니다~!






300x250

TIZEN 소스코드 빌드 gbs가 설치되지 않을 때 수동으로 설치하기

공대생의 팁 2015. 5. 2. 10:48

 Tizen 개발에 입문하기 위해 프로그램들을 설치하는 과정을 진행하던 도중 참으로 이상한 문제가 발생했습니다. 다음과 같이 우분투 apt-get reopsitory에 Tizen 관련 패키지들을 설치할 수 있도록 환경을 구성한 후 gbs 설치를 시도하려 하였으나 더이상 진행이 되지 않는 것이었습니다.


$ sudo vim /etc/apt/sources.list
deb http://download.tizen.org/tools/latest-release/Ubuntu_14.04 /
$ sudo apt-get update

$ sudo apt-get install gbs




 위에서와 같이 gbs를 apt-get을 통해 설치를 시도하니 위에 보는 저 3줄만 덩그러니 뜨기만 하고 설치가 더이상  진행이 되지 않는겁니다. 무언가 제 컴퓨터상의 설정이 잘못되어서 그런지는 잘 모르겠으나 일단 gbs를 설치하셔야 TIZEN 개발환경을 사용하실 수 있습니다. 이 경우 본인은 사이트에 직접 접속하여 소스를 다운로드 받았습니다. 그렇다면 그 방법에 대해 자세히 살펴보도록 합시다.


 먼저 아래 주소로 이동합니다. 우분투 운영체제가 12.04일 경우를 기준으로 설명해 드리겠습니다.


 http://download.tizen.org/tools/latest-release/Ubuntu_14.04



 이 사이트에서 우리들이 설치하고자 하는 패키지를 찾아 설치해보도록 하겠습니다. 현재 우리들이 설치하고자 하는 패키지는 gbs로 해당 패키지를 찾아보도록 하겠습니다.

 apt-get에서 자신이 설치하고자 하는 패키지를 찾는 방법은 위의 파일목록 중 'Packages'를 클릭하시면 TIZEN과 관계된 패키지 목록들을 살펴보실 수 있습니다.



 위의 화면에서 보시는 바와 같이 Package 명이 gbs인 패키지의 정보를 찾으실 수 있습니다. 여기서 해당 패키지의 경로가 'Filename' 부분에 자세히 표기되어 있습니다. 이 위치에 있는 패키지를 다운로드 받으신 후 자신의 컴퓨터에서 실행해줍시다.



 처음 패키지를 실행하게 되면 의존성 문제로 인해 설치가 더이상 진행되지 않습니다. 그렇습니다.. apt-get 명령어를 사용하면 관련 의존성 패키지들이 단 한번에 설치되지만 수동으로 설치하게 되는 경우 이러한 의존성 패키지를 일일히 찾아주어야 하는 상당히 번거로운 과정을 거쳐야 하는 것입니다.

 위에서 보이시는 부분에서 '패키지 의존관계가 불충분함:gbs-api(=0.23.2)' 부분에서 해당 패키지를 위 Package 파일에서 해당 패키지를 찾는 과정을 한 번 더 거쳐줍니다.



 이러한 방식으로 조금은 불편하고 번거롭습니다만 의존성 파일을 지속적으로 찾아주시면서 설치를 진행하시면 gbs 패키지를 모두 설치하실 수 있을 것입니다. 모두들 패키지를 모두 설치하시고 TIZEN 개발자로서의 첫 걸음을 밟아보시기를 바랍니다!

300x250

안드로이드 Framework에서 Camera 동작 원리 분석(4)

안드로이드/카메라 2015. 5. 1. 00:34

 최근 지속적으로 안드로이드 Camera의 동작 원리에 대해 공부를 꾸준히 하고 있습니다만, 생각보다 다루어야 할 내용이 많군요. 처음엔 Application 단계에서 수행되는 3개의 코드만 분석하면 끝일 줄 알았건만 각 코드의 동작을 이해하기 위해 세부적인 공부가 병행되다보니 포스팅을 시작한지 40일이 넘은 지금 시점에서 4번째 포스팅이 진행되게 되었습니다. 참으로 안드로이드의 구조가 우주처럼 방대해 보이는건 왜일까요?


 서론이 너무 길어지는군요. 그럼 지금부터 포스팅을 진행해 보도록 하겠습니다. 그 전에 1번째 포스팅에서 다루었었던 Application 단계에서의 Camera 구동 방식을 다시 한 번 보도록 합시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;
    private Camera camera;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
....        
        surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(surfaceListener);
        
    }
    
    private SurfaceHolder.Callback surfaceListener = new SurfaceHolder.Callback() {
        
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            camera.release();
            
        }
        
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            camera = Camera.open();
            try {
                camera.setPreviewDisplay(holder);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
        
        @Override
        public void surfaceChanged(SurfaceHolder holder, int formatint width, int height) {
            // TODO Auto-generated method stub
            Camera.Parameters parameters = camera.getParameters();
            parameters.setPreviewSize(width, height);
            camera.startPreview();
            
        }
    };
 
 
cs

 지난 (1)~(3)번 포스팅에서는 Camera.open(); 코드의 실행 과정을 살펴보았습니다만 Application 단계에서 단 한줄의 실행이 이토록 길게 다루어질 줄은 생각도 못했습니다. 참고로 이후의 포스팅은 지난 포스팅들에서 확인하였던 개념들을 모두 알고 있다는 전제하에 진행되므로 포스팅을 읽던 도중 모르는 개념이 나온다 싶으면 이전 포스팅의 내용을 복습하면서 읽어나가셨으면 합니다.


camera.setPreviewDisplay(holder);


 Camera 클래스 변수에 객체를 초기화 한 후 처음으로 실행되는 함수입니다. 이 함수를 통해 SurfaceHolder의 값이 Camera에 적용되게 됩니다.


/frameworks/base/core/java/android/hardware/Camera.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
     * Sets the {@link Surface} to be used for live preview.
     * Either a surface or surface texture is necessary for preview, and
     * preview is necessary to take pictures.  The same surface can be re-set
     * without harm.  Setting a preview surface will un-set any preview surface
     * texture that was set via {@link #setPreviewTexture}.
     *
     * <p>The {@link SurfaceHolder} must already contain a surface when this
     * method is called.  If you are using {@link android.view.SurfaceView},
     * you will need to register a {@link SurfaceHolder.Callback} with
     * {@link SurfaceHolder#addCallback(SurfaceHolder.Callback)} and wait for
     * {@link SurfaceHolder.Callback#surfaceCreated(SurfaceHolder)} before
     * calling setPreviewDisplay() or starting preview.
     *
     * <p>This method must be called before {@link #startPreview()}.  The
     * one exception is that if the preview surface is not set (or set to null)
     * before startPreview() is called, then this method may be called once
     * with a non-null parameter to set the preview surface.  (This allows
     * camera setup and surface creation to happen in parallel, saving time.)
     * The preview surface may not otherwise change while preview is running.
     *
     * @param holder containing the Surface on which to place the preview,
     *     or null to remove the preview surface
     * @throws IOException if the method fails (for example, if the surface
     *     is unavailable or unsuitable).
     */
    public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
        if (holder != null) {
            setPreviewDisplay(holder.getSurface());
        } else {
            setPreviewDisplay((Surface)null);
        }
    }
 
    private native final void setPreviewDisplay(Surface surface) throws IOException;
cs

 이번에도 setPreviewDisplay() native 함수가 호출되고 있는 모습을 보실 수 있습니다. SurfaceHolder 내에는 Surface값이 저장되어 있으며 이 곳에 저장되어 있던 Surface 클래스를 holder에 넘겨준다고 생각하시면 될 듯합니다. 혹시 SurfaceHolder가 어떤 방식으로 동작하는 지에 대해 알고 싶으신 분들께서는 아래 링크를 참조해 주시길 바랍니다.


http://elecs.tistory.com/78


/frameworks/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz, jobject jSurface)
{
    ALOGV("setPreviewDisplay");
    sp<Camera> camera = get_native_camera(env, thiz, NULL);
    if (camera == 0return;
 
    sp<IGraphicBufferProducer> gbp;
    sp<Surface> surface;
    if (jSurface) {
        surface = android_view_Surface_getSurface(env, jSurface);
        if (surface != NULL) {
            gbp = surface->getIGraphicBufferProducer();
        }
    }
 
    if (camera->setPreviewTarget(gbp) != NO_ERROR) {
        jniThrowException(env, "java/io/IOException""setPreviewTexture failed");
    }
}
cs


 드디어 Camera에 Surface가 적용되는 순간이다. 이 부분을 잘 살펴본다면 Camera의 화면과 관련된 소스코드를 볼 수 있을 것으로 기대된다. 당장 확인해 보도록 해보자.


sp<Camera> camera = get_native_camera(env, thiz, NULL);


 이전에 우리들이 Open() 함수를 통해 만들었던 Camera 클래스를 데리고 오는 것으로 보인다. 해당 함수의 구현을 살펴보도록 하자.


/frameworks/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct fields_t {
    jfieldID    context;
    jfieldID    facing;
    jfieldID    orientation;
    jfieldID    canDisableShutterSound;
    jfieldID    face_rect;
    jfieldID    face_score;
    jfieldID    rect_left;
    jfieldID    rect_top;
    jfieldID    rect_right;
    jfieldID    rect_bottom;
    jmethodID   post_event;
    jmethodID   rect_constructor;
    jmethodID   face_constructor;
};
 
static fields_t fields;
 
....
 
sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, JNICameraContext** pContext)
{
    sp<Camera> camera;
    Mutex::Autolock _l(sLock);
    JNICameraContext* context = 
        reinterpret_cast<JNICameraContext*>(env->GetIntField(thiz, fields.context));
    if (context != NULL) {
        camera = context->getCamera();
    }
    ALOGV("get_native_camera: context=%p, camera=%p", context, camera.get());
    if (camera == 0) {
        jniThrowRuntimeException(env, "Method called after release()");
    }
 
    if (pContext != NULL) *pContext = context;
    return camera;
}
cs

이제 각 코드를 한 줄씩 살펴보도록 합시다.


JNICameraContext* context = reinterpret_cast<JNICameraContext*>(env->GetIntField(thiz, fields.context));


 이전에 Camera를 설정하는 도중에 context 함수를 만들었던 바가 있습니다. 이 때 생성되었던 context의 경우 Smart Pointer의 참조를 강화시켰기 때문에 아직까지 메모리에 남아있습니다. 이를 reinterpret_cast<>를 이용하여 JNICameraContext 클래스를 불러들이도록 합니다.


camera = context->getCamera();


 불러들인 context 내에 있는 camera 클래스를 꺼내옵니다.


/frameworks/base/core/jni/android_hardware_Camera.cpp

1
sp<Camera> getCamera() { Mutex::Autolock _l(mLock); return mCamera; }
cs

위 과정을 통해 Camera 클래스를 다시 불어들이게 되었습니다.


이제 나머지 코드들을 하나씩 살펴보도록 합시다.


/frameworks/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz, jobject jSurface)
{
    ALOGV("setPreviewDisplay");
    sp<Camera> camera = get_native_camera(env, thiz, NULL);
    if (camera == 0return;
 
    sp<IGraphicBufferProducer> gbp;
    sp<Surface> surface;
    if (jSurface) {
        surface = android_view_Surface_getSurface(env, jSurface);
        if (surface != NULL) {
            gbp = surface->getIGraphicBufferProducer();
        }
    }
 
    if (camera->setPreviewTarget(gbp) != NO_ERROR) {
        jniThrowException(env, "java/io/IOException""setPreviewTexture failed");
    }
}
cs


surface = android_view_Surface_getSurface(env, jSurface)


 Native 단계에서 Surface 클래스를 생성하는 함수입니다. 함수 내용을 자세하 분석해 보도록 합시다. 그전에 앞서 android_view_Surface.cpp 소스코드가 Java 단계의 Surface와 연결되는 과정에 대해 확인해 보도록 하겠습니다. Java와 C++ 이 JNI로 연결는 과정은 아래 포스팅을 참조해 주시기 바랍니다.


http://elecs.tistory.com/82


/frameworks/base/core/jni/android_view_Surface.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int register_android_view_Surface(JNIEnv* env)
{
    int err = AndroidRuntime::registerNativeMethods(env, "android/view/Surface",
            gSurfaceMethods, NELEM(gSurfaceMethods));
 
    jclass clazz = env->FindClass("android/view/Surface");
    gSurfaceClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
    gSurfaceClassInfo.mNativeObject =
            env->GetFieldID(gSurfaceClassInfo.clazz, "mNativeObject""I");
    gSurfaceClassInfo.mLock =
            env->GetFieldID(gSurfaceClassInfo.clazz, "mLock""Ljava/lang/Object;");
    gSurfaceClassInfo.ctor = env->GetMethodID(gSurfaceClassInfo.clazz, "<init>""(I)V");
 
    clazz = env->FindClass("android/graphics/Canvas");
    gCanvasClassInfo.mFinalizer = env->GetFieldID(clazz, "mFinalizer""Landroid/graphics/Canvas$CanvasFinalizer;");
    gCanvasClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas""I");
    gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat""I");
 
    clazz = env->FindClass("android/graphics/Canvas$CanvasFinalizer");
    gCanvasFinalizerClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas""I");
 
    clazz = env->FindClass("android/graphics/Rect");
    gRectClassInfo.left = env->GetFieldID(clazz, "left""I");
    gRectClassInfo.top = env->GetFieldID(clazz, "top""I");
    gRectClassInfo.right = env->GetFieldID(clazz, "right""I");
    gRectClassInfo.bottom = env->GetFieldID(clazz, "bottom""I");
 
    return err;
}
cs


 위 과정을 통해 JNI로 연결된 Java 단계의 Surface와 Native 단계의 android_view_Surface을 이용하여 아래 소스코드를 살펴보도록 합시다.


/frameworks/base/core/jni/android_view_Surface.cpp

1
2
3
4
5
6
7
8
9
10
11
sp<Surface> android_view_Surface_getSurface(JNIEnv* env, jobject surfaceObj) {
    sp<Surface> sur;
    jobject lock = env->GetObjectField(surfaceObj,
            gSurfaceClassInfo.mLock);
    if (env->MonitorEnter(lock) == JNI_OK) {
        sur = reinterpret_cast<Surface *>(
                env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeObject));
        env->MonitorExit(lock);
    }
    return sur;
}
cs

 

그러면 이제 한 줄씩 살펴보도록 합시다.


jobject lock = env->GetObjectField(surfaceObj, gSurfaceClassInfo.mLock);

 Java Object 변수 lock에 Java 클래스 내에 있는 Object 변수 mLock를 저장한다.

 

sur = reinterpret_cast<Surface *>(

                env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeObject));

 

 Surface 변수 sur에 GetIntField()에 설정된 surface의 값을 받는다.

 

이제 마지막으로 IGraphicBufferProducer 클래스가 설정되는 과정을 확인해 보도록 하겠습니다.

 

/frameworks/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz, jobject jSurface)
{
    ALOGV("setPreviewDisplay");
    sp<Camera> camera = get_native_camera(env, thiz, NULL);
    if (camera == 0return;
 
    sp<IGraphicBufferProducer> gbp;
    sp<Surface> surface;
    if (jSurface) {
        surface = android_view_Surface_getSurface(env, jSurface);
        if (surface != NULL) {
            gbp = surface->getIGraphicBufferProducer();
        }
    }
 
    if (camera->setPreviewTarget(gbp) != NO_ERROR) {
        jniThrowException(env, "java/io/IOException""setPreviewTexture failed");
    }
}
cs

 

gbp = surface->getIGraphicBufferProducer();


  해당 Surface 클래스에 저장되어 있는 IGraphicBufferProducer 클래스의 값을 저장합니다. 이를 카메라에 연결하면 Surface와 연결되는 과정이라고 생각하시면 되겠습니다.


/frameworks/native/libs/gui/Surface.cpp

1
2
3
sp<IGraphicBufferProducer> Surface::getIGraphicBufferProducer() const {
    return mGraphicBufferProducer;
}
cs


camera->setPreviewTarget(gbp)
 

 Camera 클래스에 IGraphicBufferProducer를 연결합니다.


/frameworks/av/camera/Camera.cpp

1
2
3
4
5
6
7
8
9
// pass the buffered IGraphicBufferProducer to the camera service
status_t Camera::setPreviewTarget(const sp<IGraphicBufferProducer>& bufferProducer)
{
    ALOGV("setPreviewTarget(%p)", bufferProducer.get());
    sp <ICamera> c = mCamera;
    if (c == 0return NO_INIT;
    ALOGD_IF(bufferProducer == 0"app passed NULL surface");
    return c->setPreviewTarget(bufferProducer);
}
cs


/frameworks/av/include/camera/ICamera.h

1
2
3
4
5
6
7
8
9
10
11
12
13
class ICamera: public IInterface
{
    /**
     * Keep up-to-date with ICamera.aidl in frameworks/base
     */
public:
....
    // pass the buffered IGraphicBufferProducer to the camera service
    virtual status_t        setPreviewTarget(
            const sp<IGraphicBufferProducer>& bufferProducer) = 0;
 
....
}
cs


 setPreviewTarget() 함수가 virtual로 선언되어 있으므로 Dynamic 으로 작동됨을 알 수 있습니다. mCamera 값이 어떤 것으로 선언 되어 있는지는 CameraService::Connect()에 잘 표현되어 있습니다.



/frameworks/av/services/camera/libcameraservice/CameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
status_t CameraService::connect(
        const sp<ICameraClient>& cameraClient,
        int cameraId,
        const String16& clientPackageName,
        int clientUid,
        /*out*/
        sp<ICamera>& device) {
 
    String8 clientName8(clientPackageName);
    int callingPid = getCallingPid();
 
    LOG1("CameraService::connect E (pid %d \"%s\", id %d)", callingPid,
            clientName8.string(), cameraId);
 
    status_t status = validateConnect(cameraId, /*inout*/clientUid);
    if (status != OK) {
        return status;
    }
 
 
    sp<Client> client;
    {
        Mutex::Autolock lock(mServiceLock);
        sp<BasicClient> clientTmp;
        if (!canConnectUnsafe(cameraId, clientPackageName,
                              cameraClient->asBinder(),
                              /*out*/clientTmp)) {
            return -EBUSY;
        } else if (client.get() != NULL) {
            device = static_cast<Client*>(clientTmp.get());
            return OK;
        }
 
        int facing = -1;
        int deviceVersion = getDeviceVersion(cameraId, &facing);
 
        // If there are other non-exclusive users of the camera,
        //  this will tear them down before we can reuse the camera
        if (isValidCameraId(cameraId)) {
            // transition from PRESENT -> NOT_AVAILABLE
            updateStatus(ICameraServiceListener::STATUS_NOT_AVAILABLE,
                         cameraId);
        }
 
        switch(deviceVersion) {
          case CAMERA_DEVICE_API_VERSION_1_0:
            client = new CameraClient(this, cameraClient,
                    clientPackageName, cameraId,
                    facing, callingPid, clientUid, getpid());
            break;
          case CAMERA_DEVICE_API_VERSION_2_0:
          case CAMERA_DEVICE_API_VERSION_2_1:
          case CAMERA_DEVICE_API_VERSION_3_0:
            client = new Camera2Client(this, cameraClient,
                    clientPackageName, cameraId,
                    facing, callingPid, clientUid, getpid(),
                    deviceVersion);
            break;
          case -1:
            ALOGE("Invalid camera id %d", cameraId);
            return BAD_VALUE;
          default:
            ALOGE("Unknown camera device HAL version: %d", deviceVersion);
            return INVALID_OPERATION;
        }
 
        status_t status = connectFinishUnsafe(client, client->getRemote());
        if (status != OK) {
            // this is probably not recoverable.. maybe the client can try again
            // OK: we can only get here if we were originally in PRESENT state
            updateStatus(ICameraServiceListener::STATUS_PRESENT, cameraId);
            return status;
        }
 
        mClient[cameraId] = client;
        LOG1("CameraService::connect X (id %d, this pid is %d)", cameraId,
             getpid());
    }
    // important: release the mutex here so the client can call back
    //    into the service from its destructor (can be at the end of the call)
 
    device = client;
    return OK;
}
 
cs


 위에서 device 변수는 Camera->mCamera 변수를 나타내고 있습니다. 이에 따라 mCamera는 CameraClient의 함수를 실행하고 있음을 눈치채실 수 있으리라 생각합니다.


/frameworks/av/services/camera/libcameraservice/api1/CameraClient.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// set the buffer consumer that the preview will use
status_t CameraClient::setPreviewTarget(
        const sp<IGraphicBufferProducer>& bufferProducer) {
    LOG1("setPreviewTarget(%p) (pid %d)", bufferProducer.get(),
            getCallingPid());
 
    sp<IBinder> binder;
    sp<ANativeWindow> window;
    if (bufferProducer != 0) {
        binder = bufferProducer->asBinder();
        // Using controlledByApp flag to ensure that the buffer queue remains in
        // async mode for the old camera API, where many applications depend
        // on that behavior.
        window = new Surface(bufferProducer, /*controlledByApp*/ true);
    }
    return setPreviewWindow(binder, window);
}
cs


/frameworks/av/services/camera/libcameraservice/api1/CameraClient.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
status_t CameraClient::setPreviewWindow(const sp<IBinder>& binder,
        const sp<ANativeWindow>& window) {
    Mutex::Autolock lock(mLock);
    status_t result = checkPidAndHardware();
    if (result != NO_ERROR) return result;
 
    // return if no change in surface.
    if (binder == mSurface) {
        return NO_ERROR;
    }
 
    if (window != 0) {
        result = native_window_api_connect(window.get(), NATIVE_WINDOW_API_CAMERA);
        if (result != NO_ERROR) {
            ALOGE("native_window_api_connect failed: %s (%d)", strerror(-result),
                    result);
            return result;
        }
    }
 
    // If preview has been already started, register preview buffers now.
    if (mHardware->previewEnabled()) {
        if (window != 0) {
            native_window_set_scaling_mode(window.get(),
                    NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
            native_window_set_buffers_transform(window.get(), mOrientation);
            result = mHardware->setPreviewWindow(window);
        }
    }
 
    if (result == NO_ERROR) {
        // Everything has succeeded.  Disconnect the old window and remember the
        // new window.
        disconnectWindow(mPreviewWindow);
        mSurface = binder;
        mPreviewWindow = window;
    } else {
        // Something went wrong after we connected to the new window, so
        // disconnect here.
        disconnectWindow(window);
    }
 
    return result;
}
cs


 분량이 다소 길어진 관계로 다음 포스팅에서 내용을 이어가도록 하겠습니다.


300x250

안드로이드 Framework 단계에서 Surface 생성과정

안드로이드/프레임워크 2015. 4. 30. 19:12

 안드로이드 기기에 있어 가장 중요한 부분이라 할 수 있는 것 중 하나가 바로 기기의 화면에 나오는 View들을 제대로 처리하는 것이라 생각합니다. 특히 역동적인 안드로이드 화면을 출력하기 위해서는 SurfaceView를 사용하게 됩니다. 이번 포스팅에서는 안드로이드 Framework 단계에서 Surface가 생성돠는 과정에 대해 살펴보도록 하겠습니다.


※본 예제는 Android의 Camera 동작 과정을 토대로 Surface가 동작하는 과정에 대해 다루었습니다.


/frameworks/base/core/java/android/hardware/Camera.java

1
2
3
4
5
6
7
    public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
        if (holder != null) {
            setPreviewDisplay(holder.getSurface());
        } else {
            setPreviewDisplay((Surface)null);
        }
    }
cs


 안드로이드의 카메라 Preview를 설정하는 과정에서 setPreviewDisplay() 함수를 통해 SurfaceView 클래스가 적용되고 있는 상황을 나타내고 있습니다. holder 내의 SurfaceView 클래스는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class SurfaceView extends View {
    static private final String TAG = "SurfaceView";
    static private final boolean DEBUG = false;
 
    final ArrayList<SurfaceHolder.Callback> mCallbacks
            = new ArrayList<SurfaceHolder.Callback>();
 
    final int[] mLocation = new int[2];
 
    final ReentrantLock mSurfaceLock = new ReentrantLock();
    final Surface mSurface = new Surface();       // Current surface in use
    final Surface mNewSurface = new Surface();    // New surface we are switching to
....
    /**
     * Return the SurfaceHolder providing access and control over this
     * SurfaceView's underlying surface.
     *
     * @return SurfaceHolder The holder of the surface.
     */
    public SurfaceHolder getHolder() {
        return mSurfaceHolder;
    }
....
 
    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
 
        private static final String LOG_TAG = "SurfaceHolder";
....
        @Override
        public Surface getSurface() {
            return mSurface;
        }
 
        @Override
        public Rect getSurfaceFrame() {
            return mSurfaceFrame;
        }
    };
}
cs


 위의 소스코드를 통해 holder가 mSurface를 return 하고 있고 mSurface는 new Surface()를 통해 클래스를 생성하고 있는 것을 확인하실 수 있습니다. 그렇다면 이번에는 Surface 클래스가 생성되는 과정을 살펴보도록 합시다.


/frameworks/base/core/java/android/view/Surface.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Surface implements Parcelable {
    private static final String TAG = "Surface";
....
    // Guarded state.
    final Object mLock = new Object(); // protects the native state
    private String mName;
    int mNativeObject; // package scope only for SurfaceControl access
....
    /**
     * Create an empty surface, which will later be filled in by readFromParcel().
     * @hide
     */
    public Surface() {
    }
....
    public void readFromParcel(Parcel source) {
        if (source == null) {
            throw new IllegalArgumentException("source must not be null");
        }
 
        synchronized (mLock) {
            // nativeReadFromParcel() will either return mNativeObject, or
            // create a new native Surface and return it after reducing
            // the reference count on mNativeObject.  Either way, it is
            // not necessary to call nativeRelease() here.
            mName = source.readString();
            setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
        }
    }
....
    private void setNativeObjectLocked(int ptr) {
        if (mNativeObject != ptr) {
            if (mNativeObject == 0 && ptr != 0) {
                mCloseGuard.open("release");
            } else if (mNativeObject != 0 && ptr == 0) {
                mCloseGuard.close();
            }
            mNativeObject = ptr;
            mGenerationId += 1;
        }
    }
....
}
 
cs


 맨 처음에 surface가 생성될 때에는 빈 Class 하나가 생성됩니다. 이후 readFromParcel()을 통하여 Surface 클래스 내에 mNativeObject의 값이 채워지게 되며 nativeReadFromParcel() 함수는 다음과 같다.


/frameworks/base/core/jni/android_view_Surface.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static jint nativeReadFromParcel(JNIEnv* env, jclass clazz,
        jint nativeObject, jobject parcelObj) {
    Parcel* parcel = parcelForJavaObject(env, parcelObj);
    if (parcel == NULL) {
        doThrowNPE(env);
        return 0;
    }
 
    sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
    sp<IBinder> binder(parcel->readStrongBinder());
 
    // update the Surface only if the underlying IGraphicBufferProducer
    // has changed.
    if (self != NULL
            && (self->getIGraphicBufferProducer()->asBinder() == binder)) {
        // same IGraphicBufferProducer, return ourselves
        return int(self.get());
    }
 
    sp<Surface> sur;
    sp<IGraphicBufferProducer> gbp(interface_cast<IGraphicBufferProducer>(binder));
    if (gbp != NULL) {
        // we have a new IGraphicBufferProducer, create a new Surface for it
        sur = new Surface(gbp, true);
        // and keep a reference before passing to java
        sur->incStrong(&sRefBaseOwner);
    }
 
    if (self != NULL) {
        // and loose the java reference to ourselves
        self->decStrong(&sRefBaseOwner);
    }
 
    return int(sur.get());
}
cs


 일단 지금은 이러한 방식으로 Surface가 생성되고 있구나 라고 감을 잡으시고 이해를 하고 넘어가셨으면 합니다. 위 코드를 확인해보고 예상컨데 Surface로 Parcel을 보내는 주체는 IGraphicBufferProduce를 넘겨주고 있는 듯 하다는 생각으로 진행하면 될 것으로 보입니다.



300x250