Android 从3gpp生成音频图形

Android 从3gpp生成音频图形,android,audio,xamarin,xamarin.android,Android,Audio,Xamarin,Xamarin.android,我写的录音机作为应用程序的一部分,它也有音频图。要录制和显示实时音频图非常简单,只需每隔X毫秒调用MediaRecorder.getMaxAmplium(),并基于表示音频图的更新画布 问题是,我想在打开一个录音时使用相同的音频图形,所以现在我不能使用.getMaxAmplium()方法,因为我需要完全播放录音才能生成图形,这会花费太多时间,而且很愚蠢 如果录制输出是.wav,这将是非常简单的,有很多资料说明如何做,但是MediaRecorder不支持.wav,我真的不想在我的应用程序中嵌入完整

我写的录音机作为应用程序的一部分,它也有音频图。要录制和显示实时音频图非常简单,只需每隔X毫秒调用MediaRecorder.getMaxAmplium(),并基于表示音频图的更新画布

问题是,我想在打开一个录音时使用相同的音频图形,所以现在我不能使用.getMaxAmplium()方法,因为我需要完全播放录音才能生成图形,这会花费太多时间,而且很愚蠢

如果录制输出是.wav,这将是非常简单的,有很多资料说明如何做,但是MediaRecorder不支持.wav,我真的不想在我的应用程序中嵌入完整的ffmpeg和包装,只是为了这个小功能将3gpp解码为wav


我的选择是什么?

使用
MediaExtractor
MediaCodec
您可以将
3gp
(或任何其他受支持的mime类型)解码为一系列PCM-16位(mime
音频/raw
)缓冲区,并从中进行子采样,以“可绘制”的采样率获得所需的振幅图

这是一个在输入/输出缓冲区上使用同步处理的示例,因此在非UI线程上运行它

同步PCM-16位
MediaCodec
示例:
var file=new Java.IO.file(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryDownloads),“someaudiofile.mp3”);
if(file.CanRead())
{
var mediatextractor=新的mediatextractor();
SetDataSource(file.ToString());
mediaExtractor.SelectTrack(0);//哪个曲目?在本例中假设为single/mono
var mediaFormat=mediaExtractor.GetTrackFormat(0);
var mime=mediaFormat.GetString(mediaFormat.keymie);
var mediaCodec=mediaCodec.CreateDecoderByType(mime);
配置(mediaFormat,null,null,MediaCodeConfigFlags.None);
mediaCodec.Start();
var bufferInfo=new MediaCodec.bufferInfo();
var inputDone=false;
while(true)
{
if(!inputDone)//处理输入流并将其排队以进行输出处理
{
int-inputBufferIndex=mediaCodec.DequeueInputBuffer(10000);
如果(inputBufferIndex>=0)
{
var inputBuffer=mediaCodec.GetInputBuffer(inputBufferIndex);
int chunkSize=mediaExtractor.ReadSampleData(inputBuffer,0);
Debug(“SO“,$”输入缓冲区:{inputBufferIndex}”);
如果(chunkSize=0)
{
Debug(“SO“,$”输出缓冲区:{outputBufferIndex}”);
如果(bufferInfo.Size!=0)
{
var outputBuffer=mediaCodec.GetOutputBuffer(outputBufferIndex);//PCM 16位输出
var outpuFormat=mediaCodec.GetOutputFormat(outputBufferIndex);
输出缓冲区位置(0);
//!!!根据显示所需的采样率对缓冲区进行子采样
var pcm16bitBuffer=outputBuffer.AsShortBuffer();
while(pcm16bitBuffer.HasRemaining)
{
var x=pcm16bitBuffer.Get();
//存储先前的值和avg./max/…以供以后根据某些子采样率显示
}
pcm16bitBuffer.Dispose();
mediaCodec.ReleaseOutputBuffer(outputBufferIndex,false);
if(bufferInfo.Flags.HasFlag(MediaCodecBufferFlags.EndOfStream))
打破
}
其他的
打破
}
else if(outputBufferIndex==-2)
{
调试(“所以”,“输出缓冲区还不可用,输入更多”);
}
}
mediaCodec.Stop();
mediaCodec.Release();
}
注意:也有异步方法可用,请参阅适用于Android API级别的
MediaCodec
文档,了解适用于应用程序目标受众的方法

使用上述方法输出样本:


回复:

很抱歉回复太晚,但谢谢!这是我第一次处理音频处理,所以我有点忙得不可开交。还有一件事,我该如何将采样率应用于此?只需从列表中选取元素,跳过{Sample rate}之后的元素数?@EdgarasAusvicas您可以跳过X个样本数,这可能会在图形中出现奇怪的小故障,或者创建峰值输出的移动平均值,然后每X个样本取一个平均值样本(我倾向于此),但最终这取决于您需要图形查看波形的准确性。
var file = new Java.IO.File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryDownloads), "someaudiofile.mp3");
if (file.CanRead())
{
    var mediaExtractor = new MediaExtractor();
    mediaExtractor.SetDataSource(file.ToString());
    mediaExtractor.SelectTrack(0); // which track? lets assume single/mono for this example
    var mediaFormat = mediaExtractor.GetTrackFormat(0);
    var mime = mediaFormat.GetString(MediaFormat.KeyMime);
    var mediaCodec = MediaCodec.CreateDecoderByType(mime);
    mediaCodec.Configure(mediaFormat, null, null, MediaCodecConfigFlags.None);
    mediaCodec.Start();
    var bufferInfo = new MediaCodec.BufferInfo();
    var inputDone = false;
    while (true)
    {
        if (!inputDone) // process input stream and queue it up for output processing
        {
            int inputBufferIndex = mediaCodec.DequeueInputBuffer(10000);
            if (inputBufferIndex >= 0)
            {
                var inputBuffer = mediaCodec.GetInputBuffer(inputBufferIndex);
                int chunkSize = mediaExtractor.ReadSampleData(inputBuffer, 0);
                Log.Debug("SO", $"Input Buffer: {inputBufferIndex}");
                if (chunkSize <= 0)
                {
                    mediaCodec.QueueInputBuffer(inputBufferIndex, 0, 0, 0L, MediaCodecBufferFlags.EndOfStream);
                    inputDone = true;
                }
                else
                {
                    mediaCodec.QueueInputBuffer(inputBufferIndex, 0, chunkSize, mediaExtractor.SampleTime, MediaCodecBufferFlags.None);
                    mediaExtractor.Advance();
                }
            }
        }

        int outputBufferIndex = mediaCodec.DequeueOutputBuffer(bufferInfo, 1000000);
        if (outputBufferIndex >= 0)
        {
            Log.Debug("SO", $"Output Buffer: {outputBufferIndex}");
            if (bufferInfo.Size != 0)
            {
                var outputBuffer = mediaCodec.GetOutputBuffer(outputBufferIndex); // PCM 16-bit output
                var outpuFormat = mediaCodec.GetOutputFormat(outputBufferIndex);
                outputBuffer.Position(0);
                // !!! Sub-sample the buffer based upon your needed sampling rate for display
                var pcm16bitBuffer = outputBuffer.AsShortBuffer();
                while (pcm16bitBuffer.HasRemaining)
                {
                    var x = pcm16bitBuffer.Get();
                    // store the prior values and avg./max/... them for later display based upon some subsampling rate
                }
                pcm16bitBuffer.Dispose();
                mediaCodec.ReleaseOutputBuffer(outputBufferIndex, false);
                if (bufferInfo.Flags.HasFlag(MediaCodecBufferFlags.EndOfStream))
                    break;
            }
            else
                break;
        }
        else if (outputBufferIndex == -2)
        {
            Log.Debug("SO", "Output buffer is not available yet, feed more input");
        }
    }
    mediaCodec.Stop();
    mediaCodec.Release();
}