轉載自:http://blog.chinaunix.net/uid-25272011-id-3153434.html
?
最近在調試一款原相PAP7501攝像頭中的USB的麥克風,USB層走的應該是標準的UAC協議,具體可以見USB的官網:http://www.usb.org/developers/devclass_docs#approved,而音頻部分則可以跑目前Linux標準的ALSA的PCM接口,對于硬件CODEC來說,與其是完全兼容的。
???? 給出一份參考代碼:
???? 這個是仿照arecord寫的一個簡略的測試代碼,保存為wav格式的。
1、recod.c
- /*
- This example reads from the default PCM device
- and writes to standard output for 5 seconds of data.
- */
- /* Use the newer ALSA API */
- #define ALSA_PCM_NEW_HW_PARAMS_API
- #include <alsa/asoundlib.h>
- /**************************************************************/
- #define ID_RIFF 0x46464952
- #define ID_WAVE 0x45564157
- #define ID_FMT 0x20746d66
- #define ID_DATA 0x61746164
- typedef unsigned long uint32_t;
- typedef unsigned short uint16_t;
- #define FORMAT_PCM 1
- static uint32_t totle_size = 0;
- struct wav_header {
- ????/* RIFF WAVE Chunk */
- ????uint32_t riff_id;
- ????uint32_t riff_sz;
- ????uint32_t riff_fmt;
- ????/* Format Chunk */
- ????uint32_t fmt_id;
- ????uint32_t fmt_sz;
- ????uint16_t audio_format;
- ????uint16_t num_channels;
- ????uint32_t sample_rate;
- ????uint32_t byte_rate; /* sample_rate * num_channels * bps / 8 */
- ????uint16_t block_align; /* num_channels * bps / 8 */
- ????uint16_t bits_per_sample;
- ????/* Data Chunk */
- ????uint32_t data_id;
- ????uint32_t data_sz;
- }__attribute__((packed));
- static struct wav_header hdr;
- /**************************************************************/
- int record_file(unsigned rate, unsigned channels, int fd, unsigned count)
- {
- ????long loops;
- ??? int val;
- ????int rc;
- ????int size;
- ????snd_pcm_t *handle;
- ????snd_pcm_hw_params_t *params;
- ????int dir;
- ????snd_pcm_uframes_t frames;
- ????char *buffer;????????????????????/* TODO */
- ????/* Open PCM device for recording (capture). */
- ????rc = snd_pcm_open(&handle, "plughw:0,0", SND_PCM_STREAM_CAPTURE, 0);
- ????if (rc < 0) {
- ????????fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
- ????????exit(1);
- ????}
- ????/* Allocate a hardware parameters object. */
- ????snd_pcm_hw_params_alloca(¶ms);
- ????/* Fill it in with default values. */
- ????snd_pcm_hw_params_any(handle, params);
- ????/* Set the desired hardware parameters. */
- ????/* Interleaved mode */
- ????snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
- ????/* Signed 16-bit little-endian format */
- ????snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
- ????/* Two channels (stereo) */
- ????snd_pcm_hw_params_set_channels(handle, params, channels);
- ????/* rate bits/second sampling rate (CD quality) */
- ????snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir);
- ????/* Set period size to 32 frames. */
- ????frames = 320;???? /* 這邊的大小也不是絕對的,可以調整 */
- ????snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
- ????/* Write the parameters to the driver */
- ????rc = snd_pcm_hw_params(handle, params);
- ????if (rc < 0) {
- ????????fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc));
- ????????exit(1);
- ????}
- ????/* Use a buffer large enough to hold one period */
- ????snd_pcm_hw_params_get_period_size(params, &frames, &dir);/* 獲取實際的frames */
- ????
- ????size = frames * 2; /* 2 bytes/sample, 1 channels */
- ????buffer = (char *) malloc(size);
- ????/* We want to loop for 20 seconds 時間不一定準確 */
- ????snd_pcm_hw_params_get_period_time(params, &val, &dir);
- ????loops = 20000000 / val;
- ????
- ????while (loops > 0) {
- ????????loops--;
- ????????rc = snd_pcm_readi(handle, buffer, frames);
- ????????if (rc == -EPIPE) {
- ???????? /* EPIPE means overrun */
- ???????? fprintf(stderr, "overrun occurred\n");
- ???????? snd_pcm_prepare(handle);
- ????????} else if (rc < 0) {
- ???????? fprintf(stderr, "error from read: %s\n", snd_strerror(rc));
- ????????} else if (rc != (int)frames) {
- ???????? fprintf(stderr, "short read, read %d frames\n", rc);
- ????????}
- ????????rc = write(fd, buffer, size);
- ????????totle_size += rc;????????????????????????/* totle data size */
- ????????if (rc != size)
- ???????? fprintf(stderr, "short write: wrote %d bytes\n", rc);
- ????}
- ????
- ????lseek(fd, 0, SEEK_SET);????? /* 回到文件頭,重新更新音頻文件大小 */
- ????hdr.riff_sz = totle_size + 36;
- ????hdr.data_sz = totle_size;
- ????
- ????if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
- ????????fprintf(stderr, "arec: cannot write header\n");
- ????????return -1;
- ????}
- ????
- ????snd_pcm_drain(handle);
- ????snd_pcm_close(handle);
- ????free(buffer);
- ????return 0;
- }
- /**************************************************************/
- int rec_wav(const char *fn)
- {
- ????unsigned rate, channels;
- ????int fd;
- ????fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
- ????if (fd < 0) {
- ????????fprintf(stderr, "arec: cannot open '%s'\n", fn);
- ????????return -1;
- ????}
- ????hdr.riff_id = ID_RIFF;
- ????hdr.riff_fmt = ID_WAVE;
- ????hdr.fmt_id = ID_FMT;
- ????hdr.audio_format = FORMAT_PCM;
- ????hdr.fmt_sz = 16;
- ????hdr.bits_per_sample = 16;
- ????hdr.num_channels = 1;
- ????hdr.data_sz = 0;????????????????????????/* TODO before record over */
- ????hdr.sample_rate = 16000;
- ????hdr.data_id = ID_DATA;
- ????
- ????hdr.byte_rate = hdr.sample_rate * hdr.num_channels * hdr.bits_per_sample / 8;
- ????hdr.block_align = hdr.num_channels * hdr.bits_per_sample / 8;
- ????
- ????if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
- ????????fprintf(stderr, "arec: cannot write header\n");
- ????????return -1;
- ????}
- ????fprintf(stderr,"arec: %d ch, %ld hz, %d bit, %s\n",
- ????????????hdr.num_channels, hdr.sample_rate, hdr.bits_per_sample,
- ????????????hdr.audio_format == FORMAT_PCM ? "PCM" : "unknown");
- ????
- ????return record_file(hdr.sample_rate, hdr.num_channels, fd, hdr.data_sz);
- }
- int main(int argc, char **argv)
- {
- ????if (argc != 2) {
- ????????fprintf(stderr,"usage: arec <file>\n");
- ????????return -1;
- ????}
- ????return rec_wav(argv[1]);
- }
樣本長度(sample):樣本是記錄音頻數據最基本的單位,常見的有8位和16位。
通道數(channel):該參數為1表示單聲道,2則是立體聲。
楨(frame):楨記錄了一個聲音單元,其長度為樣本長度與通道數的乘積。
采樣率(rate):每秒鐘采樣次數,該次數是針對楨而言。
周期(period):音頻設備一次處理所需要的楨數,對于音頻設備的數據訪問以及音頻數據的存儲,都是以此為單位。
交錯模式(interleaved):是一種音頻數據的記錄方式,在交錯模式下,數據以連續楨的形式存放,即首先記錄完楨1的左聲道樣本和右聲道樣本(假設為立體聲格式),再開始楨2的記錄。而在非交錯模式下,首先記錄的是一個周期內所有楨的左聲道樣本,再記錄右聲道樣本,數據是以連續通道的方式存儲。不過多數情況下,我們只需要使用交錯模式就可以了。
具體可以參照:http://blog.chinaunix.net/uid-25272011-id-3151136.html
一開始我犯過一個錯誤就是rc = snd_pcm_readi(handle, buffer, frames),這個函數的參數3的單位應該是幀大小,而一個幀的大小是根據你的量化位數和聲道數決定的,對于本代碼,是16bit單聲道,自然一個幀大小是2字節,起初我將申請的buffer大小傳給了這個參數,結果必然導致“卡頓”或者“快進”,“卡頓”是因為我在項目中是實時傳輸,會導致阻塞,畢竟數據量大了一倍,“快進”則是因為緩沖區的大小是實際讀取數據的一半,有一半的數據在buffer中被自己給覆蓋掉了,所以要慎重啊。
2、Makefile
- exe = record
- src = record.c
- CC = arm-linux-gcc
- INC = -I/nfs/usr/local/arm-alsa/include
- LDFLAGS = -lpthread -L/nfs/usr/local/arm-alsa/lib -lasound
- $(exe) : $(src) FORCE
- ????$(CC) -o $@ $(src) $(LDFLAGS) $(INC)
- FORCE:
- clean:
- ????rm -f ./*.o $(exe)
????? 此處的alsa-lib庫就是之前介紹的安裝的庫的路徑,編譯可以引用該路徑的庫,而運行之后庫的路徑可不受限制,按照你定義的環境變量找到即可。
???? 對于內核的配置則在
? ? ?
- Device Drivers --->
- <*> Sound card support --->
- <*> Advanced Linux Sound Architecture --->
- [*] USB sound devices --->
- <*> USB Audio/MIDI driver
???? 對于這款USB麥克風,我正常的去錄音的時候,上層的直觀感覺就是卡頓,這個與上面提到的是有區別的,因為同樣的代碼在arm上是好的,所以就懷疑是底層讀慢了,(我們的應用背景是開發板實時錄音,通過USB-WIFI發到上位機同步播放)很明顯的是讀取音頻數據慢了。而同樣的代碼跑硬件的CODEC是很好的,不卡頓,所以很有可能問題出在USB上。我們剛好有USB的協議分析儀,我們USB是跑的全速模式,其描述符為
- Interface Descriptor:
- ??????bLength 9
- ??????bDescriptorType 4
- ??????bInterfaceNumber 3
- ??????bAlternateSetting 0
- ??????bNumEndpoints 0
- ??????bInterfaceClass 1
- ??????bInterfaceSubClass 2
- ??????bInterfaceProtocol 0
- ??????iInterface 0
- ????Interface Descriptor:
- ??????bLength 9
- ??????bDescriptorType 4
- ??????bInterfaceNumber 3
- ??????bAlternateSetting 1
- ??????bNumEndpoints 1
- ??????bInterfaceClass 1
- ??????bInterfaceSubClass 2
- ??????bInterfaceProtocol 0
- ??????iInterface 0
- ??????AudioStreaming Interface Descriptor:
- ????????bLength 7
- ????????bDescriptorType 36
- ????????bDescriptorSubtype 1 (AS_GENERAL)
- ????????bTerminalLink 3
- ????????bDelay 1 frames
- ????????wFormatTag 1 PCM
- ??????AudioStreaming Interface Descriptor:
- ????????bLength 11
- ????????bDescriptorType 36
- ????????bDescriptorSubtype 2 (FORMAT_TYPE)
- ????????bFormatType 1 (FORMAT_TYPE_I)
- ????????bNrChannels 1
- ????????bSubframeSize 2
- ????????bBitResolution 16
- ????????bSamFreqType 1 Discrete
- ????????tSamFreq[ 0] 16000
- ??????Endpoint Descriptor:
- ????????bLength 9
- ????????bDescriptorType 5
- ????????bEndpointAddress 0x83 EP 3 IN
- ????????bmAttributes 5
- ??????????Transfer Type Isochronous
- ??????????Synch Type Asynchronous
- ??????????Usage Type Data
- ????????wMaxPacketSize 0x0020 1x 32 bytes
- ????????bInterval 4
- ????????bRefresh 0
- ????????bSynchAddress 0
- ????????AudioControl Endpoint Descriptor:
- ??????????bLength 7
- ??????????bDescriptorType 37
- ??????????bDescriptorSubtype 1 (EP_GENERAL)
- ??????????bmAttributes 0x01
- ????????????Sampling Frequency
- ??????????bLockDelayUnits 0 Undefined
- ??????????wLockDelay 0 Undefined
- /************************************************************************/
- ????Interface Descriptor:
- ??????bLength 9
- ??????bDescriptorType 4
- ??????bInterfaceNumber 3
- ??????bAlternateSetting 2
- ??????bNumEndpoints 1
- ??????bInterfaceClass 1
- ??????bInterfaceSubClass 2
- ??????bInterfaceProtocol 0
- ??????iInterface 0
- ??????AudioStreaming Interface Descriptor:
- ????????bLength 7
- ????????bDescriptorType 36
- ????????bDescriptorSubtype 1 (AS_GENERAL)
- ????????bTerminalLink 3
- ????????bDelay 1 frames
- ????????wFormatTag 1 PCM
- ??????AudioStreaming Interface Descriptor:
- ????????bLength 11
- ????????bDescriptorType 36
- ????????bDescriptorSubtype 2 (FORMAT_TYPE)
- ????????bFormatType 1 (FORMAT_TYPE_I)
- ????????bNrChannels 1
- ????????bSubframeSize 2
- ????????bBitResolution 16
- ????????bSamFreqType 1 Discrete
- ????????tSamFreq[ 0] 48000
- ??????Endpoint Descriptor:
- ????????bLength 9
- ????????bDescriptorType 5
- ????????bEndpointAddress 0x83 EP 3 IN
- ????????bmAttributes 5
- ??????????Transfer Type Isochronous
- ??????????Synch Type Asynchronous
- ??????????Usage Type Data
- ????????wMaxPacketSize 0x0060 1x 96 bytes
- ????????bInterval 4
- ????????bRefresh 0
- ????????bSynchAddress 0
- ????????AudioControl Endpoint Descriptor:
- ??????????bLength 7
- ??????????bDescriptorType 37
- ??????????bDescriptorSubtype 1 (EP_GENERAL)
- ??????????bmAttributes 0x01
- ????????????Sampling Frequency
- ??????????bLockDelayUnits 0 Undefined
- ??????????wLockDelay 0 Undefined
? ? 看到描述符,順便插一句,對照端點大小計算一下,藍色字體,這款USB-AUDIO只支持16K和48K的16bit單聲道錄音,拿16K為例,1s數據量應該是16K*16/8=32KB,對應于端點的大小32B*1000=32KB,也就是說全速模式下應該是每幀(1ms)請求一次才對,而對于圖中的紅色字體,說明的意思是全速模式下的ISO傳輸請求間隔參數是4,對應我們的USB的控制器,意思即為每8幀才發起一次ISO請求,抓包驗證確實如此,這一點確實比較詭異,問題可能就出在這里:

