"); //-->
上一篇《骁龙820A汽车与智能设备间进行USB音频分享方案介绍--USB协议篇》对涉及到的技术关键细节作进一步的详细分析,这一篇是涉及到的UAC篇。
前两篇文章已经可以得到结论,一个控制器需要同时连接多个设备,PC也有很多任务,两者的特点导致了它们具有相对不稳定的传输延迟。USB总线是非常复杂的同时也兼具连 接 灵活,易扩展的特点,但是USB总线并非为音频传输而特别设置的传输方式。
传输机制:
声卡设备需要与主机同步 ,如何解决这种不稳定的传输延迟问题?
为了解决延迟导致的音画不同步问题,因此就有了大家熟知的三种同步方式:SYNC、自适应、ASYNC模式。
SYNC是将输出时钟与每个Frame的SOF包同步,但前文可以看到SOF包本身就允许较大的抖动。
自适应是根据Host传送数据的速率调整输出频率。这两种同步方式下USB界面都是被动适应Host端的发送节奏,本身没有反馈机制,产生的Jitter受USB总线影响较大。
ASYNC下,USB界面会额外申请一条Feedback传输端口。这里有两种实现,一种是显式Feedback,一种是隐式Feedback。
显式Feedback下,USB界面会将单位时间内该传多少Samples回传给Host,让Host计算并知道之后该“补”多少或者该“少”多少采样传给USB界面,这样就能与USB界面主时钟同步并保持不溢出/欠载的缓冲区。UAC1使用数据格式10.14(因为是1ms),UAC2使用数据格式16.16(因为是125us),有所不同
这里以俗知的Amanero界面为例:
Endpoint Descriptor: ------------------------------ 0x07 bLength 0x05 bDescriptorType 0x05 bEndpointAddress (OUT Endpoint) #主机端->USB界面 0x05 bmAttributes (Transfer: Isochronous / Synch: Asynchronous / Usage: Data) #传输类型Isochronous 同步方式Asynchronous 0x0400 wMaxPacketSize (1024 Bytes) #最大包大小 0x01 bInterval #传输间隔 2^(1-1) x 125。也就是125us传输一次 Endpoint Descriptor: ------------------------------ 0x07 bLength 0x05 bDescriptorType 0x81 bEndpointAddress (IN Endpoint) #USB界面->主机端 0x11 bmAttributes (Transfer: Isochronous / Synch: None / Usage: Feedback) #传输类型Isochronous 用于Feedback 0x0004 wMaxPacketSize (4 Bytes) #最大包大小4字节 0x06 bInterval #传输间隔 2^(6-1) x 125。也就是4000us(4ms)传输一次
这是一个典型的显式Feedback的ASYNC,上行反馈Endpoint,每4ms传输一次,最大包大小是4字节。再来看看Linux的UAC驱动是如何处理反馈的
... 1156 /* 1157 * process after playback sync complete 1158 * 1159 * Full speed devices report feedback values in 10.14 format as samples 1160 * per frame, high speed devices in 16.16 format as samples per 1161 * microframe. 1162 * 1163 * Because the Audio Class 1 spec was written before USB 2.0, many high 1164 * speed devices use a wrong interpretation, some others use an 1165 * entirely different format. 1166 * 1167 * Therefore, we cannot predict what format any particular device uses 1168 * and must detect it automatically. 1169 */ 1170 1171 if (urb->iso_frame_desc[0].status != 0 || 1172 urb->iso_frame_desc[0].actual_length < 3) 1173 return; 1174 1175 f = le32_to_cpup(urb->transfer_buffer); 1176 if (urb->iso_frame_desc[0].actual_length == 3) 1177 f &= 0x00ffffff; 1178 else 1179 f &= 0x0fffffff; 1180 1181 if (f == 0) 1182 return; 1183 1184 if (unlikely(sender->tenor_fb_quirk)) { 1185 /* 1186 * Devices based on Tenor 8802 chipsets (TEAC UD-H01 1187 * and others) sometimes change the feedback value 1188 * by +/- 0x1.0000. 1189 */ 1190 if (f < ep->freqn - 0x8000) 1191 f += 0xf000; 1192 else if (f > ep->freqn + 0x8000) 1193 f -= 0xf000; 1194 } else if (unlikely(ep->freqshift == INT_MIN)) { 1195 /* 1196 * The first time we see a feedback value, determine its format 1197 * by shifting it left or right until it matches the nominal 1198 * frequency value. This assumes that the feedback does not 1199 * differ from the nominal value more than +50% or -25%. 1200 */ 1201 shift = 0; 1202 while (f < ep->freqn - ep->freqn / 4) { 1203 f <<= 1; 1204 shift++; 1205 } 1206 while (f > ep->freqn + ep->freqn / 2) { 1207 f >>= 1; 1208 shift--; 1209 } 1210 ep->freqshift = shift; 1211 } else if (ep->freqshift >= 0) 1212 f <<= ep->freqshift; 1213 else 1214 f >>= -ep->freqshift; 1215 1216 if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) { 1217 /* 1218 * If the frequency looks valid, set it. 1219 * This value is referred to in prepare_playback_urb(). 1220 */ 1221 spin_lock_irqsave(&ep->lock, flags); 1222 ep->freqm = f; 1223 spin_unlock_irqrestore(&ep->lock, flags); 1224 } else { 1225 /* 1226 * Out of range; maybe the shift value is wrong. 1227 * Reset it so that we autodetect again the next time. 1228 */ 1229 ep->freqshift = INT_MIN; 1230 }
这里主要是处理来自于USB界面的反馈(并且应付一些不按标准做的USB界面),将获取的值保存进freqm。之后这个值会在snd_usb_endpoint_next_packet_size函数被使用。
145 int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep) 146 { 147 unsigned long flags; 148 int ret; 149 150 if (ep->fill_max) 151 return ep->maxframesize; 152 153 spin_lock_irqsave(&ep->lock, flags); 154 ep->phase = (ep->phase & 0xffff) 155 + (ep->freqm << ep->datainterval); 156 ret = min(ep->phase >> 16, ep->maxframesize); 157 spin_unlock_irqrestore(&ep->lock, flags); 158 159 return ret; 160 }
这里将freqm转化为下一次的包大小,并供prepare_playback_urb使用
1451 static void prepare_playback_urb(struct snd_usb_substream *subs, 1452 struct urb *urb) 1453 { 1454 struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; 1455 struct snd_usb_endpoint *ep = subs->data_endpoint; 1456 struct snd_urb_ctx *ctx = urb->context; 1457 unsigned int counts, frames, bytes; 1458 int i, stride, period_elapsed = 0; 1459 unsigned long flags; 1460 1461 stride = runtime->frame_bits >> 3; 1462 1463 frames = 0; 1464 urb->number_of_packets = 0; 1465 spin_lock_irqsave(&subs->lock, flags); 1466 subs->frame_limit += ep->max_urb_frames; 1467 for (i = 0; i < ctx->packets; i++) { 1468 if (ctx->packet_size) 1469 counts = ctx->packet_size; 1470 else 1471 counts = snd_usb_endpoint_next_packet_size(ep);
prepare_playback_urb这个函数主要决定了音频回放的USB请求数据块(URB)准备工作,包括该传多少USB数据给界面,而这里可以看到snd_usb_endpoint_next_packet_size对于包大小很重要。除此之外没有什么其它作用
如果仔细读代码,可以发现ASYNC和别的同步方式的最大区别它如何影响主机发送数据的多少,其它是与对待别的同步方式一模一样的。
ASYNC的最大好处是,USB界面决定了主机每次Frame中每个包该给多少Samples给它,这样USB界面可以自己决定主时钟并且用这个时钟去“校准”主机发送的数据速率,而不再需要适应Host的发送频率
通常高速需要有125us x 2的Buffer,全速需要1ms x 2的buffer。这些构成了USB音频的最小延迟。配合一定的USB Buffer以及合适的FIFO Buffer,就可以从根本上对USB总线的不稳定时钟“去耦”了。最多是缓冲区欠载产生播放停顿 或者缓冲区溢出程序没处理好造成程序崩溃。
对于USB界面自身,需要监控自身主时钟与来自主机的SOF包之间的时间,计算出偏差不断给Host反馈。并且因为Host发送速率和实际播放速率并不一致,USB界面自身需要合成与播放相关的Clock,这个合成实现具体做法十分影响最终出来的效果,这对嵌入式开发者是一个不小的挑战。
另外还有一个常见的误区,就是异步每次数据包里包含的“采样数”可以变化很大,实际上并不是这样。USB规范中最多允许每个USB包包含的samples变化在±1内[7]。因此如果之前数据错误丢失了采样,也不可以因此“索取”更多的“采样”
上述就是和USB音频传输相关的一些UAC的基础知识。
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。