【问题标题】:Video rendering is broken MediaCodec H.264 stream视频渲染中断 MediaCodec H.264 流
【发布时间】:2015-12-19 19:35:12
【问题描述】:

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

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();
        }
    }
}

现在的问题是 - 流正在解码并在表面上渲染,但视频不清楚。看起来框架被打破了,场景被扭曲/脏了。机芯到处都是破碎的方形碎片(非常抱歉,我现在没有截图)。

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

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

对于 p 帧和 I 帧,每个帧都有前面的起始代码 (0x00 0x00 0x00 0x01)(对于 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));

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

编辑 2

我尝试了硬件加速 {{TextureView}},正如official documentation 中提到的那样(尽管我在 MediaCodec-textureView 的示例项目中没有找到任何硬件加速代码)。但仍然没有进展。现在我注释了硬件加速代码sn-p。

编辑 3

现在可以看到截图了:

编辑 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 e6 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 cd 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 46 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 f9 ef 43 a4 0a ca 25 75 19 6b f7 14 7b 76 af e9 8f 7d 79 fa 9d 9a 63 de 1f 是 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 9d 欧共体

我很确定我的流的正确性,因为我使用ffmpeg 解码和GLSurfaceviewOpenGLES 2.0 成功渲染。

【问题讨论】:

  • 凯杜尔您好!我现在有非常相似的问题。您可以在my question 中看到我的解码方法(与您的略有不同)。我的视频输出看起来和你的很相似。我在我的项目中尝试了你的解码器类,不幸的是得到了相同的结果。你让它正常工作了吗?
  • @KrzysztofKansy 希望我不是唯一一个面临这个问题的人 ;) 不,不幸的是我无法解决这个问题。我还没有尝试过您的代码,但似乎我们几乎无动于衷。如果您或我能找到解决方案,我希望两者都将是有利的:)
  • 我也希望如此;)至于今天,我主要是在修改演示时间戳,但实际上我尝试过的任何事情都不会对我的输出产生积极影响。您是否尝试将解码的帧导出到文件?我的意思是在离开解码器之后和在表面上绘图之前检查它们是否有效。我尝试了位图导出,但没有成功。好吧,我什至不确定解码器的输出缓冲区是否可以通过这种方式正确导出。无论如何,现在解码顺利就好了。问候;)
  • 嗨!我更新了我的问题,只是为了让你知道。我成功地用我的播放器播放了用 MediaCodec 编码的视频,所以我猜在处理解码器的原始数据时存在一些问题,至少在我的情况下。我认为您也可以尝试一下,以消除播放器本身出现错误的可能性。问候:)
  • 好的,我明白了。我尝试只解码 I 帧并跳过 p 帧,似乎在这种情况下渲染并没有中断。但我最终不能跳过 p 帧,所以这不是一个解决方案。你能给我你的编码器和编码帧生成代码吗?

标签: android h.264 android-mediacodec textureview


【解决方案1】:

我从native层和Java层都取了H.264 dump,发现native层的dump播放完美,但是Java层的dump播放和解码流一样坏。问题是 - 在将编码流从本机层传递到 Java 期间,编码流没有正确传递(损坏),这是因为我的实现有问题(很抱歉给谁关注这个线程带来不便)。

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

【讨论】:

  • 你能解释一下你是如何工作的吗?我正在使用 RTP 并尝试为 android MediaCodec 解包,图片出来很奇怪,有很多奇怪的颜色等等。绝对不是它应该的样子。
猜你喜欢
  • 1970-01-01
  • 2011-09-19
  • 2015-12-04
  • 1970-01-01
  • 2015-08-21
  • 2013-04-07
  • 2022-03-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多