当前位置: 首页 > 知识库问答 >
问题:

视频呈现已中断MediaCodec H.264流

山乐生
2023-03-14

我正在使用mediacodecJava 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结构。编码过程中使用的颜色格式(在本机层中使用FFMPEG)是YUV420规划器。从本机层发送的数据长度是正常的(宽度*高度*(3/2))。

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

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

此外,我将每个帧的表示时间戳设置为System.CurrRentTimeMillis()-startTime,该帧的顺序对每个新帧都是递增的。我想这应该不会引起什么问题(如果我错了请纠正我)。

我的设备是谷歌的Nexus5,Android版本4.4.4,芯片组是高通MSM8974骁龙800。我正在使用surface进行解码,因此我认为不应该存在任何特定于设备的颜色格式不匹配问题。

如果需要,我还可以提供我的textureview代码。

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

我尝试在配置期间手动传递特定于编解码器的数据(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

截图现在是可用的:

编辑4

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

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 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 73af 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 b0 19 23 44 ec 91 c4 bd 80 65 4A 46 b3 1E 53 5D 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 a71B 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 be fa 6C 65 ba 5F 9D b0 f4 71 cb e2 ea d6 dc c6 55 98 1B cd 55 d9 eb 9C 75 fc 9D ec

我非常肯定我的流的正确性,因为我使用FFMPEG解码和GLSurfaceView使用OpenGles 2.0成功地呈现了流。

共有1个答案

暴奕
2023-03-14

我从原生层和Java层获取了h.264转储,发现原生层的转储播放得很好,但是Java层的转储播放得像解码流一样破碎。问题是--在将编码流从本机层传递到Java的过程中,编码流没有正确地传递(损坏),这是因为我的实现有缺陷(对于这个不便,向跟踪这个线程的人表示抱歉)。

此外,我只将I-Frame的有效负载传递给解码器,这导致了中断的渲染。现在我正在通过完整的NAL单元(SPS+PPS+有效载荷),现在一切都好了:)

 类似资料:
  • 问题内容: 最终,我创建了一个流式录像机Flash应用程序及其简单的Red5后端,但是Red5当然再次使我开玩笑。在大多数情况下,录制的视频已损坏,无法在不随机停止- 继续播放的情况下播放它们,也不会和播放器和我一起玩。为什么这样做呢? 我研究了互联网,发现了这个问题,但没有解决方案!我尝试不录制视频,而是将其切换为直播并附加ffmpeg来完成肮脏的工作,但ffmpeg自然无法在red5的输出上显

  • 我需要验证视频文件是(在Java): 视频是H.264编码的 我调查过JMF和Xuggle。 Xuggle使加载和解码文件变得更容易,并将其转换为另一种格式,但我还不知道如何确定我加载的文件的编码。 所以我想知道Xuggle是否有能力简单地返回视频类型 如果我需要自己确定,有人能给我指一些H.264格式的文档吗

  • 我有两个h264视频文件。一个是“Big buck Bunny”,另一个是我使用FFMPEG创建的。两者在大多数浏览器中都可以播放,但在Firefox31.1.0中,“Big buck Bunny”播放得很好,但我的视频给出了“损坏视频”的响应。 ffprobe在屯门的两个视频的输出如下(首先是兔子,然后是我的) 谁能明白为什么我的不玩...?

  • 我指的是演示应用程序Grafika,其中的CameraCaptureActivity记录了一段视频,同时显示了应用效果的实时预览。 有没有一种方法只渲染一次OpenGL效果? 如果我能把一个表面的内容复制到另一个表面,那就太好了。 有没有办法做到这一点?

  • 背景: 两天来,我一直在努力实现一个像Vine一样的录像机。首先,我试了MediaRecorder。但我需要的视频可能是由小视频剪辑组成的。此类不能用于录制短时视频剪辑。然后我找到了MediaCodec、FFmpeg和JavaCV。FFmpeg和JavaCV可以解决这个问题。但是我必须用许多库文件来编译我的项目。它将生成一个非常大的APK文件。所以我更喜欢用MediaCodec实现它,尽管这个类只

  • 我已经使用Android MediaCodec库来转码视频文件(这里主要是更改分辨率示例代码) 我想做的另一件事是截断视频--只花15秒开始。逻辑是检查是否大于15秒,我将向解码器缓冲区写入。 但是我得到了一个异常