生活随笔
收集整理的這篇文章主要介紹了
关于USB-AUDIO使用ALSA编程的一点问题
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
轉(zhuǎn)載自:http://blog.chinaunix.net/uid-25272011-id-3153434.html
?
最近在調(diào)試一款原相PAP7501攝像頭中的USB的麥克風(fēng),USB層走的應(yīng)該是標(biāo)準(zhǔn)的UAC協(xié)議,具體可以見(jiàn)USB的官網(wǎng):http://www.usb.org/developers/devclass_docs#approved,而音頻部分則可以跑目前Linux標(biāo)準(zhǔn)的ALSA的PCM接口,對(duì)于硬件CODEC來(lái)說(shuō),與其是完全兼容的。
???? 給出一份參考代碼:
???? 這個(gè)是仿照arecord寫(xiě)的一個(gè)簡(jiǎn)略的測(cè)試代碼,保存為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;???? /* 這邊的大小也不是絕對(duì)的,可以調(diào)整 */
????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);/* 獲取實(shí)際的frames */
????
????size = frames * 2; /* 2 bytes/sample, 1 channels */
????buffer = (char *) malloc(size);
????/* We want to loop for 20 seconds 時(shí)間不一定準(zhǔn)確 */
????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]);
} 對(duì)于上述代碼補(bǔ)充說(shuō)明一點(diǎn),這個(gè)是設(shè)計(jì)ALSA的一點(diǎn)概念:
樣本長(zhǎng)度(sample):樣本是記錄音頻數(shù)據(jù)最基本的單位,常見(jiàn)的有8位和16位。
通道數(shù)(channel):該參數(shù)為1表示單聲道,2則是立體聲。
楨(frame):楨記錄了一個(gè)聲音單元,其長(zhǎng)度為樣本長(zhǎng)度與通道數(shù)的乘積。
采樣率(rate):每秒鐘采樣次數(shù),該次數(shù)是針對(duì)楨而言。
周期(period):音頻設(shè)備一次處理所需要的楨數(shù),對(duì)于音頻設(shè)備的數(shù)據(jù)訪問(wèn)以及音頻數(shù)據(jù)的存儲(chǔ),都是以此為單位。
交錯(cuò)模式(interleaved):是一種音頻數(shù)據(jù)的記錄方式,在交錯(cuò)模式下,數(shù)據(jù)以連續(xù)楨的形式存放,即首先記錄完楨1的左聲道樣本和右聲道樣本(假設(shè)為立體聲格式),再開(kāi)始楨2的記錄。而在非交錯(cuò)模式下,首先記錄的是一個(gè)周期內(nèi)所有楨的左聲道樣本,再記錄右聲道樣本,數(shù)據(jù)是以連續(xù)通道的方式存儲(chǔ)。不過(guò)多數(shù)情況下,我們只需要使用交錯(cuò)模式就可以了。
具體可以參照:http://blog.chinaunix.net/uid-25272011-id-3151136.html
一開(kāi)始我犯過(guò)一個(gè)錯(cuò)誤就是rc = snd_pcm_readi(handle, buffer, frames),這個(gè)函數(shù)的參數(shù)3的單位應(yīng)該是幀大小,而一個(gè)幀的大小是根據(jù)你的量化位數(shù)和聲道數(shù)決定的,對(duì)于本代碼,是16bit單聲道,自然一個(gè)幀大小是2字節(jié),起初我將申請(qǐng)的buffer大小傳給了這個(gè)參數(shù),結(jié)果必然導(dǎo)致“卡頓”或者“快進(jìn)”,“卡頓”是因?yàn)槲以陧?xiàng)目中是實(shí)時(shí)傳輸,會(huì)導(dǎo)致阻塞,畢竟數(shù)據(jù)量大了一倍,“快進(jìn)”則是因?yàn)榫彌_區(qū)的大小是實(shí)際讀取數(shù)據(jù)的一半,有一半的數(shù)據(jù)在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庫(kù)就是之前介紹的安裝的庫(kù)的路徑,編譯可以引用該路徑的庫(kù),而運(yùn)行之后庫(kù)的路徑可不受限制,按照你定義的環(huán)境變量找到即可。
???? 對(duì)于內(nèi)核的配置則在
? ? ? Device Drivers --->
<*> Sound card support --->
<*> Advanced Linux Sound Architecture --->
[*] USB sound devices --->
<*> USB Audio/MIDI driver
???? 對(duì)于這款USB麥克風(fēng),我正常的去錄音的時(shí)候,上層的直觀感覺(jué)就是卡頓,這個(gè)與上面提到的是有區(qū)別的,因?yàn)橥瑯拥拇a在arm上是好的,所以就懷疑是底層讀慢了,(我們的應(yīng)用背景是開(kāi)發(fā)板實(shí)時(shí)錄音,通過(guò)USB-WIFI發(fā)到上位機(jī)同步播放)很明顯的是讀取音頻數(shù)據(jù)慢了。而同樣的代碼跑硬件的CODEC是很好的,不卡頓,所以很有可能問(wèn)題出在USB上。我們剛好有USB的協(xié)議分析儀,我們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 ???
? ? 看到描述符,順便插一句,對(duì)照端點(diǎn)大小計(jì)算一下,藍(lán)色字體,這款USB-AUDIO只支持16K和48K的16bit單聲道錄音,拿16K為例,1s數(shù)據(jù)量應(yīng)該是16K*16/8=32KB,對(duì)應(yīng)于端點(diǎn)的大小32B*1000=32KB,也就是說(shuō)全速模式下應(yīng)該是每幀(1ms)請(qǐng)求一次才對(duì),而對(duì)于圖中的紅色字體,說(shuō)明的意思是全速模式下的ISO傳輸請(qǐng)求間隔參數(shù)是4,對(duì)應(yīng)我們的USB的控制器,意思即為每8幀才發(fā)起一次ISO請(qǐng)求,抓包驗(yàn)證確實(shí)如此,這一點(diǎn)確實(shí)比較詭異,問(wèn)題可能就出在這里:
????? 但是對(duì)于幾乎同樣的驅(qū)動(dòng)版本,我們611的是2.6.32.9而arm的是2.6.32.2,其sound目錄下的usbaudio.c基本是相同的,描述符又是相同的,所以上層獲取描述符進(jìn)行的配置應(yīng)該也是相同的,唯一的區(qū)別就是USB的控制器,我們611的是musb,而arm的是OHCI,我們musb對(duì)這個(gè)bInterval的配置是
因此驅(qū)動(dòng)固然是8幀請(qǐng)求一次。我在OHCI的控制器中未找到類(lèi)似的寄存器,猜想可能是OHCI默認(rèn)的就是ISO傳輸每一幀會(huì)保證一次,其抓包圖如下:
所以只好強(qiáng)制去修改musb的驅(qū)動(dòng)配置,/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就是每幀請(qǐng)求一次,同樣的代碼,效果和2440的也一樣了。
總結(jié)
以上是生活随笔為你收集整理的关于USB-AUDIO使用ALSA编程的一点问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。