1. 2010.6 임베디드 인터넷 전화기 #
개발기간 32일 (2010년 5월 19일 ~ 2010년 6월 22일) ● 프로그램 요약 I. Dice(Dongguk ICE) Phone 이란? ▶ ARM 계열 프로세서와 터치스크린, 네트워크 접근이 가능한 장비에서 SIP 프로토콜를 기반으로 구현한 임베디드 리눅스 기반의 인터넷 전화기 프로그램이다. 인터넷이 연결되어 있는 상황이라면 일반 전화 쓰듯이 전화를 주고 받을 수 있다. II. 프로그램 개발 언어(SDK) 및 환경 ▶ 개발 언어(SDK) : C/C++ (Qt/Embedded 4) ▶ 개발 환경 : Code::Blocks, Fedora 12 호스트 : CPU Intel Core2Duo T5600 1.83GHz, 2GB RAM, 사용OS: 페도라 12 타겟 : PXA270 : Intel Xscale계열,clock speed ,520Mhz, DRAM : 128MB 컴파일러 : ARM-LINUX-GCC 3.3.3 III. 사용 환경 ▶ Embedded GNU/Linux (kernerl 2.6.X) IV. 주요 기능 ① 전화 걸기/받기 ▶ SIP 규격으로 초기화를 하고 국내 SIP 프록시 서버에 접근하여 전화를 걸고 받는다. 사용자가 입력한 번호로 전화를 걸거나 받으면서 로그를 출력. ② 전화번호 입력 ▶ 터치스크린을 눌러 전화번호를 입력.
[JPG image (21.16 KB)]
시작 시 이루어지는 작업으로 먼저 네트워크 연결을 위한 초기화 작업과 환경파일(/home/dicephonerc)에서 저장된 SIP 설정 정보를 읽어 들이고, 마지막으로 음성 코덱을 읽어오면 초기화가 완료 된다. 본 프로젝트에서 사용된 음성 코덱은 훌륭한 오픈 소스 음성 코덱인 speex와 PCMU/PCMA(G.711)을 사용하였며 실행 시스템에서 사용가능한 음성코덱은 모두 로드된다. 코덱을 모두 읽어 들이면 네트워크 상태(대역폭)에 따라 최적의 코덱이 자동으로 선택된다. speex를 사용하려면 arm계열로 크로스 컴파일 후 라이브러리에 등록을 해야 한다.
[JPG image (10.76 KB)]
지금은 SIP 인증은 따로 입력을 받지 않고 자동으로 이루어진다. 인증이 성공하면 SIP 로그에 "Registration on sip:<프록시 주소> success" 라는 메시지가 출력된다.
SIP 규격에 맞추어 프록시 서버에 인증 시도 - 인증 정보 요구 - 인증 정보 전송 등 인증 절차가 완료되면 통화가 가능하며 터치 스크린 혹은 LDS4000 Keypad(option)를 이용하여 번호를 입력 하고 통화를 연결한다.
SIP 규격에 맞추어 프록시 서버에 인증 시도 - 인증 정보 요구 - 인증 정보 전송 등 인증 절차가 완료되면 통화가 가능하며 터치 스크린 혹은 LDS4000 Keypad(option)를 이용하여 번호를 입력 하고 통화를 연결한다.
[JPG image (21.77 KB)]
[JPG image (16.51 KB)]
[JPG image (23.58 KB)]
[JPG image (22.69 KB)]
[JPG image (19.42 KB)]
전송 로그와 클라이언트의 일반 상태(General State) 로그를 보여준다. 이는 LDS 상에도 띄우는 것을 목표로 했었으나 구조적인 문제가 있었다. dicephone의 back-end를 이루는 linphone의 coreapi는 C로 구현되어 있고 수신시, 전송시, 인증 요청시 등 각 SIP 이벤트에 대한 호출 장소는 메인 핸들인 linphone 코어(LinphoneCore *)의 멤버 변수인 가상 함수 테이블 구조체에 의해 참조되는 콜백 함수로 정의되어 있는데, linphone coreapi를 C++로 변환하는 과정에서 메서드의 함수 포인터를 얻기 위해 클래스 안에서 정의되는 콜백 함수들은 정적 함수로 정의 하였으며 이 때문에 메인 위젯 클래스에 포함되는 Qt의 디자인 위젯 클래스 m_ui 멤버에 대한 접근이 불가능 하였다. 이는 언어의 차이에서 오는 구조적인 문제로 이를 해결하기 위해서는 구조를 상당부분 바꾸어야 하는 등 투자하는 비용대비 효과가 작기 때문에 프레임 버퍼상의 Qt 위젯이 아닌 콘솔 창에 로그를 출력하는 것으로 대신하였다.
[JPG image (62.83 KB)]
항목 별 설명
1. osip2: GNU의 SIP 라이브러리로 SIP 초기화와 컨트롤 인터페이스를 제공한다.
2. eXosip2: osip2의 확장 라이브러리
3. oRTP: 실시간 전송 프로토콜 라이브러리
4. mediastream2: linphone에서 사용하는 미디어 전송 라이브러리
5. linphone core: 보다 상위 응용 계층 수준으로 추상화된 SIP와 멀티미디어 인터페이스를 제공
6. dicephone: Qt4를 통한 인터페이스, linphone core의 front-end 구성
1. osip2: GNU의 SIP 라이브러리로 SIP 초기화와 컨트롤 인터페이스를 제공한다.
2. eXosip2: osip2의 확장 라이브러리
3. oRTP: 실시간 전송 프로토콜 라이브러리
4. mediastream2: linphone에서 사용하는 미디어 전송 라이브러리
5. linphone core: 보다 상위 응용 계층 수준으로 추상화된 SIP와 멀티미디어 인터페이스를 제공
6. dicephone: Qt4를 통한 인터페이스, linphone core의 front-end 구성
mediastream2 이하 모든 라이브러리는 적절한 소스 수정과 컴파일 옵션을 통해 arm 용으로 크로스 컴파일 하여야 한다.
버전 정보
speex >= 1.2rc , ortp >= 0.16.3 (qt4 >= 4.5.3)
osip2 >= 3.1.0, eXosip2 >= 3.1.0
speex >= 1.2rc , ortp >= 0.16.3 (qt4 >= 4.5.3)
osip2 >= 3.1.0, eXosip2 >= 3.1.0
[JPG image (38.32 KB)]
마지막.
(※음성 코덱 테스트하며 생긴 기록 )
[JPG image (324.09 KB)]
(※음성 코덱 테스트하며 생긴 기록 )
- 코드 샘플 #1
- "임베디드 인터넷 전화기" 프로젝트 코드 샘플:
프론트엔드를 이루는 Qt 메인 위젯 부분
#include <QApplication> #include <QString> #include <QFile> #include <QTimer> #include <QTime> #include <QDir> #include "dicewidget.h" DiceWidget::DiceWidget(QWidget *parent) : QFrame(parent) { m_ui.setupUi(this); // Timer m_pTimer = new QTimer(this); m_pTimerCall = new QTimer(this); connect(m_pTimer, SIGNAL(timeout()), this, SLOT(on_timer())); connect(m_pTimerCall, SIGNAL(timeout()), this, SLOT(on_timerCall())); // Dicecore initializing this_program_ident_string=strdup("dicephone_ident_string=1.0.0"); qWarning(this_program_ident_string); m_ui.systemLog->appendPlainText(this_program_ident_string); vtable.show=linphone_qt_show; vtable.inv_recv=linphone_qt_inv_recv; vtable.bye_recv=linphone_qt_bye_recv; vtable.notify_presence_recv=linphone_qt_notify_recv; vtable.new_unknown_subscriber=linphone_qt_new_unknown_subscriber; vtable.auth_info_requested=linphone_qt_auth_info_requested; vtable.display_status=linphone_qt_display_status; vtable.display_message=linphone_qt_display_message; vtable.display_warning=linphone_qt_display_warning; vtable.display_url=linphone_qt_display_url; vtable.display_question=linphone_qt_display_question; vtable.call_log_updated=linphone_qt_call_log_updated; vtable.text_received=linphone_qt_text_received; vtable.general_state=linphone_qt_general_state; vtable.refer_received=linphone_qt_refer_received; vtable.buddy_info_updated=linphone_qt_buddy_info_updated; dicephone_init(); } DiceWidget::~DiceWidget() { if (m_pTimer->isActive()) m_pTimer->stop(); linphone_core_destroy(the_core); free(progpath); } void DiceWidget::on_close_clicked() { close(); } void DiceWidget::on_key1_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="1"; m_ui.displayPad->setText(str); } void DiceWidget::on_key2_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="2"; m_ui.displayPad->setText(str); } void DiceWidget::on_key3_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="3"; m_ui.displayPad->setText(str); } void DiceWidget::on_key4_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="4"; m_ui.displayPad->setText(str); } void DiceWidget::on_key5_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="5"; m_ui.displayPad->setText(str); } void DiceWidget::on_key6_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="6"; m_ui.displayPad->setText(str); } void DiceWidget::on_key7_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="7"; m_ui.displayPad->setText(str); } void DiceWidget::on_key8_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="8"; m_ui.displayPad->setText(str); } void DiceWidget::on_key9_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="9"; m_ui.displayPad->setText(str); } void DiceWidget::on_key0_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="0"; m_ui.displayPad->setText(str); } void DiceWidget::on_keyAsta_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="*"; m_ui.displayPad->setText(str); } void DiceWidget::on_keySharp_clicked() { QString str=m_ui.displayPad->toPlainText(); str+="#"; m_ui.displayPad->setText(str); str.sprintf("audio port=%d, UDP port=%d", linphone_core_get_audio_port(the_core), linphone_core_get_sip_port(the_core)); m_ui.systemLog->appendPlainText(str); } void DiceWidget::on_keyCall_clicked() { if (linphone_core_in_call(the_core) == TRUE) return; linphone_qt_start_call(m_ui.displayPad->toPlainText().toAscii()); m_ui.displayPad->setText("Calling ...\nDuration 00:00"); m_pTimerCall->start(1000); } void DiceWidget::on_keyQuit_clicked() { if (linphone_core_in_call(the_core) == TRUE) linphone_core_terminate_call(the_core,addr_to_call); else m_ui.displayPad->setText(""); } void DiceWidget::on_timer(){ linphone_qt_iterate(); } void DiceWidget::on_timerCall(){ static QTime duration(0, 0); static QTime t; static int sec = 0; if (linphone_core_in_call(the_core) == TRUE){ QString str = "Calling ...\nDuration "; t = duration.addSecs(sec++); m_ui.displayPad->setText(str + t.toString("mm:ss")); } else{ sec=0; duration.restart(); //m_pTimerCall->stop(); } } const char *DiceWidget::linphone_qt_get_config_file(){ /*try accessing a local file first if exists*/ if (access(CONFIG_FILE,F_OK)==0){ snprintf(_config_file,sizeof(_config_file),"%s",CONFIG_FILE); }else{ #ifdef WIN32 const char *appdata=getenv("APPDATA"); if (appdata){ snprintf(_config_file,sizeof(_config_file),"%s\\%s",appdata,LINPHONE_CONFIG_DIR); CreateDirectory(_config_file,NULL); snprintf(_config_file,sizeof(_config_file),"%s\\%s",appdata,LINPHONE_CONFIG_DIR "\\" CONFIG_FILE); } #else const char *home=getenv("HOME"); if (home==NULL) home="."; snprintf(_config_file,sizeof(_config_file),"%s/%s",home,CONFIG_FILE); #endif } return _config_file; } #define FACTORY_CONFIG_FILE "dicephonerc.factory" const char *DiceWidget::linphone_qt_get_factory_config_file(){ /*try accessing a local file first if exists*/ if (access(FACTORY_CONFIG_FILE,F_OK)==0){ snprintf(_factory_config_file,sizeof(_factory_config_file), "%s",FACTORY_CONFIG_FILE); } else { char *progdir; if (progpath != NULL) { char *basename; progdir = strdup(progpath); #ifdef WIN32 basename = strrchr(progdir, '\\'); if (basename != NULL) { basename ++; *basename = '\0'; snprintf(_factory_config_file, sizeof(_factory_config_file), "%s\\..\\%s", progdir, FACTORY_CONFIG_FILE); } else { if (workingdir!=NULL) { snprintf(_factory_config_file, sizeof(_factory_config_file), "%s\\%s", workingdir, FACTORY_CONFIG_FILE); } else { free(progdir); return NULL; } } #else basename = strrchr(progdir, '/'); if (basename != NULL) { basename ++; *basename = '\0'; snprintf(_factory_config_file, sizeof(_factory_config_file), "%s/../share/dicephone/%s", progdir, FACTORY_CONFIG_FILE); } else { free(progdir); return NULL; } #endif free(progdir); } } return _factory_config_file; } LinphoneCore *DiceWidget::linphone_qt_get_core(void){ return the_core; } void DiceWidget::linphone_qt_iterate(){ LinphoneCore *lc=the_core; static bool_t first_time=TRUE; static bool_t in_iterate=FALSE; /*avoid reentrancy*/ if (in_iterate) return; in_iterate=TRUE; linphone_core_iterate(lc); if (first_time){ /*after the first call to iterate, SipSetupContexts should be ready, so take actions:*/ //linphone_qt_show_directory_search(); first_time=FALSE; } in_iterate=FALSE; } void DiceWidget::linphone_qt_init_liblinphone(const char *config_file, const char *factory_config_file) { linphone_core_set_user_agent("Dicephone", DICEPHONE_VERSION); the_core=linphone_core_new(&vtable,config_file,NULL,NULL); linphone_core_set_waiting_callback(the_core,linphone_qt_wait,NULL); } bool DiceWidget::linphone_qt_start_call_do(const char *uri){ const char *entered=uri; if (linphone_core_invite(linphone_qt_get_core(),entered)==0) { qWarning(entered); m_ui.systemLog->appendPlainText(entered); }else{ qWarning("term"); m_ui.systemLog->appendPlainText("term"); } return FALSE; } void DiceWidget::linphone_qt_accept_call(){ LinphoneCore *lc=linphone_qt_get_core(); // todo: indicate incoming call .. --- Qt linphone_core_accept_call(lc,NULL); //linphone_qt_call_started(); //ltodo inphone_qt_enable_mute_button() --- Qt } void DiceWidget::linphone_qt_start_call(const char *uri){ LinphoneCore *lc=linphone_qt_get_core(); char *full_addr=(char*)malloc(sizeof(char)*64); sprintf(full_addr,"sip:%s@proxy.samsung070.com",uri); if (linphone_core_inc_invite_pending(lc)){ /*accept the call*/ linphone_qt_accept_call(); }else if (linphone_core_in_call(lc)) { /*already in call */ }else{ /*change into in-call mode, then do the work later as it might block a bit */ // started ---- Qt linphone_qt_start_call_do(full_addr); } } void DiceWidget::linphone_qt_terminate_call(){ linphone_core_terminate_call(linphone_qt_get_core(),NULL); qWarning("terminate"); m_ui.systemLog->appendPlainText("terminate"); } void DiceWidget::linphone_qt_decline_call(){ linphone_core_terminate_call(linphone_qt_get_core(),NULL); qWarning("decline"); m_ui.systemLog->appendPlainText("decline"); } void DiceWidget::linphone_qt_set_audio_only(){ linphone_core_enable_video(linphone_qt_get_core(),FALSE,FALSE); linphone_core_enable_video_preview(linphone_qt_get_core(),FALSE); } /* Interface */ void DiceWidget::linphone_qt_show(LinphoneCore *lc){ } void DiceWidget::linphone_qt_inv_recv(LinphoneCore *lc, const char *from){ qWarning("inv_recv"); } void DiceWidget::linphone_qt_bye_recv(LinphoneCore *lc, const char *from){ qWarning("bye_recv"); } void DiceWidget::linphone_qt_notify_recv(LinphoneCore *lc, LinphoneFriend * fid){ qWarning("notify_recv"); } void DiceWidget::linphone_qt_new_unknown_subscriber(LinphoneCore *lc, LinphoneFriend *lf, const char *url){ qWarning("unknown_subscriber"); } void DiceWidget::linphone_qt_auth_info_requested(LinphoneCore *lc, const char *realm, const char *username){ // LinphoneAuthInfo *info = linphone_auth_info_new("07070179658", NULL, NULL, NULL, "proxy.samsung070.com"); qWarning("auth info requested"); LinphoneAuthInfo *info = linphone_auth_info_new(username, NULL, NULL, NULL, realm); linphone_auth_info_set_passwd(info, "10020851"); linphone_core_add_auth_info(lc,info); qWarning("added auth info"); } void DiceWidget::linphone_qt_display_status(LinphoneCore *lc, const char *status){ qWarning(status); } void DiceWidget::linphone_qt_display_message(LinphoneCore *lc, const char *msg){ qWarning(msg); } void DiceWidget::linphone_qt_display_warning(LinphoneCore *lc, const char *warning){ qWarning(warning); } void DiceWidget::linphone_qt_display_url(LinphoneCore *lc, const char *msg, const char *url){ // TODO: DISPLAY INFO MESSAGE BOX uri char richtext[4096]; snprintf(richtext,sizeof(richtext),"%s %s",msg,url); qWarning(richtext); } void DiceWidget::linphone_qt_display_question(LinphoneCore *lc, const char *question){ // TODO: DISPLAY MESSAGE BOX QUESTION //linphone_qt_display_something(GTK_MESSAGE_QUESTION,question); } void DiceWidget::linphone_qt_call_log_updated(LinphoneCore *lc, LinphoneCallLog *cl){ // call log qWarning(linphone_call_log_to_str(cl)); } void DiceWidget::linphone_qt_text_received(LinphoneCore *lc, LinphoneChatRoom *room, const char *from, const char *message){ // call log qWarning(from); qWarning(message); } void DiceWidget::linphone_qt_general_state(LinphoneCore *lc, LinphoneGeneralState *gstate){ // bool show_general_state if (1) { switch(gstate->new_state) { case GSTATE_POWER_OFF: qWarning("GSTATE_POWER_OFF"); break; case GSTATE_POWER_STARTUP: qWarning("GSTATE_POWER_STARTUP"); break; case GSTATE_POWER_ON: qWarning("GSTATE_POWER_ON"); break; case GSTATE_POWER_SHUTDOWN: qWarning("GSTATE_POWER_SHUTDOWN"); break; case GSTATE_REG_NONE: qWarning("GSTATE_REG_NONE"); break; case GSTATE_REG_OK: qWarning("GSTATE_REG_OK"); break; case GSTATE_REG_FAILED: qWarning("GSTATE_REG_FAILED"); break; case GSTATE_CALL_IDLE: qWarning("GSTATE_CALL_IDLE"); break; case GSTATE_CALL_OUT_INVITE: qWarning("GSTATE_CALL_OUT_INVITE"); break; case GSTATE_CALL_OUT_CONNECTED: qWarning("GSTATE_CALL_OUT_CONNECTED"); break; case GSTATE_CALL_IN_INVITE: qWarning("GSTATE_CALL_IN_INVITE"); break; case GSTATE_CALL_IN_CONNECTED: qWarning("GSTATE_CALL_IN_CONNECTED"); break; case GSTATE_CALL_END: qWarning("GSTATE_CALL_END"); break; case GSTATE_CALL_ERROR: qWarning("GSTATE_CALL_ERROR"); break; default: printf("GSTATE_UNKNOWN_%d",gstate->new_state); } if (gstate->message) qWarning(" %s", gstate->message); printf("\n"); } } void DiceWidget::linphone_qt_refer_received(LinphoneCore *lc, const char *refer_to){ qWarning("refer_received"); } void DiceWidget::linphone_qt_buddy_info_updated(LinphoneCore *lc, LinphoneFriend *lf){ } void DiceWidget::linphone_qt_load_identities(){ const MSList *elem; LinphoneProxyConfig *def=NULL; int def_index=0, i; if (linphone_core_get_proxy_config_list(linphone_qt_get_core()) == NULL){ def=linphone_proxy_config_new(); linphone_proxy_config_edit(def); linphone_proxy_config_set_server_addr(def, "sip:proxy.samsung070.com"); linphone_proxy_config_set_identity(def,"sip:07070179658@proxy.samsung070.com"); linphone_core_add_proxy_config(linphone_qt_get_core(),def); } //linphone_core_set_default_proxy(the_core, def); for(i=1,elem=linphone_core_get_proxy_config_list(linphone_qt_get_core()); elem!=NULL; elem=ms_list_next(elem),i++){ LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)elem->data; qWarning(linphone_proxy_config_get_identity(cfg)); linphone_proxy_config_enable_register(cfg, TRUE); if (cfg==def) { def_index=i; } } qWarning("LOAD IDENTITIES"); m_ui.systemLog->appendPlainText("LOAD IDENTITIES"); } bool DiceWidget::linphone_qt_can_manage_accounts(){ LinphoneCore *lc=linphone_qt_get_core(); const MSList *elem; for(elem=linphone_core_get_sip_setups(lc);elem!=NULL;elem=elem->next){ SipSetup *ss=(SipSetup*)elem->data; if (sip_setup_get_capabilities(ss) & SIP_SETUP_CAP_ACCOUNT_MANAGER){ return TRUE; } } return FALSE; } void DiceWidget::linphone_qt_manage_login(){ LinphoneCore *lc=linphone_qt_get_core(); LinphoneProxyConfig *cfg=NULL; linphone_core_get_default_proxy(lc,&cfg); if (cfg){ SipSetup *ss=linphone_proxy_config_get_sip_setup(cfg); if (ss && (sip_setup_get_capabilities(ss) & SIP_SETUP_CAP_LOGIN)){ //linphone_qt_show_login_frame(cfg); qWarning("Log in frame"); } } } void DiceWidget::linphone_qt_init_main_window(){ linphone_qt_load_identities(); qWarning("get presence info"); m_ui.systemLog->appendPlainText("get presence info"); qWarning(linphone_online_status_to_string(linphone_core_get_presence_info(the_core))); m_ui.systemLog->appendPlainText(linphone_online_status_to_string(linphone_core_get_presence_info(the_core))); } void DiceWidget::linphone_qt_close(){ #if 0 /*shutdown call if any*/ if (linphone_core_in_call(lc)){ linphone_core_terminate_call(lc,NULL); // linphone_qt_call_terminated(NULL); } #endif } void * DiceWidget::linphone_qt_wait(LinphoneCore *lc, void *ctx, LinphoneWaitingState ws, const char *purpose, float progress){ } void DiceWidget::linphone_qt_log_handler(OrtpLogLevel lev, const char *fmt, va_list args){ if (0){ const char *lname="undef"; char *msg; va_list cap;//copy of our argument list: a va_list cannot be re-used (SIGSEGV on linux 64 bits) switch(lev){ case ORTP_DEBUG: lname="debug"; break; case ORTP_MESSAGE: lname="message"; break; case ORTP_WARNING: lname="warning"; break; case ORTP_ERROR: lname="error"; break; case ORTP_FATAL: lname="fatal"; break; default: qWarning("Bad level !"); } va_copy(cap,args); vsprintf(msg,fmt,cap); va_end(cap); qWarning("linphone-%s : %s",lname,msg); free(msg); } //linphone_qt_log_push(lev,fmt,args); } void DiceWidget::linphone_qt_check_soundcards(){ const char **devices=linphone_core_get_sound_devices(linphone_qt_get_core()); if (devices==NULL || devices[0]==NULL){ qWarning("No sound cards have been detected on this computer.\nYou won't be able to send or receive audio calls."); } } void DiceWidget::linphone_qt_set_codec(){ PayloadType *pt=NULL; LinphoneCore *lc=linphone_qt_get_core(); MSList *codec_list=ms_list_copy(linphone_core_get_audio_codecs(lc)); linphone_core_set_audio_codecs(lc,codec_list); linphone_qt_show_codecs(codec_list); //linphone_qt_select_codec(v,pt); } void DiceWidget::linphone_qt_show_codecs(const MSList *codeclist){ const MSList *elem=NULL; int rate=0; float bitrate=0.0; const char *params=""; QString str; struct _PayloadType *pt=NULL; m_ui.systemLog->appendPlainText("Codec Load"); for(elem=codeclist; elem!=NULL; elem=elem->next){ pt=(struct _PayloadType *)elem->data; linphone_core_payload_type_enabled(linphone_qt_get_core(),pt); if (linphone_core_check_payload_type_usability(linphone_qt_get_core(),pt)) qWarning("usable"); else qWarning("unusable"); /* get an iterator */ bitrate=payload_type_get_bitrate(pt)/1000.0; rate=payload_type_get_rate(pt); if (pt->recv_fmtp!=NULL) params=pt->recv_fmtp; qWarning(payload_type_get_mime(pt)); str.sprintf("%s ... Loaded",payload_type_get_mime(pt)); m_ui.systemLog->appendPlainText(str); } } void DiceWidget::dicephone_init(){ const char *config_file; const char *factory_config_file; const char *lang; progpath = strdup(QApplication::applicationDirPath().toAscii()); config_file=linphone_qt_get_config_file(); /*for pulseaudio:*/ //g_setenv("PULSE_PROP_media.role", "phone", TRUE); /* Now, look for the factory configuration file, we do it this late since we want to have had time to change directory and to parse the options, in case we needed to access the working directory */ factory_config_file = linphone_qt_get_factory_config_file(); qWarning("factory_config"); m_ui.systemLog->appendPlainText("factory_config"); qWarning(factory_config_file); m_ui.systemLog->appendPlainText(factory_config_file); if (linphone_core_wake_up_possible_already_running_instance( config_file, NULL) == 0){ qWarning("Another running instance of linphone has been detected. It has been woken-up."); qWarning("This instance is going to exit now."); exit(1); } linphone_core_enable_logs_with_cb(linphone_qt_log_handler); linphone_qt_init_liblinphone(config_file, NULL); /* do not lower timeouts under 30 ms because it exhibits a bug on gtk+/win32, with cpu running 20% all the time...*/ m_pTimer->start(30); strcpy(addr_to_recv,""); linphone_qt_init_main_window(); linphone_qt_check_soundcards(); linphone_qt_set_audio_only(); linphone_qt_set_codec(); //linphone_core_set_ringer_device(the_core, "/dev/dsp"); //linphone_core_set_playback_device(the_core, "/dev/dsp"); //linphone_core_set_capture_device(the_core, "/dev/dsp"); linphone_core_set_ringback(the_core, "/mym/shere/ringback.wav"); linphone_core_set_play_level(the_core, 60); linphone_core_set_ring_level(the_core, 60); //linphone_core_enable_echo_cancellation(the_core, TRUE); qWarning("init end"); m_ui.systemLog->appendPlainText("init end"); }
2. 2010.7 퀄리스타.ITS #
[JPG image (36.16 KB)]
개발기간 60일 (2010년 7월 1일 ~ 2010년 8월 31일) ● 프로그램 요약 I. 퀄리스타.ITS 란? ▶ 프로젝트를 진행하다 보면, 크고 작은 이슈들에 직면하게 되는데 프로젝트에서 발생하는 버그 및 이슈를 추적, 관리하는 프로그램이다. 이슈 추적시스템(ITS)은 Mantis, Trac, Bugzilla 등 여러 종류가 있지만 퀄리스타.ITS는 국내 환경에 맞추어 사용자 편의와 보다 빠르고 쉬운 작업을 목적으로 비전문가들도 쉽게 사용할 수 있도록 제작되었다. II. 프로그램 개발 언어 및 환경 ▶ 개발 언어 : C#.NET 3.5 (Winform) ▶ 개발 환경 : Visual Studio 2010, Windows 7 UltimateK III. 사용 환경 ▶ Windows XP, Windows 7 (32bit) IV. 주요 기능 ① 이슈추적 ② 체크리스트 ③ 오프라인 동기화 작업지원 V. 담당 업무 UI 개발 담당. (커스텀 리스트 뷰, 이슈 등록, 데이터 동기화 상태 변화, 첨부파일 추가 및 파일 전송 컴포넌트, 체크리스트 프리셋 내보내기/가져오기 등.)
[JPG image (168.47 KB)]
[JPG image (169.76 KB)]
[JPG image (133.1 KB)]
- 코드 샘플 #2
- "퀄리스타 ITS" 프로젝트 코드 샘플:
옵저버 패턴이 적용 된 유저 컨트롤(파일첨부) 부분.
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using Qualista.ITS.Items; using ComponentFactory.Krypton.Toolkit; namespace Qualista.ITS.UserControls { public partial class UcAttachFileEx : UserControl { public bool FileViewOnly { get; set; } public UcAttachFileEx() { InitializeComponent(); initContextMenu(); initLanguage(); } private void initContextMenu() { // contextMenu 1 KryptonContextMenuItems contextItems1 = new KryptonContextMenuItems(); contextItems1.Items.Add(new KryptonContextMenuItem(Lang.Instance.GetText("open"), openFile_ContextEvent)); contextItems1.Items.Add(new KryptonContextMenuItem(Lang.Instance.GetText("folder_open"), openFolder_ContextEvent)); contextItems1.Items.Add(new KryptonContextMenuSeparator()); contextItems1.Items.Add(new KryptonContextMenuItem(Lang.Instance.GetText("delete"), (sender, e) => deleteSelectedItem())); contextItems1.Items.Add(new KryptonContextMenuItem(Lang.Instance.GetText("delete_all"), (sender, e) => Clear())); kryptonContextMenu1.Items.Add(contextItems1); // contextMenu 2 KryptonContextMenuItems contextItems2 = new KryptonContextMenuItems(); contextItems2.Items.Add(new KryptonContextMenuItem(Lang.Instance.GetText("open"), openFile_ContextEvent)); contextItems2.Items.Add(new KryptonContextMenuItem(Lang.Instance.GetText("folder_open"), openFolder_ContextEvent)); kryptonContextMenu2.Items.Add(contextItems2); } private void initLanguage() { columnHeader1.Text = Lang.Instance.GetText("name"); columnHeader2.Text = Lang.Instance.GetText("size"); columnHeader3.Text = Lang.Instance.GetText("location"); } private void openFile_ContextEvent(object sender, EventArgs e) { openFile(listView1.SelectedItems[0].SubItems[2].Text); } private void openFolder_ContextEvent(object sender, EventArgs e) { System.Diagnostics.Process.Start("explorer.exe", System.IO.Path.GetDirectoryName(listView1.SelectedItems[0].SubItems[2].Text)); } private void deleteSelectedItem() { foreach (ListViewItem item in listView1.SelectedItems) { listView1.Items.Remove(item); } } private void openFile(string filename) { try { System.Diagnostics.Process process = new System.Diagnostics.Process(); string myDocumentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); process.StartInfo.FileName = filename; process.StartInfo.CreateNoWindow = true; process.Start(); } catch (Exception ex) { MessageBox.Show(ex.Message); } } public void AttachFile(string file) { if (listView1.FindItemWithText(file) != null) return; System.IO.FileInfo f = new System.IO.FileInfo(file); FileSize fs = new FileSize(Convert.ToDouble(f.Length)); string filename = System.IO.Path.GetFileName(file); if (fs.MB > 2.0d) { string message = string.Format(Lang.Instance.GetText("attachfile_msg1") + "\n\n{0}({1})", filename, fs.ToString()); MessageBox.Show(message); return; } ListViewItem item = new ListViewItem(); item.Text = filename; item.SubItems.Add(fs.ToString()); item.SubItems.Add(file); item.Tag = Convert.ToDouble(f.Length); listView1.Items.Add(item); } public int LoadFiles(List<IssueFileItem> fileItems) { if (fileItems == null) return -1; foreach (IssueFileItem fileItem in fileItems) { ListViewItem item = new ListViewItem(); item.Text = fileItem.FileName; item.SubItems.Add(new FileSize(Convert.ToDouble(fileItem.FileSize)).ToString()); item.SubItems.Add(fileItem.LocalLocation); item.Tag = Convert.ToDouble(fileItem.FileSize.Length); listView1.Items.Add(item); } return fileItems.Count; } /// <summary> /// 컨트롤이 가지고 있는 파일 아이템 리턴 /// </summary> /// <returns></returns> public List<IssueFileItem> GetFileItems() { List<IssueFileItem> items = new List<IssueFileItem>(); foreach (ListViewItem listItem in listView1.Items) { IssueFileItem item = new IssueFileItem(); item.FileName = listItem.Text; item.FileSize = listItem.Tag.ToString(); item.LocalLocation = listItem.SubItems[2].Text; items.Add(item); } return items; } public void Clear() { foreach (ListViewItem item in listView1.Items) { listView1.Items.Remove(item); } } private void listView1_DragDrop(object sender, DragEventArgs e) { if (FileViewOnly != true && e.Data.GetDataPresent(DataFormats.FileDrop)) { string[] strFiles = e.Data.GetData(DataFormats.FileDrop) as string[]; foreach (string strFile in strFiles) { AttachFile(strFile); } } } private void listView1_DragOver(object sender, DragEventArgs e) { if (FileViewOnly != true && e.Data.GetDataPresent(DataFormats.FileDrop)) { e.Effect = DragDropEffects.Copy; } } /// <summary> /// 드래그 중 ESC 키 입력시 취소 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void listView1_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) { if (e.EscapePressed) { e.Action = DragAction.Cancel; } } private void listView1_Resize(object sender, EventArgs e) { if (FileViewOnly) { columnHeader3.Width = this.Width - 290; } } public void ResizeTo(int width) { columnHeader3.Width = width - 280; listView1.Width = width; } /// <summary> /// Delete 키로 파일 아이템 하나 삭제 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void listView1_KeyDown(object sender, KeyEventArgs e) { if (FileViewOnly != true) { ListView control = (ListView)sender; if (e.KeyCode == Keys.Delete) { deleteSelectedItem(); } } } /// <summary> /// 컬럼 헤더 클릭 시 정렬 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void listView1_ColumnClick(object sender, ColumnClickEventArgs e) { if (this.listView1.Sorting == SortOrder.Ascending) { listView1.ListViewItemSorter = new ListViewItemComparer(e.Column, "asc"); listView1.Sorting = SortOrder.Ascending; listView1.Sort(); listView1.Sorting = SortOrder.Descending; } else { listView1.ListViewItemSorter = new ListViewItemComparer(e.Column, "des"); listView1.Sorting = SortOrder.Descending; listView1.Sort(); listView1.Sorting = SortOrder.Ascending; } } private void listView1_DoubleClick(object sender, EventArgs e) { if (FileViewOnly) openFile((sender as ListView).SelectedItems[0].SubItems[2].Text); } private void listView1_MouseClick(object sender, MouseEventArgs e) { if (e.Button == System.Windows.Forms.MouseButtons.Right && this.FileViewOnly == true) { kryptonContextMenu2.Show(sender); } else if (e.Button == System.Windows.Forms.MouseButtons.Right && this.FileViewOnly == false) { kryptonContextMenu1.Show(sender); } } } #region ListViewItemComparer class ListViewItemComparer : IComparer { private int _col; private string _sort = "asc"; public ListViewItemComparer() { _col = 0; } public ListViewItemComparer(int column, string sort) { _col = column; this._sort = sort; } public int Compare(object x, object y) { if (_sort == "asc") { if (_col == 1) { string xLength = (x as ListViewItem).Tag.ToString(); string yLength = (y as ListViewItem).Tag.ToString(); if (_sort == "asc") return ns.StringLogicalComparer.Compare(xLength, yLength); else return ns.StringLogicalComparer.Compare(yLength, xLength); } else return ns.StringLogicalComparer.Compare(((ListViewItem)x).SubItems[_col].Text, ((ListViewItem)y).SubItems[_col].Text); } else { if (_col == 1) { string xLength = (x as ListViewItem).Tag.ToString(); string yLength = (y as ListViewItem).Tag.ToString(); if (_sort == "des") return ns.StringLogicalComparer.Compare(yLength, xLength); else return ns.StringLogicalComparer.Compare(xLength, yLength); } else return ns.StringLogicalComparer.Compare(((ListViewItem)y).SubItems[_col].Text, ((ListViewItem)x).SubItems[_col].Text); } } } #endregion #region FileSize public class FileSize { public enum UNITS { B, KB, MB, GB, TB } double b = 0; double kb = 0; double mb = 0; double gb = 0; double tb = 0; public double B { get { return this.b; } set { this.b = value; this.kb = this.b / 1024; this.mb = this.kb / 1024; this.gb = this.mb / 1024; this.tb = this.gb / 1024; } } public double KB { get { return this.kb; } set { this.kb = value; this.B = this.kb * 1024; } } public double MB { get { return this.mb; } set { this.mb = value; this.KB = this.mb * 1024; } } public double GB { get { return this.gb; } set { this.gb = value; this.MB = this.gb * 1024; } } public double TB { get { return this.tb; } set { this.tb = value; this.GB = this.tb * 1024; } } public FileSize(double size) { this.B = size; } public override string ToString() { string ret = string.Empty; if (b < 1024D) { ret = string.Format("{0} {1}", Math.Round(b, 2), "B"); } else if (kb < 1024D) { ret = string.Format("{0} {1}", Math.Round(kb, 2), "KB"); } else if (mb < 1024D) { ret = string.Format("{0} {1}", Math.Round(mb, 2), "MB"); } else if (gb < 1024D) { ret = string.Format("{0} {1}", Math.Round(gb, 2), "GB"); } else { ret = string.Format("{0} {1}", Math.Round(tb, 2), "TB"); } return ret; } } #endregion }
3. 2010.12 안드로이드 기반 모니터 좌표 인식 데이터 전송 애플리케이션 #
[PNG image (1.32 MB)]
● 프로그램 요약 I. Aero Snap 이란? ▶ 안드로이드 카메라를 통해 모니터 화면을 표시하면 그 좌표에 위치한 오브젝트(파일, 사진, 글, 주소창 주소 등)를 Wi-Fi를 통해 안드로이드로 가져오는 데이터 전송 애플리케이션. USB가 필요한 데이터 매니저와 달리 무선으로 스마트폰과 PC의 파일을 교환할 수 있다는 장점을 지니며, 촬용을 통해 사용자가 쉽게 원하는 파일을 주고 받을 수 있는 장점을 가진다. II. 프로그램 개발 언어(SDK) 및 환경 ▶ 개발 언어(SDK) : (Android)Java, (PC)C#.NET 4.0 (Win32API, OpenCV) ▶ 개발 환경 : Eclipse Helios, Visual Studio 2010, Windows 7 UltimateK III. 사용 환경 ▶ Android : Android 2.1 ▶ PC : Windows XP, Windows 7 (32bit) V. 주요 기능 ① 안드로이드와 같은 네트워크의 PC 검색 ▶ 안드로이드와 같은 공유기에 연결된 PC를 검색해 Aero Snap PC가 실행되어 접속을 기다리고 있는 PC를 출력한다. ② 사진을 통한 좌표 인식 ▶ 클라이언트에서 사진 촬영을 하면 셔터가 닫히기 직전 서버(PC)에서 순간적으로 좌표 인식을 돕기 위한 마커를 표시하며 표시된 마커를 통해 좌표를 인식한다. 검색 알고리즘은 정규화 과정을 거친 템플리트 매칭 알고리즘을 사용한다.
[JPG image (428.42 KB)]
그림 1. 인식용 마커. 마커는 GDI+로 직접 그렸다. 패턴은 임의로 그렸는데 나중에 인식률을 높일 수 있는 마커를 더 연구해 볼 수 있겠다.
단, 파란색과 빨간색 사각형은 좌표 인식 과정에 빠질 수 없는 성분이며 정규화에 사용된다. 정규화는 몇 가지 절차를 거치는데 크게 먼저 카메라 distortion을 보정(undistortion)하고, 허프 변환을 통해 빨간색 사각형의 직선 성분을 검출하여 회전을 보정한 다음, 파란색 사각형을 레퍼런스 사각형으로 삼아 크기를 정규화(size normalization)한다.
[JPG image (315.35 KB)]
그림 2. 안드로이드 RAW 영상
[JPG image (3.26 KB)]
그림 3. 정규화 과정을 거친 이미지