深入OSS開發

轉自:http://www.ibm.com/developerworks/cn/linux/l-ossdev/

級別: 初級

湯凱 ([email protected]),

2004 年 4 月 01 日

本文將對OSS(Open Sound System)的開發進行一些深入的討論,具體的內容包括:播放音頻的時延問題,並定量的對不同的緩衝區配置進行分析;非阻塞write;應用程序對驅動程序中DMA buffer的直接訪問。這些是在深入OSS的開發過程中,開發者會遇到的一些實際問題,比如,在開發基於Linux平臺的遊戲程序時,就必須要考慮到如何降低播放音頻的時延,使得在需要的時候,能夠將遊戲的音效儘快地播放出來,並與畫面的進行保持同步。

在討論這些方面時,除了從使用的角度介紹以外,還結合具體的驅動實現,分析這些功能對應的內部原理,以加深讀者的理解。

爲了在閱讀文章時有一個共同的認識,本文首先簡單介紹了OSS的一些基本內容。 關於OSS編程更詳細的介紹,可以參考IBM DeveloperWork以前的 相關文獻

1.OSS簡介

OSS的層次結構非常簡單,應用程序通過API(定義於 <soundcard.h>)訪問OSS driver,OSS driver控制聲卡。如下圖所示:


oss結構

聲卡中主要有兩個基本裝置:Mixer和CODEC(ADC/DAC)。Mixer用來控制輸入音量的大小,對應的設備文件爲/dev/mixer;CODEC用來實現錄音(模擬信號轉變爲數字信號)和播放聲音(數字信號轉變爲模擬信號)的功能,對應的設備文件爲/dev/dsp。

開發OSS應用程序的一般流程是:

1)包含OSS頭文件:#include <soundcard.h>
2)打開設備文件,返回文件描述符
3)使用ioctl設置設備的參數,控制設備的特性
4)對於錄音,從設備讀(read)
5)對於播放,向設備寫(write)
6)關閉打開的設備

 




回頁首

 

2.緩衝區設置的性能分析

在設置驅動內部的緩衝區時,存在一個矛盾:在聲卡驅動程序中,爲了防止抖動的出現,保證播放的性能,設置了內部緩衝區-DMA buffer。在播放時,應用程序通過驅動程序首先將音頻數據從應用程序緩衝區-APP buffer,寫入到DMA buffer。接着,由DMA控制器把DMA buffer中的音頻數據發送到DAC(Digital-Analog Converter)。某些時刻CPU非常的繁忙,比如正在從磁盤讀入數據,或者正在重畫屏幕,沒有時間向DMA buffer放入新的音頻數據。DAC由於沒有輸入新的音頻數據,導致聲音播放的間斷,這就出現了聲音的抖動現象。此時,需要將DMA buffer設置的足夠大,使得DAC始終有數據播放。但是,DMA buffer的增大使得每次從APP buffer拷貝的時間也變長,導致了更大的播放延遲。這對於那些延遲敏感的應用場合,如與用戶有交互的音頻應用程序,就會出現問題。

對於這個矛盾,可以從兩個不同的方面分別着手解決。驅動程序採用多緩衝(Multi-buffering)的方式,即將大的DMA buffer分割成多個小的緩衝區,稱之爲fragment,它們的大小相同。驅動程序開始時只需等待兩個fragment滿了就開始播放。這樣可以通過增加fragment的個數來增加緩衝區的大小,但同時每個fragment被限制在合適的大小,也不影響時延。音頻驅動程序中的多緩衝機制一般會利用底層DMA控制器的scatter-gather功能。

另一方面,應用程序也可指導驅動程序選擇合適大小的緩衝區,使得在沒有抖動的情況下,時延儘可能的小。特別的,應用程序將驅動程序中的緩衝通過mmap映射到自己地址空間後,會以自己的方式來處理這些緩衝區(與驅動程序的不一定一致),這時應用程序往往會先根據自己的需要設置驅動程序中內部緩衝區的大小。

在OSS的ioctl接口中,SNDCTL_DSP_SETFRAGMENT就是用來設置驅動程序內部緩衝區大小。具體的用法如下:

int param;
param = ( 0x0004 << 16) + 0x000a;
if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &param) == -1) {  
                ...error handling...
}

 

參數param由兩部分組成:低16位爲fragment的大小,此處0x000a表示fragment大小爲2^0xa,即1024字節;高16位爲fragment的數量,此處爲0x0004,即4個fragement。設置好fragment參數後,通過ioctl的SNDCTL_DSP_SETFRAGMENT命令調整驅動程序中的緩衝區。

爲了給音頻程序的開發者展示緩衝區配置對播放效果的影響,我們將對緩衝區配置與播放性能的關係進行測試。下面首先介紹測試的環境,包括測試方法的原理和測試結果的含義;接着針對兩種情況進行測試,並解釋測試的結果。

測試環境

測試是在PC機上進行的,具體的測試環境參見下表。

項目 參數
CPU PIII 800
內存 256M SDRAM
硬盤 ST 80G UDMA
顯卡 TNT2 m64 16M
聲卡 主板集成(工作在44.1KHz,立體聲,16bit的模式)
內核 Linux kernel 2.4.20(Redhat 9.0)

測試軟件(latencytest)由兩部分組成:音頻播放測試程序、系統運行負載模擬程序。(注:latencytest軟件主要目的是測試內核的時延,但這裏作爲對不同緩衝配置進行比較的工具。)

音頻播放測試程序的工作流程見下面的代碼。爲了保證音頻播放在調度上的優先性,音頻播放測試程序使用SCHED_FIFO調度策略(通過sched_setscheduler())。

while(1)
{
  time1=my_gettime();
  通過空循環消耗一定的CPU時間
  time2=my_gettime();
  write(audio_fd,playbuffer,fragmentsize); 
  time3=my_gettime();
}

 

my_gettime返回當前的時刻,在每個操作的開始和結束分別記錄下時間,就可以得到操作所花費的時間。audio_fd爲打開音頻設備的文件描述符,playbuffer是應用程序中存放音頻數據的緩衝區,也就是APP buffer,fragmentsize爲一個fragment的大小,write操作控制向驅動寫入一個fragment。空循環用來模擬在播放音頻時的CPU運算負載,典型的例子是合成器(synthesizer)實時產生波形後,再進行播放(write)。空循環消耗的時間長度設置爲一個fragment播放時延的80%。

相關指標的計算方法如下:

  • 1) 一個fragment的播放時延(fragm.latency) = fragment大小/(頻率*2*2)。以fragment大小爲512字節和以上的測試環境爲例,一個fragment時延 = 512/(44100*2*2) = 2.90ms[44100表示44.1KHz的採樣頻率,第一個2表示立體聲的兩個聲道,第二個2表示16bit爲2個字節]。
  • 2) 一個fragment的傳輸時延 = 將一個fragment從APP buffer拷貝到DMA buffer的時延。
  • 3) time3-time1 = 一次循環持續的時間 = 空循環消耗的CPU時間 + 一個fragment的傳輸時延。
  • 4) time2-time1 = 空循環消耗的實際CPU時間(cpu latency)。

爲了模擬真實的系統運行情況,在測試程序播放音頻數據的同時,還運行了一個系統負載。一共設置5種負載場景,按順序分別是:

  • 1) 高強度的圖形輸出(使用x11perf來模擬大量的BitBlt操作)
  • 2) 高強度對/proc文件系統的訪問(使用top,更新頻率爲0.01秒)
  • 3) 高強度的磁盤寫(向硬盤寫一個大文件)
  • 4) 高強度的磁盤拷貝(將一個文件拷貝到另一個地方)
  • 5) 高強度的磁盤讀(從硬盤讀一個大文件)

針對不同的系統負載場景,測試分別給出了各自的結果。測試結果以圖形的形式表示,測試結果中圖形的含義留待性能分析時再行解釋。

性能分析

下面,我們分別對兩種緩衝區的配置進行性能比較,

爲了看懂測試結果,需要了解測試結果圖形中各種標識的含義:

  • 1) 紅線:全部緩衝區的播放時延。全部緩衝區播放時延 = 一個fragment時延 x fragment的個數。對於測試的第一種情況,全部緩衝區時延 = 2.90ms x 2 = 5.8ms。
  • 2) 白線:實際的調度時延,即一次循環的時間(time3-time1)。如果白線越過了紅線,則說明所有的緩衝區中音頻數據播放結束後,應用程序仍然沒有來得及將新的數據放入到緩衝區中,此時會出現聲音的丟失,同時overruns相應的增加1。
  • 3) 綠線:CPU執行空循環的時間(即前面的time2-time1)。綠線的標稱值爲fragm.latency x 80%。由於播放進程使用SCHED_FIFO調度策略,所以如果綠線所代表的時間變大,則說明出現了總線競爭,或者是系統長時間的處於內核中。
  • 4) 黃線:一個fragment播放時延。白線應該接近於黃線。
  • 5) 白色的between +/-1ms:實際的調度時延落入到fragm.latency +/-1ms範圍的比例。
  • 6) 白色的between +/-2ms:實際的調度時延落入到fragm.latency +/-2ms範圍的比例。
  • 7) 綠色的between +/-0.2ms:CPU的空循環時延波動+/-0.2ms範圍的比例(即落入到標稱值+/-0.2ms範圍的比例)。
  • 8) 綠色的between +/-0.1ms:CPU的空循環時延波動+/-0.1ms範圍的比例(即落入到標稱值+/-0.1ms範圍的比例)。

第一種情況的緩衝區很小,每個fragment只有512字節,總共的緩衝區大小爲2 x 512 = 1024字節。1024字節只能播放5.8ms。根據OSS的說明,由於Unix是一個多任務的操作系統,有多個進程共享CPU,播放程序必須要保證選擇的緩衝區配置要提供足夠的大小,使得當CPU被其它進程使用時(此時不能繼續向聲卡傳送新的音頻數據),不至於出現欠載的現象。欠載是指應用程序提供音頻數據的速度跟不上聲卡播放的速度,這時播放就會出現暫停或滴答聲。因此,不推薦使用fragment大小小於256字節的設置。從測試結果中看到,不管使用那種系統負載,都會出現欠載的現象,特別是在寫硬盤的情況下,一共發生了14次欠載(overruns = 14)。

當然,對於那些實時性要求高的音頻播放程序,希望使用較小的緩衝區,因爲只有這樣才能保證較小的時延。在上面的測試結果我們看到了欠載的現象,但是,這並不完全是緩衝區過小所導致的。實際上,由於Linux內核是不可搶佔的,所以無法確知Linux在內核中停留的時間,因此也就無法保證以確定的速度調度某個進程,即使現在播放程序使用了SCHED_FIFO調度策略。從這個角度來說,多媒體應用(如音頻播放)對操作系統內核提出了更高的要求。在目前Linux內核的情況下,較小的調度時延可以通過一些專門的內核補丁(low-latency patch)達到。不過我們相信Linux2.6新內核會有更好的表現。

第二種情況的緩衝區要大得多,總共的緩衝區大小爲4 x 2048 = 8192字節。8192字節可以播放0.046秒。從測試的圖形來看,結果比較理想,即使在系統負載較重的情況,仍然能夠基本保證播放時延的要求,而且沒有出現一次欠載的現象。

當然,並不是說緩衝區越大越好,如果繼續選擇更大的緩衝區,將會產生比較大的時延,對於實時性要求比較高的音頻流來說,是不能接受的。從測試結果中可以看到,第二種配置的時延抖動比第一種配置要大得多。不過,在一般情況下,驅動程序會根據硬件的情況,選擇一個缺省的緩衝區配置,播放程序通常不需要修改驅動程序的緩衝區配置,而可以獲得較好的播放效果。

 




回頁首

 

3.非阻塞寫(non-blocking write)

如果播放程序寫入的速度超過了DAC的播放速度,DMA buffer就會充滿了音頻數據。應用程序調用write時就會因爲沒有空閒的DMA buffer而被阻塞,直到DMA buffer出現空閒爲止。此時,從某種程度來說,應用程序的推進速度依賴於播放的速度,不同的播放速度就會產生不同的推進速度。因此,有時我們不希望write被阻塞,這就需要我們能夠知道DMA buffer的使用情況。

