精华内容
下载资源
问答
  • Linux 媒体框架(Media Framework)一
    千次阅读
    2021-09-15 11:40:07

    Linux V4l2 框架 一

    v4l2的框架图

    在这里插入图片描述

    1.Linux Media Framework( 媒体框架)

    1.基本概念

    media framework 是嵌入到lv4l2 框架中,主要分成entity pad link三大部分,entity设备的实例

    pad 用来作为entity的连接点 link 用来连接entity 下图可以直观的看到

    在这里插入图片描述

    2.media的源码

    1.注册media总线

    首先看media-devnode.c文件,在/sys/bus/下注册了media总线,后续相关的media设备都会注册到下面

    Z:\RV1109\kernel\drivers\media\media-devnode.c
    subsys_initcall(media_devnode_init);
    
    static int __init media_devnode_init(void)
    {
    	int ret;
    
    	pr_info("Linux media interface: v0.10\n");
    	ret = alloc_chrdev_region(&media_dev_t, 0, MEDIA_NUM_DEVICES,
    				  MEDIA_NAME);
    	if (ret < 0) {
    		pr_warn("unable to allocate major\n");
    		return ret;
    	}
    
    	ret = bus_register(&media_bus_type);
    	if (ret < 0) {
    		unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);
    		pr_warn("bus_register failed\n");
    		return -EIO;
    	}
    
    	return 0;
    }
    
    
    

    2.注册media实例

    注册完总线后需要把媒体设备实例化,调用注册媒体设备接口在media总线下注册

    Z:\RV1109\kernel\drivers\media\media-device.c
    //初始化media device下的 entity pad link  链表
    void media_device_init(struct media_device *mdev)
    {
    	INIT_LIST_HEAD(&mdev->entities);
    	INIT_LIST_HEAD(&mdev->interfaces);
    	INIT_LIST_HEAD(&mdev->pads);
    	INIT_LIST_HEAD(&mdev->links);
    	INIT_LIST_HEAD(&mdev->entity_notify);
    	mutex_init(&mdev->graph_mutex);
    	ida_init(&mdev->entity_internal_idx);
    
    	dev_dbg(mdev->dev, "Media device initialized\n");
    }    
    //将媒体实例化注册到media总线下 实际调用media_devnode_register 注册media节点
    int __must_check __media_device_register(struct media_device *mdev,
    					 struct module *owner)
    {
    	struct media_devnode *devnode;
    	int ret;
    
    	devnode = kzalloc(sizeof(*devnode), GFP_KERNEL);
    	if (!devnode)
    		return -ENOMEM;
    
    	/* Register the device node. */
    	mdev->devnode = devnode;
    	devnode->fops = &media_device_fops;
    	devnode->parent = mdev->dev;
    	devnode->release = media_device_release;
    
    	/* Set version 0 to indicate user-space that the graph is static */
    	mdev->topology_version = 0;
    
    	ret = media_devnode_register(mdev, devnode, owner);
    	if (ret < 0) {
    		/* devnode free is handled in media_devnode_*() */
    		mdev->devnode = NULL;
    		return ret;
    	}
    
    	ret = device_create_file(&devnode->dev, &dev_attr_model);
    	if (ret < 0) {
    		/* devnode free is handled in media_devnode_*() */
    		mdev->devnode = NULL;
    		media_devnode_unregister_prepare(devnode);
    		media_devnode_unregister(devnode);
    		return ret;
    	}
    
    	dev_dbg(mdev->dev, "Media device registered\n");
    
    	return 0;
    }
    //注册media node节点
    int __must_check media_devnode_register(struct media_device *mdev,
    					struct media_devnode *devnode,
    					struct module *owner)
    {
    	int minor;
    	int ret;
    
    	/* Part 1: Find a free minor number */
    	mutex_lock(&media_devnode_lock);
    	minor = find_next_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES, 0);
    	if (minor == MEDIA_NUM_DEVICES) {
    		mutex_unlock(&media_devnode_lock);
    		pr_err("could not get a free minor\n");
    		kfree(devnode);
    		return -ENFILE;
    	}
    
    	set_bit(minor, media_devnode_nums);
    	mutex_unlock(&media_devnode_lock);
    
    	devnode->minor = minor;
    	devnode->media_dev = mdev;
    
    	/* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent */
    	devnode->dev.bus = &media_bus_type;
    	devnode->dev.devt = MKDEV(MAJOR(media_dev_t), devnode->minor);
    	devnode->dev.release = media_devnode_release;
    	if (devnode->parent)
    		devnode->dev.parent = devnode->parent;
    	dev_set_name(&devnode->dev, "media%d", devnode->minor);
    	device_initialize(&devnode->dev);
    
    	/* Part 2: Initialize the character device */
    	cdev_init(&devnode->cdev, &media_devnode_fops);
    	devnode->cdev.owner = owner;
    
    	/* Part 3: Add the media and char device */
    	ret = cdev_device_add(&devnode->cdev, &devnode->dev);
    	if (ret < 0) {
    		pr_err("%s: cdev_device_add failed\n", __func__);
    		goto cdev_add_error;
    	}
    
    	/* Part 4: Activate this minor. The char device can now be used. */
    	set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
    
    	return 0;
    
    cdev_add_error:
    	mutex_lock(&media_devnode_lock);
    	clear_bit(devnode->minor, media_devnode_nums);
    	devnode->media_dev = NULL;
    	mutex_unlock(&media_devnode_lock);
    
    	put_device(&devnode->dev);
    	return ret;
    }
    

    3.初始化entity 实例相关

    //pads entity links 头文件
    media/media-entity.h
    //entity init
    int media_entity_init(struct media_entity *entity, u16 num_pads,
    		  struct media_pad *pads, u16 extra_links)
    //entity pad init
    int media_entity_pads_init(struct media_entity *entity, u16 num_pads,
    			   struct media_pad *pads)
    {
    	struct media_device *mdev = entity->graph_obj.mdev;
    	unsigned int i;
    
    	if (num_pads >= MEDIA_ENTITY_MAX_PADS)
    		return -E2BIG;
    
    	entity->num_pads = num_pads;
    	entity->pads = pads;
    
    	if (mdev)
    		mutex_lock(&mdev->graph_mutex);
    
    	for (i = 0; i < num_pads; i++) {
    		pads[i].entity = entity;
    		pads[i].index = i;
    		if (mdev)
    			media_gobj_create(mdev, MEDIA_GRAPH_PAD,
    					&entity->pads[i].graph_obj);
    	}
    
    	if (mdev)
    		mutex_unlock(&mdev->graph_mutex);
    
    	return 0;
    } 
    //控制pipeline开启
    __must_check int media_pipeline_start(struct media_entity *entity,
    				      struct media_pipeline *pipe)
    {
    	struct media_device *mdev = entity->graph_obj.mdev;
    	int ret;
    
    	mutex_lock(&mdev->graph_mutex);
    	ret = __media_pipeline_start(entity, pipe);
    	mutex_unlock(&mdev->graph_mutex);
    	return ret;
    }
    //控制pipeline 关闭
    void media_pipeline_stop(struct media_entity *entity)
    {
    	struct media_device *mdev = entity->graph_obj.mdev;
    
    	mutex_lock(&mdev->graph_mutex);
    	__media_pipeline_stop(entity);
    	mutex_unlock(&mdev->graph_mutex);
    }
    

    4.links的连接

    //entity的相关连接是通过pad之间的连接
    //创建pad -> pad 的连接
    media_create_pad_link()
    //remove link
    media_entity_remove_links()
    //interface to entity的连接
    media_create_intf_link()
    //remove
    media_remove_intf_links()
        
    

    2.linux V4l2 Framework

    1.v4l2 device

    1.V4L2 device 实例基本概念

    每个设备实体都代表着一个struct v4l2_device,在linux中我们往往把这个结构嵌入到大框架中,旗下包含着设备实体

    2.v4l2 设备的注册以及相关操作

    //注册v4l2 device
    int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
    {
    	if (v4l2_dev == NULL)
    		return -EINVAL;
    
    	INIT_LIST_HEAD(&v4l2_dev->subdevs);
    	spin_lock_init(&v4l2_dev->lock);
    	v4l2_prio_init(&v4l2_dev->prio);
    	kref_init(&v4l2_dev->ref);
    	get_device(dev);
    	v4l2_dev->dev = dev;
    	if (dev == NULL) {
    		/* If dev == NULL, then name must be filled in by the caller */
    		if (WARN_ON(!v4l2_dev->name[0]))
    			return -EINVAL;
    		return 0;
    	}
    
    	/* Set name to driver name + device name if it is empty. */
    	if (!v4l2_dev->name[0])
    		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
    			dev->driver->name, dev_name(dev));
    	if (!dev_get_drvdata(dev))
    		dev_set_drvdata(dev, v4l2_dev);
    	return 0;
    }
    //初始化v4l2 device name
    int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
    						atomic_t *instance)
    {
    	int num = atomic_inc_return(instance) - 1;
    	int len = strlen(basename);
    
    	if (basename[len - 1] >= '0' && basename[len - 1] <= '9')
    		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
    				"%s-%d", basename, num);
    	else
    		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
    				"%s%d", basename, num);
    	return num;
    }
    

    2.v4l2 subdevice

    相对于一个监控系统来说,v4l2 subdevice 通常是作为一个sensor来做相应的操作

    1.v4l2 sudevice相关操作

    //v4l2 subdevice init
    void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops)
    {
    	INIT_LIST_HEAD(&sd->list);
    	BUG_ON(!ops);
    	sd->ops = ops;
    	sd->v4l2_dev = NULL;
    	sd->flags = 0;
    	sd->name[0] = '\0';
    	sd->grp_id = 0;
    	sd->dev_priv = NULL;
    	sd->host_priv = NULL;
    #if defined(CONFIG_MEDIA_CONTROLLER)
    	sd->entity.name = sd->name;
    	sd->entity.obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV;
    	sd->entity.function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
    #endif
    }
    //v4l2 subdevice通常引入media framework
    //注册subdevice到v4l2 框架中
    v4l2_device_register_subdev();
    

    2.I2c subdevice 相关操作

    //初始化device
    v4l2_i2c_subdev_init()
    //v4l2_subdev struct to the i2c_client struct
    struct i2c_client *client = v4l2_get_subdevdata(sd);
    //i2c_client to a v4l2_subdev struct
    struct v4l2_subdev *sd = i2c_get_clientdata(client);
    
    

    work
    //注册subdevice到v4l2 框架中
    v4l2_device_register_subdev();

    
    #### 2.I2c subdevice 相关操作
    
    ```c
    //初始化device
    v4l2_i2c_subdev_init()
    //v4l2_subdev struct to the i2c_client struct
    struct i2c_client *client = v4l2_get_subdevdata(sd);
    //i2c_client to a v4l2_subdev struct
    struct v4l2_subdev *sd = i2c_get_clientdata(client);
    
    
    更多相关内容
  • MediaCoder-Premium-x64 MediaCoder是最早开始使用GPU进行视频编码加速的影音转码软件之一。通过将原本完全依赖CPU的计算工作转移到GPU上进行,H.264和H.265编码所需的时间被大幅缩短。
  • jquery.media.js

    2017-03-30 16:27:43
    jquery.media.js
  • jquery media插件
  • Java中进行图像I/O(即读图片和写图片,不涉及到复杂图像处理)有三个方法: Java Image I/O API,支持常见图片,从Java 2 version 1.4.0开始就内置了。...JAI的com.sun.media.jai.codec 也有一定的图像解码能力
  • Flash Media Live Encoder 3.2 简体中文版和一分钟搞定视频直播服务器搭建(内涵基本配置文件可直接调用以及摄像头直播的web测试页面)本软件由“沙体行书
  • 基于Intel Media SDK编写的解码demo

    热门讨论 2016-01-14 11:25:34
    基于Intel Media SDK编写的解码demo 仿网络流放数据 自己配置下就可以用了
  • live555MediaServer.exe

    热门讨论 2014-12-18 18:43:54
    live555MediaServer是用来搭建流媒体服务的一个应用程序,非常好用!
  • html5media.min.js

    千次下载 热门讨论 2015-12-04 11:53:41
    html5中Video标签兼容各主流浏览器
  • 使你的 windows media player 变成全能播放器的解码器插件
  • Android nomedia问题分析

    千次阅读 2020-04-23 18:49:32
    迁移本人cnblog文章: ...内部讨论后,初步怀疑是nomedia导致,查看外置存储根目录的隐藏文件,果然有.nomdia生成,但这个是谁生成的呢?无从知晓,随后让同事提供试用过程,一步步盘查,结果定位到国内某度...

    迁移本人cnblog文章:

    一、问题起源

      最近有同事反馈试用的机器出现问题,图库的照片全部消失,新下载的第三方应用图片,也无法显示。针对该问题,当时以为是媒体库scan过程和数据库存在异常,查了半天无任何结论。内部讨论后,初步怀疑是nomedia导致,查看外置存储根目录的隐藏文件,果然有.nomdia生成,但这个是谁生成的呢?无从知晓,随后让同事提供试用过程,一步步盘查,结果定位到国内某度应用导致。对比国内其他机器,无此问题,应该是规避了。那么如何规避该问题,删除此文件或者排除此路径的隐藏机制?

     

    二、nomedia实现方式

      既然规避,自然需要弄清楚系统如何实现nomedia隐藏的机制。那么nomedia到底如何定义的呢?

    frameworks/base/core/java/android/provider/MediaStore.java

    复制代码

    /**
     * Name of the file signaling the media scanner to ignore media in the containing directory
     * and its subdirectories. Developers should use this to avoid application graphics showing
     * up in the Gallery and likewise prevent application sounds and music from showing up in
     * the Music app.
     */
    public static final String MEDIA_IGNORE_FILENAME = ".nomedia";

    复制代码

      如上定义,顾名思义,是隐藏此文件当前目录以及子目录的媒体文件。那么系统是如何利用.nomedia实现该机制的呢?
    根据代码搜索到的路径分析,目前有两个地方进行了隐藏处理,MediaProvider和MediaScanner,下面先看MediaProvider:
    1、MediaProvider
    packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java

    复制代码

        /*
         * Sets the media type of all files below the newly added .nomedia file or
         * hidden folder to 0, so the entries no longer appear in e.g. the audio and
         * images views.
         *
         * @param path The path to the new .nomedia file or hidden directory
         */
        private void processNewNoMediaPath(final DatabaseHelper helper, final SQLiteDatabase db,
                final String path) {
            final File nomedia = new File(path);
            if (nomedia.exists()) {
                hidePath(helper, db, path);
            } else {
                // File doesn't exist. Try again in a little while.
                // XXX there's probably a better way of doing this
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        SystemClock.sleep(2000);
                        if (nomedia.exists()) {
                            hidePath(helper, db, path);
                        } else {
                            Log.w(TAG, "does not exist: " + path, new Exception());
                        }
                    }}).start();
            }
        }

    复制代码

    可以看到processNewNoMediaPath方法对.nomedia进行隐藏处理,判断的代码如下:

    媒体库update时:

                } else if (newPath.toLowerCase(Locale.US).endsWith("/.nomedia")) {
                    processNewNoMediaPath(helper, db, newPath);
                }

    媒体库insertInternal:

            if (path != null && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
                // need to set the media_type of all the files below this folder to 0
                processNewNoMediaPath(helper, db, path);
            }
            return newUri;

    下面看下processNewNoMediaPath方法如何实现隐藏的:

    processNewNoMediaPath方法中调用了hidePath进行隐藏实现,而hidePath方法的关键是将媒体库中的media_type更新为0:

    复制代码

        private void hidePath(DatabaseHelper helper, SQLiteDatabase db, String path) {
            // a new nomedia path was added, so clear the media paths
            MediaScanner.clearMediaPathCache(true /* media */, false /* nomedia */);
            File nomedia = new File(path);
            String hiddenroot = nomedia.isDirectory() ? path : nomedia.getParent();
    
            // query for images and videos that will be affected
            Cursor c = db.query("files",
                    new String[] {"_id", "media_type"},
                    "_data >= ? AND _data < ? AND (media_type=1 OR media_type=3)"
                    + " AND mini_thumb_magic IS NOT NULL",
                    new String[] { hiddenroot  + "/", hiddenroot + "0"},
                    null /* groupBy */, null /* having */, null /* orderBy */);
            if(c != null) {
                if (c.getCount() != 0) {
                    Uri imagesUri = Uri.parse("content://media/external/images/media");
                    Uri videosUri = Uri.parse("content://media/external/videos/media");
                    while (c.moveToNext()) {
                        // remove thumbnail for image/video
                        long id = c.getLong(0);
                        long mediaType = c.getLong(1);
                        Log.i(TAG, "hiding image " + id + ", removing thumbnail");
                        removeThumbnailFor(mediaType == FileColumns.MEDIA_TYPE_IMAGE ?
                                imagesUri : videosUri, db, id);
                    }
                }
                IoUtils.closeQuietly(c);
            }
    
            // set the media type of the affected entries to 0
            ContentValues mediatype = new ContentValues();
            mediatype.put("media_type", 0);
            int numrows = db.update("files", mediatype,
                    "_data >= ? AND _data < ?",
                    new String[] { hiddenroot  + "/", hiddenroot + "0"});
            helper.mNumUpdates += numrows;
            ContentResolver res = getContext().getContentResolver();
            res.notifyChange(Uri.parse("content://media/"), null);
        }

    复制代码

    以上实现了媒体库的文件隐藏。下面来看MediaScanner的过程:

    2、MediaScanner

    frameworks/base/media/java/android/media/MediaScanner.java

    isNoMediaPath中:

    复制代码

                      // check to see if any parent directories have a ".nomedia" file
    1500                  // start from 1 so we don't bother checking in the root directory
    1501                  int offset = 1;
    1502                  while (offset >= 0) {
    1503                      int slashIndex = path.indexOf('/', offset);
    1504                      if (slashIndex > offset) {
    1505                          slashIndex++; // move past slash
    1506                          File file = new File(path.substring(0, slashIndex) + ".nomedia");
    1507                          if (file.exists()) {
    1508                              // we have a .nomedia in one of the parent directories
    1509                              mNoMediaPaths.put(parent, "");
    1510                              return true;
    1511                          }
    1512                      }

    复制代码

    这里可以看到在 isNoMediaPath方法中,每次扫描到含有.nomedia的路径,都会被添加到mNoMediaPaths的map中。下面看下此方法的作用:

    endfile中:

    复制代码

                    int mediaType = 0;
                    if (!MediaScanner.isNoMediaPath(entry.mPath)) {
                        int fileType = MediaFile.getFileTypeForMimeType(mMimeType);
                        if (MediaFile.isAudioFileType(fileType)) {
                            mediaType = FileColumns.MEDIA_TYPE_AUDIO;
                        } else if (MediaFile.isVideoFileType(fileType)) {
                            mediaType = FileColumns.MEDIA_TYPE_VIDEO;
                        } else if (MediaFile.isImageFileType(fileType)) {
                            mediaType = FileColumns.MEDIA_TYPE_IMAGE;
                        } else if (MediaFile.isPlayListFileType(fileType)) {
                            mediaType = FileColumns.MEDIA_TYPE_PLAYLIST;
                        }
                        values.put(FileColumns.MEDIA_TYPE, mediaType);
                    }
                    mMediaProvider.update(result, values, null, null);

    复制代码

     scanSignleFile中:

    // always scan the file, so we can return the content://media Uri for existing files
    return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
            false, true, MediaScanner.isNoMediaPath(path));

    下面分析doScanFile:

    此方法除了被scanSingleFile调用完,还被scanFile调用,说明是MediaScanner隐藏媒体文件机制的关键,下面看其实现:

                    FileEntry entry = beginFile(path, mimeType, lastModified,
                            fileSize, isDirectory, noMedia);

    其又调用了beginFile,又做了下面判断:

    复制代码

                    // rescan for metadata if file was modified since last scan
                    if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
                        if (noMedia) {
                            result = endFile(entry, false, false, false, false, false);
                        } else {
                            String lowpath = path.toLowerCase(Locale.ROOT);
                            boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);

    复制代码

    beginFile:

                if (!isDirectory) {
                    if (!noMedia && isNoMediaFile(path)) {
                        noMedia = true;
                    }
                    mNoMedia = noMedia;

    这里mNoMedia就是关键了,调用如下:

    endFile中:

    复制代码

                if (!mNoMedia) {
                    if (MediaFile.isVideoFileType(mFileType)) {
                        tableUri = mVideoUri;
                    } else if (MediaFile.isImageFileType(mFileType)) {
                        tableUri = mImagesUri;
                    } else if (MediaFile.isAudioFileType(mFileType)) {
                        tableUri = mAudioUri;
                    }
                }

    复制代码

    toValue中:

    复制代码

                if (!mNoMedia) {
                    if (MediaFile.isVideoFileType(mFileType)) {
                        map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0
                                ? mArtist : MediaStore.UNKNOWN_STRING));
                        map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0
                                ? mAlbum : MediaStore.UNKNOWN_STRING));
                        map.put(Video.Media.DURATION, mDuration);

    复制代码

    本次我们追踪的是.nomedia文件隐藏机制,可以看到与传入的noMedia的值有关,noMedia和mNoMedia决定了扫描到的媒体数据是否保存,而mNoMedia在本次分析中又取决于传入的noMedia,那么noMedia的值是如何来的呢?前面我们已经知道部分是 scanSignleFile中的isNoMediaPath调用值,另外的就是scanFile,其定义如下:

    复制代码

            @Override
            public void scanFile(String path, long lastModified, long fileSize,
                    boolean isDirectory, boolean noMedia) {
                // This is the callback funtion from native codes.
                // Log.v(TAG, "scanFile: "+path);
                doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
            }

    复制代码

    这个值又是native传过来的,继续追踪native的流程,最终定位到下面流程:

    frameworks/av/media/libmedia/MediaScanner.cpp

    复制代码

        // Treat all files as non-media in directories that contain a  ".nomedia" file
        if (pathRemaining >= 8 /* strlen(".nomedia") */ ) {
            strcpy(fileSpot, ".nomedia");
            if (access(path, F_OK) == 0) {
                ALOGV("found .nomedia, setting noMedia flag");
                noMedia = true;
            }
    
            // restore path
            fileSpot[0] = 0;
        }

    复制代码

    理清了上面的处理流程,接下来问题的解决就清晰了。

     

    三、总结

      本次处理的问题,应该是三方应用设计不规范导致,系统提供的nomedia机制本来是方便应用隐藏缓存文件,结果有些app设计者不清楚其实现机制,随意创建该文件,导致出现本问题。从用户角度考虑,该问题其实是系统的设计缺陷,不能因为ap调用不规范就引起其他应用出现问题,此类问题在Android系统上经常看到,也只能遇到一次规避一次.

    展开全文
  • V4L2框架-media device

    千次阅读 多人点赞 2018-07-02 21:14:30
    阅读原文 ...本文的目标是掌握 media device 的编码使用方法以及功能运用。 01 - V4L2框架-v4l2 device 00 - V4L2框架概述 media framework 简介 相关的控制 API 在 Documentation/Doc...

    阅读原文

    本文对 V4L2 的运行时数据流设备管理做一个详细的介绍,包括什么叫「运行时设备管理」,它是干什么用的,怎么使用等等。本文的目标是掌握 media device 的编码使用方法以及功能运用。

    01 - V4L2框架-v4l2 device
    00 - V4L2框架概述

    media framework

    • 简介
      相关的控制 API 在 Documentation/DocBook/media/v4l/media-controller.xml,本文档聚焦于内核测的media框架实现。注意:直接查看是看不出啥的,在内核的根目录下 make htmldocs 或者其它格式的都行,易于查看。

    • 运行时设备控制
      也就是设备启动之后的数据流线路控制,就像一个工厂流水线一样,流水线上面的一个个节点(贴商标、喷丝印、打包)就形同于输入设备中的一个个子设备,运行时设备控制就是要达到能够控制节点的效果,比如贴商标的机器有好几台,应该选择哪一台进行此次流水线处理,要不要把喷丝印加上去,加哪一个机子等等。

    • 作用
      提供实时的 pipeline 管理,pipeline 就理解为管道,想象一下水管,里面的水就是数据流,输入设备中的 csi->isp->video 就组成了一个 pipeline 线路。media framework 提供 pipeline 的开启、关停、效果控制、节点控制等功能。

    • 如何使用
      内核当中主要利用四个结构体把众多的节点组织起来:media_device,media_entity,media_link,media_pad。整个 media framework 都是围绕这四个结构体来进行使用的,下文会对这些进行详细介绍。

    • 抽象设备模型
      media framework 其中一个目的是:在运行时状态下发现设备拓扑并对其进行配置。为了达到这个目的,media framework将硬件设备抽象为一个个的entity,它们之间通过links连接。

    1. entity:硬件设备模块抽象(类比电路板上面的各个元器件、芯片)
    2. pad:硬件设备端口抽象(类比元器件、芯片上面的管脚)
    3. link:硬件设备的连线抽象,link的两端是pad(类比元器件管脚之间的连线)
    #------------#                #------------#
    |          __|__            __|__          |
    |         |  |  |   link   |  |  |         |
    |         | pad |<-------->| pad |         |
    |         |__|__|          |__|__|         |
    |            |                |            |
    |   entity   |                |   entity   |
    #------------#                #------------# 
    

    可以想象一下,如果各个 entity 之间需要建立连接的话,就需要在 pad 中存储 link 以及 entity 信息,link 中需要存储 pad 与 entity 信息,entity 里面需要存储 link 与 pad 信息,属于你中有我,我中有你的情况。

    media 设备

    一个 media 设备用一个 media_device 结构体来表示,通常情况下该结构体要嵌入到一个更大的设备自定义的结构体里面,并且大多数时候 media_devicev4l2_device 是处于并列的级别,还是以 omap3isp 的代码为例:

    struct isp_device {
    	struct v4l2_device v4l2_dev;
    	struct v4l2_async_notifier notifier;
    	struct media_device media_dev;
    	struct device *dev;
    	u32 revision;
    	... ...
    }
    

    使用以下函数进行 meida 设备的注册:media_device_register(struct media_device *mdev);
    函数的调用者需要在注册之前设置以下结构体成员(提前初始化该结构体是调用者的责任):

    • dev:必须指向一个父设备,通常是平台设备的device成员。
    • model:模型名字。
      以下的成员是可选的:
    • serial:序列号,必须是唯一的
    • bus_info:总线信息,如果是PCI设备的话就可以设置成"PCI:"
    • hw_revision:硬件版本。可以的话,应该用KERNEL_VERSION宏定义进行格式化
    • driver_version:驱动版本。最终生成的设备节点的名称是media[0-9],节点号由内核自动生成。

    使用以下函数进行设备卸载:media_device_unregister(struct media_device *mdev);
    需要注意的是,卸载一个并没有注册过的设备是***不安全的***。个人查看代码猜想不安全的原因主要有几个:1. 如果没有被注册,那么 media_device 内部的 entity 成员就有可能没有被初始化,如果其值为一个不确定的值,那么就会引起非法访问;2. 如果没有注册,内部的 devnode 成员就没有初始化,卸载时就会出现问题。

    「entities、pads、links」

    entities

    entities 用一个 media_entity 结构体来表示,该结构体通常被嵌入到一个更大的结构体里面,比如 v4l2_subdev 或者 video_device 结构体(不必自行分配空间,结构体内部已经包含),当然也可以直接分配一个 entities。使用以下函数对 entity 进行初始化:

    media_entity_init(struct media_entity *entity, u16 num_pads, struct
        media_pad *pads, u16 extra_links);
    

    在执行初始化函数之前需要注意的参数有:

    • num_pads:pad的数量,与驱动子设备结构相关。
    • pads:media_pad结构体数组,通常pad被嵌入到驱动自定义的结构体里面,数组地址被传递给该参数,pad需提前初始化。
    • extra_links:该函数会根据num_pads分配link数目,该参数则指明除了预分配的数量之外还需要多少额外的links。
    • entity:media_entityname、type、flags、revision和group_id 需要在初始化之前或者之后进行设置,如果结构体被嵌入到更高级的结构体里面,这些成员也可能被更高级的框架代码所设置,entity的id在注册的时候被填充(如果提前设置了id成员,则注册的时候就保持预设的值)。entity有相关的标志位「flags」来标识它的状态与功能,MEDIA_ENT_FL_DEFAULT 就表示这是一个默认的 entity。可以设置多个 entity 的组 ID 为同一个整数来标识它们是属于同一类别的,对于内核来说,组ID是没有用处的,但是组 ID 会在枚举 entity 的时候被传递到用户空间,可能在用户空间的某种情况下用得上。

    pads

    pad 使用一个 media_pad 结构体来表示,pads 数据被驱动程序管理(数组形式)。pads 使用 entity 与数组下标来进行唯一标识,entity 内部的 id 不会重复,但是不同 entity 之间的 pad id 可能会重复,所以 pad 的索引要 entity 与 id 联合确认。

    由于 pads 的数量是提前获知的(你做的芯片,你肯定知道它有几个管脚),所以 media_pad 结构体不再动态分配,并且驱动应负责对该结构体数组进行管理(避免动态分配)。驱动必须在 media_entity_init 函数被调用之前对 pads 的方向属性进行设置,pads 有 flags 位来标识它的属性,在初始化的时候仅需要设置该成员即可,其余的交由 media_entity_init 函数来完成:

    MEDIA_PAD_FL_SINK:目的pad
    MEDIA_PAD_FL_SOURCE:源pad
    

    links

    links 用一个 media_link 结构体来表示,每一个 entity 的所有 pads 里面都存储了与之相关的所有 links,一个 link 会分别被源 pad 以及目的 pad 存储,以便实现正反两个方向的遍历。使用以下函数创建 links:

    media_entity_create_link(struct media_entity *source, u16 source_pad,
    	struct media_entity *sink, u16 sink_pad, u32 flags);
    

    links 有一些 flags 位来标识其属性:

    MEDIA_LNK_FL_ENABLED:link被使能,可以用来传输数据,多个link连接到同一个sink pad时,只有一个link可以被使能。
    MEDIA_LNK_FL_IMMUTABLE:link的使能状态不能在运行时被改变,一般情况下这两个标志位同时被设置。
    MEDIA_LNK_FL_DYNAMIC:link的状态是动态可变的。
    

    和 pads 不一样,links 的数量并不总是提前确定的(电路板上面有时候你也无法完全确认需要管脚连到多少个设备上面,极有可能出现临时变更的情况),所以 media_entity_init 函数根据传入的参数预分配一定数量的 media_link 结构体,如果不够用的话会在 media_entity_create_link 中动态分配(如果 link 数量大于等于 max_link 的话就会扩充 link 数量)。

    注册与卸载

    驱动需要使用以下函数对 entity 进行注册与卸载(不需要手动执行,在 v4l2_device_un/register_subdev 函数里面完成):

    media_device_register_entity(struct media_device *mdev, struct 
        media_entity *entity);
    media_device_unregister_entity(struct media_entity *entity);
    

    内核里面使用一个唯一的正整数来表示每一个 entity(同一个 media_device 下唯一),驱动也可以通过填充 media_entity->id 成员来指定 entity 的 ID 值,但是必须保证唯一。如果 ID 由内核自动生成,则不能保证它们是连续的,事实上内核自动生成的 id 是由 entity 的 media_device->entity_id++ 来实现赋值的,该值在 media_device_register 函数里面被初始化为1。

    在卸载 entity 之后需要调用以下函数来释放申请到的相关资源,主要是释放动态分配的 media_link 结构体内存:

    media_entity_cleanup(struct media_entity *entity); //与media_entity_init结对使用
    

    要想遍历 entities 可以在用户空间进行 MEDIA_IOC_ENUM_ENTITIES 系统调用,需要设置 media_entity_desc 的 id 为 (0|MEDIA_ENT_ID_FLAG_NEXT),循环的过程中只需要设置 id |= MEDIA_ENT_ID_FLAG_NEXT 即可完成 entity 的遍历过程,如果需要枚举指定的 entitiy,需要设置 id 为指定 entity 的 id 值(内核 entity 的 id 是从1开始),这个在 entity 注册函数里面可以看到。代码实例如下,我尽量精简了贴出来的代码,防止占用过大篇幅

    int enum_media_device_entities(int iFd)
    {
    	int iRet;
    	struct media_entity_desc desc;
    
    	desc.id = 0 | MEDIA_ENT_ID_FLAG_NEXT;
    	while (1) {
    		iRet = ioctl(iFd, MEDIA_IOC_ENUM_ENTITIES, &desc);
    		if (iRet < 0) {
    			MODULE_WRN("enum media entities end\n");
    			break;
    		}
    		MODULE_DBG("entity name[%s]\n", desc.name);
    		desc.id |= MEDIA_ENT_ID_FLAG_NEXT;
    	}
    
    	return 0;
    }
    
    int main(int argc, char *argv[])
    {
    	int iErr = 0, ivFd;
    
    	ivFd = open("/dev/media0", O_RDWR);
    	iErr = enum_media_device_entities(ivFd);
    
    	close(ivFd);
    open_err:
    	return iErr;
    }
    

    图遍历(深度优先)

    图遍历是干嘛的?它为我们提供在运行时访问每一个、指定的 entities 的方法,至于为什么需要访问,是因为我们可能会需要在运行时去管理它们。

    可以使用下面的函数对同属于一个media设备的entities进行遍历(线性遍历,非典型图遍历,也就是跟链表一样的遍历方式):

    struct media_entity *entity;
    media_device_for_each_entity(entity, mdev) {
    	/* entity will point to each entity in turn */
    	...
    }
    

    驱动可能需要从一个给定的 entity,通过使能的 links 对所有的可访问到的 entities 进行遍历,meida 框架提供了一个深度优先的 API 来完成这个任务。需要注意的是,要避免对闭环的图进行遍历,否则会陷入死循环,为了避免这种情况,函数限制了最大遍历深度为 MEDIA_ENTITY_ENUM_MAX_DEPTH,该宏最新的定义是16**「截至 Linux-4.4.138」**。

    media_entity_graph_walk_start(struct media_entity_graph *graph,
        struct media_entity *entity);
    media_entity_graph_walk_next(struct media_entity_graph *graph);
    

    使用时先用第一个函数初始化图,然后循环调用第二个函数进行遍历,遍历全部完成之后第二个函数会返回NULL。遍历过程可以在任意一个时刻中断,并且无需调用清理函数。

    有相应的帮助函数用来寻找两个给定的 pads 的 link,或者通过一个 pad 来找到与之相连的另一个 pad。

    media_entity_find_link(struct media_pad *source, struct media_pad *sink);
    media_entity_remote_pad(struct media_pad *pad);
    

    补充 links 的设置

    link 的属性可以在运行时被改变,调用以下函数即可完成:media_entity_setup_link(struct media_link *link, u32 flags); flags 参数用来设置指定的 link 的属性,允许被配置的属性是从 MEDIA_LNK_FL_ENABLED 属性到 MEDIA_LNK_FL_ENABLE 或者 MEDIA_LNK_FL_DISABLE 标志,如果 link 设置了 MEDIA_LNK_FL_IMMUTABLE 标志的话,就不能够使能或者关闭。当一个 link 使能或者关闭时,meida framework 会分两次调用 sink 以及 source 端的 link_setup 来进行相关的设置,如果第二次调用失败的话,第一个调用也会被复原。

    media 设备驱动可以设置 media_device->link_notify 指向一个回调函数,此函数会在 link_setup 操作完成之后被调用。如果有任何的 link 是 non-immutable 的话,entity 驱动就需要自己实现 link_setup 操作。一个 link 的配置不应该影响到其他 link,如果一个 link 连接在 sink pad 上,并且该 link 被使能了,那么其他连接到该 pad 的 link 就不能再被使能,此时应该返回 -EBUSY

    1. pipeline与media流
      pipeline 的概念前面已经介绍过了,不再重复,这里给一副说明图(其实这个不是非常的清晰易懂,还有更加清晰易懂的由于某些原因不能放出,请读者发挥想象,根据图中以及上面的描述自行抽象出来一个图,只要紧紧围绕一点-pipeline 就是数据流链路的抽象,我相信你):
      pipeline 抽象图
    pipeline 抽象图

    当开启 streaming 时,驱动应当通知 pipeline 上所有的 entity 来维护当前状态不被改变,可以调用下面的函数完成通知(该函数只会调用 sink 端的 validate 回调):

    media_entity_pipeline_start(struct media_entity *entity, struct media_pipeline *pipe);
    

    该函数将会标记所有的处在使能 link 连线上的 entity 为 streaming 状态,不管是直接还是间接。第二个参数指向的 media_pipeline 结构体会被传递给 pipeline 上面的所有 entity,驱动需要把 media_pipeline 结构体嵌入到一个更高级的结构体里面,并且可以从 media_entity 结构体访问到 pipeline。等到需要停止 streaming 时,需要调用:

    media_entity_pipeline_stop(struct media_entity *entity);
    

    由于start函数可以嵌套调用,所以与之对应,stop函数也应该保持相应数量的调用。media_entity_pipeline_start 函数会进行 link 有效性的检验,此时 media_entitylink_validate 成员会被调用用来完成检验。下面一幅图是遍历的说明:
    pipeline 遍历

    pipeline 遍历

    上图中的圆圈序号指的是访问的先后顺序。值得一提的是内核关于广度优先图遍历的实现很耐人寻味,很具有参考价值,值得自己去找到内核代码探究一番。其中用到了栈、位图等概念。具体的代码在 media-entity.c/media_entity_pipeline_start 函数里面。

    由于上面的遍历是广度优先的,并且是全部遍历,也就是说如果你的 isp 有两个输入源,那这两个输入源可能会同时被打开,更多时候我们并不希望这种情况发生,我们期望的是指定的输入源、指定的单链路的 pipeline 被打开,此时就需要自行去管理 pipeline,可以实现一个自己的 pipeline 结构体进行相关的管理。实现方式就很简单,这个 pipeline 结构体类似下面:

    struct spec_pipeline {
        struct list_head entities; // pipeline 线上的 entity 链表头
        struct media_entity entities[PIPE_LENGTH]; //与上一个类似,随自己想法去实现
        int (*open_fun)(struct media_entity entity, int onoff);
        int (*s_stream)(struct media_entity entity, int onoff);
        ... ...
    };
    

    接下来就不用多说了吧,思维赶紧发散一下、扩展一下,很容易实现一个易用的自己特定的 pipeline,可以实现指定同源不同目的的数据流区分管理,不同源不同目的的数据流区分管理等等。

    Tips

    1. media_entity 找到 v4l2_subdev 可以使用 media_entity_to_v4l2_subdev 函数来完成。
    2. 可以在 video 模块的 streamon 部分加入对link的使能标记,media_entity_pipeline_start 函数会对 entity.stream_count 成员进行增值操作,并且会将第二个参数中的 pipe 传递给 link 线上的 entity.pipe 成员。使能标记完成之后可以对各个 entity 进行图遍历来调用其 set_stream 成员开启 stream。事实上在 s_param,s_fmt 等 ioctl 调用时就需要进行图遍历以调用整个 pipeline 上面的各个 entity 的回调函数进行相关的设置。
    3. 在子设备节点都注册完毕之后可以通过 media_entity_create_link 来完成各个 entity 的连接,以待之后整个数据流的开启。
    4. 为什么要有 media framework?因为仅仅有 subdev 的话,各个子设备类是处于平级状态,并没有数据流的流向之分,如果需要建立数据流 pipeline 的话需要自行去实现,这样会比较麻烦,而有了 media framework 的话这些管理就会方便很多,因为它提供了 pipeline 的一切管理操作,这样就可以把众多的 subdev 串联成为一个完整的数据流,从而进行正确、有向的数据传递。
    5. v4l2_subdevvideo_device 内部都有一个 media_entity 结构体(非指针类型),video_device 内部的entity会随着video 设备的注册而注册,名字沿用 video_deivce 的名字,这两个是有所区别的,使用的时候需要特别注意下。

    结束语:本文到此结束,希望你已经能够完成一个完整功能的 pipeline,能够进行 open/close, s_stream 等操作了,并且可以实现自定义的设备数据流串联,实时管理等功能。下文预告-videobuf2 的实现与使用。


    想做的事情就去做吧
    展开全文
  • Mediasoup简介及其基本概念

    万次阅读 2020-08-04 21:18:52
    流媒体服务器Mediasoup】 源码中重要类基本概念 、上层代码作用详解、底层C++类关系详解(四) Guo_IT 2020-02-24 17:02:24 1673 收藏 6 版权 目录 前言 MediaSoup的特性 特性一 特性二 特性三 Media...

    1.概念

        mediasoup 是完全兼容webrtc的高性能sfu服务器,它由ts语言实现的master端和基于libuv的c++语言实现的work模块组成。

    2.MediaSoup的特性 

    特性一

    •       支持IPV6
    •       ICE/DTLS/RTP/RTCP    既可以在TCP协议上运行也可以在UDP协议上运行
    •       支持Simulcast(多流发送) 和 SVC(分层接收) 

    特性二

    •        支持拥塞控制
    •        带宽评估  (Remb前几个章节有提到)
    •        支持STCP协议(非音视频数据 如文件,文本数据)

    特性三

    •         多路使用同一个 ICE + DTLS 传输通道 (减少端口的占用)
    •         拥有强大的性能(底层是C++ 使用进程+LIBUV  异步I/O实践处理机制)

    3.MediaSoup SFU简单的架构说明

                  å¨è¿éæå¥å¾çæè¿°

    Worker

          一个Worker代表着一个运行在单核CPU上并处理Router实例的mediasoup C++子进程;

    Router

          Router用于注入、选择和转发通过Transport实例创建的媒体流;

    Transport

        Transport将终端与MediaSoup Router连接起来,并通过在其上创建的Producer和Consumer实例实现双向媒体传输,实         现了下面3种Transport:WebRtcTransport,PlainRtpTransport,PipeTransport.

    1.       每个Client创建两个Peerconnection分别用于发送和接受媒体流,发送端用于发送承载本地videoTrack和audioTrack的      localStream,接收端接受来自其他Client的remoteStream;
    2.       同时Room会为每个Client创建一个Peer,Peer管理两个Transport用于接受Client的媒体流和向Client发送媒体流;
    3.      Peer为对应的Client发送的videoTrack和audioTrack分别创建一个Producer(共2个);
    4.      Peer为其他两个Client发送的videoTrack和audioTrack分别创建2个Consumer(共2个);
    5.      Producer将媒体数据发送给每一个订阅者Consumer
    6.     Consumer代表着一个被MediaSoup Router转发到终端的音频或视频源。它是在定义媒体数据包传送方式的Transport之上创建的。

     

    4.MediaSoup库中Lib目录下的JS作用

      

                                                                     

    AudioLevelObserver.js  

              用于检测声音的大小, 通过C++检测音频声音返回应用层,通过Observer接收并展示音频大小

    Channel.js

              主要用于与C++部分信令通讯,外部调用ts的命令,比如说创建router,最后通过channel把命令发送给worker并执行。

    Consume.js

          消费媒体数据,音频或视频

    EnhancedEventEmitter.js

          EventEmitter的封装,C++底层向上层发送事件

    Logger.js

          用于写日志

    PipeTransport.js

          Router之间的转发

    PlainRtpTransport.js

          普通的rtp传输通道,如FFmpeg等不经过浏览器rtp协议的数据传输

    Producer.js

         生产媒体数据,音频或视频

    Routers.js

         代表一个房间或者一个路由器

    RtpObserver.js

         Rtp数据的观察者 回调用的

    Transport.js

         所有传输的的基类(父类)

    WebRtcRtpTransport.js

      浏览器使用的传输

    Worker.js

        一个节点或者一个进程,实际应该是进程,代码中根据CPU核数启动相对   应的Worker数量;一个房间只能在一个Worker里。

    Errors.js

         错误信息的定义

    Index.js

         Mediasoup的库,上层引入Mediasoup最先导入的库,也为库的索引。

    Ortc.js

         其与SDP相对应,以对象的形式标识SDP,如编解码参数,编解码器,帧   率等,以对象方式去存储。

    ScalabilityModes.js

      一般不关心,略过

    SupportedRtpCapabilities.js

      对通讯能力的支持,实际上是媒体协商相关的东西,如你支持的帧率, 码率,编解码器是什么等

    Utils.js

           一些常见的工具函数

     

    5.MediaSoup-JS类的关系图

     

                                

    • 每个Worker里有多个Router
    • 每个Worker里都有一个Channel(管道) 通过其与C++进行通讯
    • 每个用户的Transport可能会有多个Produces或者 Consume
    • ransoprt为WebRtcTransport(浏览器加密数据使用的传输)、PlainRtpTransport(自定义Rtp数据,如FFmpeg推流使用的传输)、PipeTransport(不同Router之间的通信传输)的基类

    MediaSoup js部分起到的作用

    • 起到管理作用,通过上图可以看出各个模块之间的关系
    • 生成JSON字符串,传给C++; 作为承上启下的作用,即是应用层调用的接口层,又是C++层的适配层或者桥梁层,上层应用与C++的一个桥梁。

    例如

     
    1. createRouter({ mediaCodecs, appData = {} } = {}) {

    2. ....

    3. yield this._channel.request('worker.createRouter', internal);

    4. const data = { rtpCapabilities };

    5. const router = new Router_1.Router({

    6. internal,

    7. data,

    8. channel: this._channel,

    9. appData

    10. });

    11. .....

    12. }

     ..channel.request()最后会构造json字符串通过channel 传给C++层,C++层则做它自己相对应的逻辑操作.

     

    MediaSoup C++ 库类关系图

       对于C++库来说是整个MediaSoup库中最核心的部分,包括了基本的一些管理,这些管理或者关系相对于JS来说要少一些,但最主要的是流的传输,首先对于WebRtc 要先进行数据加密再传到服务端之后要对这些数据进行解密操作。另外包括整个数据的安全,它的验证机制是由C++部分进行验证的,包括流的流转,数据的流转,带宽的评估,发生丢包之后的通知客户端进行重传等操作都是有C++部分完成这些工作。

    核心类图

                     

    SimpleConsumer

            普通RTP数据的消费者,比如有音频流和视频流每个都是SimpleConsumer,没有按类型区分,音视频流都是一样的,最简单的consumer

    PipeConsumer

            不同Worker之间Router之间的数据流转,则其为接收或者消费从另外一个Worker中的Router传过来的数据

    SvcConsumer

            传输时一般分为3层(核心层、拓展层、边缘层)进行传输,则其处理消费多层数据

    SimulcastConsumer

            当共享者使用的是多路流时,则使用其来接收

    Consumer

            为上述模块的基类(父类)

    WebRtcTransport

            主要用于浏览器之间的或者浏览器与其他终端进行通讯的,这种传输数据一般是进行加密的,为了保证数据安全,它有很多安全机制,安全机制较为复杂。

    PlainRtpTransport

    用于普通或者自定义的rtp数据传输

    PipeTransport

    不同Worker之间Router之间的数据传输

    TransportTuple

    包括了本地的Socket,远端的Soucket ,使用的是TCP还是UDP , 传输协议等信息存储地方

    Transport

            各种传输的基类(父类)

     

    C++类图

                     

    RtpPack的起作用为对rtp数据包的一个分析,如Rtp包中有包头,拓展头,数据,对于数据协议或者解析都是它的工作

    SeqManager对传输的数据重新进行排序和处理,相当于WebRtc客户端与服务端之间进行传输数据的时候 服务端要新产生一个流推送给客户端,整个顺序都是重新排的,某个SSRC所对应的起始位置是多少,后面的包都是以这个起始包基础上进行传输和排序递增

    所有Consumer都包含了RtpStreamSend对象, 从服务端角度来说,消费数据等于把数据发送给其他客户端

    Producer对于服务端来说,他要生产流数据则就是接受客户端传输来的数据,因此每个Producer会对应多个RtpStreamRecv,为什么会有多个接收流?有可能是丢包了,丢包重传的数据也是单独的一路流。RtpStreamRecv使用了NackGenerator(丢包的一个产生器),对于接受者来说,发送者发了100个包,那么接受者是知道丢了哪些包(通过SeqManager知道丢的哪些包), 如果短时间内可以通过NackGenerator对客户端通知 进行补包。

    WebRtcTransport

    可以使用UDP或者TCP来传输数据,这两种传输都用PortManager进行端口管理,Mediasoup的默认端口为4000~4999(不同woker[进程]可复用),管理如关口是否被占用等一些策略。具体的传输工作还是 handle目录下的::UdpSocket和 ::TcpServer来完成。UdpSocket和TcpServer只是做了一层封装。

    使用了上述的数据连接之后,对于上层传输来说,DtlsTransport 使用了dtls协议对rtp数据包进行加密的传输,在DtlsTransport 会用到SrtpSession的 收与发。RembClient和RembServer主要用于带宽的评估,对于共享者来说MediaSoup它的WebRtcTransport就是一个Clinet端,对于消费者来说,它就是Server端。Remb只是其中一种带宽评估方法,还有其他,这里不做重点讲解。

    TransportTuple很多可选项存储在IceServer里,一对多的关系,TransportTuple如果是Tcp连接那么里面还包含了::TcpConnection,  其与 ::TcpServer又有关系,它包含了多个::TcpConnection

     

    小结

       有很多人对 Nodejs 比较诟病,认为 Nodejs 提拱不了高性能的流媒体服务器。实际上,如果按照传输的 Nodejs 应用开发出的流媒体服务器肯定是不能胜任这项工作的。但对于 Mediasoup 来讲,它只不过使用 Nodejs 做 信令处理 及 业务的管理 工作,所以它的负担并不重。对性能要求高的是媒体数据流的转发工作,而这部分工作是由 Mediasoup(C++)部分实现的。Nodejs 与 Mediasoup之间通过管道进行通信。

       严格意义上来说,Mediasoup是单进程的。但这不影响了它的性能。实际上,它是使用单进程的方式将服务器上CPU某个 充分利用好,然后在业务层控制进程的个数。比如说你的服务器是个 8 核的CPU,那么在业务层你就该启动 8 个Mediasoup进程。通过这种方式来达到对 CPU 的充分利用。

        设计理念对象实现,便于理解,但是注释比较弱,完全没有janus注释完善。

    展开全文
  • FMS4.5( Adobe Flash Media Server4.5)流媒体服务器搭建

    千次下载 热门讨论 2015-09-25 17:12:36
    FMS4.5( Adobe Flash Media Server4.5)流媒体服务器搭建 支持rtmp推送与浏览
  • windows media player插件

    热门讨论 2012-07-14 16:01:16
    安装此插件,windows media player几乎可播放大部分常用格式视频。
  • MediaSession框架全解析

    万次阅读 多人点赞 2019-04-16 18:38:04
    MediaSession这种媒体框架由MediaBrowser(媒体浏览器)和MediaBrowserService(媒体浏览器服务)两部分组成。主要作用是规范了媒体服务和界面的通信接口,达到了完全解耦,可以自由、高效进行不同的媒体的切换。 一、...
  • MediaSoup MediaSoup-demo v3 版本部署测试

    千次阅读 2020-07-04 03:05:04
    zip -r mediasoup-demo.zip mediasoup-demo 第二,mediasoup-demo/server 目录下执行: cp config.example.js config.js 第三,修改 config.js 中的个别配置 1. domain : process.env.DOMAIN || 'localhost', 将 ...
  • javax.media.jai.jar

    千次下载 热门讨论 2013-09-29 15:08:12
    javax.media.jai;javax.media.jai.RenderedOp;
  • 功能:使用Windows Media Player播放音乐,视频,支持 WAV、MID、MP3、MPG、AVI、ASF、WMV、RM、RMVB等文件。 技术:C# + WinForm 版本:Microsoft Visual Studio 2008 上开发 实现功能: 1.选择文件播放 2.设置...
  • Android的Media架构介绍

    千次阅读 2019-06-17 10:28:47
    MediaPlayer部分的头文件在frameworks/base/include/media/目录中,这个目录是和libmedia.so库源文件的目录frameworks/base/media/libmedia/相对应的。主要的头文件有以下几个: IMediaPlayer.h IMediaPlayerClient....
  • Adobe Flash Media Live Encoder 3.2

    热门讨论 2012-12-15 16:28:03
    Adobe Flash Media Live Encoder 3.2 用于rtmp流媒体采集,配合red5流媒体服务器使用,好使!!!
  • Mediasoup流媒体服务器架构及特点 前言 WebRtc有两种含义,其一是Google开源的流媒体实时通讯客户端,主要运用于浏览器之间的实时通讯,当然也可以通过提取完成移动端,PC端的通讯;另外WebRtc也是一套规范,这套...
  • 媒体查询@media使用方法

    千次阅读 2021-07-01 08:12:32
    媒体查询@media使用方法一、 书写注意事项:二、媒体特性的书写方式和样式的书写方式非常相似,主要分为两个部分,三、 媒体特性是通过min/max来表示大于等于或小于做为逻辑判断,而不是使用小于(<)和大于(>...
  • 使Windows Media Player可以播放MKV格式文件的插件 安装之后,就可以用Windows Media Player播放,不需要安装其他播放器。
  • Linux media子系统

    千次阅读 2019-05-24 14:21:02
    为什么会有media这样的一个子系统? 在多媒体的框架中,总是复杂多样的,为了解决多媒体设备的复杂性和数据流动性,创建了media子系统。Media使用一个树状结构,将多媒体数据通路的各个设备连接在一起,方便各个...
  • No MediaQuery widget ancestor found. Scaffold widgets require a MediaQuery widget ancestor. The specific widget that could not find a MediaQuery ancestor was: Scaffold dirty state: ScaffoldSta
  • mediasoup-demo 实践

    千次阅读 2020-03-14 14:08:27
    mediasoup-demo 作为 WebRTC 入门的 Hello World 演示是一个非常不错的选择。下文中记录搭建 mediasoup-demo 的过程。 1 准备环境 准备 MacOS 或者 Linux 系统环境,安装 npm 环境 更新 npm 环境 npm install -g...
  • mediasoup-demo启动流程与信令交互

    千次阅读 2021-10-23 22:33:19
    在这篇博客我们介绍了如何使用 mediasoup-demo 搭建多人音视频聊天室,本文将介绍 mediasoup-demo 的启动流程以及信令交互流程。 mediasoup-demo 项目组成主要包括两部分:app,server。 app 目录存放客户端代码...
  • @media 如何使用?

    万次阅读 2018-01-04 14:48:20
    方法一: 直接写在 CSS 样式中让其根据设备判断: @media mediatype and|not|only (media feature) { CSS-Code; } 方法二: 针对不同的媒体设备外部链入不同的 stylesheets: not / only / all not: not是用来排除掉...
  • MediaQuery 通常情况下,不会直接将MediaQuery当作一个控件,而是使用MediaQuery.of获取当前设备的信息,用法如下: var data = MediaQuery.of(context); 此方式必须放在MediaQuery作用域内,否则会抛出异常,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 637,626
精华内容 255,050
关键字:

media

友情链接: CATODO COMUM.rar