8

次の呼び出しで、jLayer を使用して MP3 データをデコードしています。

SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);

デコードされたデータを返すこの呼び出しは、short[] の配列を返します。 output.getBuffer();

そのメソッドで AudioTrack write() を呼び出すと、ファイルをループするときに正常に再生されます。

at.write(output.getBuffer(), 0, output.getBuffer().length);

ただし、この回答のいずれかの方法を使用して short[] 配列を byte[] 配列に変換すると: https://stackoverflow.com/a/12347176/1176436音が歪んでぎくしゃくします:

at.write(output.getBuffer(), 0, output.getBuffer().length);

になります:

byte[] array = ShortToByte_Twiddle_Method(output.getBuffer());
at.write(array,  0,  array.length);

私は何か間違ったことをしていますか?それを修正するにはどうすればよいですか? 残念ながら、私が使用している別のサードパーティ ライブラリのバイト配列に pcm データが必要です。それが重要な場合、ファイルは22kHzであり、これがどのようにインスタンス化されているかです:

at = new AudioTrack(AudioManager.STREAM_MUSIC, 22050, AudioFormat.CHANNEL_OUT_STEREO,
                AudioFormat.ENCODING_PCM_16BIT, 10000 /* 10 second buffer */,
                AudioTrack.MODE_STREAM);   

よろしくお願いします。

編集: これは、AudioTrack 変数をインスタンス化する方法です。したがって、44kHz ファイルの場合、送信される値は 44100 ですが、22kHz ファイルの場合、値は 22050 です。

at = new AudioTrack(AudioManager.STREAM_MUSIC, decoder.getOutputFrequency(), 
                                  decoder.getOutputChannels() > 1 ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO,
                                  AudioFormat.ENCODING_PCM_16BIT, 10000 /* 10 second buffer */,
                                  AudioTrack.MODE_STREAM);

これはデコード方法です:

public byte[] decode(InputStream inputStream, int startMs, int maxMs) throws IOException {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);

        float totalMs = 0;
        boolean seeking = true;

        try {
            Bitstream bitstream = new Bitstream(inputStream);
            Decoder decoder = new Decoder();

            boolean done = false;
            while (!done) {
                Header frameHeader = bitstream.readFrame();
                if (frameHeader == null) {
                    done = true;
                } else {
                    totalMs += frameHeader.ms_per_frame();

                    if (totalMs >= startMs) {
                        seeking = false;
                    }

                    if (!seeking) {
                        // logger.debug("Handling header: " + frameHeader.layer_string());
                        SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);                            

                        short[] pcm = output.getBuffer();
                        for (short s : pcm) {
                            outStream.write(s & 0xff);
                            outStream.write((s >> 8) & 0xff);
                        }
                    }

                    if (totalMs >= (startMs + maxMs)) {
                        done = true;
                    }
                }
                bitstream.closeFrame();
            }

            return outStream.toByteArray();
        } catch (BitstreamException e) {
            throw new IOException("Bitstream error: " + e);
        } catch (DecoderException e) {
            throw new IOException("Decoder error: " + e);
        }
    }

これがどのように聞こえるかです (数秒待ちます): https://vimeo.com/60951237 (これが実際のファイルです: http://www.tonycuffe.com/mp3/tail%20toddle.mp3 )

編集:賞金を分割したかったのですが、代わりに賞金をビルに与え、受け入れられた回答をニールに与えました。どちらも大変参考になりました。疑問に思っている人のために説明すると、最終的に Sonic のネイティブ コードを書き直したので、プロセスを進めることができました。

4

2 に答える 2

4

@Bill Pringlemeir が言うように、問題は変換メソッドが実際には変換されないことです。short は 16 ビットの数値です。バイトは 8 ビットの数値です。選択した方法では、short の内容は変換されません (つまり、内容が 16 ビットから 8 ビットに変換されます)。同じビット コレクションの格納方法が変更されます。あなたが言うように、次のようなものが必要です。

SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
byte[] array = MyShortToByte(output.getBuffer());
at.write(array,  0,  array.length);

@Bill Pringlemeir のアプローチは、すべての short を 256 で割ってバイト範囲に収まるようにすることと同じです。

byte[] MyShortToByte(short[] buffer) {
    int N = buffer.length;
    ByteBuffer byteBuf = ByteBuffer.allocate(N);
    while (N >= i) {
        byte b = (byte)(buffer[i]/256);  /*convert to byte. */
        byteBuf.put(b);
        i++;
    }
    return byteBuf.array();
}

これは機能しますが、おそらく非常に静かでエッジの効いたトーンが得られます。処理時間に余裕がある場合は、2 パス アプローチの方が良い結果が得られる可能性があります。

byte[] MyShortToByte(short[] buffer) {
    int N = buffer.length;
    short min = 0;
    short max = 0;
    for (int i=0; i<N; i++) {
         if (buffer[i] > max) max = buffer[i];
         if (buffer[i] < min) min = buffer[i];
         }
    short scaling = 1+(max-min)/256; // 1+ ensures we stay within range and guarantee no divide by zero if sequence is pure silence ...

    ByteBuffer byteBuf = ByteBuffer.allocate(N);
    for (int i=0; i<N; i++) {
        byte b = (byte)(buffer[i]/scaling);  /*convert to byte. */
        byteBuf.put(b);
    }
    return byteBuf.array();
}

