我们正在实施一项Android手机程序,播放从互联网流式传输的音频。以下是我们的工作:
目前我们的目标设备是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服务器,并使用解密的MP3为MediaPlayer提供服务。这样我们就可以利用内置的MP3解码器。
通过NDK访问内置MP3解码器。我不知道这是否可能。
有没有人对我们如何加速MP3解码有任何建议?
- Rob Sz
答案 0 :(得分:2)
正确的方法是构建自己的固件并将解密作为自定义OpenCORE编解码器的一部分。当然,这会将您限制在可以安装该固件的设备上。
请记住,其余部分有点投机。我实际上需要做一些类似你所描述的事情,但我没有时间来解决这个问题几个月。所以,我将以我如何解决问题的方式来描述这一点。
一种解决方案是twk的答案中描述的解决方案。您不必使用SD卡,但您可能必须在应用程序本地文件存储(getFilesDir()
)中拥有一个世界可读的临时文件。下载第一个块,解密它,将其写成一个完整的世界可读的MP3文件(但具有适当模糊的目录/路径),并通过MediaPlayer
将其传递给setDataSource()
。播放时,您下载/解密并设置第二个MediaPlayer
实例,该实例在第一个实例结束时立即开始播放,以实现尽可能无缝的转换。然后重置第一个MediaPlayer
并将其重新用于第三个块,在两者之间进行乒乓。
相关解决方案将在jleedev的评论中。除了您通过FileDescriptor
提供ContentProvider
之外,这几乎是一回事。这有一个让你使用套接字的选项,可以让你避免使用临时文件。但是,ContentProvider
本身必须是公共可访问的,因此具有模糊目录的临时文件实际上可能更私密。
如果您担心其他进程可以读取这些内容,请理解MediaPlayer
本身(或者更确切地说,OpenCORE子系统)在另一个进程中。此外,您建议的HTTP服务器在设备上也是世界可读的。因此,如果您要让MediaPlayer
进行解码,那么默默无闻的安全性是您唯一可行的选择。
AFAIK,NDK不授予访问OpenCORE的权限,虽然我承认NDK体验有限,所以我可能错了。当然还有其他的MP3解码器可供使用(ffmpeg
/ mplayer
等),虽然这些可以很容易地转换成NDK库但目前还不清楚。
所以,它真的归结为你想要防守的对象。如果你想要防御用户,你可能不得不自己解码它。
答案 1 :(得分:1)
MAD是一个纯整数运算的解码器库,似乎很受欢迎。
(不会将数据解码到SD卡上会使你的速度变慢吗?)
答案 2 :(得分:0)
我没试过这个,但我相信你可以给媒体播放器一个文件描述符:
public void setDataSource (FileDescriptor fd, long offset, long length)
也许您可以将解密的mp3写入文件并使用文件描述符播放它。假设您提前知道文件长度,这甚至可能适用于流式传输。如果它工作,它将比做一个localhost网络服务器简单得多。
答案 3 :(得分:0)
在播放之前解密加密的mp3文件。有三种方式:
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);
在Android版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;
}
}