Java-очередь, массивы и JNI - PullRequest
1 голос
/ 26 июля 2010

Я думаю, что это общий вопрос Java, но он затрагивает некоторые аспекты Android.

Итак, вот в чем дело: у меня есть версия MPG123 для Android в JNI-оболочке, и я могу получать данные 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?Может быть, это какая-то странная ошибка с AndroidTrack Audio?

Спасибо!

-Гриф

РЕДАКТИРОВАТЬ - Я решил проблему.Кажется, это проблема с моей реализацией 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;
}

1 Ответ

2 голосов
/ 12 августа 2010

Похоже, роль вашей JNI-функции:

Передайте ему инициализированный буфер (т. Е. Объект короткого массива Java), и он будет заполнен следующим фрагментом выборок изmp3 декодер.

Для выполнения этой задачи она, кажется, реализована правильно.Функция никогда не создает экземпляр объекта java short array, и я не думаю, что этого следует ожидать.

Я дам описание проблемы, хотя, кажется, вы уже поняли, что происходит.

В вашем методе 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 за четыре секунды, то вам неизбежно понадобится много буферного пространства.Но, как правило, производительность лучше, если память выделяется один раз в начале и используется повторно, а не многократно динамически.

...