android用MediaCodeC将opengl绘制内容录制为一个mp4
来源:互联网 发布:org域名不能注册了? 编辑:程序博客网 时间:2024/05/16 17:34
做了一个demo:用MediaCodeC将opengl绘制内容录制为一个mp4
https://github.com/xiaxveliang/GL_AUDIO_VIDEO_RECODE
学习MediaCodeC时的参考代码
http://bigflake.com/mediacodec/EncodeAndMuxTest.java.txt
对于以上代码,我做了一个简单的注释,代码如下:
import android.media.MediaCodec;import android.media.MediaCodecInfo;import android.media.MediaFormat;import android.media.MediaMuxer;import android.opengl.EGL14;import android.opengl.EGLConfig;import android.opengl.EGLContext;import android.opengl.EGLDisplay;import android.opengl.EGLExt;import android.opengl.EGLSurface;import android.opengl.GLES20;import android.os.Environment;import android.test.AndroidTestCase;import android.util.Log;import android.view.Surface;import java.io.File;import java.io.IOException;import java.nio.ByteBuffer;// wiki:// http://bigflake.com/mediacodec/EncodeAndMuxTest.java.txtpublic class EncodeAndMuxTest extends AndroidTestCase { private static final String TAG = EncodeAndMuxTest.class.getSimpleName(); // 输出文件路径 private static final File MY_OUTPUT_DIR = Environment.getExternalStorageDirectory(); // H.264编码 private static final String MY_MIME_TYPE = "video/avc"; // 视频文件的宽高 private static final int MY_VIDEO_WIDTH = 480; private static final int MY_VIDEO_HEIGHT = 480; // 视频码率 private static final int MY_BIT_RATE = 800000; // 每秒钟15帧 private static final int MY_FPS = 15; // 总共30帧,每一秒15帧,所以30帧为2秒钟 private static final int NUM_FRAMES = 30; // RGB color values for generated frames private static final int TEST_R0 = 0; private static final int TEST_G0 = 136; private static final int TEST_B0 = 0; // private static final int TEST_R1 = 236; private static final int TEST_G1 = 50; private static final int TEST_B1 = 186; // encoder / muxer state private MediaCodec mEncoder; // H.264 转 mp4 private MediaMuxer mMuxer; private CodecInputSurface mInputSurface; private int mTrackIndex; private boolean mMuxerStarted; // allocate one of these up front so we don't need to do it every time private MediaCodec.BufferInfo mBufferInfo; /** * opengl绘制一个buffer,转成MP4,程序入口 */ public void testEncodeVideoToMp4() { try { // 初始化Encoder initVideoEncoder(); // 设置 EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx mInputSurface.makeCurrent(); // 共30帧 for (int i = 0; i < NUM_FRAMES; i++) { // mEncoder从缓冲区取数据,然后交给mMuxer编码 drainEncoder(false); // opengl绘制一帧 generateSurfaceFrame(i); // 设置图像,发送给EGL的显示时间 mInputSurface.setPresentationTime(computePresentationTimeNsec(i)); // Submit it to the encoder mInputSurface.swapBuffers(); } // send end-of-stream to encoder, and drain remaining output drainEncoder(true); } finally { // release encoder, muxer, and input Surface releaseEncoder(); } } /** * 初始化视频编码器 */ private void initVideoEncoder() { // 创建一个buffer mBufferInfo = new MediaCodec.BufferInfo(); //-----------------MediaFormat----------------------- // mediaCodeC采用的是H.264编码 MediaFormat format = MediaFormat.createVideoFormat(MY_MIME_TYPE, MY_VIDEO_WIDTH, MY_VIDEO_HEIGHT); // 数据来源自surface format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // 视频码率 format.setInteger(MediaFormat.KEY_BIT_RATE, MY_BIT_RATE); // fps format.setInteger(MediaFormat.KEY_FRAME_RATE, MY_FPS); //设置关键帧的时间 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); //-----------------Encoder----------------------- try { mEncoder = MediaCodec.createEncoderByType(MY_MIME_TYPE); mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); // 创建一个surface Surface surface = mEncoder.createInputSurface(); // 创建一个CodecInputSurface,其中包含GL相关 mInputSurface = new CodecInputSurface(surface); // mEncoder.start(); } catch (Exception e) { e.printStackTrace(); } //-----------------输出文件路径----------------------- // 输出文件路径 String outputPath = new File(MY_OUTPUT_DIR, "test." + MY_VIDEO_WIDTH + "x" + MY_VIDEO_HEIGHT + ".mp4").toString(); //-----------------MediaMuxer----------------------- try { // 输出为MP4 mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); } catch (IOException ioe) { throw new RuntimeException("MediaMuxer creation failed", ioe); } mTrackIndex = -1; mMuxerStarted = false; } /** * Releases encoder resources. May be called after partial / failed initialization. * 释放资源 */ private void releaseEncoder() { if (mEncoder != null) { mEncoder.stop(); mEncoder.release(); mEncoder = null; } if (mInputSurface != null) { mInputSurface.release(); mInputSurface = null; } if (mMuxer != null) { mMuxer.stop(); mMuxer.release(); mMuxer = null; } } /** * mEncoder从缓冲区取数据,然后交给mMuxer编码 * * @param endOfStream 是否停止录制 */ private void drainEncoder(boolean endOfStream) { final int TIMEOUT_USEC = 10000; // 停止录制 if (endOfStream) { mEncoder.signalEndOfInputStream(); } //拿到输出缓冲区,用于取到编码后的数据 ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers(); while (true) { //拿到输出缓冲区的索引 int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { // no output available yet if (!endOfStream) { break; // out of while } else { } } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { //拿到输出缓冲区,用于取到编码后的数据 encoderOutputBuffers = mEncoder.getOutputBuffers(); } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // should happen before receiving buffers, and should only happen once if (mMuxerStarted) { throw new RuntimeException("format changed twice"); } // MediaFormat newFormat = mEncoder.getOutputFormat(); // now that we have the Magic Goodies, start the muxer mTrackIndex = mMuxer.addTrack(newFormat); // mMuxer.start(); mMuxerStarted = true; } else if (encoderStatus < 0) { } else { //获取解码后的数据 ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; if (encodedData == null) { throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); } // if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { mBufferInfo.size = 0; } // if (mBufferInfo.size != 0) { if (!mMuxerStarted) { throw new RuntimeException("muxer hasn't started"); } // adjust the ByteBuffer values to match BufferInfo (not needed?) encodedData.position(mBufferInfo.offset); encodedData.limit(mBufferInfo.offset + mBufferInfo.size); // 编码 mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); } //释放资源 mEncoder.releaseOutputBuffer(encoderStatus, false); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { if (!endOfStream) { Log.w(TAG, "reached end of stream unexpectedly"); } else { } break; // out of while } } } } /** * Generates a frame of data using GL commands. We have an 8-frame animation * sequence that wraps around. It looks like this: * <pre> * 0 1 2 3 * 7 6 5 4 * </pre> * We draw one of the eight rectangles and leave the rest set to the clear color. */ private void generateSurfaceFrame(int frameIndex) { frameIndex %= 8; int startX, startY; if (frameIndex < 4) { // (0,0) is bottom-left in GL startX = frameIndex * (MY_VIDEO_WIDTH / 4); startY = MY_VIDEO_HEIGHT / 2; } else { startX = (7 - frameIndex) * (MY_VIDEO_WIDTH / 4); startY = 0; } GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glEnable(GLES20.GL_SCISSOR_TEST); GLES20.glScissor(startX, startY, MY_VIDEO_WIDTH / 4, MY_VIDEO_HEIGHT / 2); GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glDisable(GLES20.GL_SCISSOR_TEST); } /** * Generates the presentation time for frame N, in nanoseconds. * 好像是生成当前帧的时间,具体怎么计算的,不懂呀?????????????????????????? */ private static long computePresentationTimeNsec(int frameIndex) { final long ONE_BILLION = 1000000000; return frameIndex * ONE_BILLION / MY_FPS; } /** * Holds state associated with a Surface used for MediaCodec encoder input. * <p> * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that * to create an EGL window surface. Calls to eglSwapBuffers() cause a frame of data to be sent * to the video encoder. * <p> * This object owns the Surface -- releasing this will release the Surface too. */ private static class CodecInputSurface { private static final int EGL_RECORDABLE_ANDROID = 0x3142; private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; private Surface mSurface; /** * Creates a CodecInputSurface from a Surface. */ public CodecInputSurface(Surface surface) { if (surface == null) { throw new NullPointerException(); } mSurface = surface; initEGL(); } /** * 初始化EGL */ private void initEGL() { //--------------------mEGLDisplay----------------------- // 获取EGL Display mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); // 错误检查 if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { throw new RuntimeException("unable to get EGL14 display"); } // 初始化 int[] version = new int[2]; if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { throw new RuntimeException("unable to initialize EGL14"); } // Configure EGL for recording and OpenGL ES 2.0. int[] attribList = { EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8, EGL14.EGL_ALPHA_SIZE, 8, // EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, // 录制android EGL_RECORDABLE_ANDROID, 1, EGL14.EGL_NONE }; EGLConfig[] configs = new EGLConfig[1]; int[] numConfigs = new int[1]; // eglCreateContext RGB888+recordable ES2 EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0); // Configure context for OpenGL ES 2.0. int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE }; //--------------------mEGLContext----------------------- // eglCreateContext mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, attrib_list, 0); checkEglError("eglCreateContext"); //--------------------mEGLSurface----------------------- // 创建一个WindowSurface并与surface进行绑定,这里的surface来自mEncoder.createInputSurface(); // Create a window surface, and attach it to the Surface we received. int[] surfaceAttribs = { EGL14.EGL_NONE }; // eglCreateWindowSurface mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface, surfaceAttribs, 0); checkEglError("eglCreateWindowSurface"); } /** * Discards all resources held by this class, notably the EGL context. Also releases the * Surface that was passed to our constructor. * 释放资源 */ public void release() { if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); EGL14.eglReleaseThread(); EGL14.eglTerminate(mEGLDisplay); } mSurface.release(); mEGLDisplay = EGL14.EGL_NO_DISPLAY; mEGLContext = EGL14.EGL_NO_CONTEXT; mEGLSurface = EGL14.EGL_NO_SURFACE; mSurface = null; } /** * Makes our EGL context and surface current. * 设置 EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx */ public void makeCurrent() { EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext); checkEglError("eglMakeCurrent"); } /** * Calls eglSwapBuffers. Use this to "publish" the current frame. * 用该方法,发送当前Frame */ public boolean swapBuffers() { boolean result = EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); checkEglError("eglSwapBuffers"); return result; } /** * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. * 设置图像,发送给EGL的时间间隔 */ public void setPresentationTime(long nsecs) { // 设置发动给EGL的时间间隔 EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); checkEglError("eglPresentationTimeANDROID"); } /** * Checks for EGL errors. Throws an exception if one is found. * 检查错误,代码可以忽略 */ private void checkEglError(String msg) { int error; if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); } } }}
阅读全文
0 0
- android用MediaCodeC将opengl绘制内容录制为一个mp4
- 将RTSP流录制为mp4文件
- 将RTSP流录制为mp4文件
- 关于Android MediaCodec 的内容
- 关于Android MediaCodec 的内容
- OpenGL:将绘制场景保存为bmp图片
- FFMPEG研究: Android下录制/dev/video0设备h264编码保存为mp4格式视频
- Generate mp4 video with image files using MediaCodec in Android
- 将MP4转换为GIF
- iOSMp4Cameravideo,mp4,camera,视频,录制,拍摄录制视频,并且将视频转换成mp4格式
- Android在MediaMuxer和MediaCodec录制视频示例 - audio+video
- 用libmp4v2录制h264和aac rtp流为mp4文件
- opengl绘制一个圆
- 【OpenGL】绘制一个点
- [Modern OpenGL系列(三)]用OpenGL绘制一个三角形
- Android 将View内容存为Bitmap
- android中利用opengl es绘制一个球体
- Android OpenGL ES(十一):绘制一个20面体
- Maven学习总结(19)——Maven+Nexus+Myeclipse集成
- 多台web服务器之间共享session
- Failed to read artifact descriptor for xxx:jar的问题解决
- 理解ASP.NET MVC底层运行机制
- ubuntu 安装pycharm
- android用MediaCodeC将opengl绘制内容录制为一个mp4
- 关于百度特效“黑洞”的css3,自己记录一下
- QColor类的使用
- linux defunct僵尸进程
- Arduino-开发入门2-Arduino蓝牙模块与Android实现通信
- 概率论与自然语言处理1
- 便携式跌倒检测研究
- Android eclipse解决方法超过65535
- 移动互联未来发展之我所见 (2014.11.12 )