add mka support

This commit is contained in:
Alexey Kuznetsov 2017-02-14 15:01:05 +03:00
commit a1212bd408
12 changed files with 250 additions and 22 deletions

View file

@ -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')
}

View file

@ -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);

View file

@ -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));

View file

@ -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

View file

@ -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();
}

View file

@ -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;

View file

@ -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);
}
}
});

View 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;
}
}

View file

@ -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();
}

View file

@ -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">

View file

@ -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">

View file

@ -1 +1 @@
include ':app', ':android-library'
include ':app', ':android-library', ':jebml'