使用Android MediaCodec解码原始H264视频流时出现解码错误

3
我正在开发一个屏幕共享应用,试图从 socket 接收原始的 H264 视频流,并使用 MediaCodec 类进行解码,通过 surfaceview 进行显示。然而,我已经成功地能够接收数据并在 surface 上显示。但是,我遇到了一个问题,也就是视频非常卡顿、抖动并且有绿色条纹和斑块。您可以在 YouTube 的 视频链接 中看到这个问题。如果我将该视频保存到 SD 卡上,并使用 MxPlayer 播放,则一切正常。我还尝试了 Gstreamer,一切都很好。 这是我从 Activity 调用的类,当 surfaceview 创建时会调用此类。
//New Edited Code
public class Server {
static final int socketServerPORT = 53515;
MainActivity activity;
ServerSocket serverSocket;

public Server(MainActivity activity, Surface surface) {
    Log.e("constructor()", "called");
    this.activity = activity;
    Thread socketServerThread = new Thread(new SocketServerThread(surface));
    socketServerThread.start();
}

private static MediaCodecInfo selectCodec(String mimeType) {
    int numCodecs = MediaCodecList.getCodecCount();
    for (int i = 0; i < numCodecs; i++) {
        MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);

        if (codecInfo.isEncoder()) {
            continue;
        }

        String[] types = codecInfo.getSupportedTypes();
        for (int j = 0; j < types.length; j++) {
            if (types[j].equalsIgnoreCase(mimeType)) {
                Log.e("codecinfo", codecInfo.getName());
                return codecInfo;
            }
        }
    }
    return null;
}

private class SocketServerThread extends Thread {
    InputStream is;
    Socket socket;
    private MediaCodec codec;
    private Surface surface;

    public SocketServerThread(Surface surface) {
        this.surface = surface;
    }

    @Override
    public void run() {
        Log.e("socketthread", "called");
        try {
            selectCodec("video/avc");
            codec = MediaCodec.createByCodecName(selectCodec("video/avc").getName());

            MediaFormat format = MediaFormat.createVideoFormat("video/avc", 800, 480);
            //  format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
            format.setInteger(MediaFormat.KEY_BIT_RATE, 1024000);
            format.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
            codec.configure(format, surface, null, 0);
            codec.start();
            serverSocket = new ServerSocket(socketServerPORT);
            while (true) {
                socket = serverSocket.accept();
                Log.e("connection", "accepted");
                is = socket.getInputStream();
                if (is != null) {
                    //          File file = new File(Environment.getExternalStorageDirectory() + "/stream.mp4");
                    //              OutputStream output = new FileOutputStream(file);
                    byte[] buff = new byte[4 * 1024]; // or other buffer size
                    int read;
                    while ((read = is.read(buff)) != -1) {
                        //              output.write(buff, 0, read);
                        if (buff.length == 1)
                            continue;

                        int inIndex = codec.dequeueInputBuffer(10000);
                        if (inIndex >= 0) {
                            ByteBuffer inputBuffer = codec.getInputBuffer(inIndex);
                            inputBuffer.clear();
                            inputBuffer.put(buff);

                            codec.queueInputBuffer(inIndex, 0, buff.length, 16, 0);
                        }

                        MediaCodec.BufferInfo buffInfo = new MediaCodec.BufferInfo();
                        int outIndex = codec.dequeueOutputBuffer(buffInfo, 10000);

                        switch (outIndex) {
                            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                                break;
                            case MediaCodec.INFO_TRY_AGAIN_LATER:
                                break;
                            case -3:
                                break;
                            default:
                                codec.releaseOutputBuffer(outIndex, true);
                        }
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (codec != null) {
                codec.release();
            }
            if (socket != null) {
                try {
                    socket.close();
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  }
}

我尝试了你提到的方法来创建MediaCodec

MediaCodec.createByCodecName(selectCodec("video/avc").getName());

   private static MediaCodecInfo selectCodec(String mimeType) {
    int numCodecs = MediaCodecList.getCodecCount();
    for (int i = 0; i < numCodecs; i++) {
        MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);

        if (!codecInfo.isEncoder()) {
            continue;
        }

        String[] types = codecInfo.getSupportedTypes();
        for (int j = 0; j < types.length; j++) {
            if (types[j].equalsIgnoreCase(mimeType)) {
                Log.e("codecinfo",codecInfo.getName());
                return codecInfo;
            }
        }
    }
    return null;
}

但是它给我报错了

I/OMXClient: MuxOMX ctor
I/MediaCodec: [OMX.qcom.video.encoder.avc] setting surface generation   to 28345345
W/ACodec: [OMX.qcom.video.encoder.avc] Failed to set standard   component role 'video_decoder.avc'.
E/ACodec: [OMX.qcom.video.encoder.avc] configureCodec returning error -1010
E/ACodec: signalError(omxError 0x80001001, internalError -1010)
E/MediaCodec: Codec reported err 0xfffffc0e, actionCode 0, while in state 3
E/MediaCodec: configure failed with err 0xfffffc0e, resetting...
I/OMXClient: MuxOMX ctor
E/AndroidRuntime: FATAL EXCEPTION: Thread-5
Process: com.androidsrc.server, PID: 27681
android.media.MediaCodec$CodecException: Error 0xfffffc0e
at android.media.MediaCodec.configure(MediaCodec.java:1884)
at com.androidsrc.server.Server$SocketServerThread.run(Server.java:70)
at java.lang.Thread.run(Thread.java:761)

我之前尝试使用这种方式创建Mediacodec

codec =  MediaCodec.createDecoderByType("video/avc");

但视频质量与在YouTube分享的视频相同。
3个回答

1

我更愿意发表评论,但仍然缺少必要的要点:D

我认为你(或者我不知道它是否可以通过其他方式工作)缺少一些重要的部分。你提供的视频显示了如何解释事物的信息缺失。

  • 你是否发送和使用SPS和PPS?我不确定你创建的MediaFormat是否包含所有所需的信息。

  • MediaCodec需要整个帧被输入。你确定使用这种逻辑接收到完整的帧吗?

  • 某些解码器希望没有NAL头的帧。你发送带有NAL头的帧吗?

  • 我没有看到你传输任何呈现时间

我建议您查看RTP / RTSP协议以了解如何传输媒体。 为了进一步帮助您,我建议您提供生产者逻辑。


ChrisBe是正确的,TCP不是传输实时视频的好网络层。另外,你的h264数据包的来源是什么?也许它们与你的客户端不兼容。 - Alex Cohn
请看一下,我正在使用这个Github上的screenCast项目。如果我将此流保存到SD卡中,则它可以在我的Ubuntu上的gstreamer应用程序或我的Android上的mxPlayer上很好地工作。 - neeraj sinha
1
你真的需要再仔细看一下源代码。首先发送的是HTTP_MESSAGE_TEMPLATE,其中包含视频尺寸信息,请将其过滤掉。如果选择了AVC,则接下来会发送SPS和PPS。您需要使用BUFFER_FLAG_CODEC_CONFIG标志将其提供给解码器。我还建议您使用不同的方法传输H264 avc帧,而不仅仅是普通的TCP写入和读取。如果您想坚持这个项目,请使用VP8,因为提供的IvfWriter可以在ivf格式中传输帧。 - ChrisBe
1
我同意你所说的。我正在研究RTSP。我会研究如何实现它并告诉你。 :) - neeraj sinha
@ChrisBe:ivf 也支持 H264,这就是 screencast 使用的。 - Alex Cohn

0

OMX.google.h264.decoder 是一种具有有限功能的软件编解码器(至少在某些设备和 API 级别上如此)。

尝试使用 createDecoderByType 而不是 createDecoderByName,或者选择另一个编解码器,例如这个示例中的: https://developer.android.com/reference/android/media/MediaCodecInfo.html (修改它以选择除 OMX.google.h264.decoder 之外的解码器)


1
在你的代码中,应该删除!codecInfo.isEncoder()中的“!”。请注意,您帖子中的YouTube链接是“私人的”。 - Tomer Nuni
非常抱歉,我已经将链接公开,现在您可以看到它了。另外,我按照您的要求进行了更改。我已经用新代码编辑了问题,请看一下,这是根据新代码更改的新视频。使用硬件解码器解码效果更差,因为它本身返回了"OMX.qcom.video.decoder.avc"。 - neeraj sinha
@neerajsinha硬件解码器比软件解码器更苛刻。但无论如何,您都需要解析IFV帧并逐个将提取的缓冲区馈送到MediaCodec实例。 - Alex Cohn

0

尝试减少出队列的超时时间(codec.dequeueInputBuffer(10000);),除非您认为解码需要很长时间。

此外,请设置以下标志:

mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, CodecCapabilities.COLOR_FormatSurface); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

您提到您更改了代码以按类型选择,但在创建解码器时原始帖子存在错误。您能够修复这些错误吗?

您确实需要使用硬件解码器来确保可以在不显著影响性能的情况下播放。新视频肯定比第一次尝试更糟糕,这意味着您存在解码器选择问题。您可以使用带有-f h264选项的ffplay并确认格式是否正确。

最后,请查看视频尺寸,并且您可以从解码器中读取回传缓冲区(通过在releaseOutputBuffer中传递false)并将其写入磁盘以确认您的尺寸是否正确。原始视频和最终视频将具有不同的尺寸。从codecFormat读取宽度、高度以确认其能够正确解码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接