29

我们正在为 Android 手机实施一个程序,该程序可以播放来自互联网的音频流。这大概是我们所做的:

  1. 下载自定义加密格式。
  2. 解密以获取大量常规 MP3 数据。
  3. 将 MP3 数据解码为内存缓冲区中的原始 PCM 数据。
  4. 将原始 PCM 数据通过管道传输到 AudioTrack

到目前为止,我们的目标设备是 Droid 和 Nexus One。在 Nexus One 上一切正常,但在 Droid 上 MP3 解码太慢了。如果我们将 Droid 置于负载下,音频播放就会开始跳过。我们不允许将 MP3 数据解码到 SD 卡,但我知道这不是我们的问题。

我们没有编写自己的 MP3 解码器,而是使用了 MPADEC ( http://sourceforge.net/projects/mpadec/ )。它是免费的,并且很容易与我们的程序集成。我们用 NDK 编译它。

在使用各种分析工具进行详尽分析后,我们确信正是这个解码器落后了。

以下是我们正在考虑的选项:

  1. 找到另一个我们可以使用 Android NDK 编译的 MP3 解码器。该 MP3 解码器必须进行优化以在移动 ARM 设备上运行,或者可能使用仅整数数学或其他一些优化来提高性能。

  2. 由于内置的​​ Android MediaPlayer 服务将获取 URL,我们可以在我们的程序中实现一个小型 HTTP 服务器,并为 MediaPlayer 提供解密的 MP3。这样我们就可以利用内置的 MP3 解码器。

  3. 通过 NDK 访问内置 MP3 解码器。我不知道这是否可能。

有人对我们可以做些什么来加快 MP3 解码有任何建议吗?

-- 罗布斯

4

4 回答 4

2

正确的方法是构建您自己的固件并作为自定义 OpenCORE 编解码器的一部分进行解密。当然,这会将您限制在可以安装该固件的设备上。

请记住,其余部分是推测性的。我实际上需要做一些类似于你所描述的事情,但我没有时间留出几个月来解决这个问题。所以,我将以我如何解决问题的方式来描述这一点。

一种解决方案是 twk 的答案中描述的解决方案。您不必使用 SD 卡,但您可能必须在应用程序本地文件存储 ( getFilesDir()) 中有一个全球可读的临时文件。下载第一个块,解密它,将它写成一个完整的世界可读的 MP3 文件(但有一个适当模糊的目录/路径),然后把它交给一个MediaPlayervia setDataSource(). 在播放时,您下载/解密并设置第二个MediaPlayer实例,它会在第一个实例结束后立即开始播放,以实现尽可能无缝的过渡。然后你重置第一个MediaPlayer并在你的第三个块中重用它,在两者之间进行乒乓球。

一个相关的解决方案将在 jleedev 的评论中。这几乎是一样的,只是你提供了一个FileDescriptorvia a ContentProvider。这有一个选项可以让您使用套接字,这可以让您避免临时文件。但是,该ContentProvider文件本身必须是可公开访问的,因此具有不明确目录的临时文件实际上可能更私密。

如果您担心这些东西可以被其他进程读取,请理解它MediaPlayer本身(或者更确切地说,OpenCORE 子系统)在另一个进程中。此外,您建议的 HTTP 服务器在设备上也是全球可读的。因此,如果您打算让MediaPlayer进行解码,那么默默无闻的安全性是您唯一可行的选择。

AFAIK,NDK 不授予对 OpenCORE 的访问权限,尽管我承认 NDK 经验有限,所以我可能错了。当然还有其他可用的 MP3 解码器(ffmpeg/mplayer等),尽管尚不清楚将这些解码器转换为 NDK 库的难易程度。

因此,这实际上归结为您要防御的对象。如果你试图防御用户,你可能不得不自己解码它,不知何故。

于 2010-03-25T15:19:48.163 回答
1

MAD是一个整数运算解码器库,似乎很受好评。

(无论如何,将数据解码到 SD 卡上不会减慢您的速度吗?)

于 2010-03-25T23:26:10.227 回答
0

我没有试过这个,但我相信你可以给媒体播放器一个文件描述符:

public void setDataSource (FileDescriptor fd, long offset, long length)

也许您可以将解密的 mp3 写入文件并使用文件描述符播放它。假设您提前知道文件长度,这甚至可能适用于流式传输。如果它有效,它会比做一个本地主机网络服务器简单得多。

于 2010-03-23T17:42:19.603 回答
0

在播放之前解密加密的 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;
    }
}
于 2017-10-23T15:55:26.067 回答