【问题标题】:MP3 Decoding on AndroidAndroid 上的 MP3 解码
【发布时间】:2011-01-30 11:19:13
【问题描述】:

我们正在为 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 解码有任何建议吗?

-- 罗伯斯

【问题讨论】:

  • 在您的选项 #2 中,我希望 HTTP 开销会淹没您使用内置 MediaPlayer 流支持所获得的收益。
  • 如果我没记错的话,内置的MediaPlayer 也可以从任何content:// URI 流式传输,这实际上为您提供了一个可以写入的管道。
  • @jleedev:你能详细说明一下吗?听起来很有趣!
  • @Rob 对您最终选择的方法非常感兴趣。现在正在研究同样的情况。

标签: android mp3 decode android-ndk


【解决方案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;
    }
}

【讨论】:

    猜你喜欢
    • 2023-03-07
    • 2023-03-30
    • 1970-01-01
    • 2013-09-08
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多