Я думаю, что это - общий вопрос о Java, но он действительно включает некоторый материал Android.
Таким образом, вот соглашение: у меня есть перенесенная версия JNI MPG123 для Android, и я могу вытянуть данные PCM из файла MP3 на SDCard. Я хотел бы вытянуть блок эти данные, сделать анализ его с помощью другого класса, который я записал, затем продвиньте его в очередь. Идеально, я хотел бы, чтобы аудио воспроизвело с четырехсекундной задержкой. Я достиг его без задержки с помощью этого кода:
public void run()
{
// The actual thread where the PCM data gets processed and analyzed
short[] pcm = new short[ 4096 ];
short[] zero = new short[ 4096 ];
Queue<short[]> buffer = new LinkedList<short[]>();
// Fill the zero buffer
for( int i = 0; i < 4096; i++ )
zero[i] = 0;
// Push back 4 seconds of silence - Note this commented section
//for( int i = 0; i < 43; i++ )
// buffer.add( zero );
// Analyze the whole file
while( !isInterrupted() )
{
// Grab and analyze data, add events
int ret = audio.GetPCM( pcm );
//Log.d( "AudioThread", "GetPCM returns: " + ret );
if( ret != MPG123.MPG123_DONE )
{
// Process the PCM data
if( bd.Process( pcm ) != BeatDetection.AUDALYSIS_OK )
Log.v( "AudioThread", "Beat Detection Error!" );
// Add the data to the PCM buffer
buffer.add( pcm );
}
// Play the audio
short[] data = buffer.poll();
if( data != null ) // If we have data left in the buffer
at.write( data, 0, 4096 ); // Write it to the audio track
else
break; // Otherwise we need to exit the loop
Log.d( "AudioThread", "BufferSize= " + buffer.size() );
}
// Debug message
Log.d( "AudioThread", "Exiting Thread." );
// Clean stuff up
bd.Cleanup();
audio.Cleanup();
at.stop();
at.release();
}
Тем не менее, когда я пытаюсь ввести задержку в свою очередь путем добавления "4 секунд" ценность 0 в очередь (Отметьте прокомментированный блок), мои музыкальные игры сразу же, но запускает 4 секунды в песню и играет в в реальном времени, пока очередь не начинает пустеть, где она играет тот же блок много раз до убегающий из цикла. Почти кажется, как будто очередь действует как очередь "Первым пришел - последним вышел", а не очередь "Метода"первым пришел - первым вышел"".
Возможно, я не использую Очередь Java правильно? Возможно, это - некоторая нечетная ошибка с android AudioTrack?
Спасибо!
- Griff
РЕДАКТИРОВАНИЕ - я решил проблему. Это, кажется, проблема с моей реализацией JNI GetPCM. Кажется, что, когда я скопировал данные в PCM от уровня JNI, это установило те же данные для каждого экземпляра PCM, пододвинутой обратно в очередь, установив каждую запись PCM в очереди к тому же кадру данных PCM. Я решил проблему путем изменения:
buffer.add( pcm );
кому:
buffer.add( pcm.clone() );
Новый Вопрос: это нормальное поведение Java? Я реализовывал функцию JNI неправильно?
JNIEXPORT jint JNICALL Java_com_motalenbubble_projectlucid_MPG123_GetPCM(
JNIEnv* env,
jobject obj,
jshortArray data ) // jshortarray already has a size attribute
{
// Call mpg123_read
jint err = MPG123_OK, length;
jshort *pcm;
jsize len = (*env)->GetArrayLength( env, data );
pcm = (*env)->GetShortArrayElements( env, data, NULL );
err = mpg123_read( mh, (unsigned char*)pcm, len * sizeof( short ), &length ); // Read length is in bytes - we need in shorts.
(*env)->ReleaseShortArrayElements( env, data, pcm, 0 );
// Annoying logging
//dprintf(0, "read(%d): %s\n", length, err == MPG123_ERR ? mpg123_strerror(mh) : mpg123_plain_strerror(err));
return err;
}
Похоже, роль вашей JNI-функции заключается в следующем:
Передайте ей инициализированный буфер (т.е. объект короткого массива java), и он будет заполнен следующей порцией выборок из декодер mp3.
Для выполнения этой задачи, похоже, она реализована правильно. Функция никогда не создает экземпляр объекта короткого массива java, и я не думаю, что этого следует ожидать.
Я дам описание проблемы, хотя, кажется, вы уже поняли, что происходит.
В методе run () для переменной pcm
вы создаете экземпляр короткого массива один раз. Каждый раз, когда вы вызываете audio.GetPCM (pcm)
, вы передаете ему один и тот же объект массива. А затем, когда вы вызываете buffer.add (pcm)
, он всегда ставит в очередь ссылку на тот же массив . Итак, если вы вызовете GetPCM, а затем снова вызовете GetPCM до того, как вы записали кадр на аудиоустройство, GetPCM катастрофически перезапишет массив.
Вы решили проблему с помощью buffer.add (pcm.clone ())
, потому что он всегда выделяет новый массив и добавляет ссылку на этот новый массив в очередь.GetPCM по-прежнему перезаписывает один и тот же массив ( pcm
) каждый раз, но это не проблема, потому что очередь содержит ссылки на несвязанные массивы, которые были созданы как копии предыдущего содержимого массива pcm
.
Проблема на самом деле не связана с Android или JNI; GetPCM можно также реализовать на чистой Java, и возникнет такая же проблема.
Использование pcm.clone () - безопасное решение, но оно может быть не самым эффективным, поскольку требует создания экземпляра нового массива для каждого кадра.
Если вам не нужно заранее декодировать данные mp3, вы должны иметь возможность разработать механизм, который предотвращает вызов GetPCM до тех пор, пока очередь не станет пустой, и вы можете обойтись одним или двумя заранее выделенными буферы.
С другой стороны, если вам абсолютно необходимо декодировать данные mp3 за четыре секунды до этого, вам неизбежно потребуется много места в буфере. Но в целом производительность лучше, если память выделяется один раз вначале и используется повторно, а не динамически выделяется повторно.