You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 10及以上Xamarin.Android无FFmpeg/外部服务器转MP4/WebM为MP3

在Android 10+的Xamarin.Android中无需FFmpeg/外部服务器转MP4/WebM为MP3的方案

核心思路

直接用Android原生的MediaExtractor和MediaCodec API完成全程处理:MediaExtractor负责从MP4/WebM文件里剥离音频轨道,MediaCodec分别承担音频解码(得到PCM原始数据)和MP3编码(把PCM转成MP3格式)的工作,全程不需要第三方库或外部服务。

具体实现步骤

  1. 权限准备:确保应用拥有读取输入文件、写入输出文件的权限。Android 10+优先用MediaStore或存储访问框架(SAF)处理文件,避免直接操作路径;如果必须用传统路径访问,需申请MANAGE_EXTERNAL_STORAGE权限(仅限特定场景)。
  2. 初始化MediaExtractor:加载目标MP4/WebM文件,遍历所有轨道找到音频轨道并切换到该轨道。
  3. 配置解码器:根据音频轨道的格式信息,初始化MediaCodec解码器,设置参数后启动。
  4. 配置编码器:根据解码得到的PCM参数(采样率、声道数等),初始化MediaCodec MP3编码器,设置比特率等参数后启动。
  5. 数据流转处理:循环从MediaExtractor读取编码数据送入解码器,得到PCM数据后再送入编码器,获取编码后的MP3数据写入输出文件。
  6. 资源释放:转换完成后,关闭并释放MediaExtractor、MediaCodec解码器/编码器、文件流等资源,避免内存泄漏。

代码示例

using Android.Media;
using Android.OS;
using System.IO;

