Создавайте файлы mp4 на Android с помощью Jcodec

у меня возникли проблемы с записью файлов mp4 на Android с использованием MediaRecorder и Jcodec, вот мой код

public class SequenceEncoder {
    private final static String CLASSTAG = SequenceEncoder.class.getSimpleName();

    private SeekableByteChannel ch;

    private byte[] yuv = null;

    private ArrayList<ByteBuffer> spsList;
    private ArrayList<ByteBuffer> ppsList;

    private CompressedTrack outTrack;

    private int frameNo;
    private MP4Muxer muxer;

    ArrayList<ByteBuffer> spsListTmp = new ArrayList<ByteBuffer>();
    ArrayList<ByteBuffer> ppsListTmp = new ArrayList<ByteBuffer>();

    // Encoder
    private MediaCodec mediaCodec = null;

    public SequenceEncoder(File out) throws IOException {
        this.ch = NIOUtils.writableFileChannel(out);

        // Muxer that will store the encoded frames
        muxer = new MP4Muxer(ch, Brand.MP4);

        // Add video track to muxer
        outTrack = muxer.addTrackForCompressed(TrackType.VIDEO, 25);

        // Encoder extra data ( SPS, PPS ) to be stored in a special place of
        // MP4
        spsList = new ArrayList<ByteBuffer>();
        ppsList = new ArrayList<ByteBuffer>();
    }

    @SuppressWarnings("unchecked")
    public void encodeImage(ByteBuffer buffer, int width, int height) throws IOException {
        if (yuv == null) {
            int bufferSize = width * height * 3 / 2;

            yuv = new byte[bufferSize];

            int bitRate = bufferSize;
            int frameRate = 25;
            String mimeType = "video/avc";

            // "video/avc"
            mediaCodec = MediaCodec.createEncoderByType(mimeType);
            MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, width, height);
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); // 125000);
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);

            mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mediaCodec.start();
        }

        byte[] rgba = buffer.array();

        // Convert RGBA image to NV12 (YUV420SemiPlanar)
        Rgba2Yuv420.convert(rgba, yuv, width, height);

        synchronized (mediaCodec) {
        try {
            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();

            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(yuv);
                mediaCodec.queueInputBuffer(inputBufferIndex, 0,
                        yuv.length, 0, 0);
            }

            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(
                    bufferInfo, 0);

            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                byte[] outData = new byte[bufferInfo.size];
                outputBuffer.get(outData);

                ByteBuffer frameBuffer = ByteBuffer.wrap(outData);

                spsListTmp.clear();
                ppsListTmp.clear();

                H264Utils.encodeMOVPacket(frameBuffer, spsListTmp, ppsListTmp);

                if (!spsListTmp.isEmpty())
                    spsList = (ArrayList<ByteBuffer>) spsListTmp.clone();
                if (!ppsListTmp.isEmpty())
                    ppsList = (ArrayList<ByteBuffer>) ppsListTmp.clone();

                outTrack.addFrame(new MP4Packet(frameBuffer, frameNo, 25, 1,
                        frameNo, true, null, frameNo, 0));

                frameNo++;

                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mediaCodec.dequeueOutputBuffer(
                        bufferInfo, 0);
            }

            if (outputBufferIndex < 0)
                switch (outputBufferIndex) {
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    outputBuffers = mediaCodec.getOutputBuffers();
                    break;
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    break;
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    break;
                default:
                    break;
                }
            } catch (Exception e) {
            }
        }
    }

    public void finish() throws IOException {
        if (!ch.isOpen())
            return;

        if (mediaCodec != null) {
            mediaCodec.stop();
            mediaCodec.release();
        }

        outTrack.addSampleEntry(H264Utils.createMOVSampleEntry(spsList, ppsList));

        // Write MP4 header and finalize recording
        muxer.writeHeader();
        NIOUtils.closeQuietly(ch);

        ch.close();
    }
}

Как мы видим, Android MediaCodec ожидает YUV420SemiPlanar в качестве входного изображения, поэтому я даю ему правильное изображение. В результате у меня есть поврежденный файл mp4 с недопустимыми цветами, когда я открываю этот файл mp4 из AVCon, я вижу, что формат цвета в выходном файле - yuv420p, так что, может быть, проблема в этом? Пожалуйста, предложите, как это исправить.

Также есть еще вопрос, как добавить сжатый аудиопоток в мультиплексор, примеров не нашел.


person user2427841    schedule 28.05.2013    source источник


Ответы (2)


В Android 4.3 (API 18) есть две новые полезные функции.

Во-первых, класс MediaCodec принимает входные данные от Surface, поэтому все, что вы можете декодировать в Surface или визуализировать с помощью OpenGL ES, можно записать без необходимости возиться с цветовыми плоскостями YUV.

Во-вторых, новый класс MediaMuxer позволяет комбинировать аудио и H. 264 видео в файл .mp4.

Пример исходного кода (главным образом для аспектов видео) можно найти здесь.

person fadden    schedule 24.07.2013

YUV420SemiPlanar изображения 4x4 имеет формат, подобный YYYYYYYYYYYYYYYUVUVUVUV, а не YYYYYYYYYYYYYYYUUUUVVVV. Я могу получить файл mp4 с правильным цветом, используя Jcodec и MediaCodec на Android, после того, как я передал изображение с форматом.

У меня нет ответа насчет звука.

person gunyarakun    schedule 24.06.2013