
设置OpenGL ES环境
创建GLSurfaceView
为了显示OpenGL的图形,你需要使用GLSurfaceView类,就像其他任何的View子类意义,你可以将它添加到你的Activity或Fragment之上,通过在布局xml文件中定义或者在代码中创建实例。
在本次的教程中,我们使用GLSurfaceView作为唯一的View在我们的Activity中,因此,为了简便,我们在代码中创建GLSurfaceView的实例并将其传入setContentView中,这样它将会填充你的整个手机屏幕。Activity中的onCreate方法如下:
<code class="hljs" java="">protected void onCreate(Bundle savedInstanceState) { superonCreate(savedInstanceState); GLSurfaceView view = new GLSurfaceView(this); setContentView(view);}</code>
因为媒体效果的框架仅仅支持OpenGL ES20及以上的版本,所以在setEGLContextClientVersion 方法中传入2;
<code avrasm="" class="hljs">viewsetEGLContextClientVersion(2);</code>
为了确保GLSurfaceView仅仅在必要的时候进行渲染,我们在setRenderMode 方法中进行设置:
<code avrasm="" class="hljs">viewsetRenderMode(GLSurfaceViewRENDERMODE_WHEN_DIRTY);</code>
创建Renderer
Renderer负责渲染GLSurfaceView中的内容。
创建类实现接口GLSurfaceViewRenderer,在这里我们打算将这个类命名为EffectsRenderer,添加构造函数并覆写接口中的抽象方法,如下:
<code class="hljs" java="">public class EffectsRenderer implements GLSurfaceViewRenderer { public EffectsRenderer(Context context){ super(); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { } @Override public void onDrawFrame(GL10 gl) { }}</code>
回到Activity中调用setRenderer方法,让GLSurfaceView使用我们创建的Renderer:
<code class="hljs" cs="">viewsetRenderer(new EffectsRenderer(this));</code>
编写Manifest文件
如果你想要发布你的App到谷歌商店,在AndroidManifestxml文件中添加如下语句:
<code class="hljs" xml=""><uses-feature android:glesversion="0x00020000" android:required="true"></uses-feature></code>
这会确保你的app只能被安装在支持OpenGL ES20的设备之上。现在OpenGL环境准备完毕。
创建一个OpenGL平面
定义顶点
GLSurfaceView是不能直接显示一张照片的,照片首先应该被转化为纹理,应用在OpenGL square之上。在本次教程中,我将创建一个2D平面,并且具有4个顶点。为了简单,我将使用一个长方形,现在,创建一个新的类Square,用它来代表形状。
<code class="hljs" cs="">public class Square {}</code>
默认的OpenGL系统的坐标系中的原点是在中心,因此4个角的坐标可以表示为:
左下角: (-1, -1) 右下角:(1, -1) 右上角:(1, 1) 左上角:(-1, 1)
我们使用OpenGL绘制的所有的物体都应该是由三角形决定的,为了画一个方形,我们需要两个具有一条公共边的三角形,那意味着这些三角形的坐标应该是:
triangle 1: (-1, -1), (1, -1), 和 (-1, 1) triangle 2: (1, -1), (-1, 1), 和 (1, 1)
创建一个float数组来代表这些顶点:
<code class="hljs" cpp="">private float vertices[] = { -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f,};</code>
为了在square上定位纹理,需要确定纹理的顶点坐标,创建另一个数组来表示纹理顶点的坐标:
<code class="hljs" cpp="">private float textureVertices[] = { 0f,1f, 1f,1f, 0f,0f, 1f,0f};</code>
创建缓冲区
这些坐标数组应该被转变为缓冲字符(byte buffer)在OpenGL可以使用之前,接下来进行定义:
<code class="hljs" cs="">private FloatBuffer verticesBuffer;private FloatBuffer textureBuffer;</code>
在initializeBuffers方法中去初始化这些缓冲区:使用ByteBufferallocateDirect来创建缓冲区,因为float是4个字节,那么我们需要的byte数组的长度应该为float的4倍。
下面使用ByteBuffernativeOrder方法来定义在底层的本地平台上的byte的顺序。使用asFloatBuffer方法将ByteBuffer转化为FloatBuffer,在FloatBuffer被创建后,我们调用put方法来将float数组放入缓冲区,最后,调用position方法来保证我们是由缓冲区的开头进行读取。
<code avrasm="" class="hljs">private void initializeBuffers(){ ByteBuffer buff = ByteBufferallocateDirect(verticeslength 4); bufforder(ByteOrdernativeOrder()); verticesBuffer = buffasFloatBuffer(); verticesBufferput(vertices); verticesBufferposition(0); buff = ByteBufferallocateDirect(textureVerticeslength 4); bufforder(ByteOrdernativeOrder()); textureBuffer = buffasFloatBuffer(); textureBufferput(textureVertices); textureBufferposition(0);}</code>
创建着色器
着色器只不过是简单的运行在GPU中的每个单独的顶点的C程序,在本次教程中,我们使用两种着色器:顶点着色器和片段着色器。
顶点着色器的代码:
<code class="hljs" glsl="">attribute vec4 aPosition; attribute vec2 aTexPosition; varying vec2 vTexPosition; void main() { gl_Position = aPosition; vTexPosition = aTexPosition; };</code>
片段着色器的代码
<code class="hljs" glsl="">precision mediump float; uniform sampler2D uTexture; varying vec2 vTexPosition; void main() { gl_FragColor = texture2D(uTexture, vTexPosition); };</code>
如果你了解OpenGL,那么这段代码对你来说是熟悉的,如果你不能理解这段代码,你可以参考OpenGL documentation。
Android 用MediaCodec实现视频硬解码
本文向你讲述如何用android标准的API (MediaCodec)实现视频的硬件编解码。例程将从摄像头采集视频开始,然后进行H264编码,再解码,然后显示。我将尽量讲得简短而清晰,不展示那些不相关的代码。但是,我不建议你读这篇文章,也不建议你开发这类应用,而应该转而开发一些戳鱼、打鸟、其乐融融的程序。好吧,下面的内容是写给那些执迷不悟的人的,看完之后也许你会同意我的说法:Android只是一个玩具,很难指望它来做靠谱的应用。
1、从摄像头采集视频
可以通过摄像头Preview的回调,来获取视频数据。
首先创建摄像头,并设置参数:
[java] view plaincopy
cam = Cameraopen();
camsetPreviewDisplay(holder);
CameraParameters parameters = camgetParameters();
parameterssetFlashMode("off"); // 无闪光灯
parameterssetWhiteBalance(CameraParametersWHITE_BALANCE_AUTO);
parameterssetSceneMode(CameraParametersSCENE_MODE_AUTO);
parameterssetFocusMode(CameraParametersFOCUS_MODE_AUTO);
parameterssetPreviewFormat(ImageFormatYV12);
parameterssetPictureSize(camWidth, camHeight);
parameterssetPreviewSize(camWidth, camHeight);
//这两个属性 如果这两个属性设置的和真实手机的不一样时,就会报错
camsetParameters(parameters);
宽度和高度必须是摄像头支持的尺寸,否则会报错。要获得所有支持的尺寸,可用getSupportedPreviewSizes,这里不再累述。据说所有的参数必须设全,漏掉一个就可能报错,不过只是据说,我只设了几个属性也没出错。 然后就开始Preview了:
[java] view plaincopy
buf = new byte[camWidth camHeight 3 / 2];
camaddCallbackBuffer(buf);
camsetPreviewCallbackWithBuffer(this);
camstartPreview();
setPreviewCallbackWithBuffer是很有必要的,不然每次回调系统都重新分配缓冲区,效率会很低。
在onPreviewFrame中就可以获得原始的了(当然,this 肯定要 implements PreviewCallback了)。这里我们是把它传给编码器:
[java] view plaincopy
public void onPreviewFrame(byte[] data, Camera camera) {
if (frameListener != null) {
frameListeneronFrame(data, 0, datalength, 0);
}
camaddCallbackBuffer(buf);
}
2、编码
首先要初始化编码器:
[java] view plaincopy
mediaCodec = MediaCodeccreateEncoderByType("Video/AVC");
MediaFormat mediaFormat = MediaFormatcreateVideoFormat(type, width, height);
mediaFormatsetInteger(MediaFormatKEY_BIT_RATE, 125000);
mediaFormatsetInteger(MediaFormatKEY_FRAME_RATE, 15);
mediaFormatsetInteger(MediaFormatKEY_COLOR_FORMAT, MediaCodecInfoCodecCapabilitiesCOLOR_FormatYUV420Planar);
mediaFormatsetInteger(MediaFormatKEY_I_FRAME_INTERVAL, 5);
mediaCodecconfigure(mediaFormat, null, null, MediaCodecCONFIGURE_FLAG_ENCODE);
mediaCodecstart();
然后就是给他喂数据了,这里的数据是来自摄像头的:
[java] view plaincopy
public void onFrame(byte[] buf, int offset, int length, int flag) {
ByteBuffer[] inputBuffers = mediaCodecgetInputBuffers();
ByteBuffer[] outputBuffers = mediaCodecgetOutputBuffers();
int inputBufferIndex = mediaCodecdequeueInputBuffer(-1);
if (inputBufferIndex >= 0)
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBufferclear();
inputBufferput(buf, offset, length);
mediaCodecqueueInputBuffer(inputBufferIndex, 0, length, 0, 0);
}
MediaCodecBufferInfo bufferInfo = new MediaCodecBufferInfo();
int outputBufferIndex = mediaCodecdequeueOutputBuffer(bufferInfo,0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
if (frameListener != null)
frameListeneronFrame(outputBuffer, 0, length, flag);
mediaCodecreleaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodecdequeueOutputBuffer(bufferInfo, 0);
}
先把来自摄像头的数据喂给它,然后从它里面取压缩好的数据喂给解码器。
3、解码和显示
首先初始化解码器:
[java] view plaincopy
mediaCodec = MediaCodeccreateDecoderByType("Video/AVC");
MediaFormat mediaFormat = MediaFormatcreateVideoFormat(mime, width, height);
mediaCodecconfigure(mediaFormat, surface, null, 0);
mediaCodecstart();
这里通过给解码器一个surface,解码器就能直接显示画面。
然后就是处理数据了:
[java] view plaincopy
public void onFrame(byte[] buf, int offset, int length, int flag) {
ByteBuffer[] inputBuffers = mediaCodecgetInputBuffers();
int inputBufferIndex = mediaCodecdequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBufferclear();
inputBufferput(buf, offset, length);
mediaCodecqueueInputBuffer(inputBufferIndex, 0, length, mCount 1000000 / FRAME_RATE, 0);
mCount++;
}
MediaCodecBufferInfo bufferInfo = new MediaCodecBufferInfo();
int outputBufferIndex = mediaCodecdequeueOutputBuffer(bufferInfo,0);
while (outputBufferIndex >= 0) {
mediaCodecreleaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = mediaCodecdequeueOutputBuffer(bufferInfo, 0);
}
}
queueInputBuffer第三个参数是时间戳,其实怎么写都无所谓,只要是按时间线性增加的就可以,这里就随便弄一个了。后面一段的代码就是把缓冲区给释放掉,因为我们直接让解码器显示,就不需要解码出来的数据了,但是必须要这么释放一下,否则解码器始终给你留着,内存就该不够用了。
好了,到现在,基本上就可以了。如果你运气够好,现在就能看到视频了,比如在我的三星手机上这样就可以了。但是,我试过几个其他平台,多数都不可以,总是有各种各样的问题,如果要开发一个不依赖平台的应用,还有很多的问题要解决。说说我遇到的一些情况:
1、视频尺寸
一般都能支持176X144/352X288这种尺寸,但是大一些的,640X480就有很多机子不行了,至于为什么,我也不知道。当然,这个尺寸必须和摄像头预览的尺寸一致,预览的尺寸可以枚举一下。
2、颜色空间
根据ANdroid SDK文档,确保所有硬件平台都支持的颜色,在摄像头预览输出是YUV12,在编码器输入是COLOR_FormatYUV420Planar,也就是前面代码中设置的那样。 不过,文档终究是文档,否则安卓就不是安卓。
在有的平台上,这两个颜色格式是一样的,摄像头的输出可以直接作为编码器的输入。也有的平台,两个是不一样的,前者就是YUV12,后者等于I420,需要把前者的UV分量颠倒一下。下面的代码效率不高,可供参考。
[java] view plaincopy
byte[] i420bytes = null;
private byte[] swapYV12toI420(byte[] yv12bytes, int width, int height) {
if (i420bytes == null)
i420bytes = new byte[yv12byteslength];
for (int i = 0; i < widthheight; i++)
i420bytes[i] = yv12bytes[i];
for (int i = widthheight; i < widthheight + (width/2height/2); i++)
i420bytes[i] = yv12bytes[i + (width/2height/2)];
for (int i = widthheight + (width/2height/2); i < widthheight + 2(width/2height/2); i++)
i420bytes[i] = yv12bytes[i - (width/2height/2)];
return i420bytes;
}
这里的困难是,我不知道怎样去判断是否需要这个转换。据说,Android 43不用再从摄像头的PreView里面取图像,避开了这个问题。这里有个例子,虽然我没读,但看起来挺厉害的样子,应该不会有错吧(觉厉应然)。>
'获得光标位置(按字节计算,起如行和起始列均为0)
Private Function GetCurPos(ptPos As POINTAPI) As Long
Dim nLine As Long, nCol As Long
Call SendMessage(MeText1hwnd, EM_GETSEL, 0, ptPos)
nLine = SendMessage(MeText1hwnd, EM_LINEFROMCHAR, ptPosx, ByVal 0&) '获得行号
nCol = ptPosx - SendMessage(MeText1hwnd, EM_LINEINDEX, -1, 0) '获得列号
GetCurPos = ptPosx '光标位置(按字节计算)
ptPosy = nLine
ptPosx = nCol
End Function
'获得光标位置(按字符计算)
Private Function GetCurPosCh(ptPos As POINTAPI) As Long
Dim nLine As Long, nCol As Long
Dim ByteBuffer() As Byte
SendMessage MeText1hwnd, EM_GETSEL, 0, ptPos
nLine = SendMessage(MeText1hwnd, EM_LINEFROMCHAR, ptPosx, ByVal 0&) '获得行号
nCol = ptPosx - SendMessage(MeText1hwnd, EM_LINEINDEX, -1, 0)
ReDim ByteBuffer(1024)
ByteBuffer(1) = 4
SendMessage MeText1hwnd, EM_GETLINE, nLine, ByteBuffer(0)
ReDim Preserve ByteBuffer(nCol)
nCol = Len(StrConv(ByteBuffer, vbUnicode)) - 1 '获得列号
GetCurPosCh = MeText1SelStart '光标位置(按字符计算)
ptPosy = nLine + 1
ptPosx = nCol + 1
Erase ByteBuffer
End Function
Call GetCurPosCh(ptPos)
GetTextInfo = " 第 " & ptPosy & " 行 , 第 " & ptPosx & " 列"
首先澄清一点:这三个方法都是ByteBuffer的抽象基类Buffer定义的方法,ByteBuffer只是继承了它们。
其次,你要理解缓冲区的概念,就是Buffer的意义:缓冲区是特定基本类型元素的线性有限序列。除内容外,缓冲区的基本属性还包括容量、限制和位置:
缓冲区的容量 是它所包含的元素的数量。缓冲区的容量不能为负并且不能更改。
缓冲区的限制 是第一个不应该读取或写入的元素的索引。缓冲区的限制不能为负,并且不能大于其容量。
缓冲区的位置 是下一个要读取或写入的元素的索引。缓冲区的位置不能为负,并且不能大于其限制。
任何插入或读取都不能超出限制。
标记、位置、限制和容量值遵守以下不变式:
0 <= 标记 <= 位置 <= 限制 <= 容量
新创建的缓冲区总有一个 0 位置和一个未定义的标记。初始限制可以为 0,也可以为其他值,这取决于缓冲区类型及其构建方式。一般情况下,缓冲区的初始内容是未定义的。
clear() 使缓冲区为一系列新的通道读取或相对放置 *** 作做好准备:它将限制设置为容量大小,将位置设置为 0。
通道读取指从通道将数据读入到buffer中,相对放置是从位置开始将数据插入到buffer中
flip() 使缓冲区为一系列新的通道写入或相对获取 *** 作做好准备:它将限制设置为当前位置,然后将位置设置为 0。
把限制设置为当前位置是为了保证数据的可靠性。让从buffer写入到通道的数据是buffer中确实是已经存在的数据。
rewind() 使缓冲区为重新读取已包含的数据做好准备:它使限制保持不变,将位置设置为 0。
和clear()类似,只是不改动限制
这三个方法在源码上就对缓冲区的数据不进行任何修改
JDK14以后就提供javanio的包,nio主要提供字节与字符的映射、内存映射文件和文件加锁机制
其中内存映射文件在读取大文件时可能会用上,因为内存映射不是直接把文件加载到JVM内存空间
而是借用 *** 作系统对文件的读取,这经历了由当前Java态进入到 *** 作系统内核态,再由 *** 作系统读取文件,
并返回数据到当前Java态的过程。由Java态进入 *** 作系统内核态离不开nio包中两个重要的类
FileChannel 和 ByteBuffer。FileChannel表示文件通道,可以从FileInputStream、FileOutputStream
以及RandomAccessFile对象获取文件通道,你可以从文件通道直接读取文件,也可以使用“内存映射”
即使用通道,将文件内存映射到ByteBuffer,可以映射一部分内容,也可以映射全部内容,使用内存映射
能大幅提高我们 *** 作大文件的速度
FileChannel 和 ByteBuffer文件读取
[java] view plain copy
package nio;
import javaioBufferedInputStream;
import javaioFile;
import javaioFileInputStream;
import javaioIOException;
import javaioRandomAccessFile;
import javanioByteBuffer;
import javanioMappedByteBuffer;
import javaniochannelsFileChannel;
import javaniochannelsFileChannelMapMode;
/
Channel类似与流,数据可以从Channel读取到Buffer,也可以从Buffer写入到Channel
但通道和流还是有区别,比如流只能是单向读或写,而通道可以异步读写
@author yli
/
public class FileChannelTest {
当用到二进制传输的时候,一定会用bytebuffer这个类,它是buffer接口的子类。
ByteBuffer byteBuffer = ByteBufferallocate(6);
虽然使用allocate方法创建的缓冲区并不是一次性地分配内存空间,但我们可以从用户地角度将一个缓冲区想象成一个长度为capacity的数组。当缓冲区创建后,和数组一样,缓冲区的大小(capacity值)将无法改变,也无法访问缓冲区外的数据
既然缓冲区和数组类似,那么缓冲区也应该象数组一样可以标识当前的位置。缓冲区的position方法为我们提供了这个功能。position方法有两种重载形式,它们的定义如下:
public final int position()
public final Buffer position(int newPosition)
第一个重载形式用来获取缓冲区的当前位置。在创建缓冲区后,position的初始值是0,也就是缓冲区第一个元素的位置。当从缓冲区读取一个元素后,position的值加1我们从这一点可以看出,position方法返回的位置就是当前可以读取的元素的位置。position的取值范围从0到capacity – 1如果position的值等于capacity,说明缓冲区当前已经没有数据可读了。
在大多数情况下不需要直接控制缓冲区的位置。缓冲区类提供的用于读写数据的方法可以自动地设置缓冲区的当前位置。在缓冲区类中,get和put方法用于读写缓冲区中的数据。 每当put方法向缓冲区写入一个数据后,缓冲区的当前位置都会加1如果缓冲区的当前位置已经等于capacity,调用put方法就会抛出一个javanioBufferOverflowException异常。在缓冲区未初赋值的区域将被0填充。使用get方法可以得到缓冲区当前位置的数据,并使缓冲区的当前位置加1和put方法一样,[/color]在缓冲区当前位置等于capacity时使用get方法也会抛出javanioBufferOverflowException异常
可以使用position方法将当前位置移到缓冲区的任何位置。缓冲区除了position和capacity外,还提供了一个标识limit来限制缓冲区可访问的范围。[color=red]在初始状态下,缓冲区的limit和capacity值相同。但limit和capacity的区别是limit可以通过limit方法进行设置,而capacity在创建缓冲区时就已经指定了,并且不能改变。limit还可以表示缓冲区中实际的数据量
while(byteBufferhasRemaining()) // 枚举byteBuffer中的数据,常用判断
byteBufferflip(); // 将limit设为position即2,然后将position设置为 0
因为数据很长。
数据长度内存能接受,就全部接收完成才进行字符转换。
编码各种各样,对是否是完整汉字的判断也是一件巨大的麻烦。其实鉴于降低模块耦合的问题,通信模块压根不应该管是不是完整汉字,判断最后一个字节是不是有效汉字的问题应该交给接收方来另作判断,对于不完整的字符,先不处理,等下一批数据来了再拼起来处理。
以上就是关于如何使用Android中的OpenGL ES媒体效果全部的内容,包括:如何使用Android中的OpenGL ES媒体效果、Android MediaCodec surface模式下如何读取原始视频数据、在VB中如何获取TextBox的行数等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)