【问题标题】:Improper decoding of raw H264 video stream using android MediaCodec使用 android MediaCodec 对原始 H264 视频流的解码不正确
【发布时间】:2017-12-16 22:34:31
【问题描述】:

我正在构建一个屏幕共享应用程序,并尝试从套接字接收原始 H264 视频流,并通过使用 MediaCodec 类解码来使用surfaceview 显示它。但是我成功地能够接收数据并在表面上显示。但是我被困两天的问题是视频非常滞后,抖动并且有绿色带和补丁。您可以在 youtube video link 中查看该问题。如果我将此视频保存到 sdcard 并使用 MxPlayer 播放,那么它可以正常播放。我也尝试使用 Gstreamer,一切都很好。 这是我在创建surfaceview 时从Activity 调用的类。

//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 上分享的视频相同。

【问题讨论】:

    标签: android video-streaming h.264 android-mediacodec screencast


    【解决方案1】:

    我宁愿对此发表评论,但仍然缺少必要的点:D

    我认为您(或者我不知道它在其他情况下是否有效)缺少一些重要的部分。您提供的视频显示缺少有关如何解释事物的信息。

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

    • MediaCodec 需要将整个帧馈送到它。您确定使用这种逻辑会收到完整的帧吗?

    • 一些解码器期望帧没有 NAL 标头。您是否发送没有 NAL 标头的帧?

    • 我没有看到您转移任何演示时间

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

    【讨论】:

    • ChrisBe 是对的,TCP 不是一个很好的网络层来传输实时视频。另外,您的 h264 数据包的来源是什么?也许它们与您的客户端不兼容。
    • 请看一下,我正在使用这个screenCast project on github。因为如果我将此流保存到 sdcard,它适用于我的 ubuntu 上的 gstreamer 应用程序或我的 android 上的 mxPlayer。
    • 你真的需要再看看源代码。发送的第一件事是带有视频尺寸的 HTTP_MESSAGE_TEMPLATE,将其过滤掉。如果选择 AVC,接下来发送 SPS 和 PPS。您需要使用 BUFFER_FLAG_CODEC_CONFIG 标志将其提供给解码器。我还建议您使用不同的方法来传输 H264 avc 帧,而不仅仅是普通的 tcp 写入和读取。如果您想对这个项目感到厌烦,请使用 VP8,因为提供的 IvfWriter 在 ivf format 中进行传输帧
    • 我同意你说的。我正在寻找rtsp。我将研究如何实现它并让你知道。 :)
    • @ChrisBe:ivf 也支持 h264。这就是截屏视频的用途。
    【解决方案2】:

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

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

    【讨论】:

    • 你应该删除'!'在您的代码中的!codecInfo.isEncoder() 中。请注意,您帖子中的 youtube 链接是“私人的”。
    • 我很抱歉。我已经公开了链接,现在你可以看到它。我也按照你说的做了修改。我用新代码编辑了问题。请看一下,这是new video 根据新代码更改。使用它自己返回的硬件解码器,解码变得更糟。我的情况是它返回“OMX.qcom.video.decoder.avc”
    • @neerajsinha 硬件解码器没有软件那么宽容。但无论如何,您需要解析 ifv 帧并将提取的缓冲区一一提供给 MediaCodec 实例。
    【解决方案3】:

    尽量减少出队的超时时间 (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 中读取 width, height 以确认它能够正确解码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-06
      • 1970-01-01
      • 2019-06-21
      • 2017-02-19
      • 2016-05-27
      • 2012-10-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多