4

I have found the answer for Java: https://stackoverflow.com/a/36357819/202179 and tried to port it to Xamarin.

Here is the code that I've made:

    const string COMPRESSED_AUDIO_FILE_MIME_TYPE = "audio/mp4a-latm";
    const int COMPRESSED_AUDIO_FILE_BIT_RATE = 64000; // 64kbps
    const int SAMPLING_RATE = 48000;
    const int BUFFER_SIZE = 48000;
    const int CODEC_TIMEOUT_IN_MS = 5000;

    void Compress()
    {
        var inputFile = new Java.IO.File(tempFileWavPath);
        var fis = new Java.IO.FileInputStream(inputFile);

        var outputFile = new Java.IO.File(fileM4APath);
        if (outputFile.Exists())
            outputFile.Delete();

        var mux = new MediaMuxer(outputFile.AbsolutePath, MuxerOutputType.Mpeg4);

        MediaFormat outputFormat = MediaFormat.CreateAudioFormat(COMPRESSED_AUDIO_FILE_MIME_TYPE, SAMPLING_RATE, 1);
        outputFormat.SetInteger(MediaFormat.KeyAacProfile, (int)MediaCodecProfileType.Aacobjectlc);
        outputFormat.SetInteger(MediaFormat.KeyBitRate, COMPRESSED_AUDIO_FILE_BIT_RATE);
        outputFormat.SetInteger(MediaFormat.KeyMaxInputSize, 16384);

        MediaCodec codec = MediaCodec.CreateEncoderByType(COMPRESSED_AUDIO_FILE_MIME_TYPE);
        codec.Configure(outputFormat, null, null, MediaCodecConfigFlags.Encode);
        codec.Start();

        MediaCodec.BufferInfo outBuffInfo = new MediaCodec.BufferInfo();
        byte[] tempBuffer = new byte[BUFFER_SIZE];
        var hasMoreData = true;
        double presentationTimeUs = 0;
        int audioTrackIdx = 0;
        int totalBytesRead = 0;
        int percentComplete = 0;
        do
        {
            int inputBufIndex = 0;
            while (inputBufIndex != -1 && hasMoreData)
            {
                inputBufIndex = codec.DequeueInputBuffer(CODEC_TIMEOUT_IN_MS);

                if (inputBufIndex >= 0)
                {
                    var dstBuf = codec.GetInputBuffer(inputBufIndex);
                    dstBuf.Clear();

                    int bytesRead = fis.Read(tempBuffer, 0, dstBuf.Limit());
                    if (bytesRead == -1)
                    { // -1 implies EOS
                        hasMoreData = false;
                        codec.QueueInputBuffer(inputBufIndex, 0, 0, (long)presentationTimeUs, MediaCodecBufferFlags.EndOfStream);
                    }
                    else
                    {
                        totalBytesRead += bytesRead;
                        dstBuf.Put(tempBuffer, 0, bytesRead);
                        codec.QueueInputBuffer(inputBufIndex, 0, bytesRead, (long)presentationTimeUs, 0);
                        presentationTimeUs = 1000000l * (totalBytesRead / 2) / SAMPLING_RATE;
                    }
                }
            }
            // Drain audio
            int outputBufIndex = 0;
            while (outputBufIndex != (int)MediaCodecInfoState.TryAgainLater)
            {
                outputBufIndex = codec.DequeueOutputBuffer(outBuffInfo, CODEC_TIMEOUT_IN_MS);
                if (outputBufIndex >= 0)
                {
                    var encodedData = codec.GetOutputBuffer(outputBufIndex);
                    encodedData.Position(outBuffInfo.Offset);
                    encodedData.Limit(outBuffInfo.Offset + outBuffInfo.Size);
                    if ((outBuffInfo.Flags & MediaCodecBufferFlags.CodecConfig) != 0 && outBuffInfo.Size != 0)
                    {
                        codec.ReleaseOutputBuffer(outputBufIndex, false);
                    }
                    else
                    {
                        mux.WriteSampleData(audioTrackIdx, encodedData, outBuffInfo);
                        codec.ReleaseOutputBuffer(outputBufIndex, false);
                    }
                }
                else if (outputBufIndex == (int)MediaCodecInfoState.OutputFormatChanged)
                {
                    outputFormat = codec.OutputFormat;
                    audioTrackIdx = mux.AddTrack(outputFormat);
                    mux.Start();
                }
            }
            percentComplete = (int)Math.Round(((float)totalBytesRead / (float)inputFile.Length()) * 100.0);
        } while (outBuffInfo.Flags != MediaCodecBufferFlags.EndOfStream);
        fis.Close();
        mux.Stop();
        mux.Release();
    }

This almost works as it converts the file, but the resulting file appears to be encoded too fast - the pitch is too high and speed is too high and the reproduction lasts shorter than expected.

It is likely that just some slight change is needed, but I am not sure what. Can anyone suggest?

Ivan Ičin
  • 9,672
  • 5
  • 36
  • 57

1 Answers1

2

I could reproduce the resulting file appears to be encoded too fast when i use the different size of SAMPLING_RATE.

For example, i download a wav file online. The Sampline Rate is 11025. If i use the original rate 48000 in the code, it would play too fast. When i use 11025, it would work.

enter image description here

So we need to know the Sampling Rate of the wav fille and then set it in the code.

const int SAMPLING_RATE = 11025;//44100, 48000
Wendy Zang - MSFT
  • 10,509
  • 1
  • 7
  • 17
  • Probably this can be used in some way, but my file does have 48000: Bit rate : 768 kb/s Channel(s) : 1 channel Sampling rate : 48.0 kHz – Ivan Ičin Aug 31 '21 at 10:47
  • Does it still be encoded too fast with the correct parameters? Have you try the other wav file? The same issue? – Wendy Zang - MSFT Sep 01 '21 at 06:29
  • my files are always encoded by the app so they come with same parameters. I have checked them on the PC to be 48KHz. I can possibly try this trick to see if some number produces reasonable results, but that's a bit of guessing and I will never know if it is actually 1:1 encoding... – Ivan Ičin Sep 01 '21 at 08:10
  • Maybe the encoding cause this. If you have something new, glad to let me know. – Wendy Zang - MSFT Sep 06 '21 at 06:49