精华内容
下载资源
问答
  • 第4章 Java多媒体的支持Java语言的内置类库多媒体技术的支持能力相当强,尤其是文本、图形、图像、声音等媒体处理与展示均提供了极其方便而又丰富的接口。更令人兴奋不已的是,综合运用这些媒体所编制出来的...

    第4章 Java对多媒体的支持

    Java语言的内置类库对多媒体技术的支持能力相当强,尤其是对文本、图形、图像、声音等媒体的处理与展示均提供了极其方便而又丰富的接口。更

    令人兴奋不已的是,综合运用这些媒体所编制出来的一个个Java小应用程序(Applet),使向来冷冰冰的静态的Web主页(Homepage)上居然

    展现出一番热热闹闹的动态的新景观,这便是著名的Java动画。在这一章里,我们就将进入到Java多姿多彩的多媒体世界

    如何利用Java的绘图方法来绘制各式各样图形以及显示各种文本字体,并配以所喜爱颜色,可以说是Java多媒体技术中的一项基本功,也是这一节将要介绍的主要内容。

    Java语言的类库中提供了丰富的绘图方法(method),其中大部分对图形、文本、图像(image)的操作方法都定义在Graphics

    类中。我们已经知道,Graphics类又是java.awt程序包的一部分,因此,每当我们要进行图形、文本、图像的处理时,不要忘了在Java源文件

    的头部先写上:

    import java.awt.Graphics;

    在这里要特别指出的是,当我们想要在屏幕上绘制图形、文本、图像时,并不需要直接使用new来产生一个Graphics类的对象实例,而在java.awt.Applet类的paint(

    )方法(见上一章)中,我们已经得到了一个Graphics对象的引用,这是系统直接将生成好的Graphics对象通过参数形式传递给paint( )方法。因此,我们只要在这个对象上进行图形、文本及图像的绘制操作,就可以在屏幕上看到所显示的结果。

    1. 图形坐标系统

    为了将某一图形在屏幕上绘制出来,我们首先要碰到的问题也许就是“画在哪个位置”,为了解决这个问题就必须有一个精确的图形坐标系统来将该图形定位。

    与大多数其它计算机图形系统所采用的二维坐标系统一样,Java的坐标原点(0,0)在屏幕的左上角,水平向右为X轴的正方向,竖直向下为Y轴

    的正方向,每个坐标点的值表示屏幕上的一个象素点的位置,因此,所有坐标点的值都取整数。图4-1表示用此图形坐标系统在屏幕上绘制一个矩形。

    2. 画线

    在Java的Graphics类中提供画线功能的是drawLine( )方法,其调用格式如下:

    drawLine(int x1,int y1,int x2,int y2)

    该方法需要设置四个参数,其中x1,y1表示线段的一个坐标点,x2,y2表示线段的另一个坐标点。如下面这段程序画出两条线段,其显示结果如图4-2所示。

    500462_1.gif

    图4-1 图形坐标系统

    1: import java.awt.Graphics;

    2: public class Lines extends java.applet.Applet{

    3: public void paint(Graphics g){

    4: g.drawLine(30,30,70,70);

    5: g.drawLine(60,50,60,50);

    6: }

    7: }

    500462_2.gif

    图4-2 一条线段与一个点

    由于Graphics类不专门提供画点的方法,所以程序中第5行将线段的两个点的坐标均设为(60,50),因而就相当于在此处画了一个点。

    3. 矩形

    Graphics类中提供了三种类型的矩形,它们分别是普通矩形、圆角矩形和立体矩形。而每一种矩形都提供两种不同风格的方法,一种是仅画出矩形的边框;另一种是不仅画出边框,并且还用相同的颜色将整个矩形区域填满。

    (1)普通矩形

    画普通矩形需调用drawRect( )或fillRect( )方法,它们的调用格式如下:

    drawRect(int x, int y, int width, int height) //边框型风格

    fillRect(int x, int y, int width, int height) //填充型风格

    其中头两个参数分别表示矩形左上角的x坐标和y坐标,后两个参数分别表示矩形的宽度和高度。如下面的paint( )方法画出两个矩形,其显示结果如图4-2所示。

    public void paint(Graphics g){

    g.drawRect(40,20,60,40);

    g.fillRect(120,20,60,40);

    }

    500462_3.gif

    图4-3 普通矩形的例子

    (2)圆角矩形

    圆角矩形,也就是矩形的四个顶角呈圆弧状,每个圆弧其实是由四分之一的椭圆弧所构成。画圆角矩形的两个方法的调用格式如下:

    drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)

    fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)

    我们可以看出,它们除了具有和普通矩形含义相同的前四个参数外,还多了两个用来述圆角性质的参数。其中arcWidth代表了圆角弧的横向直

    径;arcHeight代表了圆角弧的纵向直径。例如图4-4中左边一个圆角矩形所设的圆角参数为(arcWidth=80,

    arcHeight=30),其效果就相当于该圆角弧存在于一个长40宽15的小矩形中;而右边一个圆角矩形的圆角参数为(arcWidth=100,

    arcHeight=60),并且和整个圆角矩形的width和height参数值相等,因而该圆角矩形实际变成了一个椭圆。

    500462_4.gif

    图4-4 圆角矩形的参数设置

    下面的paint( )方法画出三个圆角矩形,显示效果如图4-5所示。我们不难发现,随着arcWidth和arcHeight值的增大,矩形的圆角就越圆。

    public void paint(Graphics g){

    g.drawRoundRect(20,20,80,60,20,20);

    g.fillRoundRect(120,20,80,60,40,30);

    g.drawRoundRect(220,20,80,60,60,40);}

    500462_5.gif

    图4-5 圆角矩形的例子

    (3)立体矩形

    立体矩形也可以说是三维矩形。其实,Java中的立体矩形并非真正的三维图形,而仅仅是在矩形的边框上增加一点阴影,使矩形看上去相对表平面好象有凸出或凹下的效果,其调用方法的格式如下:

    draw3DRect(int x, int y, int width, int height, boolean raised)

    fill3DRect(int x, int y, int width, int height, boolean raised)

    这两个方法中的前四个参数与drawRect( )方法中所用的参数含义是一样的,第五个参raised便是定义该立体矩形是具有凸出(值为true)还是凹下(值为false)的效果。例如,下面的paint(

    )方法中,分别画了一个凸出的和一个凹下的矩形。其显示效果如图4-6所示。确实,由于Java立体矩形中的阴影实在太薄,立体效果当然也就不太明显,图4-6右边是一个放大了的凹角形状。

    public void paint(Graphics g){

    g.draw3DRect(20,20,80,60,true);

    g.fill3DRect(120,20,80,60,false);

    }

    500462_6.gif

    图4-6 立体矩形的例子

    4.多边形

    多边形的画法通常是给出一组坐标点,再用直线段将这些点依次连接起来。Graphics类中也提供两个方法来画多边形,一个是边框型drawPolygon(

    )方法,另一个是填充型fillPolygon( )方法,并且每一种方法都有两种不同的参数类型。第一种参数类型的调用格式为:

    drawPolygon(int xPoints[],int yPoints[],int nPoints)

    fillPolygon(int xPoints[],int yPoints[],int nPoints)

    其中xPoints参数是一个整数数组,用以存放多边形坐标点的X坐标值,yPoints参数放相应的一组Y坐标值,nPoints则表示共有几个坐标点。如下面的paint(

    )方法分别画了一个边框型和一个填充型的多边形,其显示效果如图4-7所示。

    public void paint(Graphics g){

    int Poly1_x[]={30,63,115,72,67};

    int Poly1_y[]={40,20,95,74,106};

    int Poly1_pts=Poly1_x.length;

    int Poly2_x[]={180,213,265,222,217};

    int Poly2_y[]={40,20,95,74,106};

    int Poly2_pts=Poly2_x.length;

    g.drawPolygon(Poly1_x,Poly1_y, Poly1_pts);

    g.fillPolygon(Poly2_x,Poly2_y, Poly2_pts);

    }

    500462_7.gif

    图4-7 多边形的例子

    可以看出,边框型多边形并不自动关闭多边形的最后一条边,而仅是一段开放的折线。所以,若想画封闭的边框型多边形,不要忘了在数组的尾部再添上一个起始点的坐标。上述两个画多边形方法的第二种参数传递形式为:

    drawPolygon(Polygon p)

    fillPolygon(Polygon p)

    其中Polygon是定义在java.awt中的一个类,它的构造方法也有两种不同的参数传递形式,一种与drawPolygon( )方法的第一种调用格式一样:

    Polygon(int xPoints[],int yPoints[],int nPoints)

    另一种调用格式则是创建一个空的多边形(无参数):

    Polygon( )

    那为什么要另外创建Polygon对象?生成一个空的Polygon对象又有何用呢?原来,olygon类中提供了一系列特有的方法,可以方便的进行与多边形相关的操作,象其中的ddPoint(

    )方法可将多边形的坐标点动态地增加到Polygon对象中。如下面列出的paint( )方法所执行的结果与图4-7所示的结果是一样的。

    public void paint(Graphics g){

    int Poly1_x[]={30,63,115,72,67};

    int Poly1_y[]={40,20,95,74,106};

    int Poly1_pts=Poly1_x.length;

    Polygon poly1= new Polygon(Poly1_x,Poly1_y, Poly1_pts);

    Polygon poly2= new Polygon();

    poly2.addPoint(180,40);

    poly2.addPoint(213,20);

    poly2.addPoint(265,95);

    poly2.addPoint(222,74);

    poly2.addPoint(217,106);

    g.drawPolygon(poly1);

    g.fillPolygon(poly2);

    }

    5. 椭圆

    在Java中绘制椭圆的方法是给出该椭圆的外接矩形作为参数,其调用格式与画普通矩形的方法相似:

    drawOval(int x, int y, int width, int height) //边框型风格

    illOval(int x, int y, int width, int height) //填充型风格

    这里要特别注意:x和y不是椭圆的圆心坐标,而是该椭圆外接矩形的左上角。因此,椭圆时,把它先看作是一个矩形将有助于在坐标系统中定位。另

    外,Graphics类不专门提供画圆的方法,而只需将width与height参数置成相等就可以了。例如,下面的paint(

    )方法,画出一个圆和一个用颜色填充的椭圆,其显示效果如图4-8所示。

    public void paint(Graphics g){

    g.drawOval(30,20,60,60);

    g.fillOval(130,20,80,60);

    }

    500462_8.gif

    图4-8 椭圆的例子

    6. 画弧

    弧是椭圆的一部分,因而画弧的方法就相当于先画一个椭圆,而后取该椭圆中所需要一部分。它们的调用格式如下:

    drawArc(int x, int y, int width, int height,int startAngle, int arcAngle)

    //边框型风格

    fillArc(int x, int y, int width, int height,int startAngle, int arcAngle)

    //填充型风格

    其中前四个参数的含义与画椭圆一样,因此也必须用矩形的观点来确定弧在坐标系统的位置。后两个参数就是用来定义椭圆的一部分:

    startAngle参数表示该弧从什么角度开始,arcAngle参数表示从startAngle开始转了多少度。如图4-9的弧度坐标系所示,水平向

    右表示0度,逆时钟方向为正角度值,顺时钟方向为负角度值。

    500462_9.gif

    图4-9 弧度坐标系

    如果startAngle和arcAngle中有任一值大于360度的话,都会被转换为0到360度之间的值。因此若要画满整个椭圆,arcAngle需设为360的整数倍,若设为370度则相当于只画了10度。另外fillArc(

    )方法的效果并不是填充弧的两个端点直接连线所围的区域,而是填充弧的两端点与圆心连线所围的扇形区域,象一个饼图。下面的paint( )方法画了图4-9中的几段弧,其显示效果如图4-10所示。

    public void paint(Graphics g){

    g.drawArc(10,20,100,60,35,65);

    g.drawArc(110,20,100,60,35,-140);

    g.fillArc(210,20,100,60,35,65);

    g.fillArc(310,20,100,60,35,-140);

    }

    500462_10.gif

    图4-10 弧的例子

    7.复制与清除图形

    当我们需要在屏幕上重复绘制一些相同的图形时,也可采用Graphics类中的copyArea( )法,它能将屏幕上某一矩形区域里的内容复制到屏幕的另一区域。其调用格式如下:

    copyArea(int x, int y, int width, int height, int dx, int dy)

    前四个参数我们应该是相当熟悉了,它定义了要被复制的屏幕的矩形区域。最后两个数则表示新区域与原始屏幕区域的偏移距离:若dx,dy为正值,则表示新区域相对于原域的右方及下方所偏移的像素值;反之,它们取负值则分别表示相对左方及上方的偏移量。

    若要清除屏幕的某一矩形区域所画的内容,就要选用clearRect( )方法,它用当前的背景颜色来填充整个矩形区域。其调用格式为:clearRect(int

    x, int y, int width, int height)可以看出这四个参数定义了所要清除的矩形区域。例如,想要清除整个applet区域,可先调用Applet类的size(

    )方法得到整个applet的宽度和高度(该方法没有参数,返回值是一个Dimension对象,该对象具有width和height实例变量),再调用clearRect(

    )方法就可以了:

    g.clearRect(0, 0, size( ).width, size( ).height);

    4.1.2 文本与字体

    Graphics类也提供了在屏幕上显示文本的方法,但若要使文本的显示更具特色,让它满足某种字体、某种风格及尺寸大小的要求,就需要用字体类Font来定义。

    1. 设置文本信息

    当我们想要在屏幕上输出文本信息时,首先要确定的就是采用何种字体,例如中文的“宋体”、“楷体”,或是英文的“TimesRoman”体、

    “Courier”体等等,接着再决定该字体输出时采用哪种风格,是斜体型还是粗体型等等,最后还要确定该字体的大小尺寸。所有这些都由Font类来定

    义,我们不难猜出其构造方法的调用格式:

    Font(String name, int style, int size)

    不错,它的三个参数就是我们先前所说的字体名、字体风格和尺寸大小。并且Font类中已定义了类变量来表示字体的style值,如

    Font.BOLD(表示粗体)、Font.ITALIC(表示斜体)、Font.PLAIN(表示普通体)。由于它们被定义为整数常量,因此可以进行相

    加运算来生成复合style,例如想让style即是粗体又是斜体,可以这样写:

    Font fn = new Font("TimesRoman", Font.BOLD+Font.ITALIC, 28);

    虽然我们定义了所需的字体,但其显示结果有时也并非如愿。因为运行该applet的客户端系统有可能并未安装该字体,这时Java就会以缺省字

    体来替代它。因此,不妨先查看一下客户端系统目前究竟支持哪些字体,这就要用到java.awt.Toolkit类中的getFontlist(

    )方法,它返回系统目前可用的字体列表,然后就可决定到底选用哪种字体。例如:

    Toolkit systk = Toolkit.getDefaultToolkit( );

    tring fonts = systk.getFontList( );

    2. 显示文本

    创建了Font对象以后,我们就可以利用Graphics类中提供的drawString( )、drawChars( )等方法来显示字符串与字符。当然,首先还要用setFont(

    )方法,将所创建的Font对象设为当前所用的字体。下面就是Graphics类中这三个方法的调用格式:

    setFont(Font font);

    drawString(String str, int x, int y)

    drawChars(char data[], int offset, int length, int x, int y)

    其中setFont(

    )方法的参数就是一个创建好的Font对象,表明系统当前选用哪个Font对象所定义的字体信息。drawString(

    )方法中的str即是要显示的字符串,x,y指明字符串显示的起始位置坐标,具体的说,x表示第一个字符的左边界,y表示整个字符串的基线

    (baseline,见图4-12)位置坐标。因此,这里的坐标并不是通常意义上的矩形区域的左上角。drawChars(

    )方法则是用来显示多个字符的,也就是从给定的字符数组中抽取连续的一部分显示在屏幕上。其中data参数就是给定的原始字符数组,offset表示从第

    几个字符位置开始显示,length表示共显示几个字符,x与y参数的含义与drawString( )方法一样,代表显示在屏幕上的起始位置。

    如下面的程序显示了一些不同的文本字体,其显示结果如图4-11所示。

    import java.awt.Graphics;

    import java.awt.Font;

    public class Fonts extends java.applet.Applet{

    public void paint(Graphics g){

    Font ftp20 = new Font("TimesRoman",Font.PLAIN,20);

    Font fai15 = new Font("Arial",Font.ITALIC,15);

    Font fcb24 = new Font("Courier",Font.BOLD,24);

    Font fsib30 = new Font("宋体",Font.ITALIC+Font.BOLD,30);

    g.setFont(ftp20);

    g.drawString("Font name TimesRoman , style plain , size 20",10,20);

    g.setFont(fai15);

    g.drawString("Font name Arial , style italic , size 15",10,50);

    g.setFont(fcb24);

    g.drawString("Font name Courier , style bold , size 24",10,80);

    g.setFont(fsib30);

    g.drawString("字体名 宋体,风格 斜体+粗体,尺寸 30",10,120);

    }

    }

    500462_11.gif

    图4-11 各种字体的例子

    3. 获取字体信息

    (1)获取基本信息

    如果不清楚系统当前所用的字体信息,可以先调用Graphics类中的getFont( )方法,该方法无参数,它返回系统当前所用的Font对象,然后就可以调用Font类中提供的几个方法来获取该字体的基本信息。表4-1列出了Font类中的这些方法。

    表4-1 Font类提供的一些主要方法

    (2)获取详细信息

    有时候,我们为了在屏幕上更精确地定位文本,还需要了解所选字体的更详细的信息,例如整个字符串到底有多高,有多宽,两行字符串的间隙有多少等

    等。这时,我们需要用到一个新的类FontMetrics来提供这一信息。我们可以调用Graphics类中的getFontMetrics(

    )方法来获取关于当前字体的FontMetrics对象(该方法也无参数)。然后就可以利用表4-2所示的FontMetrics类中所提供的方法来获取

    更详细的字体信息。图4-12中给出了字体中关于Ascent、Descent、Leading等概念的示意图。

    4.1.3 颜色的设置

    现在,让我们改变一下总是在灰色背景上用黑色绘图以及显示文本的习惯,而给我们的applet增添一些五彩缤纷的色彩。与设置字体信息相似,要

    设置新的颜色,必须先创建Color对象,然后再调用Graphics类中设置颜色的方法来将生成的Color对象设为当前所用的绘图颜色。

    500462_12.gif

    图4-12字体

    1. 创建Color类

    Java中每一种颜色都看成是由红(R)、绿(G)、蓝(B)三原色组合而成的。因此Color类的构造方法采用如下格式:

    Color(int r, int g, int b)

    其中每个参数的值都在0到255之间,数值越大就表明这种颜色的成份越重。例如(0,0,0)代表黑色,(255,0,0)代表红色。当然最终

    在屏幕上是否能显示所定义的颜色还取决于客户端系统的调色板所支持的颜色种类的多少。若客户端系统的调色板并不支持当前所定义的颜色值,就会在调色板中挑

    选最接近的颜色来代替。

    Color类中还定义了一些标准颜色的Color对象存储在类变量中,使的这些标准颜色的引用显得更为方便。这些类变量如表4-3所示。

    2. 设置当前颜色

    为了能使用刚才生成好的Color对象来显示文本及绘制图形,还需调用Graphics类中的setColor( )方法把这个对象设置为系统当前所用的绘画颜色,其调用格式为:

    setColor(Color c)

    例如,想要用蓝色来显示文本,最简单的办法是直接引用标准色的类变量:

    setColor(Color.blue);

    另外,Java还提供了设置整个applet的背景和前景的方法,它们分别是setBackground(

    )方法和setForeground(

    )方法,它们都被定义在java.awt.Component类中,因此该方法能被其子类(包括Applet类及Applet类的子类)自动继承,它们的

    调用格式与setColor( )方法一样:

    setBackground(Color c)

    setForeground(Color c)

    其中setForeground( )方法将影响到applet中所有已经用其它任何颜色所绘制的图形显示的文本,把它们一下子都变为该方法所定义的前景颜色,而不需用该颜色重新一一绘制。有“set”必有相应的“get”,Java中还提供了getColor(

    )方法(Graphics类中)、getBackground( )方法和getForeground( )方法(Component类中)来分别获取当前的绘图颜色、applet背景及前景颜色的对象。

    下面,我们写一段程序来显示一排用随机定义的颜色所填充的小方块,它们的显示效果如图4-13所示。

    import java.awt.Graphics;

    mport java.awt.Color;

    public class Colors extends java.applet.Applet{

    public void paint(Graphics g){

    int red,green,blue;

    for (inti=10;i<400;i+=40){

    red = (int)Math.floor(Math.random()*256);

    green = (int)Math.floor(Math.random()*256);

    blue = (int)Math.floor(Math.random()*256);

    g.setColor(new Color(red,green,blue));

    g.fillRect(i,20,30,30);

    }

    }

    }

    500462_13.gif

    图4-13 使用绘图颜色的例子。

    展开全文
  • freeswitch对媒体的处理有三种方式: 媒体代理方式 a.默认方式 媒体通过freeswitch, RTP被freeswtich转发,freeswitch控制编码协商并在协商不一致时提供语音编码转换能力, 支持录音,二...

    freeswitch对媒体的处理有三种方式:

    媒体代理方式    
    a.默认方式

    媒体通过freeswitch,

    RTP被freeswtich转发,freeswitch控制编码的协商并在协商不一致时提供语音编码转换能力,
    支持录音,二次拨号等。

    更适合呼叫中心等富功能应用,但性能相比其他两个也是最差的
    b.代理模式(Proxy Media)

    媒体通过freeswitch转发,但是不处理媒体,

    RTP通过freewtich转发(只改动sdp c= ip)
    freeswtich不控制 sdp参数,只是转发。
    通话的终端必须有一致的语音或者视频编码,因为freeswitch此时不支持转码(适合视频编码)
    不支持录音, 二次拨号等功能

    更适合处理nat问题,
    可以考虑用这种模式做一个session border controlor,也适合于外部MCU配合做为视频会议,性能也明显好于a
    c.旁路模式(Bypass Media)

    不转发也不处理媒体,FS不会对SDP控制,音视频也不走FS。

    此模式下freeswitch更像是一个信令proxy,媒体不会通过freeswitch,sdp消息体不做修改,没有录音,二次拨号等功能

    更像是一个信令代理,性能最高,但提供的功能有限


    模式配置方式:

    Proxy Media Bypass Media

    conf\sip_profiles\internal.xml 在 sip_profile 中

    设置proxy media 模式,其他模式注释掉,

    <param name="inbound-proxy-media" value="true"/>

     

     还要在Dailplan中 ,在打电话bridge 之前

    <param name="set" value="proxy_media=true"/>

    conf\sip_profiles\internal.xml中在sip_profile 中

    设置bypass-media模式,其他模式注释掉,

    <param name="inbound-bypass-media" value="true"/>

     

    还要在Dailplan中的default.xml的local-extension中

    设置bypass-media=true

    参考https://freeswitch.org/confluence/display/FREESWITCH/Proxy+Media 参考https://freeswitch.org/confluence/display/FREESWITCH/Bypass+Media+Overview

     

     

     

    展开全文
  • 背景业务需要在原有RGW服务基础上加上多媒体类资源的处理,比如图片resize、视频转码等。参考过七牛云等多个厂家设计,发现对方多媒体类处理都是在URL里面加上query string来实现,比如:七牛云:裁剪正中...

    背景

    业务需要在原有RGW的服务基础上加上对多媒体类资源的处理,比如图片resize、视频转码等。参考过七牛云等多个厂家的设计,发现对方的多媒体类处理都是在URL里面加上query string来实现,比如:七牛云:裁剪正中部分,等比缩小生成200x200缩略图,对应的URL如下:http://odum9helk.qnssl.com/resource/gogopher.jpg?imageView2/1/w/200/h/200

    图片操作的配置参数都是通过增加Query String=imageView2/1/w/200/h/200

    a95f080b231450ef5c07f4f89913ace4.png

    问题

    • 用户体验:这种设计咋看起来很直观,但是一旦QueryString字段多了或者整个字符串过长以后,肉眼看起来非常累,特别是排错调参的时候,你会觉得自己像小学生数着手指头算数。

    • 代码开发:引入这种设计,在RGW中整个请求路由层写起来会要一堆正则,很容易破坏现有的代码结构。(个人愚见)

    • 版本维护:随着各种新增加功能模块的不断引入,RGW的版本维护会变得比较痛苦。同时要知道原生RGW现在没有平滑升级方案,必须重启。(当然有其他办法解决)

    思路:

    • 通过复用S3请求中的用户自定义元数据字段(header中x-amz-meta-开头)来加载多媒体处理任务信息,从而绕开需要单独设计Restful API及完成相关签名认证部分的代码设计,同时做到一个数据上传请求同时包含了多媒体处理任务的下发。(异步场景)

    • 复用原生S3 Response Header中的x-amz-request-id来实现任务ID的生成(全局唯一),实现基于ID的任务的跟踪与管理。(异步场景)

    • 异步任务完成以后,转码后的文件会按指定路径存放,如图中的"tmp/new-img.jpg"。

    • 在整个服务入口网关处加上对业务类型的逻辑判断,同步类型的请求在本地操作(减少后端RGW压力,同时减少IO路径),异步请求发后台服务器。这样做还有一个好处是方便后期扩展,同步服务能力不足就新增入口节点,异步处理能力不足就新增后台服务器。

    b05baf7910ad2ff62abb1a49fd1b2354.png

    需求描述

    • 客户端在尽量少改动现有接口API的情况下完成图片、视频等多种类型多媒体文件的转码一类处理。

    • 满足业务数据上传和转码操作在同一次Request请求中提交,减少请求次数。

    • 针对后台业务的处理能力及时长不同,需要同时支持同步(小图片)及异步(大视频)两种类型的任务下发操作。

    构架介绍

    23583581da400fbc1b2c34d8f328aee9.png

    • Client:请求发起端,提交数据及转码相关任务到服务端。

    • Openresty:服务网关,接受客户端的请求,根据请求的类型(同步or异步)来进行不同的处理。其中http_image_filter_module用于演示图片resize的同步操作,log_by_lua_file用于向后端发送异步任务到kafka。

    • RGW:提供基础S3对象存储服务。

    • Kafka:负责日志消息存储及转发。

    • Job Server:多媒体处理服务,负责从kafka中取任务,之后将任务状态更新到DB中。

    • DB:记录任务状态数据,供前台服务查询。

    任务操作流程-异步方式

    • 任务提交阶段

    ea668509a91a5178b22bf4910568bad2.png

    • Step1. 客户端使用普通的Put object请求,只需要加上自定义的metadata字段即可完成数据及任务的提交。

    • Step2. Openresty使用proxy模式将请求转发到RGW,由RGW完成后台数据存储处理。

    • Step3. Openresty在RGW完成数据存储以后,调用log_by_lua_file将对应请求的用户自定义metadata及x-amz-request-id转发到后台kafka。

    • Setp4. Job Server从kafka中拿到新增的任务及相应ID。

    • Setp5. Job Server从RGW下载对应的Object数据到本地,执行相应的多媒体处理操作,并将结果更新到DB

    • 任务查询阶段

    Step1. 客户端使用之前记录下来的x-amz-request-id向服务器发起任务查询请求(这里需要单独做一个服务查询接口)。

    Step2. Openresty接收到客户端查询请求以后从DB中查询相应的任务状态数据并返回给客户端。

    69812242a0898c15f1b4f54b146638d2.png

    前端逻辑
    • Step1. Job Server将处理完成的任务数据,按照用户指定的对象名称写回RGW。

    • Step2. Job Server完成RGW的数据写入以后,更新相应的任务状态到DB。

    后端逻辑

    • Step1. 客户端使用之前记录下来的x-amz-request-id向服务器发起任务查询请求(这里需要单独做一个服务查询接口)。

    • Step2. Openresty接收到客户端查询请求以后从DB中查询相应的任务状态数据并返回给客户端。

    • 获取任务结果

    08eb1463e10747c37786f757b4d8c0db.png

    • Step1. 客户端在上一步骤中查询到对应的任务已经完成的情况下,直接通过Get Object即可取回转码完成后的数据。

    任务操作流程-同步方式

    • 准备阶段

    • Step1. 客户端需要提前上传好Object,之后设置对应的ACL=public-read。(设置public-read是为了演示方便,如果要鉴权则需要修改nginx的module逻辑)

      82bc6b399ece219223c76496c274612e.png

    • 使用阶段

      6927b9763fa18aa22cf4f2adb6d4d9dd.png

    • Step1. 客户端在需要获取的Object的URL后面加上对应的height和width参数等。

    • Step2. Openresty拿到后台RGW的数据以后,将数据加载到内存之后经过http_image_filter_module模块处理,完成内存中临时数据的生成,并将内存中的临时数据返回给用户。

    Demo演示

    Demo实现以下功能:同步请求:借助已有的http_image_filter_module模块实现已经存储在RGW的图片(Acl=Public-read)的在线resize操作。异步请求:过滤Put Object成功的请求,并将其中的用户自定义metadata及x-amz-request-id发送到后端kafka。(kafka之后的Job Server相关需要自己实现,这里省略)

    1. 环境配置说明

    使用的是openresty/1.15.8.2,需要编译的时候需要加上"--with-http_image_filter_module"来开启http_image_filter_module模块。

    /usr/local/openresty/nginx/conf/nginx.conf 配置如下

        server {
            listen       80;
            server_name  test.s3.c.local *.s3.jrss.c.local;
            client_max_body_size 20m;
        location ~ "^(/.*/.*\.(jpg|png|jpeg))!c(\d+)-(\d+)$" {
            set $w $3;
            set $h $4;
            rewrite ^(/.*/.*\.(jpg|png|jpeg))!c(\d+)-(\d+)$ $1 break;
            image_filter crop $w $h; #裁剪图片
            image_filter_buffer 20M;
            proxy_pass http://172.25.60.215:9000;
        }
        location ~ "^(/.*/.*\.(jpg|png|jpeg))!r(\d+)-(\d+)$" {
            set $w $3;
            set $h $4;
            rewrite ^(/.*/.*\.(jpg|png|jpeg))!r(\d+)-(\d+)$ $1 break;
            image_filter resize $w $h; #缩略图
            image_filter_buffer 20M;
            proxy_pass http://172.25.60.215:9000;
        }
        location ~ "^(/.*/.*\.(jpg|png|jpeg))!d(\d+)$" {
            set $d $3;
            rewrite ^(/.*/.*\.(jpg|png|jpeg))!d(\d+)$ $1 break;
            image_filter rotate $d; #翻转图片
            image_filter_buffer 20M;
            proxy_pass http://172.25.60.215:9000;
        }
            location / {
                proxy_http_version 1.1;
                proxy_set_header Connection "";
                proxy_set_header HTTP_PROXY "";
                proxy_set_header Proxy "";
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_pass http://172.25.60.215:9000;
            log_by_lua_file /usr/local/openresty/nginx/conf/kafka.lua;
            }

    其中/usr/local/openresty/nginx/conf/kafka.lua用来将用户自定义的metadata及任务ID转发到kafka。

    local cjson = require "cjson"
    local producer = require "resty.kafka.producer"
    local broker_list = {
        { host = "localhost", port = 9092 },
    }
    function send_job_to_kafka()
        local req_header = ""
        local udf_meta_reg = "^x-amz-meta-"
        local headers_ = ngx.req.get_headers()
        for k, v in pairs(headers_) do
            local meta_check, meta_err = ngx.re.match(k,udf_meta_reg)
            if meta_check then
             req_header = req_header .. k.."="..v.." "
            end
        end
        local log_json = {}
        log_json["uri"]=ngx.var.uri
        log_json["host"]=ngx.var.host
        log_json["remote_addr"] = ngx.var.remote_addr
        log_json["status"] = ngx.var.status
        log_json["request_method"] = ngx.var.request_method
        log_json["req_header"] = req_header
        log_json["http_x_amz_request_id"] = ngx.var.upstream_http_x_amz_request_id
        local message = cjson.encode(log_json);
        return message
    end

    local is_args = ngx.var.is_args
    local request_method = ngx.var.request_method
    local status_code = ngx.var.status

    -- 过滤Put Object成功的请求,记录相应的metadata及请求ID,并转发到kafka
    if request_method == "PUT" and status_code == "200" and is_args == "" then
        local bp = producer:new(broker_list, { producer_type = "async" })
        local ok, err = bp:send("test""key", send_job_to_kafka())
        if not ok then
            ngx.log(ngx.ERR, "kafka send err:", err)
            return
        end
        ngx.log(ngx.ERR, "kafka send sucessful:", ok)
    end

    2. 测试用例

    import boto3
    from botocore.client import Config

    aws_access_key_id = '' #AK
    aws_secret_access_key = '' #SK
    endpoint = 'http://test.s3.c.local'
    bucket = 'demo' #bucket名称
    objcet = '1.jpg' #对象名称
    file_path = '/Desktop/op.jpg' #本地图片文件路径
    # aws4
    s3 = boto3.client('s3', region_name='cn-hb-pri1',
                      use_ssl=False,
                      endpoint_url=endpoint,
                      aws_access_key_id=aws_access_key_id,
                      aws_secret_access_key=aws_secret_access_key,
                      config=Config(signature_version='s3v4',
                                    s3={'addressing_style''path'}))


    with open(file_path, 'r'as f:
        response = s3.put_object(
            ACL='public-read',
            Body=f,
            Bucket=bucket,
            Key=objcet,
            ContentType='image/jpeg',
            Metadata={
                'save-path''tmp/new-img.jpg',
                'width''1920',
                'heigh''1200',
            },
        )

    print "{}/{}/{}".format(endpoint,bucket,objcet) #默认分辨率图片地址
    print "{}/{}/{}!r50-50".format(endpoint,bucket,objcet) #生成50x50分辨率缩略图
    print "{}/{}/{}!r100-100".format(endpoint,bucket,objcet) #裁剪图片
    print "{}/{}/{}!d90".format(endpoint,bucket,objcet) #图片旋转90度

    抓包可以看到整个请求头部的内容如下:

    19682d55a6da07d2e63474b3e9211acc.png

    上面脚本跑完会生成2个URL,使用对应的URL就可以对比看到对应的图片resize等效果。

    通过运行下面的KafkaConsumer脚本,可以看到异步任务对应的kafka消息数据也发送成功

    from kafka import KafkaConsumer

    # To consume latest messages and auto-commit offsets
    consumer = KafkaConsumer('test',
                             group_id='my-group',
                             bootstrap_servers=['localhost:9092'])
    for message in consumer:
        # message value and key are raw bytes -- decode if necessary!
        # e.g., for unicode: `message.value.decode('utf-8')`
        print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
                                              message.offset, message.key,
                                              message.value))

    结果截图

    ac34ecf22740ee919910f91d788d0f21.png


    Ceph中国社区

    是国内唯一官方正式授权的社区,

    为广大Ceph爱好者提供交流平台!

    ↓↓↓43854a2cfd6eda4164fb3dd54e9d5641.png

    开源-创新-自强

    官方网站:www.ceph.org.cn

    合作邮箱:devin@ceph.org.cn

    投稿地址:tougao@ceph.org.cn

    长期招募热爱翻译人员,

    参与社区翻译外文资料工作。

    664fc3b5565d7ad3303a9e783f5ab7c2.png
    展开全文
  • 本文将就Oracle多媒体信息进行处理的方式,工具,特色以及一些基本原理做一个综述。 但是我们不能不遗憾的看到,现有数据库一些多媒体数据所特有的操作能力还是很有限的。针对多媒体数据库的性能调优现在也遇到...
  • 阿里云媒体处理

    2018-05-05 17:23:07
    一、介绍:媒体处理(ApsaraVideo for Media Processing,原MTS)是一种多媒体数据处理服务。它以经济、弹性和高可扩展音视频转换方法,将多媒体数据转码成适合在全平台播放格式。并基于海量数据深度学习,音...

    一、介绍:

    媒体处理(ApsaraVideo for Media Processing,原MTS)是一种多媒体数据处理服务。它以经济、弹性和高可扩展的音视频转换方法,将多媒体数据转码成适合在全平台播放的格式。并基于海量数据深度学习,对音视频的内容、文字、语音、场景多模态分析,实现智能审核、内容理解、智能编辑。

    相关API连接:https://help.aliyun.com/product/29194.html?spm=a2c4g.11186623.6.168.YjvZGq

    二、使用

    这里主要使用阿里云的视频转码。转码可以使用自定义模板或者阿里云默认模板,本人使用阿里云默认模板

    视频转码步骤:1.提交视频转码任务 2.获取视频转码任务结果。

    在我们提交视频转码任务时并没有返回视频转码结果,返回的是任务id。而阿里云提供两种获取视频:

    1.根据任务id轮询转码查询接口 

    2.异步回调开启消息通知(需要购买消息通知服务,当完成转码将会向客户发送一条消息)

    准备工作:

    管道id:登陆控制台,查看管道id

    顶置模板id:在这里可以查看需要用的模板id


    提交的转码任务:

    /**
     * Author: hezishan
     * Date: 2018/5/2.
     * Description:
     **/
    @Service
    public class SubmitJobServiece {
    
        private static final Logger LOGGER = Logger.getLogger(SubmitJobServiece.class);
    
        /**
         * 配置信息
         */
        @Autowired
        private OSSConfiguration configuration;
    
        /**
         * 获取iacsClient
         */
        private IAcsClient acsClient = IAcsClientFactory.getInstance();
    
        /**
         * 初始化input参数
         *
         * @param location
         * @param bucketName
         * @param ossInputObject 转码文件
         * @return
         */
        private JSONObject initInputParams(String location, String bucketName, String ossInputObject) {
            JSONObject inputObj = new JSONObject();
            try {
                inputObj.put("Location", location);
                inputObj.put("Bucket", bucketName);
                inputObj.put("Object", URLEncoder.encode(ossInputObject, "utf-8"));
            } catch (UnsupportedEncodingException e) {
                throw new ServiceException("input URL encode failed");
            }
            return inputObj;
        }
    
        /**
         * 初始化output
         *
         * @param outputOSSObject
         * @param templateId
         * @return
         */
        private JSONObject initOutputParams(String outputOSSObject, String templateId) {
            JSONObject outputObj = new JSONObject();
            try {
                outputObj.put("OutputObject", URLEncoder.encode(outputOSSObject, "utf-8"));
                outputObj.put("TemplateId", templateId);
            } catch (UnsupportedEncodingException e) {
                throw new ServiceException("input URL encode failed");
            }
            return outputObj;
        }
    
    
        /**
         * 提交转码任务
         *
         * @param ossInputObject
         * @param outputOSSObject
         * @param templateId
         * @return
         */
        public Map<String, String> submitTransferJob(String ossInputObject, String outputOSSObject, String templateId) {
            Map<String, String> map = new HashMap<String, String>();
            // 创建API请求并设置参数
            SubmitJobsRequest request = new SubmitJobsRequest();
            JSONArray outputs = new JSONArray();
            outputs.add(initOutputParams(outputOSSObject, templateId));
            request.setOutputs(outputs.toJSONString());
            request.setInput(initInputParams(configuration.getOssLocation(), configuration.getBucketName(), ossInputObject).toJSONString());
            request.setOutputBucket(configuration.getBucketName());
            request.setOutputLocation(configuration.getOssLocation());
            request.setPipelineId(configuration.getPipelineId());
            SubmitJobsResponse response;
            try {
                response = acsClient.getAcsResponse(request);
                if (response.getJobResultList().get(0).getSuccess()) {
                    String jobId = response.getJobResultList().get(0).getJob().getJobId();
                    map.put("jobId", jobId);
                    map.put("code", "1");
                } else {
                    map.put("code", "0");
                    LOGGER.info("[SubmitJobServiece.class:submitTransferJob]:" + "SubmitJobs Failed code:" + response.getJobResultList().get(0).getCode() +
                            " message:" + response.getJobResultList().get(0).getMessage());
                }
    
            } catch (Exception e) {
                map.put("code", "0");
                throw new ServiceException(e.getMessage());
            }
            return map;
        }
    }

    调用工具类

        /**
         * 提交单个转码任务
         *
         * @param ossInputObject
         * @param type
         * @return
         */
        public Map<String, String> submitJob(String ossInputObject, Integer type) {
            String tempId = "";
            if (Mp4Type.STANDARD_MP4 == type) {
                tempId = configuration.getTemIdOfStandardMp4();
            } else if (Mp4Type.HIGH_MP4 == type) {
                tempId = configuration.getTemIdOfHighMp4();
            } else {
                tempId = configuration.getTemIdOfFullMp4();
            }
            String outputObject = OSSUtils.generateTransferKey(configuration.getSuffxx());
            return submitJobServiece.submitTransferJob(ossInputObject, outputObject, tempId);
        

    本人在提交转码任务时将任务id保存在表中,开启定时器扫描表的数据,轮询查询转码任务完成情况:

    **
     * Author: hezishan
     * Date: 2018/5/3.
     * Description: 查询转码轮询定时任务
     **/
    public class QueryJob implements Job {
    
        private static final Logger LOGGER = Logger.getLogger(QueryJob.class);
    
        private OSSConfiguration configuration;
    
        private JobInfoDao jobInfoDao;
    
        private RecordDao recordDao;
    
        /**
         * 获取iacsClient
         */
        private IAcsClient acsClient = IAcsClientFactory.getInstance();
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            //通过bean获取对象
            configuration = (OSSConfiguration) SpringUtils.getBean("OSSConfiguration");
            recordDao = (RecordDao) SpringUtils.getBean("recordDao");
            jobInfoDao = (JobInfoDao) SpringUtils.getBean("jobInfoDao");
    
            List<JobInfo> jobInfos = jobInfoDao.findByIsQuery(0);
            LOGGER.info("[QueryJob.class:execute]:查询转码任务数量:" + jobInfos.size());
            List<Map<String, String>> resultlist = new ArrayList<Map<String, String>>();
            try {
                if (jobInfos != null && jobInfos.size() > 0) {
                    for (JobInfo info : jobInfos) {
                        QueryJobListRequest request = new QueryJobListRequest();
                        request.setJobIds(info.getJobList());
                        QueryJobListResponse response = acsClient.getAcsResponse(request);
                        int count = 0;
                        if (response.getJobList() != null && response.getJobList().size() > 0) {
                            for (int i = 0; i < response.getJobList().size(); i++) {
                                Map<String, String> map = new HashMap<String, String>();
                                QueryJobListResponse.Job job = response.getJobList().get(i);
                                String state = job.getState();
                                if (JobType.SUCESS.equals(state)) {
                                    map.put("type", handleType(job.getOutput().getTemplateId()));
                                    map.put("inputObject", job.getInput().getObject());
                                    map.put("outputObject", job.getOutput().getOutputFile().getObject());
                                    resultlist.add(map);
                                    count++;
                                } else if (JobType.FAIL.equals(state)) {
                                    LOGGER.info("[QueryJob.class:execute]:任务id:" + job.getJobId() + "转码任务失败");
                                    count = -3;
                                }
                            }
                        }
                        if (count == 3) {
                            //成功,更新数据库
                            info.setIsQuery(1);
                            info.setUpdateTime(new Date());
                            jobInfoDao.save(info);
                            String standardUrl = "";
                            String highUrl = "";
                            String fullUrl = "";
                            for (Map<String, String> map : resultlist) {
                                String type = map.get("type");
                                if ("standard".equals(type)) {
                                    standardUrl = OSSUtils.generateResponseUrl(configuration.getBucketName(), configuration.getEndPoint(), map.get("outputObject"));
                                }
                                if ("high".equals(type)) {
                                    highUrl = OSSUtils.generateResponseUrl(configuration.getBucketName(), configuration.getEndPoint(), map.get("outputObject"));
                                }
                                if ("full".equals(type)) {
                                    fullUrl = OSSUtils.generateResponseUrl(configuration.getBucketName(), configuration.getEndPoint(), map.get("outputObject"));
                                }
                            }
                            saveRecord(resultlist.get(0).get("inputObject"), standardUrl, highUrl, fullUrl);
                            LOGGER.info("[QueryJob.class:execute]:任务id:" + info.getJobList() + "转码任务成功");
                            return;
                        }
                        if (count == 0) {
                            LOGGER.info("[QueryJob.class:execute]:任务id:" + info.getJobList() + "转码任务未完成");
                            return;
                        }
                        if (count < 0) {
                            info.setIsQuery(2);
                            info.setUpdateTime(new Date());
                            jobInfoDao.save(info);
                            String yunUrl = OSSUtils.generateResponseUrl(configuration.getBucketName(), configuration.getEndPoint(), resultlist.get(0).get("inputObject"));
                            saveRecord(resultlist.get(0).get("inputObject"), yunUrl, yunUrl, yunUrl);
                            LOGGER.info("[QueryJob.class:execute]:任务id:" + info.getJobList() + "转码任务失败");
                            return;
                        }
    
                    }
                }
    
            } catch (Exception e) {
                throw new ServiceException(e.getMessage());
            }
        }
    
    
        /**
         * 保存record
         *
         * @param yunUrl
         * @param standardUrl
         * @param highUrl
         * @param fullUrl
         */
        private void saveRecord(String yunUrl, String standardUrl, String highUrl, String fullUrl) {
            String[] url = yunUrl.split("%2F");
            yunUrl = url[url.length - 1];
            Record record = recordDao.findByYunUrlEndingWith(yunUrl);
            if (record != null) {
                record.setStandUrl(standardUrl);
                record.setHighUrl(highUrl);
                record.setFullUrl(fullUrl);
                recordDao.save(record);
            }
        }
    
    
        /**
         * 处理类型
         *
         * @param tempId
         * @return
         */
        private String handleType(String tempId) {
            String result = "";
            if (configuration.getTemIdOfStandardMp4().equals(tempId)) {
                result = "standard";
                return result;
            }
            if (configuration.getTemIdOfHighMp4().equals(tempId)) {
                result = "high";
                return result;
            }
            if (configuration.getTemIdOfFullMp4().equals(tempId)) {
                result = "full";
                return result;
            }
            return result;
        }
    }


    展开全文
  • 相应,在社会媒体处理上,从传统媒体到社交媒体过渡,情感影响是一方面,大家还会用社交媒体做电影票房预测、做股票预测等等。 但是从长远角度看,社会、人文等学科与计算机学科结合是历史性。...
  • 万象系统是百度搜索为了解决富媒体信息海量处理问题而设计和开发系统,文中万象系统进行了一次全面总览介绍,万象系统目前在百度已经承接了搜索所需要所有图片、视频数据加工和处理,管理着超大量级图片...
  • 通过将所有不同工具和库隐藏到它插件中,以及使用媒体管道 这个一般性概念,GStreamer 能以一种统一方式表示不同类型媒体所进行操作。这使得您能够将精力集中于现有的媒体,而不是困惑于究竟应该使用什么...
  • 摘要: 不同于其他云服务厂商的媒体转码产品,阿里云将更多的技术赋能给用户,通过媒体处理,用户可以基于海量数据深度学习,对媒体的内容、文字、...自建转码与采用阿里云媒体处理的区别阿里云媒体处理服务是基...
  • 这些传输网络可能是低带宽的,也有可能是高带宽的,最重要的是媒体内容要与传输媒介协调一致。否则,用户体验是和糟糕的——移动缓慢的视频、音频以及巨大的处理延迟。  同等画质下压缩带宽的估计
  • 但如今多媒体技术却面临着无法处理大量数据、音视频不能同步、难以同时处理来自不同地方,如本地文件,网络,电话广播和数码相机媒体硬件也有很强依赖性。诸多问题,阻碍了多媒体技术大力推广
  • Java多媒体支持

    2013-08-05 14:03:58
    Java语言的内置类库多媒体技术的支持能力相当强,尤其是文本、图形、图像、声音等媒体处理与展示均提供了极其方便而又丰富的接口。更令人兴奋不已的是,综合运用这些媒体所编制出来的一个个Java小应用程序...
  • 如果你翻阅词典,你会发现,对于“多媒体技术”表述是——通过计算机文字、数据、图形、图像、视频、音频等多种媒体信息进行综合处理和管理,使用户可通过多种途径与计算机进行实时信息交互技术。显然,这种...
  • 多媒体技术重点难点分析和处理措施-1 多媒体技术重点难点分析和处理措施 1交叉作业处理措施 ? A交叉作业中安全措施该措施是最重要也是工作前提所以有必要首先提出来给予保障 ? a双方单位在同一作业区域内...
  • FFmpeg 是一套在命令行界面运行跨平台媒体处理工具,属于自由软件,常用来视频音频和图片等媒体文件进行格式转换、分割和合并等,也可录屏录音。 开发语言:C官网:https://www.ffmpeg.org官方说明文档:...
  • 本文主要分析licode C++部分视频流的处理流程。主要介绍licode从发送客户端接收视频流,然后经过内部的处理,再将视频流发送到接收客户端。 licode虽然是MCU模型,但提供主要功能还是SFU,它不能将同一个房间内...
  • 我将要处理,分析和可视化数据集是Twitter用户@dog_rates推文存档,也称为WeRateDogs。 WeRateDogs是一个Twitter帐户,通过幽默评论为人们狗评分。这些评级分母几乎总是10。但是,分子是多少?几乎...
  • WPF中数据的处理

    2021-01-20 17:03:32
    所以WPF中的对数据的处理与我之前所学前端中数据处理完全是两个方面,之前所学前端中数据处理是单一、统一,从页面获取条件,通过后台处理和判断,直接从数据库中获取数据,再放到页面显示;...
  • 1.Django css静态文件的处理 我们先来看Django css静态文件是怎么处理的,一起来实现一下: ...
  • 第7章多媒体处理工具Photoshop应用;1快捷工具栏 快捷工具栏中工具主要是用于页面布局以及显示方式;4. 文件基本操作 1文件新建 执行文件新建命令或者按Ctrl+N快捷键可以打开新建对话框如图7-2所示文件大小...
  • Archer是Netflix的媒体处理引擎,底层是执行MapReduce各种Docker,在上层跑各种算法。Archer可以检测出视频中图像错误,字幕关键内容遮挡...
  • 1. 简介 流媒体是使用了流式传输的多媒体应用技术。如下是维基百科关于流媒体概念的定义: 流媒体 (streaming media) 是指将一连串的媒体数据压缩后,经过网络分段...1.1 FFmpeg 影音处理的层次 FFmpeg 中影音数
  • 1、QoS(Quality of Service,服务质量):一个网络能够利用各种基础技术,为指定网络通信提供更好服务能力, 是网络...但是关键应用和多媒体应用就十分必要。当网络过载或拥塞时,QoS 能确保重要业务量不受延
  • Atitit ffmpeg功能表 多媒体处理类库工具 音频视频 目录 1.1. ffmpeg音视频合成 1 1.2. Atitit 视频音频分离 提取法 1 ...1.6. 多媒体视频处理工具FFmpeg有非常强大功能包括视频采集功能、视频...
  • 这是数字信号处理教程随书光盘,学习DSP很有帮助
  • 长期以来,多媒体信息在计算机中都是以文件形式存放,由操作...本文将就Oracle多媒体信息进行处理的方式,工具,特色以及一些基本原理做一个综述。 但是我们不能不遗憾的看到,现有数据库一些多媒体数据所特有的操
  • 我们先来看Django css静态文件是怎么处理的,一起来实现一下: 第一步:首先需要在settings文件中指定静态文件的存储路径,可以使用Django模板引擎语法提示,如STATICFILES_DIRS = (os.path.join(BASE_DIR, ...
  • 开源媒体中心 OSMC(开源媒体中心)是基于Linux... 致力于OSMC代码库内容已成为OSMC版权,因此可以在必要时其进行维护和重新许可(即-> GPLv3),而无需与作者保持联系。 OSMC不声称拥有自己上游代码,建议您

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,681
精华内容 1,472
关键字:

对媒体处理的是