精华内容
下载资源
问答
  •  计算机多媒体会议系统是一个以计算机为会议终端的会议系统。会议应有的各种功能都是借助于计算机来实现的,整个会议系统是通过网络互连起来的。计算机多媒体会议系统除了要提供音频、视频业务外,还应该提供如电子...
  • 多媒体开发软件开发中非常重要的一个大方向。我们日常生活中看的电影电视,听的音乐广播,户外广告,安防监控摄像头,视频直播,视频会议等等,其核心都是多媒体系统。所以,要参与这些系统的开发,就必须熟悉...
  • 设计并实现一个可行的数字多媒体文件系统是一项极具挑战性的工作,因为它通常是用在一个低成本消费类产品中,如数字视频录像机或MP3播放器,为了能够录制和回放多媒体数据,设备平台必须要有操作系统、设备驱动程序...

    设计并实现一个可行的数字多媒体文件系统是一项极具挑战性的工作,因为它通常是用在一个低成本消费类产品中,如数字视频录像机或MP3播放器,为了能够录制和回放多媒体数据,设备平台必须要有操作系统、设备驱动程序、多媒体数字信号编解码器、用于选择播放数据的用户界面以及其它复杂及开发费用昂贵的组件。

     

    这种文件系统通常应具有下列功能:


    1. 暂停和恢复播放当前节目(要求同时录制和回放)


    2. 同时录制两个不同的节目(如果有两个调谐器可以使用)


    3. 播放节目实现快进和快退(带有来自实际内容的可视或可听提示)


    4. 跳过数字多媒体数据流(前进跳过广告或后退即时重放)


    5. 将多个录制的节目保存到磁盘并进行组织,以便单独选择回放

     

    现在市面上有很多产品可以录制和回放数字多媒体节目,你可能希望通过某些快速研究就能了解将多媒体文件系统嵌入到产品中的相关信息,然而却会发现市场上没有一个视频或音频产品提供了有关其文件系统格式的更多信息。你还会发现DVD文件格式的保密性非常好,而要合法地了解其格式则需要很大一笔费用以及公司高层签署的协议书。虽然在黑客网站有一些格式说明文件,但并不能确切知道其中哪些部分可以让人相信。通常用于网站和PC浏览器的流文件格式也难以找到相关信息,并且很复杂,一般采用远低于节目录制的数据传输速率。

     

    流量控制也有很大的问题。离线视频以一个大致连续的流进行传输,经过数字化和压缩后,视频数据传输速率要大大低于硬盘传输速率,这意味着以前录制的数据可能以远大于正常速度的速率从硬盘上播放,多媒体文件要稳定播放其速率必须进行人工限制或数据流量控制。

     

    与离线视频连续流不同,硬盘数据分为需隔一定时间读取的数据块(chunk),结果导致较大的流量控制、缓存和延时问题。如果磁盘寻道时间太长或者处理器在关键时刻处理的事务太多,缓存将显得太小,且视频处理可能出现下溢;如果缓存过大,通道改变时间会增加,这样要占用更多的RAM,产品成本因而又会相应上升。为了避免在播放到暂停再到快退等转换期间出现上溢和下溢,需要进行仔细的系统分析和设计。

     

    磁盘布局

     

    和任何文件一样,多媒体数据也存储在磁盘上的“数据块”中,不过这些数据块通常是磁盘驱动器原始扇区大小一个很大的倍数。例如通常磁盘扇区大小为512字节,而我们可以找到一个512KB的数据块(1,024个连续扇区),足够用于存储高数据传输率(2至4Mbps)的MPEG-2视频流。因为磁盘寻道时间占据了磁盘存取总时间的绝大部分,所以在连续磁盘扇区进行读写比在磁盘上跳转具有更高的带宽效率。

     

    虽然如此,播放录制的多媒体文件仍需要一种快速方式,在输出数据块N的同时找到数据块N+1的位置。在两个数据块之间可能仍然需要磁盘寻道,但我们还需要一种方式让软件来确定下一个数据块的开始位置。存储和读取多媒体节目的一种方法是在某个磁盘数据块开始写入数据,并且使用连续数据块,直到整个节目结束为止。尽管这种方法是有效的,但显然不是最优,因为它会在节目删除时产生碎片。

     

    一个更加可靠的设计可以让组成文件的数据块分散在磁盘各处,这种设计必须要有一种按希望的顺序找出所有数据块的方法,此时一种称为文件分配表(FAT)的方法即能起到这种作用。图1:多媒体文件存储的磁盘逻辑结构。

     

    因为设备可能被有意或无意地关掉,所以记录各个程序的FAT需要以某种不易丢失的方式存储。如果FAT存储在磁盘上,则对每一个多媒体数据块的存取都将需要两次磁盘操作(一次是找到FAT数据以确定下一个数据块的位置,另一次操作才是实际读取希望的数据块)。尽管磁盘的数据传输率远大于视频数据数字化和压缩速率,但四处移动磁头也很消耗时间。即使将磁盘扇区组成较大数据块加以改进之后,每次记录数据块或播放数据块都要存取磁盘两次还是会大大降低数据输出裕量。

     

    流媒体数据输出要求非常高,而人的眼睛和耳朵对延迟和抖动又非常敏感,所以尽管磁盘驱动器确实很快,但录制和同时播放多媒体的要求表明,对每个多媒体数据块都要进行多次磁盘操作不是一个好方法。

     

    那么该怎么办呢?一种解决方法如图1和图2所示,其基本概念是将录制文件的FAT存储在RAM中并每过一定时间将FAT数据写入磁盘。

     

    图1显示了多媒体文件系统如何与硬盘上的标准文件系统共存。其中“目录”提供多媒体文件系统其它部分的参考信息,位于一个已知的固定位置。每个正在录制或已经录制的节目都有一个FAT,而打开的文件(正在录制或播放的节目)则在RAM中有一份FAT。图2说明了FAT如何连续指向含有多媒体文件的各个数据块。

     

    可用磁盘空间管理

     

    在数据管理中有一些微妙的地方需要注意。例如开机时,RAM中没有FAT,系统并不知道磁盘的内容,所以在进行任何操作之前,系统必须了解磁盘的内容和结构。很明显,必须找出一定的可用磁盘空间才能录制节目,找到节目的开始处才能播放节目。解决方法之一是在磁盘上一个已知位置存储“目录”,该目录提供各个节目的相关信息,如名称、录制日期以及如何找到相关FAT地址等。

     

    但是第一次系统接通电源时会怎么样呢?磁盘没有目录,也没有结构,显然需要某种格式化功能,以及确定磁盘是否已经格式化的方法。想象一种情况,新格式化磁盘有一个文件,即“全部可用空间文件”,在该磁盘进行录制就要从可用空间文件挪用数据块,然后在数据块中写入多媒体数据,并在录制相关FAT时输入指向各数据块的指针,再每隔一定时间将FAT写入磁盘。节目完成后,我们需要关闭文件,将最后的FAT写入磁盘,更新目录信息,并确保所有未使用的RAM空间可用于录制新文件。

     

    打开一个文件进行录制与打开一个已录制文件进行播放有明显的区别。播放文件时,必须在播放开始之前将存档FAT的全部或部分载入RAM。在对成本非常敏感的场合RAM很有限,因而任何时候都只将部分FAT载入RAM具有很多好处。但如果只载入部分FAT,就必须要小心预计随着节目播放将需要更多的FAT信息,这在快进和快退时尤其重要,这两种情况下,必须动态地读取FAT后面或前面的数据。

     

    为了使产品能正确工作,在最差时间条件下也必须能够处理数据的读取。播放文件时,应从磁盘读取与各个连续FAT相关的多媒体数据块,并将数据传递给音频/视频子系统,同时注意缓存和流量控制问题。在多媒体数据流中进行跳转(如跳过广告功能)时必须做一系列同步工作,确保读取数据、缓存、处理等事务正确排列,以避免停顿、急速播放和抖动。

     

    删除一个已录制的节目涉及将其所有多媒体数据块归还给可用空间、更新目录和其它一些过程。删除文件会产生一些问题,必须在产品出货前加以解决,如可用空间会含有碎片,所以空间分配算法需要能够解决,另外一些难题包括在系统运行同时处理目录中的间隙以及将数据块动态地归还给可用空间等等。

     

    在记录文件期间或者文件未正确关闭时关掉电源也会出现类似问题。下次接通电源时,必须进行一次恢复操作,以使混乱的系统恢复正常(最好在对磁盘进行任何存取之前完成)。系统需要有一个相对简单的方法来确定文件是否存在、是否打开、是否没有正确关闭以及明确其它各种操作条件。

     

    API函数

     

    让我们看一看在一个嵌入式平台上实现多媒体所需的函数。标准磁盘函数如打开、关闭、读取、写入、搜索、删除和格式化等都是不可少的,但是不同应用程序的多媒体特性可能需要某些不同的实现方法(特别是考虑到向前和向后读取以支持快进和快退的需要)。此外,与特定应用程序相关的磁盘函数还包括获取和设置节目信息、支持文件跳转的时间跟踪和计算、调试应用程序以及支持用户界面的应用程序等。最后,在产品出货前,必须还有磁盘碎片整理和错误恢复软件。

     

    下面我们介绍一些大多数系数都需要的多媒体文件系统(MMFs)函数:

     

    MMFsInit()


    在开机时调用,作用是初始化子系统。

     

    MMFsFormat()
    在多媒体应用程序使用磁盘前,必须对硬盘上的某些信息进行初始化并组织可用空间,应用程序还必须能够识别磁盘已经格式化。每次格式化都指定一个版本号是一个好的做法,这样以后版本的文件系统软件可以后向兼容并使用户可以继续查看以前录制的资料。

     

    MMFsOpen()
    用于打开现有文件以便播放或创建新的文件以完成录制,视应用程序具体情况不同,你也可能希望打开现有文件并向该文件追加新内容。当一个文件打开后,将占用指定给FAT的一块RAM,它含有要播放的预先录制的文件连续数据块列表,或用于录制文件的系列“可用”数据块列表。维护已打开文件列表精确性对设备正确操作至关重要。

     

    MMFsClose()
    当一个文件关闭时,我们应确定所有多媒体数据块均已写入并且最新的FAT被存档到磁盘。某些常规事务还是必不可少的,必须更新文件状态以指明文件没有打开,并且将保留它的FAT的RAM标记为可用。

     

    MMFsRecover()
    有时候文件未正确关闭(例如用户正在进行录制时关闭设备),这将产生不正确的文件“打开”状态和其它一些潜在的问题,还有可能在磁盘四处留下不连续的多媒体数据块。不正确状态应该在开机后立即纠正,丢失的空间最终可能相当大,一个定期清除不连续数据块的后台工具可延长设备使用寿命,增强使用效果。

     

    MMFsRead()和MMFsWrite()
    读取磁盘是很简单的工作,即访问FAT的RAM副本,获取下一个数据块的磁盘地址,然后读取该数据;写入在概念上也是很简单的,获取要写入下一个数据块的磁盘地址,然后写入数据。处理快进和快退可能涉及一些额外的信息,如跳过磁盘数据块并读取下一个数据块。

     

    MMFsDelete()
    删除一个多媒体文件在概念上相当简单,只需将与文件关联的所有磁盘数据块归还给可用空间即可,但实际实施还是有一点挑战性。要注意的第一件事是我们需要将文件的FAT(或至少一部分)载入RAM之后才可识别那些要归还给可用空间的磁盘数据块,我们还需注意嵌入式系统的多任务问题。必须防止占用磁盘空间的操作在将磁盘空间释放同时存取数据结构,如果允许在录制的同时删除文件,则对软件开发人员实在是一个有趣的挑战。还要注意删除一个文件将在目录中形成间隙,间隙可能在形成的同时经压缩而消失,其方法是通过动态改变当前录制目录实现。另外也有可能保留间隙,在这种情况下,必须设计几个函数作为正常操作的一部分来处理数组中的间隙。

     

    MMFsSeek()
    几种流行的搜索方式在多媒体文件系统中都相当有用。可以按时间(如10秒)或距离(如三个磁盘数据块)进行搜索,还可以进行绝对(从文件的开始处重新开始)和相对(向前跳过30秒)搜索。

     

    MMFsListDir()


    列出所有已录制的多媒体。根据具体实施情况不同,此函数也可列出磁盘上所有可用空间。

     

    MMFsModeChange()
    多媒体文件系统有很多操作模式,这些模式之间的转换必须同步协调进行。例如,要使用户从播放转换到快进时感到满意,可能需要转储已经等待显示的“播放”数据,然后用经过特殊读取和处理的“快进”数据代替。如果系统将读取、处理和显示操作分在不同的任务之中,则需要完成一些比较困难的同步工作。

     

    MMFsSetInfo()和MMFsGetInfo()
    有一些维护函数用来设置和获取与存储的多媒体文件有关的信息。举例而言,这些信息包括节目标题、录制的时间和日期、简短的节目介绍以及观看者书签。高级实施方案可以添加诸如“隐藏”之类文件属性,甚至可以允许用密码保护文件。

     

    MMFsManageFreeSpace()
    可用空间是多媒体文件系统的生命线,可用空间分配给录制过程,在文件删除后归还。此处理必须在一个具有优先时序安排、中断处理以及任务间通信和同步的环境中有效无误地操作。随着时间的推移,磁盘上的可用空间将含有很多碎片,有些空间还会丢失,应精心设计确保可用空间管理及相关碎片整理得到有效实施。

     

    RawWrite()和RawRead()
    用于处理从一个特定磁盘扇区开始的较大连续数据块的低层程序。连续安排数据是很好的做法,因为它让硬盘自己的固件控制磁头移动,这是其主要特性所在。但是如果磁盘数据块太大,将因为处理大量的扇区而形成延迟,进而会导致其它问题。

     

    一个针对多媒体应用程序相当流行的观点是,应在磁盘驱动程序中关闭错误恢复以防止驱动器独占系统处理器。我的经验是因硬盘错误而造成的系统操作中断相当少见,其它系统干扰如用户通道改变、信号丢失甚至系统崩溃更有可能干扰运行。

     

    最后,你还可能对实际的磁盘驱动程序如此之差的工作状况惊讶不已。为了使性能达到最优,你希望实际的硬盘传输通过直接内存存取进行处理,还希望一次传输完成中断。但如果多媒体应用程序运行时,遇到所选操作系统磁盘驱动程序的错误和缺陷,也是很正常的,因为多媒体磁盘不在针对大多数驱动程序的标准测试参数之内。图2:FAT记录指明了数据块的位置。

     

    其它注意事项

     

    听起来像有很多工作要做,事实也确实如此,多媒体文件系统的设计和实施不是一件微不足道的事,要获得成功,必须要有适当的人员配备和时间安排。另外除了上面讨论的方案,你可能还会对以下一些挑战感兴趣:

     

    1. 为多媒体数据的数据块选择正确大小。选择太大,低比特率节目将用很多时间来填写一个数据块;选择得太小,将需要过多的磁盘存取(也即磁头需要大量移动)。每一种极端情况下系统的性能都会下降,我发现512K连续磁盘数据块在2至4Mbps数据传输率范围内能较好地满足这些互相冲突的要求。

     

    2. 与可用空间分配和管理有关的详细资料。我赞成预先分配大块可用空间来录制文件,这将最大程度地减少碎片(因而减少磁头移动)且还有其它优点,当然它也有一些缺点。

     

    3. 录制很长的节目时处理FAT溢出。您可以选择一种实施方案,在这种方案中FAT足够大,可以处理持续时间大于磁盘实际可以容纳的节目,在这种情况下,FAT绝对不会溢出。这是一种工程折衷,你必须要考虑到这一点。

     

    4. 什么时候、以什么频率将FAT和目录写入磁盘?最有可能通过一种低优先级的后台任务来完成,但是须小心不要将优先级别颠倒。

     

    5. 在播放、快退、快进、暂停和所有其它“特别模式”之间转换同步时需要注意的一些重要事情。缓存需要清洗和重填,读指针需要重新定位,甚至在不同的显示模式之间读数方式也可能有很大的改变。

     

    6. 相对于其它系统函数录制和播放的优先级。确保在分析中包含模式同步,在录制的同时进行播放和快退、快进及其它操作转换需要一定的技巧,须注意系统死锁、优先级倒置和其它一些问题。

     

    7. 很多内容供应商和广播公司要求存储在文件系统中的数据经过加密,因而不能复制这些数据,最终结果可能应用了几层加密。

     

    8. 处理大硬盘驱动器。“大”是一个相对术语,去年还是大的在今年已经不再算大了。事实上,太级驱动器可能很快就会出现在消费类市场中,此类驱动器可以存储数以千计的多媒体节目。所有这些信息究竟如何通过一个简单的用户界面组织和存取呢?

     

    在我从事的所有项目中,为数字机顶盒设计和实施多媒体文件系统是最有乐趣、最富知识挑战性的事情之一。多媒体文件系统需要在一个消费类产品价位的平台上处理如此大量的高速数据,工程折衷几乎没有止境。幸运的是,最终结果令人相当满意,再也没有比看见你的产品出现在消费者手中或者放在你的家中更令你高兴的事情了。

     

    作者: Mike Ficco


    顾问


    Email: mficco@comcast.net

    展开全文
  • 多媒体教室

    2014-04-18 12:21:34
    作为新型的教育、培训模式,学校多媒体教室的建设为其提供了丰富...众多因素要求多媒体教学系统应实现如下多种功能:既能进行丰富的多媒体教学,又能进行传统教学,同时可作它用,力求教室资源达到最大限度的有效利用。
  • 5、成品文件夹只含一个完成的数据库设计。 说明: 由于自己这个数据库设计成绩只得了个中,做得实在很简单,呵呵,所以只能给大家做个参考而已。 目录与要求: 多媒体教室申请管理系统 1.项目背景 1.1选题背景 ...
  • MediaStore这个类android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取。这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,android把所有的多媒体数据库接口进行了封装,...

    引用:http://www.oschina.net/question/16_7603

    应网友要求,今天给大家讲android的多媒体数据库。MediaStore这个类是android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取。这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,android把所有的多媒体数据库接口进行了封装,所有的数据库不用自己进行创建,直接调用利用ContentResolver去掉用那些封装好的接口就可以进行数据库的操作了。今天我就介绍一些这些接口的用法。

      首先,要得到一个ContentResolver实例,ContentResolver可以这样获取,利用一个Activity或者Service的Context即可。如下所示:

      ContentResolver mResolver = ctx.getContentResolver();

      上面的那个ctx的就是一个context,Activity.this就是那个Context,这个Context就相当于一个上下文环境。得到这个Context后就可以调用getContentResolver接口获取ContentResolver实例了。ContentResolver实例获得后,就可以进行各种查询,下面我就以音频数据库为例讲解增删改查的方法,视频和图像和音频非常类似。

      在讲解各种查询之前,我给大家介绍下怎么看android都提供了哪些多媒体表。在adb shell中,找到/data/data/com.android.providers.media/databases/下,然后找到SD卡的数据库文件(一般是一个.db文件),然后输入命令sqlite3加上这个数据库的名字就可以查询android的多媒体数据库了。.table命令可以列出所有多媒体数据库的表,.scheme加上表名可以查询表中的所有列名。这里可以利用SQL语句来查看你想要的数据,记得最后一定要记住每条语句后面都加上分号。下面开始讲述怎么在这些表上进行增删改查。

    查询,代码如下所示:

      Cursor cursor = resolver.query(_uri, prjs, selections, selectArgs, order);

         ContentResolver的query方法接受几个参数,参数意义如下:

         Uri:这个Uri代表要查询的数据库名称加上表的名称。这个Uri一般都直接从MediaStore里取得,例如我要取所有歌的信息,就必须利用MediaStore.Audio.Media. EXTERNAL _CONTENT_URI这个Uri。专辑信息要利用MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI这个Uri来查询,其他查询也都类似。

         Prjs:这个参数代表要从表中选择的列,用一个String数组来表示。

         Selections:相当于SQL语句中的where子句,就是代表你的查询条件。

         selectArgs:这个参数是说你的Selections里有?这个符号是,这里可以以实际值代替这个问号。如果Selections这个没有?的话,那么这个String数组可以为null。

         Order:说明查询结果按什么来排序。

         上面就是各个参数的意义,它返回的查询结果一个Cursor,这个Cursor就相当于数据库查询的中Result,用法和它差不多。

    增加,代码如下所以:

         ContentValues values = new ContentValues();

    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER,0);

    resolver.insert(_uri, values);

      这个insert传递的参数只有两个,一个是Uri(同查询那个Uri),另一个是ContentValues。这个ContentValuses对应于数据库的一行数据,只要用put方法把每个列的设置好之后,直接利用insert方法去插入就好了。

      更新,代码如下:

    ContentResolver resolver = ctx.getContentResolver();

    Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;

    ContentValues values = new ContentValues();

    values.put(MediaStore.Audio.Media.DATE_MODIFIED, sid);

    resolver.update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,values, where, selectionArgs);

    上面update方法和查询还有增加里的参数都很类似,这里就不再重复叙述了,大家也可直接参考google的文档,那里也写的很清楚。

      删除,代码如下:

    ContentResolver resolver = ctx.getContentResolver();

      resolver.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,where, selectionArgs);

      delete和更新的方法很类似,大家对照更新的方法看下马上就会明白,今天就写到这里了最近比较忙,打算一周发一篇心得体会吧,希望大家也把自己的开发心得写出来多多交流。

    展开全文
  • 众多因素要求多媒体教学系统应实现如下多种功能:既能进行丰富的多媒体教学,又能进行传统教学,同时可作它用,力求教室资源达到最大限度的有效利用。 保靖县迁陵镇xx中心完小地处保靖城东,距县城1.3公里。学校...
  • 项目需求瞄准了Bilibili的录屏直播功能,基本就仿着做一个吧。研究后发现Bilibili使用的MediaProjection 与 VirtualDisplay结合实现的,需要 Android 5.0 Lollipop API 21以上的系统才能使用。 其实官方提供...

    应项目需求瞄准了Bilibili的录屏直播功能,基本就仿着做一个吧。研究后发现Bilibili是使用的MediaProjection 与 VirtualDisplay结合实现的,需要 Android 5.0 Lollipop API 21以上的系统才能使用。

    其实官方提供的 android-ScreenCapture 这个Sample中已经有了MediaRecorder的实现与使用方式,还有使用MediaRecorder实现的录制屏幕到本地文件的Demo,从中我们都能了解这些API的使用。

    而如果需要直播推流的话就需要自定义MediaCodec,再从MediaCodec进行编码后获取编码后的帧,免去了我们进行原始帧的采集的步骤省了不少事。可是问题来了,因为之前没有仔细了解H264文件的结构与FLV封装的相关技术,其中爬了不少坑,此后我会一一记录下来,希望对用到的朋友有帮助。

    项目中对我参考意义最大的一个Demo是网友Yrom的GitHub项目 ScreenRecorder ,Demo中实现了录屏并将视频流存为本地的MP4文件(咳咳,其实Yrom就是Bilibili的员工吧?( ゜- ゜)つロ):smirk:。在此先大致分析一下该Demo的实现,之后我会再说明我的实现方式。

    ScreenRecorder

    具体的 原理 在Demo的README中已经说得很明白了:

    • Display 可以“投影”到一个 VirtualDisplay
    • 通过 MediaProjectionManager 取得的 MediaProjection 创建 VirtualDisplay
    • VirtualDisplay 会将图像渲染到 Surface 中,而这个 Surface 是由 MediaCodec 所创建的
    > mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
    > ...
    > mSurface = mEncoder.createInputSurface();
    > ...
    > mVirtualDisplay = mMediaProjection.createVirtualDisplay(name, mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mSurface, null, null);
    >
    

    >

    • MediaMuxer 将从 MediaCodec 得到的图像元数据封装并输出到MP4文件中
    > int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
    > ...
    > ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
    > ...
    > mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);
    >
    >
    

    >

    所以其实在 Android 4.4 上可以通过 DisplayManager 来创建 VirtualDisplay 也是可以实现录屏,但因为权限限制需要 ROOT 。 (see DisplayManager.createVirtualDisplay() )

    Demo很简单,两个Java文件:

    • MainActivity.java
    • ScreenRecorder.java

    MainActivity

    类中仅仅是实现的入口,最重要的方法是 onActivityResult ,因为MediaProjection就需要从该方法开启。但是别忘了先进行MediaProjectionManager的初始化

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
        if (mediaProjection == null) {
            Log.e("@@", "media projection is null");
            return;
        }
        // video size
        final int width = 1280;
        final int height = 720;
        File file = new File(Environment.getExternalStorageDirectory(),
                "record-" + width + "x" + height + "-" + System.currentTimeMillis() + ".mp4");
        final int bitrate = 6000000;
        mRecorder = new ScreenRecorder(width, height, bitrate, 1, mediaProjection, file.getAbsolutePath());
        mRecorder.start();
        mButton.setText("Stop Recorder");
        Toast.makeText(this, "Screen recorder is running...", Toast.LENGTH_SHORT).show();
        moveTaskToBack(true);
    }
    

    ScreenRecorder

    这是一个线程,结构很清晰, run() 方法中完成了MediaCodec的初始化,VirtualDisplay的创建,以及循环进行编码的全部实现。

    线程主体

     @Override
    public void run() {
        try {
            try {
                prepareEncoder();
                mMuxer = new MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",
                    mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                    mSurface, null, null);
            Log.d(TAG, "created virtual display: " + mVirtualDisplay);
            recordVirtualDisplay();
        } finally {
            release();
        }
    }
    

    MediaCodec的初始化

    方法中进行了编码器的参数配置与启动、Surface的创建两个关键的步骤

    private void prepareEncoder() throws IOException {
        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // 录屏必须配置的参数
        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
        Log.d(TAG, "created video format: " + format);
        mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
        mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mSurface = mEncoder.createInputSurface(); // 需要在createEncoderByType之后和start()之前才能创建,源码注释写的很清楚
        Log.d(TAG, "created input surface: " + mSurface);
        mEncoder.start();
    }
    

    编码器实现循环编码

    下面的代码就是编码过程,由于作者使用的是Muxer来进行视频的采集,所以在resetOutputFormat方法中实际意义是将编码后的视频参数信息传递给Muxer并启动Muxer。

    private void recordVirtualDisplay() {
        while (!mQuit.get()) {
            int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
            Log.i(TAG, "dequeue output buffer index=" + index);
            if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                resetOutputFormat();
            } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
                Log.d(TAG, "retrieving buffers time out!");
                try {
                    // wait 10ms
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                }
            } else if (index >= 0) {
                if (!mMuxerStarted) {
                    throw new IllegalStateException("MediaMuxer dose not call addTrack(format) ");
                }
                encodeToVideoTrack(index);
                mEncoder.releaseOutputBuffer(index, false);
            }
        }
    }
    
    private void resetOutputFormat() {
        // should happen before receiving buffers, and should only happen once
        if (mMuxerStarted) {
            throw new IllegalStateException("output format already changed!");
        }
        MediaFormat newFormat = mEncoder.getOutputFormat();
      	// 在此也可以进行sps与pps的获取,获取方式参见方法getSpsPpsByteBuffer()
        Log.i(TAG, "output format changed.\n new format: " + newFormat.toString());
        mVideoTrackIndex = mMuxer.addTrack(newFormat);
        mMuxer.start();
        mMuxerStarted = true;
        Log.i(TAG, "started media muxer, videoIndex=" + mVideoTrackIndex);
    }
    

    获取sps pps的ByteBuffer,注意此处的sps pps都是read-only只读状态

    private void getSpsPpsByteBuffer(MediaFormat newFormat) {
    	ByteBuffer rawSps = newFormat.getByteBuffer("csd-0");  
    	ByteBuffer rawPps = newFormat.getByteBuffer("csd-1"); 
    }
    

    录屏视频帧的编码过程

    BufferInfo.flags表示当前编码的信息,如源码注释:

     /**
     * This indicates that the (encoded) buffer marked as such contains
     * the data for a key frame.
     */
    public static final int BUFFER_FLAG_KEY_FRAME = 1; // 关键帧
    
    /**
     * This indicated that the buffer marked as such contains codec
     * initialization / codec specific data instead of media data.
     */
    public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 该状态表示当前数据是avcc,可以在此获取sps pps
    
    /**
     * This signals the end of stream, i.e. no buffers will be available
     * after this, unless of course, {@link #flush} follows.
     */
    public static final int BUFFER_FLAG_END_OF_STREAM = 4;
    

    实现编码:

    private void encodeToVideoTrack(int index) {
        ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
            // The codec config data was pulled out and fed to the muxer when we got
            // the INFO_OUTPUT_FORMAT_CHANGED status.
            // Ignore it.
            // 大致意思就是配置信息(avcc)已经在之前的resetOutputFormat()中喂给了Muxer,此处已经用不到了,然而在我的项目中这一步却是十分重要的一步,因为我需要手动提前实现sps, pps的合成发送给流媒体服务器
            Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
            mBufferInfo.size = 0;
        }
        if (mBufferInfo.size == 0) {
            Log.d(TAG, "info.size == 0, drop it.");
            encodedData = null;
        } else {
            Log.d(TAG, "got buffer, info: size=" + mBufferInfo.size
                    + ", presentationTimeUs=" + mBufferInfo.presentationTimeUs
                    + ", offset=" + mBufferInfo.offset);
        }
        if (encodedData != null) {
            encodedData.position(mBufferInfo.offset);
            encodedData.limit(mBufferInfo.offset + mBufferInfo.size); // encodedData是编码后的视频帧,但注意作者在此并没有进行关键帧与普通视频帧的区别,统一将数据写入Muxer
            mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);
            Log.i(TAG, "sent " + mBufferInfo.size + " bytes to muxer...");
        }
    }
    

    以上就是对ScreenRecorder这个Demo的大体分析,由于总结时间仓促,很多细节部分我也没有进行深入的发掘研究,所以请大家抱着怀疑的态度阅读,如果说明有误或是理解不到位的地方,希望大家帮忙指出,谢谢!

    展开全文
  • MediaStore这个类android系统提供的一个多媒体数据库,android 中多媒体信息都可以从这里提取。这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,android把所有的多媒体数据库接口 进行了封装,...

    应网友要求,今天给大家讲android的多媒体数据库。MediaStore这个类是android系统提供的一个多媒体数据库,android 中多媒体信息都可以从这里提取。这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,android把所有的多媒体数据库接口 进行了封装,所有的数据库不用自己进行创建,直接调用利用ContentResolver去掉用那些封装好的接口就可以进行数据库的操作了。今天我就介绍 一些这些接口的用法。

      首先,要得到一个ContentResolver实例,ContentResolver可以这样获取,利用一个Activity或者Service的Context即可。如下所示:

      ContentResolver mResolver = ctx.getContentResolver();

      上面的那个ctx的就是一个context,Activity.this就是那个Context,这个Context就相当于一个上下文环境。 得到这个Context后就可以调用getContentResolver接口获取ContentResolver实例了。 ContentResolver实例获得后,就可以进行各种查询,下面我就以音频数据库为例讲解增删改查的方法,视频和图像和音频非常类似。

      在讲解各种查询之前,我给大家介绍下怎么看android都提供了哪些多媒体表。在adb shell中,找到/data/data/com.android.providers.media/databases/下,然后找到SD卡的数据库文 件(一般是一个.db文件),然后输入命令sqlite3加上这个数据库的名字就可以查询android的多媒体数据库了。.table命令可以列出所有 多媒体数据库的表,.scheme加上表名可以查询表中的所有列名。这里可以利用SQL语句来查看你想要的数据,记得最后一定要记住每条语句后面都加上分 号。下面开始讲述怎么在这些表上进行增删改查。

    查询,代码如下所示:

      Cursor cursor = resolver.query(_uri, prjs, selections, selectArgs, order);

         ContentResolver的query方法接受几个参数,参数意义如下:

         Uri:这个Uri代表要查询的数据库名称加上表的名称。这个Uri一般都直接从MediaStore里取得,例如我要取所有歌的信息,就必须利用 MediaStore.Audio.Media. EXTERNAL _CONTENT_URI这个Uri。专辑信息要利用MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI这个 Uri来查询,其他查询也都类似。

         Prjs:这个参数代表要从表中选择的列,用一个String数组来表示。

         Selections:相当于SQL语句中的where子句,就是代表你的查询条件。

         selectArgs:这个参数是说你的Selections里有?这个符号是,这里可以以实际值代替这个问号。如果Selections这个没有?的话,那么这个String数组可以为null。

         Order:说明查询结果按什么来排序。

         上面就是各个参数的意义,它返回的查询结果一个Cursor,这个Cursor就相当于数据库查询的中Result,用法和它差不多。

    增加,代码如下所以:

         ContentValues values = new ContentValues();

    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER,0);

    resolver.insert(_uri, values);

      这个insert传递的参数只有两个,一个是Uri(同查询那个Uri),另一个是ContentValues。这个ContentValuses对应于数据库的一行数据,只要用put方法把每个列的设置好之后,直接利用insert方法去插入就好了。

      更新,代码如下:

    ContentResolver resolver = ctx.getContentResolver();

    Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;

    ContentValues values = new ContentValues();

    values.put(MediaStore.Audio.Media.DATE_MODIFIED, sid);

    resolver.update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,values, where, selectionArgs);

    上面update方法和查询还有增加里的参数都很类似,这里就不再重复叙述了,大家也可直接参考google的文档,那里也写的很清楚。

      删除,代码如下:

    ContentResolver resolver = ctx.getContentResolver();

      resolver.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,where, selectionArgs);

      delete和更新的方法很类似,大家对照更新的方法看下马上就会明白,今天就写到这里了最近比较忙,打算一周发一篇心得体会吧,希望大家也把自己的开发心得写出来多多交流。

    展开全文
  • 零起点学通C++多媒体范例教学代码

    热门讨论 2010-11-30 09:35:13
    第2章 做一个最简短的C4-+程序 2.1 简单的屏幕输出小程序 2.2 输出语句的使用 2.3 std::介绍 2.4 iostream与iostream.h的区别 2.5 重名问题 2.6 注释 2.7 总结 第3章 初步了解函数 3.1 一个简单的函数 3.2 函数的...
  • 键盘检测系统

    2010-03-03 09:25:00
    在键盘生产流水线上,每一个键盘都要经过工人测试后,方能出厂,这里给出的适合大规模,高速生产的键盘检测系统程序,该程序特点能有效检测POWER,Sleep,Wake/up,WINDOWS标志和多媒体等按键。在检测完毕后能自动复位....
  • 第2章 做一个最简短的C4-+程序 2.1 简单的屏幕输出小程序 2.2 输出语句的使用 2.3 std::介绍 2.4 iostream与iostream.h的区别 2.5 重名问题 2.6 注释 2.7 总结 第3章 初步了解函数 3.1 一个简单的函数 ...
  • 同时给出了一个标准小区智能系统的组建方法。本文介绍的标准小区智能信息系统工程分为园区周界防护系统、视频监控系统、电子巡更系统、联网型可视对讲系统、家庭安防系统和家庭多媒体信息系统等六大部分。 1、系统...
  • 3G移动通信系统将是一个能综合实时业务、非实时业务、宽带业务、窄带业务的网络,能够满足多媒体和视频业务发展的需求。同时,业务承载网应提高安全性并能够提供服务质量保证。因此,3G移动通信系统应同时拥有...
  •  第9章“Android的多媒体系统”,介绍Android的多媒体系统的核心部分,包括Android中多媒体系统的业务、结构、多媒体系统的核心框架、OpenCore系统结构和使用等内容。  第10章“Android的电话部分”,介绍Android...
  •  本书是一本讲述数据库系统原理的经典教材,重点强调了数据库的设计与实现。全书分为4部分。第一部分讲述事务处理的概念、并发控制技术和数据库恢复技术;第二部分讲述面向数据库的概念、语言、设计及对象-关系和...
  • Sharetronix是一个可以发视频,音乐,文件和文字的多媒体微博客平台。最新版本1.3.0提供包括中文在内19种语言支持,有一般微博具备的所有功能。简单好用,我个人非常喜欢。下面就向大家介绍如何安装这款微博客系统...
  • 多媒体播放都需要媒体流一个稳定的速率,当达不到这个播放需求时,win7系统就会变卡,遇到这样的问题该怎么解决呢?下面小编告诉win7系统开启MMCSS服务出现卡机现象的关闭方法。 具体方法如下: 1、首先点击...
  • 校园网的硬件平台主要指将校内的各个部门的计算机和相关的信息处理设备以某种方式物理地连接起来,形成一个信息交流与共享的网络。根据目前网络技术的发展状况,结合学校包含大量多媒体信息的特点,选用具有足够...
  • 衡量网络上数据传输速率的单位每秒传送多少二进制位,记为( ) A.BPS B.OSI C.MODEM D.TCP/IP 4.局域网常用的基本拓扑结构有( )环型和星型 A.层次型 B.总线型 C.交换型 D.分组型 5.目前,局域网的传输介质...
  •  本书是一本讲述数据库系统原理的教材,重点强调数据库建模与设计的基础、数据库管理系统提供的语言和工具以及系统实现技术。全书共分4部分,第一部分介绍最基本的概念、术语及建模原则,第二部分描述了关系数据...
  • 1 综述 移动数据交换和存储,近年来IT行业的热点。... 与此同时,我们还注意到这样一个问题:移动数据交换业,伴随着通用串行总线(Universal Serial Bus,简称为USB)的成熟而兴起的。USB既是数
  • 在弹出的“高级音频属性”对话框你可以为自己的多媒体系统设定最接近你 的硬件配置的扬声器模式。  (9)在“高级音频属性”对话框中选择“性能”选项卡,这里提供了对音 频播放及其硬件加速和采样率转换质量的...
  • 3GPP 首5G标准

    千次阅读 2017-03-14 16:53:20
    近日,3GPP SA WG1召开会议...2、5G应是一个可扩展的、可定制的网络,可以根据需求为多类服务及垂直市场定制(例如:网络分层、网络功能虚拟化); 3、5G应面向从低数据物联网业务及高比特率多媒体业务提供高的资源利用
  • 10年,4单位

    千次阅读 2013-03-17 01:59:37
    一个单位: ... 入职后的第一件事情,负责《劳动力市场信息网络》(一个关于劳动力招聘和求职的系统)的升级扩容,这主要包括两个部分:一将《劳动力市场信息网络》扩展到下属的38个乡镇的办事

空空如也

空空如也

1 2 3 4 5 6
收藏数 104
精华内容 41
关键字:

多媒体系统应是一个