2013-05-21 1 views
9

Also, ich habe eine konzeptionelle Frage. Ich habe mit JNI für Android gearbeitet, um Low-Level-Audio-Dateien zu erstellen. Ich habe in C/C++ viel Audio-Kodierung gemacht, also dachte ich, dass das kein großes Problem wäre. Ich entschied mich, C++ in meinem "nativen" Code zu verwenden (denn wer liebt OOP nicht?). Das Problem, das mir begegnet ist, scheint (mir) seltsam zu sein: Wenn ich ein Objekt für die Verarbeitung von Audio im C++ - Code erstelle und dieses Objekt niemals an Java übergebe (oder andersherum), rufe Methoden darauf auf Objekt scheint die Garbage Collection ziemlich oft aufzurufen. Da dies innerhalb Audio-Rückrufe geschieht, ist das Ergebnis Stottern Audio, und ich erhalte häufig Nachrichten entlang der Linien von:Rufen C++ - Objekte in Android JNI-Code die Garbage Collection auf?

WAIT_FOR_CONCURRENT_GC blocked 23ms 

Wenn jedoch fahre ich die gleichen Operationen durch statische Funktionen zu schaffen (und nicht als Aufruf an dem Mitglied Methoden ein Memeber-Objekt) scheint die Leistung der App in Ordnung zu sein, und ich sehe die obige Log-Nachricht nicht mehr.

Gibt es einen Grund, eine statische Funktion aufzurufen, sollte eine bessere Leistung haben als den Aufruf von Member-Methoden für ein Member-Objekt im systemeigenen Code? Genauer gesagt, sind Member-Objekte oder eingeschränkte Scope-Variablen, die vollständig im nativen Code eines JNI-Projekts enthalten sind, das an der Garbage Collection beteiligt ist? Ist der C++ - Aufrufstack in GC involviert? Gibt es irgendeinen Einblick, den irgendjemand mir geben kann, wie C++ Speichermanagement Java-Speichermanagement trifft, wenn es um JNI-Programmierung geht? Das heißt, in dem Fall, dass ich keine Daten zwischen Java und C++ übergebe, beeinflusst die Art, wie ich C++ Code schreibe, die Java-Speicherverwaltung (GC oder anders)?

Lassen Sie mich versuchen, ein Beispiel zu geben. Halte mit mir, denn es ist verdammt lang, und wenn du denkst, dass du Einsicht hast, kannst du hier mit dem Lesen aufhören.

Ich habe ein paar Objekte. Eines, das für das Erstellen der Audio-Engine, das Initialisieren der Ausgabe usw. verantwortlich ist. Es heißt HelloAudioJNI (tut mir leid, dass ich keine kompilierbaren Beispiele gemacht habe, aber es gibt eine Menge Code).

class CHelloAudioJNI { 

    ... omitted members ... 

    //member object pointers 
    COscillator *osc; 
    CWaveShaper *waveShaper; 

    ... etc ... 

public: 
    //some methods 
    void init(float fs, int bufferSize, int channels); 

    ... blah blah blah ... 

Daraus folgt, dass ich ein paar mehr Klassen habe. Die WaveShaper Klasse sieht wie folgt aus:

class CWaveShaper : public CAudioFilter { 
protected: 
    double *coeffs; 
    unsigned int order;//order 
public: 
    CWaveShaper(const double sampleRate, const unsigned int numChannels, 
       double *coefficients, const unsigned int order); 

    double processSample(double input, unsigned int channel); 
    void reset(); 
}; 

Lassen Sie uns nicht für jetzt über die CAudioFilter Klasse kümmern, da dieses Beispiel schon ziemlich lang ist. Die WaveShaper CPP-Datei sieht wie folgt aus:

CWaveShaper::CWaveShaper(const double sampleRate, 
         const unsigned int numChannels, 
         double *coefficients, 
         const unsigned int numCoeffs) : 
    CAudioFilter(sampleRate,numChannels), coeffs(coefficients), order(numCoeffs) 
{} 

double CWaveShaper::processSample(double input, unsigned int channel) 
{ 
    double output = 0; 
    double pow = input; 

    //zeroth order polynomial: 
    output = pow * coeffs[0]; 

    //each additional iteration 
    for(int iteration = 1; iteration < order; iteration++){ 
     pow *= input; 
     output += pow * coeffs[iteration]; 
    } 

    return output; 
} 

void CWaveShaper::reset() {} 

und dann gibt es HelloAudioJNI.cpp. Hier kommen wir ins Thema. Ich eröffne die Mitglieder Objekte richtig, in der init-Funktion neu, so:

void CHelloAudioJNI::init(float samplerate, int bufferSize, int channels) 
{ 
    ... some omitted initialization code ... 

     //wave shaper numero uno 
    double coefficients[2] = {1.0/2.0, 3.0/2.0}; 
    waveShaper = new CWaveShaper(fs,outChannels,coefficients,2); 

    ... some more omitted code ... 
} 

Ok scheint alles so weit in Ordnung. Dann innerhalb des Audio-Rückruf wir wie so einige Member-Methoden auf dem Element-Objekt aufrufen:

void CHelloAudioJNI::processOutputBuffer() 
{ 
    //compute audio using COscillator object 
    for(int index = 0; index < outputBuffer.bufferLen; index++){ 
     for(int channel = 0; channel < outputBuffer.numChannels; channel++){ 
      double sample; 

      //synthesize 
      sample = osc->computeSample(channel); 
      //wave-shape 
      sample = waveShaper->processSample(sample,channel); 

      //convert to FXP and save to output buffer 
      short int outputSample = amplitude * sample * FLOAT_TO_SHORT; 
      outputBuffer.buffer[interleaveIndex(index,channel)] = outputSample; 
     } 
    } 
} 

Dies ist, was häufige Audio-Unterbrechungen und viele Nachrichten über Garbage Collection produziert. Allerdings, wenn ich kopieren Sie die CWaveShaper :: Process() Funktion zum HelloAudioJNI.cpp unmittelbar über dem Rückruf und nenne es direkt anstelle die Elementfunktion:

sample = waveShape(sample, coeff, 2); 

Dann bekomme ich schönen schönen Ton aus meinem Android kommt Gerät und ich bekomme keine so häufigen Nachrichten über Garbage Collection.Noch einmal sind die Fragen Mitgliederobjekte oder eingeschränkte Bereichsvariablen, die vollständig innerhalb des nativen Codes eines JNI-Projekts, das an der Garbage Collection beteiligt ist, leben? Ist der C++ - Aufrufstack in GC involviert? Gibt es irgendeinen Einblick, den irgendjemand mir geben kann, wie C++ Speichermanagement Java-Speichermanagement trifft, wenn es um JNI-Programmierung geht? Das heißt, in dem Fall, dass ich keine Daten zwischen Java und C++ übergebe, beeinflusst die Art, wie ich C++ Code schreibe, die Java-Speicherverwaltung (GC oder anders)?

Antwort

3

CHelloAudioJNI::init(...) speichert einen Zeiger auf eine Stapelvariable (double coefficients[2]) in WaveShaper. Wenn Sie auf waveShaper->coeffs zugreifen, nachdem die Koeffizienten den Gültigkeitsbereich verlassen haben, wird BadThings (tm) ausgeführt.

Erstellen Sie eine Kopie des Arrays in Ihrem CWaveShaper -Konstruktor (und vergessen Sie nicht, es in Ihrem Destruktor zu löschen). Oder verwenden Sie .

+0

Dies kann tangential sein, da Koeffizienten wie etwas klingen, das gelesen, aber nicht geschrieben würde. Daher sind die Werte beim Zugriff möglicherweise unbestimmt, aber es ist nicht sofort klar, dass eine Stapelkorruption auftreten würde. –

+0

@ChrisStratton, können Sie Ihren Kommentar erklären? Ich verstehe nicht, was du meinst. – xaviersjs

+0

Das hier angesprochene Problem sollte behoben werden, da die Werte der Koeffizienten, auf die von nicht mehr zugewiesenem Speicher zugegriffen wird, wahrscheinlich nicht korrekt sind. Aber wenn Sie später nicht versuchen, die Koeffizienten zu ändern, würde dies keinen fehlerhaften Programmablauf verursachen, was die einzige Erklärung für Ihre implizit durch diese Antwort angebotene Speicherbereinigung zu sein scheint. Wenn Sie nur die Koeffizienten lesen, die in einer Berechnung verwendet werden sollen, die für alle Eingaben gültig ist, dann ist "BadThings (tm)" auf "falsche Berechnungsergebnisse" beschränkt. –

5

Es gibt keine Beziehung zwischen C++ - Objekten und Dalviks Speicherbereinigung. Dalvik interessiert sich nicht für den Inhalt des nativen Heapspeichers, außer für seinen eigenen internen Speicher. Alle aus Java-Quellen erstellten Objekte befinden sich auf dem "verwalteten" Heap, wo die Garbage-Collection stattfindet.

Der Dalvik GC untersucht den nativen Stapel nicht; Jeder der VM bekannte Thread verfügt über einen separaten Stapel, den der Interpreter verwenden kann. Die einzige Möglichkeit, mit der C++ und verwaltete Objekte verknüpft sind, besteht darin, eine Beziehung durch Paarung von Objekten auf irgendeine Weise zu erstellen (z. B. Erstellen eines neuen verwalteten Objekts aus einem C++ - Konstruktor oder Löschen eines systemeigenen Objekts aus einem Java-Finalizer).

Sie können die Funktion "Allocation Tracker" von DDMS/ADT verwenden, um die zuletzt erstellten Objekte auf dem verwalteten Heap und von wo sie zugewiesen werden zu sehen. Wenn Sie das während des GC-Aufruhrs ausführen, sollten Sie in der Lage sein zu sagen, was es verursacht.

Auch die Logcat-Nachrichten zeigen die Prozess- und Thread-IDs (aus der Befehlszeile verwenden, adb logcat -v threadtime), die Sie überprüfen sollten, um sicherzustellen, dass die Nachrichten von Ihrer App kommen, und auch zu sehen, welche Thread die GC-Aktivität tritt auf. Sie können die Thread-Namen auf der Registerkarte "Threads" in DDMS/ADT sehen.