2007年4月4日 星期三

Linux下的即時流媒體編程

原文出處:http://blog.csdn.net/kolaye/archive/2004/07/23/50374.aspx
Linux下的即時流媒體編程
肖文鵬 (xiaowp@263.net)
北京理工大學電腦系,碩士研究生
2004 年 2 月
流媒體指的是在網路中使用流技術傳輸的連續時基媒體,其特點是在播放前不需要下載整個檔,而是採用邊下載邊播放的方式,它是視頻會議、IP電話等應用場合的技術基礎。RTP是進行即時流媒體傳輸的標準協議和關鍵技術,本文介紹如何在Linux下利用JRTPLIB進行即時流媒體編程。
一、流媒體簡介
隨著Internet的日益普及,在網路上傳輸的資料已經不再局限於文字和圖形,而是逐漸向聲音和視頻等多媒體格式過渡。目前在網路上傳輸音頻/視頻(Audio/Video,簡稱A/V)等多媒體檔時,基本上只有下載和流式傳輸兩種選擇。通常說來,A/V檔佔據的存儲空間都比較大,在帶寬受限的網路環境中下載可能要耗費數分鐘甚至數小時,所以這種處理方法的延遲很大。如果換用流式傳輸的話,聲音、影像、動畫等多媒體檔將由專門的流媒體伺服器負責向用戶連續、即時地發送,這樣用戶可以不必等到整個檔全部下載完畢,而只需要經過幾秒鐘的啟動延時就可以了,當這些多媒體資料在客戶機上播放時,檔的剩餘部分將繼續從流媒體伺服器下載。
流(Streaming)是近年在Internet上出現的新概念,其定義非常廣泛,主要是指通過網路傳輸多媒體資料的技術總稱。流媒體包含廣義和狹義兩種內涵:廣義上的流媒體指的是使音頻和視頻形成穩定和連續的傳輸流和重播流的一系列技術、方法和協議的總稱,即流媒體技術;狹義上的流媒體是相對于傳統的下載-重播方式而言的,指的是一種從Internet上獲取音頻和視頻等多媒體資料的新方法,它能夠支援多媒體資料流程的即時傳輸和即時播放。通過運用流媒體技術,伺服器能夠向客戶機發送穩定和連續的多媒體資料流程,客戶機在接收資料的同時以一個穩定的速率重播,而不用等資料全部下載完之後再進行重播。
由於受網路帶寬、電腦處理能力和協定規範等方面的限制,要想從Internet上下載大量的音頻和視頻資料,無論從下載時間和存儲空間上來講都是不太現實的,而流媒體技術的出現則很好地解決了這一難題。目前實現流媒體傳輸主要有兩種方法:順序流(progressive streaming)傳輸和即時流(realtime streaming)傳輸,它們分別適合於不同的應用場合。
順序流傳輸
順序流傳輸採用順序下載的方式進行傳輸,在下載的同時用戶可以線上重播多媒體資料,但給定時刻只能觀看已經下載的部分,不能跳到尚未下載的部分,也不能在傳輸期間根據網路狀況對下載速度進行調整。由於標準的HTTP伺服器就可以發送這種形式的流媒體,而不需要其他特殊協定的支援,因此也常常被稱作HTTP流式傳輸。順序流式傳輸比較適合於高品質的多媒體片段,如片頭、片尾或者廣告等。
即時流傳輸
即時流式傳輸保證媒體信號帶寬能夠與當前網路狀況相匹配,從而使得流媒體資料總是被即時地傳送,因此特別適合於現場事件。即時流傳輸支持隨機訪問,即用戶可以通過快進或者後退操作來觀看前面或者後面的內容。從理論上講,即時流媒體一經播放就不會停頓,但事實上仍有可能發生週期性的暫停現象,尤其是在網路狀況惡化時更是如此。與順序流傳輸不同的是,即時流傳輸需要用到特定的流媒體伺服器,而且還需要特定網路協定的支援。
二、流媒體協議
即時傳輸協議(Real-time Transport Protocol,PRT)是在Internet上處理多媒體資料流程的一種網路協定,利用它能夠在一對一(unicast,單播)或者一對多(multicast,多播)的網路環境中實現傳流媒體資料的即時傳輸。RTP通常使用UDP來進行多媒體資料的傳輸,但如果需要的話可以使用TCP或者ATM等其他協定,整個RTP協定由兩個密切相關的部分組成:RTP資料協定和RTP控制協定。即時流協議(Real Time Streaming Protocol,RTSP)最早由Real Networks和Netscape公司共同提出,它位於RTP和RTCP之上,其目的是希望通過IP網路有效地傳輸多媒體資料。
2.1 RTP資料協定
RTP資料協定負責對流媒體資料進行封包並實現媒體流的即時傳輸,每一個RTP資料報都由頭部(Header)和負載(Payload)兩個部分組成,其中頭部前12個位元組的含義是固定的,而負載則可以是音頻或者視頻資料。RTP資料報的頭部格式如圖1所示:
其中比較重要的幾個域及其意義如下:
• CSRC記數(CC)  表示CSRC標識的數目。CSRC標識緊跟在RTP固定頭部之後,用來表示RTP資料報的來源,RTP協議允許在同一個會話中存在多個資料源,它們可以通過RTP混合器合併為一個資料源。例如,可以產生一個CSRC列表來表示一個電話會議,該會議通過一個RTP混合器將所有講話者的語音資料組合為一個RTP資料源。
• 負載類型(PT)  標明RTP負載的格式,包括所採用的編碼演算法、採樣頻率、承載通道等。例如,類型2表明該RTP資料包中承載的是用ITU G.721演算法編碼的語音資料,採樣頻率為8000Hz,並且採用單聲道。
• 序列號  用來為接收方提供探測資料丟失的方法,但如何處理丟失的資料則是應用程式自己的事情,RTP協定本身並不負責資料的重傳。
• 時間戳  記錄了負載中第一個位元組的採樣時間,接收方能夠時間戳能夠確定資料的到達是否受到了延遲抖動的影響,但具體如何來補償延遲抖動則是應用程式自己的事情。
從RTP資料報的格式不難看出,它包含了傳輸媒體的類型、格式、序列號、時間戳以及是否有附加資料等資訊,這些都為即時的流媒體傳輸提供了相應的基礎。RTP協定的目的是提供即時資料(如互動式的音頻和視頻)的端到端傳輸服務,因此在RTP中沒有連接的概念,它可以建立在底層的面向連接或面向非連接的傳輸協定之上;RTP也不依賴於特別的網路位址格式,而僅僅只需要底層傳輸協定支援組幀(Framing)和分段(Segmentation)就足夠了;另外RTP本身還不提供任何可靠性機制,這些都要由傳輸協定或者應用程式自己來保證。在典型的應用場合下,RTP一般是在傳輸協定之上作為應用程式的一部分加以實現的,如圖2所示:
2.2 RTCP控制協議
RTCP控制協定需要與RTP資料協定一起配合使用,當應用程式啟動一個RTP會話時將同時佔用兩個埠,分別供RTP和RTCP使用。RTP本身並不能為按序傳輸資料包提供可靠的保證,也不提供流量控制和擁塞控制,這些都由RTCP來負責完成。通常RTCP會採用與RTP相同的分發機制,向會話中的所有成員週期性地發送控制資訊,應用程式通過接收這些資料,從中獲取會話參與者的相關資料,以及網路狀況、分組丟失概率等回饋資訊,從而能夠對服務品質進行控制或者對網路狀況進行診斷。
RTCP協定的功能是通過不同的RTCP資料報來實現的,主要有如下幾種類型:
• SR  發送端報告,所謂發送端是指發出RTP資料報的應用程式或者終端,發送端同時也可以是接收端。
• RR  接收端報告,所謂接收端是指僅接收但不發送RTP資料報的應用程式或者終端。
• SDES  源描述,主要功能是作為會話成員有關標識資訊的載體,如用戶名、郵件位址、電話號碼等,此外還具有向會話成員傳達會話控制資訊的功能。
• BYE  通知離開,主要功能是指示某一個或者幾個源不再有效,即通知會話中的其他成員自己將退出會話。
• APP  由應用程式自己定義,解決了RTCP的擴展性問題,並且為協議的實現者提供了很大的靈活性。
RTCP資料報攜帶有服務品質監控的必要資訊,能夠對服務品質進行動態的調整,並能夠對網路擁塞進行有效的控制。由於RTCP資料報採用的是多播方式,因此會話中的所有成員都可以通過RTCP資料報返回的控制資訊,來瞭解其他參與者的當前情況。
在一個典型的應用場合下,發送媒體流的應用程式將週期性地產生發送端報告SR,該RTCP資料報含有不同媒體流間的同步資訊,以及已經發送的資料報和位元組的計數,接收端根據這些資訊可以估計出實際的資料傳輸速率。另一方面,接收端會向所有已知的發送端發送接收端報告RR,該RTCP資料報含有已接收資料報的最大序列號、丟失的資料報數目、延時抖動和時間戳等重要資訊,發送端應用根據這些資訊可以估計出往返時延,並且可以根據資料報丟失概率和時延抖動情況動態調整發送速率,以改善網路擁塞狀況,或者根據網路狀況平滑地調整應用程式的服務品質。
2.3 RTSP即時流協議
作為一個應用層協議,RTSP提供了一個可供擴展的框架,它的意義在於使得即時流媒體資料的受控和點播變得可能。總的說來,RTSP是一個流媒體表示協議,主要用來控制具有即時特性的資料發送,但它本身並不傳輸資料,而是必須依賴於下層傳輸協定所提供的某些服務。RTSP可以對流媒體提供諸如播放、暫停、快進等操作,它負責定義具體的控制消息、操作方法、狀態碼等,此外還描述了與RTP間的交互操作。
RTSP在制定時較多地參考了HTTP/1.1協議,甚至許多描述與HTTP/1.1完全相同。RTSP之所以特意使用與HTTP/1.1類似的語法和操作,在很大程度上是為了相容現有的Web基礎結構,正因如此,HTTP/1.1的擴展機制大都可以直接引入到RTSP中。
由RTSP控制的媒體流集合可以用表示描述(Presentation Description)來定義,所謂表示是指流媒體伺服器提供給客戶機的一個或者多個媒體流的集合,而表示描述則包含了一個表示中各個媒體流的相關資訊,如數據編碼/解碼演算法、網路位址、媒體流的內容等。
雖然RTSP伺服器同樣也使用識別字來區別每一流連接會話(Session),但RTSP連接並沒有被綁定到傳輸層連接(如TCP等),也就是說在整個RTSP連接期間,RTSP用戶可打開或者關閉多個對RTSP伺服器的可靠傳輸連接以發出RTSP 請求。此外,RTSP連接也可以基於面向無連接的傳輸協定(如UDP等)。
RTSP協定目前支援以下操作:
• 檢索媒體  允許用戶通過HTTP或者其他方法向媒體伺服器提交一個表示描述。如表示是組播的,則表示描述就包含用於該媒體流的組播位址和埠號;如果表示是單播的,為了安全在表示描述中應該只提供目的地址。
• 邀請加入  媒體伺服器可以被邀請參加正在進行的會議,或者在表示中重播媒體,或者在表示中錄製全部媒體或其子集,非常適合於分散式教學。
• 添加媒體  通知用戶新加入的可利用媒體流,這對現場講座來講顯得尤其有用。與HTTP/1.1類似,RTSP請求也可以交由代理、通道或者緩存來進行處理。
三、流媒體編程
RTP是目前解決流媒體即時傳輸問題的最好辦法,如果需要在Linux平臺上進行即時流媒體編程,可以考慮使用一些開放源代碼的RTP庫,如LIBRTP、JRTPLIB等。JRTPLIB是一個面向物件的RTP庫,它完全遵循RFC 1889設計,在很多場合下是一個非常不錯的選擇,下面就以JRTPLIB為例,講述如何在Linux平臺上運用RTP協議進行即時流媒體編程。
3.1 環境搭建
JRTPLIB是一個用C++語言實現的RTP庫,目前已經可以運行在Windows、Linux、FreeBSD、Solaris、Unix和VxWorks等多種作業系統上。要為Linux 系統安裝JRTPLIB,首先從JRTPLIB的網站(http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.html)下載最新的源碼包,此處使用的是jrtplib-2.7b.tar.bz2。假設下載後的源碼包保存在/usr/local/src目錄下,執行下面的命令可以對其進行解壓縮:
[root@linuxgam src]# bzip2 -dc jrtplib-2.7b.tar.bz2 | tar xvf -
接下去需要對JRTPLIB進行配置和編譯:
[root@linuxgam src]# cd jrtplib-2.7
[root@linuxgam jrtplib-2.7b]# ./configure
[root@linuxgam jrtplib-2.7b]# make

最後再執行如下命令就可以完成JRTPLIB的安裝:
[root@linuxgam jrtplib-2.7b]# make install
3.2 初始化
在使用JRTPLIB進行即時流媒體資料傳輸之前,首先應該生成RTPSession類的一個實例來表示此次RTP會話,然後調用Create()方法來對其進行初始化操作。RTPSession類的Create()方法只有一個參數,用來指明此次RTP會話所採用的埠號。清單1給出了一個最簡單的初始化框架,它只是完成了RTP會話的初始化工作,還不具備任何實際的功能。
代碼清單1:initial.cpp
#include "rtpsession.h"
int main(void) {
RTPSession sess; sess.Create(5000); return 0;
}

如果RTP會話創建過程失敗,Create()方法將會返回一個負數,通過它雖然可以很容易地判斷出函數調用究竟是成功的還是失敗的,但卻很難明白出錯的原因到底什麼。JRTPLIB採用了統一的錯誤處理機制,它提供的所有函數如果返回負數就表明出現了某種形式的錯誤,而具體的出錯資訊則可以通過調用RTPGetErrorString()函數得到。RTPGetErrorString()函數將錯誤代碼作為參數傳入,然後返回該錯誤代碼所對應的錯誤資訊。清單2給出了一個更加完整的初始化框架,它可以對RTP會話初始化過程中所產生的錯誤進行更好的處理:
代碼清單2:framework.cpp
#include
#include "rtpsession.h"
int main(void)
{
RTPSession sess;
int status;
char* msg; sess.Create(6000);
msg = RTPGetErrorString(status);
printf("Error String: %s\\n", msg);
return 0; }

