前段時(shí)間有個(gè)讀者咨詢(xún)UVC bulk 傳輸實(shí)現(xiàn),接著這個(gè)機(jī)會(huì)重新梳理一遍UVC bulk 傳輸實(shí)現(xiàn)思路,同時(shí)對(duì)比ISO 與 Bulk 實(shí)現(xiàn)不同。有關(guān)bulk 傳輸實(shí)現(xiàn)請(qǐng)先看前文
閱讀此文前提:假設(shè)讀者,已知曉UVC 協(xié)議,以及UVC 設(shè)備驅(qū)動(dòng)框架

ISO 傳輸模式下的描述符布局如上圖所示:
接口0:VideoControl。處理 UVC CT/PU/XU 等處理,主要用來(lái)控制 接口1:VideoStream。處理UVC 視頻流控制。
一個(gè)VideoControl 可以關(guān)聯(lián)一個(gè)或者多個(gè)流接口。每個(gè)流接口下面,關(guān)聯(lián)2個(gè)多或者多個(gè)接口配置。
對(duì)于ISO 傳輸,流接口下有多個(gè)備用接口,用來(lái)控制視頻流的傳輸。一般用alt0 關(guān)流,altx 開(kāi)流。

BULK 傳輸模式下的描述符布局如上圖所示:
接口0:VideoControl。處理 UVC CT/PU/XU 等處理,主要用來(lái)控制 接口1:VideoStream。處理UVC 視頻流控制。
對(duì)于BULK 傳輸, 與ISO 模式最大的不同,是流接口只有一個(gè)alt0 接口配置。故,對(duì)其視頻流的控制流程,相比較ISO 模式比較復(fù)雜一些。
根據(jù)USB規(guī)范可知,同步傳輸方式是只要選中中帶有同步端點(diǎn)的接口,系統(tǒng)會(huì)定時(shí)從設(shè)備中讀取數(shù)據(jù),無(wú)論設(shè)備中是否有數(shù)據(jù)。而如要停止數(shù)據(jù)的傳輸,只需要選中不帶有同步端點(diǎn)的接口即可。
USB同步傳輸這種靈活的數(shù)據(jù)傳輸方式是依靠視頻流接口的轉(zhuǎn)換接口即我們常說(shuō)的備份接口實(shí)現(xiàn)的。
Stream ON:

Stream OFF:

整個(gè)視頻流的控制流程(開(kāi)流/關(guān)流),可以在設(shè)備收到set_alt1/0 后進(jìn)行處理。
從描述符上分析 UVC 的 bulk 傳輸只有一個(gè)備用接口。故無(wú)法通過(guò)ISO 模式下通過(guò)備用接口進(jìn)行開(kāi)關(guān)流控制。
StreamOn:

StreamOff