for (;;) {
audio_buf_info info;
/* Ask OSS if there is any free space in the buffer. */
if (ioctl(dsp,SNDCTL_DSP_GETOSPACE,&info) != 0) {
perror("Unable to query buffer space");
close(dsp);
return 1;
};
/* Any empty fragments? */
if (info.fragments > 0) break;
/* Not enough free space in the buffer. Waste time. */
usleep(100);
};

 

以上的代碼不停的查詢驅動程序中是否有空的fragment(SNDCTL_DSP_GETOSPACE),如果沒有,則進入睡眠(usleep(100)),此時應用程序做其它的事情,比如更新畫面,網絡傳輸等。如果有空閒的fragment(info.fragments > 0),則退出循環,接着就可以進行非阻塞的write了。

 




回頁首

 

4.DMA buffer的直接訪問(mmap)

除了依賴於操作系統內核提供更好的調度性能,音頻播放應用程序也可以採用一些技術以提高音頻播放的實時性。繞過APP buffer,直接訪問DMA buffer的mmap方法就是其中之一。

我們知道,將音頻數據輸出到音頻設備通常使用系統調用write,但是這會帶來性能上的損失,因爲要進行一次從用戶空間到內核空間的緩衝區拷貝。這時,可以考慮利用mmap系統調用,獲得直接訪問DMA buffer的能力。DMA控制器不停的掃描DMA buffer,將數據發送到DAC。這有點類似於顯卡對顯存的操作,大家都知道,GUI可以通過mmap將framebuffer(顯存)映射到自己的地址空間,然後直接操縱顯存。這裏的DMA buffer就是聲卡的framebuffer。

理解mmap方法的最好方法是通過實際的例子, 代碼1(list1.c)

代碼中有詳細的註釋,這裏只給出一些說明。

PlayerDMA函數的參數samples指向存放音頻數據的緩衝,rate/bits/channels分別說明音頻數據的採樣速率、每次採樣的位數、聲道數。

在打開/dev/dsp以後,根據/rate/bits/channels參數的要求配置驅動程序。需要注意的是,這些要求並一定能得到滿足,驅動程序要根據自己的情況選擇,因此在配置後,需要再次查詢,獲取驅動程序真正使用的參數值。

在使用mmap之前,要查看驅動程序是否支持這種模式(SNDCTL_DSP_GETCAPS)。使用SNDCTL_DSP_GETOSPACE得知驅動選擇的framgment大小和個數,就可以計算出全部DMA buffer的大小dmabuffer_size。

mmap將dmabuffer_size大小的DMA buffer映射到調用進程的地址空間,DMA buffer在應用進程的起始地址爲dmabuffer。以後就可以直接使用指針dmabuffer訪問DMA buffer了。這裏需要對mmap中的參數做些解釋。

音頻驅動程序針對播放和錄音分別有各自的緩衝區,mmap不能同時映射這兩組緩衝,具體選擇映射哪個緩衝取決於mmap的prot參數。PROT_READ選擇輸入(錄音)緩衝,PROT_WRITE選擇輸出(播放)緩衝,代碼中使用了PROT_WRITE|PROT_READ,也是選擇輸出緩衝。(這是BSD系統的要求,如果只有PROT_WRITE,那麼每次對緩衝的訪問都會出現segmentation/bus error)。

一旦DMA buffer被mmap後,就不能再通過read/write接口來控制驅動程序了。只能通過SNDCTL_DSP_SETTRIGGER打開DAC的使能位,當然,先要關閉使能位。

DMA一旦啓動後,就會周而復始的掃描DMA buffer。當然我們總是希望提前爲DMA準備好新的數據,使得DMA的播放始終連續。因此,PlayerDMA函數將mmap後的DMA buffer分割成前後兩塊,中間設置一個界限。當DMA掃描前面一塊時,就填充後面一塊。一旦DMA越過了界限,就去填充前面一塊。

使用mmap的問題是,不是所有的聲卡驅動程序都支持mmap方式。因此,在出現不兼容的情況下,應用程序要能夠轉而去使用傳統的方式。

最後,爲了能深入的理解mmap的實現原理,我們以某種聲卡驅動程序爲例,介紹了其內部mmap函數時具體實現。 代碼2(list2.c)

