Skip to content

Instantly share code, notes, and snippets.

@a-m-s
Last active February 19, 2023 06:54
Show Gist options
  • Save a-m-s/1991ab18fbcb0fcc2cf9 to your computer and use it in GitHub Desktop.
Save a-m-s/1991ab18fbcb0fcc2cf9 to your computer and use it in GitHub Desktop.
Android code to extract raw audio from arbitrary media files.
/* MediaDecoder
Author: Andrew Stubbs (based on some examples from the docs)
This class opens a file, reads the first audio channel it finds, and returns raw audio data.
Usage:
MediaDecoder decoder = new MediaDecoder("myfile.m4a");
short[] data;
while ((data = decoder.readShortData()) != null) {
// process data here
}
*/
import java.nio.ByteBuffer;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
public class MediaDecoder {
private MediaExtractor extractor = new MediaExtractor();
private MediaCodec decoder;
private MediaFormat inputFormat;
private ByteBuffer[] inputBuffers;
private boolean end_of_input_file;
private ByteBuffer[] outputBuffers;
private int outputBufferIndex = -1;
public MediaDecoder(String inputFilename) {
extractor.setDataSource(inputFilename);
// Select the first audio track we find.
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; ++i) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("audio/")) {
extractor.selectTrack(i);
decoder = MediaCodec.createDecoderByType(mime);
decoder.configure(format, null, null, 0);
inputFormat = format;
break;
}
}
if (decoder == null) {
throw new IllegalArgumentException("No decoder for file format");
}
decoder.start();
inputBuffers = decoder.getInputBuffers();
outputBuffers = decoder.getOutputBuffers();
end_of_input_file = false;
}
// Read the raw data from MediaCodec.
// The caller should copy the data out of the ByteBuffer before calling this again
// or else it may get overwritten.
private ByteBuffer readData(BufferInfo info) {
if (decoder == null)
return null;
for (;;) {
// Read data from the file into the codec.
if (!end_of_input_file) {
int inputBufferIndex = decoder.dequeueInputBuffer(10000);
if (inputBufferIndex >= 0) {
int size = extractor.readSampleData(inputBuffers[inputBufferIndex], 0);
if (size < 0) {
// End Of File
decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
end_of_input_file = true;
} else {
decoder.queueInputBuffer(inputBufferIndex, 0, size, extractor.getSampleTime(), 0);
extractor.advance();
}
}
}
// Read the output from the codec.
if (outputBufferIndex >= 0)
// Ensure that the data is placed at the start of the buffer
outputBuffers[outputBufferIndex].position(0);
outputBufferIndex = decoder.dequeueOutputBuffer(info, 10000);
if (outputBufferIndex >= 0) {
// Handle EOF
if (info.flags != 0) {
decoder.stop();
decoder.release();
decoder = null;
return null;
}
// Release the buffer so MediaCodec can use it again.
// The data should stay there until the next time we are called.
decoder.releaseOutputBuffer(outputBufferIndex, false);
return outputBuffers[outputBufferIndex];
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
// This usually happens once at the start of the file.
outputBuffers = decoder.getOutputBuffers();
}
}
}
// Return the Audio sample rate, in samples/sec.
public int getSampleRate() {
return inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
}
// Read the raw audio data in 16-bit format
// Returns null on EOF
public short[] readShortData() {
BufferInfo info = new BufferInfo();
ByteBuffer data = readData(info);
if (data == null)
return null;
int samplesRead = info.size/2;
short[] returnData = new short[samplesRead];
// Converting the ByteBuffer to an array doesn't actually make a copy
// so we must do so or it will be overwritten later.
System.arraycopy(data.asShortBuffer().array(), 0, returnData, 0, samplesRead);
return returnData;
}
}
@Riaxter
Copy link

Riaxter commented Jun 6, 2018

I love your example, but there are some errors.
IllegalStateException and UnsupportedOperationException on Line 132 "System.arraycopy(data.asShortBuffer().array(), 0, returnData, 0, samplesRead);"
The IllegalStateException occurs because access to "ByteBuffer data" which has been released on Line 102 "decoder.releaseOutputBuffer(outputBufferIndex, false);"
The UnsupportedOperationException occurs because the "ShortBuffer" from "ByteBuffer.asShortBuffer" cannot use "array" function.
So I changed this line to "outputBuffers[outputBufferIndex].order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(returnData);" and it works like a charm!
Thank you for example code!

@FreeTaek
Copy link

Is there any other way? There is an array error

I did what Riaxter said, but I could not solve it.

@nanjingdaqi
Copy link

Is there any other way? There is an array error

I did what Riaxter said, but I could not solve it.

Me too. Another issue:

java.lang.IllegalStateException: buffer is inaccessible
        at java.nio.DirectByteBuffer.getUnchecked(DirectByteBuffer.java:475)
        at java.nio.ByteBufferAsShortBuffer.get(ByteBufferAsShortBuffer.java:102)
        at java.nio.ShortBuffer.get(ShortBuffer.java:417)
        at org.peace.allinone.ui.MediaDecoder.readShortData(MediaDecoder.java:138)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment