[FreeBSD-users-jp 95276] Re: 動画のキャプチャ
SAITOU Toshihide
toshi at ruby.ocn.ne.jp
Tue Sep 9 13:39:56 UTC 2014
In message: <20140909.200550.1315917916182598029.toshi at ruby.ocn.ne.jp>
SAITOU Toshihide <toshi at ruby.ocn.ne.jp> writes:
>
> 添付ファイルは UVC の Uncompressed な形式の画像を出力します.
> 以下コマンドに渡して再生できていたと思います.
すみません.添付ファイルの差し替えと不足のファイルを添付しま
した.Mac と 6 月ころの FreeBSD 11.0-Current で試して動いて
るみたいいです.
齊藤
-------------- next part --------------
/*
* Copyright (c) 2012-2014 SAITOU Toshihide
* All rights reserved.
*
* A sample program for LibUSB isochronous transfer using UVC cam.
*
*
* Compiling and Running
*
* (Mac OS)
*
* $ cc -Wall -I/opt/local/include/libusb-1.0 -L/opt/local/lib -lusb-1.0
* -o ex41 ex41.c
*
* (FreeBSD)
*
* $ cc -Wall -lusb -o ex41 ex41.c
*
* (Run)
*
* $ ./ex41 bFrameIndex [file]
* $ ./ex41 1 | mplayer - -demuxer rawvideo -rawvideo w=640:h=480:yuy2
* $ ./ex41 1 | ffmpeg -f rawvideo -s 1280x720 -i - tmp.avi
*
* show all frame indexes and sizes.
*
* $ ./ex41 0
*
* Reference
*
* [PL_UnComp]
*
* (USB_Video_Payload_Uncompressed_1.5.pdf),
* <http://www.usb.org/developers/devclass_docs>.
*
* [UVC1.5]
*
* Universal Serial Bus Device Class Definition for Video Devices, (UVC 1.5
* Class specification.pdf), <http://www.usb.org/developers/devclass_docs>.
*
* [USB2.0]
*
* USB 2.0 Specification Universal Serial Bus Revision 2.0 specification,
* (usb_20.pdf), <http://www.USB.org/developers/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <libusb.h>
#include <fcntl.h>
#include "payload.h"
typedef struct uvc_device
{
uint16_t VID;
uint16_t PID;
uint8_t ConfIdx; /* configuration index */
uint8_t ConfVal; /* configuration value */
uint8_t IfNum; /* interface number */
uint8_t AltSetting; /* alternate setting of the interface */
uint8_t Endpoint; /* endpoint */
uint8_t FrameIndex; /* FrameIndex for 640x480 */
uint16_t PuId; /* VC_PROCESSING_UNIT, bUnitID<<8 */
uint16_t TermId; /* VC_INPUT_TERMINAL, bTerminalID<<8 */
} uvc_device;
uvc_device uvc_device_list[] =
{
/* Isochronous */
/* MSK-1425: Microsoft, Microsoft LifeCam StudioTM */
{ 0x045e, 0x0772, 0, 1, 1, 0, 0x81, 2, 0x0400, 0x0100 },
/* BSW20K07HWH: iBUFFALO, BSW20K07HWH */
{ 0x0458, 0x7081, 0, 1, 1, 0, 0x82, 7, 0x0200, 0x0100 },
/* UCAM-DLY300TA: Etron Technology, Inc., UCAM-DLY300TA */
{ 0x056e, 0x7008, 0, 1, 1, 0, 0x82, 1, 0x0200, 0x0100 },
/* C920: Logitech Inc., LOGICOOL HD Webcam C920 */
{ 0x046d, 0x082d, 0, 1, 1, 10, 0x81, 1, 0x0300, 0x0100 },
/* UCAM-MS130: Etron Technology, Inc., UCAM-MS130SV */
{ 0x056e, 0x7012, 0, 1, 1, 0, 0x81, 2, 0x0300, 0x0100 },
/* KBCR-S01MU */
{ 0x05ca, 0x18d0, 0, 1, 1, 0, 0x82, 1, 0x0200, 0x0400 },
/* Bulk */
/* ESCH021: e-con System Pvt. Ltd., See3CAM_10CUG_CH */
{ 0x2560, 0xc111, 0, 1, 1, 0, 0x83, 1, 0x0200, 0x0100 },
/* ESMH156: e-con System Pvt. Ltd., See3CAM_10CUG_MH */
{ 0x2560, 0xc110, 0, 1, 1, 0, 0x83, 1, 0x0200, 0x0100 },
{ 0, 0, 0, 0, 0, 0, 0 }
};
uvc_device uvc;
int width;
int height;
int XferType; /* isochronous / bulk */
int BitPerPixel; /* 8: 1 byte/pixel, 16: 2 byte/pxel */
int FrameSize;
int FrameBufferSize; /* FrameSize * BitPerPixel */
uint8_t *padding; /* padding data */
uint32_t PKT_LEN; /* dwMaxPayloadTransferSize 0xc00, 0xa80,... */
#define PKTS_PER_XFER 0x40
#define NUM_TRANSFER 2
#define TIMEOUT 500 /* 500 ms */
libusb_context *ctx = NULL;
libusb_device_handle *handle;
struct libusb_transfer * xfers[NUM_TRANSFER];
int fd;
int total = 0;
int16_t brightness;
/* for debug */
struct timeval tv1;
struct timeval tv2;
struct timezone tz;
int totalFrame = 0;
/* boolean */
#define TRUE 1
#define FALSE 0
void
signal_callback_handler(int signum)
{
uint8_t buf[2];
int i;
switch (signum)
{
case SIGUSR1:
/*
* BSW20K07HWH
* min: 0xff81, max: 0x0080, res: 0x0001
* UCAM-DLY300TA
* min: 0x000a, max: 0xfff6, res: 0x0001
* 前者は cur を段階的に設定できたが後者は 2 値のみ受け付けた.
* 別の設定と連動しているかあるいは何れかの値がおかしい.
*
* いずれも VC_PROCESSING_UNIT の bUnitID が 2 であり
* 下記の通りこの値が固定かつ同一のコーディングとなっている.
*/
/*
* PU_BRIGHTNESS_CONTROL(0x02), GET_MIN(0x82) [UVC1.5, p. 160,
* 158, 96]
*/
libusb_control_transfer(
handle, 0xa1, 0x82, 0x0200, uvc.PuId, buf, 2, TIMEOUT);
fprintf(stderr, "brightness min: %02x%02x\n", buf[1], buf[0]);
/*
* PU_BRIGHTNESS_CONTROL(0x02), GET_MAX(0x83) [UVC1.5, p. 160,
* 158, 96]
*/
libusb_control_transfer(
handle, 0xa1, 0x83, 0x0200, uvc.PuId, buf, 2, TIMEOUT);
fprintf(stderr, "brightness max: %02x%02x\n", buf[1], buf[0]);
/*
* PU_BRIGHTNESS_CONTROL(0x02), GET_RES(0x84) [UVC1.5, p. 160,
* 158, 96]
*/
libusb_control_transfer(
handle, 0xa1, 0x84, 0x0200, uvc.PuId, buf, 2, TIMEOUT);
fprintf(stderr, "brightness res: %02x%02x\n", buf[1], buf[0]);
/*
* PU_BRIGHTNESS_CONTROL(0x02), GET_CUR(0x81) [UVC1.5, p. 160,
* 158, 96]
*/
libusb_control_transfer(
handle, 0xa1, 0x81, 0x0200, uvc.PuId, buf, 2, TIMEOUT);
fprintf(stderr, "brightness cur: %02x%02x\n", buf[1], buf[0]);
/* change brightness */
brightness = buf[1]<<8 | buf[0];
// brightness += 30;
brightness += 1;
buf[1] = brightness<<8;
buf[0] = brightness & 0xff;
/*
* PU_BRIGHTNESS_CONTROL(0x02), SET_CUR(0x01) [UVC1.5, p. 160,
* 158, 96]
*/
libusb_control_transfer(
handle, 0x21, 0x01, 0x0200, uvc.PuId, buf, 2, TIMEOUT);
/*
* PU_BRIGHTNESS_CONTROL(0x02), GET_CUR(0x81) [UVC1.5, p. 160,
* 158, 96]
*/
libusb_control_transfer(
handle, 0xa1, 0x81, 0x0200, uvc.PuId, buf, 2, TIMEOUT);
fprintf(stderr, "brightness: %02x%02x\n", buf[1], buf[0]);
/*
* こんだけコマンドを一度に送信するとデータが乱れる.
*/
break;
case SIGUSR2:
/*
* <UCAM-DLY300TA Elecom Corp.> では,パッケージにオート・フォー
* カス対応とあり CT_FOCUS_AUTO_CONTROL で機能を制御できた.
*/
/*
* CT_FOCUS_AUTO_CONTROL(0x08), GET_CUR(0x81) [UVC1.5, p. 160,
* 159, 86]
*/
libusb_control_transfer(
handle, 0xa1, 0x81, 0x0800, uvc.TermId, buf, 1, TIMEOUT);
buf[0] = !buf[0];
/*
* CT_FOCUS_AUTO_CONTROL(0x08), SET_CUR(0x01) [UVC1.5, p. 160,
* 159, 86]
*/
libusb_control_transfer(
handle, 0x21, 0x01, 0x0800, uvc.TermId, buf, 1, TIMEOUT);
fprintf(stderr, "auto focus control: %02x\n", buf[0]);
break;
case SIGINT:
case SIGPIPE:
gettimeofday(&tv2, &tz);
fprintf(stderr, "time lapse: %ld\n",
((tv2.tv_sec - tv1.tv_sec) * 1000000)
+ tv2.tv_usec - tv1.tv_usec);
fprintf(stderr, "fps: %d\n",
(int) ((1000000*totalFrame) /
(long)(((tv2.tv_sec - tv1.tv_sec) * 1000000)
+ tv2.tv_usec - tv1.tv_usec)) );
for (i=0; i<NUM_TRANSFER; i++)
{
if (libusb_cancel_transfer(xfers[i]))
fprintf(stderr, "cancel transfer 0 failed\n");
libusb_free_transfer(xfers[i]);
}
/*
* このインタフェースと代替設定は存在しないので失敗するがこれに
* より NOT STREAMING 状態への遷移には成功する.
*/
libusb_set_interface_alt_setting(handle, 1, 0);
//libusb_reset_device(handle);
//libusb_close(handle);
libusb_exit(ctx);
close(fd);
exit(signum);
default:
break;
}
}
static void cb(struct libusb_transfer *xfer)
{
uint8_t *p;
int plen;
int i;
p = xfer->buffer;
for (i = 0; i < xfer->num_iso_packets; i++, p += PKT_LEN)
{
if (xfer->iso_packet_desc[i].status != LIBUSB_TRANSFER_COMPLETED)
continue;
plen = xfer->iso_packet_desc[i].actual_length;
/* packet only contains an acknowledge? */
if (plen < 2)
continue;
/* error packet */
if (p[1] & UVC_STREAM_ERR) /* bmHeaderInfo */
continue;
/* subtract the header size */
plen -= p[0];
/* check the data size before write */
if (plen + total > FrameBufferSize)
{
#if DEBUG
fprintf(stderr, "truncate the excess payload length.\n");
#endif
plen = FrameBufferSize - total;
}
write(fd, p + p[0], plen);
/* update padding data */
memcpy(padding + total, p + p[0], plen);
total += plen;
/* this is the EOF data. */
if (p[1] & UVC_STREAM_EOF)
{
fprintf(stderr, "%d\n", total);
if (total < FrameBufferSize)
{
/*
* insufficient frame data, so pad with the
* previous frame data.
*/
write(fd, padding + total,
FrameBufferSize - total);
fprintf(stderr, "insufficient frame data.\n");
}
total = 0;
fprintf(stderr, "%d\n", ++totalFrame);
}
}
/* re-submit a transfer before returning. */
if (libusb_submit_transfer(xfer) != 0)
{
fprintf(stderr, "submit transfer failed.\n");
}
}
int main( int argc, char **argv)
{
libusb_device **devs;
libusb_device *dev;
const struct libusb_interface *intf;
struct libusb_device_descriptor desc;
struct libusb_config_descriptor *confDesc;
struct libusb_interface_descriptor *intfDesc;
struct libusb_endpoint_descriptor *ep;
int frameIndex;
uint8_t *buf = (void *)malloc(1024);
int i;
int j;
int k;
int foundIt;
int maxPktSize;
int altSetting;
/*
* handle the command argument.
*/
if (argc < 2 || argc > 3)
{
fprintf(stderr, "Usage: ex41 bFrameIndex [file]\n");
exit(1);
}
frameIndex = strtol( argv[1], NULL, 0 );
if (argv[2] != NULL)
{
if ( (fd = open(argv[2], O_CREAT|O_TRUNC|O_RDWR, 0600)) < 0)
{
fprintf(stderr, "open file failed.\n");
exit(0);
}
}
else
fd = open("/dev/stdout", O_WRONLY, 0);
/*
* regsiter the signal callback.
*/
signal(SIGINT, signal_callback_handler);
signal(SIGPIPE, signal_callback_handler);
signal(SIGUSR1, signal_callback_handler);
signal(SIGUSR2, signal_callback_handler);
/*
* get cam device.
*/
libusb_init(&ctx);
#if DEBUG
libusb_set_debug(ctx, 1);
#endif
libusb_get_device_list(ctx, &devs);
foundIt = FALSE;
i = 0;
while ((dev = devs[i++]) != NULL)
{
libusb_get_device_descriptor(dev, &desc);
for (j = 0; uvc_device_list[j].VID != 0; j++)
{
uvc = uvc_device_list[j];
if (uvc.VID == desc.idVendor &&
uvc.PID == desc.idProduct)
{
foundIt = TRUE;
goto FOUND;
}
}
}
FOUND:
if (!foundIt)
{
fprintf(stderr, "device not found.\n");
exit(1);
}
libusb_open(dev, &handle);
libusb_free_device_list(devs, 1);
/*
* if the kernel driver is active, detach it.
*/
libusb_get_config_descriptor(dev, uvc.ConfIdx, &confDesc);
for (i=0; i<confDesc->bNumInterfaces; i++)
{
if (libusb_kernel_driver_active(handle, i) == 1)
{
fprintf(stderr,
"detaching kernel driver for interface %d.\n", i);
if (libusb_detach_kernel_driver(handle, i) != 0)
{
fprintf(stderr, "detach failed.\n");
}
}
}
/*
* search user-specified bFrameIndex which usually located at interface 1.
*/
foundIt = FALSE;
for (i = 1; i < confDesc->bNumInterfaces; i++) /* seek interfaces */
{
intf = (void *) &confDesc->interface[i];
intfDesc = (void *) &intf->altsetting[0]; /* interface i alt 0 */
/* CC_VIDEO && SC_VIDEOSTREAMING */
if (intfDesc->bInterfaceClass == 0x0e
&& intfDesc->bInterfaceSubClass == 0x02)
{
uvc.IfNum = i;
buf = (void *) intfDesc->extra;
foundIt = TRUE;
break;
}
}
if (!foundIt)
{
fprintf(stderr, "no SC_VIDEOSTREAMING.\n");
exit(1);
}
for (i = 0; i < intfDesc->extra_length;)
{
/* CS_INTERFACE && VS_FRAME_UNCOMPRESSED */
if (buf[i+1] == 0x24 && buf[i+2] == 0x05)
{
width = (buf[i+6]<<8) | buf[i+5];
height = (buf[i+8]<<8) | buf[i+7];
fprintf(stderr, "%d: %dx%d\n", buf[i+3], width, height);
if (buf[i+3] == frameIndex)
{
foundIt = TRUE;
break;
}
}
i += buf[i+0];
}
if (!foundIt)
{
fprintf(stderr, "Can't find the frame index.\n");
exit(1);
}
/*
* search VS_FORMAT_UNCOMPRESSED and see what bBitsPerPixel is.
*/
foundIt = FALSE;
for (i = 0; i < intfDesc->extra_length;)
{
/* CS_INTERFACE && VS_FORMAT_UNCOMPRESSED */
if (buf[i+1] == 0x24 && buf[i+2] == 0x04)
{
BitPerPixel = buf[i+11];
foundIt = TRUE;
break;
}
i += buf[i+0];
}
if (!foundIt)
{
fprintf(stderr, "no VS_FORMAT_UNCOMPRESSED.\n");
exit(1);
}
/*
* search payload transfer endpoint.
*/
foundIt = FALSE;
maxPktSize = 0;
altSetting = 0;
for (i = 1; i < confDesc->bNumInterfaces; i++) /* seek interfaces */
{
intf = (void *) &confDesc->interface[i];
for (j = 0; j < intf->num_altsetting; j++) /* seek alt settings */
{
intfDesc = (void *) &intf->altsetting[j];
/* CC_VIDEO && SC_VIDEOSTREAMING */
if (intfDesc->bInterfaceClass == 0x0e
&& intfDesc->bInterfaceSubClass == 0x02
&& intfDesc->bNumEndpoints != 0)
{
for (k = 0; k < intfDesc->bNumEndpoints; k++)
{
ep = (void *) &intfDesc->endpoint[k];
if (ep->wMaxPacketSize > maxPktSize)
{
maxPktSize = ep->wMaxPacketSize;
altSetting = j;
foundIt = TRUE;
}
}
}
}
}
if (!foundIt)
{
fprintf(stderr, "Can't find the appropriate endpoint.\n");
exit(1);
}
if (uvc.AltSetting == 0)
uvc.AltSetting = altSetting;
if (ep->bmAttributes == 0x05) /* isochronous ? */
XferType = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS;
else
XferType = LIBUSB_TRANSFER_TYPE_BULK;
fprintf(stderr, "XferType: %x\n", XferType);
uvc.FrameIndex = frameIndex;
FrameSize = width * height;
if (BitPerPixel == 16)
FrameBufferSize = 2*FrameSize;
else if (BitPerPixel == 8)
FrameBufferSize = 1*FrameSize;
padding = (void *) malloc(FrameBufferSize);
libusb_free_config_descriptor(confDesc);
/* set the active configuration */
if (libusb_set_configuration(handle, uvc.ConfVal) != 0)
fprintf(stderr, "set configuration failed.\n");
/* claim an interface in a given libusb_handle. */
if (libusb_claim_interface(handle, 0) != 0)
fprintf(stderr, "claim interface failed.\n");
/*
* negotiate the streaming parameters
*
* Device State Transition [UVC1.5, p. 107]
* Video Probe and Commit Controls [UVC1.5, p. 134]
*
* [0] what fields shall be kept fixed
* [1]
* [2] Video format index (Uncompressed)
* [3] Video frame index
* [4] Frame interval in 100ns
* [5]
* [6]
* [7]
* [8] Key frame rate in key-frame per videoframe units
* [9]
* [10] PFrame rate in PFrame/key frame units.
* [11]
* [12] Compression quality control in abstract units 1 to 10000.
* [13]
* [14] Window size for average bit rate control.
* [15]
* [16] Internal video streaming interface latency in ms.
* [17]
* [18] Max. video frame or codec-specific segment size in bytes (ro)
* [19] (dwMaxVideoFrameSize)
* [20]
* [21]
* [22] Specifies the maximum number of bytes (ro)
* [23] (dwMaxPayloadTransferSize)
* [24]
* [25]
*/
buf[0] = 0x01;
buf[1] = 0x00;
buf[2] = 0x01;
buf[3] = uvc.FrameIndex;
buf[4] = 0x15; /* propose: 0x00051615 (33ms) */
buf[5] = 0x16;
buf[6] = 0x05;
buf[7] = 0x00;
buf[8] = 0x00;
buf[9] = 0x00;
buf[10] = 0x00;
buf[11] = 0x00;
buf[12] = 0x00;
buf[13] = 0x00;
buf[14] = 0x00;
buf[15] = 0x00;
buf[16] = 0x00;
buf[17] = 0x00;
buf[18] = 0x00;
buf[19] = 0x00;
buf[20] = 0x00;
buf[21] = 0x00;
buf[22] = 0x00;
buf[23] = 0x00;
buf[24] = 0x00;
buf[25] = 0x00;
/* VS_PROBE_CONTROL(0x01), SET_CUR(0x01) [UVC1.5, p. 161, 158] */
libusb_control_transfer(
handle, 0x21, 0x01, 0x0100, 0x0001, buf, 26, TIMEOUT);
/* VS_PROBE_CONTROL(0x01), GET_MIN(0x82) [UVC1.5, p. 161, 158] */
libusb_control_transfer(
handle, 0xa1, 0x82, 0x0100, 0x0001, buf, 26, TIMEOUT);
/* VS_COMMIT_CONTROL(0x02), SET_CUR(0x01) [UVC1.5, p. 161, 158] */
libusb_control_transfer(
handle, 0x21, 0x01, 0x0200, 0x0001, buf, 26, TIMEOUT);
#if DEBUG
for (i = 0; i < 26; i++)
fprintf(stderr, "%02x ", buf[i]);
fprintf(stderr, "\n");
#endif
/* PKT_LEN */
PKT_LEN = (buf[25]<<24 | buf[24]<<16 | buf[23]<<8 | buf[22]);
fprintf(stderr, "dwMaxPayloadTransferSize: %08x\n", PKT_LEN);
/*
* set interface, set alt interface [USB2.0, p. 250]
*/
/* claim an interface in a given libusb_handle. */
if (libusb_claim_interface(handle, uvc.IfNum) != 0)
fprintf(stderr, "claim interface failed.\n");
/* activate an alternate setting for an interface. */
if (uvc.AltSetting != 0)
if (libusb_set_interface_alt_setting(
handle, uvc.IfNum, uvc.AltSetting) != 0)
fprintf(stderr, "activate an alternate setting failed.\n");
/*
* do an isochronous / bulk transfer
*/
for (i=0; i<NUM_TRANSFER; i++)
{
xfers[i] = libusb_alloc_transfer(PKTS_PER_XFER);
uint8_t *data = malloc(PKT_LEN*PKTS_PER_XFER);
libusb_fill_iso_transfer(
xfers[i], handle, uvc.Endpoint,
data, PKT_LEN*PKTS_PER_XFER, PKTS_PER_XFER,
cb, NULL, 0);
libusb_set_iso_packet_lengths(xfers[i], PKT_LEN);
}
for (i=0; i<NUM_TRANSFER; i++)
if (libusb_submit_transfer(xfers[i]) != 0)
fprintf(stderr, "submit xfer failed.\n");
gettimeofday(&tv1, &tz);
while (1)
libusb_handle_events(ctx);
}
-------------- next part --------------
#ifndef VIDEO_FORMAT_DESCRIPTOR
#define VIDEO_FORMAT_DESCRIPTOR
typedef struct payloadHeader {
uint8_t bHeaderLength;
uint8_t bmHeaderInfo;
#define UVC_STREAM_EOH (1 << 7)
#define UVC_STREAM_ERR (1 << 6)
#define UVC_STREAM_STI (1 << 5)
#define UVC_STREAM_RES (1 << 4)
#define UVC_STREAM_SCR (1 << 3)
#define UVC_STREAM_PTS (1 << 2)
#define UVC_STREAM_EOF (1 << 1)
#define UVC_STREAM_FID (1 << 0)
} payloadHeader __attribute__((aligned(sizeof(void *))));
typedef struct videoFormatDescriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bDescriptorSubtype;
uint8_t bFormatIndex;
uint8_t bNumFrameDescriptors;
uint8_t guidFormat[16];
uint8_t bBitsPerPixel;
uint8_t bDefaultFrameIndex;
uint8_t bAspectRatioX;
uint8_t bAspectRatioY;
uint8_t bmInterlaceFlags;
uint8_t bCopyProtect;
} videoFormatDescriptor __attribute__((aligned(sizeof(void *))));
typedef struct videoFrameDescriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bDescriptorSubtype;
uint8_t bFrameIndex;
uint8_t bmCapabilities;
uint16_t wWidth;
uint16_t wHeight;
uint32_t dwMinBitRate;
uint32_t dwMaxBitRate;
uint32_t dwMaxVideoFrameBufferSize;
uint32_t dwDefaultFrameInterval;
uint8_t bFrameIntervalType;
} videoFrameDescriptor __attribute__((aligned(sizeof(void *))));
#endif
More information about the freebsd-users-jp
mailing list