audio_mmap()是實現mmap接口的函數,它首先根據mmap調用的prot參數(vma->vm_flags),選擇合適的緩衝(輸入還是輸出);vma->vm_end - vma->vm_start爲需要映射到應用進程地址空間的大小,必須和DMA buffer的大小(s->fragsize * s->nbfrags)一致;如果DMA buffer還沒有建立,則調用audio_setup_buf(s)建立;接着對所有的fragment,從映射起始地址開始(vma->vm_start),建立實際物理地址與映射的虛擬地址之間的對應關係(remap_page_range)。最後設置mmap標誌(s->mapped = 1)。

 




回頁首

 

5.結束語

當然,除了上面所討論的問題以外,音頻應用的開發還有很多實際的問題需要去面對,比如多路音頻流的合併,各種音頻文件格式的打開等等。

OSS音頻接口存在於Linux內核中許多年了,由於在體系結構上有許多的侷限性,在Linux 2.6內核中引入了一種全新的音頻體系和接口——ALSA(Advanced Linux Sound Architecture),它提供了很多比OSS更好的特性,包括完全的thread-safe和SMP-safe,模塊化的設計,支持多個聲卡等等。爲了保持和OSS接口的兼容性,ALSA還提供了OSS的仿真接口,使得那些爲OSS接口開發的大量應用程序仍然能夠在新的ALSA體系下正常的工作。

 

參考資料

  1. Open Sound System Programmer's Guide, http://www.opensound.com
  2. John R. Hall, Programming Linux Games, Loki Software, Inc
  3. latencytest-0.42-png.tar.gz, http://www.gardena.net/benno/linux/audio/

 

關於作者

 

湯凱,通過 [email protected]可以跟他聯繫。

 

 

/*list1.c*/

/* DMA sound playback with OSS. */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/mman.h>

/* Plays a sound with OSS (/dev/dsp), using direct DMA access.
   Returns 0 on successful playback, nonzero on error. */
int PlayerDMA(u_int8_t *samples, unsigned int bits, unsigned int channels,
              unsigned int rate, unsigned int bytes)
{
    /* file handle for /dev/dsp */
    int dsp = 0;

    /* Variables for ioctl's. */
    unsigned int requested, ioctl_format, ioctl_channels,
	ioctl_rate, ioctl_caps, ioctl_enable;
    audio_buf_info ioctl_info;
	
    /* Buffer information. */
    unsigned int frag_count, frag_size;
    u_int8_t *dmabuffer = NULL;
    unsigned int dmabuffer_size = 0;
    int dmabuffer_flag = 0;

    /* Playback status variables. */
    unsigned int position = 0, done = 0;

    /* Attempt to open /dev/dsp for playback. We need to open for
       read/write in order to mmap() the device file. */
    dsp = open("/dev/dsp",O_RDWR);
	
    /* This could very easily fail, so we must handle errors. */
    if (dsp < 0) {
	perror("DMA player: error opening /dev/dsp for playback");
	goto error;
    }
	
    /* Select the appropriate sample format. */
    switch (bits) {
    case 8: ioctl_format = AFMT_U8; break;		
    case 16: ioctl_format = AFMT_S16_NE; break;		
    default: printf("DMA player: unknown sample size./n");
	goto error;
    }

    /* We've decided on a format. We now need to pass it to OSS. */
    requested = ioctl_format;
    if (ioctl(dsp,SNDCTL_DSP_SETFMT,&ioctl_format) == -1) {
	perror("DMA player: format selection failed");
	goto error;
    }
	
    /* ioctl's usually modify their arguments. SNDCTL_DSP_SETFMT
       sets its integer argument to the sample format that OSS
       actually gave us. This could be different than what we
       requested. For simplicity, we will not handle this situation. */
    if (requested != ioctl_format) {
	printf("DMA player: unsupported sample format./n");
	goto error;
    }
		
    /* We must inform OSS of the number of channels (mono or stereo)
       before we set the sample rate. This is due to limitations in
       some (older) sound cards. */
    ioctl_channels = channels;
    if (ioctl(dsp,SNDCTL_DSP_CHANNELS,&ioctl_channels) == -1) {
	perror("DMA player: unable to set the number of channels");
	goto error;
    }

    /* OSS might not have granted our request, even if the ioctl
       succeeded. */
    if (channels != ioctl_channels) {
	printf("DMA player: unable to set the number of channels./n");
	goto error;
    }

    /* We can now set the sample rate. */
    ioctl_rate = rate;
    if (ioctl(dsp,SNDCTL_DSP_SPEED,&ioctl_rate) == -1) {
	perror("DMA player: unable to set sample rate");
	goto error;
    }
	
    /* OSS sets the SNDCTL_DSP_SPEED argument to the actual sample rate,
       which may be different from the requested rate. In this case, a
       production-quality player would upsample or downsample the sound
       data. We'll simply report an error. */
    if (rate != ioctl_rate) {
	printf("DMA player: unable to set the desired sample rate./n");
	goto error;
    }

    /* Now check for DMA compatibility. It's quite possible that the driver
       won't support this. It would be a *very* good idea to provide a
       fallback in case DMA isn't supported - there are some sound cards that
       simply don't work with the DMA programming model at all. */
    if (ioctl(dsp,SNDCTL_DSP_GETCAPS,&ioctl_caps) != 0) {
	perror("DMA player: unable to read sound driver capabilities");
	goto error;
    }
	
    /* The MMAP and TRIGGER bits must be set for this to work.
       MMAP gives us the ability to access the DMA buffer directly,
       and TRIGGER gives us the ability to start the sound card's
       playback with a special ioctl. */
    if (!(ioctl_caps & DSP_CAP_MMAP) || !(ioctl_caps & DSP_CAP_TRIGGER)) {
	printf("DMA player: this sound driver is not capable of direct DMA.");
	goto error;
    }

    /* Query the sound driver for the actual fragment configuration
	   so that we can calculate the total size of the DMA buffer.
	   Note that we haven't selected a particular fragment size or
	   count. Fragment boundaries are meaningless in a mapped buffer;
	   we're really just interested in the total size. */
    if (ioctl(dsp,SNDCTL_DSP_GETOSPACE,&ioctl_info) != 0) {
	perror("DMA player: unable to query buffer information");
	goto error;
    }

    frag_count = ioctl_info.fragstotal;
    frag_size = ioctl_info.fragsize;
    dmabuffer_size = frag_count * frag_size;
	
    /* We're good to go. Map a buffer onto the audio device. */
    dmabuffer = mmap(NULL,
		     dmabuffer_size,         /* length of region to map */
		     PROT_WRITE|PROT_READ             /* select the output buffer
						(PROT_READ alone selects input) */
		     /* NOTE: I had to add PROT_READ to
			make this work with FreeBSD.
			However, this causes the code to
			fail under Linux. SNAFU. */
		     MAP_FILE | MAP_SHARED,	 /* see the mmap() manual page */
		     dsp,			 /* opened file to map */
		     0);			 /* start at offset zero */

    /* This could fail for a number of reasons. */
    if (dmabuffer == (u_int8_t *)MAP_FAILED) {
	perror("DMA player: unable to mmap a DMA buffer");
	goto error;
    }
	
    /* Clear the buffer to avoid static at the beginning. */
    memset(dmabuffer, 0, dmabuffer_size);

    /* The DMA buffer is ready! Now we can start playback by toggling
       the device's PCM output bit. Yes, this is a very hacky interface.
       We're actually using the OSS "trigger" functionality here. */
    ioctl_enable = 0;
    if (ioctl(dsp, SNDCTL_DSP_SETTRIGGER, &ioctl_enable) != 0) {
	perror("DMA player: unable to disable PCM output");
	goto error;
    }
	
    ioctl_enable = PCM_ENABLE_OUTPUT;
    if (ioctl(dsp, SNDCTL_DSP_SETTRIGGER, &ioctl_enable) != 0) {
	perror("DMA player: unable to enable PCM output");
	goto error;
    }

    /* The done variable simply makes sure that the last chunk actually
       gets played. We'll play a brief period of silence after the last
       data chunk. */
    while (done < 4) {
	struct count_info status;
	unsigned int i;
		
	/* Find the location of the DMA controller within the buffer.
	   This will be exact at least to the level of a fragment. */
	if (ioctl(dsp, SNDCTL_DSP_GETOPTR, &status) != 0) {
	    perror("DMA player: unable to query playback status");
	    goto error;
	}
		
	/* Our buffer is comprised of several fragments. However, in DMA
	   mode, it is safe to treat the entire buffer as one big block.
	   We will divide it into two logical chunks. While the first chunk
	   is playing, we will fill the second with new samples, and 
	   vice versa. With a small buffer, we will still enjoy low latency.
		   
	   status.ptr contains the offset of the DMA controller within
	   the buffer. */

	if (dmabuffer_flag == 0) {
	    /* Do we need to refill the first chunk? */
	    if ((unsigned)status.ptr < dmabuffer_size/2) {
		unsigned int amount;
		
		/* Copy data into the DMA buffer. */
		if (bytes - position < dmabuffer_size/2) {
		    amount = bytes-position;
		} else amount = dmabuffer_size/2;
		
		for (i = 0; i < amount; i++) {
		    dmabuffer[i+dmabuffer_size/2] = samples[position+i];
		}
		
		/* Zero the rest of this half. */
		for (; i < dmabuffer_size/2; i++) {
		    dmabuffer[i+dmabuffer_size/2] = 0;
		}
								
		/* Update the buffer position. */
		position += amount;
			
		/* Next update will be the first chunk. */
		dmabuffer_flag = 1;
				
		/* Have we reached the end? */
		if (position >= bytes) done++;
	    }
	} else if (dmabuffer_flag == 1) {
	    /* Do we need to refill the first chunk? */
	    if ((unsigned)status.ptr >= dmabuffer_size/2) {
		unsigned int amount;

		/* Copy data into the DMA buffer. */
		if (bytes - position < dmabuffer_size/2) {
		    amount = bytes-position;
		} else amount = dmabuffer_size/2;
				
		for (i = 0; i < amount; i++) {
		    dmabuffer[i] = samples[position+i];
		}

		/* Zero the rest of this half. */
		for (; i < dmabuffer_size/2; i++) {
		    dmabuffer[i] = 0;
		}
				
		/* Update the buffer position. */
		position += amount;
							
		/* Next update will be the second chunk. */
		dmabuffer_flag = 0;

		/* Have we reached the end? */
		if (position >= bytes) done++;
	    }
	}

	WritePlaybackStatus(position, bytes, channels, bits, rate); 
	printf(" (%i)/r", dmabuffer_flag);
	fflush(stdout);
		
	/* Wait a while. A game would normally do the rest of its
	   processing here. */
	usleep(50);
    }

    printf("/n");

    munmap(dmabuffer,dmabuffer_size);
    close(dsp);

    return 0;
	
    /* Error handler. goto's are normally bad, but they make sense here. */
 error:
    if (dmabuffer != NULL)
	munmap(dmabuffer,dmabuffer_size);
    if (dsp > 0) close(dsp);
		
    return -1;
}
/*list2.c*/
static int audio_mmap(struct file *file, struct vm_area_struct *vma)
{
	audio_state_t *state = file->private_data;
	audio_stream_t *s;
	unsigned long size, vma_addr;
	int i, ret;

	if (vma->vm_pgoff != 0)
		return -EINVAL;

	if (vma->vm_flags & VM_WRITE) {
		if (!state->wr_ref)
			return -EINVAL;;
		s = state->output_stream;
	} else if (vma->vm_flags & VM_READ) {
		if (!state->rd_ref)
			return -EINVAL;
		s = state->input_stream;
	} else return -EINVAL;

	if (s->mapped)
		return -EINVAL;
	size = vma->vm_end - vma->vm_start;
	if (size != s->fragsize * s->nbfrags)
		return -EINVAL;
	if (!s->buffers && audio_setup_buf(s))
		return -ENOMEM;
	vma_addr = vma->vm_start;
	for (i = 0; i < s->nbfrags; i++) {
		audio_buf_t *buf = &s->buffers[i];
		if (!buf->master)
			continue;
		ret = remap_page_range(vma_addr, buf->dma_desc->dsadr,
				       buf->master, vma->vm_page_prot);
		if (ret)
			return ret;
		vma_addr += buf->master;
	}
	for (i = 0; i < s->nbfrags; i++)
		s->buffers[i].dma_desc->ddadr &= ~DDADR_STOP;
	s->mapped = 1;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章