usb声卡驱动(七)
前面记录了pcm的打开关闭,还有硬件参数和软件参数的设置
现在就可以进行播放了。
在播放之前,需要通过ioctl,传递SNDRV_PCM_IOCTL_PREPARE来调用prepare,alsa内部的prepare的逻辑实现,不做过多的探究。
当应用传递命令SNDRV_PCM_IOCTL_PREPARE时,usb声卡驱动,将会调用pcm的prepare回调。见《usb声卡驱动(四)》prepare
在前面的介绍中,open,hw_params回调已经将部分设置,初始化好了。剩下的就交给prepare回调了,看看prepare的回调的具体内容:
static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
{
//各种判断,无需赘述
//同步端点,数据端点,等待完全停止
snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint);
snd_usb_endpoint_sync_pending_stop(subs->data_endpoint);
//设置当前的音频格式,即切换到正确的接口,并创建对应的endpoint封装对象
//以后usb声卡的直接操作这个endpoint封装对象,类型为:snd_usb_endpoint
ret = set_format(subs, subs->cur_audiofmt);
//初始化采样率,内部通过usb的快捷方法,snd_usb_ctl_msg进行设置
ret = snd_usb_init_sample_rate(subs->stream->chip,
subs->cur_audiofmt->iface,
alts,
subs->cur_audiofmt,
subs->cur_rate);
//需要配置subs里面创建的snd_usb_endpoint对象。
//每次硬件参数更改,也需要重新配置
if (subs->need_setup_ep) {
ret = configure_endpoint(subs);
if (ret < 0)
goto unlock;
subs->need_setup_ep = false;
}
/* for playback, submit the URBs now; otherwise, the first hwptr_done
* updates for all URBs would happen at the same time when starting */
//如果是播放,现在开始启动端点
//启动端点,先启动数据端点,然后是同步端点。
//端点的启动,即提交urb给HCD。当没有数据时,发送的是静默数据。
if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
ret = start_endpoints(subs, true);
}
上面的函数,做了如下的事情:
- 切换这个substream对应的usb接口和设置。并创建usb声卡需要操作的usb端点封装对象snd_usb_endpoint。它在set_format函数中完成
- 对上面的封装对象进行初始化。初始中,最主要的就是创建每个period使用的urb。它在configure_endpoint函数中完成
- 如果是播放端点,则直接开始。此时,应用向驱动写入数据,即可开始播放
- 顺便初始化了采样率,pitch
总体来说prepare只有两部分:
- 声卡内部对象的创建和初始化,这个对象就是usb端点的封装对象
- 硬件的一些初始化,如采样率,pitch等。
usb声卡的端点管理
1. 端点的创建
在继续prepare之前,需要先对usb声卡内部的一个数据结构进行介绍。它就是usb端点的封装对象snd_usb_endpoint如下:
struct snd_usb_endpoint {
struct snd_usb_audio *chip;
int use_count;
int ep_num; /* the referenced endpoint number */
int type; /* SND_USB_ENDPOINT_TYPE_* */
unsigned long flags;
void (*prepare_data_urb) (struct snd_usb_substream *subs,
struct urb *urb);
void (*retire_data_urb) (struct snd_usb_substream *subs,
struct urb *urb);
struct snd_usb_substream *data_subs;
struct snd_usb_endpoint *sync_master;
struct snd_usb_endpoint *sync_slave;
struct snd_urb_ctx urb[MAX_URBS];
struct snd_usb_packet_info {
uint32_t packet_size[MAX_PACKS_HS];
int packets;
} next_packet[MAX_URBS];
int next_packet_read_pos, next_packet_write_pos;
struct list_head ready_playback_urbs;
unsigned int nurbs; /* # urbs */
unsigned long active_mask; /* bitmask of active urbs */
unsigned long unlink_mask; /* bitmask of unlinked urbs */
char *syncbuf; /* sync buffer for all sync URBs */
dma_addr_t sync_dma; /* DMA address of syncbuf */
unsigned int pipe; /* the data i/o pipe */
unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */
unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */
int freqshift; /* how much to shift the feedback value to get Q16.16 */
unsigned int freqmax; /* maximum sampling rate, used for buffer management */
unsigned int phase; /* phase accumulator */
unsigned int maxpacksize; /* max packet size in bytes */
unsigned int maxframesize; /* max packet size in frames */
unsigned int max_urb_frames; /* max URB size in frames */
unsigned int curpacksize; /* current packet size in bytes (for capture) */
unsigned int curframesize; /* current packet size in frames (for capture) */
unsigned int syncmaxsize; /* sync endpoint packet size */
unsigned int fill_max:1; /* fill max packet size always */
unsigned int udh01_fb_quirk:1; /* corrupted feedback data */
unsigned int datainterval; /* log_2 of data packet interval */
unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */
unsigned char silence_value;
unsigned int stride;
int iface, altsetting;
int skip_packets; /* quirks for devices to ignore the first n packets
in a stream */
spinlock_t lock;
struct list_head list;
};
- chip:chip对象指针
- use_count:这个端点的使用者的个数
- ep_num,type:usb endpoint数和类型
- flags:用于表示各种状态,如:EP_FLAG_STOPPING和EP_FLAG_RUNNING分别表示正在停止和正在运行
- prepare_data_urb:在正式提交urb之前,这个回调函数会被回调。用于urb的一些特殊处理
- retire_data_urb:在使用完成之后,这个回调会被回调
- data_subs:对应的substream
- sync_master:同步用的端点,数据端点赋值此值为同接口下的同步端点
- sync_slave:同步用的端点,同步端点赋值此值为同接口下的数据端点
- urb[]:urb的封装对象数组,个数为nurbs
- next_packet[]:为隐式fb端点而设计,在此不表
- next_packet_read_pos, next_packet_write_pos:为隐式fb端点设计,在此不表
- ready_playback_urbs:为隐式fb端点设计,在此不表
- nurbs:每个period需要提交的urb个数
- active_mask:激活的urb的bitmask,即每提交一个urb就会将对应的位置位
- unlink_mask:unlink的urb的bitmask,即每unlink一个urb对应的位就会被置位
- syncbuf:同步端点使用的buffer指针
- sync_dma:同步buffer的dma指针
- pipe: 数据io管道
- freqn:Q16.16格式的标准采样速率
- freqm:Q16.16格式的瞬时采样速率
- freqshift:反馈值需要移动freqshift才能得到Q16.16
- freqmax:最大采样值
- phase:phase累加器
- maxpacksize:包最大大小
- maxframesize:每帧中的包的最大值
- max_urb_frames: 每帧中的urb最大值
- curpacksize:当前包大小,单位字节
- curframesize:当前包大小,单位帧
- syncmaxsize:同步端点packet大小
- fill_max:是否要使用最大的包大小
- udh01_fb_quirk:反馈数据是否损坏
- datainterval:数据包间隔
- syncinterval:正值表示自适应模式。否则为0
- silence_value:静态数据的值
- stride:每帧字节数
- iface, altsetting:接口相关:iface,接口。altsetting可选设置
- skip_packets:忽略,前面的skip_packets个数字的包
snd_usb_endpoint对象的创建,使用snd_usb_add_endpoint函数。如下:
struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip,
struct usb_host_interface *alts,
int ep_num, int direction, int type)
{
//...
mutex_lock(&chip->mutex);
//寻找空闲的snd_usb_endpoint对象,如果找到,就返回
list_for_each_entry(ep, &chip->ep_list, list) {
if (ep->ep_num == ep_num &&
ep->iface == alts->desc.bInterfaceNumber &&
ep->altsetting == alts->desc.bAlternateSetting) {
usb_audio_dbg(ep->chip,
"Re-using EP %x in iface %d,%d @%pK\n",
ep_num, ep->iface, ep->altsetting, ep);
goto __exit_unlock;
}
}
usb_audio_dbg(chip, "Creating new %s %s endpoint #%x\n",
is_playback ? "playback" : "capture",
type == SND_USB_ENDPOINT_TYPE_DATA ? "data" : "sync",
ep_num);
//如果没有找到,则重新创建
//然后各种初始化
ep = kzalloc(sizeof(*ep), GFP_KERNEL);
if (!ep)
goto __exit_unlock;
ep->chip = chip;
spin_lock_init(&ep->lock);
ep->type = type;
ep->ep_num = ep_num;
ep->iface = alts->desc.bInterfaceNumber;
ep->altsetting = alts->desc.bAlternateSetting;
INIT_LIST_HEAD(&ep->ready_playback_urbs);
ep_num &= USB_ENDPOINT_NUMBER_MASK;
if (is_playback)
ep->pipe = usb_sndisocpipe(chip->dev, ep_num);
else
ep->pipe = usb_rcvisocpipe(chip->dev, ep_num);
if (type == SND_USB_ENDPOINT_TYPE_SYNC) {
if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
get_endpoint(alts, 1)->bRefresh >= 1 &&
get_endpoint(alts, 1)->bRefresh <= 9)
ep->syncinterval = get_endpoint(alts, 1)->bRefresh;
else if (snd_usb_get_speed(chip->dev) == USB_SPEED_FULL)
ep->syncinterval = 1;
else if (get_endpoint(alts, 1)->bInterval >= 1 &&
get_endpoint(alts, 1)->bInterval <= 16)
ep->syncinterval = get_endpoint(alts, 1)->bInterval - 1;
else
ep->syncinterval = 3;
ep->syncmaxsize = le16_to_cpu(get_endpoint(alts, 1)->wMaxPacketSize);
if (chip->usb_id == USB_ID(0x0644, 0x8038) /* TEAC UD-H01 */ &&
ep->syncmaxsize == 4)
ep->udh01_fb_quirk = 1;
}
//接着放入chip对象的ep_list链表中
list_add_tail(&ep->list, &chip->ep_list);
__exit_unlock:
mutex_unlock(&chip->mutex);
return ep;
}
从prepare知道,上面函数的触发就在prepare回调中。
2. 端点的配置
上面的函数,处理逻辑非常简单。一旦创建了snd_usb_endpoint之后,就可以开始使用它了。
使用这个snd_usb_endpoint之前,需要先对其进行配置。看看它的配置函数。
int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
snd_pcm_format_t pcm_format,
unsigned int channels,
unsigned int period_bytes,
unsigned int period_frames,
unsigned int buffer_periods,
unsigned int rate,
struct audioformat *fmt,
struct snd_usb_endpoint *sync_ep)
{
int err;
//如果正在使用,则不能够对其进行配置
if (ep->use_count != 0) {
usb_audio_warn(ep->chip,
"Unable to change format on ep #%x: already in use\n",
ep->ep_num);
return -EBUSY;
}
/* release old buffers, if any */
//因为需要在这个阶段,分配urb,所以需要将以前的urb都释放掉
release_urbs(ep, 0);
//又是各种赋值。无需赘言
//下面根据端点的类型,分别调用数据端点或同步端点的配置函数。
switch (ep->type) {
case SND_USB_ENDPOINT_TYPE_DATA:
err = data_ep_set_params(ep, pcm_format, channels,
period_bytes, period_frames,
buffer_periods, fmt, sync_ep);
break;
case SND_USB_ENDPOINT_TYPE_SYNC:
err = sync_ep_set_params(ep);
break;
default:
err = -EINVAL;
}
usb_audio_dbg(ep->chip,
"Setting params for ep #%x (type %d, %d urbs), ret=%d\n",
ep->ep_num, ep->type, ep->nurbs, err);
return err;
}
上面函数,分别调用数据端点或同步端点的配置函数,如下:
static int data_ep_set_params(struct snd_usb_endpoint *ep,
snd_pcm_format_t pcm_format,
unsigned int channels,
unsigned int period_bytes,
unsigned int frames_per_period,
unsigned int periods_per_buffer,
struct audioformat *fmt,
struct snd_usb_endpoint *sync_ep)
{
//...
//各种各样对,nurbs的计算,计算的过程,暂且不表。大概步骤如下:
/*
1. 根据采样率和usb的传输间隔,计算出每次传输包的大小x
2. period_size 除以x,得 n
3. 然后根据各种最大值,求得period_size里面需要m个urb
4. n除以m,即为nurbs的值
*/
//根据nurbs,分配好urb
/* allocate and initialize data urbs */
for (i = 0; i < ep->nurbs; i++) {
struct snd_urb_ctx *u = &ep->urb[i];
u->index = i;
u->ep = ep;
u->packets = urb_packs;
u->buffer_size = maxsize * u->packets;
if (fmt->fmt_type == UAC_FORMAT_TYPE_II)
u->packets++; /* for transfer delimiter */
u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
if (!u->urb)
goto out_of_memory;
u->urb->transfer_buffer =
usb_alloc_coherent(ep->chip->dev, u->buffer_size,
GFP_KERNEL, &u->urb->transfer_dma);
if (!u->urb->transfer_buffer)
goto out_of_memory;
u->urb->pipe = ep->pipe;
u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
u->urb->interval = 1 << ep->datainterval;
u->urb->context = u;
u->urb->complete = snd_complete_urb;
INIT_LIST_HEAD(&u->ready_list);
}
return 0;
out_of_memory:
release_urbs(ep, 0);
return -ENOMEM;
}
接下来看看,同步端点的配置,如下:
static int sync_ep_set_params(struct snd_usb_endpoint *ep)
{
int i;
ep->syncbuf = usb_alloc_coherent(ep->chip->dev, SYNC_URBS * 4,
GFP_KERNEL, &ep->sync_dma);
if (!ep->syncbuf)
return -ENOMEM;
for (i = 0; i < SYNC_URBS; i++) {
struct snd_urb_ctx *u = &ep->urb[i];
u->index = i;
u->ep = ep;
u->packets = 1;
u->urb = usb_alloc_urb(1, GFP_KERNEL);
if (!u->urb)
goto out_of_memory;
u->urb->transfer_buffer = ep->syncbuf + i * 4;
u->urb->transfer_dma = ep->sync_dma + i * 4;
u->urb->transfer_buffer_length = 4;
u->urb->pipe = ep->pipe;
u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
u->urb->number_of_packets = 1;
u->urb->interval = 1 << ep->syncinterval;
u->urb->context = u;
u->urb->complete = snd_complete_urb;
}
ep->nurbs = SYNC_URBS;
return 0;
out_of_memory:
release_urbs(ep, 0);
return -ENOMEM;
}
同步端点的配置函数,跟数据端点的函数功能类似:分配必要的urb。不同的是,同步端点使用的urb数是固定的。
上面配置逻辑的触发,在每次硬件参数更改时,都会被触发。
3. 端点开始和停止
要进行数据的传输。就需要
- 将应用写入缓冲区的数据复制到urb中,然后提交到HCD。
- 然后在urb的complete中,继续重复1的步骤。
第一步的开始,使用snd_usb_endpoint_start触发。如下:
nt snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep)
{
int err;
unsigned int i;
//处在关机状态下,则返回错误值
if (ep->chip->shutdown)
return -EBADFD;
//如果已经开始了,则不用再次开始
if (++ep->use_count != 1)
return 0;
//如果还有激活的urb,则直接unlink掉
deactivate_urbs(ep, false);
if (can_sleep)
wait_clear_urbs(ep);
ep->active_mask = 0;
ep->unlink_mask = 0;
ep->phase = 0;
snd_usb_endpoint_start_quirk(ep);
/*
* If this endpoint has a data endpoint as implicit feedback source,
* don't start the urbs here. Instead, mark them all as available,
* wait for the record urbs to return and queue the playback urbs
* from that context.
*/
//设置flag表示,其处于running状态
set_bit(EP_FLAG_RUNNING, &ep->flags);
//如果这个端点,是作为隐式FB端点,则将urb全部放入ready_playback_urbs队列,等待后面被触发
//所有隐式FB端点不做讨论
if (snd_usb_endpoint_implicit_feedback_sink(ep)) {
for (i = 0; i < ep->nurbs; i++) {
struct snd_urb_ctx *ctx = ep->urb + i;
list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
}
return 0;
}
//否则,提交nurbs个urb以满足,硬件的需求
for (i = 0; i < ep->nurbs; i++) {
struct urb *urb = ep->urb[i].urb;
if (snd_BUG_ON(!urb))
goto __error;
//在提交之前,需要将缓冲区的数据,复制到urb中
//prepare_xxbound_urb就是做这个用处
if (usb_pipeout(ep->pipe)) {
prepare_outbound_urb(ep, urb->context);
} else {
prepare_inbound_urb(ep, urb->context);
}
//提交
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err < 0) {
usb_audio_err(ep->chip,
"cannot submit urb %d, error %d: %s\n",
i, err, usb_error_string(err));
goto __error;
}
set_bit(i, &ep->active_mask);
}
return 0;
__error:
clear_bit(EP_FLAG_RUNNING, &ep->flags);
ep->use_count--;
deactivate_urbs(ep, false);
return -EPIPE;
}
上面的函数,只有两件事:
- 复制数据
- 提交
复制数据,在prepare_xxbound_urb中完成。如下:
static void prepare_outbound_urb(struct snd_usb_endpoint *ep,
struct snd_urb_ctx *ctx)
{
//...
switch (ep->type) {
case SND_USB_ENDPOINT_TYPE_DATA:
//如果有prepare_data_urb回调,则进入回调,否则用静默数据填充
if (ep->prepare_data_urb) {
ep->prepare_data_urb(ep->data_subs, urb);
} else {
/* no data provider, so send silence */
unsigned int offs = 0;
for (i = 0; i < ctx->packets; ++i) {
int counts;
if (ctx->packet_size[i])
counts = ctx->packet_size[i];
else
counts = snd_usb_endpoint_next_packet_size(ep);
urb->iso_frame_desc[i].offset = offs * ep->stride;
urb->iso_frame_desc[i].length = counts * ep->stride;
offs += counts;
}
urb->number_of_packets = ctx->packets;
urb->transfer_buffer_length = offs * ep->stride;
memset(urb->transfer_buffer, ep->silence_value,
offs * ep->stride);
}
break;
case SND_USB_ENDPOINT_TYPE_SYNC:
//填充同步端点的数据
if (snd_usb_get_speed(ep->chip->dev) >= USB_SPEED_HIGH) {
/*
* fill the length and offset of each urb descriptor.
* the fixed 12.13 frequency is passed as 16.16 through the pipe.
*/
urb->iso_frame_desc[0].length = 4;
urb->iso_frame_desc[0].offset = 0;
cp[0] = ep->freqn;
cp[1] = ep->freqn >> 8;
cp[2] = ep->freqn >> 16;
cp[3] = ep->freqn >> 24;
} else {
/*
* fill the length and offset of each urb descriptor.
* the fixed 10.14 frequency is passed through the pipe.
*/
urb->iso_frame_desc[0].length = 3;
urb->iso_frame_desc[0].offset = 0;
cp[0] = ep->freqn >> 2;
cp[1] = ep->freqn >> 10;
cp[2] = ep->freqn >> 18;
}
break;
}
}
在上面函数中,主要就是,数据的填充,而使用者,可以注册prepare_data_urb回调来填充自己的数据。如果没有,则默认填充静默数据。
当urb的控制权回到本驱动时,complete回调会被触发。如下:
static void snd_complete_urb(struct urb *urb)
{
//必须做的urb的状态判断
//...
if (usb_pipeout(ep->pipe)) {
//在再次复用urb之前,需要将urb的控制权,交给使用者,进行最后一步的处理
//retire_xxbound_urb函数就是做这个用法的
retire_outbound_urb(ep, ctx);
/* can be stopped during retire callback */
if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
goto exit_clear;
//隐式FB端点,暂且不表
if (snd_usb_endpoint_implicit_feedback_sink(ep)) {
unsigned long flags;
spin_lock_irqsave(&ep->lock, flags);
list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
spin_unlock_irqrestore(&ep->lock, flags);
queue_pending_output_urbs(ep);
goto exit_clear;
}
//在使用之前,需要赋值相应的数据,因此调用preapre函数
prepare_outbound_urb(ep, ctx);
} else {
retire_inbound_urb(ep, ctx);
/* can be stopped during retire callback */
if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
goto exit_clear;
prepare_inbound_urb(ep, ctx);
}
//再次提交
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err == 0)
return;
usb_audio_err(ep->chip, "cannot submit urb (err = %d)\n", err);
//snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
exit_clear:
clear_bit(ctx->index, &ep->active_mask);
}
complete函数主要做如下几件事情:
- 将urb的使用权,交给使用者,通过retire_xxbound_urb函数
- 再次使用时,调用prepare_xxbound_urb函数,复制数据
- 再次提交这个urb。
循环往复,这样端点就开始运行起来了。
接下来看看端点的停止。
端点停止,调用的函数,名字和开始函数类似,为:snd_usb_endpoint_stop
void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep)
{
if (!ep)
return;
if (snd_BUG_ON(ep->use_count == 0))
return;
if (--ep->use_count == 0) {
deactivate_urbs(ep, false);
ep->data_subs = NULL;
ep->sync_slave = NULL;
ep->retire_data_urb = NULL;
ep->prepare_data_urb = NULL;
set_bit(EP_FLAG_STOPPING, &ep->flags);
}
}
该函数的逻辑如下:
- 使用者减一
- 当使用者为0时,unlink所有的urb,并设置状态位:EP_FLAG_STOPPING
至此,usb端点的管理,告一段落。这个笔记里面主要记录的是他们的操作流程。而不是具体的数据分析。比如端点内部的interva怎么计算,每个周期需要多少urb。都不涉及。
想来这些问题,只需稍稍在纸上面动动笔,就能够解其中的奥秘了。
继续prepare
上面prepare回调中,有一个关键的函数,set_format。它会触发对应的端点对象的创建。
static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
{
//...
/* close the old interface */
//关掉以前的interface
if (subs->interface >= 0 && subs->interface != fmt->iface) {
err = usb_set_interface(subs->dev, subs->interface, 0);
if (err < 0) {
dev_err(&dev->dev,
"%d:%d: return to setting 0 failed (%d)\n",
fmt->iface, fmt->altsetting, err);
return -EIO;
}
subs->interface = -1;
subs->altset_idx = 0;
}
/* set interface */
//切换到期望的接口
if (subs->interface != fmt->iface ||
subs->altset_idx != fmt->altset_idx) {
err = snd_usb_select_mode_quirk(subs, fmt);
if (err < 0)
return -EIO;
err = usb_set_interface(dev, fmt->iface, fmt->altsetting);
if (err < 0) {
dev_err(&dev->dev,
"%d:%d: usb_set_interface failed (%d)\n",
fmt->iface, fmt->altsetting, err);
return -EIO;
}
dev_dbg(&dev->dev, "setting usb interface %d:%d\n",
fmt->iface, fmt->altsetting);
subs->interface = fmt->iface;
subs->altset_idx = fmt->altset_idx;
snd_usb_set_interface_quirk(dev);
}
//创建usb端点的封装对象。snd_usb_endpoint。
//详见端点操作
subs->data_endpoint = snd_usb_add_endpoint(subs->stream->chip,
alts, fmt->endpoint, subs->direction,
SND_USB_ENDPOINT_TYPE_DATA);
if (!subs->data_endpoint)
return -EINVAL;
//创建同步端点使用的封装对象snd_usb_endpoint
err = set_sync_endpoint(subs, fmt, dev, alts, altsd);
if (err < 0)
return err;
//初始化pitch,内部调用snd_usb_ctl_msg函数完成
err = snd_usb_init_pitch(subs->stream->chip, fmt->iface, alts, fmt);
if (err < 0)
return err;
subs->cur_audiofmt = fmt;
return 0;
}
在上面的函数中,主要有两件事:
- 切换到对应的interface
- 然后分别创建数据端点,和同步端点的封装对象。他们分别保存在substream的data_endpoint和sync_endpoint中
- 顺便初始化pitch
看看set_sync_endpoint函数:
static int set_sync_endpoint(struct snd_usb_substream *subs,
struct audioformat *fmt,
struct usb_device *dev,
struct usb_host_interface *alts,
struct usb_interface_descriptor *altsd)
{
//前面是各种判断,因为有些设备可能并没有按照要求来
//接下来调用snd_usb_add_endpoint创建端点封装对象
//详见端点操作
subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip,
alts, ep, !subs->direction,
implicit_fb ?
SND_USB_ENDPOINT_TYPE_DATA :
SND_USB_ENDPOINT_TYPE_SYNC);
if (!subs->sync_endpoint)
return -EINVAL;
subs->data_endpoint->sync_master = subs->sync_endpoint;
return 0;
}
同样上面的函数,非常容易看懂。接下来回到configure_endpoint函数中。如下:
static int configure_endpoint(struct snd_usb_substream *subs)
{
int ret;
//在设置端点之前,需要停止端点
stop_endpoints(subs, true);
//调用snd_usb_endpoint_set_params,该函数会分配使用的urb
//关于端点的操作,见前面的端点操作详情
ret = snd_usb_endpoint_set_params(subs->data_endpoint,
subs->pcm_format,
subs->channels,
subs->period_bytes,
subs->period_frames,
subs->buffer_periods,
subs->cur_rate,
subs->cur_audiofmt,
subs->sync_endpoint);
if (ret < 0)
return ret;
//如果存在同步端点,则继续配置同步端点
if (subs->sync_endpoint)
ret = configure_sync_endpoint(subs);
return ret;
}
继续看configure_sync_endpoint
static int configure_sync_endpoint(struct snd_usb_substream *subs)
{
//先判断同步端点的类型,正常的同步端点会进入if里面执行
if (subs->sync_endpoint->type != SND_USB_ENDPOINT_TYPE_DATA ||
!subs->stream)
return snd_usb_endpoint_set_params(subs->sync_endpoint,
subs->pcm_format,
subs->channels,
subs->period_bytes,
0, 0,
subs->cur_rate,
subs->cur_audiofmt,
NULL);
//如果该端点是,隐式的FB端点,首先则找到最匹配的格式,然后再重新配置
//因为对隐式端点不是特别清楚,因此,在此不表。
//等有机会,回来填上这些空缺。
return ret;
}
至此。prepare已经OK
为了本笔记,不至于太长,下一篇继续,pcm的开始和停止操作。