如何在android的MediaCodec上下文中使用ByteBuffer

Posted

技术标签:

【中文标题】如何在android的MediaCodec上下文中使用ByteBuffer【英文标题】:How to use ByteBuffer in the MediaCodec context in android 【发布时间】:2013-01-26 06:00:42 【问题描述】:

到目前为止,我能够设置 MediaCodec 来编码视频流。目的是将我的用户生成的艺术作品保存到视频文件中。

我使用用户艺术品的 android Bitmap 对象将帧推送到流中。

查看我在这篇文章底部使用的代码 sn-p(它是完整的代码,没有任何修剪):

MediaCodec 使用 ByteBuffer 来处理视频/音频流。

位图基于 int[],如果转换为 byte[],则需要 x4 的大小为 int[]

我做了一些研究来弄清楚在 MediaCodec 中处理视频/音频流时 ByteBuffer 有哪些合同,但信息几乎接近 zilch。

所以, MediaCodec 中的 ByteBuffer 使用合同是什么?

在 MediaFormat 中指定帧尺寸是否自动意味着 ByteBuffers 具有宽度 * 高度 * 4 字节的容量?

(我为每一帧一次使用一个位图对象)

感谢您的帮助。

(已编辑,添加代码)

    import java.io.ByteArrayOutputStream;
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.nio.ByteBuffer;

    import android.graphics.Rect;
    import android.graphics.Bitmap.CompressFormat;
    import android.media.MediaCodec;
    import android.media.MediaCodec.BufferInfo;
    import android.media.CamcorderProfile;
    import android.media.MediaCodecInfo;
    import android.media.MediaFormat;
    import android.util.Log;
    import android.view.View;

    public class VideoCaptureManager 

        private boolean running;

        private long presentationTime;

        public void start(View rootView, String saveFilePath)
            Log.e("OUT", saveFilePath);
            this.running = true;
            this.presentationTime = 0;
            this.capture(rootView, saveFilePath);
        

        private void capture(final View rootView, String saveFilePath)
            if(rootView != null)
                rootView.setDrawingCacheEnabled(true);

                final Rect drawingRect = new Rect();
                rootView.getDrawingRect(drawingRect);

                try
                    final File file = new File(saveFilePath);
                    if(file.exists())
                        // File exists return
                        return;
                     else 
                        File parent = file.getParentFile();
                        if(!parent.exists())
                            parent.mkdirs();
                        
                    

            new Thread()
                public void run()
                    try
                        DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));

                        MediaCodec codec = MediaCodec.createEncoderByType("video/mp4v-es");
                        MediaFormat mediaFormat = null;
                        if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P))
                            mediaFormat = MediaFormat.createVideoFormat("video/mp4v-es", 720, 1280);
                         else 
                            mediaFormat = MediaFormat.createVideoFormat("video/mp4v-es", 480, 720);
                        


                        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 700000);
                        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 10);
                        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
                        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
                        codec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

                        codec.start();

                        ByteBuffer[] inputBuffers = codec.getInputBuffers();
                        ByteBuffer[] outputBuffers = codec.getOutputBuffers();

                        while(VideoCaptureManager.this.running)
                            try
                                int inputBufferIndex = codec.dequeueInputBuffer(-2);
                                if(inputBufferIndex >= 0)
                                    // Fill in the bitmap bytes
                                    // inputBuffers[inputBufferIndex].
                                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                                    rootView.getDrawingCache().compress(CompressFormat.JPEG, 80, baos);
                                    inputBuffers[inputBufferIndex].put(baos.toByteArray());

                                    codec.queueInputBuffer(inputBufferIndex, 0, inputBuffers[inputBufferIndex].capacity(), presentationTime, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
                                    presentationTime += 100;
                                

                                BufferInfo info = new BufferInfo();
                                int outputBufferIndex = codec.dequeueOutputBuffer(info, -2);
                                if(outputBufferIndex >= 0)
                                    // Write the bytes to file
                                    byte[] array = outputBuffers[outputBufferIndex].array(); // THIS THORWS AN EXCEPTION. WHAT IS THE CONTRACT TO DEAL WITH ByteBuffer in this code?
                                    if(array != null)
                                        dos.write(array);
                                    

                                    codec.releaseOutputBuffer(outputBufferIndex, false);
                                 else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
                                    outputBuffers = codec.getOutputBuffers();
                                 else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
                                    // codec format is changed
                                    MediaFormat format = codec.getOutputFormat();
                                

                                Thread.sleep(100);
                            catch(Throwable th)
                                Log.e("OUT", th.getMessage(), th);
                            
                        

                        codec.stop();
                        codec.release();
                        codec = null;

                        dos.flush();
                        dos.close();
                    catch(Throwable th)
                        Log.e("OUT", th.getMessage(), th);
                    
                
                    .start();

                catch(Throwable th)
                    Log.e("OUT", th.getMessage(), th);
                
            
        

        public void stop()
            this.running = false;
        
    

【问题讨论】:

【参考方案1】:

ByteBuffer 的确切布局由您选择的输入格式的编解码器决定。并非所有设备都支持所有可能的输入格式(例如,一些 AVC 编码器需要平面 420 YUV,其他需要半平面)。旧版本的 Android (MediaCodec 生成视频帧。

在 Android 4.3 (API 18) 中,您有两种选择。首先,MediaCodec 现在接受来自 Surface 的输入,这意味着您可以使用 OpenGL ES 绘制的任何内容都可以录制为电影。例如,请参阅EncodeAndMuxTest sample。

其次,您仍然可以选择使用软件生成的 YUV 420 缓冲区,但现在它们更有可能发挥作用,因为有 CTS 测试可以使用它们。您仍然需要对平面或半平面进行运行时检测,但实际上只有两种布局。有关示例,请参见 EncodeDecodeTest 的缓冲区到缓冲区变体。

【讨论】:

以上是关于如何在android的MediaCodec上下文中使用ByteBuffer的主要内容,如果未能解决你的问题,请参考以下文章

Android MediaCodec在异步模式下比在同步模式下慢吗?

如何使用 Android MediaCodec 编码相机数据(YUV420sp)

Android MediaCodec 状态(States)转换分析

Android MediaCodec简单总结

Android MultiMedia框架——MediaCodec编码(下)

使用 MediaCodec 和 Surface 进行 Android 编码