public async Task ConvertVideoToMp3Async(string inputFilePath, string outputFilePath)
{
    MediaExtractor extractor = new MediaExtractor();
    MediaCodec decoder = null;
    MediaCodec encoder = null;
    FileOutputStream outputStream = null;

    try
    {
        // 初始化MediaExtractor并定位音频轨道
        extractor.SetDataSource(inputFilePath);
        int audioTrackIndex = -1;
        for (int i = 0; i < extractor.TrackCount; i++)
        {
            MediaFormat trackFormat = extractor.GetTrackFormat(i);
            string mimeType = trackFormat.GetString(MediaFormat.KeyMime);
            if (mimeType.StartsWith("audio/"))
            {
                audioTrackIndex = i;
                break;
            }
        }
        if (audioTrackIndex == -1)
            throw new Exception("文件中未找到有效音频轨道");

        extractor.SelectTrack(audioTrackIndex);
        MediaFormat audioFormat = extractor.GetTrackFormat(audioTrackIndex);

        // 初始化音频解码器
        string decoderMimeType = audioFormat.GetString(MediaFormat.KeyMime);
        decoder = MediaCodec.CreateDecoderByType(decoderMimeType);
        decoder.Configure(audioFormat, null, null, 0);
        decoder.Start();

        // 初始化MP3编码器
        MediaFormat encoderFormat = MediaFormat.CreateAudioFormat("audio/mp3", 
            audioFormat.GetInteger(MediaFormat.KeySampleRate), 
            audioFormat.GetInteger(MediaFormat.KeyChannelCount));
        encoderFormat.SetInteger(MediaFormat.KeyBitRate, 128000); // 设置128kbps比特率
        encoder = MediaCodec.CreateEncoderByType("audio/mp3");
        encoder.Configure(encoderFormat, null, null, MediaCodecConfigFlags.Encode);
        encoder.Start();

        // 打开输出文件流
        outputStream = new FileOutputStream(outputFilePath);

        // 处理数据流转逻辑
        MediaCodec.BufferInfo decoderBufferInfo = new MediaCodec.BufferInfo();
        MediaCodec.BufferInfo encoderBufferInfo = new MediaCodec.BufferInfo();
        bool isExtractorDone = false;

        while (true)
        {
            // 向解码器输入数据
            if (!isExtractorDone)
            {
                int inputBufferId = decoder.DequeueInputBuffer(10000);
                if (inputBufferId >= 0)
                {
                    ByteBuffer inputBuffer = decoder.GetInputBuffer(inputBufferId);
                    int sampleSize = extractor.ReadSampleData(inputBuffer, 0);
                    long presentationTimeUs = extractor.SampleTime;
                    if (sampleSize < 0)
                    {
                        decoder.QueueInputBuffer(inputBufferId, 0, 0, 0, MediaCodecBufferFlags.EndOfStream);
                        isExtractorDone = true;
                    }
                    else
                    {
                        decoder.QueueInputBuffer(inputBufferId, 0, sampleSize, presentationTimeUs, 0);
                        extractor.Advance();
                    }
                }
            }

            // 从解码器读取PCM数据
            int decoderOutputBufferId = decoder.DequeueOutputBuffer(decoderBufferInfo, 10000);
            if (decoderOutputBufferId >= 0)
            {
                ByteBuffer outputBuffer = decoder.GetOutputBuffer(decoderOutputBufferId);
                if ((decoderBufferInfo.Flags & MediaCodecBufferFlags.EndOfStream) != 0)
                {
                    // 解码器输出完毕,向编码器发送结束信号
                    int encoderInputBufferId = encoder.DequeueInputBuffer(10000);
                    if (encoderInputBufferId >= 0)
                    {
                        encoder.QueueInputBuffer(encoderInputBufferId, 0, 0, 0, MediaCodecBufferFlags.EndOfStream);
                    }
                }
                else if (decoderBufferInfo.Size > 0)
                {
                    // 将PCM数据送入编码器
                    int encoderInputBufferId = encoder.DequeueInputBuffer(10000);
                    if (encoderInputBufferId >= 0)
                    {
                        ByteBuffer encoderInputBuffer = encoder.GetInputBuffer(encoderInputBufferId);
                        encoderInputBuffer.Clear();
                        outputBuffer.Position(decoderBufferInfo.Offset);
                        outputBuffer.Limit(decoderBufferInfo.Offset + decoderBufferInfo.Size);
                        encoderInputBuffer.Put(outputBuffer);
                        encoder.QueueInputBuffer(encoderInputBufferId, 0, decoderBufferInfo.Size, decoderBufferInfo.PresentationTimeUs, 0);
                    }
                }
                decoder.ReleaseOutputBuffer(decoderOutputBufferId, false);
            }

            // 从编码器读取MP3数据并写入文件
            int encoderOutputBufferId = encoder.DequeueOutputBuffer(encoderBufferInfo, 10000);
            if (encoderOutputBufferId >= 0)
            {
                ByteBuffer encoderOutputBuffer = encoder.GetOutputBuffer(encoderOutputBufferId);
                if ((encoderBufferInfo.Flags & MediaCodecBufferFlags.EndOfStream) != 0)
                {
                    break; // 编码完成,退出循环
                }
                if (encoderBufferInfo.Size > 0)
                {
                    byte[] mp3Data = new byte[encoderBufferInfo.Size];
                    encoderOutputBuffer.Position(encoderBufferInfo.Offset);
                    encoderOutputBuffer.Get(mp3Data);
                    outputStream.Write(mp3Data);
                }
                encoder.ReleaseOutputBuffer(encoderOutputBufferId, false);
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"转换失败: {ex.Message}");
        throw;
    }
    finally
    {
        // 释放所有原生资源
        extractor?.Release();
        decoder?.Stop();
        decoder?.Release();
        encoder?.Stop();
        encoder?.Release();
        outputStream?.Close();
    }
}

关键注意事项

  • 权限适配:Android 10+需遵循分区存储规则,若用MediaStore保存输出文件,要通过ContentResolver插入记录获取输出URI,不能直接用文件路径。
  • 参数匹配:MP3编码器的采样率、声道数必须和解码得到的PCM参数一致,否则会出现音质异常或编码失败。
  • 异常处理:要捕获MediaCodec初始化失败、文件读写错误等异常,避免应用崩溃。
  • 内存管理:操作ByteBuffer时要注意position和limit的设置,确保数据传输正确;所有原生资源必须在finally块中释放。

内容的提问来源于stack exchange,提问作者Peťo Build

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 03:10:35