Forum Xamarin.Android

Video Cutting / Alternative to Android.FFMpeg

LippelLippel DEMember ✭✭

Hello, I need to cut videos on Android.
I've been using Android.FFMpeg but it turns out that it doesn't work for some 7.0 and higher Versions of Android (because of text relocations)

Do you know of an alternative that offers video cutting?

Best Answer

  • LippelLippel DE ✭✭
    edited June 2018 Accepted Answer

    I did indeed find an android java example on stackoverflow.

    EDIT: This is the post on S/O: https://stackoverflow.com/questions/42772008/how-to-trim-video-with-mediacodec
    Thanks a lot to the poster!

    I translated it to C# and so far it works flawlessly:

    public string Trim(int startMs, int endMs, string inputPath)
            {
                // Set up MediaExtractor to read from the source.
                MediaExtractor extractor = new MediaExtractor();
                extractor.SetDataSource(inputPath);
                int trackCount = extractor.TrackCount;
                // Set up MediaMuxer for the destination.
                MediaMuxer muxer;
                string outputPath = GetOutputPath(inputPath);
                muxer = new MediaMuxer(outputPath, MuxerOutputType.Mpeg4);
                // Set up the tracks and retrieve the max buffer size for selected
                // tracks.
                Dictionary<int, int> indexDict = new Dictionary<int, int>(trackCount);
                int bufferSize = -1;
                for (int i = 0; i < trackCount; i++)
                {
                    MediaFormat format = extractor.GetTrackFormat(i);
                    string mime = format.GetString(MediaFormat.KeyMime);
                    bool selectCurrentTrack = false;
                    if (mime.StartsWith("audio/"))
                    {
                        selectCurrentTrack = true;
                    }
                    else if (mime.StartsWith("video/"))
                    {
                        selectCurrentTrack = true;
                    }
                    if (selectCurrentTrack)
                    {
                        extractor.SelectTrack(i);
                        int dstIndex = muxer.AddTrack(format);
                        indexDict.Add(i, dstIndex);
                        if (format.ContainsKey(MediaFormat.KeyMaxInputSize))
                        {
                            int newSize = format.GetInteger(MediaFormat.KeyMaxInputSize);
                            bufferSize = newSize > bufferSize ? newSize : bufferSize;
                        }
                    }
                }
                if (bufferSize < 0)
                {
                    bufferSize = 1337; //TODO: I don't know what to put here tbh, it will most likely be above 0 at this point anyways :)
                }
                // Set up the orientation and starting time for extractor.
                MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever();
                retrieverSrc.SetDataSource(inputPath);
                string degreesString = retrieverSrc.ExtractMetadata(MetadataKey.VideoRotation);
                if (degreesString != null)
                {
                    int degrees = int.Parse(degreesString);
                    if (degrees >= 0)
                    {
                        muxer.SetOrientationHint(degrees);
                    }
                }
                if (startMs > 0)
                {
                    extractor.SeekTo(startMs * 1000, MediaExtractorSeekTo.ClosestSync);
                }
                // Copy the samples from MediaExtractor to MediaMuxer. We will loop
                // for copying each sample and stop when we get to the end of the source
                // file or exceed the end time of the trimming.
                int offset = 0;
                int trackIndex = -1;
                ByteBuffer dstBuf = ByteBuffer.Allocate(bufferSize);
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                try
                {
                    muxer.Start();
                    while (true)
                    {
                        bufferInfo.Offset = offset;
                        bufferInfo.Size = extractor.ReadSampleData(dstBuf, offset);
                        if (bufferInfo.Size < 0)
                        {
                            bufferInfo.Size = 0;
                            break;
                        }
                        else
                        {
                            bufferInfo.PresentationTimeUs = extractor.SampleTime;
                            if (endMs > 0 && bufferInfo.PresentationTimeUs > (endMs * 1000))
                            {
                                Console.WriteLine("The current sample is over the trim end time.");
                                break;
                            }
                            else
                            {
                                bufferInfo.Flags = ConvertMediaExtractorSampleFlagsToMediaCodecBufferFlags(extractor.SampleFlags);
                                trackIndex = extractor.SampleTrackIndex;
                                muxer.WriteSampleData(indexDict[trackIndex], dstBuf, bufferInfo);
                                extractor.Advance();
                            }
                        }
                    }
                    muxer.Stop();
    
                    //deleting the old file
                    //JFile file = new JFile(srcPath);
                    //file.Delete();
                }
                catch (IllegalStateException e)
                {
                    // Swallow the exception due to malformed source.
                    Console.WriteLine("The source video file is malformed");
                }
                finally
                {
                    muxer.Release();
                }
                return outputPath;
            }
    
            //Splits the string at the dot, separating the file name and the extension. then adding the "_trimmed" string between both
            private string GetOutputPath(string inputPath)
            {
                string[] parts = inputPath.Split('.');
                return $"{parts[0]}_trimmed.{parts[1]}";
            }
    
            private MediaCodecBufferFlags ConvertMediaExtractorSampleFlagsToMediaCodecBufferFlags(MediaExtractorSampleFlags mediaExtractorSampleFlag)
            {
                switch (mediaExtractorSampleFlag)
                {
                    case MediaExtractorSampleFlags.None:
                        return MediaCodecBufferFlags.None;
                    case MediaExtractorSampleFlags.Encrypted:
                        return MediaCodecBufferFlags.KeyFrame;
                    case MediaExtractorSampleFlags.Sync:
                        return MediaCodecBufferFlags.SyncFrame;
                    default:
                        throw new NotImplementedException("ConvertMediaExtractorSampleFlagsToMediaCodecBufferFlags");
                }
            }
    

    As far as I can tell is, it takes every frame of the video at the point of the first cut and put every following frame into a buffer until the point of the end cut.

