通过MediaCodec直接使用纹理渲染转换h264视频

时间:2016-11-11 04:38:09

标签: android video mediacodec transcode

我正在使用MediaCodec进行转码器。

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

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

我认为它有些不对劲,但我无法弄明白......

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

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

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是帧字节缓冲区和帧存在时间的简单存储,需要

1 个答案:

答案 0 :(得分:0)

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

Android CTS测试(定义所有设备可以预期的行为)从bytebuffer输入编码故意只测试16的倍数,因为他们知道不同的硬件供应商这样做不同,他们没有我想强制执行任何特定的处理。

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

此外,您甚至不能假设解码器使用与编码器类似的像素格式。许多高通设备使用特殊的平铺像素格式作为解码器输出,而编码器输入是普通的平面数据。在这些情况下,您必须实现一个非常复杂的逻辑,以便在将数据输入编码器之前对数据进行取消组合。

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