1. 程式人生 > >用osip2+eXosip2+ortp+mediastreamer實現的linux簡易軟電話原始碼(可實現通話)

用osip2+eXosip2+ortp+mediastreamer實現的linux簡易軟電話原始碼(可實現通話)

編譯命令:
gcc -losip2 -leXosip2 -lmediastreamer -lpthread -o my_exosip_phone my_exosip_phone.c

my_exosip_phone.c如下:
#include <eXosip2/eXosip.h>
#include <osip2/osip_mt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <mediastreamer2/mediastream.h>
#include <pthread.h>


#define WAIT_TIMER 200
#define REG_TIMER 30*1000

int doing;
int rtp_port;
int dtmfing, calling,picked;
int call_id ,dialog_id;

char *dtmf_str="this is dtmf";
//CString dtmf_str;
//char *server_url="192.168.18.249";
char *server_url="192.168.30.130";
char *server_port="5060";
char *local_port="5000";
char *username="2001";
char *password="2001";
char *telNum="2002";

AudioStream *audio = NULL;
RtpProfile *profile = NULL;
RtpSession *session = NULL;
OrtpEvQueue *q = NULL;


int build_media(int local_port, const char *remote_ip, int remote_port, int payload, const char *fmtp, int jitter, int ec, int bitrate)
{
if(payload != 0 && payload != 8)
{
/* 目前僅支援0,8 711ulaw,711alaw */
return -1;
}

PayloadType *pt;

profile = rtp_profile_clone_full(&av_profile);
q = ortp_ev_queue_new();

pt = rtp_profile_get_payload(profile, payload);
if (pt==NULL)
{
printf("codec error!");
return -1;
}

if (fmtp != NULL) payload_type_set_send_fmtp(pt, fmtp);
if (bitrate > 0) pt->normal_bitrate = bitrate;

if (pt->type != PAYLOAD_VIDEO)
{
printf("audio stream start!");
audio = audio_stream_start(profile, local_port, remote_ip, remote_port, payload, jitter, ec);
if (audio)
{
session = audio->session;
}
else
{
printf("session error!");
return -1;
}
}
else
{
printf("codec select error!");
return -1;
}

rtp_session_register_event_queue(session, q);
return 0;
}


int real_send_register(int expires)
{
char identity[100];
char registerer[100];
char localip[128];
eXosip_guess_localip (AF_INET, localip, 128);
sprintf(identity,"sip:%[email protected]%s:%s",username,localip,local_port);
sprintf(registerer,"sip:%s:%s",server_url,server_port);

osip_message_t *reg = NULL;

eXosip_lock ();
int ret = eXosip_register_build_initial_register(identity, registerer, NULL,expires, &reg);
eXosip_unlock ();
if(0 > ret)
{
printf("register init Failed!");
return -1;
}

eXosip_lock ();
eXosip_clear_authentication_info(); //去除上次加入的錯誤認證資訊
eXosip_add_authentication_info(username, username,password, "md5", NULL);
ret = eXosip_register_send_register(ret, reg);
eXosip_unlock ();
if(0 != ret)
{
printf("register send Failed!");
return -1;
}

return 0;
}


int sip_ua_monitor()
{
int ret = -1;
char *payload_str; /* 伺服器優先編碼值 */
char localip[128];
char tmp[4096];
char dtmf[50] = {0};

int reg_remain = REG_TIMER;
usleep(500);
printf("Event monitor for uac/uas start!/n");

eXosip_event_t *uac_e; /* 事件處理 */
osip_message_t *ack = NULL; /* 響應訊息 */
osip_message_t *answer = NULL; /* 請求訊息的迴應 */

/* 響應SDP(用於UAC) */
sdp_message_t * msg_rsp = NULL;
sdp_connection_t * con_rsp = NULL;
sdp_media_t * md_rsp = NULL;

/* 請求SDP(用於UAS) */
sdp_message_t * msg_req = NULL;
sdp_connection_t * con_req = NULL;
sdp_media_t * md_req = NULL;

char out_str[100] = {0};

eXosip_lock ();
eXosip_automatic_action();
eXosip_unlock ();

while(doing)
{
eXosip_lock ();
uac_e = eXosip_event_wait (0, WAIT_TIMER);
eXosip_unlock ();

reg_remain = reg_remain - WAIT_TIMER;
if(reg_remain < 0)
{
//超時,重新註冊
eXosip_lock ();
eXosip_automatic_refresh();
eXosip_unlock ();
reg_remain = REG_TIMER;
printf("register timeout,retry!");
}

if(dtmfing)
{
strcpy(dtmf, dtmf_str);
int index;
for( index=0; index<50; index++)
{
//依次讀取字元
if(dtmf[index] == '/0') break;

/* 傳送DTMF */
eXosip_lock();
audio_stream_send_dtmf(audio, dtmf[index]);
eXosip_unlock();

sprintf(out_str, "DTMF send <%c> OK!", dtmf[index]);
printf("%s",out_str);
usleep(500);
}

dtmfing = 0;
}

if (uac_e == NULL)
{
//DEBUG_INFO("nothing");
continue;
}

eXosip_lock ();
eXosip_default_action(uac_e); /* 處理407加入鑑權資訊 */
eXosip_unlock ();

if(NULL != uac_e->response)
{
//UAC 訊息處理前檢查
sprintf(out_str, "%d %s/n", uac_e->response->status_code, uac_e->response->reason_phrase);
printf(out_str);

if(487 == uac_e->response->status_code)
{
printf("(取消呼叫成功)/n");
continue;
}

if(480 == uac_e->response->status_code)
{
//480 無應答
printf("(無應答)/n");

picked = 0;
calling = 0;
call_id = 0;
dialog_id = 0;
continue;
}
if(401 == uac_e->response->status_code)
{
eXosip_clear_authentication_info(); //去除上次加入的錯誤認證資訊
eXosip_add_authentication_info(username, username,password, "md5", NULL);
printf("register again!/n");
}
}

if(NULL != uac_e->request)
{
}

if(NULL != uac_e->ack)
{
}

switch (uac_e->type)
{
case EXOSIP_CALL_SERVERFAILURE:
case EXOSIP_CALL_RELEASED:
/* 伺服器錯誤或對方忙 */
printf("(對方或伺服器正忙!)");

call_id = 0;
dialog_id = 0;
picked = 0;
calling = 0;

printf("Dest or Server Busy!");
break;

/* UAS 處理事件 */
case EXOSIP_MESSAGE_NEW: //新的訊息到來
printf("EXOSIP_MESSAGE_NEW!/n");
if(MSG_IS_MESSAGE(uac_e->request)) //如果接受到的訊息型別是MESSAGE
{
osip_body_t *body;
osip_message_get_body (uac_e->request, 0, &body);
printf ("Reveivc a msg : %s/n", body->body);
}
//按照規則,需要回復200 OK資訊
eXosip_message_build_answer (uac_e->tid, 200,&answer);
eXosip_message_send_answer (uac_e->tid, 200,answer);
break;

case EXOSIP_CALL_INVITE:
sprintf(out_str, "收到來自 %s 的呼叫!",uac_e->request->from->url->string);
printf("%s/n",out_str);

eXosip_lock ();
eXosip_call_send_answer(uac_e->tid, 180, NULL);
if(0 != eXosip_call_build_answer(uac_e->tid, 200, &answer))
{
eXosip_call_send_answer(uac_e->tid, 603, NULL);
printf("error build answer!");
continue;
}
eXosip_unlock ();

call_id = uac_e->cid; //供結束通話電話上下文操作
dialog_id = uac_e->did;

/* 構建本地SDP訊息供媒體建立 */
eXosip_guess_localip(AF_INET, localip, 128);
snprintf (tmp, 4096,
"v=0/r/n"
"o=youtoo 1 1 IN IP4 %s/r/n"
"s=##youtoo demo/r/n"
"c=IN IP4 %s/r/n"
"t=0 0/r/n"
"m=audio %d RTP/AVP 0 8 101/r/n"
"a=rtpmap:0 PCMU/8000/r/n"
"a=rtpmap:8 PCMA/8000/r/n"
"a=rtpmap:101 telephone-event/8000/r/n"
"a=fmtp:101 0-15/r/n", localip, localip, rtp_port);

//設定回覆的SDP訊息體,下一步計劃分析訊息體
eXosip_lock ();
osip_message_set_body(answer, tmp, strlen(tmp));
osip_message_set_content_type(answer, "application/sdp");

/* 解析SDP */
msg_req = eXosip_get_remote_sdp(uac_e->did);
con_req = eXosip_get_audio_connection(msg_req);
md_req = eXosip_get_audio_media(msg_req);
eXosip_unlock ();

/*
payload_str = (char *)osip_list_get(&md_req->m_payloads, 0); //取主叫媒體能力編碼
//暫時只支援0/8 711u/711a
*/

calling = 1;

while(!picked)
{
//未接通進入迴圈檢測
usleep(200);
}

eXosip_unlock ();
eXosip_call_send_answer(uac_e->tid, 200, answer);
eXosip_unlock ();

printf("200 ok 傳送");
break;

case EXOSIP_CALL_CANCELLED:
/* 拒絕接聽 */

call_id = 0;
dialog_id = 0;
picked = 0;
calling = 0;
printf("被叫拒絕接聽/n");
break;

case EXOSIP_CALL_ACK:
/* 返回200後收到ack才建立媒體 */
if(calling)
{
/* 建立RTP連線 */
ret = build_media(rtp_port, con_req->c_addr, atoi(md_req->m_port), 0, NULL, 0, 0, 0);
if(!ret)
{
printf("媒體建立失敗,無法建立通話,請結束通話!");
//pMainWnd->OnHang();
}
}
break;

/* UAC 處理事件 */
case EXOSIP_REGISTRATION_FAILURE:
if(401 == uac_e->response->status_code)
{
//4o1 Unauthorized register again
printf("register again!/n");
/* 先清除鑑權資訊,再應用新輸入的鑑權資訊 */
eXosip_clear_authentication_info();
eXosip_add_authentication_info(username, username, password, "md5", NULL);


osip_message_t *rereg;
eXosip_lock ();
eXosip_register_build_register(uac_e->rid, 300, &rereg);//
//取回認證的字串authorization
//osip_authorization_t *auth;
///char *strAuth;
//osip_message_get_authorization((const osip_message_t *)rereg,0,&auth);
//osip_authorization_to_str(auth,&strAuth);
//strcpy(str_auth,strAuth);//儲存認證字串
eXosip_register_send_register(uac_e->rid,rereg);
eXosip_unlock ();
}
break;

case EXOSIP_REGISTRATION_SUCCESS:
printf("textinfo is %s/n", uac_e->textinfo);
break;

case EXOSIP_CALL_CLOSED:
if(audio)
{
//被動關閉媒體連線(遠端觸發)
eXosip_lock ();
audio_stream_stop(audio);

ortp_ev_queue_destroy(q);
rtp_profile_destroy(profile);
eXosip_unlock ();

audio = NULL;
q = NULL;
profile = NULL;
printf("audio stream stoped!");
}

printf("(對方已結束通話)");
call_id = 0;
dialog_id = 0;
picked = 0;
calling = 0;
break;

case EXOSIP_CALL_PROCEEDING:
printf("(查詢連線中..)");
break;

case EXOSIP_CALL_RINGING:
printf("(對方振鈴)");
call_id = uac_e->cid;
dialog_id = uac_e->did;

/*
RingStream *r;
MSSndCard *sc;
sc=ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
r=ring_start("D://mbstudio//vcwork//YouToo//dial.wav",2000,sc);

Sleep(10);
ring_stop(r);
*/
break;

case EXOSIP_CALL_ANSWERED:
//ring_stop(ring_p);
printf("(對方已接聽)");

call_id = uac_e->cid;
dialog_id = uac_e->did;

eXosip_lock ();
eXosip_call_build_ack (uac_e->did, &ack);
eXosip_call_send_ack (uac_e->did, ack);

/* 響應SIP訊息中SDP分析 */
msg_rsp = eXosip_get_sdp_info(uac_e->response);
con_rsp = eXosip_get_audio_connection(msg_rsp);
md_rsp = eXosip_get_audio_media(msg_rsp);

/* 取伺服器支援的最優先的編碼方式 */
payload_str = (char *)osip_list_get(&md_rsp->m_payloads, 0);
eXosip_unlock ();

/* 建立RTP連線 */
ret = build_media(rtp_port, con_rsp->c_addr, atoi(md_rsp->m_port), atoi(payload_str), NULL, 0, 0, 0);
if(!ret)
{
printf("媒體建立失敗,無法建立通話,請結束通話!");
//pMainWnd->OnHang();
}
break;

default:
break;
}

eXosip_event_free (uac_e);

fflush(stdout);
}

return 0;
}

void OnHang()
{
int ret;

if(audio)
{
//主動關閉媒體連線(本地端)
eXosip_lock ();
audio_stream_stop(audio);

ortp_ev_queue_destroy(q);
rtp_profile_destroy(profile);
eXosip_unlock ();

printf("audio stream stoped!");
audio = NULL;
q = NULL;
profile = NULL;
}

eXosip_lock ();
ret = eXosip_call_terminate(call_id, dialog_id);
eXosip_unlock ();
if(0 != ret)
{
printf("hangup/terminate Failed!/n");
}
else
{
printf("(已結束通話)");
call_id = 0;
dialog_id = 0;
picked = 0;
calling = 0;
}
}

int sip_init()
{
int ret = 0;

ret = eXosip_init ();
eXosip_set_user_agent("##YouToo0.1");

if(0 != ret)
{
printf("Couldn't initialize eXosip!/n");
return -1;
}

ret = eXosip_listen_addr (IPPROTO_UDP, NULL, 5000, AF_INET, 0);
if(0 != ret)
{
eXosip_quit ();
printf("Couldn't initialize transport layer!/n");
return -1;
}

//AfxBeginThread(sip_ua_monitor,(void *)this);
pthread_t id;
int i=pthread_create(&id,NULL,(void *) sip_ua_monitor,NULL);
if(i!=0)
{
printf ("Create pthread error!/n");
return -1;
}

/* rtp */
ortp_init();
//ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR|ORTP_FATAL);

/* media */
ms_init();

return 0;
}


int main()
{
char *payload_str;
char localip[128];
char tmp[4096];
char command;
char dtmf[50] = {0};

int reg_remain = REG_TIMER;
char out_str[100] = {0};

osip_message_t *invite=NULL;
osip_message_t *info=NULL;
osip_message_t *message=NULL;

char source_call[100];
char dest_call[100];

doing = 1; /* 事件迴圈開關 */
calling = 0; /* 是否正在被叫(判斷ack型別) */
picked = 0;
dtmfing = 0;

if(0 != sip_init ())
{
printf("Quit!/n");
return -1;
}




rtp_port = 9900; //rtp 媒體本地埠
//codec_id = 0; //編碼 G711uLaw 0

call_id = 0;
dialog_id = 0;



printf("r 向伺服器註冊/n/n");
printf("c 取消註冊/n/n");
printf("i 發起呼叫請求/n/n");
printf("h 結束通話/n/n");
printf("a 接聽電話/n/n");
printf("q 推出程式/n/n");
printf("s 執行方法INFO/n/n");
printf("m 執行方法MESSAGE/n/n");

int flag=1;

while(flag)
{
//輸入命令
printf("Please input the command:/n");
scanf("%c",&command);
getchar();

switch(command)
{
case 'r':// 先清除鑑權資訊,再應用新輸入的鑑權資訊
real_send_register(1800);
sleep(5);
real_send_register(1800);
break;
case 'i':

sprintf(source_call,"sip:%[email protected]%s:%s",username,localip,local_port);
sprintf(dest_call,"sip:%[email protected]%s:%s",telNum,server_url,server_port);

//char tmp[4096];
bzero(tmp,4096);

int i = eXosip_call_build_initial_invite (&invite, dest_call, source_call, NULL, "This is a call invite");
if (i != 0)
{
printf("Intial INVITE failed!/n");
}

char localip[128];

eXosip_guess_localip (AF_INET, localip, 128);
snprintf (tmp, 4096,
"v=0/r/n"
"o=youtoo 1 1 IN IP4 %s/r/n"
"s=##youtoo demo/r/n"
"c=IN IP4 %s/r/n"
"t=0 0/r/n"
"m=audio %d RTP/AVP 0 8 101/r/n"
"a=rtpmap:0 PCMU/8000/r/n"
"a=rtpmap:8 PCMA/8000/r/n"
"a=rtpmap:101 telephone-event/8000/r/n"
"a=fmtp:101 0-15/r/n", localip, localip, rtp_port);

osip_message_set_body (invite, tmp, strlen(tmp));
osip_message_set_content_type (invite, "application/sdp");

eXosip_lock ();
i = eXosip_call_send_initial_invite (invite);
eXosip_unlock ();

break;

case 'h': //結束通話
OnHang();
break;

case 'c':
real_send_register(0);
//printf("This modal is not commpleted!/n");
break;

case 's': //傳輸INFO方法
eXosip_call_build_info(dialog_id,&info);
snprintf(tmp,4096,"/nThis is a sip message(Method:INFO)");
osip_message_set_body(info,tmp,strlen(tmp));
//格式可以任意設定,text/plain代表文字資訊
osip_message_set_content_type(info,"text/plain");
eXosip_call_send_request(dialog_id,info);
break;

case 'm':
//傳輸MESSAGE方法,也就是即時訊息,和INFO方法相比,我認為主要區別是:
//MESiSAGE不用建立連線,直接傳輸資訊,而INFO訊息必須在建立INVITE的基礎上傳輸
bzero(tmp,4096);
printf("the method : MESSAGE/n");
eXosip_message_build_request(&message,"MESSAGE",dest_call,source_call,NULL);
//內容,方法, to ,from ,route
snprintf(tmp,4096,"This is a sip message(Method:MESSAGE)");
osip_message_set_body(message,tmp,strlen(tmp));
//假設格式是xml
osip_message_set_content_type(message,"text/xml");
eXosip_message_send_request(message);
break;

case 'q':
doing = -1;
flag=0;
usleep(1000); //保證事件執行緒能退出(執行緒迴圈檢測時間毫秒級<1000)
//主動關閉媒體連線(本地端)
printf("Bye!/n");
break;

case 'a':
picked = 1;
printf("已接聽/n");
break;

case 'd':
dtmfing=1;
break;
default:
printf("input error! please input again/n");
break;
}
}
return(0);
}

相關推薦

osip2+eXosip2+ortp+mediastreamer實現linux簡易電話原始碼實現通話

編譯命令: gcc -losip2 -leXosip2 -lmediastreamer -lpthread -o my_exosip_phone my_exosip_phone.c my_exosip_phone.c如下: #include <eXosip2/eXosip.h> #inc

c語言實現linux下高危函式system 簡易V1.0版本

system這個函式真的是要慎用,一不小心就會留下漏洞。 下面是用c語言簡易的實現了一下system函式 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<err

c語言實現linux下高危函式system 簡易V1.0版本

system這個函式真的是要慎用,一不小心就會留下漏洞。 下面是用c語言簡易的實現了一下system函式 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #inc

EasyUI-DataGrid實現列表批量刪除的功能ASP.NET/MVC

1、前端程式碼:首先給列表新增多選框。注意:當singleSelect的屬性值為false時,才能實現多選功能;當checkbox屬性值為true選擇行勾選,false選擇行不勾選。 <table id="dataGrid" class="easyui-datagrid" title=""

【轉載】linux下安裝wget命令sftp實現

 如何安裝wget命令。 方法一:通過yum 命令列為:yum install wget 完成。此操作很簡單,但是我安裝的linux是centos的最小版本,執行上述命令時會出現無法連線到源網站(大概是這個意思)的問題。 方法二:通過rpm 據說rpm是linux的通用安裝法,小白表示不懂

Linux使用nginx反向代理。實現域名指向特定埠

在配置80指向域名的時候出現端口占用,使用kill -9無法殺死埠,應使用下面的命令來殺死程序 killall -9 nginx 後在root許可權下的nginx的sbin下使用./nginx -t(命令重啟nginx並檢查是否有語法錯誤) 或者使用 ./ngi

java實現對MongoDB的基本操作增刪改查

準備工作:要想用java實現對MongoDB的增刪改查,首先需要下載mongo的java驅動,mongo-java-driver-3.2.2, 下載地址:https://oss.sonatype.org/content/repositories/releases/org/m

Linux下nfs實現跨機器的檔案共享個人專案經驗

目前的專案開發過程中都是採用分散式,在上傳檔案的時候,檔案不一定會在同一臺機器中,因此就需要跨機器共享檔案,在這裡就簡單的採用nfs實現跨機器的檔案共享。 1、安裝nfs和rpcbind(在centOS6之前是portmap)       檢查自己的電腦是否已經預設安裝了n

條件隨機場CRF進行字標註中文分詞Python實現

        本文運用字標註法進行中文分詞,使用4-tag對語料進行字標註,觀察分詞效果。模型方面選用開源的條件隨機場工具包“CRF++: Yet Another CRF toolkit”進行分詞。         本文使用的中文語料資源是SIGHAN提供的backof

最大熵模型進行字標註中文分詞Python實現

        同前面的那篇文章一樣(參見:最大熵模型進行中文分詞),本文運用字標註法進行中文分詞,分別使用4-tag和6-tag對語料進行字標註,觀察分詞效果。前面的文章中使用了模型工具包中自帶的一個樣例進行4-tag中文分詞,但由於其選取的特徵是針對英文詞性標註開發

C語言課程設計——簡易公交車管理系統陣列實現

/**************************標頭檔案宣告************************************/ #include "stdio.h" #include "stdlib.h" #include "string.h" #includ

電腦Linux/Windows使用SSH遠端登入安卓Android手機實現無線傳輸和管理檔案圖文詳解

電腦(Linux/Windows系統)使用SSH遠端登入安卓(Android)手機實現無線傳輸和管理檔案(圖文詳解) 溫馨提示 本文只針對安卓(Android)手機!iPhone或者WP的手機使用者,請不要浪費時間在本文。 前言 在將And

vuevue-concise-slider外掛實現網站新聞公告上下滾動支援移動端【轉】

程式碼如下: 隱藏滾動點的方法:在自定義scss檔案中,新增如下樣式 .swiper-container-vertical .slider-pagination-bullets { display: none; } <template> <di

一個棧實現另一個棧的排序java實現

一、題目    一個棧中元素的型別為整形,現在想將該棧從頂到底按從大到小的順序排序,只許申請一個棧,除此之外,可以申請新的變數,但不能申請額外的資料結構。如何完成排序? 二、解答     將要排序的棧記為stack,申請的輔助棧記為help。在stack上執行pop操作,彈出

linux核心設計與實現 —— 定時器和時間管理第11章

核心中的時間概念 硬體為核心提供了一個系統定時器用以計算流逝的時間。系統定時器是一種可程式設計硬體晶片,它能以固定頻率產生中斷。該頻率可以通過程式設計預定,稱作節拍率(tick rate)。該中斷就是所謂的定時器中斷,它所對應的中斷處理程式負責更新系統時間,也

田螺便利店—filezilla實現Linux和windows通信

rmi 更改 管理 ssh配置 客戶 linu 說明 正常 root filezilla,FileZilla是一個免費開源的FTP軟件,分為客戶端版本和服務器版本,具備所有的FTP軟件功能。可控性、有條理的界面和管理多站點的簡化方式使得Filezilla客戶端版成為一個方便

【Java並發編程】之六:Runnable和Thread實現多線程的區別含代碼

技術分享 runnable 避免 實際應用 details div 一個 預測 enter 轉載請註明出處:http://blog.csdn.net/ns_code/article/details/17161237 Java中實現多線程有兩種方法:繼承Thre

【轉】Entity Framework6 with Oracle實現code first

ocs driver 版本 nag model oracl 新的 vid req Oracle 已在2014年底提供對EF6的支持。以前只支持到EF5。EF6有很多有用的功能 值得升級。這裏介紹下如何支持Oracle 一.Oracle 對.net支持的一些基礎知識了解

ASP.NET Core 簡單實現七牛圖片上傳FormData 和 Base64

private stream public 圖片 ASP.NET Core 簡單實現七牛圖片上傳(FormData 和 Base64)七牛圖片上傳 SDK(.NET 版本):https://developer.qiniu.com/kodo/sdk/1237/csharpUpoladServic

【LeetCode-面試算法經典-Java實現】【056-Merge Intervals區間合並

解題思路 結果 led data- javascrip res 一段 元素 轉載 【056-Merge Intervals(區間合並)】 【LeetCode-面試算法經典-Java實現】【全部題目文件夾索引】 原題   Given a co