Answers

  • Conor.MurphyConor.Murphy GBMember ✭✭

    Did you find a solution for this?

  • LippelLippel DEMember ✭✭
    edited June 2018 Accepted Answer

    I did indeed find an android java example on stackoverflow.

    EDIT: This is the post on S/O: https://stackoverflow.com/questions/42772008/how-to-trim-video-with-mediacodec
    Thanks a lot to the poster!

    I translated it to C# and so far it works flawlessly:

    public string Trim(int startMs, int endMs, string inputPath)
            {
                // Set up MediaExtractor to read from the source.
                MediaExtractor extractor = new MediaExtractor();
                extractor.SetDataSource(inputPath);
                int trackCount = extractor.TrackCount;
                // Set up MediaMuxer for the destination.
                MediaMuxer muxer;
                string outputPath = GetOutputPath(inputPath);
                muxer = new MediaMuxer(outputPath, MuxerOutputType.Mpeg4);
                // Set up the tracks and retrieve the max buffer size for selected
                // tracks.
                Dictionary<int, int> indexDict = new Dictionary<int, int>(trackCount);
                int bufferSize = -1;
                for (int i = 0; i < trackCount; i++)
                {
                    MediaFormat format = extractor.GetTrackFormat(i);
                    string mime = format.GetString(MediaFormat.KeyMime);
                    bool selectCurrentTrack = false;
                    if (mime.StartsWith("audio/"))
                    {
                        selectCurrentTrack = true;
                    }
                    else if (mime.StartsWith("video/"))
                    {
                        selectCurrentTrack = true;
                    }
                    if (selectCurrentTrack)
                    {
                        extractor.SelectTrack(i);
                        int dstIndex = muxer.AddTrack(format);
                        indexDict.Add(i, dstIndex);
                        if (format.ContainsKey(MediaFormat.KeyMaxInputSize))
                        {
                            int newSize = format.GetInteger(MediaFormat.KeyMaxInputSize);
                            bufferSize = newSize > bufferSize ? newSize : bufferSize;
                        }
                    }
                }
                if (bufferSize < 0)
                {
                    bufferSize = 1337; //TODO: I don't know what to put here tbh, it will most likely be above 0 at this point anyways :)
                }
                // Set up the orientation and starting time for extractor.
                MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever();
                retrieverSrc.SetDataSource(inputPath);
                string degreesString = retrieverSrc.ExtractMetadata(MetadataKey.VideoRotation);
                if (degreesString != null)
                {
                    int degrees = int.Parse(degreesString);
                    if (degrees >= 0)
                    {
                        muxer.SetOrientationHint(degrees);
                    }
                }
                if (startMs > 0)
                {
                    extractor.SeekTo(startMs * 1000, MediaExtractorSeekTo.ClosestSync);
                }
                // Copy the samples from MediaExtractor to MediaMuxer. We will loop
                // for copying each sample and stop when we get to the end of the source
                // file or exceed the end time of the trimming.
                int offset = 0;
                int trackIndex = -1;
                ByteBuffer dstBuf = ByteBuffer.Allocate(bufferSize);
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                try
                {
                    muxer.Start();
                    while (true)
                    {
                        bufferInfo.Offset = offset;
                        bufferInfo.Size = extractor.ReadSampleData(dstBuf, offset);
                        if (bufferInfo.Size < 0)
                        {
                            bufferInfo.Size = 0;
                            break;
                        }
                        else
                        {
                            bufferInfo.PresentationTimeUs = extractor.SampleTime;
                            if (endMs > 0 && bufferInfo.PresentationTimeUs > (endMs * 1000))
                            {
                                Console.WriteLine("The current sample is over the trim end time.");
                                break;
                            }
                            else
                            {
                                bufferInfo.Flags = ConvertMediaExtractorSampleFlagsToMediaCodecBufferFlags(extractor.SampleFlags);
                                trackIndex = extractor.SampleTrackIndex;
                                muxer.WriteSampleData(indexDict[trackIndex], dstBuf, bufferInfo);
                                extractor.Advance();
                            }
                        }
                    }
                    muxer.Stop();
    
                    //deleting the old file
                    //JFile file = new JFile(srcPath);
                    //file.Delete();
                }
                catch (IllegalStateException e)
                {
                    // Swallow the exception due to malformed source.
                    Console.WriteLine("The source video file is malformed");
                }
                finally
                {
                    muxer.Release();
                }
                return outputPath;
            }
    
            //Splits the string at the dot, separating the file name and the extension. then adding the "_trimmed" string between both
            private string GetOutputPath(string inputPath)
            {
                string[] parts = inputPath.Split('.');
                return $"{parts[0]}_trimmed.{parts[1]}";
            }
    
            private MediaCodecBufferFlags ConvertMediaExtractorSampleFlagsToMediaCodecBufferFlags(MediaExtractorSampleFlags mediaExtractorSampleFlag)
            {
                switch (mediaExtractorSampleFlag)
                {
                    case MediaExtractorSampleFlags.None:
                        return MediaCodecBufferFlags.None;
                    case MediaExtractorSampleFlags.Encrypted:
                        return MediaCodecBufferFlags.KeyFrame;
                    case MediaExtractorSampleFlags.Sync:
                        return MediaCodecBufferFlags.SyncFrame;
                    default:
                        throw new NotImplementedException("ConvertMediaExtractorSampleFlagsToMediaCodecBufferFlags");
                }
            }
    

    As far as I can tell is, it takes every frame of the video at the point of the first cut and put every following frame into a buffer until the point of the end cut.

  • Jingo21Jingo21 Member ✭✭

    I'm fairly new to programming android apps. So I would like to know if the following would work or am I wasting my time...

    I would like to build an App in Xamarin that can record RTSP from an IP camera. I was thinking of using Exoplayer to display the video on my phone and MediaMuxer to record it to the sdcard. I want to use opensource components.

    Is it possible to use your code, and change the input source to the "RTSP stream" instead of the "inputpath"
    Regards

  • LippelLippel DEMember ✭✭

    @Jingo21 Xamarin basically exposes all the Android APIs to you as C# types. So if you enter something like "android MediaExtractor" into your favorite search engine you will find the class that I am using in my code (well, the android version).
    If you add "xamarin" to the search-term, you will most likely find the documentation of the class I used in the code.

    From here you might know what you have to do to get an answer to your question.
    Tip: search for "android mediaextractor setdatasource rtsp"
    Thats what I just did for you and I found this interesting stack overflow comment: https://stackoverflow.com/a/53807413/7236414

    Why am I telling you this? Next time you need to know this, you might just hit up a search engine and find the already-written answer on the web.

Sign In or Register to comment.