Android 上的 MP3 解码
Posted
技术标签:
【中文标题】Android 上的 MP3 解码【英文标题】:MP3 Decoding on Android 【发布时间】:2011-01-30 11:19:13 【问题描述】:我们正在为 android 手机实施一个程序,该程序可以播放来自互联网的音频流。以下是我们的大致做法:
-
下载自定义加密格式。
解密以获得常规 MP3 数据块。
将 MP3 数据解码为内存缓冲区中的原始 PCM 数据。
将原始 PCM 数据通过管道传输到 AudioTrack
到目前为止,我们的目标设备是 Droid 和 Nexus One。在 Nexus One 上一切正常,但在 Droid 上 MP3 解码太慢了。如果我们将 Droid 置于负载下,音频播放就会开始跳过。我们不允许将 MP3 数据解码到 SD 卡,但我知道这不是我们的问题。
我们没有编写自己的 MP3 解码器,而是使用了 MPADEC (http://sourceforge.net/projects/mpadec/)。它是免费的,并且很容易与我们的程序集成。我们用 NDK 编译它。
在使用各种分析工具进行详尽分析后,我们确信是这个解码器落后了。
以下是我们正在考虑的选项:
找到另一个我们可以使用 Android NDK 编译的 MP3 解码器。此 MP3 解码器必须进行优化以在移动 ARM 设备上运行,或者可能使用仅整数数学或其他一些优化来提高性能。
由于内置的 Android MediaPlayer 服务将获取 URL,我们可以在我们的程序中实现一个小型 HTTP 服务器,并为 MediaPlayer 提供解密的 MP3。这样我们就可以利用内置的 MP3 解码器。
通过 NDK 访问内置 MP3 解码器。我不知道这是否可能。
有人对我们可以做些什么来加快 MP3 解码有任何建议吗?
-- 罗伯斯
【问题讨论】:
在您的选项 #2 中,我希望 HTTP 开销会淹没您使用内置MediaPlayer
流支持所获得的收益。
如果我没记错的话,内置的MediaPlayer
也可以从任何content://
URI 流式传输,这实际上为您提供了一个可以写入的管道。
@jleedev:你能详细说明一下吗?听起来很有趣!
@Rob 对您最终选择的方法非常感兴趣。现在正在研究同样的情况。
【参考方案1】:
执行此操作的正确方法是构建您自己的固件并作为自定义 OpenCORE 编解码器的一部分进行解密。当然,这会将您限制在可以安装该固件的设备上。
请记住,剩下的部分是推测性的。我实际上需要做一些类似于你所描述的事情,但我没有时间留出几个月来解决这个问题。所以,我将以我如何解决问题的方式来描述这一点。
一种解决方案是 twk 的答案中描述的解决方案。您不必使用 SD 卡,但您可能必须在应用程序本地文件存储 (getFilesDir()
) 中有一个全球可读的临时文件。下载第一个块,解密它,将它写成一个完整的世界可读的 MP3 文件(但具有适当的模糊目录/路径),然后通过setDataSource()
将其交给MediaPlayer
。在播放时,您下载/解密并设置第二个MediaPlayer
实例,它会在第一个实例结束后立即开始播放,以实现尽可能无缝的过渡。然后您重置第一个 MediaPlayer
并将其与您的第三个块一起重用,在两者之间进行乒乓操作。
jleedev 的评论中有一个相关的解决方案。这几乎是一回事,只是您通过ContentProvider
提供FileDescriptor
。这有一个选项让您使用套接字,可能让您避免临时文件。但是,ContentProvider
本身必须是可公开访问的,因此具有不明确目录的临时文件实际上可能更私密。
如果您担心这些东西可以被其他进程读取,请理解MediaPlayer
本身(或者更确切地说,OpenCORE 子系统)在另一个进程中。此外,您建议的 HTTP 服务器在设备上也是全球可读的。因此,如果您打算让MediaPlayer
进行解码,那么通过默默无闻的安全性是您唯一可行的选择。
AFAIK,NDK 不授予对 OpenCORE 的访问权限,尽管我承认 NDK 经验有限,所以我可能错了。当然还有其他可用的 MP3 解码器(ffmpeg
/mplayer
等),不过这些转换成 NDK 库的难易程度尚不清楚。
因此,这真的归结为您要防御的对象。如果你想保护用户,你可能不得不自己解码,不知何故。
【讨论】:
MediaPlayer.create(context, uri) ??【参考方案2】:MAD 是一个仅限整数运算的解码器库,似乎备受推崇。
(将数据解码到 SD 卡上不会减慢您的速度吗?)
【讨论】:
请注意,MAD 在 GPL 下。【参考方案3】:这个我没试过,但是我相信你可以给媒体播放器一个文件描述符:
public void setDataSource (FileDescriptor fd, long offset, long length)
也许您可以将解密后的 mp3 写入文件并使用文件描述符播放。假设您提前知道文件长度,这甚至可能适用于流式传输。如果它有效,它会比做一个本地主机网络服务器简单得多。
【讨论】:
也许你没有注意到他的评论:“我们不允许将 MP3 数据解码到 SD 卡”。出于版权原因和数字版权管理(我认为)。 也许你没有注意到 twk 没有提到 SD 卡。 啊,对了——我确实错过了。不过,也许您可以使用 pipe() 来获取一些链接的文件描述符。【参考方案4】:在播放之前解密加密的 mp3 文件。有3种方式:
1、使用MediaPlayer api:
Android M 版之后:
you can play mp3 data in byte array directly by:
//Read encrypted mp3:
byte[] bytesAudioData = Utils.readFile(path); // or from a url.
final byte[] bytesAudioDataDecrypted = YourUtils.decryptFileToteArray(bytesAudioData);
ByteArrayMediaDataSource bds = new ByteArrayMediaDataSource(bytesAudioDataDecrypted) ;
mediaPlayer.setDataSource(bds);
在安卓版本M之前:
//Read encrypted mp3:
byte[] bytesAudioData = Utils.readFile(path); // or from a url.
final byte[] bytesAudioDataDecrypted = YourUtils.decryptFileToteArray(bytesAudioData);
File file =new File(dirofyouraudio.mp3");
FileOutputStream fos = new FileOutputStream(file);
fos.write(bytesAudioDataDecrypted);
Log.d(TAG, "playSound :: file write to temp :: System. nanoTime() ="+System. nanoTime());
fos.close();
FileInputStream fis = new FileInputStream(file);
mediaPlayer.setDataSource(fis.getFD());
2,如果你使用AudioTrack api:
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT);
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBufferSize,
AudioTrack.MODE_STREAM);
int i = 0;
byte[] tempMinBufferToRead = new byte[minBufferSize];
try
byte[] bytesAudioData = Utils.readFile( lastRecordedAudiofilePath);
bytesAudioData = yourUtils.decryptYourFileToByteArray(bytesAudioData);
try
fileInputStremForPlay = new FileInputStream( lastRecordedAudiofilePath);
dataInputStreamForPlay = new DataInputStream(new ByteArrayInputStream(bytesAudioData));
track.play();
while ((i = dataInputStreamForPlay.read(tempMinBufferToRead, 0, minBufferSize)) > -1)
Log.d(LOG_TAG, "mThreadExitFlag= " + mThreadExitFlag);
if ((mThreadExitFlag == true) || (!audioFilePathInThisThread.equals(lastRecordedAudiofilePath)))
m_handler.post(new Runnable()
public void run()
Log.d(LOG_TAG, "startPlaying: break and reset play button ");
startPlayBtnInToolbar.setEnabled(true);
stopPlayBtnInToolbar.setEnabled(false);
);
break;
track.write(tempMinBufferToRead, 0, i);
Log.d(LOG_TAG, "===== Playing Audio Completed ===== ");
track.stop();
track.release();
dataInputStreamForPlay.close();
fileInputStremForPlay.close();
catch (FileNotFoundException e)
e.printStackTrace();
catch (IOException e)
e.printStackTrace();
3,使用 MediaExtractor , MediaCodec 和 AudioTrack ,您可以使用提取器设置数据源:
extractor.setDataSource(this.url);
或者
extractor.setDataSource(this.Mediadatasource);
ps: ByteArrayMediaDataSource 类:
import android.annotation.TargetApi;
import android.media.MediaDataSource;
import android.os.Build;
import java.io.IOException;
import android.content.res.AssetFileDescriptor;
import android.media.MediaDataSource;
import android.util.Log;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
@TargetApi(Build.VERSION_CODES.M)
public class ByteArrayMediaDataSource extends MediaDataSource
private static final String TAG = ByteArrayMediaDataSource.class.getSimpleName();
private byte[] data;
public ByteArrayMediaDataSource(byte []data)
// assert data != null;
this.data = data;
@Override
public int readAt(long position, byte[] buffer, int offset, int size) throws IOException
if (position >= data.length)
return -1; // -1 indicates EOF
if (position + size > data.length)
size -= (position + size) - data.length;
Log.d(TAG, "MediaDataSource size = "+size);
System.arraycopy(data, (int)position, buffer, offset, size);
return size;
@Override
public long getSize() throws IOException
Log.d(TAG, "MediaDataSource data.length = "+data.length);
return data.length;
@Override
public void close() throws IOException
// Nothing to do here
data =null;
【讨论】:
以上是关于Android 上的 MP3 解码的主要内容,如果未能解决你的问题,请参考以下文章
[gitbook] Android框架分析系列之Android stagefright框架