视频渲染损坏 MediaCodec H.264 流

Kai*_*dul 6 android h.264 textureview android-mediacodec

我正在使用 Java API 实现解码器MediaCodec来解码实时 H.264 远程流。我使用回调 ( ) 从本机层接收 H.264 编码数据,在上void OnRecvEncodedData(byte[] encodedData)进行解码和渲染。我的实现已完成(使用回调、解码和渲染等检索编码流)。这是我的解码器类:SurfaceTextureView

public class MediaCodecDecoder extends Thread implements MyFrameAvailableListener {

    private static final boolean VERBOSE = true;
    private static final String LOG_TAG = MediaCodecDecoder.class.getSimpleName();
    private static final String VIDEO_FORMAT = "video/avc"; // h.264
    private static final long mTimeoutUs = 10000l;

    private MediaCodec mMediaCodec;
    Surface mSurface;
    volatile boolean m_bConfigured;
    volatile boolean m_bRunning;
    long startMs;

    public MediaCodecDecoder() {
        JniWrapper.SetFrameAvailableListener(this);
    }

    // this is my callback where I am receiving encoded streams from native layer 
    @Override
    public void OnRecvEncodedData(byte[] encodedData) {
        if(!m_bConfigured && bKeyFrame(encodedData)) {
            Configure(mSurface, 240, 320, encodedData);
        }
        if(m_bConfigured) {
            decodeData(encodedData);
        }
    }

    public void SetSurface(Surface surface) {
        if (mSurface == null) {
            mSurface = surface;
        }
    }

    public void Start() {
        if(m_bRunning)
            return;
        m_bRunning = true;
        start();
    }

    public void Stop() {
        if(!m_bRunning)
            return;
        m_bRunning = false;
        mMediaCodec.stop();
        mMediaCodec.release();
    }

    private void Configure(Surface surface, int width, int height, byte[] csd0) {
        if (m_bConfigured) {
            Log.e(LOG_TAG, "Decoder is already configured");
            return;
        }
        if (mSurface == null) {
            Log.d(LOG_TAG, "Surface is not available/set yet.");
            return;
        }
        MediaFormat format = MediaFormat.createVideoFormat(VIDEO_FORMAT, width, height);
        format.setByteBuffer("csd-0", ByteBuffer.wrap(csd0));
        try {
            mMediaCodec = MediaCodec.createDecoderByType(VIDEO_FORMAT);
        } catch (IOException e) {
            Log.d(LOG_TAG, "Failed to create codec: " + e.getMessage());
        }

        startMs = System.currentTimeMillis();
        mMediaCodec.configure(format, surface, null, 0);
        if (VERBOSE) Log.d(LOG_TAG, "Decoder configured.");

        mMediaCodec.start();
        Log.d(LOG_TAG, "Decoder initialized.");

        m_bConfigured = true;
    }

    @SuppressWarnings("deprecation")
    private void decodeData(byte[] data) {
        if (!m_bConfigured) {
            Log.e(LOG_TAG, "Decoder is not configured yet.");
            return;
        }
        int inIndex = mMediaCodec.dequeueInputBuffer(mTimeoutUs);
        if (inIndex >= 0) {
            ByteBuffer buffer;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                buffer = mMediaCodec.getInputBuffers()[inIndex];
                buffer.clear();
            } else {
                buffer = mMediaCodec.getInputBuffer(inIndex);
            }
            if (buffer != null) {
                buffer.put(data);
                long presentationTimeUs = System.currentTimeMillis() - startMs;
                mMediaCodec.queueInputBuffer(inIndex, 0, data.length, presentationTimeUs, 0);
            }
        }
    }

    private static boolean bKeyFrame(byte[] frameData) {
        return ( ( (frameData[4] & 0xFF) & 0x0F) == 0x07);
    }

    @Override
    public void run() {
        try {
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            while(m_bRunning) {
                if(m_bConfigured) {
                    int outIndex = mMediaCodec.dequeueOutputBuffer(info, mTimeoutUs);
                    if(outIndex >= 0) {
                        mMediaCodec.releaseOutputBuffer(outIndex, true);
                    }
                } else {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException ignore) {
                    }
                }
            }
        } finally {
            Stop();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在的问题是 - 流正在被解码并在表面上呈现,但视频不清楚。看起来框架已损坏,场景扭曲/肮脏。机芯坏了,到处都是方形碎片(我真的很抱歉,因为我现在没有屏幕截图)。

关于我的流 - 它是 H.264 编码的,仅包含 I 帧和 P 帧(没有 B 帧)。每个 I 框架都有SPS + PPS + payload结构。编码时使用的颜色格式(在本机层使用FFMPEG)是YUV420 planner。本机层发送的数据长度是可以的(宽度*高度*(3 / 2))。

期间configure()我只是csd-0用SPS框架设置值。用于配置的帧是I帧(SPS + PPS +负载) - 前缀是SPS帧,所以我认为配置成功。请注意,我没有csd-1使用 PPS 框架设置该值(这是一个问题吗?)。

0x00 0x00 0x00 0x01对于 p 帧和 I 帧,每个帧都有前面的起始码 ( )(对于 I 帧,起始码同时出现在 SPS 和 PPS 帧之前)。

此外,我为每个帧设置演示时间戳System.currrentTimeMillis() - startTime,每个新帧的顺序都会增加。我认为这不会造成任何问题(如果我错了,请纠正我)。

我的设备是 Google 的 Nexus 5,Android 版本为 4.4.4,芯片组是 Qualcomm MSM8974 Snapdragon 800。我用于Surface解码,所以我认为不应该存在任何设备特定的颜色格式不匹配问题。

TextureView如果需要的话我也可以提供我的代码。

我的解码/渲染不正确可能是什么原因?提前致谢!

编辑1

我尝试在配置期间手动传递特定于编解码器的数据(SPS 和 PPS 字节)。但这并没有带来任何改变:(

byte[] sps  = {0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x0c, (byte) 0xda, 0x0f, 0x0a, 0x68, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x07, (byte) 0xa3, (byte) 0xc5, 0x0a, (byte) 0xa8};
format.setByteBuffer("csd-0", ByteBuffer.wrap(sps));

byte[] pps = {0x00, 0x00, 0x00, 0x01, 0x68, (byte) 0xef, 0x04, (byte) 0xf2, 0x00, 0x00};
format.setByteBuffer("csd-1", ByteBuffer.wrap(pps));
Run Code Online (Sandbox Code Playgroud)

我也尝试过修剪起始代码(0x00, 0x00, 0x00, 0x01),但没有任何进展!

编辑2

我尝试使用硬件加速 {{TextureView}},正如官方文档中提到的那样(尽管我在 MediaCodec-textureView 的示例项目中没有找到任何硬件加速代码)。但仍然没有任何进展。现在我评论了硬件加速代码片段。

编辑3

屏幕截图现已提供:

损坏的 H264 视频 MediaCodec 解码 在此输入图像描述

编辑4

为了进一步说明,这是我的 H.264 编码的 I 帧十六进制流格式:

00 00 00 01 67 4d 40 0c da 0f 0a 68 40 00 00 03 00 40 00 00 07 a3 c5 0a a8 00 00 00 01 68 ef 04 f2 00 00 01 06 05 ff ff 69 dc 45 e9 bd 6 d9 48 b7 96 2c d8 20 d9 23 ee EF 78 32 36 34 20 2d 20 63 6f 72 65 20 31 34 36 20 2d 20 48 2e 32 36 34 2f 4d 50 45 47 2d 34 20 41 56 43 20 63 6f 64 65 63 20 2d 20 43 6f 70 79 6c 65 66 74 20 32 30 30 33 2d 32 30 31 35 20 2d 20 68 74 74 70 3a 2f 2f 77 77 77 2e 76 69 64 65 6f 6c 61 6e 2e 6f 72 67 2f 78 32 36 34 2e 68 74 6d 6c 20 2d 20 6f 70 74 69 6f 6e 73 3a 20 63 61 62 61 63 3d 31 20 72 65 66 3d 31 20 64 65 62 6c 6f 63 6b 3d 31 3a 30 3a 30 20 61 6e 61 6c 79 73 65 3d 30 78 31 3a 30 78 31 20 6d 65 3d 68 65 78 20 73 75 62 6d 65 3d 30 20 70 73 79 3d 31 20 70 73 79 5f 72 64 3d 31 2e 30 30 3a 30 2e 30 30 20 6d 69 78 65 64 5f 72 65 66 3d 30 20 6d 65 5f 72 61 6e 67 65 3d 31 36 20 63 68 72 6f 6d 61 5f 6d 65 3d 31 20 74 72 65 6c 6c 69 73 3d 30 20 38 78 38 64 63 74

这是一个 P 帧:

00 00 00 01 41 9a 26 22 df 76 4b b2 ef cf 57 ac 5b b6 3b 68 b9 87 b2 71 a5 9b 61 3c 93 47 bc 79 c5 ab 0f 87 34 f6 40 6a cd 80 03 b1 a2 c2 4e 08 13 光盘4e 3c 62 3e 44 0a e8 97 80 ec 81 3f 31 7c f1 29 f1 43 a0 c0 a9 0a 74 62 c7 62 74 da c3 94 f5 19 23 ff 4b 9c c1 69 55 54 2f 62 f0 5e 64 7f 18 3f 58 73 af 93 6e 92 06 fd 9f a1 1a 80 cf 86 71 24 7d f7 56 2c c1 57 cf ba 05 17 77 18 f1 8b 3c 33 40 18 30 1f b0 19 23 44 ec 91 c4 bd 80 65 4a 4 6 b3 1e 53 5d 6d a3 f0 b5 50 3a 93 ba 81 71 f3 09 98 41 43 ba 5f a1 0d 41 a3 7b c3 fd eb 15 89 75 66 a9 ee 3a 9c 1b c1 aa f8 58 10 88 0c 79 77 ff 7d 15 28 EB 12 A7 1b 76 36 aa 84 e1 3e 63 cf a9 a3 cf 4a 2d c2 33 18 91 30 f7 3c 9c 56 f5 4c 12 6c 4b 12 1f c5 ec 5a 98 8c 12 75 eb fd 98 a4 fb 7f 80 5d 28 f 9 EF 43 A4 0a ca 25 75 19 6b f7 14 7b 76 af e9 8f 7d 79 fa 9d 9a 63 de 1f be fa 6c 65 ba 5f 9d b0 b0 f4 71 cb e2 ea d6 dc c6 55 98 1b cd 55 d9 eb 9c 75 fc 9十月

我非常确定我的流的正确性,因为我使用ffmpeg解码GLSurfaceviewOpenGLES 2.0.

Kai*_*dul 4

我从本机层和 Java 层获取了 H.264 转储,发现本机层的转储播放完美,但 Java 层的转储播放与解码流一样损坏。问题是 - 在将编码流从本机层传递到 Java 期间,编码流未正确传递(已损坏),这是因为我的实现有缺陷(对关注此线程的人造成不便表示歉意)。

此外,我仅将 I 帧的有效负载传递给解码器,这导致渲染损坏。现在我正在传递完整的 NAL 单元(SPS + PPS + 有效负载),现在一切都很好:)