高品质的流媒体codeC崩溃高品质、流媒体、codeC

2023-09-03 21:29:33 作者:﹎葬¦龙王¦

我用下面的code的(原指南):

I am decoding a h264 video stream with the following code (original guide):

public void configure(Surface surface, int width, int height, ByteBuffer csd0) {
        String VIDEO_FORMAT = "video/avc";
        if (mConfigured) {
            throw new IllegalStateException("Decoder is already configured");
        }
        MediaFormat format = MediaFormat.createVideoFormat(VIDEO_FORMAT, width, height);
        // little tricky here, csd-0 is required in order to configure the codec properly
        // it is basically the first sample from encoder with flag: BUFFER_FLAG_CODEC_CONFIG
        format.setByteBuffer("csd-0", csd0);
        try {
            mCodec = MediaCodec.createDecoderByType(VIDEO_FORMAT);
        } catch (IOException e) {
            throw new RuntimeException("Failed to create codec", e);
        }
        mCodec.configure(format, surface, null, 0);
        mCodec.start();
        mConfigured = true;
    }

    @SuppressWarnings("deprecation")
    public void decodeSample(byte[] data, int offset, int size, long presentationTimeUs, int flags) {
        if (mConfigured && mRunning) {
            int index = mCodec.dequeueInputBuffer(mTimeoutUs);
            if (index >= 0) {
                ByteBuffer buffer;
                // since API 21 we have new API to use
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    buffer = mCodec.getInputBuffers()[index];
                    buffer.clear();
                } else {
                    buffer = mCodec.getInputBuffer(index);
                }
                if (buffer != null) {
                    buffer.put(data, offset, size);
                    mCodec.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
                }
            }
        }
    }

    @Override
    public void run() {
        try {
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            while (mRunning) {
                if (mConfigured) {
                    int index = mCodec.dequeueOutputBuffer(info, mTimeoutUs);
                    if (index >= 0) {
                        // setting true is telling system to render frame onto Surface
                        mCodec.releaseOutputBuffer(index, true);
                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                            break;
                        }
                    }
                } else {
                    // just waiting to be configured, then decode and render
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException ignore) {
                    }
                }
            }
        } finally {
            if (mConfigured) {
                mCodec.stop();
                mCodec.release();
            }
        }
    }

我可以在中低质量的两个我的Nexus 6(API 22)和三星Galaxy核心(API 16)运行此。然而,当我切换到高画质(720p)的是约30帧崩溃后对三星(但没有被渲染到屏幕)。

I can run this on both my Nexus 6 (api 22) and Samsung galaxy core (api 16) on low and medium quality. However when I switch to high quality (720p) it crashes on the Samsung after about 30 frames (but nothing is rendered to the screen).

E/ACodec﹕ [OMX.qcom.video.decoder.avc] ERROR(0x8000100a)
E/MediaCodec﹕ Codec reported an error. (omx error 0x8000100a, internalError -2147483648)
[...]
W/System.err﹕ java.lang.IllegalStateException
W/System.err﹕ at android.media.MediaCodec.dequeueInputBuffer(Native Method)
W/System.err﹕ at com.test.stream.VideoDecoder$Worker.decodeSample(VideoDecoder.java:95)
W/System.err﹕ at com.test.stream.VideoDecoder.decodeSample(VideoDecoder.java:24)
W/System.err﹕ at com.test.stream.VideoThread.run(VideoThread.java:160)

上面的错误是出现的第一个错误,IllegalStateException异常被抛出之后每一帧。

The error above is the first error that appears, the IllegalStateException is afterwards thrown on each frame.

我的问题是,这是一个设备特定问题(因为:旧的API /设备,那么强大,等等),或者是什么其实是错误的? 我应该如何处理呢?

My question is, is this a device specific problem (because of: older api/device, less powerful, etc.) or is something actually wrong? and how should I deal with this?

推荐答案

有关我的Andr​​oid的H.264去codeR我做略有不同,您的设置。我认为使用更现代化的API级别。但对我来说,它看起来更像是这样的:

For my Android h.264 decoder i do it slightly different to your setup. I think your using more modern api level. But for me it looks more like this:

public void startDecoder() {
    // Initilize codec
    mediaCodec = MediaCodec.createDecoderByType("video/avc");
    mediaFormat = MediaFormat.createVideoFormat("video/avc", 0, 0);
    bufferInfo = new MediaCodec.BufferInfo();

    // STOPS unit-tests from crashing here from mocked out android
    if (mediaCodec != null) {
        mediaCodec.configure(mediaFormat, targetSurface, null, 0);
        mediaCodec.start();
        decoderThread = new Thread(this);
        decoderThread.start();
    }
}

//德codeR线程是指这个类,请问去codeR /渲染循环:

// Decoder Thread refers to this class which does the decoder/render loop:

public void run() {
    //mediaCodec input + output dequeue timeouts
    long kInputBufferTimeoutMs = 50;
    long kOutputBufferTimeoutMs = 50;

    while (running && mediaCodec != null) {
        synchronized (mediaCodec) {
            // stop if not running.
            if (!running || mediaCodec == null)
                break;

            // Only push in new data if there is data available in the queue
            if (naluSegmentQueue.size() > 0) {
                int inputBufferIndex = mediaCodec.dequeueInputBuffer(kInputBufferTimeoutMs);
                if (inputBufferIndex >= 0) {
                    NaluSegment segment = naluSegmentQueue.poll();
                    codecInputBufferAvailable(segment, mediaCodec, inputBufferIndex);
                }
            }

            // always check if output is available.
            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, kOutputBufferTimeoutMs);
            if (outputBufferIndex >= 0) {
                // Try and render first
                codecOuputBufferAvailable(mediaCodec, outputBufferIndex, bufferInfo);
            } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                // Subsequent data will conform to new format.
                // Can ignore if using getOutputFormat(outputBufferId)
                mediaFormat = mediaCodec.getOutputFormat();
            }
        }
    }
}

将数据放入去codeR包括的参数。我不试图用C​​SD-0/1网络流可以有变化的格式描述懒得和它容易就让它动态地回升。

To put data into the decoder including the parameters. I don't bother with trying to use the csd-0/1 network streams can have changing format descriptions and its easier to just let it be picked up dynamically.

private void codecInputBufferAvailable(NaluSegment segment, MediaCodec codec, int index) {
    int flags = (segment.getType() == NaluType.SPS
            || segment.getType() == NaluType.PPS
            || segment.getType() == NaluType.SUPP_ENHANCEMENT) ?
            MediaCodec.BUFFER_FLAG_CODEC_CONFIG : MediaCodec.BUFFER_FLAG_SYNC_FRAME;

    ByteBuffer[] buffers = codec.getInputBuffers();
    ByteBuffer buffer = buffers[index];
    // Can throw buffer overflow exception when buffer sizes are too small.
    try {
        buffer.put(segment.getBuffer());
        codec.queueInputBuffer(index, 0, segment.getBufferSize(), 0, flags);
    } catch(Exception e) {
        Log.e(TAG, "Failed to push buffer to decoder");
    }
}

重要:buffer.put(segment.getBuffer()); 的GetBuffer()在这里总是会返回一个4字节的 annexb 缓冲区。在Android德codeRS不明白3个字节的NAL单元。所以,如果你有一个3字节的NAL单元把它变成4个字节的魔法序列长度+ 1和 0×00,0×00,0×00,0×01 作为启动魔法序列缓冲区的其余部分应与放大器;缓冲器[headerLength]。

IMPORTANT: buffer.put(segment.getBuffer()); getBuffer() here always returns a 4 byte annexb buffer. The android decoders do not understand 3 byte nal units. So if you have a 3 byte nal unit turn it into 4 bytes magic sequence with length + 1 and 0x00, 0x00, 0x00, 0x01 as the start magic sequence the rest of the buffer should be &buffer[headerLength].

注意在try-catch这里这并没有给编译器警告,但它可以抛出一个缓冲的溢出异常在这里,如果你有一个非常大的有效载荷和字节的缓冲区太小。

Notice the try-catch here this doesn't give a compiler warning but it can throw a buffer overflow exception here if your have a very big payload and the byte-buffer is too small.

只要你解析你的NAL单元正常,这应该为你工作。但对于我来说,我注意到,NAL单元可以是3个或4个字节的神奇头。

So long as your parse out your NAL units correctly this should work for you. But for my case i noticed that the NAL units can be 3 or 4 bytes for the magic header.

/**
 * H264 is comprised of NALU segments.
 *
 * XXXX Y ZZZZZZZZ -> XXXX Y ZZZZZZZZ -> XXXX Y ZZZZZZZZ
 *
 * Each segment is comprised of:
 *
 * XXXX   -> Magic byte header (0x00, 0x00, 0x00, 0x01) NOTE: this can be either 3 of 4 bytes
 * Y      -> The Nalu Type
 * ZZZ... -> The Payload
 *
 * Notice there is no nalu length specified. To parse an nalu, you must
 * read until the next magic-byte-sequence AKA the next segment to figure
 * out the full nalu length
 **/
public static List<NaluSegment> parseNaluSegments(byte[] buffer) throws NaluBufferException {
    List<NaluSegment> segmentList = new ArrayList<>();
    if (buffer.length < 6) {
        return segmentList;
    }

    int lastStartingOffset = -1;
    for (int i = 0; i < buffer.length - 10; ++i) {
        **if (buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01)** {
            int naluType = (buffer[i+3] & 0x1F);
            NaluSegment segment = new NaluSegment(naluType, 3, i);

            **if (i > 0 && buffer[i-1] == 0x00)** {
                // This is actually a 4 byte segment
                int currentSegmentOffset = segment.getOffset();
                segment.setHeaderSize(4);
                segment.setOffset(currentSegmentOffset - 1);
            }
...

创建自己的NALU段的物体,不要忘记尾随NAL。

Create your own nalu-segment objects and don't forget the trailing NAL.

我希望这有助于。

 
精彩推荐