Back to project page streamvid.
The source code is released under:
GNU General Public License
If you think the Android project streamvid listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * Copyright (C) 2011-2014 GUIGUI Simon, fyhertz@gmail.com * // w ww.jav a 2s. co m * This file is part of libstreaming (https://github.com/fyhertz/libstreaming) * * Spydroid is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this source code; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package net.majorkernelpanic.streaming.audio; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Field; import java.net.InetAddress; import java.nio.ByteBuffer; import net.majorkernelpanic.streaming.SessionBuilder; 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.Build; import android.os.Environment; import android.service.textservice.SpellCheckerService.Session; import android.util.Log; /** * A class for streaming AAC from the camera of an android device using RTP. * You should use a {@link Session} instantiated with {@link SessionBuilder} instead of using this class directly. * Call {@link #setDestinationAddress(InetAddress)}, {@link #setDestinationPorts(int)} and {@link #setAudioQuality(AudioQuality)} * to configure the stream. You can then call {@link #start()} to start the RTP stream. * Call {@link #stop()} to stop the stream. */ public class AACStream extends AudioStream { public final static String TAG = "AACStream"; /** MPEG-4 Audio Object Types supported by ADTS. **/ private static final String[] AUDIO_OBJECT_TYPES = { "NULL", // 0 "AAC Main", // 1 "AAC LC (Low Complexity)", // 2 "AAC SSR (Scalable Sample Rate)", // 3 "AAC LTP (Long Term Prediction)" // 4 }; /** There are 13 supported frequencies by ADTS. **/ public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0 88200, // 1 64000, // 2 48000, // 3 44100, // 4 32000, // 5 24000, // 6 22050, // 7 16000, // 8 12000, // 9 11025, // 10 8000, // 11 7350, // 12 -1, // 13 -1, // 14 -1, // 15 }; private String mSessionDescription = null; private int mProfile, mSamplingRateIndex, mChannel, mConfig; private SharedPreferences mSettings = null; private AudioRecord mAudioRecord = null; private Thread mThread = null; public AACStream() { super(); if (!AACStreamingSupported()) { Log.e(TAG,"AAC not supported on this phone"); throw new RuntimeException("AAC not supported by this phone !"); } else { Log.d(TAG,"AAC supported on this phone"); } } private static boolean AACStreamingSupported() { if (Build.VERSION.SDK_INT<14) return false; try { MediaRecorder.OutputFormat.class.getField("AAC_ADTS"); return true; } catch (Exception e) { return false; } } /** * Some data (the actual sampling rate used by the phone and the AAC profile) needs to be stored once {@link #getSessionDescription()} is called. * @param prefs The SharedPreferences that will be used to store the sampling rate */ public void setPreferences(SharedPreferences prefs) { mSettings = prefs; } @Override public synchronized void start() throws IllegalStateException, IOException { configure(); if (!mStreaming) { super.start(); } } public synchronized void configure() throws IllegalStateException, IOException { super.configure(); mQuality = mRequestedQuality.clone(); // Checks if the user has supplied an exotic sampling rate int i=0; for (;i<AUDIO_SAMPLING_RATES.length;i++) { if (AUDIO_SAMPLING_RATES[i] == mQuality.samplingRate) { mSamplingRateIndex = i; break; } } // If he did, we force a reasonable one: 16 kHz if (i>12) mQuality.samplingRate = 16000; if (mMode != mRequestedMode || mPacketizer==null) { mMode = mRequestedMode; if (mMode == MODE_MEDIARECORDER_API) { mPacketizer = new AACADTSPacketizer(); } else { mPacketizer = new AACLATMPacketizer(); } } if (mMode == MODE_MEDIARECORDER_API) { testADTS(); // All the MIME types parameters used here are described in RFC 3640 // SizeLength: 13 bits will be enough because ADTS uses 13 bits for frame length // config: contains the object type + the sampling rate + the channel number // TODO: streamType always 5 ? profile-level-id always 15 ? mSessionDescription = "m=audio "+String.valueOf(getDestinationPorts()[0])+" RTP/AVP 96\r\n" + "a=rtpmap:96 mpeg4-generic/"+mQuality.samplingRate+"\r\n"+ "a=fmtp:96 streamtype=5; profile-level-id=15; mode=AAC-hbr; config="+Integer.toHexString(mConfig)+"; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n"; } else { mProfile = 2; // AAC LC mChannel = 1; mConfig = mProfile<<11 | mSamplingRateIndex<<7 | mChannel<<3; mSessionDescription = "m=audio "+String.valueOf(getDestinationPorts()[0])+" RTP/AVP 96\r\n" + "a=rtpmap:96 mpeg4-generic/"+mQuality.samplingRate+"\r\n"+ "a=fmtp:96 streamtype=5; profile-level-id=15; mode=AAC-hbr; config="+Integer.toHexString(mConfig)+"; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n"; } } @Override protected void encodeWithMediaRecorder() throws IOException { testADTS(); ((AACADTSPacketizer)mPacketizer).setSamplingRate(mQuality.samplingRate); super.encodeWithMediaRecorder(); } @Override @SuppressLint({ "InlinedApi", "NewApi" }) protected void encodeWithMediaCodec() throws IOException { final int bufferSize = AudioRecord.getMinBufferSize(mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)*2; ((AACLATMPacketizer)mPacketizer).setSamplingRate(mQuality.samplingRate); mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 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.AACObjectLC); format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize); mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mAudioRecord.startRecording(); mMediaCodec.start(); final MediaCodecInputStream 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(10000); if (bufferIndex>=0) { 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 { //Log.v(TAG,"Pushing raw audio to the decoder: len="+len+" bs: "+inputBuffers[bufferIndex].capacity()); mMediaCodec.queueInputBuffer(bufferIndex, 0, len, System.nanoTime()/1000, 0); } } } } catch (RuntimeException e) { e.printStackTrace(); } } }); mThread.start(); // The packetizer 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; } /** Stops the stream. */ public synchronized void stop() { if (mStreaming) { if (mMode==MODE_MEDIACODEC_API) { Log.d(TAG, "Interrupting threads..."); mThread.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. */ public String getSessionDescription() throws IllegalStateException { if (mSessionDescription == null) throw new IllegalStateException("You need to call configure() first !"); return mSessionDescription; } /** * Records a short sample of AAC ADTS from the microphone to find out what the sampling rate really is * On some phone indeed, no error will be reported if the sampling rate used differs from the * one selected with setAudioSamplingRate * @throws IOException * @throws IllegalStateException */ @SuppressLint("InlinedApi") private void testADTS() throws IllegalStateException, IOException { setAudioEncoder(MediaRecorder.AudioEncoder.AAC); try { Field name = MediaRecorder.OutputFormat.class.getField("AAC_ADTS"); setOutputFormat(name.getInt(null)); } catch (Exception ignore) { setOutputFormat(6); } String key = PREF_PREFIX+"aac-"+mQuality.samplingRate; if (mSettings!=null) { if (mSettings.contains(key)) { String[] s = mSettings.getString(key, "").split(","); mQuality.samplingRate = Integer.valueOf(s[0]); mConfig = Integer.valueOf(s[1]); mChannel = Integer.valueOf(s[2]); return; } } final String TESTFILE = Environment.getExternalStorageDirectory().getPath()+"/spydroid-test.adts"; if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { throw new IllegalStateException("No external storage or external storage not ready !"); } // 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); mMediaRecorder.setAudioEncoder(mAudioEncoder); mMediaRecorder.setAudioChannels(1); mMediaRecorder.setAudioSamplingRate(mQuality.samplingRate); mMediaRecorder.setAudioEncodingBitRate(mQuality.bitRate); mMediaRecorder.setOutputFile(TESTFILE); mMediaRecorder.setMaxDuration(1000); mMediaRecorder.prepare(); mMediaRecorder.start(); // We record for 1 sec // TODO: use the MediaRecorder.OnInfoListener try { Thread.sleep(2000); } catch (InterruptedException e) {} mMediaRecorder.stop(); mMediaRecorder.release(); mMediaRecorder = null; File file = new File(TESTFILE); RandomAccessFile raf = new RandomAccessFile(file, "r"); // ADTS packets start with a sync word: 12bits set to 1 while (true) { if ( (raf.readByte()&0xFF) == 0xFF ) { buffer[0] = raf.readByte(); if ( (buffer[0]&0xF0) == 0xF0) break; } } raf.read(buffer,1,5); mSamplingRateIndex = (buffer[1]&0x3C)>>2 ; mProfile = ( (buffer[1]&0xC0) >> 6 ) + 1 ; mChannel = (buffer[1]&0x01) << 2 | (buffer[2]&0xC0) >> 6 ; mQuality.samplingRate = AUDIO_SAMPLING_RATES[mSamplingRateIndex]; // 5 bits for the object type / 4 bits for the sampling rate / 4 bits for the channel / padding mConfig = mProfile<<11 | mSamplingRateIndex<<7 | mChannel<<3; Log.i(TAG,"MPEG VERSION: " + ( (buffer[0]&0x08) >> 3 ) ); Log.i(TAG,"PROTECTION: " + (buffer[0]&0x01) ); Log.i(TAG,"PROFILE: " + AUDIO_OBJECT_TYPES[ mProfile ] ); Log.i(TAG,"SAMPLING FREQUENCY: " + mQuality.samplingRate ); Log.i(TAG,"CHANNEL: " + mChannel ); raf.close(); if (mSettings!=null) { Editor editor = mSettings.edit(); editor.putString(key, mQuality.samplingRate+","+mConfig+","+mChannel); editor.commit(); } if (!file.delete()) Log.e(TAG,"Temp file could not be erased"); } }