MediaCodec H264 编码器不适用于 Snapdragon 800 设备

Posted

技术标签:

【中文标题】MediaCodec H264 编码器不适用于 Snapdragon 800 设备【英文标题】:MediaCodec H264 Encoder not working on Snapdragon 800 devices 【发布时间】:2013-12-26 20:10:01 【问题描述】:

我使用 android 的 MediaCodec API 编写了一个 H264 流编码器。我在大约十种具有不同处理器的不同设备上对其进行了测试,除 Snapdragon 800 驱动的设备(谷歌 Nexus 5 和索尼 Xperia Z1)外,它都能在所有设备上运行。在这些设备上,我得到了 SPS 和 PPS 以及第一个关键帧,但之后 mEncoder.dequeueOutputBuffer(mBufferInfo, 0) 只返回 MediaCodec.INFO_TRY_AGAIN_LATER。我已经尝试了不同的超时、比特率、分辨率和其他配置选项,但无济于事。结果总是一样的。

我使用以下代码来初始化编码器:

        mBufferInfo = new MediaCodec.BufferInfo();
        encoder = MediaCodec.createEncoderByType("video/avc");
        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 640, 480);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 768000);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mEncoderColorFormat);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
        encoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

选择的颜色格式在哪里:

MediaCodecInfo.CodecCapabilities capabilities = mCodecInfo.getCapabilitiesForType(MIME_TYPE);
            for (int i = 0; i < capabilities.colorFormats.length && selectedColorFormat == 0; i++)
            
                int format = capabilities.colorFormats[i];
                switch (format) 
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar:
                        selectedColorFormat = format;
                        break;
                    default:
                        LogHandler.e(LOG_TAG, "Unsupported color format " + format);
                        break;
                
            

我通过做得到数据

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

        int inputBufferIndex = mEncoder.dequeueInputBuffer(-1);
        if (inputBufferIndex >= 0)
        
            // fill inputBuffers[inputBufferIndex] with valid data
            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
            inputBuffer.clear();
            inputBuffer.put(rawFrame);
            mEncoder.queueInputBuffer(inputBufferIndex, 0, rawFrame.length, 0, 0);
            LogHandler.e(LOG_TAG, "Queue Buffer in " + inputBufferIndex);
        

        while(true)
        
            int outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0);
            if (outputBufferIndex >= 0)
            
                Log.d(LOG_TAG, "Queue Buffer out " + outputBufferIndex);
                ByteBuffer buffer = outputBuffers[outputBufferIndex];
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0)
                
                    // Config Bytes means SPS and PPS
                    Log.d(LOG_TAG, "Got config bytes");
                

                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0)
                
                    // Marks a Keyframe
                    Log.d(LOG_TAG, "Got Sync Frame");
                

                if (mBufferInfo.size != 0)
                
                    // adjust the ByteBuffer values to match BufferInfo (not needed?)
                    buffer.position(mBufferInfo.offset);
                    buffer.limit(mBufferInfo.offset + mBufferInfo.size);

                    int nalUnitLength = 0;
                    while((nalUnitLength = parseNextNalUnit(buffer)) != 0)
                    
                        switch(mVideoData[0] & 0x0f)
                        
                            // SPS
                            case 0x07:
                            
                                Log.d(LOG_TAG, "Got SPS");
                                break;
                            

                            // PPS
                            case 0x08:
                            
                                Log.d(LOG_TAG, "Got PPS");
                                break;
                            

                            // Key Frame
                            case 0x05:
                            
                                Log.d(LOG_TAG, "Got Keyframe");
                            

                            //$FALL-THROUGH$
                            default:
                            
                                // Process Data
                                break;
                            
                        
                    
                

                mEncoder.releaseOutputBuffer(outputBufferIndex, false);

                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0)
                
                    // Stream is marked as done,
                    // break out of while
                    Log.d(LOG_TAG, "Marked EOS");
                    break;
                
            
            else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
            
                outputBuffers = mEncoder.getOutputBuffers();
                Log.d(LOG_TAG, "Output Buffer changed " + outputBuffers);
            
            else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
            
                MediaFormat newFormat = mEncoder.getOutputFormat();
                Log.d(LOG_TAG, "Media Format Changed " + newFormat);
            
            else if(outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
            
                // No Data, break out
                break;
            
            else
            
                // Unexpected State, ignore it
                Log.d(LOG_TAG, "Unexpected State " + outputBufferIndex);
            
        

感谢您的帮助!

【问题讨论】:

有多少输入帧在输出停止时排队? (我想确保它不仅仅是缺乏输入。) logcat 中有什么可疑的东西吗? (编解码器倾向于喷洒 Log.e,这会让人很难分辨。)选择了哪种颜色格式? (是QCOM格式吗?)你的“原始帧”的大小与输入缓冲区的容量完全一样吗? (如果不是……为什么不呢?) @fadden 我让它运行多长时间并不重要,但它似乎总是在输入缓冲区中有 5 帧。它在创建时的输出是:I/OMXClient(11245): Using client-side OMX mux. I/ACodec(11245): setupVideoEncoder succeeded 在这两种情况下选择的颜色格式都是MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar (如果我查询所有格式,它只有两种,前面提到的和一个有常量 2130708361 的如果选择就会崩溃。)原始帧和输入缓冲区不一样(原始帧大小总是更小,输入缓冲区容量总是282624) 五帧是典型的——听起来它没有处理输入,因此没有输出。我假设你打电话给encoder.start()? YUV420SemiPlanar 好; 2130708361 仅用于 Surface 输入。 YUV420 缓冲区的大小应该是width * height * 1.5,或 460800 字节,所以我对你的缓冲区大小有点困惑。您是否在日志文件中看到“媒体格式已更改”消息,如果有,它说明了什么? @fadden 是的,我打电话给encoder.start()。我回答中的痕迹的分辨率不同。 rawFrame 的大小是width * height * 1.5,但缓冲区的大小总是比这个大一点。我没有收到格式更改消息。 我不确定还有什么可以尝试的——上面粘贴的代码看起来不错。从这里开始,您最好的方法可能是采用已知可行的类似方法(例如,缓冲区到缓冲区的 EncodeDecodeTest 代码),使其在您的应用程序中工作,然后逐渐将其更改为看起来像您的实现。 【参考方案1】:

encodeCodec.queueInputBuffer(inputBufferIndex, 0, input.length, (System.currentTimeMillis() - startMs) * 1000, 0);

【讨论】:

如果您正在接收实时输入(例如来自相机),则使用当前时间很好,但如果您使用其他来源(例如以比真实速度更快的速度转码视频),则效果不佳时间)。我还建议不要使用System.currentTimeMillis(),因为它会突然跳跃(向前和向后)。单调的System.nanoTime() 是更好的来源。 即使在转码的情况下,源内容的时间戳也将适用(基于源内容的 fps)。编码器需要知道时间戳才能管理速率控制。因此,如果您已使用 mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FPS) 配置帧率,建议为非实时编码生成时间戳 (N * 1000 * 1000 / FPS)。 @fadden,我需要为异步 MediaCodec 编码器实例设置演示时间吗?当我解码 h264 输出时,ffmpeg 看不到视频帧的呈现时间。请告诉我【参考方案2】:

您需要在调用 queueInputBuffer 时设置presentationTimeUs 参数。 大多数编码器都忽略了这一点,您可以毫无问题地进行流式编码。用于 Snapdragon 800 设备的编码器没有。

这个参数代表你的帧的记录时间,因此需要增加你要编码的帧和前一帧之间的我们的数量。

如果参数集与前一帧中的值相同,则编码器将其丢弃。 如果参数设置的值太小(例如 30 FPS 录制时为 100000),则编码帧的质量会下降。

【讨论】:

嗯。表示时间戳不是 H.264 基本流的一部分,所以我的期望是该值只是通过。我添加了一个新的常见问题条目 (bigflake.com/mediacodec/#q8)。 您能否举例说明如何设置演示时间? 调用 Snapdragon 800 设备的 queueInputBuffer 值时的 TimeUs 参数

以上是关于MediaCodec H264 编码器不适用于 Snapdragon 800 设备的主要内容,如果未能解决你的问题,请参考以下文章

Android 多媒体应用使用MediaCodec将摄像头采集的视频编码为h264

Android 音视频编解码 -- 视频编码和H264格式原理讲解

Android 音视频编解码 -- 视频编码和H264格式原理讲解

H264码流结构一探究竟

H264码流结构一探究竟

如何使用 MediaCodec 将位图编码为视频?