2010-07-26 32 views
1

我認爲這是一個普通的Java問題,但它涉及到一些Android的東西。Java隊列,數組和JNI

因此,這裏的協議:我有一個JNI包裝的MPG123 for Android版本,我可以從SD卡上的MP3文件中提取PCM數據。我想將這些數據大塊,使用我寫的另一個類對其進行分析,然後將其推入隊列。理想情況下,我希望音頻以4秒的延遲播放。我使用此代碼沒有延遲實現它:

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的一個問題。看起來,當我將數據從JNI級別複製到PCM中時,它爲每個推回到隊列中的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; 
} 
+0

嗨格里芬,你可以請分享jni包裝版的MPG123爲Android? ----謝謝,Swathi – 2011-01-24 11:14:20

+0

當然。我可以發送你的方式。給我發電子郵件 - griffyboy0 - at - gmail – Griffin 2011-01-29 20:56:27

回答

2

它看起來像你的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被調用,並且您可以通過一個或兩個pre-分配緩衝區。另一方面,如果你絕對需要提前4秒解碼mp3數據,那麼你將不可避免地需要大量的緩衝空間。但是通常情況下,如果內存在開始時分配一次,並且重複使用,而不是反覆動態分配,則性能會更好。

+0

我認爲這個總結了迄今爲止我能夠收集的內容。我將重做這段代碼,以防止每個樣本幀分配內存。從C調用的角度來看,這是一個關於Java的範式轉換,以及它如何管理其內存。它很好地知道,除非特別克隆,否則一切都是參考。感謝您的深刻解答! – Griffin 2010-08-17 05:17:13