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格式)的工作,全程不需要第三方库或外部服务。
具体实现步骤
- 权限准备:确保应用拥有读取输入文件、写入输出文件的权限。Android 10+优先用
MediaStore或存储访问框架(SAF)处理文件,避免直接操作路径;如果必须用传统路径访问,需申请MANAGE_EXTERNAL_STORAGE权限(仅限特定场景)。 - 初始化MediaExtractor:加载目标MP4/WebM文件,遍历所有轨道找到音频轨道并切换到该轨道。
- 配置解码器:根据音频轨道的格式信息,初始化
MediaCodec解码器,设置参数后启动。 - 配置编码器:根据解码得到的PCM参数(采样率、声道数等),初始化
MediaCodecMP3编码器,设置比特率等参数后启动。 - 数据流转处理:循环从
MediaExtractor读取编码数据送入解码器,得到PCM数据后再送入编码器,获取编码后的MP3数据写入输出文件。 - 资源释放:转换完成后,关闭并释放
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
相关产品推荐
相关产品推荐

