将 AAC 音频文件解码为 RAW PCM 文件
Decoding an AAC audio file into a RAW PCM file
我有一个 AAC 格式的音频文件,我正在尝试将其转换为原始格式的 PCM 文件,以便将其与另一个音频文件混合并稍后使用 AudioTrack 播放。
经过一番研究,我发现 this library 可以正确解码我的 AAC 文件。但是,它只将解码后的字节直接传递给 AudioTrack。当尝试将解码后的字节写入输出流时,生成的文件只包含噪音。
这是我用来解码 AAC 文件的代码 -
public void AACDecoderAndPlay() {
ByteBuffer[] inputBuffers = mDecoder.getInputBuffers();
ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers();
BufferInfo info = new BufferInfo();
// create an audiotrack object
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, JamboxAudioTrack.FREQUENCY,
JamboxAudioTrack.CHANNEL_CONFIGURATION, JamboxAudioTrack.AUDIO_ENCODING,
JamboxAudioTrack.BUFFER_SIZE, AudioTrack.MODE_STREAM);
audioTrack.play();
long bytesWritten = 0;
while (!eosReceived) {
int inIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = mExtractor.readSampleData(buffer, 0);
if (sampleSize < 0) {
// We shouldn't stop the playback at this point, just pass the EOS
// flag to mDecoder, we will get it again from the
// dequeueOutputBuffer
Log.d(LOG_TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
mExtractor.advance();
}
int outIndex = mDecoder.dequeueOutputBuffer(info, TIMEOUT_US);
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
Log.d(LOG_TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
outputBuffers = mDecoder.getOutputBuffers();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
MediaFormat format = mDecoder.getOutputFormat();
Log.d(LOG_TAG, "New format " + format);
// audioTrack.setPlaybackRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.d(LOG_TAG, "dequeueOutputBuffer timed out!");
break;
default:
ByteBuffer outBuffer = outputBuffers[outIndex];
Log.v(LOG_TAG, "We can't use this buffer but render it due to the API limit, " + outBuffer);
final byte[] chunk = new byte[info.size];
outBuffer.get(chunk); // Read the buffer all at once
outBuffer.clear(); // ** MUST DO!!! OTHERWISE THE NEXT TIME YOU GET THIS SAME BUFFER BAD THINGS WILL HAPPEN
audioTrack.write(chunk, info.offset, info.offset + info.size); // AudioTrack write data
if (info.offset > 0) {
Log.v(LOG_TAG, "" + info.offset);
}
try {
mOutputStream.write(chunk, info.offset, info.offset + info.size);
bytesWritten += info.offset + info.size;
} catch (IOException e) {
e.printStackTrace();
}
mDecoder.releaseOutputBuffer(outIndex, false);
break;
}
// All decoded frames have been rendered, we can stop playing now
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d(LOG_TAG, "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
}
}
}
Log.v(LOG_TAG, "Bytes written: " + bytesWritten);
mDecoder.stop();
mDecoder.release();
mDecoder = null;
mExtractor.release();
mExtractor = null;
audioTrack.stop();
audioTrack.release();
audioTrack = null;
}
为了播放解码文件,我使用从缓冲区读取和播放的普通 AudioTrack -
public void start() {
new Thread(new Runnable() {
public void run() {
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
InputStream inputStream = new FileInputStream(playingFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, FREQUENCY,
CHANNEL_CONFIGURATION, AUDIO_ENCODING, BUFFER_SIZE, AudioTrack.MODE_STREAM);
short[] buffer = new short[BUFFER_SIZE / 4];
long startTime = System.currentTimeMillis();
track.play();
while (dataInputStream.available() > 0) {
int i = 0;
while (dataInputStream.available() > 0 && i < buffer.length
) {
buffer[i] = dataInputStream.readShort();
i++;
}
track.write(buffer, 0, buffer.length);
if (latency < 0) {
latency = System.currentTimeMillis() - startTime;
}
}
//
// int i = 0;
// while((i = dataInputStream.read(buffer, 0, BUFFER_SIZE)) > -1){
// track.write(buffer, 0, i);
// }
track.stop();
track.release();
dataInputStream.close();
inputStream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}
我错过了什么?
您的问题似乎在于您将输出写为纯字节(尽管我没有在您的代码中看到 mOutputStream
的设置)。这些纯字节将采用您平台的本机字节序(实际上是小字节序),但以平台无关的方式(指定为大字节序)使用 DataInputStream
将其读取为短裤。
这里最简单的解决方法就是播放时用byte
数组代替short
数组; AudioTrack 接受字节数组和短数组,当给定一个字节数组时,它以正确的(本机)方式解释它,这与 MediaCodec 的输出相匹配。只需确保缓冲区大小为偶数字节即可。
如果您确实需要将值设为 short
s,则需要使用以小端模式读取的 reader(当前所有 Android ABI 都是小端模式) .这似乎没有任何直接可用的 API,但实际上并不太难。参见例如Java : DataInputStream replacement for endianness 中的 readLittleShort
方法,以获取有关如何执行此操作的示例。
我有一个 AAC 格式的音频文件,我正在尝试将其转换为原始格式的 PCM 文件,以便将其与另一个音频文件混合并稍后使用 AudioTrack 播放。
经过一番研究,我发现 this library 可以正确解码我的 AAC 文件。但是,它只将解码后的字节直接传递给 AudioTrack。当尝试将解码后的字节写入输出流时,生成的文件只包含噪音。
这是我用来解码 AAC 文件的代码 -
public void AACDecoderAndPlay() {
ByteBuffer[] inputBuffers = mDecoder.getInputBuffers();
ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers();
BufferInfo info = new BufferInfo();
// create an audiotrack object
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, JamboxAudioTrack.FREQUENCY,
JamboxAudioTrack.CHANNEL_CONFIGURATION, JamboxAudioTrack.AUDIO_ENCODING,
JamboxAudioTrack.BUFFER_SIZE, AudioTrack.MODE_STREAM);
audioTrack.play();
long bytesWritten = 0;
while (!eosReceived) {
int inIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = mExtractor.readSampleData(buffer, 0);
if (sampleSize < 0) {
// We shouldn't stop the playback at this point, just pass the EOS
// flag to mDecoder, we will get it again from the
// dequeueOutputBuffer
Log.d(LOG_TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
mExtractor.advance();
}
int outIndex = mDecoder.dequeueOutputBuffer(info, TIMEOUT_US);
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
Log.d(LOG_TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
outputBuffers = mDecoder.getOutputBuffers();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
MediaFormat format = mDecoder.getOutputFormat();
Log.d(LOG_TAG, "New format " + format);
// audioTrack.setPlaybackRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.d(LOG_TAG, "dequeueOutputBuffer timed out!");
break;
default:
ByteBuffer outBuffer = outputBuffers[outIndex];
Log.v(LOG_TAG, "We can't use this buffer but render it due to the API limit, " + outBuffer);
final byte[] chunk = new byte[info.size];
outBuffer.get(chunk); // Read the buffer all at once
outBuffer.clear(); // ** MUST DO!!! OTHERWISE THE NEXT TIME YOU GET THIS SAME BUFFER BAD THINGS WILL HAPPEN
audioTrack.write(chunk, info.offset, info.offset + info.size); // AudioTrack write data
if (info.offset > 0) {
Log.v(LOG_TAG, "" + info.offset);
}
try {
mOutputStream.write(chunk, info.offset, info.offset + info.size);
bytesWritten += info.offset + info.size;
} catch (IOException e) {
e.printStackTrace();
}
mDecoder.releaseOutputBuffer(outIndex, false);
break;
}
// All decoded frames have been rendered, we can stop playing now
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d(LOG_TAG, "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
}
}
}
Log.v(LOG_TAG, "Bytes written: " + bytesWritten);
mDecoder.stop();
mDecoder.release();
mDecoder = null;
mExtractor.release();
mExtractor = null;
audioTrack.stop();
audioTrack.release();
audioTrack = null;
}
为了播放解码文件,我使用从缓冲区读取和播放的普通 AudioTrack -
public void start() {
new Thread(new Runnable() {
public void run() {
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
InputStream inputStream = new FileInputStream(playingFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, FREQUENCY,
CHANNEL_CONFIGURATION, AUDIO_ENCODING, BUFFER_SIZE, AudioTrack.MODE_STREAM);
short[] buffer = new short[BUFFER_SIZE / 4];
long startTime = System.currentTimeMillis();
track.play();
while (dataInputStream.available() > 0) {
int i = 0;
while (dataInputStream.available() > 0 && i < buffer.length
) {
buffer[i] = dataInputStream.readShort();
i++;
}
track.write(buffer, 0, buffer.length);
if (latency < 0) {
latency = System.currentTimeMillis() - startTime;
}
}
//
// int i = 0;
// while((i = dataInputStream.read(buffer, 0, BUFFER_SIZE)) > -1){
// track.write(buffer, 0, i);
// }
track.stop();
track.release();
dataInputStream.close();
inputStream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}
我错过了什么?
您的问题似乎在于您将输出写为纯字节(尽管我没有在您的代码中看到 mOutputStream
的设置)。这些纯字节将采用您平台的本机字节序(实际上是小字节序),但以平台无关的方式(指定为大字节序)使用 DataInputStream
将其读取为短裤。
这里最简单的解决方法就是播放时用byte
数组代替short
数组; AudioTrack 接受字节数组和短数组,当给定一个字节数组时,它以正确的(本机)方式解释它,这与 MediaCodec 的输出相匹配。只需确保缓冲区大小为偶数字节即可。
如果您确实需要将值设为 short
s,则需要使用以小端模式读取的 reader(当前所有 Android ABI 都是小端模式) .这似乎没有任何直接可用的 API,但实际上并不太难。参见例如Java : DataInputStream replacement for endianness 中的 readLittleShort
方法,以获取有关如何执行此操作的示例。