android-studioandroid-mediacodec

How to compress video with Android Media Codec in android studio kotlin


the next line causes an error invalid trackIndex but it is 0:

mediaMuxer.writeSampleData(trackIndex, compressedBuffer, bufferInfo)

Here is my full fun:

private fun reduceVideoSize(inputDescriptor: FileDescriptor, outputDescriptor: FileDescriptor, compressionPercentage: Int) {
        lifecycleScope.launch {
            Log.d("reduceVideoSize", "STARTED")
            val mediaExtractor = MediaExtractor()
            mediaExtractor.setDataSource(inputDescriptor)
            val trackIndex = selectTrack(mediaExtractor, false)
            Log.d("TRACK INDEX", trackIndex.toString())
            mediaExtractor.selectTrack(trackIndex)
            //val mediaFormat = mediaExtractor.getTrackFormat(trackIndex)
            val mediaFormat = MediaFormat.createVideoFormat("video/avc", 1920, 1080)
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 2000000)
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
            mediaFormat.setInteger(
                MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
            )
            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
            Log.d("MediaFormat", mediaFormat.toString())
            val mimeType = mediaFormat.getString(MediaFormat.KEY_MIME)
            Log.d("MIME TYPE", mimeType.toString())
            val mediaCodec = MediaCodec.createEncoderByType(mimeType!!)
            mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
            //val inputSurface = mediaCodec.createInputSurface()
            mediaCodec.start()
            val mediaMuxer = MediaMuxer(outputDescriptor, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
            val bufferInfo = MediaCodec.BufferInfo()
            var isEOS = false
            val timeoutUs = 10000L
            while (!isEOS) {
                val inputBufferIndex = mediaCodec.dequeueInputBuffer(timeoutUs)
                if (inputBufferIndex >= 0) {
                    val inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex)
                    val sampleSize = mediaExtractor.readSampleData(inputBuffer!!, 0)
                    if (sampleSize < 0) {
                        mediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                        isEOS = true
                    } else {
                        mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleSize, mediaExtractor.sampleTime, 0)
                        mediaExtractor.advance()
                    }
                }
                val outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeoutUs)
                if (outputBufferIndex >= 0) {
                    val outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex)
                    outputBuffer?.let {
                        val bufferData = ByteArray(bufferInfo.size)
                        it.get(bufferData)
                        val compressedSize = bufferData.size * compressionPercentage / 100
                        val compressedBuffer = ByteBuffer.allocate(compressedSize)
                        compressedBuffer.put(bufferData, 0, compressedSize)
                        Log.d("TRACK INDEX", trackIndex.toString())
                        //next row makes an error trackIndex is invalid (0)
                        mediaMuxer.writeSampleData(trackIndex, compressedBuffer, bufferInfo)
                    }
                    mediaCodec.releaseOutputBuffer(outputBufferIndex, false)
                }
                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
                    isEOS = true
                }
            }
            mediaMuxer.stop()
            mediaMuxer.release()
            mediaExtractor.release()
            mediaCodec.stop()
            mediaCodec.release()

        }

    }

    private fun selectTrack(extractor: MediaExtractor, audio: Boolean): Int {
        val numTracks = extractor.trackCount
        Log.d("NUMTRACKS", numTracks.toString())
        for (i in 0 until numTracks) {
            val format = extractor.getTrackFormat(i)
            val mime = format.getString(MediaFormat.KEY_MIME)
            if (audio) {
                if (mime.let{it!!.startsWith("audio/")}) {
                    Log.d("TRACK $i", "got audiotrack")
                    return i
                }
            } else {
                if (mime.let{it!!.startsWith("video/")}) {
                    Log.d("TRACK $i", "got videotrack")
                    return i
                }
            }
        }
        return -1
    }

Solution

  • Looks like the media muxer is not setup properly with the added tracks. Here is more information: https://developer.android.com/reference/android/media/MediaMuxer

    This code is from the documentation just in case.

       MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);
       // SetUp Video/Audio Tracks.
       MediaFormat audioFormat = new MediaFormat(...);
       MediaFormat videoFormat = new MediaFormat(...);
       int audioTrackIndex = muxer.addTrack(audioFormat);
       int videoTrackIndex = muxer.addTrack(videoFormat);
     
       // Setup Metadata Track
       MediaFormat metadataFormat = new MediaFormat(...);
       metadataFormat.setString(KEY_MIME, "application/gyro");
       int metadataTrackIndex = muxer.addTrack(metadataFormat);
     
       muxer.start();
       while(..) {
           // Allocate bytebuffer and write gyro data(x,y,z) into it.
           ByteBuffer metaData = ByteBuffer.allocate(bufferSize);
           metaData.putFloat(x);
           metaData.putFloat(y);
           metaData.putFloat(z);
           BufferInfo metaInfo = new BufferInfo();
           // Associate this metadata with the video frame by setting
           // the same timestamp as the video frame.
           metaInfo.presentationTimeUs = currentVideoTrackTimeUs;
           metaInfo.offset = 0;
           metaInfo.flags = 0;
           metaInfo.size = bufferSize;
           muxer.writeSampleData(metadataTrackIndex, metaData, metaInfo);
       };
       muxer.stop();
       muxer.release();
     }
    

    Check the Setup Video/Audio Tracks. section so that you can add those and use them later in your code.