Я хочу выполнить некоторую обработку аудиофайлов без их воспроизведения, только математика. У меня есть сомнения о том, правильно ли я это делаю, и есть несколько вопросов. Я читал примеры, но большинство из них о трансляции видео, и там вообще нет работы с сырыми данными.
Я подготовил mp3-файл, который имеет 2 идентичных канала, т.е. он стерео, но левый и правый каналы одинаковые. После декодировки я ожидал получить буфер с парами равных чисел, ведь PCM-16 хранит сэмплы каналов поочерёдно, как {L
R
L
R
L
R
...}, верно? Например:
{105
105
601
601
-243
-243
-484
-484
...}.
Однако, я получил пары близких чисел, но не равных:
{-308
-264
-1628
-1667
-2568
-2550
-4396
-4389
}
Алгоритмы mp3 кодируют одни и те же значения по-разному? Или в чём причина?
Я хочу обрабатывать данные пачками по 1024 сэмплов. Если для очередной пачки будет недостаточно данных, я хочу сохранить остаток до следующей пачки сырых данных. Есть ли гарантия, что порядок при этом будет сохранён?
Я привык понимать "сэмпл" как каджое единичное значение аудиоданных. Здесь я вижу методы MediaExtractor::readSampleData
и MediaExtractor::advance
. Первый возвращает ~2000 значений, в описании второго сказано "Advance to the next sample". Это просто совпадение наименований? Я видел пару примеров, где эти методы вызываются в паре в цикле. Правильно ли моё использование?
Вот код:
public static void foo(String filepath) throws IOException {
final int SAMPLES_PER_CHUNK = 1024;
MediaExtractor mediaExtractor = new MediaExtractor();
mediaExtractor.setDataSource(filepath);
MediaFormat mediaFormat = mediaExtractor.getTrackFormat(0);
mediaExtractor.release();
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
mediaFormat.setString(MediaFormat.KEY_FRAME_RATE, null);
String codecName = mediaCodecList.findDecoderForFormat(mediaFormat);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 0); // MediaCodec crashes with JNI
// error if FRAME_RATE is null
MediaCodec mediaCodec = MediaCodec.createByCodecName(codecName);
mediaCodec.setCallback(new MediaCodec.Callback() {
private MediaExtractor mExtractor;
private short[] mExcess;
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
if (mExtractor == null) {
mExtractor = new MediaExtractor();
try {
mExtractor.setDataSource(filepath);
mExtractor.selectTrack(0);
} catch (IOException e) {
e.printStackTrace();
}
mExcess = new short[0];
}
ByteBuffer in = codec.getInputBuffer(index);
in.clear();
int sampleSize = mExtractor.readSampleData(in, 0);
if (sampleSize > 0) {
boolean isOver = !mExtractor.advance();
codec.queueInputBuffer(
index,
0,
sampleSize,
mExtractor.getSampleTime(),
isOver ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
} else {
int helloAmaBreakpoint = 1;
}
}
@Override
public void onOutputBufferAvailable(
MediaCodec codec,
int index,
MediaCodec.BufferInfo info) {
ByteBuffer tmp = codec.getOutputBuffer(index);
if (tmp.limit() == 0) return;
ShortBuffer out = tmp.order(ByteOrder.nativeOrder()).asShortBuffer();
// Prepend the remainder from previous batch to the new data
short[] buf = new short[mExcess.length + out.limit()];
System.arraycopy(mExcess, 0, buf, 0, mExcess.length);
out.get(buf, mExcess.length, out.limit());
final int channelCount
= codec.getOutputFormat().getInteger(MediaFormat.KEY_CHANNEL_COUNT);
for (
int k = 0;
k + SAMPLES_PER_CHUNK * channelCount < buf.length;
k += SAMPLES_PER_CHUNK * channelCount) {
double[] x = new double[SAMPLES_PER_CHUNK]; // left channel
double[] y = new double[SAMPLES_PER_CHUNK]; // right channel
switch (channelCount) {
case 1: // if 1 channel then make 2 identical arrays
for (
int offset = 0;
offset + SAMPLES_PER_CHUNK < buf.length;
offset += SAMPLES_PER_CHUNK) {
for (int i = 0; i < SAMPLES_PER_CHUNK; ++i) {
x[i] = (double) buf[offset + i];
y[i] = (double) buf[offset + i];
}
}
break;
case 2: // if 2 channels then read values alternately
for (
int offset = 0;
offset + SAMPLES_PER_CHUNK * 2 < buf.length;
offset += SAMPLES_PER_CHUNK * 2) {
for (int i = 0; i < SAMPLES_PER_CHUNK; ++i) {
x[i] = (double) buf[offset + i * 2];
y[i] = (double) buf[offset + i * 2 + 1];
}
}
break;
default:
throw new IllegalStateException("No algorithm for " + channelCount + " channels");
}
/// ... some processing ... ///
}
// Save the rest until next batch of raw data
int samplesLeft = buf.length % (SAMPLES_PER_CHUNK * channelCount);
mExcess = new short[samplesLeft];
System.arraycopy(
buf,
buf.length - samplesLeft,
mExcess,
0,
samplesLeft);
codec.releaseOutputBuffer(index, false);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) > 0) {
codec.stop();
codec.release();
mExtractor.release();
}
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
}
});
mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT);
mediaCodec.configure(mediaFormat, null, null, 0);
mediaCodec.start();
}
Быстрый code review также приветствуется.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
не пойму как присобачить "Полкана" к "большому псу" сохраняя вызов конструктора предкав остальном всё работает как надо
господаЕсть такое задание: разработать программу, изображающую на экране работающие электронные часы с цифровым индикатором, а также с индикацией...
Mozilla декодирует закодированный url в адресной строкеИз-за чего получаю bad request, т
Есть ли способ заставить Классы в TypeScript компилироваться в Фабричные Функции, где приватные свойства и методы не будут попадать в прототип?...