用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, ®);
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
vue用vue-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