????? 但是對于幾乎同樣的驅動版本,我們611的是2.6.32.9而arm的是2.6.32.2,其sound目錄下的usbaudio.c基本是相同的,描述符又是相同的,所以上層獲取描述符進行的配置應該也是相同的,唯一的區別就是USB的控制器,我們611的是musb,而arm的是OHCI,我們musb對這個bInterval的配置是

因此驅動固然是8幀請求一次。我在OHCI的控制器中未找到類似的寄存器,猜想可能是OHCI默認的就是ISO傳輸每一幀會保證一次,其抓包圖如下:

所以只好強制去修改musb的驅動配置,/drivers/usb/musb/musb_host.c
- 2013 /* precompute rxtype/txtype/type0 register */
- 2014 type_reg = (qh->type << 4) | qh->epnum;
- 2015 switch (urb->dev->speed) {
- 2016 case USB_SPEED_LOW:
- 2017 type_reg |= 0xc0;
- 2018 break;
- 2019 case USB_SPEED_FULL:
- 2020 type_reg |= 0x80;
- 2021 break;
- 2022 default:
- 2023 type_reg |= 0x40;
- 2024 }
- 2025 qh->type_reg = type_reg;
- 2026
- 2027 /* Precompute RXINTERVAL/TXINTERVAL register */
- 2028 switch (qh->type) {
- 2029 case USB_ENDPOINT_XFER_INT:
- 2030 /*
- 2031 * Full/low speeds use the linear encoding,
- 2032 * high speed uses the logarithmic encoding.
- 2033 */
- 2034 if (urb->dev->speed <= USB_SPEED_FULL) {
- 2035 interval = max_t(u8, epd->bInterval, 1);
- 2036 break;
- 2037 }
- 2038 /* FALLTHROUGH */
- 2039 case USB_ENDPOINT_XFER_ISOC:
- 2040 /* ISO always uses logarithmic encoding */
- 2041 //interval = min_t(u8, epd->bInterval, 16);
- 2042 interval = min_t(u8, epd->bInterval, 1); //JGF
- 2043 break;
- 2044 default:
這樣USB就是每幀請求一次,同樣的代碼,效果和2440的也一樣了。