設置恰當的時戳單元,是RTP會話初始化過程所要進行的另外一項重要工作,這是通過調用RTPSession類的SetTimestampUnit()方法來實現的,該方法同樣也只有一個參數,表示的是以秒為單元的時戳單元。例如,當使用RTP會話傳輸8000Hz採樣的音頻資料時,由於時戳每秒鐘將遞增8000,所以時戳單元相應地應該被設置成1/8000:
sess.SetTimestampUnit(1.0/8000.0);
3.3 資料發送
當RTP會話成功建立起來之後,接下去就可以開始進行流媒體資料的即時傳輸了。首先需要設置好資料發送的目標位址,RTP協定允許同一會話存在多個目標位址,這可以通過調用RTPSession類的AddDestination()、DeleteDestination()和ClearDestinations()方法來完成。例如,下面的語句表示的是讓RTP會話將資料發送到本地主機的6000埠:
unsigned long addr = ntohl(inet_addr("127.0.0.1")); sess.AddDestination(addr, 6000);
目標位址全部指定之後,接著就可以調用RTPSession類的SendPacket()方法,向所有的目標位址發送流媒體資料。SendPacket()是RTPSession類提供的一個重載函數,它具有下列多種形式:
int SendPacket(void *data,int len)
int SendPacket(void *data,int len,unsigned char pt,bool mark,unsigned long timestampinc)
int SendPacket(void *data,int len,unsigned short hdrextID,void *hdrextdata,int numhdrextwords)
int SendPacket(void *data,int len,unsigned char pt,bool mark,unsigned long timestampinc,unsigned short hdrextID,void *hdrextdata,int numhdrextwords)