繰り返しますが、署名済み/未署名の問題に注意してください。上記は、signed-> signed および unsigned->unsigned で機能します。しかし、両者の間ではありません。符号付き short (-32768-32767) を読み取っているが、符号なしバイト (0-255) を出力する必要がある可能性があります...

処理時間に余裕がある場合、より正確な (よりスムーズな) アプローチは、フロートを使用することです (これは、署名付き/未署名の問題も回避します)。

byte[] MyShortToByte(short[] buffer) {
    int N = buffer.length;
    float f[] = new float[N];
    float min = 0.0f;
    float max = 0.0f;
    for (int i=0; i<N; i++) {
         f[i] = (float)(buffer[i]);
         if (f[i] > max) max = f[i];
         if (f[i] < min) min = f[i];
         }
    float scaling = 1.0f+(max-min)/256.0f; // +1 ensures we stay within range and guarantee no divide by zero if sequence is pure silence ...

    ByteBuffer byteBuf = ByteBuffer.allocate(N);
    for (int i=0; i<N; i++) {
        byte b = (byte)(f[i]/scaling);  /*convert to byte. */
        byteBuf.put(b);
    }
    return byteBuf.array();
}
于 2013-03-03T17:05:04.227 に答える
3

問題はあなたのshortコンバージョンbyteにあります。バイト変換リンクは、上位部分と下位部分を含むすべての情報を保持しますbyte16ビットから8ビットのPCMサンプルに変換する場合は、下位バイトを破棄する必要があります。私のJavaスキルは弱いので、以下は逐語的には機能しないかもしれません。参照:ショートからバイトへの変換。

ByteBuffer byteBuf = ByteBuffer.allocate(N);
while (N >= i) {
  /* byte b = (byte)((buffer[i]>>8)&0xff);  convert to byte. native endian */
 byte b = (byte)(buffer[i]&0xff);  /*convert to byte; swapped endian. */
 byteBuf.put(b);
  i++;
}

それは次の変換です、

  AAAA AAAA SBBB BBBB  -> AAAA AAAA, +1 if S==1 and positive else -1 if S==1

A保持されているビットです。 Bは破棄されたビットでありS、丸めに使用したいビットです。丸めは必要ありませんが、少し良く聞こえるかもしれません。基本的に、16ビットPCMは8ビットPCMよりも高解像度です。変換が完了すると、これらのビットは失われます。shorttobyteルーチンは、すべての情報を保持しようとします。

もちろん、使用していることをサウンドライブラリに伝える必要があります8-bit PCM。私の推測、

at = new AudioTrack(AudioManager.STREAM_MUSIC, 22050, AudioFormat.CHANNEL_OUT_STEREO,
            AudioFormat.ENCODING_PCM_8BIT, 10000 /* 10 second buffer */,
            AudioTrack.MODE_STREAM);

オーディオの再生にしか使用できない場合は、逆を実行して、ライブラリから再生用16bit PCMに変換する必要があります。また、通常、サンプルはストレートPCMではなく、u -lawまたはa-lawでエンコードされている場合が多いことにも注意してください。サードパーティのライブラリがこれらの形式を使用している場合、変換は異なりますが、ウィキペディアのリンクからコーディングできるはずです。8bit PCM16bit PCM8bit

注:丸めコードは含めていません。処理するoverflowsign答えが複雑になるためです。チェックする必要がありますoverflow(つまり、0x8f + 1は0xffを与えるか、255 + 1は-1を与える)。しかし、私はライブラリがまっすぐではないと思い8bit PCMます。

関連項目:Alsa PCMの概要、 PCMのマルチメディアwikiエントリ-最終的にAndroidはサウンドにALSAを使用します。

PCM rawバッファに対して正しくなければならない他の要因は、サンプルレート、チャネル数(ステレオ/モノラル)、ビットを含むPCM形式、圧伸、リトルエンディアン/ビッグエンディアン、およびサンプルインターリーブです。

編集:いくつかの調査の後、JLayerデコーダーは通常big endian16ビット値を返します。ソニックフィルターは、 16ビットの下byteでそれらを脅かします。little endian最後に、AudioTrackクラスは16ビットlittle endianの下にあることを期待しています。どういうわけか、JLayermp3デコーダーは16ビットlittle endian値を返すと思います。問題のdecode()メソッドは、16ビット値のバイトスワップを実行します。また、投稿された音声は、バイトが入れ替わったように聞こえます。

public byte[] decode(InputStream inputStream, int startMs, int maxMs, bool swap) throws IOException {
...
                    short[] pcm = output.getBuffer();
                    for (short s : pcm) {
                        if(swap) {
                          outStream.write(s & 0xff);
                          outStream.write((s >> 8) & 0xff);
                        } else {
                          outStream.write((s >> 8) & 0xff);
                          outStream.write(s & 0xff);
                        }
                    }
...

44k mp3の場合、ルーチンを。で呼び出しますswap = true;。22kmp3の場合swap = false。これは、報告されたすべての現象を説明しています。JLayerなぜmp3デコーダーが出力するのか、big endianそれ以外の場合があるのか​​わかりませんlittle endian。サンプルレートではなく、ソースのmp3に依存すると思います。

于 2013-03-03T16:19:15.880 に答える