Skip to content

Commit

Permalink
AAC streaming based on the MediaCodec API
Browse files Browse the repository at this point in the history
  • Loading branch information
fyhertz committed Sep 24, 2013
1 parent 879ffa8 commit 819481b
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 122 deletions.
44 changes: 26 additions & 18 deletions src/net/majorkernelpanic/streaming/MediaStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ public abstract class MediaStream implements Stream {
protected static final String TAG = "MediaStream";

/** MediaStream forwards data to a packetizer through a LocalSocket. */
public static final int MODE_STREAMING_LEGACY = 0;
public static final int MODE_MEDIARECORDER_API = 0x00;

/** MediaStream uses the new MediaCodec API introduced in JB 4.2 to stream audio/video. */
public static final int MODE_STREAMING_JB = 1;
public static final int MODE_MEDIACODEC_API = 0x01;

/** The packetizer that will read the output of the camera and send RTP packets over the networkd. */
protected AbstractPacketizer mPacketizer = null;
Expand All @@ -56,8 +56,8 @@ public abstract class MediaStream implements Stream {
private int mSocketId;

protected boolean mStreaming = false;
protected int mMode = MODE_STREAMING_LEGACY;
protected static int sSuggestedMode = MODE_STREAMING_LEGACY;
protected int mMode = MODE_MEDIARECORDER_API;
protected static int sSuggestedMode = MODE_MEDIARECORDER_API;

private LocalServerSocket mLss = null;
protected LocalSocket mReceiver, mSender = null;
Expand All @@ -69,18 +69,28 @@ public abstract class MediaStream implements Stream {
// We determine wether or not the MediaCodec API should be used
try {
Class.forName("android.media.MediaCodec");
sSuggestedMode = MODE_STREAMING_LEGACY;
Log.d(TAG,"Phone supports the MediaCoded API");
// Will be set to MODE_MEDIACODEC_API at some point...
sSuggestedMode = MODE_MEDIACODEC_API;
Log.i(TAG,"Phone supports the MediaCoded API");
} catch (ClassNotFoundException e) {
sSuggestedMode = MODE_STREAMING_LEGACY;
Log.d(TAG,"Phone does not support the MediaCodec API");
sSuggestedMode = MODE_MEDIARECORDER_API;
Log.i(TAG,"Phone does not support the MediaCodec API");
}
}

public MediaStream() {
mMode = sSuggestedMode;
}

/**
* By default, the API that will be used to encode video or audio is choosen automatically depending
* on the capabilities of the phone, and what have been implemented in libstreaming.
* @param mode {@link MediaStream#MODE_MEDIACODEC_API} or {@link MediaStream#MODE_MEDIACODEC_API}
*/
public void setAPI(int mode) {
mMode = mode;
}

/**
* Sets the destination ip address of the stream.
* @param dest The destination address of the stream
Expand Down Expand Up @@ -150,10 +160,11 @@ public int[] getLocalPorts() {

/**
* Sets the mode of the {@link MediaStream}.
* If the mode is set to {@link #MODE_STREAMING_LEGACY}, video is forwarded to a UDP socket.
* @param mode Either {@link #MODE_STREAMING_LEGACY} or {@link #MODE_STREAMING_JB}
* If the mode is set to {@link #MODE_MEDIARECORDER_API}, video is forwarded to a UDP socket.
* @param mode Either {@link #MODE_MEDIARECORDER_API} or {@link #MODE_MEDIACODEC_API}
*/
public void setMode(int mode) {
public void setMode(int mode) throws IllegalStateException {
if (mStreaming) throw new IllegalStateException("Can't be called while streaming !");
this.mMode = mode;
}

Expand Down Expand Up @@ -183,20 +194,17 @@ public boolean isStreaming() {
/** Starts the stream. */
public synchronized void start() throws IllegalStateException, IOException {

if (mPacketizer==null)
throw new IllegalStateException("setPacketizer() should be called before start().");

if (mDestination==null)
throw new IllegalStateException("No destination ip address set for the stream !");

if (mRtpPort<=0 || mRtcpPort<=0)
throw new IllegalStateException("No destination ports set for the stream !");

switch (mMode) {
case MODE_STREAMING_LEGACY:
case MODE_MEDIARECORDER_API:
encodeWithMediaRecorder();
break;
case MODE_STREAMING_JB:
case MODE_MEDIACODEC_API:
encodeWithMediaCodec();
break;
};
Expand All @@ -209,16 +217,16 @@ public synchronized void stop() {
if (mStreaming) {
mPacketizer.stop();
try {
if (mMode==MODE_STREAMING_LEGACY) {
if (mMode==MODE_MEDIARECORDER_API) {
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
closeSockets();
} else {
mMediaCodec.stop();
mMediaCodec.release();
mMediaCodec = null;
}
closeSockets();
} catch (Exception ignore) {}
mStreaming = false;
}
Expand Down
155 changes: 133 additions & 22 deletions src/net/majorkernelpanic/streaming/audio/AACStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;

import net.majorkernelpanic.streaming.rtp.AACADTSPacketizer;
import net.majorkernelpanic.streaming.rtp.AACLATMPacketizer;
import net.majorkernelpanic.streaming.rtp.MediaCodecInputStream;
import android.annotation.SuppressLint;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;
Expand All @@ -41,7 +50,7 @@
public class AACStream extends AudioStream {

public final static String TAG = "AACStream";

protected AudioQuality mQuality = new AudioQuality(32000,32000);

/** MPEG-4 Audio Object Types supported by ADTS. **/
Expand Down Expand Up @@ -76,29 +85,17 @@ public class AACStream extends AudioStream {
private int mActualSamplingRate;
private int mProfile, mSamplingRateIndex, mChannel, mConfig;
private SharedPreferences mSettings = null;
private AudioRecord mAudioRecord = null;
private Thread mThread = null, mThread2 = null;

@SuppressLint("InlinedApi")
public AACStream() throws IOException {
super();

mPacketizer = new AACADTSPacketizer();

setAudioSource(MediaRecorder.AudioSource.CAMCORDER);

if (!AACStreamingSupported()) {
Log.e(TAG,"AAC ADTS not supported on this phone");
Log.e(TAG,"AAC not supported on this phone");
throw new AACNotSupportedException();
}

try {
Field name = MediaRecorder.OutputFormat.class.getField("AAC_ADTS");
setOutputFormat(name.getInt(null));
}
catch (Exception ignore) {}

setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
setAudioSamplingRate(mQuality.samplingRate);

}

private static boolean AACStreamingSupported() {
Expand All @@ -108,7 +105,7 @@ private static boolean AACStreamingSupported() {
return true;
} catch (Exception e) {
return false;
}
}
}

/**
Expand All @@ -120,12 +117,126 @@ public void setPreferences(SharedPreferences prefs) {
}

public void start() throws IllegalStateException, IOException {
testADTS();
((AACADTSPacketizer)mPacketizer).setSamplingRate(mActualSamplingRate);
super.start();

}

@Override
@SuppressLint("InlinedApi")
protected void encodeWithMediaRecorder() throws IOException {
testADTS();
if (mPacketizer == null || mPacketizer.getClass() != AACADTSPacketizer.class) {
mPacketizer = new AACADTSPacketizer();
}
setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
try {
Field name = MediaRecorder.OutputFormat.class.getField("AAC_ADTS");
setOutputFormat(name.getInt(null));
}
catch (Exception ignore) {}
super.encodeWithMediaRecorder();
}

@Override
@SuppressLint({ "InlinedApi", "NewApi" })
protected void encodeWithMediaCodec() throws IOException {
if (mPacketizer == null || mPacketizer.getClass() != AACLATMPacketizer.class) {
mPacketizer = new AACLATMPacketizer();
}

final int bufferSize = AudioRecord.getMinBufferSize(mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_DEFAULT, bufferSize);

mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
format.setInteger(MediaFormat.KEY_BIT_RATE, mQuality.bitRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mQuality.samplingRate);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE);
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mAudioRecord.startRecording();
mMediaCodec.start();

final InputStream inputStream = new MediaCodecInputStream(mMediaCodec);
final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();

mThread = new Thread(new Runnable() {
@Override
public void run() {
int len = 0, bufferIndex = 0;
try {
while (!Thread.interrupted()) {
bufferIndex = mMediaCodec.dequeueInputBuffer(-1);
inputBuffers[bufferIndex].clear();
len = mAudioRecord.read(inputBuffers[bufferIndex], bufferSize);
if (len == AudioRecord.ERROR_INVALID_OPERATION || len == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG,"An error occured with the AudioRecord API !");
} else {
mMediaCodec.queueInputBuffer(bufferIndex, 0, len, System.nanoTime()/1000, 0);
}
}
} catch (RuntimeException e) {}
Log.e(TAG,"Thread 1 over");
}
});

mThread2 = new Thread(new Runnable() {
@Override
public void run() {

byte[] buffer = new byte[128*1024];
try {
while (!Thread.interrupted()) {
int len = inputStream.read(buffer, 0, 128*1024);
Log.d(TAG,"len: "+len);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
Log.e(TAG,"Thread 2 over");
}
});

mThread2.start();
mThread.start();

try {
// mReceiver.getInputStream contains the data from the camera
// the mPacketizer encapsulates this stream in an RTP stream and send it over the network
mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
mPacketizer.setInputStream(inputStream);
mPacketizer.start();
mStreaming = true;
} catch (IOException e) {
stop();
throw new IOException("Something happened with the local sockets :/ Start failed !");
}

mStreaming = true;

}

/** Stops the stream. */
public synchronized void stop() {
if (mStreaming) {
if (mMode == MODE_MEDIACODEC_API) {
Log.d(TAG, "Interrupting threads...");
mThread.interrupt();
mThread2.interrupt();
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
super.stop();
}
}

/**
* Returns a description of the stream using SDP. It can then be included in an SDP file.
* Will fail if called when streaming.
Expand Down Expand Up @@ -159,7 +270,7 @@ private void testADTS() throws IllegalStateException, IOException {
mActualSamplingRate = Integer.valueOf(s[0]);
mConfig = Integer.valueOf(s[1]);
mChannel = Integer.valueOf(s[2]);
//return;
return;
}
}

Expand All @@ -170,10 +281,10 @@ private void testADTS() throws IllegalStateException, IOException {
}

// The structure of an ADTS packet is described here: http://wiki.multimedia.cx/index.php?title=ADTS

// ADTS header is 7 or 9 bytes long
byte[] buffer = new byte[9];

mMediaRecorder = new MediaRecorder();
mMediaRecorder.setAudioSource(mAudioSource);
mMediaRecorder.setOutputFormat(mOutputFormat);
Expand Down
5 changes: 5 additions & 0 deletions src/net/majorkernelpanic/streaming/audio/AMRNBStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ public String generateSessionDescription() {
"a=fmtp:96 octet-align=1;\r\n";
}

@Override
protected void encodeWithMediaCodec() throws IOException {
super.encodeWithMediaRecorder();
}

}
Loading

0 comments on commit 819481b

Please sign in to comment.