简体   繁体   English

通过MediaCodec直接对h264视频进行转码而无需纹理渲染

[英]Transcode h264 video by MediaCodec directly withou texture render

I'm doing a transcoder using MediaCodec. 我正在使用MediaCodec做代码转换器。

I created two mediacodec instance, one is for decoding and another is for encoding. 我创建了两个mediacodec实例,一个用于解码,另一个用于编码。 I'm trying to send decoders outputBuffer directly into encoders inputBuffer. 我正在尝试将解码器outputBuffer直接发送到编码器inputBuffer中。

It seems has no problem while compiling and executing.And it runs quickly. 在编译和执行时似乎没有问题,而且运行速度很快。 But the output video file has something wrong.I checked the metadata of the output video and they are all right : bitrate, framerate, resolution ...Only the images in the video is wrong like this: screen shot 但是输出的视频文件有问题,我检查了输出视频的元数据,它们没问题:比特率,帧率,分辨率...只有视频中的图像是这样的: 屏幕截图

I thought it has somethings wrong,but I cannot figure it out... 我以为它出了点问题,但是我无法弄清楚...

I searched libraries and documents, and I found some sample codes using Texture surface to render the decoder output data and tranfer the data into the encoder. 我搜索了库和文档,然后使用Texture表面找到了一些示例代码,以渲染解码器输出数据并将数据传输到编码器中。 But I thought it should not be neccessary for me. 但是我认为这对我来说不是必需的。 Because I dont need to edit images of the video.What I only need to do is changing the bitrate and resolution to make the file's size smaller. 因为我不需要编辑视频图像,所以我只需要做的就是更改比特率和分辨率,以减小文件的大小。

here is the core code in my project: 这是我项目中的核心代码:

private void decodeCore() {
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    int frameCount = 0;
    while (mDecodeRunning) {

        int inputBufferId = mDecoder.dequeueInputBuffer(50);
        if (inputBufferId >= 0) {
            // fill inputBuffers[inputBufferId] with valid data
            int sampleSize = mExtractor.readSampleData(mDecodeInputBuffers[inputBufferId], 0);
            if (sampleSize >= 0) {
                long time = mExtractor.getSampleTime();
                mDecoder.queueInputBuffer(inputBufferId, 0, sampleSize, time, 0);
            } else {
                mDecoder.queueInputBuffer(inputBufferId, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            }

            mExtractor.advance();
        }

        int outputBufferId = mDecoder.dequeueOutputBuffer(bufferInfo, 50);
        if (outputBufferId >= 0) {

            FrameData data = mFrameDataQueue.obtain();
            //wait until queue has space to push data
            while (data == null) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                data = mFrameDataQueue.obtain();
            }

            data.data.clear();
            data.size = 0;
            data.offset = 0;
            data.flag = 0;
            data.frameTimeInUs = bufferInfo.presentationTimeUs;

            // outputBuffers[outputBufferId] is ready to be processed or rendered.
            if (bufferInfo.size > 0) {
                ByteBuffer buffer = mDecodeOutputBuffers[outputBufferId];

                buffer.position(bufferInfo.offset);
                buffer.limit(bufferInfo.offset + bufferInfo.size);

                data.data.put(buffer);
                data.data.flip();

                data.size = bufferInfo.size;
                data.frameIndex = frameCount++;

            }

            data.flag = bufferInfo.flags;

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                Log.d("bingbing_transcode", "decode over! frames:" + (frameCount - 1));
                mDecodeRunning = false;
            }

            mFrameDataQueue.pushToQueue(data);
            mDecoder.releaseOutputBuffer(outputBufferId, false);
            Log.d("bingbing_transcode", "decode output:\n frame:" + (frameCount - 1) + "\n" + "size:" + bufferInfo.size);
        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            mDecodeOutputBuffers = mDecoder.getOutputBuffers();
        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // Subsequent data will conform to new format.
            mDecodeOutputVideoFormat = mDecoder.getOutputFormat();
            configureAndStartEncoder();
        }
    }

    mDecoder.stop();
    mDecoder.release();
}

