omap3isp上层应用解析
代码仓库位置:
https://git.ideasonboard.org/
仓库包含几个项目:
media-ctl
media-enum
omap3-isp-dsp
omap3-isp-live
本文从omap3-isp-dsp入手分析上层应用中media的使用流程。
main入口函数
omap3-isp-dsp/isp-dsp.c
int main(int argc __attribute__((__unused__)), char *argv[] __attribute__((__unused__)))
{
struct omap3_dsp_device dsp;
struct omap3_isp_device isp;
struct timespec start, end;
unsigned int count = 0;
int exit_code = EXIT_FAILURE;
float fps;
int ret;
/* Register a signal handler for SIGINT, received when the user presses
* CTRL-C. This will allow the main loop to be interrupted, and resources
* to be freed cleanly.
*/
signal(SIGINT, sigint_handler);
memset(&dsp, 0, sizeof dsp);
memset(&isp, 0, sizeof isp);
/* Open the media device and setup the capture pipeline. The pipeline
* topology is hardcoded to sensor -> CCDC -> CCDC output.
*/
/*
原英文注释已经很清楚:sensor输出视频-》CCDC接收-》CCDC输出数据到内存(可能用于LCD显示等)。
media是本文的重点分析。
*/
isp.mdev = media_open(MEDIA_DEVICE, 0);
if (isp.mdev == NULL) {
printf("error: unable to open media device %s\n", MEDIA_DEVICE);
goto cleanup;
}
ret = omap3isp_pipeline_setup(&isp);//建立pipeline,重点2
if (ret < 0) {
printf("error: unable to setup pipeline\n");
goto cleanup;
}
/* Open the video capture device, setup the format and allocate the
* buffers.
*/
isp.vdev = v4l2_open(isp.video->devname);//V4L2的流程在此不关心
if (isp.vdev == NULL) {
printf("error: unable to open video capture device %s\n",
isp.video->devname);
goto cleanup;
}
ret = omap3isp_video_setup(&isp);
if (ret < 0) {
printf("error: unable to setup video capture\n");
goto cleanup;
}
/* Initialize the DSP. */
ret = omap3dsp_setup(&dsp, isp.vdev->buffers);
if (ret < 0) {
printf("error: unable to setup DSP\n");
goto cleanup;
}
/* Start the DSP and ISP. */
ret = omap3dsp_start(&dsp);
if (ret < 0)
goto cleanup;
ret = omap3isp_video_start(&isp);
if (ret < 0)
goto cleanup;
clock_gettime(CLOCK_MONOTONIC, &start);
/* Main capture loop. Wait for a video buffer using select() and process
* it.
*/
while (!done) {
struct timeval timeout;
fd_set rfds;
timeout.tv_sec = SELECT_TIMEOUT / 1000;
timeout.tv_usec = (SELECT_TIMEOUT % 1000) * 1000;
rfds = isp.fds;
ret = select(isp.vdev->fd + 1, &rfds, NULL, NULL, &timeout);
if (ret < 0) {
/* EINTR means that a signal has been received, continue
* to the next iteration in that case.
*/
if (errno == EINTR)
continue;
printf("error: select failed with %d\n", errno);
goto cleanup;
}
if (ret == 0) {
/* select() should never time out as the ISP is supposed
* to capture images continuously. A timeout is thus
* considered as a fatal error.
*/
printf("error: select timeout\n");
goto cleanup;
}
process_image(&isp, &dsp);
count++;
}
clock_gettime(CLOCK_MONOTONIC, &end);
/* Stop the DSP and ISP. */
omap3isp_video_stop(&isp);
omap3dsp_stop(&dsp);
/* Print some statistics. */
end.tv_sec -= start.tv_sec;
end.tv_nsec -= start.tv_nsec;
if (end.tv_nsec < 0) {
end.tv_sec--;
end.tv_nsec += 1000000000;
}
fps = count / (end.tv_sec + end.tv_nsec / 1000000000.0);
printf("%u images processed in %lu.%06lu seconds (%f fps)\n", count,
end.tv_sec, end.tv_nsec / 1000, fps);
exit_code = EXIT_SUCCESS;
cleanup:
/* Cleanup the DSP and ISP resources. */
omap3dsp_cleanup(&dsp);
omap3isp_video_cleanup(&isp);
if (isp.mdev)
media_close(isp.mdev);
media_open获取media信息的流程
struct media_device *media_open(const char *name, int verbose)
{
struct media_device *media;
int ret;
media = malloc(sizeof(*media));
if (media == NULL) {
printf("%s: unable to allocate memory\n", __func__);
return NULL;
}
memset(media, 0, sizeof(*media));
if (verbose)
printf("Opening media device %s\n", name);
media->fd = open(name, O_RDWR);
if (media->fd < 0) {
media_close(media);
printf("%s: Can't open media device %s\n", __func__, name);
return NULL;
}
//MEDIA_IOC_DEVICE_INFO所提取的信息后面稍用到。
ret = ioctl(media->fd, MEDIA_IOC_DEVICE_INFO, &media->info);
if (ret < 0) {
printf("%s: Unable to retrieve media device information for "
"device %s (%s)\n", __func__, name, strerror(errno));
media_close(media);
return NULL;
}
if (verbose)
printf("Enumerating entities\n");
//枚举所有entity,并保存起来。关键。
ret = media_enum_entities(media);
if (ret < 0) {
printf("%s: Unable to enumerate entities for device %s (%s)\n",
__func__, name, strerror(-ret));
media_close(media);
return NULL;
}
if (verbose) {
printf("Found %u entities\n", media->entities_count);
printf("Enumerating pads and links\n");
}
//枚举所有link,并保存起来。关键。
ret = media_enum_links(media);
if (ret < 0) {
printf("%s: Unable to enumerate pads and linksfor device %s\n",
__func__, name);
media_close(media);
return NULL;
}
return media;
}
static int media_enum_entities(struct media_device *media)
{
struct media_entity *entity;
struct stat devstat;
unsigned int size;
char devname[32];
char sysname[32];
char target[1024];
char *p;
__u32 id;
int ret;
for (id = 0; ; id = entity->info.id) {
size = (media->entities_count + 1) * sizeof(*media->entities);
media->entities = realloc(media->entities, size);//entities存放所有entity
entity = &media->entities[media->entities_count];
memset(entity, 0, sizeof(*entity));
entity->fd = -1;
entity->info.id = id | MEDIA_ENT_ID_FLAG_NEXT;//希望获得NEXT id
/*
entity->info结构体是内核标准结构体:
struct media_entity_desc info;
其中有两个成员容易误会,所以将这两个成员的驱动赋值贴出来,其意思一目了然:
u_ent.pads = ent->num_pads;//entity pad数
u_ent.links = ent->num_links - ent->num_backlinks;//entity的link数
*/
ret = ioctl(media->fd, MEDIA_IOC_ENUM_ENTITIES, &entity->info);
if (ret < 0) {
if (errno == EINVAL)
break;
return -errno;
}
/* Number of links (for outbound links) plus number of pads (for
* inbound links) is a good safe initial estimate of the total
* number of links.
*/
//未能理解透彻?
entity->max_links = entity->info.pads + entity->info.links;
//以下的pads和links都是应用程序自定义的结构体,不是内核的。
entity->pads = malloc(entity->info.pads * sizeof(*entity->pads));//将会记录此entity所有pad
entity->links = malloc(entity->max_links * sizeof(*entity->links));//将会记录此entity所有link
if (entity->pads == NULL || entity->links == NULL)
return -ENOMEM;
media->entities_count++;
/* Find the corresponding device name. */
//只查找videoX或者v4l-subdevX这些节点的entity,其他类型的entity不填充devname。
if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE &&
media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
continue;
sprintf(sysname, "/sys/dev/char/%u:%u", entity->info.v4l.major,
entity->info.v4l.minor);
ret = readlink(sysname, target, sizeof(target));
if (ret < 0)
continue;
target[ret] = '\0';
p = strrchr(target, '/');
if (p == NULL)
continue;
sprintf(devname, "/dev/%s", p + 1);
ret = stat(devname, &devstat);
if (ret < 0)
continue;
/* Sanity check: udev might have reordered the device nodes.
* Make sure the major/minor match. We should really use
* libudev.
*/
if (major(devstat.st_rdev) == entity->info.v4l.major &&
minor(devstat.st_rdev) == entity->info.v4l.minor)
strcpy(entity->devname, devname);
//最后devname应该是:videoX、v4l-subdevX这些名称。
}
return 0;
}
/*
小结:
1.填充应用自定义的entity->max_links,为entity->pads和entity->links分配足够内存;
2. media->entities_count记录entity总个数。
*/
static int media_enum_links(struct media_device *media)
{
__u32 id;
int ret = 0;
for (id = 1; id <= media->entities_count; id++) {
struct media_entity *entity = &media->entities[id - 1];
struct media_links_enum links;//media_links_enum是内核定义的结构体,不是应用定义
unsigned int i;
links.entity = entity->info.id;//此entity的id号
links.pads = calloc(entity->info.pads, sizeof(struct media_pad_desc));//为当前entity分配所有pad内存. media_pad_desc内核定义的结构体
links.links = calloc(entity->info.links, sizeof(struct media_link_desc));//为当前entity分配所有link内存
//ioctl会返回这个entity对应的所有pad和link。
if (ioctl(media->fd, MEDIA_IOC_ENUM_LINKS, &links) < 0) {
printf("%s: Unable to enumerate pads and links (%s).\n",
__func__, strerror(errno));
free(links.pads);
free(links.links);
return -errno;
}
for (i = 0; i < entity->info.pads; ++i) {
//提取所有pad的参数,保存到自定义的结构体entity->pads中。
entity->pads[i].entity = entity;
entity->pads[i].index = links.pads[i].index;
entity->pads[i].flags = links.pads[i].flags;
}
for (i = 0; i < entity->info.links; ++i) {
//提取所有link的参数。
//以下除了source和sink,其他都是内核定义的结构体
struct media_link_desc *link = &links.links[i];
struct media_entity_link *fwdlink;
struct media_entity_link *backlink;
struct media_entity *source;
struct media_entity *sink;
source = media_get_entity_by_id(media, link->source.entity);
sink = media_get_entity_by_id(media, link->sink.entity);
if (source == NULL || sink == NULL) {
printf("WARNING entity %u link %u from %u/%u to %u/%u is invalid!\n",
id, i, link->source.entity, link->source.index,
link->sink.entity, link->sink.index);
ret = -EINVAL;
}
//在应用程序定义的source和sink entity各自增加一个link,将连接的信息保存好。
fwdlink = media_entity_add_link(source);
fwdlink->source = &source->pads[link->source.index];
fwdlink->sink = &sink->pads[link->sink.index];
fwdlink->flags = links.links[i].flags;
backlink = media_entity_add_link(sink);
backlink->source = &source->pads[link->source.index];
backlink->sink = &sink->pads[link->sink.index];
backlink->flags = links.links[i].flags;
fwdlink->reverse = backlink;
backlink->reverse = fwdlink;
}
free(links.pads);
free(links.links);
}
return ret;
}
/*
回顾应用程序自定义的struct media_device,注意这是应用程序自定义的不是内核那个。
struct media_device {
int fd;//打开/dev/media0的句柄
int refcount;
char *devnode;
struct media_device_info info;//一些驱动信息,基本没啥用途
struct media_entity *entities;//一个数组,记录整个驱动所有entity
unsigned int entities_count;//entities的个数
void (*debug_handler)(void *, ...);
void *debug_priv;
struct {
struct media_entity *v4l;
struct media_entity *fb;
struct media_entity *alsa;
struct media_entity *dvb;
} def;
};
*/
omap3isp_pipeline_setup建立pipeline
static int omap3isp_pipeline_setup(struct omap3_isp_device *isp)
{
struct v4l2_mbus_framefmt format;
struct media_entity *entity;
unsigned int i;
int ret;
/* Reset all links to make sure we're in a consistent, known state. */
//恢复所有link,其实就是设置disable所有非可动态改变的link
ret = media_reset_links(isp->mdev);
if (ret < 0) {
printf("error: unable to reset links.\n");
return ret;
}
/* Setup a Sensor -> CCDC -> memory pipeline.
*
* Start by locating the three entities. The output video node is
* located by looking for a devnode connected to the CCDC.
*/
/*
在所有entity数组里面寻找这个名字的entity,注意entity是应用自定义的,
跟内核的struct media_entity有区别。
MEDIA_IOC_DEVICE_INFO从驱动获取的信息,终于在这里派上用场,
media_get_entity_by_name内部用到其中的name。
*/
//找到CCDC entity。
isp->ccdc = media_get_entity_by_name(isp->mdev, ENTITY_CCDC,
strlen(ENTITY_CCDC));
if (isp->ccdc == NULL) {
printf("error: unable to locate CCDC.\n");
return -ENOENT;
}
//找到SENSOR entity。
isp->sensor = media_get_entity_by_name(isp->mdev, ENTITY_SENSOR,
strlen(ENTITY_CCDC));
if (isp->sensor == NULL) {
printf("error: unable to locate sensor.\n");
return -ENOENT;
}
for (i = 0; i < isp->ccdc->info.links; ++i) {
entity = isp->ccdc->links[i].sink->entity;
/*
这里找的是CCDC video output的videoX节点对于的entity。
也就是memory输出。
*/
if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE)
break;
}
if (i == isp->ccdc->info.links) {
printf("error: unable to locate CCDC output video node.\n");
return -ENOENT;
}
isp->video = entity;
/* Enable the Sensor -> CCDC and CCDC -> memory links. */
//这里sensor->CCDC CCDC_PAD_SINK。
ret = media_setup_link(isp->mdev, &isp->sensor->pads[0],
&isp->ccdc->pads[0], MEDIA_LNK_FL_ENABLED);
if (ret < 0) {
printf("error: unable to setup sensor -> CCDC link.\n");
return ret;
}
//这里CCDC CCDC_PAD_SINK-> CCDC video_out SINK。
ret = media_setup_link(isp->mdev, &isp->ccdc->pads[1],
&isp->video->pads[0], MEDIA_LNK_FL_ENABLED);
if (ret < 0) {
printf("error: unable to setup CCDC -> devnode link.\n");
return ret;
}
/* Configure formats. Retrieve the default format at the sensor output
* and propagate it through the pipeline. As the CCDC will not perform
* any cropping we can just apply the same format on all pads.
*/
ret = v4l2_subdev_get_format(isp->sensor, &format, 0,
V4L2_SUBDEV_FORMAT_TRY);
if (ret < 0) {
printf("error: get format on sensor output failed.\n");
return ret;
}
ret = v4l2_subdev_set_format(isp->sensor, &format, 0,
V4L2_SUBDEV_FORMAT_ACTIVE);
if (ret < 0) {
printf("error: set format failed on %s:%u.\n",
isp->sensor->info.name, 0);
return ret;
}
ret = v4l2_subdev_set_format(isp->ccdc, &format, 0,
V4L2_SUBDEV_FORMAT_ACTIVE);
if (ret < 0) {
printf("error: set format failed on %s:%u.\n",
isp->ccdc->info.name, 1);
return ret;
}
ret = v4l2_subdev_set_format(isp->ccdc, &format, 1,
V4L2_SUBDEV_FORMAT_ACTIVE);
if (ret < 0) {
printf("error: set format failed on %s:%u.\n",
isp->ccdc->info.name, 1);
return ret;
}
isp->format = format;
return 0;
}