SendPacket()最典型的用法是類似於下面的語句,其中第一個參數是要被發送的資料,而第二個參數則指明將要發送資料的長度,再往後依次是RTP負載類型、標識和時戳增量。
sess.SendPacket(buffer, 5, 0, false, 10);
對於同一個RTP會話來講,負載類型、標識和時戳增量通常來講都是相同的,JRTPLIB允許將它們設置為會話的默認參數,這是通過調用RTPSession類的SetDefaultPayloadType()、SetDefaultMark()和SetDefaultTimeStampIncrement()方法來完成的。為RTP會話設置這些默認參數的好處是可以簡化資料的發送,例如,如果為RTP會話設置了默認參數:
sess.SetDefaultPayloadType(0);
sess.SetDefaultMark(false);
sess.SetDefaultTimeStampIncrement(10);

之後在進行資料發送時只需指明要發送的資料及其長度就可以了:
sess.SendPacket(buffer, 5);
3.4 資料接收
對於流媒體資料的接收端,首先需要調用RTPSession類的PollData()方法來接收發送過來的RTP或者RTCP資料報。由於同一個RTP會話中允許有多個參與者(源),你既可以通過調用RTPSession類的GotoFirstSource()和GotoNextSource()方法來遍曆所有的源,也可以通過調用RTPSession類的GotoFirstSourceWithData()和GotoNextSourceWithData()方法來遍曆那些攜帶有資料的源。在從RTP會話中檢測出有效的資料源之後,接下去就可以調用RTPSession類的GetNextPacket()方法從中抽取RTP資料報,當接收到的RTP資料報處理完之後,一定要記得及時釋放。下面的代碼示範了該如何對接收到的RTP資料報進行處理:
if (sess.GotoFirstSourceWithData())
{ do {
RTPPacket *pack;
pack = sess.GetNextPacket(); // 處理接收到的資料 delete pack; }
while (sess.GotoNextSourceWithData());
}

JRTPLIB為RTP資料報定義了三種接收模式,其中每種接收模式都具體規定了哪些到達的RTP資料報將會被接受,而哪些到達的RTP資料報將會被拒絕。通過調用RTPSession類的SetReceiveMode()方法可以設置下列這些接收模式:
• RECEIVEMODE_ALL  缺省的接收模式,所有到達的RTP資料報都將被接受;
• RECEIVEMODE_IGNORESOME  除了某些特定的發送者之外,所有到達的RTP資料報都將被接受,而被拒絕的發送者列表可以通過調用AddToIgnoreList()、DeleteFromIgnoreList()和ClearIgnoreList()方法來進行設置;
• RECEIVEMODE_ACCEPTSOME  除了某些特定的發送者之外,所有到達的RTP資料報都將被拒絕,而被接受的發送者列表可以通過調用AddToAcceptList ()、DeleteFromAcceptList和ClearAcceptList ()方法來進行設置。
3.5 控制資訊
JRTPLIB是一個高度封裝後的RTP庫,程式師在使用它時很多時候並不用關心RTCP資料報是如何被發送和接收的,因為這些都可以由JRTPLIB自己來完成。只要PollData()或者SendPacket()方法被成功調用,JRTPLIB就能夠自動對到達的RTCP資料報進行處理,並且還會在需要的時候發送RTCP資料報,從而能夠確保整個RTP會話過程的正確性。
而另一方面,通過調用RTPSession類提供的SetLocalName()、SetLocalEMail()、SetLocalLocation()、SetLocalPhone()、SetLocalTool()和SetLocalNote()方法,JRTPLIB又允許程式師對RTP會話的控制資訊進行設置。所有這些方法在調用時都帶有兩個參數,其中第一個參數是一個char型的指標,指向將要被設置的數據;而第二個參數則是一個int型的數值,表明該資料中的前面多少個字元將會被使用。例如下面的語句可以被用來設置控制資訊中的電子郵件位址:
sess.SetLocalEMail("xiaowp@linuxgam.com",19);
在RTP會話過程中,不是所有的控制資訊都需要被發送,通過調用RTPSession類提供的EnableSendName()、EnableSendEMail()、EnableSendLocation()、EnableSendPhone()、EnableSendTool()和EnableSendNote()方法,可以為當前RTP會話選擇將被發送的控制資訊。
3.6 實際應用
最後通過一個簡單的流媒體發送-接收實例,介紹如何利用JRTPLIB來進行即時流媒體的編程。清單3給出了資料發送端的完整代碼,它負責向用戶指定的IP位址和埠,不斷地發送RTP資料包:
代碼清單3:sender.cpp

#include
#include
#include "rtpsession.h" // 錯誤處理函數
void checkerror(int err) {
if (err < 0) {
char* errstr = RTPGetErrorString(err);
printf("Error:%s\\n", errstr);
exit(-1);
}
}
int main(int argc, char** argv) {
RTPSession sess;
unsigned long destip;
int destport;
int portbase = 6000;
int status, index;
char buffer[128];
if (argc != 3) {
printf("Usage: ./sender destip destport\\n");
return -1; } // 獲得接收端的IP地址和埠號
destip = inet_addr(argv[1]);
if (destip == INADDR_NONE) {
printf("Bad IP address specified.\\n"); return -1; }
destip = ntohl(destip);
destport = atoi(argv[2]); // 創建RTP會話
status = sess.Create(portbase);
checkerror(status); // 指定RTP資料接收端
status = sess.AddDestination(destip, destport);
checkerror(status); // 設置RTP會話默認參數
sess.SetDefaultPayloadType(0); sess.SetDefaultMark(false); sess.SetDefaultTimeStampIncrement(10); // 發送流媒體資料
index = 1; do { sprintf(buffer, "%d: RTP packet", index ++); sess.SendPacket(buffer, strlen(buffer));
printf("Send packet !\\n"); }
while(1); return 0; }

清單4則給出了資料接收端的完整代碼,它負責從指定的埠不斷地讀取RTP資料包:
代碼清單4:receiver.cpp

#include
#include "rtpsession.h"
#include "rtppacket.h" // 錯誤處理函數
void checkerror(int err) { if (err < 0) { char* errstr = RTPGetErrorString(err); printf("Error:%s\\n", errstr); exit(-1); } }
int main(int argc, char** argv) {
RTPSession sess;
int localport;
int status;
if (argc != 2) { printf("Usage: ./sender localport\\n"); return -1; } // 獲得用戶指定的埠號
localport = atoi(argv[1]); // 創建RTP會話
status = sess.Create(localport);
checkerror(status); do { // 接受RTP數據
status = sess.PollData(); // 檢索RTP資料源
if (sess.GotoFirstSourceWithData()) { do { RTPPacket* packet; // 獲取RTP資料報
while ((packet = sess.GetNextPacket()) != NULL) { printf("Got packet !\\n"); // 刪除RTP資料報 delete packet; } }
while (sess.GotoNextSourceWithData()); } }
while(1);
return 0; }

本文源碼下載 http://www-900.ibm.com/developerWorks/cn/linux/l-mdst/code.zip
四、小結
隨著多媒體資料在Internet上所承擔的作用變得越來越重要,需要即時傳輸音頻和視頻等多媒體資料的場合也將變得越來越多,如IP電話、視頻點播、線上會議等。RTP是用來在Internet上進行即時流媒體傳輸的一種協議,目前已經被廣泛地應用在各種場合,JRTPLIB是一個面向物件的RTP封裝庫,利用它可以很方便地完成Linux平臺上的即時流媒體編程。
五、參考資源
1. 在JRTPLIB的網站http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.html上,可以下載到JRTPLIB最新的源碼包,並且還能找到一些與RTP相關的資源。
2. 顧淑珍等編著,寬頻增值服務開發實例,北京:機械工業出版社,2002
3. 黃永峰等編著,IP網路多媒體通信技術,北京:人民郵電出版社,2003
關於作者

本文作者肖文鵬是一名自由軟體愛好者,主要從事作業系統和分散式計算環境的研究,喜愛Linux和Python。你可以通過xiaowp@263.net與他取得聯繫,也可以在網站http://www.linuxgam.com上就本文同他做進一步的交流。目前他正致力於OpenEC項目的開發,旨在將Linux推向家庭娛樂領域,你可以從網站http://www.openec.org.cn瞭解到該專案的當前進展和最新成果。
=====================================================
jrtp
http://research.edm.luc.ac.be/jori/jrtplib/jrtplib.html

JRTPLIB is an object-oriented RTP library written in C++. It was first developed for my thesis at the School for Knowledge Technology (or 'School voor Kennistechnologie' in Dutch), a cooperation between the Hasselt University) and the Maastricht University.

The latest version of the library is 3.5.2 (March 2006). You can take a look at the ChangeLog.

The 3.x.x series is a complete rewrite of the library and is meant to be compliant with RFC 3550. Also, the library now provides many useful components which can be helpful in building all sorts of RTP-capable applications. This version was developed at the The Expertise Centre for Digital Media (EDM), a research centre of the Hasselt University.

If you're interested in the 2.x series, you should take a look here:
JRTPLIB v2.x

1 則留言:

Unknown 提到...

这样传视频是不行的
Jrtplib里面设置大小为1400,一般视频压缩后都远超这个值,需要再分割

一個小故事讓我們明白資金流通的意義

“又是炎熱小鎮慵懶的一天。太陽高掛,街道無人,每個人都債台高築,靠信用度日。這時,從外地來了一位有錢的旅客,他進了一家旅館,拿出一張1000 元鈔票放在櫃檯,說想先看看房間,挑一間合適的過夜,就在此人上樓的時候---- 店主抓了這張1000 元鈔,跑到隔壁屠戶那裡支付了他欠的肉錢...