private void encodeCore() {
    int trackIndex = 0;
    boolean muxerStarted = false;
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    int frameCount = 0;
    while (mEncodeRunning) {
        int inputBufferId = mEncoder.dequeueInputBuffer(50);
        if (inputBufferId >= 0) {
            FrameData data = mFrameDataQueue.pollFromQueue();
            //wait until queue has space to push data
            while (data == null) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                data = mFrameDataQueue.obtain();
            }

            if (data.size > 0) {
                ByteBuffer inputBuffer = mEncodeInputBuffers[inputBufferId];
                inputBuffer.clear();
                inputBuffer.put(data.data);
                inputBuffer.flip();
            }
            mEncoder.queueInputBuffer(inputBufferId, 0, data.size, data.frameTimeInUs, data.flag);
            mFrameDataQueue.recycle(data);
        }

        int outputBufferId = mEncoder.dequeueOutputBuffer(bufferInfo, 50);
        if (outputBufferId >= 0) {
            // outputBuffers[outputBufferId] is ready to be processed or rendered.
            ByteBuffer encodedData = mEncodeOutputBuffers[outputBufferId];

            if (bufferInfo.size > 0) {
                if (encodedData == null) {
                    throw new RuntimeException("encoderOutputBuffer " + outputBufferId + " was null");
                }

                if (!muxerStarted) {
                    throw new RuntimeException("muxer hasn't started");
                }

                frameCount++;
            }
            // adjust the ByteBuffer values to match BufferInfo (not needed?)
            encodedData.position(bufferInfo.offset);
            encodedData.limit(bufferInfo.offset + bufferInfo.size);

            mMuxer.writeSampleData(trackIndex, encodedData, bufferInfo);

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                Log.d("bingbing_transcode", "encode over! frames:" + (frameCount - 1));
                mEncodeRunning = false;
            }

            mEncoder.releaseOutputBuffer(outputBufferId, false);
            Log.d("bingbing_transcode", "encode output:\n frame:" + (frameCount - 1) + "\n" + "size:" + bufferInfo.size);

        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            mEncodeOutputBuffers = mEncoder.getOutputBuffers();
        } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // should happen before receiving buffers, and should only happen once
            if (muxerStarted) {
                throw new RuntimeException("format changed twice");
            }

            MediaFormat newFormat = mEncoder.getOutputFormat();
            Log.d("bingbing_transcode", "encoder output format changed: " + newFormat);

            // now that we have the Magic Goodies, start the muxer
            trackIndex = mMuxer.addTrack(newFormat);
            mMuxer.start();
            muxerStarted = true;
            mEncodeOutputVideoFormat = newFormat;
        }
    }


    mEncoder.stop();
    mEncoder.release();
    if (muxerStarted) {
        mMuxer.stop();
        mMuxer.release();
    }
}

these two functions run in two different threads. 这两个函数在两个不同的线程中运行。

FrameData is a simple storage of frame bytebuffer and frame present time and something needed FrameData是帧字节缓冲区和帧当前时间以及所需内容的简单存储

When using bytebuffer input, there are a few details that are undefined about the input data layout. 使用字节缓冲区输入时,有一些未定义的有关输入数据布局的细节。 When the width isn't a multiple of 16, some encoders want to have the input data row length padded to a multiple of 16, while others will assume a line length equal to the width, with no extra padding. 当宽度不是16的倍数时,某些编码器希望将输入数据行的长度填充为16的倍数,而其他编码器将假定线长等于宽度,而没有额外的填充。

The Android CTS tests (which define what behaviour one can expect across all devices) for encoding from bytebuffer inputs intentionally only test resolutions that are a multiple of 16, since they know different hardware vendors do this differently, and they didn't want to enforce any particular handling. Android CTS测试(定义了人们在所有设备上期望的行为)用于从字节缓冲区输入进行编码的目的是故意仅测试分辨率为16的倍数,因为他们知道不同的硬件供应商会以不同的方式执行此操作,并且他们不想强制执行任何特殊处理。

You can't generally assume that the decoder output would use a similar row size as what the encoder consumes either. 通常,您不能假定解码器的输出将使用与编码器消耗的行大小相似的行大小。 The decoder is free to (and some actually do) return a significantly larger width than the actual content size, and use the crop_left/crop_right fields for indicating what parts of it actually are intended to be visible. 解码器可以自由地(并且有些实际上可以)返回比实际内容大小大得多的宽度,并使用crop_left / crop_right字段来指示实际上打算将其可视的哪些部分。 So in case the decoder did that, you can't pass the data straight from the decoder to the encoder unless you copy it line by line, taking into account the actual line sizes used by the decoder and encoder. 因此,如果解码器这样做了,则除非考虑解码器和编码器实际使用的行大小,否则除非将数据逐行复制,否则无法将数据直接从解码器传递到编码器。

Additionally, you can't even assume that the decoder uses a similar pixel format as the encoder. 此外,您甚至无法假设解码器使用与编码器类似的像素格式。 Many qualcomm devices use a special tiled pixel format for the decoder output, while the encoder input is normal planar data. 许多高通设备将特殊的平铺像素格式用于解码器输出,而编码器输入是常规平面数据。 In these cases, you'd have to implement a pretty complex logic for unshuffling the data before you can feed it into the encoder. 在这些情况下,您必须实现一个相当复杂的逻辑来对数据进行混洗,然后才能将其输入编码器。

Using a texture surface as intermediate hides all of these details. 使用纹理表面作为中间隐藏所有这些细节。 It might not sound completely necessary for your use case, but it does hide all the variation in buffer formats between decoder and encoder. 在您的用例中,听起来可能并非完全必要,但是它确实隐藏了解码器和编码器之间缓冲区格式的所有变化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM