Android上的MP3解码

时间:2010-03-22 17:48:04

标签: android mp3 decode android-ndk

我们正在实施一项Android手机程序,播放从互联网流式传输的音频。以下是我们的工作:

  1. 下载自定义加密格式。
  2. 解密获取常规MP3数据块。
  3. 将MP3数据解码为内存缓冲区中的原始PCM数据。
  4. 将原始PCM数据传输到AudioTrack
  5. 目前我们的目标设备是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服务器,并使用解密的MP3为MediaPlayer提供服务。这样我们就可以利用内置的MP3解码器。

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

    4. 有没有人对我们如何加速MP3解码有任何建议?

      - Rob Sz

4 个答案:

答案 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;
    }
}