通過(guò)抓包對(duì)比發(fā)現(xiàn),開(kāi)流過(guò)程通過(guò)uvc probe 與commit 流程處理。關(guān)流過(guò)程主機(jī)會(huì)下發(fā)一個(gè)clear_halt 請(qǐng)求。在clear_halt 請(qǐng)求里面做關(guān)流的后處理。
通過(guò)對(duì)比發(fā)現(xiàn),對(duì)于ISO 傳輸,針對(duì)UVC 的設(shè)備描述修改基本不大,難處理的是UVC 的控制流程,特別是視頻流的處理,接下來(lái)我們會(huì)以linux 平臺(tái)介紹如何在iso 基礎(chǔ)上修改為 bulk 模式
我們以linux 平臺(tái)舉例,其他平臺(tái)只要熟悉uvc 底層協(xié)議,即可快速遷移。
源碼位置:driver/media/uvc/uvc_queue.c
uvc 使能:
int?uvc_video_enable(struct?uvc_streaming?*stream,?int?enable)
{
?int?ret;
?if?(!enable)?{
??...
??if?(stream->intf->num_altsetting?>?1)?{
???usb_set_interface(stream->dev->udev,
???????stream->intfnum,?0);
??}?else?{
???/*?UVC?doesn't?specify?how?to?inform?a?bulk-based?device
????*?when?the?video?stream?is?stopped.?Windows?sends?a
????*?CLEAR_FEATURE(HALT)?request?to?the?video?streaming
????*?bulk?endpoint,?mimic?the?same?behaviour.
????*/
???......
???usb_clear_halt(stream->dev->udev,?pipe);
??}
?......
??return?0;
?}
?/*?Commit?the?streaming?parameters.?*/
?ret?=?uvc_commit_video(stream,?&stream->ctrl);
?if?(ret?0)
??goto?error_commit;
?ret?=?uvc_init_video(stream,?GFP_KERNEL);
?if?(ret?0)
??goto?error_video;
?return?0;
error_video:
?usb_set_interface(stream->dev->udev,?stream->intfnum,?0);
error_commit:
?uvc_video_clock_cleanup(stream);
?return?ret;
}
StreamOn:
uvc_start_streaming()??//
????ret?=?uvc_video_enable(stream,?1);??
StreamOff:
uvc_stop_streaming();
????uvc_video_enable(stream,?0);
從host 端源碼分析和我們猜想得到了印證:
視頻傳輸:源碼參考:driver/media/usb/uvc_video.c
//?usb?complete?中斷
static?void?uvc_video_complete(struct?urb?*urb)
{
?struct?uvc_streaming?*stream?=?urb->context;
?struct?uvc_video_queue?*queue?=?&stream->queue;
?struct?uvc_buffer?*buf?=?NULL;
?unsigned?long?flags;
?int?ret;
?switch?(urb->status)?{
?case?0:
??break;
?default:
??uvc_printk(KERN_WARNING,?"Non-zero?status?(%d)?in?video?"
???"completion?handler.\n",?urb->status);
??/*?fall?through?*/
?case?-ENOENT:??/*?usb_kill_urb()?called.?*/
??if?(stream->frozen)
???return;
??/*?fall?through?*/
?case?-ECONNRESET:?/*?usb_unlink_urb()?called.?*/
?case?-ESHUTDOWN:?/*?The?endpoint?is?being?disabled.?*/
??uvc_queue_cancel(queue,?urb->status?==?-ESHUTDOWN);
??return;
?}
?spin_lock_irqsave(&queue->irqlock,?flags);
?if?(!list_empty(&queue->irqqueue))
??buf?=?list_first_entry(&queue->irqqueue,?struct?uvc_buffer,
???????????queue);
?spin_unlock_irqrestore(&queue->irqlock,?flags);
???/*?視頻解碼?:視頻傳輸關(guān)鍵?*/
?stream->decode(urb,?stream,?buf);
?/*?提交urb?*/
?if?((ret?=?usb_submit_urb(urb,?GFP_ATOMIC))?0)?{
??uvc_printk(KERN_ERR,?"Failed?to?resubmit?video?URB?(%d).\n",
???ret);
?}
}
host 端視頻數(shù)據(jù)解碼:
stream->decode(urb,?stream,?buf);
????uvc_video_decode_bulk();
????????uvc_video_decode_start();?//?解析uvc?header
????????uvc_video_decode_data();?//?解析視頻數(shù)據(jù)
????????uvc_video_decode_end();?//?幀結(jié)束標(biāo)記
源碼位置:driver/usb/gadget/function
ISO 開(kāi)關(guān)流
static?int?uvc_function_set_alt(struct?usb_function?*f,?unsigned?interface,?unsigned?alt)
{?
?.......
?/*?VideoControl?process?*/
?if?(interface?==?uvc->control_intf)?{
????/*?復(fù)位控制端點(diǎn)?*/
?????usb_ep_disable(uvc->control_ep);
?????...
?????usb_ep_enable(uvc->control_ep);
?????
?????if?(uvc->state?==?UVC_STATE_DISCONNECTED)?{
????????/*提交uvc?狀態(tài)?*/
?????????memset(&v4l2_event,?0?,sizof(v4l2_evetn);
?????????v4l2_event.type?=?UVC_EVENT_CONNECT;
?????????uvc_event->speed?=?cdev->gadget->speed;
?????????v4l2_event_queue(&uvc->vdev,?&v4l2_event);
?????????uvc->state?=?UVC_STATE_CONNECTED;
?????}
?????
?????return?0;
?????
?}
?/*?VideoStream?process?*/
?if?(interface?!=?uvc->streaming_intf)?{
?????return?-EINVAL;
?}
?
?/*?判斷端點(diǎn)是否為bulk 端點(diǎn):?*/
?if?(usb_endpoint_xfer_bulk(&uvc->desc.vs_ep))?{
?????/*?使能端點(diǎn)?*/
?????usb_ep_enable(uvc->video_ep);
?????return?alt???-EIVAL?:?0;
?}
?
?/*ISO?開(kāi)關(guān)流處理?*/
?switch?(alt)?{
?case?0:
??????if?(uvc->state?!=?UVC_STATE_STREAMING)
???return?0;
??if?(uvc->video.ep)
???usb_ep_disable(uvc->video.ep);
??????/*提交應(yīng)用?:?關(guān)流*/
??memset(&v4l2_event,?0,?sizeof(v4l2_event));
??v4l2_event.type?=?UVC_EVENT_STREAMOFF;
??v4l2_event_queue(&uvc->vdev,?&v4l2_event);
??uvc->state?=?UVC_STATE_CONNECTED;
??return?0;
?????
?case?1:
???????if?(uvc->state?!=?UVC_STATE_CONNECTED)
???return?0;
??if?(!uvc->video.ep)
???return?-EINVAL;
??INFO(cdev,?"reset?UVC\n");
??usb_ep_disable(uvc->video.ep);
??ret?=?config_ep_by_speed(f->config->cdev->gadget,
????&(uvc->func),?uvc->video.ep);
??if?(ret)
???return?ret;
??usb_ep_enable(uvc->video.ep);
?
??????/*?開(kāi)流*/
??memset(&v4l2_event,?0,?sizeof(v4l2_event));
??v4l2_event.type?=?UVC_EVENT_STREAMON;
??v4l2_event_queue(&uvc->vdev,?&v4l2_event);
??return?USB_GADGET_DELAYED_STATUS;
?default:
??return?-EINVAL;
?}
??
}
bulk 傳輸開(kāi)關(guān)流
uvc_function_setup(struct?usb_function?*f,?const?struct?usb_ctrlrequest?*ctrl)
{
?struct?uvc_device?*uvc?=?to_uvc(f);
?struct?v4l2_event?v4l2_event;
?struct?uvc_event?*uvc_event?=?(void?*)&v4l2_event.u.data;
?/*?printk(KERN_INFO?"setup?request?%02x?%02x?value?%04x?index?%04x?%04x\n",
??*?ctrl->bRequestType,?ctrl->bRequest,?le16_to_cpu(ctrl->wValue),
??*?le16_to_cpu(ctrl->wIndex),?le16_to_cpu(ctrl->wLength));
??*/
?if?((ctrl->bRequestType?&?USB_TYPE_MASK)?!=?USB_TYPE_CLASS)?{
??????/* bulk 傳輸關(guān)流:需要修改udc 控制器,將usb 控制器clear_halt 注冊(cè)到uvc function 驅(qū)動(dòng)里面?*/
???????if?(usb_endpoint_xfer_bulk(&uvc->desc.vs_ep))?{
????????????memset(&v4l2_event,?0,?sizeof(v4l2_event));
?????????v4l2_event.type?=?UVC_EVENT_STREANOFF;
?????????memcpy(&uvc_event->req,?ctrl,?sizeof(uvc_event->req));
?????????v4l2_event_queue(&uvc->vdev,?&v4l2_event);
???????????
???????}?else?{
?????????INFO(f->config->cdev,?"invalid?request?type\n");
??????return?-EINVAL;
???????}
?}
?/*?Stall?too?big?requests.?*/
?if?(le16_to_cpu(ctrl->wLength)?>?UVC_MAX_REQUEST_SIZE)
??return?-EINVAL;
?/*?Tell?the?complete?callback?to?generate?an?event?for?the?next?request
??*?that?will?be?enqueued?by?UVCIOC_SEND_RESPONSE.
??*/
?uvc->event_setup_out?=?!(ctrl->bRequestType?&?USB_DIR_IN);
?uvc->event_length?=?le16_to_cpu(ctrl->wLength);
???/* bulk 傳輸?開(kāi)流:通過(guò)uvc probe 和 commit 提交分辨率和幀率控制?*/
?memset(&v4l2_event,?0,?sizeof(v4l2_event));
?v4l2_event.type?=?UVC_EVENT_SETUP;
?memcpy(&uvc_event->req,?ctrl,?sizeof(uvc_event->req));
?v4l2_event_queue(&uvc->vdev,?&v4l2_event);
?return?0;
}

整個(gè)bulk 傳輸控制流程如上圖所示。
對(duì)于linux 平臺(tái)而言,需要關(guān)心的有兩點(diǎn):1)如何將底層clear_halt 請(qǐng)求 與uvc_function 請(qǐng)求關(guān)聯(lián)上,而不影響其他端點(diǎn);2)如何將clear_halt 請(qǐng)求提交到應(yīng)用層去處理。這一步不是非必須的。
對(duì)于Rtos 平臺(tái)大同小異,只要理解了整個(gè)開(kāi)關(guān)流流程,處理起來(lái)自然簡(jiǎn)單許多。
