繁体   English   中英

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

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

我正在使用MediaCodec做代码转换器。

我创建了两个mediacodec实例,一个用于解码,另一个用于编码。 我正在尝试将解码器outputBuffer直接发送到编码器inputBuffer中。

在编译和执行时似乎没有问题,而且运行速度很快。 但是输出的视频文件有问题,我检查了输出视频的元数据,它们没问题:比特率,帧率,分辨率...只有视频中的图像是这样的: 屏幕截图

我以为它出了点问题,但是我无法弄清楚...

我搜索了库和文档,然后使用Texture表面找到了一些示例代码,以渲染解码器输出数据并将数据传输到编码器中。 但是我认为这对我来说不是必需的。 因为我不需要编辑视频图像,所以我只需要做的就是更改比特率和分辨率,以减小文件的大小。

这是我项目中的核心代码:

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();
    }
}

这两个函数在两个不同的线程中运行。

FrameData是帧字节缓冲区和帧当前时间以及所需内容的简单存储

使用字节缓冲区输入时,有一些未定义的有关输入数据布局的细节。 当宽度不是16的倍数时,某些编码器希望将输入数据行的长度填充为16的倍数,而其他编码器将假定线长等于宽度,而没有额外的填充。

Android CTS测试(定义了人们在所有设备上期望的行为)用于从字节缓冲区输入进行编码的目的是故意仅测试分辨率为16的倍数,因为他们知道不同的硬件供应商会以不同的方式执行此操作,并且他们不想强制执行任何特殊处理。

通常,您不能假定解码器的输出将使用与编码器消耗的行大小相似的行大小。 解码器可以自由地(并且有些实际上可以)返回比实际内容大小大得多的宽度,并使用crop_left / crop_right字段来指示实际上打算将其可视的哪些部分。 因此,如果解码器这样做了,则除非考虑解码器和编码器实际使用的行大小,否则除非将数据逐行复制,否则无法将数据直接从解码器传递到编码器。

此外,您甚至无法假设解码器使用与编码器类似的像素格式。 许多高通设备将特殊的平铺像素格式用于解码器输出,而编码器输入是常规平面数据。 在这些情况下,您必须实现一个相当复杂的逻辑来对数据进行混洗,然后才能将其输入编码器。

使用纹理表面作为中间隐藏所有这些细节。 在您的用例中,听起来可能并非完全必要,但是它确实隐藏了解码器和编码器之间缓冲区格式的所有变化。

暂无
暂无

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

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