今天要在linux下搞音頻編程,在網上查閱了一下資料,網上很多資料都是在linux下直接對/dev/dsp進行編程的,因為在以往的linux系統中,我們是可以通過cat xxx.wav /dev/dsp 來很容易的播放一個音頻文件,在應用程序中,也可以直接操作/dev/dsp,實現聲音的播放:打開->寫入(實際上就能播放)->關閉。
然而在換成了ubuntu-12.04 LST后,我發現/dev中根本找不到dsp,之前直接操作/dev/dsp的程序都無法正常運行,而是 can't find /dev/dsp.
經過我再三查找,發現現在基本上都不用dsp了,大多數人都在用ALSA音頻編程(其實我也是個菜鳥)。首先介紹一下一些關於ALSA編程的知識:
1、GNU/Linux 系統下三大主流聲卡驅動程序集:
Linux 有三個主流的聲卡驅動程序集:OSS/Lite(也稱為OSS/Free)、OSS/Full
(商業軟件)、ALSA(自由軟件)。
OSS/Lite 是現在linux內核中自帶的聲卡驅動程序集,最初由 Hannu Savolainen
開發。后來 Hannu 跑去開發 Open Sound System(也就是上面所說的OSS/Full)。
由於 Hannu 的“逃跑”,RH 資助 Alan Cox 增強 Hannu 開發的驅動程序,並使它們
完全模塊化。現在 Alan Cox 是內核聲卡驅程集的維護人。OSS/Lite 從kernel-2.0開
始並入內核,現在大家使用的聲卡驅程默認都是OSS/Lite中的。
OSS/Full 是由 4Front Technologies 開發並銷售的商業軟件。它可以驅動很多
聲卡並且可以用在很多 UNIX 系統中。OSS/Full 完全兼容以前基於 OSS/Lite 開發
的應用程序。作為一個商業軟件,你雖然可以使用它,但是你得不到它的源代碼,並且
你必須為此而付錢。
ALSA 是linux內核的下一代標准聲卡驅動集。開始時 Jaroslav Kysela 等人為
Gravis UltraSound Card 開發驅動程序,后來該計划改名為 ALSA「先進的linux音頻
體系」,因為他們認為 ALSA 比原來內核中的 OSS/Lite 驅動程序集更優秀,完全可以
代替 OSS/Lite。他們是對的,ALSA 支持的聲卡比 OSS/Lite 多,完全兼容以前基於
OSS 開發的程序,SMP(多處理器) 與 線程安全設計,並且從 2.5 分支的內核開始,
ALSA 的驅動程序集開始並入內核,大家可以在今年的 2.6 版本的內核中看到使用它們。
2、為什么要使用 ALSA 開發音頻程序
首先,ALSA 是 linux 以后聲卡驅動程序的標准,OSS/Lite 遲早會從內核中去除。
開發基於 ALSA 的音頻程序可以保證以后的兼容。
其次,我們簡單比較一下開發基於 OSS 與 ALSA 的方法。
OSS 向應用程序提供了一系列的系統接口。開發基於 OSS 的應用程序需要使用
open,close,ioctl,read,write 等低級系統調用來完成音頻設備的控制、音頻流的輸入
輸出。
而 ALSA 則為應用程序開發人員提供了一個優秀的音頻庫。利用該音頻庫,開發
人員可以方便快捷地開發出自己的應用程序,細節則留給音頻庫內部處理。當然 ALSA
也提供了類似於 OSS 的系統接口,不過 ALSA 的開發者建議應用程序開發者使用音頻
庫而不是驅程API。
3、那么我該從何開始呢
第一步當然是安裝 ALSA 驅動程序與音頻庫。
當前 ALSA 有兩個分支,一個是以前的0.5版本,一個是現在的0.9。ALSA的開發者
已經不支持0.5版本了,所以我們要使用0.9。大家可以在 ALSA 的主頁
www.alsa-project.org 上下載安裝。這個頁面上的信息對大家安裝很有用:
www.alsa-project.org/alsa-doc ,建議先瀏覽一下。
第二步是參考文檔與例子。
在 ALSA 的文檔頁面上有兩篇為應用程序開發者提供的文章:
ALSA 0.9.0 HOWTO [ http://www.suse.de/~mana/alsa090_howto.html ]
Howto use the ALSA API [ http://www.op.net/~pbd/alsa-audio.html ]
當然,還有音頻庫API的在線參考:
要是你已經完成以上幾步的話,那么你就應該開始開發了。
4. ALSA設備文件結構
我們從alsa在Linux中的設備文件結構開始我們的alsa之旅. 看看我的電腦中的alsa驅動的設備文件結構:
$ cd /dev/snd $ ls -l crw-rw----+ 1 root audio 116, 8 2011-02-23 21:38 controlC0 crw-rw----+ 1 root audio 116, 4 2011-02-23 21:38 midiC0D0 crw-rw----+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c crw-rw----+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p crw-rw----+ 1 root audio 116, 5 2011-02-23 21:38 pcmC0D1p crw-rw----+ 1 root audio 116, 3 2011-02-23 21:38 seq crw-rw----+ 1 root audio 116, 2 2011-02-23 21:38 timer $
我們可以看到以下設備文件:
controlC0 --> 用於聲卡的控制,例如通道選擇,混音,麥克風的控制等 midiC0D0 --> 用於播放midi音頻 pcmC0D0c --〉 用於錄音的pcm設備 pcmC0D0p --〉 用於播放的pcm設備 seq --〉 音序器 timer --〉 定時器
其中,C0D0代表的是聲卡0中的設備0,pcmC0D0c最后一個c代表capture,pcmC0D0p最后一個p代表playback,這些都是alsa-driver中的命名規則。從上面的列表可以看出,我的聲卡下掛了6個設備,根據聲卡的實際能力,驅動實際上可以掛上更多種類的設備,在include/sound/core.h中,定義了以下設備類型:
#define SNDRV_DEV_TOPLEVEL ((__force snd_device_type_t) 0)
#define SNDRV_DEV_CONTROL ((__force snd_device_type_t) 1)
#define SNDRV_DEV_LOWLEVEL_PRE ((__force snd_device_type_t) 2)
#define SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0x1000)
#define SNDRV_DEV_PCM ((__force snd_device_type_t) 0x1001)
#define SNDRV_DEV_RAWMIDI ((__force snd_device_type_t) 0x1002)
#define SNDRV_DEV_TIMER ((__force snd_device_type_t) 0x1003)
#define SNDRV_DEV_SEQUENCER ((__force snd_device_type_t) 0x1004)
#define SNDRV_DEV_HWDEP ((__force snd_device_type_t) 0x1005)
#define SNDRV_DEV_INFO ((__force snd_device_type_t) 0x1006)
#define SNDRV_DEV_BUS ((__force snd_device_type_t) 0x1007)
#define SNDRV_DEV_CODEC ((__force snd_device_type_t) 0x1008)
#define SNDRV_DEV_JACK ((__force snd_device_type_t) 0x1009)
#define SNDRV_DEV_LOWLEVEL ((__force snd_device_type_t) 0x2000)
通常,我們更關心的是pcm和control這兩種設備。
5.一些例子,這些例子在官方文檔也有,請自行查閱
1.)顯示一些PCM的類型和格式:
#include <iostream> #include <alsa/asoundlib.h> int main() { std::cout << "ALSA library version: " << SND_LIB_VERSION_STR << std::endl; std::cout << "PCM stream types: " << std::endl; for (int val=0; val <= SND_PCM_STREAM_LAST; ++val) std::cout << snd_pcm_stream_name((snd_pcm_stream_t)val) << std::endl; std::cout << std::endl; std::cout << "PCM access types: " << std::endl; for (int val=0; val <= SND_PCM_ACCESS_LAST; ++val) std::cout << snd_pcm_access_name((snd_pcm_access_t)val) << std::endl; std::cout << std::endl; std::cout << "PCM subformats: " << std::endl; for (int val=0; val <= SND_PCM_SUBFORMAT_LAST; ++val) std::cout << snd_pcm_subformat_name((snd_pcm_subformat_t)val) << " (" << snd_pcm_subformat_description((snd_pcm_subformat_t)val) << ")" << std::endl; std::cout << std::endl; std::cout << "PCM states: " << std::endl; for (int val=0; val <= SND_PCM_STATE_LAST; ++val) std::cout << snd_pcm_state_name((snd_pcm_state_t)val) << std::endl; std::cout << std::endl; std::cout << "PCM formats: " << std::endl; for (int val=0; val <= SND_PCM_FORMAT_LAST; ++val) std::cout << snd_pcm_format_name((snd_pcm_format_t)val) << " (" << snd_pcm_format_description((snd_pcm_format_t)val) << ")" << std::endl; std::cout << std::endl; }
2.)打開PCM設備和設置參數
#include <iostream> #include <alsa/asoundlib.h> int main() { int rc; snd_pcm_t* handle; snd_pcm_hw_params_t* params; unsigned int val, val2; int dir; snd_pcm_uframes_t frames; if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { std::cerr << "unable to open pcm devices: " << snd_strerror(rc) << std::endl; exit(1); } snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(handle, params); snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(handle, params, 2); val = 44100; snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); if ( (rc = snd_pcm_hw_params(handle, params)) < 0) { std::cerr << "unable to set hw parameters: " << snd_strerror(rc) << std::endl; exit(1); } std::cout << "PCM handle name = " << snd_pcm_name(handle) << std::endl; std::cout << "PCM state = " << snd_pcm_state_name(snd_pcm_state(handle)) << std::endl; snd_pcm_hw_params_get_access(params, (snd_pcm_access_t *)&val); std::cout << "access type = " << snd_pcm_access_name((snd_pcm_access_t)val) << std::endl; snd_pcm_hw_params_get_format(params, (snd_pcm_format_t*)(&val)); std::cout << "format = '" << snd_pcm_format_name((snd_pcm_format_t)val) << "' (" << snd_pcm_format_description((snd_pcm_format_t)val) << ")" << std::endl; snd_pcm_hw_params_get_subformat(params, (snd_pcm_subformat_t *)&val); std::cout << "subformat = '" << snd_pcm_subformat_name((snd_pcm_subformat_t)val) << "' (" << snd_pcm_subformat_description((snd_pcm_subformat_t)val) << ")" << std::endl; snd_pcm_hw_params_get_channels(params, &val); std::cout << "channels = " << val << std::endl; snd_pcm_hw_params_get_rate(params, &val, &dir); std::cout << "rate = " << val << " bps" << std::endl; snd_pcm_hw_params_get_period_time(params, &val, &dir); std::cout << "period time = " << val << " us" << std::endl; snd_pcm_hw_params_get_period_size(params, &frames, &dir); std::cout << "period size = " << static_cast<int>(frames) << " frames" << std::endl; snd_pcm_hw_params_get_buffer_time(params, &val, &dir); std::cout << "buffer time = " << val << " us" << std::endl; snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val); std::cout << "buffer size = " << val << " frames" << std::endl; snd_pcm_hw_params_get_periods(params, &val, &dir); std::cout << "periods per buffer = " << val << " frames" << std::endl; snd_pcm_hw_params_get_rate_numden(params, &val, &val2); std::cout << "exact rate = " << val/val2 << " bps" << std::endl; val = snd_pcm_hw_params_get_sbits(params); std::cout << "significant bits = " << val << std::endl; snd_pcm_hw_params_get_tick_time(params, &val, &dir); std::cout << "tick time = " << val << " us" << std::endl; val = snd_pcm_hw_params_is_batch(params); std::cout << "is batch = " << val << std::endl; val = snd_pcm_hw_params_is_block_transfer(params); std::cout << "is block transfer = " << val << std::endl; val = snd_pcm_hw_params_is_double(params); std::cout << "is double = " << val << std::endl; val = snd_pcm_hw_params_is_half_duplex(params); std::cout << "is half duplex = " << val << std::endl; val = snd_pcm_hw_params_is_joint_duplex(params); std::cout << "is joint duplex = " << val << std::endl; val = snd_pcm_hw_params_can_overrange(params); std::cout << "can overrange = " << val << std::endl; val = snd_pcm_hw_params_can_mmap_sample_resolution(params); std::cout << "can mmap = " << val << std::endl; val = snd_pcm_hw_params_can_pause(params); std::cout << "can pause = " << val << std::endl; val = snd_pcm_hw_params_can_resume(params); std::cout << "can resume = " << val << std::endl; val = snd_pcm_hw_params_can_sync_start(params); std::cout << "can sync start = " << val << std::endl; snd_pcm_close(handle); return 0; }
3.)一個簡單的聲音播放程序
#include <iostream> #include <alsa/asoundlib.h> int main() { long loops; int rc; int size; snd_pcm_t* handle; snd_pcm_hw_params_t* params; unsigned int val; int dir; snd_pcm_uframes_t frames; char* buffer; if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { std::cerr << "unable to open pcm device: " << snd_strerror(rc) << std::endl; exit(1); } snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(handle, params); snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(handle, params, 2); val = 44100; snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); frames = 32; snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); if ( (rc = snd_pcm_hw_params(handle, params)) < 0) { std::cerr << "unable to set hw paramseters: " << snd_strerror(rc) << std::endl; exit(1); } snd_pcm_hw_params_get_period_size(params, &frames, &dir); size = frames * 4; buffer = new char[size]; snd_pcm_hw_params_get_period_time(params, &val, &dir); loops = 5000000 / val; while (loops > 0) { loops--; if ( (rc = read(0, buffer, size)) == 0) { std::cerr << "end of file on input" << std::endl; break; } else if (rc != size) std::cerr << "short read: read " << rc << " bytes" << std::endl; if ( (rc = snd_pcm_writei(handle, buffer, frames)) == -EPIPE) { std::cerr << "underrun occurred" << std::endl; snd_pcm_prepare(handle); } else if (rc < 0) std::cerr << "error from writei: " << snd_strerror(rc) << std::endl; else if (rc != (int)frames) std::cerr << "short write, write " << rc << " frames" << std::endl; } snd_pcm_drain(handle); snd_pcm_close(handle); free(buffer); return 0; }
4.)一個簡單的記錄聲音的程序
#include <iostream> #include <alsa/asoundlib.h> int main() { long loops; int rc; int size; snd_pcm_t* handle; snd_pcm_hw_params_t* params; unsigned int val; int dir; snd_pcm_uframes_t frames; char* buffer; if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) { std::cerr << "unable to open pcm device: " << snd_strerror(rc) << std::endl; exit(1); } snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(handle, params); snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(handle, params, 2); val = 44100; snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); if ( (rc = snd_pcm_hw_params(handle, params)) < 0) { std::cerr << "unable to set hw parameters: " << snd_strerror(rc) << std::endl; exit(1); } snd_pcm_hw_params_get_period_size(params, &frames, &dir); size = frames * 4; buffer = new char[size]; snd_pcm_hw_params_get_period_time(params, &val, &dir); loops = 5000000 / val; while (loops > 0) { loops --; rc = snd_pcm_readi(handle, buffer, frames); if (rc == -EPIPE) { std::cerr << "overrun occurred" << std::endl; snd_pcm_prepare(handle); } else if (rc < 0) std::cerr << "error from read: " << snd_strerror(rc) << std::endl; else if ( rc != (int)frames) std::cerr << "short read, read " << rc << " frames" << std::endl; rc = write(1, buffer, size); if (rc != size) std::cerr << "short write: wrote " << rc << " bytes" << std::endl; } snd_pcm_drain(handle); snd_pcm_close(handle); free(buffer); return 0; }
注意:編譯的時候記得加上參數,g++ xxx.cpp -o xxx -lasound;如果編譯時出現如下錯誤:alsa/asoundlib.h: No such file or directory
缺少一個庫:
apt-get install libasound2-dev
OK!