add mka support
This commit is contained in:
parent
d3d80b62bb
commit
a1212bd408
12 changed files with 250 additions and 22 deletions
|
|
@ -44,4 +44,5 @@ dependencies {
|
|||
compile 'com.google.android.gms:play-services-appindexing:9.8.0'
|
||||
compile 'org.apache.commons:commons-math3:3.6.1'
|
||||
compile 'com.github.axet:android-library:1.8.2' // compile project(':android-library')
|
||||
compile 'com.github.axet:jebml:0.0.1' // compile project(':jebml')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import com.github.axet.audiorecorder.encoders.EncoderInfo;
|
|||
import com.github.axet.audiorecorder.encoders.FileEncoder;
|
||||
import com.github.axet.audiorecorder.encoders.Format3GP;
|
||||
import com.github.axet.audiorecorder.encoders.FormatM4A;
|
||||
import com.github.axet.audiorecorder.encoders.FormatMKA;
|
||||
import com.github.axet.audiorecorder.encoders.FormatWAV;
|
||||
import com.github.axet.audiorecorder.services.RecordingService;
|
||||
import com.github.axet.audiorecorder.widgets.PitchView;
|
||||
|
|
@ -794,6 +795,9 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
if (ext.equals("3gp")) {
|
||||
e = new Format3GP(info, out);
|
||||
}
|
||||
if (ext.equals("mka")) {
|
||||
e = new FormatMKA(info, out);
|
||||
}
|
||||
|
||||
encoder = new FileEncoder(this, in, e);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
|
|
@ -30,6 +29,9 @@ import com.github.axet.audiorecorder.R;
|
|||
import com.github.axet.audiorecorder.app.MainApplication;
|
||||
import com.github.axet.audiorecorder.encoders.MuxerMP4;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -46,6 +48,13 @@ import java.util.Map;
|
|||
*/
|
||||
public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
public static <T> T[] removeElement(Class<T> c, T[] aa, int i) {
|
||||
List<T> ll = Arrays.asList(aa);
|
||||
ll = new ArrayList<>(ll);
|
||||
ll.remove(i);
|
||||
return ll.toArray((T[]) Array.newInstance(c, ll.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* A preference value change listener that updates the preference's summary
|
||||
* to reflect its new value.
|
||||
|
|
@ -262,16 +271,37 @@ public class SettingsActivity extends AppCompatPreferenceActivity implements Sha
|
|||
bindPreferenceSummaryToValue(findPreference(MainApplication.PREFERENCE_STORAGE));
|
||||
}
|
||||
|
||||
Preference enc = findPreference(MainApplication.PREFERENCE_ENCODING);
|
||||
ListPreference enc = (ListPreference) findPreference(MainApplication.PREFERENCE_ENCODING);
|
||||
|
||||
if (Build.VERSION.SDK_INT < 18) { // depends on MediaMuxer
|
||||
if (Build.VERSION.SDK_INT < 16) { // Android 4.1
|
||||
getPreferenceScreen().removePreference(enc);
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT < 18) { // MediaMuxer
|
||||
String v = enc.getValue();
|
||||
int i = enc.findIndexOfValue("m4a");
|
||||
CharSequence[] ee = enc.getEntries();
|
||||
CharSequence[] vv = enc.getEntryValues();
|
||||
ee = removeElement(CharSequence.class, ee, i);
|
||||
vv = removeElement(CharSequence.class, vv, i);
|
||||
enc.setEntries(ee);
|
||||
enc.setEntryValues(vv);
|
||||
enc.setValueIndex(0);
|
||||
i = enc.findIndexOfValue(v);
|
||||
if (i == -1) {
|
||||
enc.setValue("wav");
|
||||
enc.setValueIndex(0);
|
||||
} else {
|
||||
enc.setValueIndex(i);
|
||||
enc.setValue(v);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, MediaCodecInfo> mime = MuxerMP4.findEncoder("audio/mp4");
|
||||
if (mime.isEmpty())
|
||||
getPreferenceScreen().removePreference(enc);
|
||||
else
|
||||
bindPreferenceSummaryToValue(enc);
|
||||
else {
|
||||
//bindPreferenceSummaryToValue(enc);
|
||||
}
|
||||
}
|
||||
|
||||
bindPreferenceSummaryToValue(findPreference(MainApplication.PREFERENCE_RATE));
|
||||
|
|
|
|||
|
|
@ -197,7 +197,20 @@ public class Storage {
|
|||
int rate = Integer.parseInt(shared.getString(MainApplication.PREFERENCE_RATE, ""));
|
||||
String ext = shared.getString(MainApplication.PREFERENCE_ENCODING, "");
|
||||
|
||||
if (ext.equals("m4a")) {
|
||||
if (ext.equals("m4a") || ext.equals("mka")) {
|
||||
long y1 = 365723; // one minute sample 16000Hz
|
||||
long x1 = 16000; // at 16000
|
||||
long y2 = 493743; // one minute sample
|
||||
long x2 = 44000; // at 44000
|
||||
long x = rate;
|
||||
long y = (x - x1) * (y2 - y1) / (x2 - x1) + y1;
|
||||
|
||||
int m = MainApplication.getChannels(context);
|
||||
long perSec = (y / 60) * m;
|
||||
return free / perSec * 1000;
|
||||
}
|
||||
|
||||
if (ext.equals("mka")) {
|
||||
long y1 = 365723; // one minute sample 16000Hz
|
||||
long x1 = 16000; // at 16000
|
||||
long y2 = 493743; // one minute sample
|
||||
|
|
|
|||
|
|
@ -3,7 +3,5 @@ package com.github.axet.audiorecorder.encoders;
|
|||
public interface Encoder {
|
||||
void encode(short[] buf, int len);
|
||||
|
||||
void end(); // flush stream. may throw state exceptions
|
||||
|
||||
void close(); // release native resources, sholud not throw exceptions
|
||||
void close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
package com.github.axet.audiorecorder.encoders;
|
||||
|
||||
/**
|
||||
* Created by axet on 11/03/16.
|
||||
*/
|
||||
public class EncoderInfo {
|
||||
public int channels;
|
||||
public int sampleRate;
|
||||
|
|
|
|||
|
|
@ -47,9 +47,7 @@ public class FileEncoder {
|
|||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int len = rs.read(buf);
|
||||
if (len <= 0) {
|
||||
encoder.end();
|
||||
handler.post(done);
|
||||
return;
|
||||
break;
|
||||
} else {
|
||||
encoder.encode(buf, len);
|
||||
handler.post(progress);
|
||||
|
|
@ -58,15 +56,15 @@ public class FileEncoder {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Exception", e);
|
||||
t = e;
|
||||
handler.post(error);
|
||||
} finally {
|
||||
encoder.close();
|
||||
if (rs != null) {
|
||||
rs.close();
|
||||
}
|
||||
handler.post(done);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Exception", e);
|
||||
t = e;
|
||||
handler.post(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
183
app/src/main/java/com/github/axet/audiorecorder/encoders/FormatMKA.java
Executable file
183
app/src/main/java/com/github/axet/audiorecorder/encoders/FormatMKA.java
Executable file
|
|
@ -0,0 +1,183 @@
|
|||
package com.github.axet.audiorecorder.encoders;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
|
||||
import org.ebml.io.FileDataWriter;
|
||||
import org.ebml.matroska.MatroskaFileFrame;
|
||||
import org.ebml.matroska.MatroskaFileTrack;
|
||||
import org.ebml.matroska.MatroskaFileWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@TargetApi(16) // mp4/aac codec
|
||||
public class FormatMKA implements Encoder {
|
||||
EncoderInfo info;
|
||||
MediaCodec encoder;
|
||||
long NumSamples;
|
||||
ByteBuffer input;
|
||||
int inputIndex;
|
||||
MatroskaFileWriter writer;
|
||||
MatroskaFileTrack track;
|
||||
MatroskaFileTrack.MatroskaAudioTrack audio;
|
||||
|
||||
MatroskaFileFrame old;
|
||||
|
||||
public FormatMKA(EncoderInfo info, File out) {
|
||||
MediaFormat format = new MediaFormat();
|
||||
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
|
||||
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, info.sampleRate);
|
||||
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, info.channels);
|
||||
format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1024);
|
||||
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE);
|
||||
format.setInteger(MediaFormat.KEY_AAC_SBR_MODE, 0);
|
||||
create(info, format, out);
|
||||
}
|
||||
|
||||
public void create(EncoderInfo info, MediaFormat format, File out) {
|
||||
this.info = info;
|
||||
try {
|
||||
encoder = MediaCodec.createEncoderByType(format.getString(MediaFormat.KEY_MIME));
|
||||
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
encoder.start();
|
||||
writer = new MatroskaFileWriter(new FileDataWriter(out.getAbsolutePath()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(short[] buf, int len) {
|
||||
for (int offset = 0; offset < len; offset++) {
|
||||
if (input == null) {
|
||||
inputIndex = encoder.dequeueInputBuffer(-1);
|
||||
if (inputIndex < 0)
|
||||
throw new RuntimeException("unable to open encoder input buffer");
|
||||
if (Build.VERSION.SDK_INT >= 21)
|
||||
input = encoder.getInputBuffer(inputIndex);
|
||||
else
|
||||
input = encoder.getInputBuffers()[inputIndex];
|
||||
input.clear();
|
||||
}
|
||||
input.putShort(buf[offset]);
|
||||
if (!input.hasRemaining()) {
|
||||
queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void queue() {
|
||||
if (input == null)
|
||||
return;
|
||||
encoder.queueInputBuffer(inputIndex, 0, input.position(), getCurrentTimeStamp(), 0);
|
||||
NumSamples += input.position() / info.channels / (Short.SIZE / 8);
|
||||
input = null;
|
||||
while (encode())
|
||||
;// do encode()
|
||||
}
|
||||
|
||||
public static ByteBuffer clone(ByteBuffer original) {
|
||||
ByteBuffer clone = ByteBuffer.allocate(original.capacity());
|
||||
original.rewind();//copy from the beginning
|
||||
clone.put(original);
|
||||
original.rewind();
|
||||
clone.flip();
|
||||
return clone;
|
||||
}
|
||||
|
||||
boolean encode() {
|
||||
MediaCodec.BufferInfo outputInfo = new MediaCodec.BufferInfo();
|
||||
int outputIndex = encoder.dequeueOutputBuffer(outputInfo, 0);
|
||||
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
|
||||
return false;
|
||||
|
||||
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
audio = new MatroskaFileTrack.MatroskaAudioTrack();
|
||||
audio.setSamplingFrequency(info.sampleRate);
|
||||
audio.setOutputSamplingFrequency(info.sampleRate);
|
||||
audio.setBitDepth(info.bps);
|
||||
audio.setChannels((short) info.channels);
|
||||
track = new MatroskaFileTrack();
|
||||
track.setCodecID("A_AAC");
|
||||
track.setAudio(audio);
|
||||
track.setTrackType(MatroskaFileTrack.TrackType.AUDIO);
|
||||
writer.addTrack(track);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (outputIndex >= 0) {
|
||||
ByteBuffer output;
|
||||
if (Build.VERSION.SDK_INT >= 21)
|
||||
output = encoder.getOutputBuffer(outputIndex);
|
||||
else
|
||||
output = encoder.getOutputBuffers()[outputIndex];
|
||||
output.position(outputInfo.offset);
|
||||
output.limit(outputInfo.offset + outputInfo.size);
|
||||
old(outputInfo.presentationTimeUs / 1000);
|
||||
if ((outputInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
|
||||
track.setCodecPrivate(clone(output));
|
||||
writer.flush();
|
||||
encoder.releaseOutputBuffer(outputIndex, false);
|
||||
} else {
|
||||
MatroskaFileFrame frame = new MatroskaFileFrame();
|
||||
frame.setKeyFrame((outputInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) == MediaCodec.BUFFER_FLAG_KEY_FRAME);
|
||||
frame.setTimecode(outputInfo.presentationTimeUs / 1000);
|
||||
frame.setTrackNo(track.getTrackNo());
|
||||
frame.setData(clone(output));
|
||||
encoder.releaseOutputBuffer(outputIndex, false);
|
||||
old = frame;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void old(long cur) {
|
||||
if (old != null) {
|
||||
old.setDuration(cur - old.getTimecode());
|
||||
writer.addFrame(old);
|
||||
writer.flush();
|
||||
old = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
end();
|
||||
encoder.release();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
long getCurrentTimeStamp() {
|
||||
return NumSamples * 1000 * 1000 / info.sampleRate;
|
||||
}
|
||||
|
||||
public void end() {
|
||||
if (input != null) {
|
||||
queue();
|
||||
}
|
||||
int inputIndex = encoder.dequeueInputBuffer(-1);
|
||||
if (inputIndex >= 0) {
|
||||
ByteBuffer input;
|
||||
if (Build.VERSION.SDK_INT >= 21)
|
||||
input = encoder.getInputBuffer(inputIndex);
|
||||
else
|
||||
input = encoder.getInputBuffers()[inputIndex];
|
||||
input.clear();
|
||||
encoder.queueInputBuffer(inputIndex, 0, 0, getCurrentTimeStamp(), MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||
}
|
||||
while (encode())
|
||||
;// do encode()
|
||||
old(getCurrentTimeStamp() / 1000);
|
||||
writer.setDuration(getCurrentTimeStamp() / 1000);
|
||||
encoder.stop();
|
||||
}
|
||||
|
||||
public EncoderInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ public class MuxerMP4 implements Encoder {
|
|||
if (input == null)
|
||||
return;
|
||||
encoder.queueInputBuffer(inputIndex, 0, input.position(), getCurrentTimeStamp(), 0);
|
||||
NumSamples += input.position() / info.channels;
|
||||
NumSamples += input.position() / info.channels / (Short.SIZE / 8);
|
||||
input = null;
|
||||
while (encode())
|
||||
;// do encode()
|
||||
|
|
@ -147,6 +147,7 @@ public class MuxerMP4 implements Encoder {
|
|||
}
|
||||
|
||||
public void close() {
|
||||
end();
|
||||
encoder.release();
|
||||
muxer.release();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
<string-array name="encodings_text">
|
||||
<item>.wav (по умолчанию)</item>
|
||||
<item>.m4a</item>
|
||||
<item>.mka</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="channels_text">
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@
|
|||
<string-array name="encodings_text">
|
||||
<item>.wav (default)</item>
|
||||
<item>.m4a</item>
|
||||
<item>.mka</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="encodings_values" translatable="false">
|
||||
<item>wav</item>
|
||||
<item>m4a</item>
|
||||
<item>mka</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="themes_text">
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
include ':app', ':android-library'
|
||||
include ':app', ':android-library', ':jebml'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue